Commit 617c38bb authored by bgm's avatar bgm Committed by Aegir user

Update Stripe extension to 5.0

parent cca46ba3
<?php
/*
* Form Class for Stripe
*/
class CRM_Core_Form_Stripe extends CRM_Core_Form {
/**
* Function to access protected payProcessors array in event registraion forms
*/
public static function get_ppids(&$form) {
$payprocessorIds = $form->_paymentProcessors;
return $payprocessorIds;
}
}
...@@ -11,9 +11,8 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment { ...@@ -11,9 +11,8 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
* pattern and cache the instance in this variable * pattern and cache the instance in this variable
* *
* @var object * @var object
* @static
*/ */
static private $_singleton = NULL; private static $_singleton = NULL;
/** /**
* Mode of operation: live or test. * Mode of operation: live or test.
...@@ -40,13 +39,10 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment { ...@@ -40,13 +39,10 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
/** /**
* This function checks to see if we have the right config values. * This function checks to see if we have the right config values.
* *
* @return string * @return null|string
* The error message if any. * The error message if any.
*
* @public
*/ */
public function checkConfig() { public function checkConfig() {
$config = CRM_Core_Config::singleton();
$error = array(); $error = array();
if (empty($this->_paymentProcessor['user_name'])) { if (empty($this->_paymentProcessor['user_name'])) {
...@@ -75,7 +71,7 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment { ...@@ -75,7 +71,7 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
*/ */
public function logStripeException($op, $exception) { public function logStripeException($op, $exception) {
$body = print_r($exception->getJsonBody(), TRUE); $body = print_r($exception->getJsonBody(), TRUE);
CRM_Core_Error::debug_log_message("Stripe_Error {$op}: <pre> {$body} </pre>"); Civi::log()->debug("Stripe_Error {$op}: <pre> {$body} </pre>");
} }
/** /**
...@@ -84,14 +80,15 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment { ...@@ -84,14 +80,15 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
* *
* @param $stripeReturn * @param $stripeReturn
* The return from a call to stripeCatchErrors * The return from a call to stripeCatchErrors
*
* @return bool * @return bool
* *
*/ */
public function isErrorReturn($stripeReturn) { public function isErrorReturn($stripeReturn) {
if (is_object($stripeReturn) && get_class($stripeReturn) == 'CRM_Core_Error') { if (is_object($stripeReturn) && get_class($stripeReturn) == 'CRM_Core_Error') {
return true; return TRUE;
} }
return false; return FALSE;
} }
/** /**
...@@ -99,11 +96,15 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment { ...@@ -99,11 +96,15 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
* *
* @param string $op * @param string $op
* Determine which operation to perform. * Determine which operation to perform.
* @param $stripe_params
* @param array $params * @param array $params
* Parameters to run Stripe calls on. * Parameters to run Stripe calls on.
* @param array $ignores
* *
* @return varies * @return bool|\CRM_Core_Error|\Stripe\Charge|\Stripe\Customer|\Stripe\Plan
* Response from gateway. * Response from gateway.
*
* @throws \CiviCRM_API3_Exception
*/ */
public function stripeCatchErrors($op = 'create_customer', $stripe_params, $params, $ignores = array()) { public function stripeCatchErrors($op = 'create_customer', $stripe_params, $params, $ignores = array()) {
$error_url = $params['stripe_error_url']; $error_url = $params['stripe_error_url'];
...@@ -144,19 +145,34 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment { ...@@ -144,19 +145,34 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
break; break;
} }
} }
catch (Stripe_CardError $e) { catch (Exception $e) {
if (is_a($e, 'Stripe_Error')) {
foreach ($ignores as $ignore) {
if (is_a($e, $ignore['class'])) {
$body = $e->getJsonBody();
$error = $body['error'];
if ($error['type'] == $ignore['type'] && $error['message'] == $ignore['message']) {
return $return;
}
}
}
}
$this->logStripeException($op, $e); $this->logStripeException($op, $e);
$error_message = ''; $error_message = '';
// Since it's a decline, Stripe_CardError will be caught // Since it's a decline, Stripe_CardError will be caught
$body = $e->getJsonBody(); $body = $e->getJsonBody();
$err = $body['error']; $err = $body['error'];
if (!isset($err['code'])) {
$err['code'] = null;
}
//$error_message .= 'Status is: ' . $e->getHttpStatus() . "<br />"; //$error_message .= 'Status is: ' . $e->getHttpStatus() . "<br />";
////$error_message .= 'Param is: ' . $err['param'] . "<br />"; ////$error_message .= 'Param is: ' . $err['param'] . "<br />";
$error_message .= 'Type: ' . $err['type'] . '<br />'; $error_message .= 'Type: ' . $err['type'] . '<br />';
$error_message .= 'Code: ' . $err['code'] . '<br />'; $error_message .= 'Code: ' . $err['code'] . '<br />';
$error_message .= 'Message: ' . $err['message'] . '<br />'; $error_message .= 'Message: ' . $err['message'] . '<br />';
if (is_a($e, 'Stripe_CardError')) {
$newnote = civicrm_api3('Note', 'create', array( $newnote = civicrm_api3('Note', 'create', array(
'sequential' => 1, 'sequential' => 1,
'entity_id' => $params['contactID'], 'entity_id' => $params['contactID'],
...@@ -165,49 +181,7 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment { ...@@ -165,49 +181,7 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
'note' => $err['code'], 'note' => $err['code'],
'entity_table' => "civicrm_contributions", 'entity_table' => "civicrm_contributions",
)); ));
if (isset($error_url)) {
// Redirect to first page of form and present error.
CRM_Core_Error::statusBounce("Oops! Looks like there was an error. Payment Response:
<br /> {$error_message}", $error_url);
}
else {
// Don't have return url - return error object to api
$core_err = CRM_Core_Error::singleton();
$message = 'Oops! Looks like there was an error. Payment Response: <br />' . $error_message;
if ($err['code']) {
$core_err->push($err['code'], 0, NULL, $message);
}
else {
$core_err->push(9000, 0, NULL, 'Unknown Error');
} }
return $core_err;
}
}
catch (Exception $e) {
if (is_a($e, 'Stripe_Error')) {
foreach ($ignores as $ignore) {
if (is_a($e, $ignore['class'])) {
$body = $e->getJsonBody();
$error = $body['error'];
if ($error['type'] == $ignore['type'] && $error['message'] == $ignore['message']) {
return $return;
}
}
}
$this->logStripeException($op, $e);
}
// Something else happened, completely unrelated to Stripe
$error_message = '';
// Since it's a decline, Stripe_CardError will be caught
$body = $e->getJsonBody();
$err = $body['error'];
//$error_message .= 'Status is: ' . $e->getHttpStatus() . "<br />";
////$error_message .= 'Param is: ' . $err['param'] . "<br />";
$error_message .= 'Type: ' . $err['type'] . "<br />";
$error_message .= 'Code: ' . $err['code'] . "<br />";
$error_message .= 'Message: ' . $err['message'] . "<br />";
if (isset($error_url)) { if (isset($error_url)) {
// Redirect to first page of form and present error. // Redirect to first page of form and present error.
...@@ -222,7 +196,7 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment { ...@@ -222,7 +196,7 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
$core_err->push($err['code'], 0, NULL, $message); $core_err->push($err['code'], 0, NULL, $message);
} }
else { else {
$core_err->push(9000, 0, NULL, 'Unknown Error'); $core_err->push(9000, 0, NULL, 'Unknown Error: ' . $message);
} }
return $core_err; return $core_err;
} }
...@@ -232,98 +206,168 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment { ...@@ -232,98 +206,168 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
} }
/** /**
* Implementation of hook_civicrm_buildForm(). * Override CRM_Core_Payment function
* *
* @param $form - reference to the form object * @return array
*/ */
public function buildForm(&$form) { public function getPaymentFormFields() {
$stripe_ppid = self::get_stripe_ppid($form); return array(
'credit_card_type',
// Add the ID to our form so our js can tell if Stripe has been selected. 'credit_card_number',
$form->addElement('hidden', 'stripe_id', $stripe_ppid, array('id' => 'stripe-id')); 'cvv2',
'credit_card_exp_date',
'stripe_token',
'stripe_pub_key',
'stripe_id',
);
}
$stripe_key = self::stripe_get_key($stripe_ppid); /**
$form->addElement('hidden', 'stripe_pub_key', $stripe_key, array('id' => 'stripe-pub-key')); * Return an array of all the details about the fields potentially required for payment fields.
*
* Only those determined by getPaymentFormFields will actually be assigned to the form
*
* @return array
* field metadata
*/
public function getPaymentFormFieldsMetadata() {
$creditCardType = array('' => ts('- select -')) + CRM_Contribute_PseudoConstant::creditCard();
return array(
'credit_card_number' => array(
'htmlType' => 'text',
'name' => 'credit_card_number',
'title' => ts('Card Number'),
'cc_field' => TRUE,
'attributes' => array(
'size' => 20,
'maxlength' => 20,
'autocomplete' => 'off',
),
'is_required' => TRUE,
),
'cvv2' => array(
'htmlType' => 'text',
'name' => 'cvv2',
'title' => ts('Security Code'),
'cc_field' => TRUE,
'attributes' => array(
'size' => 5,
'maxlength' => 10,
'autocomplete' => 'off',
),
'is_required' => TRUE,
),
'credit_card_exp_date' => array(
'htmlType' => 'date',
'name' => 'credit_card_exp_date',
'title' => ts('Expiration Date'),
'cc_field' => TRUE,
'attributes' => CRM_Core_SelectValues::date('creditCard'),
'is_required' => TRUE,
'month_field' => 'credit_card_exp_date_M',
'year_field' => 'credit_card_exp_date_Y',
),
$params = $form->get('params'); 'credit_card_type' => array(
// Contrib forms store this in $params, Event forms in $params[0]. 'htmlType' => 'select',
if (!empty($params[0]['stripe_token'])) { 'name' => 'credit_card_type',
$params = $params[0]; 'title' => ts('Card Type'),
'cc_field' => TRUE,
'attributes' => $creditCardType,
'is_required' => FALSE,
),
'stripe_token' => array(
'htmlType' => 'hidden',
'name' => 'stripe_token',
'title' => 'Stripe Token',
'attributes' => array(
'id' => 'stripe-token',
),
'cc_field' => TRUE,
'is_required' => TRUE,
),
'stripe_id' => array(
'htmlType' => 'hidden',
'name' => 'stripe_id',
'title' => 'Stripe ID',
'attributes' => array(
'id' => 'stripe-id',
),
'cc_field' => TRUE,
'is_required' => TRUE,
),
'stripe_pub_key' => array(
'htmlType' => 'hidden',
'name' => 'stripe_pub_key',
'title' => 'Stripe Public Key',
'attributes' => array(
'id' => 'stripe-pub-key',
),
'cc_field' => TRUE,
'is_required' => TRUE,
),
);
} }
$stripe_token = (empty($params['stripe_token']) ? NULL : $params['stripe_token']);
// Add some hidden fields for Stripe. /**
if (!$form->elementExists('stripe_token')) { * Get form metadata for billing address fields.
$form->setAttribute('class', $form->getAttribute('class') . ' stripe-payment-form'); *
$form->addElement('hidden', 'stripe_token', $stripe_token, array('id' => 'stripe-token')); * @param int $billingLocationID
*
* @return array
* Array of metadata for address fields.
*/
public function getBillingAddressFieldsMetadata($billingLocationID = NULL) {
$metadata = parent::getBillingAddressFieldsMetadata($billingLocationID);
if (!$billingLocationID) {
// Note that although the billing id is passed around the forms the idea that it would be anything other than
// the result of the function below doesn't seem to have eventuated.
// So taking this as a param is possibly something to be removed in favour of the standard default.
$billingLocationID = CRM_Core_BAO_LocationType::getBilling();
} }
// Add the Civi version so we can accommodate different versions in civicrm_stripe.js. // Stripe does not require the state/county field
if (self::get_civi_version() <= '4.7.0') { if (!empty($metadata["billing_state_province_id-{$billingLocationID}"]['is_required'])) {
$ext_mode = 1; $metadata["billing_state_province_id-{$billingLocationID}"]['is_required'] = FALSE;
}
else {
$ext_mode = 2;
} }
$form->addElement('hidden', 'ext_mode', $ext_mode, array('id' => 'ext-mode'));
// Add email field as it would usually be found on donation forms. return $metadata;
if (!isset($form->_elementIndex['email']) && !empty($form->userEmail)) {
$form->addElement('hidden', 'email', $form->userEmail, array('id' => 'user-email'));
}
} }
public static function get_stripe_ppid($form) { /**
if (empty($form->_paymentProcessor)) { * Set default values when loading the (payment) form
return; *
} * @param \CRM_Core_Form $form
// Determine if we are dealing with a webform in CiviCRM 4.7. Those don't have a */
// _paymentProcessors array and only have one payprocesssor. public function buildForm(&$form) {
if (in_array(get_class($form), array('CRM_Financial_Form_Payment', 'CRM_Contribute_Form_Contribution'))) { // Set default values
return $stripe_ppid = $form->_paymentProcessor['id']; $paymentProcessorId = CRM_Utils_Array::value('id', $form->_paymentProcessor);
} $publishableKey = CRM_Core_Payment_Stripe::getPublishableKey($paymentProcessorId);
else { $defaults = [
// Find a Stripe pay processor ascociated with this Civi form and find the ID. 'stripe_id' => $paymentProcessorId,
// $payProcessors = $form->_paymentProcessors; 'stripe_pub_key' => $publishableKey,
$payProcessors = CRM_Core_Form_Stripe::get_ppids($form); ];
foreach ($payProcessors as $payProcessor) { $form->setDefaults($defaults);
if ($payProcessor['class_name'] == 'Payment_Stripe') {
return $stripe_ppid = $payProcessor['id'];
break;
}
}
}
// None of the payprocessors are Stripe.
if (empty($stripe_ppid)) {
return;
}
} }
/** /**
* Given a payment processor id, return the pub key. * Given a payment processor id, return the publishable key (password field)
*
* @param $paymentProcessorId
*
* @return string
*/ */
public function stripe_get_key($stripe_ppid) { public static function getPublishableKey($paymentProcessorId) {
try { try {
$result = civicrm_api3('PaymentProcessor', 'getvalue', array( $publishableKey = (string) civicrm_api3('PaymentProcessor', 'getvalue', array(
'return' => "password", 'return' => "password",
'id' => $stripe_ppid, 'id' => $paymentProcessorId,
)); ));
} }
catch (CiviCRM_API3_Exception $e) { catch (CiviCRM_API3_Exception $e) {
return NULL; return '';
}
return $result;
} }
return $publishableKey;
/**
* Return the CiviCRM version we're running.
*/
public function get_civi_version() {
$version = civicrm_api3('Domain', 'getvalue', array(
'return' => "version",
'current_domain' => true,
));
return $version;
} }
/** /**
...@@ -333,12 +377,19 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment { ...@@ -333,12 +377,19 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
* @param array $params * @param array $params
* Assoc array of input parameters for this transaction. * Assoc array of input parameters for this transaction.
* *
* @return array * @return array|\CRM_Core_Error
* The result in a nice formatted array (or an error object). * The result in a nice formatted array (or an error object).
* *
* @public * @throws \CiviCRM_API3_Exception
*/ */
public function doDirectPayment(&$params) { public function doDirectPayment(&$params) {
if (array_key_exists('credit_card_number', $params)) {
$cc = $params['credit_card_number'];
if (!empty($cc) && substr($cc, 0, 8) != '00000000') {
Civi::log()->debug(ts('ALERT! Unmasked credit card received in back end. Please report this error to the site administrator.'));
}
}
// Let a $0 transaction pass. // Let a $0 transaction pass.
if (empty($params['amount']) || $params['amount'] == 0) { if (empty($params['amount']) || $params['amount'] == 0) {
return $params; return $params;
...@@ -358,8 +409,7 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment { ...@@ -358,8 +409,7 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
$parsed_url['query'] . "&_qf_Main_display=1&qfKey={$qfKey}", FALSE, NULL, FALSE); $parsed_url['query'] . "&_qf_Main_display=1&qfKey={$qfKey}", FALSE, NULL, FALSE);
} }
// Include Stripe library then set plugin info and API credentials. // Set plugin info and API credentials.
require_once('stripe-php/init.php');
\Stripe\Stripe::setAppInfo('CiviCRM', CRM_Utils_System::version(), CRM_Utils_System::baseURL()); \Stripe\Stripe::setAppInfo('CiviCRM', CRM_Utils_System::version(), CRM_Utils_System::baseURL());
\Stripe\Stripe::setApiKey($this->_paymentProcessor['user_name']); \Stripe\Stripe::setApiKey($this->_paymentProcessor['user_name']);
...@@ -369,10 +419,14 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment { ...@@ -369,10 +419,14 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
// Use Stripe.js instead of raw card details. // Use Stripe.js instead of raw card details.
if (!empty($params['stripe_token'])) { if (!empty($params['stripe_token'])) {
$card_details = $params['stripe_token']; $card_token = $params['stripe_token'];
}
else if(!empty(CRM_Utils_Array::value('stripe_token', $_POST, NULL))) {
$card_token = CRM_Utils_Array::value('stripe_token', $_POST, NULL);
} }
else { else {
CRM_Core_Error::fatal(ts('Stripe.js token was not passed! Report this message to the site administrator.')); CRM_Core_Error::statusBounce(ts('Unable to complete payment! Please this to the site administrator with a description of what you were trying to do.'));
Civi::log()->debug('Stripe.js token was not passed! Report this message to the site administrator. $params: ' . print_r($params, TRUE));
} }
// Check for existing customer, create new otherwise. // Check for existing customer, create new otherwise.
...@@ -463,7 +517,7 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment { ...@@ -463,7 +517,7 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
if (!isset($customer_query)) { if (!isset($customer_query)) {
$sc_create_params = array( $sc_create_params = array(
'description' => 'Donor from CiviCRM', 'description' => 'Donor from CiviCRM',
'card' => $card_details, 'card' => $card_token,
'email' => $email, 'email' => $email,
); );
...@@ -496,15 +550,13 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment { ...@@ -496,15 +550,13 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
return $stripe_customer; return $stripe_customer;
} }
// Avoid the 'use same token twice' issue while still using latest card. // Avoid the 'use same token twice' issue while still using latest card.
if (!empty($params['selectMembership']) if (!empty($params['is_secondary_financial_transaction'])) {
&& $params['selectMembership'] // This is a Contribution page with "Separate Membership Payment".
&& empty($params['contributionPageID']) // Charge is coming through for the 2nd time.
) { // Don't update customer again or we will get "token_already_used" error from Stripe.
// This is a Contribution form w/ Membership option and charge is
// coming through for the 2nd time. Don't need to update customer again.
} }
else { else {
$stripe_customer->card = $card_details; $stripe_customer->card = $card_token;
$response = $this->stripeCatchErrors('save', $stripe_customer, $params); $response = $this->stripeCatchErrors('save', $stripe_customer, $params);
if (isset($response) && $this->isErrorReturn($response)) { if (isset($response) && $this->isErrorReturn($response)) {
return $response; return $response;
...@@ -516,7 +568,7 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment { ...@@ -516,7 +568,7 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
// retrieved from Stripe. Was he deleted? // retrieved from Stripe. Was he deleted?
$sc_create_params = array( $sc_create_params = array(
'description' => 'Donor from CiviCRM', 'description' => 'Donor from CiviCRM',
'card' => $card_details, 'card' => $card_token,
'email' => $email, 'email' => $email,
); );
...@@ -556,10 +608,7 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment { ...@@ -556,10 +608,7 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
// Prepare the charge array, minus Customer/Card details. // Prepare the charge array, minus Customer/Card details.
if (empty($params['description'])) { if (empty($params['description'])) {
$params['description'] = ts('CiviCRM backend contribution'); $params['description'] = ts('Backend contribution');
}
else {
$params['description'] = ts('CiviCRM # ') . $params['description'];
} }