diff --git a/CRM/Core/Payment/Stripe.php b/CRM/Core/Payment/Stripe.php index 8fbd2930a12e2c05a09aa635c39fb194846a3d74..f17f07bd7a1e73ef07243afd519f5c0d342064c7 100644 --- a/CRM/Core/Payment/Stripe.php +++ b/CRM/Core/Payment/Stripe.php @@ -3,18 +3,20 @@ * https://civicrm.org/licensing */ +use CRM_Stripe_ExtensionUtil as E; + /** * Class CRM_Core_Payment_Stripe */ class CRM_Core_Payment_Stripe extends CRM_Core_Payment { - use CRM_Core_Payment_StripeTrait; + use CRM_Core_Payment_MJWTrait; /** * * @var string */ - const API_VERSION = '2019-05-16'; + const API_VERSION = '2019-09-09'; /** * Mode of operation: live or test. @@ -31,13 +33,14 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment { * * @param string $mode * The mode of operation: live or test. + * @param array $paymentProcessor * * @return void */ - public function __construct($mode, &$paymentProcessor) { + public function __construct($mode, $paymentProcessor) { $this->_mode = $mode; $this->_paymentProcessor = $paymentProcessor; - $this->_processorName = ts('Stripe'); + $this->_processorName = E::SHORT_NAME; } /** @@ -46,7 +49,7 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment { * @return string */ public static function getSecretKey($paymentProcessor) { - return trim(CRM_Utils_Array::value('user_name', $paymentProcessor)); + return trim(CRM_Utils_Array::value('password', $paymentProcessor)); } /** @@ -55,7 +58,7 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment { * @return string */ public static function getPublicKey($paymentProcessor) { - return trim(CRM_Utils_Array::value('password', $paymentProcessor)); + return trim(CRM_Utils_Array::value('user_name', $paymentProcessor)); } /** @@ -105,7 +108,7 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment { * The error message if any. */ public function checkConfig() { - $error = array(); + $error = []; if (!empty($error)) { return implode('<p>', $error); @@ -120,7 +123,9 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment { * @return bool */ public function supportsBackOffice() { - return TRUE; + // @fixme Make this work again with stripe elements / 6.0 + return FALSE; + // return TRUE; } /** @@ -131,8 +136,21 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment { return FALSE; } + public function supportsRecurring() { + return TRUE; + } + + /** + * Does this payment processor support refund? + * + * @return bool + */ + public function supportsRefund() { + return TRUE; + } + /** - * We can configure a start date for a smartdebit mandate + * Can we set a future recur start date? Stripe allows this but we don't (yet) support it. * @return bool */ public function supportsFutureRecurStartDate() { @@ -173,14 +191,8 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment { * * @return string errorMessage (or statusbounce if URL is specified) */ - public static function handleErrorNotification($err, $bounceURL = NULL) { - $debugMsg = $err['type'] . ' ' . $err['code'] . ' ' . $err['message']; - Civi::log()->debug('Stripe Payment Error: ' . $debugMsg); - - if ($bounceURL) { - CRM_Core_Error::statusBounce($err['message'], $bounceURL, 'Payment Error'); - } - return $debugMsg; + public function handleErrorNotification($err, $bounceURL = NULL) { + return self::handleError("{$err['type']} {$err['code']}", $err['message'], $bounceURL); } /** @@ -215,9 +227,6 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment { public function createPlan($params, $amount) { $currency = strtolower($params['currencyID']); $planId = "every-{$params['frequency_interval']}-{$params['frequency_unit']}-{$amount}-" . $currency; - if (isset($params['membership_type_tag'])) { - $planId = $params['membership_type_tag'] . $planId; - } if ($this->_paymentProcessor['is_test']) { $planId .= '-test'; @@ -236,19 +245,19 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment { if ($this->_paymentProcessor['is_test']) { $productName .= '-test'; } - $product = \Stripe\Product::create(array( + $product = \Stripe\Product::create([ "name" => $productName, "type" => "service" - )); + ]); // Create a new Plan. - $stripePlan = array( + $stripePlan = [ 'amount' => $amount, 'interval' => $params['frequency_unit'], 'product' => $product->id, 'currency' => $currency, 'id' => $planId, 'interval_count' => $params['frequency_interval'], - ); + ]; $plan = \Stripe\Plan::create($stripePlan); } } @@ -261,15 +270,7 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment { * @return array */ public function getPaymentFormFields() { - return array( - 'credit_card_type', - 'credit_card_number', - 'cvv2', - 'credit_card_exp_date', - 'stripe_token', - 'stripe_pub_key', - 'stripe_id', - ); + return []; } /** @@ -281,86 +282,7 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment { * 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', - 'extra' => ['class' => 'crm-form-select'], - ), - - 'credit_card_type' => array( - 'htmlType' => 'select', - 'name' => 'credit_card_type', - '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', - 'class' => 'payproc-metadata', - ), - 'cc_field' => TRUE, - 'is_required' => TRUE, - ), - 'stripe_id' => array( - 'htmlType' => 'hidden', - 'name' => 'stripe_id', - 'title' => 'Stripe ID', - 'attributes' => array( - 'id' => 'stripe-id', - 'class' => 'payproc-metadata', - ), - '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', - 'class' => 'payproc-metadata', - ), - 'cc_field' => TRUE, - 'is_required' => TRUE, - ), - ); + return []; } /** @@ -394,14 +316,28 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment { * @param \CRM_Core_Form $form */ public function buildForm(&$form) { - // Set default values - $paymentProcessorId = CRM_Utils_Array::value('id', $form->_paymentProcessor); - $publishableKey = CRM_Core_Payment_Stripe::getPublicKeyById($paymentProcessorId); - $defaults = [ - 'stripe_id' => $paymentProcessorId, - 'stripe_pub_key' => $publishableKey, + $jsVars = [ + 'id' => $form->_paymentProcessor['id'], + 'currency' => $this->getDefaultCurrencyForForm($form), + 'billingAddressID' => CRM_Core_BAO_LocationType::getBilling(), + 'publishableKey' => CRM_Core_Payment_Stripe::getPublicKeyById($form->_paymentProcessor['id']), + 'jsDebug' => (boolean) \Civi::settings()->get('stripe_jsdebug'), ]; - $form->setDefaults($defaults); + \Civi::resources()->addVars(E::SHORT_NAME, $jsVars); + // Assign to smarty so we can add via Card.tpl for drupal webform because addVars doesn't work in that context + $form->assign('stripeJSVars', $jsVars); + + // Add help and javascript + CRM_Core_Region::instance('billing-block')->add( + ['template' => 'CRM/Core/Payment/Stripe/Card.tpl', 'weight' => -1]); + // Add CSS via region (it won't load on drupal webform if added via \Civi::resources()->addStyleFile) + + $min = ((boolean) \Civi::settings()->get('stripe_jsdebug')) ? '' : '.min'; + CRM_Core_Region::instance('billing-block')->add([ + 'styleUrl' => \Civi::resources()->getUrl(E::LONG_NAME, "css/elements{$min}.css"), + 'weight' => -1, + ]); + \Civi::resources()->addScriptFile(E::LONG_NAME, "js/civicrm_stripe{$min}.js"); } /** @@ -422,22 +358,24 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment { * @throws \Civi\Payment\Exception\PaymentProcessorException */ public function doPayment(&$params, $component = 'contribute') { - 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.')); - } + $params = $this->beginDoPayment($params); + + // Get the passed in paymentIntent + if(!empty(CRM_Utils_Array::value('paymentIntentID', $params))) { + $paymentIntentID = CRM_Utils_Array::value('paymentIntentID', $params); + } + elseif (CRM_Core_Session::singleton()->get('stripePaymentIntent')) { + // @fixme Hack for contributionpages - see https://github.com/civicrm/civicrm-core/pull/15252 + $paymentIntentID = CRM_Core_Session::singleton()->get('stripePaymentIntent'); + $params['paymentIntentID'] = $paymentIntentID; + } + else { + CRM_Core_Error::statusBounce(E::ts('Unable to complete payment! Missing paymentIntent ID.')); + Civi::log()->debug('paymentIntentID not found. $params: ' . print_r($params, TRUE)); } - $completedStatusId = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed'); $pendingStatusId = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Pending'); - // If we have a $0 amount, skip call to processor and set payment_status to Completed. - if (empty($params['amount'])) { - $params['payment_status_id'] = $completedStatusId; - return $params; - } - $this->setAPIParams(); // Get proper entry URL for returning on error. @@ -448,162 +386,135 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment { } else { $qfKey = $params['qfKey']; - $parsed_url = parse_url($params['entryURL']); - $url_path = substr($parsed_url['path'], 1); - $params['stripe_error_url'] = CRM_Utils_System::url($url_path, - $parsed_url['query'] . "&_qf_Main_display=1&qfKey={$qfKey}", FALSE, NULL, FALSE); + $parsedUrl = parse_url($params['entryURL']); + $urlPath = substr($parsedUrl['path'], 1); + $query = $parsedUrl['query']; + if (strpos($query, '_qf_Main_display=1') === FALSE) { + $query .= '&_qf_Main_display=1'; + } + if (strpos($query, 'qfKey=') === FALSE) { + $query .= "&qfKey={$qfKey}"; + } + $params['stripe_error_url'] = CRM_Utils_System::url($urlPath, $query, FALSE, NULL, FALSE); } $amount = self::getAmount($params); - // Use Stripe.js instead of raw card details. - if (!empty($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 { - 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)); - } - $contactId = $this->getContactId($params); $email = $this->getBillingEmail($params, $contactId); // See if we already have a stripe customer $customerParams = [ 'contact_id' => $contactId, - 'card_token' => $card_token, 'processor_id' => $this->_paymentProcessor['id'], 'email' => $email, // Include this to allow redirect within session on payment failure 'stripe_error_url' => $params['stripe_error_url'], ]; + // Get the Stripe Customer: + // 1. Look for an existing customer. + // 2. If no customer (or a deleted customer found), create a new one. + // 3. If existing customer found, update the metadata that Stripe holds for this customer. $stripeCustomerId = CRM_Stripe_Customer::find($customerParams); - // Customer not in civicrm database. Create a new Customer in Stripe. if (!isset($stripeCustomerId)) { - $stripeCustomer = CRM_Stripe_Customer::create($customerParams); + $stripeCustomer = CRM_Stripe_Customer::create($customerParams, $this); } else { // Customer was found in civicrm database, fetch from Stripe. - $deleteCustomer = FALSE; try { $stripeCustomer = \Stripe\Customer::retrieve($stripeCustomerId); - } - catch (Exception $e) { + } catch (Exception $e) { $err = self::parseStripeException('retrieve_customer', $e, FALSE); - if (($err['type'] == 'invalid_request_error') && ($err['code'] == 'resource_missing')) { - $deleteCustomer = TRUE; - } - $errorMessage = self::handleErrorNotification($err, $params['stripe_error_url']); - throw new \Civi\Payment\Exception\PaymentProcessorException('Failed to create Stripe Charge: ' . $errorMessage); + $errorMessage = $this->handleErrorNotification($err, $params['stripe_error_url']); + throw new \Civi\Payment\Exception\PaymentProcessorException('Failed to retrieve Stripe Customer: ' . $errorMessage); } - if ($deleteCustomer || $stripeCustomer->isDeleted()) { + if ($stripeCustomer->isDeleted()) { // Customer doesn't exist, create a new one CRM_Stripe_Customer::delete($customerParams); try { - $stripeCustomer = CRM_Stripe_Customer::create($customerParams); - } - catch (Exception $e) { + $stripeCustomer = CRM_Stripe_Customer::create($customerParams, $this); + } catch (Exception $e) { // We still failed to create a customer - $errorMessage = self::handleErrorNotification($stripeCustomer, $params['stripe_error_url']); + $errorMessage = $this->handleErrorNotification($stripeCustomer, $params['stripe_error_url']); throw new \Civi\Payment\Exception\PaymentProcessorException('Failed to create Stripe Customer: ' . $errorMessage); } } - - $stripeCustomer->card = $card_token; - try { - $stripeCustomer->save(); - } - catch (Exception $e) { - $err = self::parseStripeException('update_customer', $e, TRUE); - if (($err['type'] == 'invalid_request_error') && ($err['code'] == 'token_already_used')) { - // This error is ok, we've already used the token during create_customer - } - else { - $errorMessage = self::handleErrorNotification($err, $params['stripe_error_url']); - throw new \Civi\Payment\Exception\PaymentProcessorException('Failed to update Stripe Customer: ' . $errorMessage); - } + else { + CRM_Stripe_Customer::updateMetadata($customerParams, $this, $stripeCustomer->id); } } // Prepare the charge array, minus Customer/Card details. if (empty($params['description'])) { - $params['description'] = ts('Backend Stripe contribution'); - } - - // Handle recurring payments in doRecurPayment(). - if (CRM_Utils_Array::value('is_recur', $params) && $params['contributionRecurID']) { - // We set payment status as pending because the IPN will set it as completed / failed - $params['payment_status_id'] = $pendingStatusId; - return $this->doRecurPayment($params, $amount, $stripeCustomer); + $params['description'] = E::ts('Contribution: %1', [1 => $this->getPaymentProcessorLabel()]); } - // Stripe charge. - $stripeChargeParams = [ - 'amount' => $amount, - 'currency' => strtolower($params['currencyID']), - 'description' => $params['description'] . ' # Invoice ID: ' . CRM_Utils_Array::value('invoiceID', $params), + $contactContribution = $this->getContactId($params) . '-' . ($this->getContributionId($params) ?: 'XX'); + $intentParams = [ + 'customer' => $stripeCustomer->id, + 'description' => "{$params['description']} {$contactContribution} #" . CRM_Utils_Array::value('invoiceID', $params), + 'statement_descriptor_suffix' => "{$contactContribution} " . substr($params['description'],0,7), + ]; + $intentParams['statement_descriptor'] = substr("{$contactContribution} " . $params['description'], 0, 22); + $intentMetadata = [ + 'amount_to_capture' => $this->getAmount($params), ]; - // Use Stripe Customer if we have a valid one. Otherwise just use the card. - if (!empty($stripeCustomer->id)) { - $stripeChargeParams['customer'] = $stripeCustomer->id; - } - else { - $stripeChargeParams['card'] = $card_token; - } - + // This is where we actually charge the customer try { - $stripeCharge = \Stripe\Charge::create($stripeChargeParams); + \Stripe\PaymentIntent::update($paymentIntentID, $intentParams); + $intent = \Stripe\PaymentIntent::retrieve($paymentIntentID); + $intent->customer = $stripeCustomer->id; + switch ($intent->status) { + case 'requires_confirmation': + $intent->confirm(); + case 'requires_capture': + $intent->capture($intentMetadata); + break; + } } catch (Exception $e) { - $err = self::parseStripeException('charge_create', $e, FALSE); - - if ($e instanceof \Stripe\Error\Card) { - if ($this->getContributionId($params)) { - civicrm_api3('Note', 'create', [ - 'entity_id' => $this->getContributionId($params), - 'contact_id' => $this->getContactId($params), - 'subject' => $err['type'], - 'note' => $err['code'], - 'entity_table' => 'civicrm_contribution', - ]); - } - } - $errorMessage = self::handleErrorNotification($err, $params['stripe_error_url']); - throw new \Civi\Payment\Exception\PaymentProcessorException('Failed to create Stripe Charge: ' . $errorMessage); + $this->handleError($e->getCode(), $e->getMessage(), $params['stripe_error_url']); + } + + // Handle recurring payments in doRecurPayment(). + if (CRM_Utils_Array::value('is_recur', $params) && $params['contributionRecurID']) { + // This is where we save the customer card + // @todo For a recurring payment we have to save the card. For a single payment we'd like to develop the + // save card functionality but should not save by default as the customer has not agreed. + $paymentMethod = \Stripe\PaymentMethod::retrieve($intent->payment_method); + $paymentMethod->attach(['customer' => $stripeCustomer->id]); + + // We set payment status as pending because the IPN will set it as completed / failed + $params['payment_status_id'] = $pendingStatusId; + return $this->doRecurPayment($params, $amount, $stripeCustomer, $paymentMethod); } // Return fees & net amount for Civi reporting. + $stripeCharge = $intent->charges->data[0]; try { $stripeBalanceTransaction = \Stripe\BalanceTransaction::retrieve($stripeCharge->balance_transaction); } catch (Exception $e) { $err = self::parseStripeException('retrieve_balance_transaction', $e, FALSE); - $errorMessage = self::handleErrorNotification($err, $params['stripe_error_url']); + $errorMessage = $this->handleErrorNotification($err, $params['stripe_error_url']); throw new \Civi\Payment\Exception\PaymentProcessorException('Failed to retrieve Stripe Balance Transaction: ' . $errorMessage); } // Success! + // Set the desired contribution status which will be set later (do not set on the contribution here!) + $params['contribution_status_id'] = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed'); // For contribution workflow we have a contributionId so we can set parameters directly. // For events/membership workflow we have to return the parameters and they might get set... - $newParams['trxn_id'] = $stripeCharge->id; - $newParams['payment_status_id'] = $completedStatusId; + // For a single charge there is no stripe invoice. + $this->setPaymentProcessorOrderID($stripeCharge->id); + $this->setPaymentProcessorTrxnID($stripeCharge->id); $newParams['fee_amount'] = $stripeBalanceTransaction->fee / 100; $newParams['net_amount'] = $stripeBalanceTransaction->net / 100; - if ($this->getContributionId($params)) { - $newParams['id'] = $this->getContributionId($params); - civicrm_api3('Contribution', 'create', $newParams); - unset($newParams['id']); - } - $params = array_merge($params, $newParams); - - return $params; + return $this->endDoPayment($params, $newParams); } /** @@ -614,8 +525,9 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment { * Assoc array of input parameters for this transaction. * @param int $amount * Transaction amount in USD cents. - * @param object $stripeCustomer + * @param \Stripe\Customer $stripeCustomer * Stripe customer object generated by Stripe API. + * @param \Stripe\PaymentMethod $stripePaymentMethod * * @return array * The result in a nice formatted array (or an error object). @@ -623,7 +535,7 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment { * @throws \CiviCRM_API3_Exception * @throws \CRM_Core_Exception */ - public function doRecurPayment(&$params, $amount, $stripeCustomer) { + public function doRecurPayment($params, $amount, $stripeCustomer, $stripePaymentMethod) { $requiredParams = ['contributionRecurID', 'frequency_unit']; foreach ($requiredParams as $required) { if (!isset($params[$required])) { @@ -635,8 +547,6 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment { // Make sure frequency_interval is set (default to 1 if not) empty($params['frequency_interval']) ? $params['frequency_interval'] = 1 : NULL; - $amount = $this->deprecatedHandleCiviDiscount($params, $amount, $stripeCustomer); - // Create the stripe plan $planId = self::createPlan($params, $amount); @@ -644,16 +554,18 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment { $subscriptionParams = [ 'prorate' => FALSE, 'plan' => $planId, + 'default_payment_method' => $stripePaymentMethod, ]; // Create the stripe subscription for the customer $stripeSubscription = $stripeCustomer->subscriptions->create($subscriptionParams); + $this->setPaymentProcessorSubscriptionID($stripeSubscription->id); $recurParams = [ 'id' => $params['contributionRecurID'], - 'trxn_id' => $stripeSubscription->id, + 'trxn_id' => $this->getPaymentProcessorSubscriptionID(), // FIXME processor_id is deprecated as it is not guaranteed to be unique, but currently (CiviCRM 5.9) // it is required by cancelSubscription (where it is called subscription_id) - 'processor_id' => $stripeSubscription->id, + 'processor_id' => $this->getPaymentProcessorSubscriptionID(), 'auto_renew' => 1, 'cycle_day' => date('d'), 'next_sched_contribution_date' => $this->calculateNextScheduledDate($params), @@ -665,6 +577,7 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment { } if ($params['installments']) { $recurParams['end_date'] = $this->calculateEndDate($params); + $recurParams['installments'] = $params['installments']; } } @@ -672,9 +585,43 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment { CRM_Stripe_Hook::updateRecurringContribution($recurParams); // Update the recurring payment civicrm_api3('ContributionRecur', 'create', $recurParams); - // Update the contribution status - return $params; + // Set the orderID (trxn_id) to the invoice ID + // The IPN will change it to the charge_id + $this->setPaymentProcessorOrderID($stripeSubscription->latest_invoice); + return $this->endDoPayment($params); + } + + /** + * Submit a refund payment + * + * @param array $params + * Assoc array of input parameters for this transaction. + * + * @throws \Civi\Payment\Exception\PaymentProcessorException + */ + public function doRefund(&$params) { + $requiredParams = ['charge_id', 'payment_processor_id']; + foreach ($requiredParams as $required) { + if (!isset($params[$required])) { + $message = 'Stripe doRefund: Missing mandatory parameter: ' . $required; + Civi::log()->error($message); + Throw new \Civi\Payment\Exception\PaymentProcessorException($message); + } + } + $refundParams = [ + 'charge' => $params['charge_id'], + ]; + if (!empty($params['amount'])) { + $refundParams['amount'] = $this->getAmount($params); + } + try { + $refund = \Stripe\Refund::create($refundParams); + } + catch (Exception $e) { + $this->handleError($e->getCode(), $e->getMessage()); + Throw new \Civi\Payment\Exception\PaymentProcessorException($e->getMessage()); + } } /** @@ -770,74 +717,6 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment { return $endDate->format('Ymd'); } - /** - * @deprecated This belongs in a separate extension / hook as it's non-standard CiviCRM behaviour - * - * This adds some support for CiviDiscount on recurring contributions and changes the default behavior to discounting - * only the first of a recurring contribution set instead of all. (Intro offer) The Stripe procedure for discounting the - * first payment of subscription entails creating a negative invoice item or negative balance first, - * then creating the subscription at 100% full price. The customers first Stripe invoice will reflect the - * discount. Subsequent invoices will be at the full undiscounted amount. - * NB: Civi currently won't send a $0 charge to a payproc extension, but it should in this case. If the discount is - * the cost of initial payment, we still send the whole discount (or giftcard) as a negative balance. - * Consider not selling giftards greater than your least expensive auto-renew membership until we can override this. - * - * @param $params - * @param $amount - * @param $stripeCustomer - * - * @return float|int - * @throws \CiviCRM_API3_Exception - */ - public function deprecatedHandleCiviDiscount(&$params, $amount, $stripeCustomer) { - if (!empty($params['discountcode'])) { - $discount_code = $params['discountcode']; - $discount_object = civicrm_api3('DiscountCode', 'get', array( - 'sequential' => 1, - 'return' => "amount,amount_type", - 'code' => $discount_code, - )); - // amount_types: 1 = percentage, 2 = fixed, 3 = giftcard - if ((!empty($discount_object['values'][0]['amount'])) && (!empty($discount_object['values'][0]['amount_type']))) { - $discount_type = $discount_object['values'][0]['amount_type']; - if ( $discount_type == 1 ) { - // Discount is a percentage. Avoid ugly math and just get the full price using price_ param. - foreach($params as $key=>$value){ - if("price_" == substr($key,0,6)){ - $price_param = $key; - $price_field_id = substr($key,strrpos($key,'_') + 1); - } - } - if (!empty($params[$price_param])) { - $priceFieldValue = civicrm_api3('PriceFieldValue', 'get', array( - 'sequential' => 1, - 'return' => "amount", - 'id' => $params[$price_param], - 'price_field_id' => $price_field_id, - )); - } - if (!empty($priceFieldValue['values'][0]['amount'])) { - $priceset_amount = $priceFieldValue['values'][0]['amount']; - $full_price = $priceset_amount * 100; - $discount_in_cents = $full_price - $amount; - // Set amount to full price. - $amount = $full_price; - } - } else if ( $discount_type >= 2 ) { - // discount is fixed or a giftcard. (may be > amount). - $discount_amount = $discount_object['values'][0]['amount']; - $discount_in_cents = $discount_amount * 100; - // Set amount to full price. - $amount = $amount + $discount_in_cents; - } - } - // Apply the disount through a negative balance. - $stripeCustomer->account_balance = -$discount_in_cents; - $stripeCustomer->save(); - } - return $amount; - } - /** * Default payment instrument validation. * @@ -863,15 +742,15 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment { $contributionRecurId = $this->getRecurringContributionId($params); try { - $contributionRecur = civicrm_api3('ContributionRecur', 'getsingle', array( + $contributionRecur = civicrm_api3('ContributionRecur', 'getsingle', [ 'id' => $contributionRecurId, - )); + ]); } catch (Exception $e) { return FALSE; } if (empty($contributionRecur['trxn_id'])) { - CRM_Core_Session::setStatus(ts('The recurring contribution cannot be cancelled (No reference (trxn_id) found).'), 'Smart Debit', 'error'); + CRM_Core_Session::setStatus(E::ts('The recurring contribution cannot be cancelled (No reference (trxn_id) found).'), 'Smart Debit', 'error'); return FALSE; } diff --git a/CRM/Core/Payment/StripeIPN.php b/CRM/Core/Payment/StripeIPN.php index 763dc7c8c5c978c8cb6bf8dbd04e3d905e56c5d3..5d096c55015e53c4f21bd0aaa49ecad7d125d858 100644 --- a/CRM/Core/Payment/StripeIPN.php +++ b/CRM/Core/Payment/StripeIPN.php @@ -1,12 +1,19 @@ <?php -/* - * @file - * Handle Stripe Webhooks for recurring payments. +/** + * https://civicrm.org/licensing */ +/** + * Class CRM_Core_Payment_StripeIPN + */ class CRM_Core_Payment_StripeIPN extends CRM_Core_Payment_BaseIPN { - use CRM_Core_Payment_StripeIPNTrait; + use CRM_Core_Payment_MJWIPNTrait; + + /** + * @var \CRM_Core_Payment_Stripe Payment processor + */ + protected $_paymentProcessor; /** * Transaction ID is the contribution in the redirect flow and a random number in the on-site->POST flow @@ -16,7 +23,7 @@ class CRM_Core_Payment_StripeIPN extends CRM_Core_Payment_BaseIPN { protected $transaction_id; // By default, always retrieve the event from stripe to ensure we are - // not being fed garbage. However, allow an override so when we are + // not being fed garbage. However, allow an override so when we are // testing, we can properly test a failed recurring contribution. protected $verify_event = TRUE; @@ -39,7 +46,7 @@ class CRM_Core_Payment_StripeIPN extends CRM_Core_Payment_BaseIPN { protected $frequency_unit = NULL; protected $plan_name = NULL; protected $plan_start = NULL; - + // Derived properties. protected $contribution_recur_id = NULL; protected $event_id = NULL; @@ -47,16 +54,14 @@ class CRM_Core_Payment_StripeIPN extends CRM_Core_Payment_BaseIPN { protected $receive_date = NULL; protected $amount = NULL; protected $fee = NULL; - protected $net_amount = NULL; - protected $previous_contribution = []; + protected $contribution = NULL; + protected $previous_contribution = NULL; /** * CRM_Core_Payment_StripeIPN constructor. * - * @param $ipnData + * @param \stdClass $ipnData * @param bool $verify - * - * @throws \CRM_Core_Exception */ public function __construct($ipnData, $verify = TRUE) { $this->verify_event = $verify; @@ -64,53 +69,12 @@ class CRM_Core_Payment_StripeIPN extends CRM_Core_Payment_BaseIPN { parent::__construct(); } - /** - * Set the value of is_email_receipt to use when a new contribution is received for a recurring contribution - * This is used for the API Stripe.Ipn function. If not set, we respect the value set on the ContributionRecur entity. - * - * @param int $sendReceipt The value of is_email_receipt - */ - public function setSendEmailReceipt($sendReceipt) { - switch ($sendReceipt) { - case 0: - $this->is_email_receipt = 0; - break; - - case 1: - $this->is_email_receipt = 1; - break; - - default: - $this->is_email_receipt = 0; - } - } - - /** - * Get the value of is_email_receipt to use when a new contribution is received for a recurring contribution - * This is used for the API Stripe.Ipn function. If not set, we respect the value set on the ContributionRecur entity. - * - * @return int - * @throws \CiviCRM_API3_Exception - */ - public function getSendEmailReceipt() { - if (isset($this->is_email_receipt)) { - return (int) $this->is_email_receipt; - } - if (!empty($this->contribution_recur_id)) { - $this->is_email_receipt = civicrm_api3('ContributionRecur', 'getvalue', [ - 'return' => "is_email_receipt", - 'id' => $this->contribution_recur_id, - ]); - } - return (int) $this->is_email_receipt; - } - /** * Store input array on the class. * We override base because our input parameter is an object * * @param array $parameters - */ + */ public function setInputParameters($parameters) { if (!is_object($parameters)) { $this->exception('Invalid input parameters'); @@ -119,14 +83,13 @@ class CRM_Core_Payment_StripeIPN extends CRM_Core_Payment_BaseIPN { // Determine the proper Stripe Processor ID so we can get the secret key // and initialize Stripe. $this->getPaymentProcessor(); + $this->_paymentProcessor->setAPIParams(); // Now re-retrieve the data from Stripe to ensure it's legit. - \Stripe\Stripe::setApiKey($this->_paymentProcessor['user_name']); - // Special case if this is the test webhook if (substr($parameters->id, -15, 15) === '_00000000000000') { http_response_code(200); - $test = (boolean) $this->_paymentProcessor['is_test'] ? '(Test processor)' : '(Live processor)'; + $test = (boolean) $this->_paymentProcessor->getPaymentProcessor()['is_test'] ? '(Test processor)' : '(Live processor)'; echo "Test webhook from Stripe ({$parameters->id}) received successfully by CiviCRM {$test}."; exit(); } @@ -155,168 +118,150 @@ class CRM_Core_Payment_StripeIPN extends CRM_Core_Payment_BaseIPN { $value = CRM_Utils_Type::validate($value, $type, FALSE); if ($abort && $value === NULL) { - echo "Failure: Missing Parameter<p>" . CRM_Utils_Type::escape($name, 'String'); - $this->exception("Could not find an entry for $name"); + echo "Failure: Missing or invalid parameter<p>" . CRM_Utils_Type::escape($name, 'String'); + $this->exception("Missing or invalid parameter {$name}"); } return $value; } /** + * @return bool * @throws \CRM_Core_Exception * @throws \CiviCRM_API3_Exception + * @throws \Stripe\Error\Api */ public function main() { // Collect and determine all data about this event. $this->event_type = CRM_Stripe_Api::getParam('event_type', $this->_inputParameters); - $pendingStatusId = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Pending'); // NOTE: If you add an event here make sure you add it to the webhook or it will never be received! switch($this->event_type) { - // Successful recurring payment. case 'invoice.payment_succeeded': + // Successful recurring payment. Either we are completing an existing contribution or it's the next one in a subscription $this->setInfo(); - if ($this->previous_contribution['contribution_status_id'] == $pendingStatusId) { - $this->completeContribution(); + if ($this->contribution['contribution_status_id'] == $pendingStatusId) { + $params = [ + 'id' => $this->contribution['id'], + 'trxn_date' => $this->receive_date, + 'contribution_trxn_id' => $this->invoice_id, + 'payment_trxn_id' => $this->charge_id, + 'total_amount' => $this->amount, + 'fee_amount' => $this->fee, + ]; + $this->updateContributionCompleted($params); + // Don't touch the contributionRecur as it's updated automatically by Contribution.completetransaction } - elseif ($this->previous_contribution['trxn_id'] != $this->charge_id) { - // The first contribution was completed, so create a new one. - // api contribution repeattransaction repeats the appropriate contribution if it is given - // simply the recurring contribution id. It also updates the membership for us. - civicrm_api3('Contribution', 'repeattransaction', array( + elseif ($this->contribution['trxn_id'] != $this->invoice_id) { + // Stripe has generated a new invoice (next payment in a subscription) so we + // create a new contribution in CiviCRM + $params = [ 'contribution_recur_id' => $this->contribution_recur_id, 'contribution_status_id' => 'Completed', 'receive_date' => $this->receive_date, - 'trxn_id' => $this->charge_id, + 'contribution_trxn_id' => $this->invoice_id, + 'payment_trxn_id' => $this->charge_id, 'total_amount' => $this->amount, 'fee_amount' => $this->fee, - 'is_email_receipt' => $this->getSendEmailReceipt(), - )); + 'previous_contribution' => $this->previous_contribution, + ]; + $this->repeatContribution($params); + // Don't touch the contributionRecur as it's updated automatically by Contribution.repeattransaction } - - // Successful charge & more to come. - civicrm_api3('ContributionRecur', 'create', array( - 'id' => $this->contribution_recur_id, - 'failure_count' => 0, - 'contribution_status_id' => 'In Progress' - )); + $this->handleInstallmentsForSubscription(); return TRUE; - // Failed recurring payment. case 'invoice.payment_failed': + // Failed recurring payment. Either we are failing an existing contribution or it's the next one in a subscription $this->setInfo(); - $failDate = date('YmdHis'); - if ($this->previous_contribution['contribution_status_id'] == $pendingStatusId) { + if ($this->contribution['contribution_status_id'] == $pendingStatusId) { // If this contribution is Pending, set it to Failed. - civicrm_api3('Contribution', 'create', array( - 'id' => $this->previous_contribution['id'], - 'contribution_status_id' => "Failed", - 'receive_date' => $failDate, - 'is_email_receipt' => 0, - )); + $params = [ + 'id' => $this->contribution['id'], + 'receive_date' => $this->receive_date, + 'cancel_reason' => $this->retrieve('failure_message', 'String'), + 'payment_trxn_id' => $this->charge_id, + ]; + $this->updateContributionFailed($params); } - else { - $contributionParams = [ + elseif ($this->contribution['trxn_id'] != $this->invoice_id) { + $params = [ 'contribution_recur_id' => $this->contribution_recur_id, 'contribution_status_id' => 'Failed', - 'receive_date' => $failDate, + 'receive_date' => $this->receive_date, + 'contribution_trxn_id' => $this->invoice_id, + 'payment_trxn_id' => $this->charge_id, 'total_amount' => $this->amount, - 'is_email_receipt' => 0, + 'fee_amount' => $this->fee, + 'previous_contribution' => $this->previous_contribution, ]; - civicrm_api3('Contribution', 'repeattransaction', $contributionParams); + $this->repeatContribution($params); + // Don't touch the contributionRecur as it's updated automatically by Contribution.completetransaction } - - $failureCount = civicrm_api3('ContributionRecur', 'getvalue', array( - 'id' => $this->contribution_recur_id, - 'return' => 'failure_count', - )); - $failureCount++; - - // Change the status of the Recurring and update failed attempts. - civicrm_api3('ContributionRecur', 'create', array( - 'id' => $this->contribution_recur_id, - 'contribution_status_id' => "Failed", - 'failure_count' => $failureCount, - 'modified_date' => $failDate, - )); return TRUE; - // Subscription is cancelled case 'customer.subscription.deleted': + // Subscription is cancelled $this->setInfo(); // Cancel the recurring contribution - civicrm_api3('ContributionRecur', 'cancel', array( - 'id' => $this->contribution_recur_id, - )); + $this->updateRecurCancelled(['id' => $this->contribution_recur_id, 'cancel_date' => $this->retrieve('cancel_date', 'String', FALSE)]); return TRUE; // One-time donation and per invoice payment. case 'charge.failed': - $chargeId = $this->retrieve('charge_id', 'String'); - $failureCode = $this->retrieve('failure_code', 'String'); - $failureMessage = $this->retrieve('failure_message', 'String'); - $contribution = civicrm_api3('Contribution', 'getsingle', ['trxn_id' => $chargeId]); - $failedStatusId = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Failed'); - if ($contribution['contribution_status_id'] != $failedStatusId) { - $note = $failureCode . ' : ' . $failureMessage; - civicrm_api3('Contribution', 'create', ['id' => $contribution['id'], 'contribution_status_id' => $failedStatusId, 'note' => $note]); - } + $this->setInfo(); + $params = [ + 'id' => $this->contribution['id'], + 'receive_date' => $this->receive_date, + 'cancel_reason' => $this->retrieve('failure_message', 'String'), + 'payment_trxn_id' => $this->charge_id, + ]; + $this->updateContributionFailed($params); return TRUE; case 'charge.refunded': - $chargeId = $this->retrieve('charge_id', 'String'); - $refunded = $this->retrieve('refunded', 'Boolean'); - $refundAmount = $this->retrieve('amount_refunded', 'Integer'); - $contribution = civicrm_api3('Contribution', 'getsingle', ['trxn_id' => $chargeId]); - if ($refunded) { - $refundedStatusId = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Refunded'); - if ($contribution['contribution_status_id'] != $refundedStatusId) { - civicrm_api3('Contribution', 'create', [ - 'id' => $contribution['id'], - 'contribution_status_id' => $refundedStatusId - ]); - } - elseif ($refundAmount > 0) { - $partiallyRefundedStatusId = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Partially Refunded'); - if ($contribution['contribution_status_id'] != $partiallyRefundedStatusId) { - civicrm_api3('Contribution', 'create', [ - 'id' => $contribution['id'], - 'contribution_status_id' => $refundedStatusId - ]); - } - } - } + $this->setInfo(); + $refunds = \Stripe\Refund::all(['charge' => $this->charge_id, 'limit' => 1]); + $params = [ + 'id' => $this->contribution['id'], + 'total_amount' => $this->retrieve('amount_refunded', 'Float'), + 'cancel_reason' => $refunds->data[0]->reason, + 'cancel_date' => date('YmdHis', $refunds->data[0]->created), + ]; + $this->updateContributionRefund($params); return TRUE; case 'charge.succeeded': $this->setInfo(); - if ($this->previous_contribution['contribution_status_id'] == $pendingStatusId) { - $this->completeContribution(); + if ($this->contribution['contribution_status_id'] == $pendingStatusId) { + $params = [ + 'id' => $this->contribution['id'], + 'trxn_date' => $this->receive_date, + 'contribution_trxn_id' => $this->charge_id, + 'payment_trxn_id' => $this->charge_id, + 'total_amount' => $this->amount, + 'fee_amount' => $this->fee, + ]; + $this->updateContributionCompleted($params); } return TRUE; case 'customer.subscription.updated': - $this->setInfo(); - if (empty($this->previous_plan_id)) { - // Not a plan change...don't care. - return TRUE; - } - - civicrm_api3('ContributionRecur', 'create', [ - 'id' => $this->contribution_recur_id, + $this->setInfo(); + if (empty($this->previous_plan_id)) { + // Not a plan change...don't care. + return TRUE; + } + + civicrm_api3('ContributionRecur', 'create', [ + 'id' => $this->contribution_recur_id, 'amount' => $this->plan_amount, 'auto_renew' => 1, 'created_date' => $this->plan_start, 'frequency_unit' => $this->frequency_unit, 'frequency_interval' => $this->frequency_interval, - ]); - - civicrm_api3('Contribution', 'create', [ - 'id' => $this->previous_contribution['id'], - 'total_amount' => $this->plan_amount, - 'contribution_recur_id' => $this->contribution_recur_id, - ]); + ]); return TRUE; } // Unhandled event type. @@ -324,42 +269,17 @@ class CRM_Core_Payment_StripeIPN extends CRM_Core_Payment_BaseIPN { } /** - * Complete a pending contribution and update associated entities (recur/membership) - * - * @throws \CiviCRM_API3_Exception - */ - public function completeContribution() { - // Update the contribution to include the fee. - civicrm_api3('Contribution', 'create', array( - 'id' => $this->previous_contribution['id'], - 'total_amount' => $this->amount, - 'fee_amount' => $this->fee, - 'net_amount' => $this->net_amount, - )); - // The last one was not completed, so complete it. - civicrm_api3('Contribution', 'completetransaction', array( - 'id' => $this->previous_contribution['id'], - 'trxn_date' => $this->receive_date, - 'trxn_id' => $this->charge_id, - 'total_amount' => $this->amount, - 'net_amount' => $this->net_amount, - 'fee_amount' => $this->fee, - 'payment_processor_id' => $this->_paymentProcessor['id'], - 'is_email_receipt' => $this->getSendEmailReceipt(), - )); - } - - /** * Gather and set info as class properties. * * Given the data passed to us via the Stripe Event, try to determine - * as much as we can about this event and set that information as + * as much as we can about this event and set that information as * properties to be used later. * * @throws \CRM_Core_Exception */ public function setInfo() { $abort = FALSE; + $stripeObjectName = get_class($this->_inputParameters->data->object); $this->customer_id = CRM_Stripe_Api::getObjectParam('customer_id', $this->_inputParameters->data->object); if (empty($this->customer_id)) { $this->exception('Missing customer_id!'); @@ -377,34 +297,30 @@ class CRM_Core_Payment_StripeIPN extends CRM_Core_Payment_BaseIPN { $this->plan_name = $this->retrieve('plan_name', 'String', $abort); $this->plan_start = $this->retrieve('plan_start', 'String', $abort); + if (($stripeObjectName !== 'Stripe\Charge') && ($this->charge_id !== NULL)) { + $charge = \Stripe\Charge::retrieve($this->charge_id); + $balanceTransactionID = CRM_Stripe_Api::getObjectParam('balance_transaction', $charge); + } + else { + $charge = $this->_inputParameters->data->object; + $balanceTransactionID = CRM_Stripe_Api::getObjectParam('balance_transaction', $this->_inputParameters->data->object); + } // Gather info about the amount and fee. // Get the Stripe charge object if one exists. Null charge still needs processing. - if ($this->charge_id !== null) { + // If the transaction is declined, there won't be a balance_transaction_id. + $this->amount = 0; + $this->fee = 0; + if ($balanceTransactionID) { try { - $charge = \Stripe\Charge::retrieve($this->charge_id); - $balance_transaction_id = $charge->balance_transaction; - // If the transaction is declined, there won't be a balance_transaction_id. - if ($balance_transaction_id) { - $balance_transaction = \Stripe\BalanceTransaction::retrieve($balance_transaction_id); - $this->amount = $charge->amount / 100; - $this->fee = $balance_transaction->fee / 100; - } - else { - $this->amount = 0; - $this->fee = 0; - } + $balanceTransaction = \Stripe\BalanceTransaction::retrieve($balanceTransactionID); + $this->amount = $charge->amount / 100; + $this->fee = $balanceTransaction->fee / 100; } catch(Exception $e) { - $this->exception('Cannot get contribution amounts'); + $this->exception('Error retrieving balance transaction. ' . $e->getMessage()); } - } else { - // The customer had a credit on their subscription from a downgrade or gift card. - $this->amount = 0; - $this->fee = 0; } - $this->net_amount = $this->amount - $this->fee; - // Additional processing of values is only relevant if there is a subscription id. if ($this->subscription_id) { // Get the recurring contribution record associated with the Stripe subscription. @@ -416,28 +332,81 @@ class CRM_Core_Payment_StripeIPN extends CRM_Core_Payment_BaseIPN { $this->exception('Cannot find recurring contribution for subscription ID: ' . $this->subscription_id . '. ' . $e->getMessage()); } } - // If a recurring contribution has been found, get the most recent contribution belonging to it. - if ($this->contribution_recur_id) { + + if ($this->charge_id) { + try { + $this->contribution = civicrm_api3('Contribution', 'getsingle', [ + 'trxn_id' => $this->charge_id, + 'contribution_test' => $this->_paymentProcessor->getIsTestMode(), + ]); + } + catch (Exception $e) { + // Contribution not found - that's ok + } + } + if (!$this->contribution && $this->invoice_id) { + try { + $this->contribution = civicrm_api3('Contribution', 'getsingle', [ + 'trxn_id' => $this->invoice_id, + 'contribution_test' => $this->_paymentProcessor->getIsTestMode(), + ]); + } + catch (Exception $e) { + // Contribution not found - that's ok + } + } + if (!$this->contribution && $this->contribution_recur_id) { + // If a recurring contribution has been found, get the most recent contribution belonging to it. try { // Same approach as api repeattransaction. - $contribution = civicrm_api3('contribution', 'getsingle', array( - 'return' => array('id', 'contribution_status_id', 'total_amount', 'trxn_id'), + $contribution = civicrm_api3('contribution', 'getsingle', [ + 'return' => ['id', 'contribution_status_id', 'total_amount', 'trxn_id'], 'contribution_recur_id' => $this->contribution_recur_id, - 'contribution_test' => isset($this->_paymentProcessor['is_test']) && $this->_paymentProcessor['is_test'] ? 1 : 0, - 'options' => array('limit' => 1, 'sort' => 'id DESC'), - )); + 'contribution_test' => $this->_paymentProcessor->getIsTestMode(), + 'options' => ['limit' => 1, 'sort' => 'id DESC'], + ]); $this->previous_contribution = $contribution; } catch (Exception $e) { $this->exception('Cannot find any contributions with recurring contribution ID: ' . $this->contribution_recur_id . '. ' . $e->getMessage()); } } + if (!$this->contribution) { + $this->exception('No matching contributions for event ' . CRM_Stripe_Api::getParam('id', $this->_inputParameters)); + } } - public function exception($message) { - $errorMessage = 'StripeIPN Exception: Event: ' . $this->event_type . ' Error: ' . $message; - Civi::log()->debug($errorMessage); - http_response_code(400); - exit(1); + /** + * This allows us to end a subscription once: + * a) We've reached the end date / number of installments + * b) The recurring contribution is marked as completed + * + * @throws \CiviCRM_API3_Exception + */ + private function handleInstallmentsForSubscription() { + if ((!$this->contribution_recur_id) || (!$this->subscription_id)) { + return; + } + + $contributionRecur = civicrm_api3('ContributionRecur', 'getsingle', [ + 'id' => $this->contribution_recur_id, + ]); + + if (empty($contributionRecur['installments']) && empty($contributionRecur['end_date'])) { + return; + } + + $stripeSubscription = \Stripe\Subscription::retrieve($this->subscription_id); + // If we've passed the end date cancel the subscription + if (($stripeSubscription->current_period_end >= strtotime($contributionRecur['end_date'])) + || ($contributionRecur['contribution_status_id'] + == CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_ContributionRecur', 'contribution_status_id', 'Completed'))) { + \Stripe\Subscription::update($this->subscription_id, ['cancel_at_period_end' => TRUE]); + $this->updateRecurCompleted(['id' => $this->contribution_recur_id]); + } + // There is no easy way of retrieving a count of all invoices for a subscription so we ignore the "installments" + // parameter for now and rely on checking end_date (which was calculated based on number of installments...) + // $stripeInvoices = \Stripe\Invoice::all(['subscription' => $this->subscription_id, 'limit' => 100]); } + } diff --git a/CRM/Core/Payment/StripeIPNTrait.php b/CRM/Core/Payment/StripeIPNTrait.php deleted file mode 100644 index 86cef1b1c0016376a94c5910b51775ccc23b8841..0000000000000000000000000000000000000000 --- a/CRM/Core/Payment/StripeIPNTrait.php +++ /dev/null @@ -1,108 +0,0 @@ -<?php -/** - * Shared payment IPN functions that should one day be migrated to CiviCRM core - * Version: 20190304 - */ - -trait CRM_Core_Payment_StripeIPNTrait { - - /** - * @var array Payment processor - */ - private $_paymentProcessor; - - /** - * Get the payment processor - * The $_GET['processor_id'] value is set by CRM_Core_Payment::handlePaymentMethod. - */ - protected function getPaymentProcessor() { - $paymentProcessorId = (int) CRM_Utils_Array::value('processor_id', $_GET); - if (empty($paymentProcessorId)) { - $this->exception('Failed to get payment processor id'); - } - - try { - $this->_paymentProcessor = \Civi\Payment\System::singleton()->getById($paymentProcessorId)->getPaymentProcessor(); - } - catch(Exception $e) { - $this->exception('Failed to get payment processor'); - } - } - - /** - * Mark a contribution as cancelled and update related entities - * - * @param array $params [ 'id' -> contribution_id, 'payment_processor_id' -> payment_processor_id] - * - * @return bool - * @throws \CiviCRM_API3_Exception - */ - protected function canceltransaction($params) { - return $this->incompletetransaction($params, 'cancel'); - } - - /** - * Mark a contribution as failed and update related entities - * - * @param array $params [ 'id' -> contribution_id, 'payment_processor_id' -> payment_processor_id] - * - * @return bool - * @throws \CiviCRM_API3_Exception - */ - protected function failtransaction($params) { - return $this->incompletetransaction($params, 'fail'); - } - - /** - * Handler for failtransaction and canceltransaction - do not call directly - * - * @param array $params - * @param string $mode - * - * @return bool - * @throws \CiviCRM_API3_Exception - */ - protected function incompletetransaction($params, $mode) { - $requiredParams = ['id', 'payment_processor_id']; - foreach ($requiredParams as $required) { - if (!isset($params[$required])) { - $this->exception('canceltransaction: Missing mandatory parameter: ' . $required); - } - } - - if (isset($params['payment_processor_id'])) { - $input['payment_processor_id'] = $params['payment_processor_id']; - } - $contribution = new CRM_Contribute_BAO_Contribution(); - $contribution->id = $params['id']; - if (!$contribution->find(TRUE)) { - throw new CiviCRM_API3_Exception('A valid contribution ID is required', 'invalid_data'); - } - - if (!$contribution->loadRelatedObjects($input, $ids, TRUE)) { - throw new CiviCRM_API3_Exception('failed to load related objects'); - } - - $input['trxn_id'] = !empty($params['trxn_id']) ? $params['trxn_id'] : $contribution->trxn_id; - if (!empty($params['fee_amount'])) { - $input['fee_amount'] = $params['fee_amount']; - } - - $objects['contribution'] = &$contribution; - $objects = array_merge($objects, $contribution->_relatedObjects); - - $transaction = new CRM_Core_Transaction(); - switch ($mode) { - case 'cancel': - return $this->cancelled($objects, $transaction); - - case 'fail': - return $this->failed($objects, $transaction); - - default: - throw new CiviCRM_API3_Exception('Unknown incomplete transaction type: ' . $mode); - } - - } - -} diff --git a/CRM/Core/Payment/StripeTrait.php b/CRM/Core/Payment/StripeTrait.php deleted file mode 100644 index a5221c43a0240923450900ba96f30ead8d0b2a7e..0000000000000000000000000000000000000000 --- a/CRM/Core/Payment/StripeTrait.php +++ /dev/null @@ -1,170 +0,0 @@ -<?php -/** - * Shared payment functions that should one day be migrated to CiviCRM core - */ - -trait CRM_Core_Payment_StripeTrait { - /********************** - * Version 20190313 - *********************/ - - /** - * Get the billing email address - * - * @param array $params - * @param int $contactId - * - * @return string|NULL - */ - protected function getBillingEmail($params, $contactId) { - $billingLocationId = CRM_Core_BAO_LocationType::getBilling(); - - $emailAddress = CRM_Utils_Array::value("email-{$billingLocationId}", $params, - CRM_Utils_Array::value('email-Primary', $params, - CRM_Utils_Array::value('email', $params, NULL))); - - if (empty($emailAddress) && !empty($contactId)) { - // Try and retrieve an email address from Contact ID - try { - $emailAddress = civicrm_api3('Email', 'getvalue', array( - 'contact_id' => $contactId, - 'return' => ['email'], - )); - } - catch (CiviCRM_API3_Exception $e) { - return NULL; - } - } - return $emailAddress; - } - - /** - * Get the contact id - * - * @param array $params - * - * @return int ContactID - */ - protected function getContactId($params) { - // contactID is set by: membership payment workflow - $contactId = CRM_Utils_Array::value('contactID', $params, - CRM_Utils_Array::value('contact_id', $params, - CRM_Utils_Array::value('cms_contactID', $params, - CRM_Utils_Array::value('cid', $params, NULL - )))); - if (!empty($contactId)) { - return $contactId; - } - // FIXME: Ref: https://lab.civicrm.org/extensions/stripe/issues/16 - // The problem is that when registering for a paid event, civicrm does not pass in the - // contact id to the payment processor (civicrm version 5.3). So, I had to patch your - // getContactId to check the session for a contact id. It's a hack and probably should be fixed in core. - // The code below is exactly what CiviEvent does, but does not pass it through to the next function. - $session = CRM_Core_Session::singleton(); - return $session->get('transaction.userID', NULL); - } - - /** - * Get the contribution ID - * - * @param $params - * - * @return mixed - */ - protected function getContributionId($params) { - /* - * contributionID is set in the contribution workflow - * We do NOT have a contribution ID for event and membership payments as they are created after payment! - * See: https://github.com/civicrm/civicrm-core/pull/13763 (for events) - */ - return CRM_Utils_Array::value('contributionID', $params); - } - - /** - * Get the recurring contribution ID from parameters passed in to cancelSubscription - * Historical the data passed to cancelSubscription is pretty poor and doesn't include much! - * - * @param array $params - * - * @return int|null - */ - protected function getRecurringContributionId($params) { - // Not yet passed, but could be added via core PR - $contributionRecurId = CRM_Utils_Array::value('contribution_recur_id', $params); - if (!empty($contributionRecurId)) { - return $contributionRecurId; - } - - // Not yet passed, but could be added via core PR - $contributionId = CRM_Utils_Array::value('contribution_id', $params); - try { - return civicrm_api3('Contribution', 'getvalue', ['id' => $contributionId, 'return' => 'contribution_recur_id']); - } - catch (Exception $e) { - $subscriptionId = CRM_Utils_Array::value('subscriptionId', $params); - if (!empty($subscriptionId)) { - try { - return civicrm_api3('ContributionRecur', 'getvalue', ['processor_id' => $subscriptionId, 'return' => 'id']); - } - catch (Exception $e) { - return NULL; - } - } - return NULL; - } - } - - /** - * - * @param array $params ['name' => payment instrument name] - * - * @return int|null - * @throws \CiviCRM_API3_Exception - */ - public static function createPaymentInstrument($params) { - $mandatoryParams = ['name']; - foreach ($mandatoryParams as $value) { - if (empty($params[$value])) { - Civi::log()->error('createPaymentInstrument: Missing mandatory parameter: ' . $value); - return NULL; - } - } - - // Create a Payment Instrument - // See if we already have this type - $paymentInstrument = civicrm_api3('OptionValue', 'get', array( - 'option_group_id' => "payment_instrument", - 'name' => $params['name'], - )); - if (empty($paymentInstrument['count'])) { - // Otherwise create it - try { - $financialAccount = civicrm_api3('FinancialAccount', 'getsingle', [ - 'financial_account_type_id' => "Asset", - 'name' => "Payment Processor Account", - ]); - } - catch (Exception $e) { - $financialAccount = civicrm_api3('FinancialAccount', 'getsingle', [ - 'financial_account_type_id' => "Asset", - 'name' => "Payment Processor Account", - 'options' => ['limit' => 1, 'sort' => "id ASC"], - ]); - } - - $paymentParams = [ - 'option_group_id' => "payment_instrument", - 'name' => $params['name'], - 'description' => $params['name'], - 'financial_account_id' => $financialAccount['id'], - ]; - $paymentInstrument = civicrm_api3('OptionValue', 'create', $paymentParams); - $paymentInstrumentId = $paymentInstrument['values'][$paymentInstrument['id']]['value']; - } - else { - $paymentInstrumentId = $paymentInstrument['id']; - } - return $paymentInstrumentId; - } - -} diff --git a/CRM/Stripe/AJAX.php b/CRM/Stripe/AJAX.php new file mode 100644 index 0000000000000000000000000000000000000000..032e7cd58c8c961ff4d96fbbe0414531e2d04c91 --- /dev/null +++ b/CRM/Stripe/AJAX.php @@ -0,0 +1,75 @@ +<?php + +/** + * https://civicrm.org/licensing + */ + +/** + * Class CRM_Stripe_AJAX + */ +class CRM_Stripe_AJAX { + + /** + * Generate the paymentIntent for civicrm_stripe.js + * + * @throws \CRM_Core_Exception + * @throws \CiviCRM_API3_Exception + */ + public static function confirmPayment() { + $paymentMethodID = CRM_Utils_Request::retrieveValue('payment_method_id', 'String'); + $paymentIntentID = CRM_Utils_Request::retrieveValue('payment_intent_id', 'String'); + $amount = CRM_Utils_Request::retrieveValue('amount', 'Money', NULL, TRUE); + $currency = CRM_Utils_Request::retrieveValue('currency', 'String', CRM_Core_Config::singleton()->defaultCurrency); + $processorID = CRM_Utils_Request::retrieveValue('id', 'Integer', NULL, TRUE); + $processor = new CRM_Core_Payment_Stripe('', civicrm_api3('PaymentProcessor', 'getsingle', ['id' => $processorID])); + $processor->setAPIParams(); + + if (!$paymentIntentID) { + try { + $intent = \Stripe\PaymentIntent::create([ + 'payment_method' => $paymentMethodID, + 'amount' => $processor->getAmount(['amount' => $amount]), + 'currency' => $currency, + 'confirmation_method' => 'manual', + 'capture_method' => 'manual', + // authorize the amount but don't take from card yet + 'setup_future_usage' => 'off_session', + // Setup the card to be saved and used later + 'confirm' => TRUE, + ]); + } + catch (Exception $e) { + CRM_Utils_JSON::output(['error' => ['message' => $e->getMessage()]]); + } + } + + self::generatePaymentResponse($intent); + } + + /** + * Generate the json response for civicrm_stripe.js + * + * @param \Stripe\PaymentIntent $intent + */ + private static function generatePaymentResponse($intent) { + if ($intent->status == 'requires_action' && + $intent->next_action->type == 'use_stripe_sdk') { + // Tell the client to handle the action + CRM_Utils_JSON::output([ + 'requires_action' => true, + 'payment_intent_client_secret' => $intent->client_secret, + ]); + } else if (($intent->status == 'requires_capture') || ($intent->status == 'requires_confirmation')) { + // The payment intent has been confirmed, we just need to capture the payment + // Handle post-payment fulfillment + CRM_Utils_JSON::output([ + 'success' => true, + 'paymentIntent' => ['id' => $intent->id], + ]); + } else { + // Invalid status + CRM_Utils_JSON::output(['error' => ['message' => 'Invalid PaymentIntent status']]); + } + } + +} diff --git a/CRM/Stripe/Api.php b/CRM/Stripe/Api.php index 733601512ab3595f9afac25137c86d403be2deab..ac1aa5f54ce0694bf9174509ba2fcde3a7743fa7 100644 --- a/CRM/Stripe/Api.php +++ b/CRM/Stripe/Api.php @@ -20,11 +20,17 @@ class CRM_Stripe_Api { return (bool) $stripeObject->refunded; case 'amount_refunded': - return (int) $stripeObject->amount_refunded / 100; - + return (float) $stripeObject->amount_refunded / 100; + case 'customer_id': return (string) $stripeObject->customer; + case 'balance_transaction': + return (string) $stripeObject->balance_transaction; + + case 'receive_date': + return self::formatDate($stripeObject->created); + } break; @@ -37,7 +43,7 @@ class CRM_Stripe_Api { return (string) $stripeObject->id; case 'receive_date': - return $stripeObject->date ? date("Y-m-d H:i:s", $stripeObject->date) : NULL; + return self::formatDate($stripeObject->created); case 'subscription_id': return (string) $stripeObject->subscription; @@ -64,10 +70,13 @@ class CRM_Stripe_Api { case 'description': return (string) $stripeObject->description; - + case 'customer_id': return (string) $stripeObject->customer; + case 'failure_message': + $stripeCharge = \Stripe\Charge::retrieve($stripeObject->charge); + return (string) $stripeCharge->failure_message; } break; @@ -93,7 +102,10 @@ class CRM_Stripe_Api { return (string) $stripeObject->plan->name; case 'plan_start': - return $stripeObject->start ? date("Y-m-d H:i:s", $stripeObject->start) : NULL; + return self::formatDate($stripeObject->start_date); + + case 'cancel_date': + return self::formatDate($stripeObject->canceled_at); case 'cycle_day': return date("d", $stripeObject->billing_cycle_anchor); @@ -119,6 +131,16 @@ class CRM_Stripe_Api { return NULL; } + /** + * Return a formatted date from a stripe timestamp or NULL if not set + * @param int $stripeTimestamp + * + * @return string|null + */ + private static function formatDate($stripeTimestamp) { + return $stripeTimestamp ? date('YmdHis', $stripeTimestamp) : NULL; + } + public static function getParam($name, $stripeObject) { // Common parameters switch ($name) { @@ -128,6 +150,9 @@ class CRM_Stripe_Api { case 'event_type': return (string) $stripeObject->type; + case 'id': + return (string) $stripeObject->id; + case 'previous_plan_id': if (preg_match('/\.updated$/', $stripeObject->type)) { return (string) $stripeObject->data->previous_attributes->plan->id; diff --git a/CRM/Stripe/Check.php b/CRM/Stripe/Check.php new file mode 100644 index 0000000000000000000000000000000000000000..c453cfef130307e86d16d948151212b8916329d6 --- /dev/null +++ b/CRM/Stripe/Check.php @@ -0,0 +1,46 @@ +<?php + +/** + * https://civicrm.org/licensing + */ + +use CRM_Stripe_ExtensionUtil as E; + +/** + * Class CRM_Stripe_Check + */ +class CRM_Stripe_Check { + + const MIN_VERSION_MJWSHARED = 0.3; + + public static function checkRequirements(&$messages) { + $extensions = civicrm_api3('Extension', 'get', [ + 'full_name' => "mjwshared", + ]); + + if (empty($extensions['id']) || ($extensions['values'][$extensions['id']]['status'] !== 'installed')) { + $messages[] = new CRM_Utils_Check_Message( + 'stripe_requirements', + E::ts('The Stripe extension requires the mjwshared extension which is not installed (https://lab.civicrm.org/extensions/mjwshared).'), + E::ts('Stripe: Missing Requirements'), + \Psr\Log\LogLevel::ERROR, + 'fa-money' + ); + } + + if (version_compare($extensions['values'][$extensions['id']]['version'], self::MIN_VERSION_MJWSHARED) === -1) { + $messages[] = new CRM_Utils_Check_Message( + 'stripe_requirements', + E::ts('The Stripe extension requires the mjwshared extension version %1 or greater but your system has version %2.', + [ + 1 => self::MIN_VERSION_MJWSHARED, + 2 => $extensions['values'][$extensions['id']]['version'] + ]), + E::ts('Stripe: Missing Requirements'), + \Psr\Log\LogLevel::ERROR, + 'fa-money' + ); + } + } + +} diff --git a/CRM/Stripe/Customer.php b/CRM/Stripe/Customer.php index 50b787f8ea257d49c1a34d5f90e8680628ddfed8..c974c25c52d6ee0c71ce099292e78565111ed210 100644 --- a/CRM/Stripe/Customer.php +++ b/CRM/Stripe/Customer.php @@ -24,7 +24,7 @@ class CRM_Stripe_Customer { 1 => [$params['contact_id'], 'String'], 2 => [$params['processor_id'], 'Positive'], ]; - + return CRM_Core_DAO::singleValueQuery("SELECT id FROM civicrm_stripe_customers @@ -108,39 +108,29 @@ class CRM_Stripe_Customer { } /** - * @param $params + * @param array $params + * @param \CRM_Core_Payment_Stripe $stripe * * @return \Stripe\ApiResource * @throws \CiviCRM_API3_Exception * @throws \Civi\Payment\Exception\PaymentProcessorException */ - public static function create($params) { - $requiredParams = ['contact_id', 'card_token', 'processor_id']; - // $optionalParams = ['email']; + public static function create($params, $stripe) { + $requiredParams = ['contact_id', 'processor_id']; foreach ($requiredParams as $required) { if (empty($params[$required])) { throw new \Civi\Payment\Exception\PaymentProcessorException('Stripe Customer (create): Missing required parameter: ' . $required); } } - $contactDisplayName = civicrm_api3('Contact', 'getvalue', [ - 'return' => 'display_name', - 'id' => $params['contact_id'], - ]); - - $stripeCustomerParams = [ - 'description' => $contactDisplayName . ' (CiviCRM)', - 'card' => $params['card_token'], - 'email' => CRM_Utils_Array::value('email', $params), - 'metadata' => ['civicrm_contact_id' => $params['contact_id']], - ]; + $stripeCustomerParams = self::getStripeCustomerMetadata($params); try { $stripeCustomer = \Stripe\Customer::create($stripeCustomerParams); } catch (Exception $e) { $err = CRM_Core_Payment_Stripe::parseStripeException('create_customer', $e, FALSE); - $errorMessage = CRM_Core_Payment_Stripe::handleErrorNotification($err, $params['stripe_error_url']); + $errorMessage = $stripe->handleErrorNotification($err, $params['stripe_error_url']); throw new \Civi\Payment\Exception\PaymentProcessorException('Failed to create Stripe Customer: ' . $errorMessage); } @@ -155,6 +145,61 @@ class CRM_Stripe_Customer { return $stripeCustomer; } + /** + * @param array $params + * @param \CRM_Core_Payment_Stripe $stripe + * @param string $stripeCustomerID + * + * @return \Stripe\Customer + * @throws \CiviCRM_API3_Exception + * @throws \Civi\Payment\Exception\PaymentProcessorException + */ + public static function updateMetadata($params, $stripe, $stripeCustomerID) { + $requiredParams = ['contact_id', 'processor_id']; + foreach ($requiredParams as $required) { + if (empty($params[$required])) { + throw new \Civi\Payment\Exception\PaymentProcessorException('Stripe Customer (updateMetadata): Missing required parameter: ' . $required); + } + } + + $stripeCustomerParams = self::getStripeCustomerMetadata($params); + + try { + $stripeCustomer = \Stripe\Customer::update($stripeCustomerID, $stripeCustomerParams); + } + catch (Exception $e) { + $err = CRM_Core_Payment_Stripe::parseStripeException('create_customer', $e, FALSE); + $errorMessage = $stripe->handleErrorNotification($err, $params['stripe_error_url']); + throw new \Civi\Payment\Exception\PaymentProcessorException('Failed to update Stripe Customer: ' . $errorMessage); + } + return $stripeCustomer; + } + + /** + * @param array $params + * Required: contact_id; Optional: email + * + * @return array + * @throws \CiviCRM_API3_Exception + */ + private static function getStripeCustomerMetadata($params) { + $contactDisplayName = civicrm_api3('Contact', 'getvalue', [ + 'return' => 'display_name', + 'id' => $params['contact_id'], + ]); + + $stripeCustomerParams = [ + 'name' => $contactDisplayName, + 'description' => 'CiviCRM: ' . civicrm_api3('Domain', 'getvalue', ['current_domain' => 1, 'return' => 'name']), + 'email' => CRM_Utils_Array::value('email', $params), + 'metadata' => [ + 'CiviCRM Contact ID' => $params['contact_id'], + 'CiviCRM URL' => CRM_Utils_System::url('civicrm/contact/view', "reset=1&cid={$params['contact_id']}", TRUE), + ], + ]; + return $stripeCustomerParams; + } + /** * Delete a Stripe customer from the CiviCRM database * diff --git a/CRM/Stripe/Upgrader.php b/CRM/Stripe/Upgrader.php index 70bfa5c295e4ddc66506b53262cc7ecc132f96df..30aacea59a5a12d2733e86d960f96051b9788cfd 100644 --- a/CRM/Stripe/Upgrader.php +++ b/CRM/Stripe/Upgrader.php @@ -1,8 +1,8 @@ <?php /** * Collection of upgrade steps. - * DO NOT USE a naming scheme other than upgrade_N, where N is an integer. - * Naming scheme upgrade_X_Y_Z is offically wrong! + * DO NOT USE a naming scheme other than upgrade_N, where N is an integer. + * Naming scheme upgrade_X_Y_Z is offically wrong! * https://chat.civicrm.org/civicrm/pl/usx3pfjzjbrhzpewuggu1e6ftw */ class CRM_Stripe_Upgrader extends CRM_Stripe_Upgrader_Base { @@ -56,7 +56,7 @@ class CRM_Stripe_Upgrader extends CRM_Stripe_Upgrader_Base { CRM_Core_DAO::executeQuery('ALTER TABLE civicrm_stripe_plans ADD COLUMN `processor_id` int(10) DEFAULT NULL COMMENT "ID from civicrm_payment_processor"'); CRM_Core_DAO::executeQuery('ALTER TABLE civicrm_stripe_subscriptions ADD COLUMN `processor_id` int(10) DEFAULT NULL COMMENT "ID from civicrm_payment_processor"'); } - return TRUE; + return TRUE; } @@ -70,14 +70,14 @@ class CRM_Stripe_Upgrader extends CRM_Stripe_Upgrader_Base { $config = CRM_Core_Config::singleton(); $dbName = DB::connect($config->dsn)->_db; - $null_count = CRM_Core_DAO::executeQuery('SELECT COUNT(*) FROM civicrm_stripe_customers where processor_id IS NULL') + + $null_count = CRM_Core_DAO::executeQuery('SELECT COUNT(*) FROM civicrm_stripe_customers where processor_id IS NULL') + CRM_Core_DAO::executeQuery('SELECT COUNT(*) FROM civicrm_stripe_plans where processor_id IS NULL') + CRM_Core_DAO::executeQuery('SELECT COUNT(*) FROM civicrm_stripe_subscriptions where processor_id IS NULL'); if ( $null_count == 0 ) { $this->ctx->log->info('Skipped civicrm_stripe update 5002. No nulls found in column processor_id in our tables.'); return TRUE; - } - else { + } + else { try { // Set processor ID if there's only one. $processorCount = civicrm_api3('PaymentProcessorType', 'get', array( @@ -109,8 +109,8 @@ class CRM_Stripe_Upgrader extends CRM_Stripe_Upgrader_Base { return TRUE; } - - /** + + /** * Add subscription_id column to civicrm_stripe_subscriptions table. * * @return TRUE on success @@ -131,11 +131,11 @@ class CRM_Stripe_Upgrader extends CRM_Stripe_Upgrader_Base { CRM_Core_DAO::executeQuery('ALTER TABLE civicrm_stripe_subscriptions ADD COLUMN `subscription_id` varchar(255) DEFAULT NULL COMMENT "Subscription ID from Stripe" FIRST'); CRM_Core_DAO::executeQuery('ALTER TABLE `civicrm_stripe_subscriptions` ADD UNIQUE KEY(`subscription_id`)'); - } - return TRUE; } - - /** + return TRUE; + } + + /** * Populates the subscription_id column in table civicrm_stripe_subscriptions. * * @return TRUE on success @@ -145,51 +145,43 @@ class CRM_Stripe_Upgrader extends CRM_Stripe_Upgrader_Base { $config = CRM_Core_Config::singleton(); $dbName = DB::connect($config->dsn)->_db; - $null_count = CRM_Core_DAO::executeQuery('SELECT COUNT(*) FROM civicrm_stripe_subscriptions where subscription_id IS NULL'); + $null_count = CRM_Core_DAO::executeQuery('SELECT COUNT(*) FROM civicrm_stripe_subscriptions where subscription_id IS NULL'); if ( $null_count == 0 ) { $this->ctx->log->info('Skipped civicrm_stripe update 5004. No nulls found in column subscription_id in our civicrm_stripe_subscriptions table.'); - } - else { - $customer_infos = CRM_Core_DAO::executeQuery("SELECT customer_id,processor_id + } + else { + $customer_infos = CRM_Core_DAO::executeQuery("SELECT customer_id,processor_id FROM `civicrm_stripe_subscriptions`;"); while ( $customer_infos->fetch() ) { $processor_id = $customer_infos->processor_id; $customer_id = $customer_infos->customer_id; - try { - $stripe_key = civicrm_api3('PaymentProcessor', 'getvalue', array( - 'return' => 'user_name', - 'id' => $processor_id, - )); - } - catch (Exception $e) { - Civi::log()->debug('Update 5004 failed. Has Stripe been removed as a payment processor?', $out = false); - return; - } - try { - \Stripe\Stripe::setApiKey($stripe_key); - $subscription = \Stripe\Subscription::all(array( - 'customer'=> $customer_id, - 'limit'=>1, - )); - } - catch (Exception $e) { - // Don't quit here. A missing customer in Stipe is OK. They don't exist, so they can't have a subscription. - Civi::log()->debug('Cannot find Stripe API key: ' . $e->getMessage()); - } - if (!empty($subscription['data'][0]['id'])) { - $query_params = array( - 1 => array($subscription['data'][0]['id'], 'String'), - 2 => array($customer_id, 'String'), - ); - CRM_Core_DAO::executeQuery('UPDATE civicrm_stripe_subscriptions SET subscription_id = %1 where customer_id = %2;', $query_params); - unset($subscription); - } + try { + $processor = new CRM_Core_Payment_Stripe('', civicrm_api3('PaymentProcessor', 'getsingle', ['id' => $processor_id])); + $processor->setAPIParams(); + + $subscription = \Stripe\Subscription::all(array( + 'customer'=> $customer_id, + 'limit'=>1, + )); + } + catch (Exception $e) { + // Don't quit here. A missing customer in Stipe is OK. They don't exist, so they can't have a subscription. + Civi::log()->debug('Cannot find Stripe API key: ' . $e->getMessage()); + } + if (!empty($subscription['data'][0]['id'])) { + $query_params = array( + 1 => array($subscription['data'][0]['id'], 'String'), + 2 => array($customer_id, 'String'), + ); + CRM_Core_DAO::executeQuery('UPDATE civicrm_stripe_subscriptions SET subscription_id = %1 where customer_id = %2;', $query_params); + unset($subscription); + } } } - return TRUE; + return TRUE; } - /** + /** * Add contribution_recur_id column to civicrm_stripe_subscriptions table. * * @return TRUE on success @@ -213,10 +205,10 @@ class CRM_Stripe_Upgrader extends CRM_Stripe_Upgrader_Base { CRM_Core_DAO::executeQuery('ALTER TABLE `civicrm_stripe_subscriptions` ADD INDEX(`contribution_recur_id`);'); CRM_Core_DAO::executeQuery('ALTER TABLE `civicrm_stripe_subscriptions` ADD CONSTRAINT `FK_civicrm_stripe_contribution_recur_id` FOREIGN KEY (`contribution_recur_id`) REFERENCES `civicrm_contribution_recur`(`id`) ON DELETE SET NULL ON UPDATE RESTRICT;'); } - return TRUE; + return TRUE; } - /** + /** * Method 1 for populating the contribution_recur_id column in the civicrm_stripe_subscriptions table. * ( A simple approach if that works if there have never been any susbcription edits in the Stripe UI. ) @@ -230,93 +222,93 @@ class CRM_Stripe_Upgrader extends CRM_Stripe_Upgrader_Base { $subscriptions = CRM_Core_DAO::executeQuery("SELECT invoice_id,is_live FROM `civicrm_stripe_subscriptions`;"); - while ( $subscriptions->fetch() ) { - $test_mode = (int)!$subscriptions->is_live; - try { - // Fetch the recurring contribution Id. - $recur_id = civicrm_api3('Contribution', 'getvalue', array( - 'sequential' => 1, - 'return' => "contribution_recur_id", - 'invoice_id' => $subscriptions->invoice_id, - 'contribution_test' => $test_mode, - )); - } - catch (CiviCRM_API3_Exception $e) { - // Don't quit here. If we can't find the recurring ID for a single customer, make a note in the error log and carry on. - Civi::log()->debug('Recurring contribution search: ' . $e->getMessage()); - } - if (!empty($recur_id)) { - $p = array( - 1 => array($recur_id, 'Integer'), - 2 => array($subscriptions->invoice_id, 'String'), - ); - CRM_Core_DAO::executeQuery('UPDATE civicrm_stripe_subscriptions SET contribution_recur_id = %1 WHERE invoice_id = %2;', $p); - } + while ( $subscriptions->fetch() ) { + $test_mode = (int)!$subscriptions->is_live; + try { + // Fetch the recurring contribution Id. + $recur_id = civicrm_api3('Contribution', 'getvalue', array( + 'sequential' => 1, + 'return' => "contribution_recur_id", + 'invoice_id' => $subscriptions->invoice_id, + 'contribution_test' => $test_mode, + )); + } + catch (CiviCRM_API3_Exception $e) { + // Don't quit here. If we can't find the recurring ID for a single customer, make a note in the error log and carry on. + Civi::log()->debug('Recurring contribution search: ' . $e->getMessage()); } - return TRUE; + if (!empty($recur_id)) { + $p = array( + 1 => array($recur_id, 'Integer'), + 2 => array($subscriptions->invoice_id, 'String'), + ); + CRM_Core_DAO::executeQuery('UPDATE civicrm_stripe_subscriptions SET contribution_recur_id = %1 WHERE invoice_id = %2;', $p); + } + } + return TRUE; } - /** + /** * Method 2 for populating the contribution_recur_id column in the civicrm_stripe_subscriptions table. Uncomment this and comment 5006. * ( A more convoluted approach that works if there HAVE been susbcription edits in the Stripe UI. ) * @return TRUE on success. Please let users uncomment this as needed and increment past 5007 for the next upgrade. * @throws Exception */ -/* - public function upgrade_5007() { - $config = CRM_Core_Config::singleton(); - $dbName = DB::connect($config->dsn)->_db; - - $subscriptions = CRM_Core_DAO::executeQuery("SELECT customer_id,is_live,processor_id - FROM `civicrm_stripe_subscriptions`;"); - while ( $subscriptions->fetch() ) { - $test_mode = (int)!$subscriptions->is_live; - $p = array( - 1 => array($subscriptions->customer_id, 'String'), - 2 => array($subscriptions->is_live, 'Integer'), - ); - $customer = CRM_Core_DAO::executeQuery("SELECT email - FROM `civicrm_stripe_customers` WHERE id = %1 AND is_live = %2;", $p); - $customer->fetch(); - // Try the billing email first, since that's what we send to Stripe. - try { - $contact = civicrm_api3('Email', 'get', array( - 'sequential' => 1, - 'return' => "contact_id", - 'is_billing' => 1, - 'email' => $customer->email, - 'api.ContributionRecur.get' => array('return' => "id", 'contact_id' => "\$value.contact_id", 'contribution_status_id' => "In Progress"), - )); - } - catch (CiviCRM_API3_Exception $e) { - // Uh oh, that didn't work. Try to retrieve the recurring id using the primary email. - $contact = civicrm_api3('Contact', 'get', array( - 'sequential' => 1, - 'return' => "id", - 'email' => $customer->email, - 'api.ContributionRecur.get' => array('sequential' => 1, 'return' => "id", 'contact_id' => "\$values.id", 'contribution_status_id' => "In Progress"), - )); - } + /* + public function upgrade_5007() { + $config = CRM_Core_Config::singleton(); + $dbName = DB::connect($config->dsn)->_db; + + $subscriptions = CRM_Core_DAO::executeQuery("SELECT customer_id,is_live,processor_id + FROM `civicrm_stripe_subscriptions`;"); + while ( $subscriptions->fetch() ) { + $test_mode = (int)!$subscriptions->is_live; + $p = array( + 1 => array($subscriptions->customer_id, 'String'), + 2 => array($subscriptions->is_live, 'Integer'), + ); + $customer = CRM_Core_DAO::executeQuery("SELECT email + FROM `civicrm_stripe_customers` WHERE id = %1 AND is_live = %2;", $p); + $customer->fetch(); + // Try the billing email first, since that's what we send to Stripe. + try { + $contact = civicrm_api3('Email', 'get', array( + 'sequential' => 1, + 'return' => "contact_id", + 'is_billing' => 1, + 'email' => $customer->email, + 'api.ContributionRecur.get' => array('return' => "id", 'contact_id' => "\$value.contact_id", 'contribution_status_id' => "In Progress"), + )); + } + catch (CiviCRM_API3_Exception $e) { + // Uh oh, that didn't work. Try to retrieve the recurring id using the primary email. + $contact = civicrm_api3('Contact', 'get', array( + 'sequential' => 1, + 'return' => "id", + 'email' => $customer->email, + 'api.ContributionRecur.get' => array('sequential' => 1, 'return' => "id", 'contact_id' => "\$values.id", 'contribution_status_id' => "In Progress"), + )); + } - if (!empty($contact['values'][0]['api.ContributionRecur.get']['values'][0]['id'])) { - $recur_id = $contact['values'][0]['api.ContributionRecur.get']['values'][0]['id']; - $p = array( - 1 => array($recur_id, 'Integer'), - 2 => array($subscriptions->customer_id, 'String'), - ); - CRM_Core_DAO::executeQuery('UPDATE civicrm_stripe_subscriptions SET contribution_recur_id = %1 WHERE customer_id = %2;', $p); - } else { - // Crap. - $this->ctx->log->info('Update 5007 failed. Consider adding recurring IDs manuallly to civicrm_stripe_subscriptions. '); - return; + if (!empty($contact['values'][0]['api.ContributionRecur.get']['values'][0]['id'])) { + $recur_id = $contact['values'][0]['api.ContributionRecur.get']['values'][0]['id']; + $p = array( + 1 => array($recur_id, 'Integer'), + 2 => array($subscriptions->customer_id, 'String'), + ); + CRM_Core_DAO::executeQuery('UPDATE civicrm_stripe_subscriptions SET contribution_recur_id = %1 WHERE customer_id = %2;', $p); + } else { + // Crap. + $this->ctx->log->info('Update 5007 failed. Consider adding recurring IDs manuallly to civicrm_stripe_subscriptions. '); + return; + } } - } - return TRUE; - } -*/ + return TRUE; + } + */ - /** + /** * Add change default NOT NULL to NULL in vestigial invoice_id column in civicrm_stripe_subscriptions table if needed. (issue #192) * * @return TRUE on success @@ -338,7 +330,7 @@ class CRM_Stripe_Upgrader extends CRM_Stripe_Upgrader_Base { MODIFY COLUMN `invoice_id` varchar(255) NULL default "" COMMENT "Safe to remove this column if the update retrieving subscription IDs completed satisfactorily."'); } - return TRUE; + return TRUE; } /** @@ -407,4 +399,25 @@ class CRM_Stripe_Upgrader extends CRM_Stripe_Upgrader_Base { return TRUE; } + public function upgrade_5023() { + $this->ctx->log->info('Applying Stripe update 5023. Swap over public/secret key settings'); + $stripeProcessors = civicrm_api3('PaymentProcessor', 'get', [ + 'payment_processor_type_id' => "Stripe", + ]); + foreach ($stripeProcessors['values'] as $processor) { + if ((substr($processor['user_name'], 0, 3) === 'sk_') + && (substr($processor['password'], 0, 3) === 'pk_')) { + // Need to switch over parameters + $createParams = [ + 'id' => $processor['id'], + 'user_name' => $processor['password'], + 'password' => $processor['user_name'], + ]; + civicrm_api3('PaymentProcessor', 'create', $createParams); + } + } + CRM_Utils_System::flushCache(); + return TRUE; + } + } diff --git a/CRM/Stripe/Webhook.php b/CRM/Stripe/Webhook.php index fe0e3733fb749239f7773c63a3600520388bb63b..07d4c3f74a8afaff8806b03420fd5ce601a1b627 100644 --- a/CRM/Stripe/Webhook.php +++ b/CRM/Stripe/Webhook.php @@ -20,13 +20,16 @@ class CRM_Stripe_Webhook { $result = civicrm_api3('PaymentProcessor', 'get', [ 'class_name' => 'Payment_Stripe', 'is_active' => 1, + 'domain_id' => CRM_Core_Config::domainID(), ]); foreach ($result['values'] as $paymentProcessor) { $messageTexts = []; $webhook_path = self::getWebhookPath($paymentProcessor['id']); - \Stripe\Stripe::setApiKey(CRM_Core_Payment_Stripe::getSecretKey($paymentProcessor)); + $processor = new CRM_Core_Payment_Stripe('', civicrm_api3('PaymentProcessor', 'getsingle', ['id' => $paymentProcessor['id']])); + $processor->setAPIParams(); + try { $webhooks = \Stripe\WebhookEndpoint::all(["limit" => 100]); } @@ -35,10 +38,7 @@ class CRM_Stripe_Webhook { $messages[] = new CRM_Utils_Check_Message( 'stripe_webhook', $error, - E::ts('Stripe Payment Processor: %1 (%2)', [ - 1 => $paymentProcessor['name'], - 2 => $paymentProcessor['id'], - ]), + self::getTitle($paymentProcessor), \Psr\Log\LogLevel::ERROR, 'fa-money' ); @@ -91,9 +91,12 @@ class CRM_Stripe_Webhook { } } else { - $messageTexts[] = E::ts('Stripe Webhook missing! Please visit <a href="%1">Fix Stripe Webhook</a> to fix.', [ - 1 => CRM_Utils_System::url('civicrm/stripe/fix-webhook'), - ]); + $messageTexts[] = E::ts('Stripe Webhook missing! Please visit <a href="%1">Fix Stripe Webhook</a> to fix.<br />Expected webhook path is: <a href="%2" target="_blank">%2</a>', + [ + 1 => CRM_Utils_System::url('civicrm/stripe/fix-webhook'), + 2 => $webhook_path, + ] + ); } } @@ -101,10 +104,7 @@ class CRM_Stripe_Webhook { $messages[] = new CRM_Utils_Check_Message( 'stripe_webhook', $messageText, - E::ts('Stripe Payment Processor Webhook: %1 (%2)', [ - 1 => $paymentProcessor['name'], - 2 => $paymentProcessor['id'], - ]), + self::getTitle($paymentProcessor), \Psr\Log\LogLevel::WARNING, 'fa-money' ); @@ -112,13 +112,30 @@ class CRM_Stripe_Webhook { } } + /** + * Get the error message title for the system check + * @param array $paymentProcessor + * + * @return string + */ + private static function getTitle($paymentProcessor) { + if (!empty($paymentProcessor['is_test'])) { + $paymentProcessor['name'] .= ' (test)'; + } + return E::ts('Stripe Payment Processor: %1 (%2)', [ + 1 => $paymentProcessor['name'], + 2 => $paymentProcessor['id'], + ]); + } + /** * Create a new webhook for payment processor * * @param int $paymentProcessorId */ public static function createWebhook($paymentProcessorId) { - \Stripe\Stripe::setApiKey(CRM_Core_Payment_Stripe::getSecretKeyById($paymentProcessorId)); + $processor = new CRM_Core_Payment_Stripe('', civicrm_api3('PaymentProcessor', 'getsingle', ['id' => $paymentProcessorId])); + $processor->setAPIParams(); $params = [ 'enabled_events' => self::getDefaultEnabledEvents(), diff --git a/api/v3/Stripe/Ipn.php b/api/v3/Stripe/Ipn.php index 07cde844b0b9b456b0803c04c4f750a21fd315c3..46c7be7203edb257d69d60c0cad07d343cb97ded 100644 --- a/api/v3/Stripe/Ipn.php +++ b/api/v3/Stripe/Ipn.php @@ -1,16 +1,16 @@ <?php /** - * This api allows you to replay Stripe events. + * This api allows you to replay Stripe events. * * You can either pass the id of an entry in the System Log (which can * be populated with the Stripe.PopulateLog call) or you can pass a * event id from Stripe directly. * * When processing an event, the event will always be re-fetched from the - * Stripe server first, so this will not work while offline or with + * Stripe server first, so this will not work while offline or with * events that were not generated by the Stripe server. - */ + */ /** * Stripe.Ipn API specification @@ -57,12 +57,9 @@ function civicrm_api3_stripe_Ipn($params) { throw new API_Exception('Please pass the payment processor id (ppid) if using evtid.', 3236); } $ppid = $params['ppid']; - $results = civicrm_api3('PaymentProcessor', 'getsingle', array('id' => $ppid)); - // YES! I know, password and user are backwards. wtf?? - $sk = $results['user_name']; + $processor = new CRM_Core_Payment_Stripe('', civicrm_api3('PaymentProcessor', 'getsingle', ['id' => $ppid])); + $processor->setAPIParams(); - require_once ("vendor/stripe/stripe-php/init.php"); - \Stripe\Stripe::setApiKey($sk); $object = \Stripe\Event::retrieve($params['evtid']); } // Avoid a SQL error if this one has been processed already. @@ -74,7 +71,7 @@ function civicrm_api3_stripe_Ipn($params) { return civicrm_api3_create_error("Ipn already processed."); } if (class_exists('CRM_Core_Payment_StripeIPN')) { - // The $_GET['processor_id'] value is normally set by + // The $_GET['processor_id'] value is normally set by // CRM_Core_Payment::handlePaymentMethod $_GET['processor_id'] = $ppid; $ipnClass = new CRM_Core_Payment_StripeIPN($object); diff --git a/api/v3/Stripe/Listevents.php b/api/v3/Stripe/Listevents.php index e0d5535ae3df613968189897499aa9de4a24187d..b1b02ebfd8720b0695652a3d4839a8fe4b834198 100644 --- a/api/v3/Stripe/Listevents.php +++ b/api/v3/Stripe/Listevents.php @@ -9,7 +9,7 @@ /** * Stripe.ListEvents API specification - * + * * * @param array $spec description of fields supported by this API call * @return void @@ -17,12 +17,13 @@ */ function _civicrm_api3_stripe_ListEvents_spec(&$spec) { $spec['ppid']['title'] = ts("Use the given Payment Processor ID"); - $spec['ppid']['type'] = CRM_Utils_Type::T_INT; + $spec['ppid']['type'] = CRM_Utils_Type::T_INT; + $spec['ppid']['api.required'] = TRUE; $spec['type']['title'] = ts("Limit to the given Stripe events type, defaults to invoice.payment_succeeded."); $spec['type']['api.default'] = 'invoice.payment_succeeded'; $spec['limit']['title'] = ts("Limit number of results returned (100 is max)"); $spec['starting_after']['title'] = ts("Only return results after this event id."); - $spec['output']['api.default'] = 'brief'; + $spec['output']['api.default'] = 'brief'; $spec['output']['title'] = ts("How to format the output, brief or raw. Defaults to brief."); } @@ -122,18 +123,17 @@ function civicrm_api3_stripe_VerifyEventType($eventType) { * Process parameters to determine ppid and sk. * * @param array $params + * + * @return array + * @throws \API_Exception */ function civicrm_api3_stripe_ProcessParams($params) { - $ppid = NULL; $type = NULL; $created = NULL; $limit = NULL; $starting_after = NULL; $sk = NULL; - if (array_key_exists('ppid', $params) ) { - $ppid = $params['ppid']; - } if (array_key_exists('created', $params) ) { $created = $params['created']; } @@ -144,29 +144,6 @@ function civicrm_api3_stripe_ProcessParams($params) { $starting_after = $params['starting_after']; } - // Select the right payment processor to use. - if ($ppid) { - $query_params = array('id' => $ppid); - } - else { - // By default, select the live stripe processor (we expect there to be - // only one). - $query_params = array('class_name' => 'Payment_Stripe', 'is_test' => 0); - } - try { - $results = civicrm_api3('PaymentProcessor', 'getsingle', $query_params); - // YES! I know, password and user are backwards. wtf?? - $sk = $results['user_name']; - } - catch (CiviCRM_API3_Exception $e) { - if(preg_match('/Expected one PaymentProcessor but/', $e->getMessage())) { - throw new API_Exception("Expected one live Stripe payment processor, but found none or more than one. Please specify ppid=.", 1234); - } - else { - throw new API_Exception("Error getting the Stripe Payment Processor to use", 1235); - } - } - // Check to see if we should filter by type. if (array_key_exists('type', $params) ) { // Validate - since we will be appending this to an URL. @@ -185,7 +162,7 @@ function civicrm_api3_stripe_ProcessParams($params) { throw new API_Exception("Created can only be passed in programatically as an array", 1237); } } - return array('sk' => $sk, 'type' => $type, 'created' => $created, 'limit' => $limit, 'starting_after' => $starting_after); + return ['type' => $type, 'created' => $created, 'limit' => $limit, 'starting_after' => $starting_after]; } /** @@ -199,7 +176,6 @@ function civicrm_api3_stripe_ProcessParams($params) { */ function civicrm_api3_stripe_Listevents($params) { $parsed = civicrm_api3_stripe_ProcessParams($params); - $sk = $parsed['sk']; $type = $parsed['type']; $created = $parsed['created']; $limit = $parsed['limit']; @@ -218,9 +194,10 @@ function civicrm_api3_stripe_Listevents($params) { if ($starting_after) { $args['starting_after'] = $starting_after; } - - require_once ("vendor/stripe/stripe-php/init.php"); - \Stripe\Stripe::setApiKey($sk); + + $processor = new CRM_Core_Payment_Stripe('', civicrm_api3('PaymentProcessor', 'getsingle', ['id' => $params['ppid']])); + $processor->setAPIParams(); + $data_list = \Stripe\Event::all($args); if (array_key_exists('error', $data_list)) { $err = $data_list['error']; diff --git a/api/v3/StripeCustomer.php b/api/v3/StripeCustomer.php index 319bccdee359b56dc91b1a6cf180256cd1d1d026..ee254ee5460b50abb09c138c07a05f1c9ec87792 100644 --- a/api/v3/StripeCustomer.php +++ b/api/v3/StripeCustomer.php @@ -258,8 +258,7 @@ function civicrm_api3_stripe_customer_updatestripemetadata($params) { throw new CiviCRM_API3_Exception('Could not find contact ID for stripe customer: ' . $customerId); } - $paymentProcessor = \Civi\Payment\System::singleton() - ->getById($customerParams['processor_id']); + $paymentProcessor = \Civi\Payment\System::singleton()->getById($customerParams['processor_id']); $paymentProcessor->setAPIParams(); // Get the stripe customer from stripe @@ -267,7 +266,7 @@ function civicrm_api3_stripe_customer_updatestripemetadata($params) { $stripeCustomer = \Stripe\Customer::retrieve($customerId); } catch (Exception $e) { $err = CRM_Core_Payment_Stripe::parseStripeException('retrieve_customer', $e, FALSE); - $errorMessage = CRM_Core_Payment_Stripe::handleErrorNotification($err, NULL); + $errorMessage = $paymentProcessor->handleErrorNotification($err, NULL); throw new \Civi\Payment\Exception\PaymentProcessorException('Failed to retrieve Stripe Customer: ' . $errorMessage); } diff --git a/api/v3/StripeSubscription.php b/api/v3/StripeSubscription.php index 3eeee290233570f1131dcbaaff8f0e4adffb2729..d152780a74030faaa2d3a0bce8a490292e061c2c 100644 --- a/api/v3/StripeSubscription.php +++ b/api/v3/StripeSubscription.php @@ -149,9 +149,10 @@ function civicrm_api3_stripe_subscription_import($params) { $paymentProcessor = \Civi\Payment\System::singleton()->getById($params['payment_processor_id'])->getPaymentProcessor(); - // Now re-retrieve the data from Stripe to ensure it's legit. - \Stripe\Stripe::setApiKey($paymentProcessor['user_name']); + $processor = new CRM_Core_Payment_Stripe('', $paymentProcessor); + $processor->setAPIParams(); + // Now re-retrieve the data from Stripe to ensure it's legit. $stripeSubscription = \Stripe\Subscription::retrieve($params['subscription_id']); // Create the stripe customer in CiviCRM diff --git a/composer.lock b/composer.lock index d488bb77a655253803f2ec3e9451bbdaeaa370a0..b305a1239ca6f8085c41fb0f451bf0b8f9867050 100644 --- a/composer.lock +++ b/composer.lock @@ -8,16 +8,16 @@ "packages": [ { "name": "stripe/stripe-php", - "version": "v6.40.0", + "version": "v6.43.1", "source": { "type": "git", "url": "https://github.com/stripe/stripe-php.git", - "reference": "9c22ffab790ef4dae0f371929de50e8b53c9ec8d" + "reference": "42fcdaf99c44bb26937223f8eae1f263491d5ab8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/stripe/stripe-php/zipball/9c22ffab790ef4dae0f371929de50e8b53c9ec8d", - "reference": "9c22ffab790ef4dae0f371929de50e8b53c9ec8d", + "url": "https://api.github.com/repos/stripe/stripe-php/zipball/42fcdaf99c44bb26937223f8eae1f263491d5ab8", + "reference": "42fcdaf99c44bb26937223f8eae1f263491d5ab8", "shasum": "" }, "require": { @@ -60,7 +60,7 @@ "payment processing", "stripe" ], - "time": "2019-06-27T23:24:51+00:00" + "time": "2019-08-29T16:56:12+00:00" } ], "packages-dev": [], diff --git a/css/elements.css b/css/elements.css new file mode 100644 index 0000000000000000000000000000000000000000..2718ae2d807bb3bf698e73e1d728b6a71d485816 --- /dev/null +++ b/css/elements.css @@ -0,0 +1,14 @@ +#card-element { + padding: 2%; + margin: 2% auto; + max-width: 800px; + background-color: ghostwhite; + -webkit-box-shadow: 10px 10px 7px -6px rgba(0,0,0,0.81); + -moz-box-shadow: 10px 10px 7px -6px rgba(0, 0, 0, 0.81); + box-shadow: 10px 10px 7px -6px rgba(0, 0, 0, 0.81); +} + +#card-errors { + margin: 2%; + display: none; +} diff --git a/css/elements.min.css b/css/elements.min.css new file mode 100644 index 0000000000000000000000000000000000000000..47a21a67c10ff4d286cd11b211b66bbf93b76e2e --- /dev/null +++ b/css/elements.min.css @@ -0,0 +1 @@ +#card-element{padding:2%;margin:2% auto;max-width:800px;background-color:ghostwhite;-webkit-box-shadow:10px 10px 7px -6px rgba(0,0,0,0.81);-moz-box-shadow:10px 10px 7px -6px rgba(0,0,0,0.81);box-shadow:10px 10px 7px -6px rgba(0,0,0,0.81)}#card-errors{margin:2%;display:none} \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index ee55a3a9621dd772cb0921a4f1eda99b7d504a98..9c447d99616df107efdf95ba7009452469974064 100644 --- a/docs/index.md +++ b/docs/index.md @@ -8,13 +8,21 @@ Latest releases can be found here: https://civicrm.org/extensions/stripe-payment View this extension in the [Extension Directory](https://civicrm.org/extensions/stripe-payment-processor). ## Compatibility / Requirements -* CiviCRM 5.10+ -* PHP 7.0+ +* CiviCRM 5.13+ +* PHP 7.1+ * Jquery 1.10 (Use jquery_update module on Drupal). * Drupal 7 / Joomla / Wordpress (latest supported release). *Not currently tested with other CMS but it may work.* -* Stripe API version: 2019-02-19 +* Stripe API version: 2019-09-09 * Drupal webform_civicrm 7.x-4.22+ (if using webform integration) +If using test mode with drupal webform_civicrm you need this patch: https://github.com/colemanw/webform_civicrm/pull/266 + +## Troubleshooting +Under *Administer->CiviContribute->Stripe Settings* you can find a setting: +* Enable Stripe Javascript debugging? + +This can be switched on to output debug info to the browser console and can be used to debug problems with submitting your payments. + ## Credits Current Maintainer: Matthew Wire - https://www.mjwconsult.co.uk diff --git a/docs/release/release_notes.md b/docs/release/release_notes.md index 14dea693632db379375b525554a00f1b69629403..94554e240ec38f68364e22f5924ffee280bd08f6 100644 --- a/docs/release/release_notes.md +++ b/docs/release/release_notes.md @@ -1,3 +1,54 @@ +## Release 6.0.beta1 + +*Thanks to Rich Lott (@artfulrobot) for contributing and testing release.* +* We don't need to confirm the payment until we capture it +* payment method id is not required when passing in an existing payment intent +* Add minified versions of js/css +* Remove onclick attribute from submit form so that CiviContribute forms do stripe processing before submission +* Description and Customer fields in Stripe backend - fixes #78 + +## Release 6.0.alpha3 + +* Support recurring payments with paymentIntents/Elements. Cancel subscription with Stripe when we reach recurring end date +* **Update required Stripe API version to 2019-09-09** +* Handle confirmation pages properly for contribution pages (make sure we pass through paymentIntentID). +* Handle card declined on client side. +* Support creating recurring payment (subscription). +* Handle IPN events for charges / invoices (support cancel/refund etc). +* Add basic support for PaymentProcessor.refund API. +* Remove membership_type_tag from plan name. + +## Release 6.0.alpha2 + +* Support Drupal Webform CiviCRM. +* Support Event Registration. +* Support Confirm/Thankyou pages on contribution pages / events. +* Support cards using 3dsecure and cards not using 3dsecure. + +### Not Supported (should be in final 6.0 release): +* Recurring payments. +* Backend payments. + +## Release 6.0.alpha1 + +* ONLY contribution pages with no confirm pages are supported. + +## Release 6.0 (not yet released) + +**This is a major new release. You cannot rollback once you've upgraded.** + +**This extension REQUIRES the mjwshared extension available here: https://lab.civicrm.org/extensions/mjwshared** + +* Use Stripe Elements: https://stripe.com/payments/elements. +* Use PaymentIntents and comply with the European SCA directive (https://stripe.com/docs/strong-customer-authentication). +* Require Stripe API Version: 2019-09-09 and ensure that all codepaths specify the API version. +* Switch publishable key/secret key in settings (upgrader does this automatically) so they are now "correct" per CiviCRM settings pages. +* Support cards using 3dsecure and cards not using 3dsecure (workflows with Stripe are slightly different but both are now handled). + +### Not supported +* CiviCRM Event Cart (requires additional funding, changes should probably be made in CiviCRM core to standardize that workflow rather than adding support via this extension). +* Card payments via the admin backend (this was supported in 5.4.1 but has unresolved issues with Stripe Elements when used via popup forms). + ## Release 5.4.1 * Don't overwrite system messages when performing webhook checks. * Add form to handle creating/updating webhooks instead of automatically during system check (Thanks @artfulrobot) diff --git a/info.xml b/info.xml index ddbb27c1cd0e39dd70fd1a728dc1b93c691386b0..e48e8c149137e4b9b0f4c62ca1fcd76c2acd0fe4 100644 --- a/info.xml +++ b/info.xml @@ -1,7 +1,7 @@ <?xml version="1.0"?> <extension key="com.drastikbydesign.stripe" type="module"> <file>stripe</file> - <name>Stripe</name> + <name>Stripe (SCA payments development version)</name> <description>Stripe Payment Processor</description> <urls> <url desc="Main Extension Page">https://lab.civicrm.org/extensions/stripe</url> @@ -12,15 +12,18 @@ <author>Matthew Wire (MJW Consulting)</author> <email>mjw@mjwconsult.co.uk</email> </maintainer> - <releaseDate>2019-07-21</releaseDate> - <version>5.4.1</version> - <develStage>stable</develStage> + <releaseDate>2019-09-12</releaseDate> + <version>6.0.beta1</version> + <develStage>beta</develStage> <compatibility> <ver>5.13</ver> </compatibility> <comments>Original Author: Joshua Walker (drastik) - Drastik by Design. Jamie Mcclelland (ProgressiveTech) did a lot of the 5.x compatibility work. </comments> + <requires> + <ext>mjwshared</ext> + </requires> <civix> <namespace>CRM/Stripe</namespace> </civix> diff --git a/js/civicrm_stripe.js b/js/civicrm_stripe.js index 54aa160d169235c45fa94027490ea1382ec76d7c..f29d7dd90a1118f967ac3acc9fb456a88b6d4d1a 100644 --- a/js/civicrm_stripe.js +++ b/js/civicrm_stripe.js @@ -1,62 +1,105 @@ /** - * @file * JS Integration between CiviCRM & Stripe. */ CRM.$(function($) { - // Response from Stripe.createToken. - function stripeResponseHandler(status, response) { - $form = getBillingForm(); - $submit = getBillingSubmit(); - - if (response.error) { - $('html, body').animate({scrollTop: 0}, 300); - // Show the errors on the form. - if ($(".messages.crm-error.stripe-message").length > 0) { - $(".messages.crm-error.stripe-message").slideUp(); - $(".messages.crm-error.stripe-message:first").remove(); - } - $form.prepend('<div class="messages alert alert-block alert-danger error crm-error stripe-message">' - + '<strong>Payment Error Response:</strong>' - + '<ul id="errorList">' - + '<li>Error: ' + response.error.message + '</li>' - + '</ul>' - + '</div>'); - - removeCCDetails($form, true); - $form.data('submitted', false); - $submit.prop('disabled', false); - } - else { - var token = response['id']; - // Update form with the token & submit. - removeCCDetails($form, false); - $form.find("input#stripe-token").val(token); + var stripe; + var card; + var form; + var submitButton; + var stripeLoading = false; - // Disable unload event handler - window.onbeforeunload = null; + function paymentIntentSuccessHandler(paymentIntent) { + debugging('paymentIntent confirmation success'); + + // Insert the token ID into the form so it gets submitted to the server + var hiddenInput = document.createElement('input'); + hiddenInput.setAttribute('type', 'hidden'); + hiddenInput.setAttribute('name', 'paymentIntentID'); + hiddenInput.setAttribute('value', paymentIntent.id); + form.appendChild(hiddenInput); + + // Submit the form + form.submit(); + } - // Restore any onclickAction that was removed. - $submit.attr('onclick', onclickAction); + function displayError(result) { + // Display error.message in your UI. + debugging('error: ' + result.error.message); + // Inform the user if there was an error + var errorElement = document.getElementById('card-errors'); + errorElement.style.display = 'block'; + errorElement.textContent = result.error.message; + document.querySelector('#billing-payment-block').scrollIntoView(); + window.scrollBy(0, -50); + submitButton.removeAttribute('disabled'); + } + + function handleCardPayment() { + debugging('handle card payment'); + stripe.createPaymentMethod('card', card).then(function (result) { + if (result.error) { + // Show error in payment form + displayError(result); + } + else { + // Send paymentMethod.id to server + var url = CRM.url('civicrm/stripe/confirm-payment'); + $.post(url, { + payment_method_id: result.paymentMethod.id, + amount: getTotalAmount(), + currency: CRM.vars.stripe.currency, + id: CRM.vars.stripe.id, + }).then(function (result) { + // Handle server response (see Step 3) + handleServerResponse(result); + }); + } + }); + } - // This triggers submit without generating a submit event (so we don't run submit handler again) - $form.get(0).submit(); + function handleServerResponse(result) { + debugging('handleServerResponse'); + if (result.error) { + // Show error from server on payment form + displayError(result); + } else if (result.requires_action) { + // Use Stripe.js to handle required card action + handleAction(result); + } else { + // All good, we can submit the form + paymentIntentSuccessHandler(result.paymentIntent); } } + function handleAction(response) { + stripe.handleCardAction( + response.payment_intent_client_secret + ).then(function(result) { + if (result.error) { + // Show error in payment form + displayError(result); + } else { + // The card action has been handled + // The PaymentIntent can be confirmed again on the server + paymentIntentSuccessHandler(result.paymentIntent); + } + }); + } + // Prepare the form. var onclickAction = null; $(document).ready(function() { // Disable the browser "Leave Page Alert" which is triggered because we mess with the form submit function. window.onbeforeunload = null; + // Load Stripe onto the form. - loadStripeBillingBlock(); - $submit = getBillingSubmit(); + checkAndLoad(); // Store and remove any onclick Action currently assigned to the form. // We will re-add it if the transaction goes through. - onclickAction = $submit.attr('onclick'); - $submit.removeAttr('onclick'); + //onclickAction = submitButton.getAttribute('onclick'); + //submitButton.removeAttribute('onclick'); // Quickform doesn't add hidden elements via standard method. On a form where payment processor may // be loaded via initial form load AND ajax (eg. backend live contribution page with payproc dropdown) @@ -82,7 +125,7 @@ CRM.$(function($) { // There is. Check if the selected payment processor is different // from the one we think we should be using. var ppid = $('#payment_processor_id').val(); - if (ppid != $('#stripe-id').val()) { + if (ppid != CRM.vars.stripe.id) { debugging('payment processor changed to id: ' + ppid); // It is! See if the new payment processor is also a Stripe // Payment processor. First, find out what the stripe @@ -106,143 +149,159 @@ CRM.$(function($) { if (pub_key) { // It is a stripe payment processor, so update the key. debugging("Setting new stripe key to: " + pub_key); - $('#stripe-pub-key').val(pub_key); + CRM.vars.stripe.publishableKey = pub_key; } else { debugging("New payment processor is not Stripe, setting stripe-pub-key to null"); - $('#stripe-pub-key').val(null); + CRM.vars.stripe.publishableKey = null; } // Now reload the billing block. - loadStripeBillingBlock(); + checkAndLoad(); }); }); } } - loadStripeBillingBlock(); + checkAndLoad(); } }); - function loadStripeBillingBlock() { - // Setup Stripe.Js - var $stripePubKey = $('#stripe-pub-key'); + function checkAndLoad() { + if (typeof CRM.vars.stripe === 'undefined') { + debugging('CRM.vars.stripe not defined!'); + return; + } - if ($stripePubKey.length) { - if (!$().Stripe) { - $.getScript('https://js.stripe.com/v2/', function () { - Stripe.setPublishableKey($('#stripe-pub-key').val()); - }); + if (typeof Stripe === 'undefined') { + if (stripeLoading) { + return; } + stripeLoading = true; + debugging('Stripe.js is not loaded!'); + + $.getScript("https://js.stripe.com/v3", function () { + debugging("Script loaded and executed."); + stripeLoading = false; + loadStripeBillingBlock(); + }); } + else { + loadStripeBillingBlock(); + } + } + + function loadStripeBillingBlock() { + stripe = Stripe(CRM.vars.stripe.publishableKey); + var elements = stripe.elements(); + + var style = { + base: { + fontSize: '20px', + }, + }; + + // Create an instance of the card Element. + card = elements.create('card', {style: style}); + card.mount('#card-element'); + + // Hide the CiviCRM postcode field so it will still be submitted but will contain the value set in the stripe card-element. + document.getElementsByClassName('billing_postal_code-' + CRM.vars.stripe.billingAddressID + '-section')[0].setAttribute('hidden', true); + card.addEventListener('change', function(event) { + updateFormElementsFromCreditCardDetails(event); + }); - // Get the form containing payment details - $form = getBillingForm(); - if (!$form.length) { + // Get the form containing payment details + form = getBillingForm(); + if (typeof form.length === 'undefined' || form.length === 0) { debugging('No billing form!'); return; } - $submit = getBillingSubmit(); + submitButton = getBillingSubmit(); // If another submit button on the form is pressed (eg. apply discount) // add a flag that we can set to stop payment submission - $form.data('submit-dont-process', '0'); + form.dataset.submitdontprocess = false; + // Find submit buttons which should not submit payment - $form.find('[type="submit"][formnovalidate="1"], ' + + var nonPaymentSubmitButtons = form.querySelectorAll('[type="submit"][formnovalidate="1"], ' + '[type="submit"][formnovalidate="formnovalidate"], ' + '[type="submit"].cancel, ' + - '[type="submit"].webform-previous').click( function() { - debugging('adding submit-dont-process'); - $form.data('submit-dont-process', 1); - }); + '[type="submit"].webform-previous'), i; + for (i = 0; i < nonPaymentSubmitButtons.length; ++i) { + nonPaymentSubmitButtons[i].addEventListener('click', function () { + debugging('adding submitdontprocess'); + form.dataset.submitdontprocess = true; + }); + } - $submit.click( function(event) { + submitButton.addEventListener('click', function(event) { // Take over the click function of the form. - debugging('clearing submit-dont-process'); - $form.data('submit-dont-process', 0); + debugging('clearing submitdontprocess'); + form.dataset.submitdontprocess = false; // Run through our own submit, that executes Stripe submission if // appropriate for this submit. - var ret = submit(event); - if (ret) { - // True means it's not our form. We are bailing and not trying to - // process Stripe. - // Restore any onclickAction that was removed. - $form = getBillingForm(); - $submit = getBillingSubmit(); - $submit.attr('onclick', onclickAction); - $form.get(0).submit(); - return true; - } - // Otherwise, this is a stripe submission - don't handle normally. - // The code for completing the submission is all managed in the - // stripe handler (stripeResponseHandler) which gets execute after - // stripe finishes. - return false; + return submit(event); }); - // Add a keypress handler to set flag if enter is pressed - $form.find('input#discountcode').keypress( function(e) { - if (e.which === 13) { - $form.data('submit-dont-process', 1); - } - }); + // Remove the onclick attribute added by CiviCRM. + submitButton.removeAttribute('onclick'); - var isWebform = getIsWebform($form); + addSupportForCiviDiscount(); // For CiviCRM Webforms. - if (isWebform) { + if (getIsDrupalWebform()) { // We need the action field for back/submit to work and redirect properly after submission - if (!($('#action').length)) { - $form.append($('<input type="hidden" name="op" id="action" />')); - } - var $actions = $form.find('[type=submit]'); + $('[type=submit]').click(function() { - $('#action').val(this.value); + addDrupalWebformActionElement(this.value); }); // If enter pressed, use our submit function - $form.keypress(function(event) { - if (event.which === 13) { - $('#action').val(this.value); + form.addEventListener('keydown', function (e) { + if (e.keyCode === 13) { + addDrupalWebformActionElement(this.value); submit(event); } }); + $('#billingcheckbox:input').hide(); $('label[for="billingcheckbox"]').hide(); } - else { - // As we use credit_card_number to pass token, make sure it is empty when shown - $form.find("input#credit_card_number").val(''); - $form.find("input#cvv2").val(''); - } function submit(event) { event.preventDefault(); debugging('submit handler'); - if ($form.data('submitted') === true) { + if (form.dataset.submitted === true) { debugging('form already submitted'); return false; } - var isWebform = getIsWebform($form); + var stripeProcessorId; + var chosenProcessorId; + + if (typeof CRM.vars.stripe !== 'undefined') { + stripeProcessorId = CRM.vars.stripe.id; + } // Handle multiple payment options and Stripe not being chosen. - if (isWebform) { - var stripeProcessorId; - var chosenProcessorId; - stripeProcessorId = $('#stripe-id').val(); + // @fixme this needs refactoring as some is not relevant anymore (with stripe 6.0) + if (getIsDrupalWebform()) { + stripeProcessorId = CRM.vars.stripe.id; // this element may or may not exist on the webform, but we are dealing with a single (stripe) processor enabled. if (!$('input[name="submitted[civicrm_1_contribution_1_contribution_payment_processor_id]"]').length) { chosenProcessorId = stripeProcessorId; } else { - chosenProcessorId = $form.find('input[name="submitted[civicrm_1_contribution_1_contribution_payment_processor_id]"]:checked').val(); + chosenProcessorId = form.querySelector('input[name="submitted[civicrm_1_contribution_1_contribution_payment_processor_id]"]:checked').val(); } } else { // Most forms have payment_processor-section but event registration has credit_card_info-section - if (($form.find(".crm-section.payment_processor-section").length > 0) - || ($form.find(".crm-section.credit_card_info-section").length > 0)) { - stripeProcessorId = $('#stripe-id').val(); - chosenProcessorId = $form.find('input[name="payment_processor_id"]:checked').val(); + if ((form.querySelector(".crm-section.payment_processor-section") !== null) + || (form.querySelector(".crm-section.credit_card_info-section") !== null)) { + stripeProcessorId = CRM.vars.stripe.id; + if (form.querySelector('input[name="payment_processor_id"]:checked') !== null) { + chosenProcessorId = form.querySelector('input[name="payment_processor_id"]:checked').value; + } } } @@ -251,8 +310,8 @@ CRM.$(function($) { // - Is the Stripe processor ID defined? // - Is selected processor ID and stripe ID undefined? If we only have stripe ID, then there is only one (stripe) processor on the page if ((chosenProcessorId === 0) - || (stripeProcessorId == null) - || ((chosenProcessorId == null) && (stripeProcessorId == null))) { + || (stripeProcessorId == null) + || ((chosenProcessorId == null) && (stripeProcessorId == null))) { debugging('Not a Stripe transaction, or pay-later'); return true; } @@ -260,22 +319,18 @@ CRM.$(function($) { debugging('Stripe is the selected payprocessor'); } - $form = getBillingForm(); - // Don't handle submits generated by non-stripe processors - if (!$('input#stripe-pub-key').length || !($('input#stripe-pub-key').val())) { + if (typeof CRM.vars.stripe.publishableKey === 'undefined') { debugging('submit missing stripe-pub-key element or value'); return true; } // Don't handle submits generated by the CiviDiscount button. - if ($form.data('submit-dont-process')) { + if (form.dataset.submitdontprocess === true) { debugging('non-payment submit detected - not submitting payment'); return true; } - $submit = getBillingSubmit(); - - if (isWebform) { + if (getIsDrupalWebform()) { // If we have selected Stripe but amount is 0 we don't submit via Stripe if ($('#billing-payment-block').is(':hidden')) { debugging('no payment processor on webform'); @@ -292,111 +347,124 @@ CRM.$(function($) { } } - // This is ONLY triggered in the following circumstances on a CiviCRM contribution page: - // - With a priceset that allows a 0 amount to be selected. - // - When Stripe is the ONLY payment processor configured on the page. - if (typeof calculateTotalFee == 'function') { - var totalFee = calculateTotalFee(); - if (totalFee == '0') { - debugging("Total amount is 0"); - return true; - } - } - - // If there's no credit card field, no use in continuing (probably wrong - // context anyway) - if (!$form.find('#credit_card_number').length) { - debugging('No credit card field'); + var totalFee = getTotalAmount(); + if (totalFee == '0') { + debugging("Total amount is 0"); return true; } + // Lock to prevent multiple submissions - if ($form.data('submitted') === true) { + if (form.dataset.submitted === true) { // Previously submitted - don't submit again alert('Form already submitted. Please wait.'); return false; } else { // Mark it so that the next submit can be ignored - // ADDED requirement that form be valid - if($form.valid()) { - $form.data('submitted', true); - } + form.dataset.submitted = true; } // Disable the submit button to prevent repeated clicks - $submit.prop('disabled', true); - - var cc_month = $form.find('#credit_card_exp_date_M').val(); - var cc_year = $form.find('#credit_card_exp_date_Y').val(); - - Stripe.card.createToken({ - name: $form.find('#billing_first_name') - .val() + ' ' + $form.find('#billing_last_name').val(), - address_zip: $form.find('#billing_postal_code-5').val(), - number: $form.find('#credit_card_number').val(), - cvc: $form.find('#cvv2').val(), - exp_month: cc_month, - exp_year: cc_year - }, stripeResponseHandler); - debugging('Created Stripe token'); - return false; + submitButton.setAttribute('disabled', true); + + // Create a token when the form is submitted. + handleCardPayment(); + + return true; } } - function getIsWebform(form) { - // Pass in the billingForm object - // If the form has the webform-client-form (drupal 7) or webform-submission-form (drupal 8) class then it's a drupal webform! - return form.hasClass('webform-client-form') || form.hasClass('webform-submission-form'); + function getIsDrupalWebform() { + // form class for drupal webform: webform-client-form (drupal 7); webform-submission-form (drupal 8) + if (form !== null) { + return form.classList.contains('webform-client-form') || form.classList.contains('webform-submission-form'); + } + return false; } function getBillingForm() { // If we have a stripe billing form on the page - var $billingForm = $('input#stripe-pub-key').closest('form'); - //if (!$billingForm.length && getIsWebform()) { - // If we are in a webform - // $billingForm = $('.webform-client-form'); - //} - if (!$billingForm.length) { + var billingFormID = $('div#card-element').closest('form').prop('id'); + if (!billingFormID.length) { // If we have multiple payment processors to select and stripe is not currently loaded - $billingForm = $('input[name=hidden_processor]').closest('form'); + billingFormID = $('input[name=hidden_processor]').closest('form').prop('id'); } - return $billingForm; + // We have to use document.getElementById here so we have the right elementtype for appendChild() + return document.getElementById(billingFormID); } function getBillingSubmit() { - $form = getBillingForm(); - var isWebform = getIsWebform($form); - - if (isWebform) { - $submit = $form.find('[type="submit"].webform-submit'); - if (!$submit.length) { + var submit = null; + if (getIsDrupalWebform()) { + submit = form.querySelector('[type="submit"].webform-submit'); + if (!submit) { // drupal 8 webform - $submit = $form.find('[type="submit"].webform-button--submit'); + submit = form.querySelector('[type="submit"].webform-button--submit'); } } else { - $submit = $form.find('[type="submit"].validate'); + submit = form.querySelector('[type="submit"].validate'); } - return $submit; + return submit; } - function removeCCDetails($form, $truncate) { - // Remove the "name" attribute so params are not submitted - var ccNumElement = $form.find("input#credit_card_number"); - var cvv2Element = $form.find("input#cvv2"); - if ($truncate) { - ccNumElement.val(''); - cvv2Element.val(''); + function getTotalAmount() { + var totalFee = null; + if (typeof calculateTotalFee == 'function') { + // This is ONLY triggered in the following circumstances on a CiviCRM contribution page: + // - With a priceset that allows a 0 amount to be selected. + // - When Stripe is the ONLY payment processor configured on the page. + totalFee = calculateTotalFee(); } - else { - var last4digits = ccNumElement.val().substr(12, 16); - ccNumElement.val('000000000000' + last4digits); - cvv2Element.val('000'); + else if (getIsDrupalWebform()) { + // This is how webform civicrm calculates the amount in webform_civicrm_payment.js + $('.line-item:visible', '#wf-crm-billing-items').each(function() { + totalFee += parseFloat($(this).data('amount')); + }); + } + return totalFee; + } + + function updateFormElementsFromCreditCardDetails(event) { + if (!event.complete) { + return; + } + document.getElementById('billing_postal_code-' + CRM.vars.stripe.billingAddressID).value = event.value.postalCode; + } + + function addSupportForCiviDiscount() { + // Add a keypress handler to set flag if enter is pressed + cividiscountElements = form.querySelectorAll('input#discountcode'); + for (i = 0; i < cividiscountElements.length; ++i) { + cividiscountElements[i].addEventListener('keydown', function (e) { + if (e.keyCode === 13) { + e.preventDefault(); + debugging('adding submitdontprocess'); + form.dataset.submitdontprocess = true; + } + }); } } function debugging (errorCode) { // Uncomment the following to debug unexpected returns. - //console.log(new Date().toISOString() + ' civicrm_stripe.js: ' + errorCode); + if ((typeof(CRM.vars.stripe) === 'undefined') || (Boolean(CRM.vars.stripe.jsDebug) === true)) { + console.log(new Date().toISOString() + ' civicrm_stripe.js: ' + errorCode); + } + } + + function addDrupalWebformActionElement(submitAction) { + var hiddenInput = null; + if (document.getElementById('action') !== null) { + hiddenInput = document.getElementById('action'); + } + else { + hiddenInput = document.createElement('input'); + } + hiddenInput.setAttribute('type', 'hidden'); + hiddenInput.setAttribute('name', 'op'); + hiddenInput.setAttribute('id', 'action'); + hiddenInput.setAttribute('value', submitAction); + form.appendChild(hiddenInput); } }); diff --git a/js/civicrm_stripe.min.js b/js/civicrm_stripe.min.js new file mode 100644 index 0000000000000000000000000000000000000000..daab8881a2c057f1deecc39ab2f58e17f2db8937 --- /dev/null +++ b/js/civicrm_stripe.min.js @@ -0,0 +1 @@ +CRM.$(function(d){var n;var b;var c;var a;var m=false;function s(y){f("paymentIntent confirmation success");var x=document.createElement("input");x.setAttribute("type","hidden");x.setAttribute("name","paymentIntentID");x.setAttribute("value",y.id);c.appendChild(x);c.submit()}function g(x){f("error: "+x.error.message);var y=document.getElementById("card-errors");y.style.display="block";y.textContent=x.error.message;document.querySelector("#billing-payment-block").scrollIntoView();window.scrollBy(0,-50);a.removeAttribute("disabled")}function v(){f("handle card payment");n.createPaymentMethod("card",b).then(function(x){if(x.error){g(x)}else{var y=CRM.url("civicrm/stripe/confirm-payment");d.post(y,{payment_method_id:x.paymentMethod.id,amount:o(),currency:CRM.vars.stripe.currency,id:CRM.vars.stripe.id}).then(function(z){t(z)})}})}function t(x){f("handleServerResponse");if(x.error){g(x)}else{if(x.requires_action){p(x)}else{s(x.paymentIntent)}}}function p(x){n.handleCardAction(x.payment_intent_client_secret).then(function(y){if(y.error){g(y)}else{s(y.paymentIntent)}})}var l=null;d(document).ready(function(){window.onbeforeunload=null;j();d("select#payment_processor_id").on("change",function(){d("input.payproc-metadata").remove()})});d(document).ajaxComplete(function(y,A,x){if((x.url.match("civicrm(/|%2F)payment(/|%2F)form")!=null)||(x.url.match("civicrm(/|%2F)contact(/|%2F)view(/|%2F)participant")!=null)){if(d("#payment_processor_id").length>0){var z=d("#payment_processor_id").val();if(z!=CRM.vars.stripe.id){f("payment processor changed to id: "+z);CRM.api3("PaymentProcessorType","getvalue",{sequential:1,"return":"id",name:"Stripe"}).done(function(B){var C=B.result;CRM.api3("PaymentProcessor","getvalue",{sequential:1,"return":"password",id:z,payment_processor_type_id:C}).done(function(D){var E=D.result;if(E){f("Setting new stripe key to: "+E);CRM.vars.stripe.publishableKey=E}else{f("New payment processor is not Stripe, setting stripe-pub-key to null");CRM.vars.stripe.publishableKey=null}j()})})}}j()}});function j(){if(typeof CRM.vars.stripe==="undefined"){f("CRM.vars.stripe not defined!");return}if(typeof Stripe==="undefined"){if(m){return}m=true;f("Stripe.js is not loaded!");d.getScript("https://js.stripe.com/v3",function(){f("Script loaded and executed.");m=false;e()})}else{e()}}function e(){n=Stripe(CRM.vars.stripe.publishableKey);var B=n.elements();var z={base:{fontSize:"20px"}};b=B.create("card",{style:z});b.mount("#card-element");document.getElementsByClassName("billing_postal_code-"+CRM.vars.stripe.billingAddressID+"-section")[0].setAttribute("hidden",true);b.addEventListener("change",function(C){r(C)});c=h();if(typeof c.length==="undefined"||c.length===0){f("No billing form!");return}a=w();c.dataset.submitdontprocess=false;var x=c.querySelectorAll('[type="submit"][formnovalidate="1"], [type="submit"][formnovalidate="formnovalidate"], [type="submit"].cancel, [type="submit"].webform-previous'),y;for(y=0;y<x.length;++y){x[y].addEventListener("click",function(){f("adding submitdontprocess");c.dataset.submitdontprocess=true})}a.addEventListener("click",function(C){f("clearing submitdontprocess");c.dataset.submitdontprocess=false;return A(C)});a.removeAttribute("onclick");k();if(u()){d("[type=submit]").click(function(){q(this.value)});c.addEventListener("keydown",function(C){if(C.keyCode===13){q(this.value);A(event)}});d("#billingcheckbox:input").hide();d('label[for="billingcheckbox"]').hide()}function A(E){E.preventDefault();f("submit handler");if(c.dataset.submitted===true){f("form already submitted");return false}var G;var D;if(typeof CRM.vars.stripe!=="undefined"){G=CRM.vars.stripe.id}if(u()){G=CRM.vars.stripe.id;if(!d('input[name="submitted[civicrm_1_contribution_1_contribution_payment_processor_id]"]').length){D=G}else{D=c.querySelector('input[name="submitted[civicrm_1_contribution_1_contribution_payment_processor_id]"]:checked').val()}}else{if((c.querySelector(".crm-section.payment_processor-section")!==null)||(c.querySelector(".crm-section.credit_card_info-section")!==null)){G=CRM.vars.stripe.id;if(c.querySelector('input[name="payment_processor_id"]:checked')!==null){D=c.querySelector('input[name="payment_processor_id"]:checked').value}}}if((D===0)||(G==null)||((D==null)&&(G==null))){f("Not a Stripe transaction, or pay-later");return true}else{f("Stripe is the selected payprocessor")}if(typeof CRM.vars.stripe.publishableKey==="undefined"){f("submit missing stripe-pub-key element or value");return true}if(c.dataset.submitdontprocess===true){f("non-payment submit detected - not submitting payment");return true}if(u()){if(d("#billing-payment-block").is(":hidden")){f("no payment processor on webform");return true}var F=d('[name="submitted[civicrm_1_contribution_1_contribution_payment_processor_id]"]');if(F.length){if(F.filter(":checked").val()==="0"||F.filter(":checked").val()===0){f("no payment processor selected");return true}}}var C=o();if(C=="0"){f("Total amount is 0");return true}if(c.dataset.submitted===true){alert("Form already submitted. Please wait.");return false}else{c.dataset.submitted=true}a.setAttribute("disabled",true);v();return true}}function u(){if(c!==null){return c.classList.contains("webform-client-form")||c.classList.contains("webform-submission-form")}return false}function h(){var x=d("div#card-element").closest("form").prop("id");if(!x.length){x=d("input[name=hidden_processor]").closest("form").prop("id")}return document.getElementById(x)}function w(){var x=null;if(u()){x=c.querySelector('[type="submit"].webform-submit');if(!x){x=c.querySelector('[type="submit"].webform-button--submit')}}else{x=c.querySelector('[type="submit"].validate')}return x}function o(){var x=null;if(typeof calculateTotalFee=="function"){x=calculateTotalFee()}else{if(u()){d(".line-item:visible","#wf-crm-billing-items").each(function(){x+=parseFloat(d(this).data("amount"))})}}return x}function r(x){if(!x.complete){return}document.getElementById("billing_postal_code-"+CRM.vars.stripe.billingAddressID).value=x.value.postalCode}function k(){cividiscountElements=c.querySelectorAll("input#discountcode");for(i=0;i<cividiscountElements.length;++i){cividiscountElements[i].addEventListener("keydown",function(x){if(x.keyCode===13){x.preventDefault();f("adding submitdontprocess");c.dataset.submitdontprocess=true}})}}function f(x){if((typeof(CRM.vars.stripe)==="undefined")||(Boolean(CRM.vars.stripe.jsDebug)===true)){console.log(new Date().toISOString()+" civicrm_stripe.js: "+x)}}function q(y){var x=null;if(document.getElementById("action")!==null){x=document.getElementById("action")}else{x=document.createElement("input")}x.setAttribute("type","hidden");x.setAttribute("name","op");x.setAttribute("id","action");x.setAttribute("value",y);c.appendChild(x)}}); \ No newline at end of file diff --git a/settings/stripe.setting.php b/settings/stripe.setting.php new file mode 100644 index 0000000000000000000000000000000000000000..2eb58cd04ca8160ed148f0f9e5552e5d0c59ceec --- /dev/null +++ b/settings/stripe.setting.php @@ -0,0 +1,23 @@ +<?php + +use CRM_Stripe_ExtensionUtil as E; + +return [ + 'stripe_jsdebug' => [ + 'name' => 'stripe_jsdebug', + 'type' => 'Boolean', + 'html_type' => 'checkbox', + 'default' => 0, + 'add' => '5.13', + 'is_domain' => 1, + 'is_contact' => 0, + 'title' => E::ts('Enable Stripe Javascript debugging?'), + 'description' => E::ts('Enables debug logging to browser console for stripe payment processors.'), + 'html_attributes' => [], + 'settings_pages' => [ + 'stripe' => [ + 'weight' => 10, + ] + ], + ] +]; diff --git a/stripe.mgd.php b/stripe.mgd.php index 2b5b3c2c936db6cb02d48bd34e889eab8bbdbb6f..486bf445bbdf5ec62f8a6140bccdd1caa2eac394 100644 --- a/stripe.mgd.php +++ b/stripe.mgd.php @@ -15,8 +15,8 @@ return [ 'title' => 'Stripe', 'description' => 'Stripe Payment Processor', 'class_name' => 'Payment_Stripe', - 'user_name_label' => 'Secret Key', - 'password_label' => 'Publishable key', + 'user_name_label' => 'Publishable key', + 'password_label' => 'Secret Key', 'url_site_default' => 'https://api.stripe.com/v2', 'url_recur_default' => 'https://api.stripe.com/v2', 'url_site_test_default' => 'https://api.stripe.com/v2', diff --git a/stripe.php b/stripe.php index f9eefe6a7b6edc7870e7c3358a42a98c4a5c39ca..c3bb467ab1a60654c4524fb1fa222532bdb52394 100644 --- a/stripe.php +++ b/stripe.php @@ -74,43 +74,47 @@ function stripe_civicrm_managed(&$entities) { } /** - * Implementation of hook_civicrm_validateForm(). - * - * Prevent server validation of cc fields - * - * @param $formName - the name of the form - * @param $fields - Array of name value pairs for all 'POST'ed form values - * @param $files - Array of file properties as sent by PHP POST protocol - * @param $form - reference to the form object - * @param $errors - Reference to the errors array. - * -*/ + * Implements hook_civicrm_alterSettingsFolders(). + */ +function stripe_civicrm_alterSettingsFolders(&$metaDataFolders = NULL) { + _stripe_civix_civicrm_alterSettingsFolders($metaDataFolders); +} - function stripe_civicrm_validateForm($formName, &$fields, &$files, &$form, &$errors) { - if (empty($form->_paymentProcessor['payment_processor_type'])) { - return; - } - // If Stripe is active here. - if ($form->_paymentProcessor['class_name'] == 'Payment_Stripe') { - if (isset($form->_elementIndex['stripe_token'])) { - if ($form->elementExists('credit_card_number')) { - $cc_field = $form->getElement('credit_card_number'); - $form->removeElement('credit_card_number', true); - $form->addElement($cc_field); - } - if ($form->elementExists('cvv2')) { - $cvv2_field = $form->getElement('cvv2'); - $form->removeElement('cvv2', true); - $form->addElement($cvv2_field); - } +/** + * Implementation of hook_civicrm_validateForm(). + * + * Prevent server validation of cc fields + * + * @param $formName - the name of the form + * @param $fields - Array of name value pairs for all 'POST'ed form values + * @param $files - Array of file properties as sent by PHP POST protocol + * @param $form - reference to the form object + * @param $errors - Reference to the errors array. + * + */ + +function stripe_civicrm_validateForm($formName, &$fields, &$files, &$form, &$errors) { + if (empty($form->_paymentProcessor['payment_processor_type'])) { + return; + } + // If Stripe is active here. + if ($form->_paymentProcessor['class_name'] == 'Payment_Stripe') { + if (isset($form->_elementIndex['stripe_token'])) { + if ($form->elementExists('credit_card_number')) { + $cc_field = $form->getElement('credit_card_number'); + $form->removeElement('credit_card_number', true); + $form->addElement($cc_field); + } + if ($form->elementExists('cvv2')) { + $cvv2_field = $form->getElement('cvv2'); + $form->removeElement('cvv2', true); + $form->addElement($cvv2_field); } - } else { - return; } + } else { + return; } - -// Flag so we don't add the stripe scripts more than once. -static $_stripe_scripts_added; +} /** * Implementation of hook_civicrm_alterContent @@ -121,7 +125,6 @@ static $_stripe_scripts_added; * @return void */ function stripe_civicrm_alterContent( &$content, $context, $tplName, &$object ) { - global $_stripe_scripts_added; /* Adding stripe js: * - Webforms don't get scripts added by hook_civicrm_buildForm so we have to user alterContent * - (Webforms still call buildForm and it looks like they are added but they are not, @@ -132,12 +135,12 @@ function stripe_civicrm_alterContent( &$content, $context, $tplName, &$object ) * */ if (($context == 'form' && !empty($object->_paymentProcessor['class_name'])) - || (($context == 'page') && !empty($object->_isPaymentProcessor))) { - if (!$_stripe_scripts_added || $object instanceof CRM_Financial_Form_Payment) { - $stripeJSURL = CRM_Core_Resources::singleton() - ->getUrl('com.drastikbydesign.stripe', 'js/civicrm_stripe.js'); + || (($context == 'page') && !empty($object->_isPaymentProcessor))) { + if (!isset(\Civi::$statics[E::LONG_NAME]['stripeJSLoaded']) || $object instanceof CRM_Financial_Form_Payment) { + $min = ((boolean) \Civi::settings()->get('stripe_jsdebug')) ? '' : '.min'; + $stripeJSURL = \Civi::resources()->getUrl(E::LONG_NAME, "js/civicrm_stripe{$min}.js"); $content .= "<script src='{$stripeJSURL}'></script>"; - $_stripe_scripts_added = TRUE; + \Civi::$statics[E::LONG_NAME]['stripeJSLoaded'] = TRUE; } } } @@ -150,17 +153,57 @@ function stripe_civicrm_alterContent( &$content, $context, $tplName, &$object ) * @param CRM_Core_Form $form */ function stripe_civicrm_buildForm($formName, &$form) { - global $_stripe_scripts_added; - if (!isset($form->_paymentProcessor)) { + // Don't load stripe js on ajax forms + if (CRM_Utils_Request::retrieveValue('snippet', 'String') === 'json') { + return; + } + + // Load stripe.js on all civi forms per stripe requirements + if (!isset(\Civi::$statics[E::LONG_NAME]['stripeJSLoaded'])) { + \Civi::resources()->addScriptUrl('https://js.stripe.com/v3'); + \Civi::$statics[E::LONG_NAME]['stripeJSLoaded'] = TRUE; + } +} + +/** + * Implements hook_civicrm_postProcess(). + * + * @param string $formName + * @param \CRM_Core_Form $form + */ +function stripe_civicrm_postProcess($formName, &$form) { + // We're only interested in forms that have a paymentprocessor + if (empty($form->get('paymentProcessor')) || ($form->get('paymentProcessor')['class_name'] !== 'Payment_Stripe')) { return; } - $paymentProcessor = $form->_paymentProcessor; - if (!empty($paymentProcessor['class_name'])) { - if (!$_stripe_scripts_added) { - CRM_Core_Resources::singleton() - ->addScriptFile('com.drastikbydesign.stripe', 'js/civicrm_stripe.js'); + + // Retrieve the paymentIntentID that was posted along with the form and add it to the form params + // This allows multi-page checkout to work (eg. register->confirm->thankyou) + $params = $form->get('params'); + if (!$params) { + // @fixme Hack for contributionpages - see https://github.com/civicrm/civicrm-core/pull/15252 + $params = $form->getVar('_params'); + $hackForContributionPages = TRUE; + } + if (isset($params['amount'])) { + // Contribution pages have params directly in the main array + $paymentParams = &$params; + } + elseif (isset($params[0]['amount'])) { + // Event registration pages have params in a sub-array + $paymentParams = &$params[0]; + } + else { + return; + } + $paymentIntentID = CRM_Utils_Request::retrieveValue('paymentIntentID', 'String'); + if ($paymentIntentID) { + $paymentParams['paymentIntentID'] = $paymentIntentID; + $form->set('params', $params); + if (isset($hackForContributionPages)) { + // @fixme Hack for contributionpages - see https://github.com/civicrm/civicrm-core/pull/15252 + CRM_Core_Session::singleton()->set('stripePaymentIntent', $paymentIntentID); } - $_stripe_scripts_added = TRUE; } } @@ -169,4 +212,20 @@ function stripe_civicrm_buildForm($formName, &$form) { */ function stripe_civicrm_check(&$messages) { CRM_Stripe_Webhook::check($messages); + CRM_Stripe_Check::checkRequirements($messages); +} + +/** + * Implements hook_civicrm_navigationMenu(). + */ +function stripe_civicrm_navigationMenu(&$menu) { + _stripe_civix_insert_navigation_menu($menu, 'Administer/CiviContribute', array( + 'label' => E::ts('Stripe Settings'), + 'name' => 'stripe_settings', + 'url' => 'civicrm/admin/setting/stripe', + 'permission' => 'administer CiviCRM', + 'operator' => 'OR', + 'separator' => 0, + )); + _stripe_civix_navigationMenu($menu); } diff --git a/templates/CRM/Core/Payment/Stripe/Card.tpl b/templates/CRM/Core/Payment/Stripe/Card.tpl new file mode 100644 index 0000000000000000000000000000000000000000..aa3e1bd86f6d5d013e7360e31053913fe0dcef8c --- /dev/null +++ b/templates/CRM/Core/Payment/Stripe/Card.tpl @@ -0,0 +1,21 @@ +{* https://civicrm.org/licensing *} + +{* Manually create the CRM.vars.stripe here for drupal webform because \Civi::resources()->addVars() does not work in this context *} +{literal} +<script type="text/javascript"> + CRM.$(function($) { + $(document).ready(function() { + if (typeof CRM.vars.stripe === 'undefined') { + var stripe = {{/literal}{foreach from=$stripeJSVars key=arrayKey item=arrayValue}{$arrayKey}:'{$arrayValue}',{/foreach}{literal}}; + CRM.vars.stripe = stripe; + } + }); + }); +</script> +{/literal} + +{* Add the components required for a Stripe card element *} +<label for="card-element"><legend>Credit or debit card</legend></label> +<div id="card-element"></div> +{* Area for Stripe to report errors *} +<div id="card-errors" role="alert" class="alert alert-danger"></div> diff --git a/tests/phpunit/CRM/Stripe/BaseTest.php b/tests/phpunit/CRM/Stripe/BaseTest.php index c5b45208bb7cc79a17063a1a02c10bc6abb3e2d0..b23d69f90bc051c1f2c1900bb08aa8d679312848 100644 --- a/tests/phpunit/CRM/Stripe/BaseTest.php +++ b/tests/phpunit/CRM/Stripe/BaseTest.php @@ -39,7 +39,7 @@ class CRM_Stripe_BaseTest extends \PHPUnit_Framework_TestCase implements Headles // Secret/public keys are PTP test keys. protected $_sk = 'sk_test_TlGdeoi8e1EOPC3nvcJ4q5UZ'; protected $_pk = 'pk_test_k2hELLGpBLsOJr6jZ2z9RaYh'; - protected $_cc = NULL; + protected $_cc = NULL; public function setUpHeadless() { // Civi\Test has many helpers, like install(), uninstall(), sql(), and sqlFile(). @@ -111,8 +111,8 @@ class CRM_Stripe_BaseTest extends \PHPUnit_Framework_TestCase implements Headles $processor = array_pop($result['values']); $this->_sk = $processor['user_name']; $this->_pk = $processor['password']; - $this->_paymentProcessor = $processor; - $this->_paymentProcessorID = $result['id']; + $this->_paymentProcessor = $processor; + $this->_paymentProcessorID = $result['id']; } /** @@ -132,9 +132,9 @@ class CRM_Stripe_BaseTest extends \PHPUnit_Framework_TestCase implements Headles ), $params); $result = civicrm_api3('ContributionPage', 'create', $params); $this->assertEquals(0, $result['is_error']); - $this->_contributionPageID = $result['id']; + $this->_contributionPageID = $result['id']; } - + /** * Submit to stripe */ @@ -179,8 +179,9 @@ class CRM_Stripe_BaseTest extends \PHPUnit_Framework_TestCase implements Headles public function assertValidTrxn() { $this->assertNotEmpty($this->_trxn_id, "A trxn id was assigned"); - \Stripe\Stripe::setApiKey($this->_sk); - $found = FALSE; + $processor = new CRM_Core_Payment_Stripe('', civicrm_api3('PaymentProcessor', 'getsingle', ['id' => $this->_paymentProcessorID])); + $processor->setAPIParams(); + try { $results = \Stripe\Charge::retrieve(array( "id" => $this->_trxn_id)); $found = TRUE; @@ -188,10 +189,10 @@ class CRM_Stripe_BaseTest extends \PHPUnit_Framework_TestCase implements Headles catch (Stripe_Error $e) { $found = FALSE; } - + $this->assertTrue($found, 'Assigned trxn_id is valid.'); - } + } /** * Create contribition */ @@ -213,7 +214,7 @@ class CRM_Stripe_BaseTest extends \PHPUnit_Framework_TestCase implements Headles ), $params)); $this->assertEquals(0, $contribution['is_error']); $this->_contributionID = $contribution['id']; - } + } public function createOrganization() { if (!empty($this->_orgID)) { @@ -230,7 +231,7 @@ class CRM_Stripe_BaseTest extends \PHPUnit_Framework_TestCase implements Headles CRM_Member_PseudoConstant::flush('membershipType'); CRM_Core_Config::clearDBCache(); $this->createOrganization(); - $params = array( + $params = array( 'name' => 'General', 'duration_unit' => 'year', 'duration_interval' => 1, @@ -251,5 +252,5 @@ class CRM_Stripe_BaseTest extends \PHPUnit_Framework_TestCase implements Headles CRM_Utils_Cache::singleton()->flush(); } - + } diff --git a/tests/phpunit/CRM/Stripe/IpnTest.php b/tests/phpunit/CRM/Stripe/IpnTest.php index 64069ff64d30605725dae244843e4d8aa6a7732f..d616615290ddef0f4a30556f32ed7933f201db26 100644 --- a/tests/phpunit/CRM/Stripe/IpnTest.php +++ b/tests/phpunit/CRM/Stripe/IpnTest.php @@ -27,7 +27,7 @@ class CRM_Stripe_IpnTest extends CRM_Stripe_BaseTest { protected $_frequency_interval = 1; protected $_membershipID; - // This test is particularly dirty for some reason so we have to + // This test is particularly dirty for some reason so we have to // force a reset. public function setUpHeadless() { $force = TRUE; @@ -71,13 +71,15 @@ class CRM_Stripe_IpnTest extends CRM_Stripe_BaseTest { $this->doPayment($payment_extra_params); // Now check to see if an event was triggered and if so, process it. - $payment_object = $this->getEvent('invoice.payment_succeeded'); + $payment_object = $this->getEvent('invoice.payment_succeeded'); if ($payment_object) { $this->ipn($payment_object); } - // Now that we have a recurring contribution, let's update it. - \Stripe\Stripe::setApiKey($this->_sk); + // Now that we have a recurring contribution, let's update it. + $processor = new CRM_Core_Payment_Stripe('', civicrm_api3('PaymentProcessor', 'getsingle', ['id' => $this->_paymentProcessorID])); + $processor->setAPIParams(); + $sub = \Stripe\Subscription::retrieve($this->_subscriptionID); // Create a new plan if it doesn't yet exist. @@ -110,7 +112,7 @@ class CRM_Stripe_IpnTest extends CRM_Stripe_BaseTest { $sub->save(); // Now check to see if an event was triggered and if so, process it. - $payment_object = $this->getEvent('customer.subscription.updated'); + $payment_object = $this->getEvent('customer.subscription.updated'); if ($payment_object) { $this->ipn($payment_object); } @@ -123,8 +125,8 @@ class CRM_Stripe_IpnTest extends CRM_Stripe_BaseTest { 'return' => array('id'), ); $result = civicrm_api3('ContributionRecur', 'getsingle', $params); - $newContributionRecurID = $result['id']; - + $newContributionRecurID = $result['id']; + // Now ensure that the membership record is updated to have this // new recurring contribution id. $membership_contribution_recur_id = civicrm_api3('Membership', 'getvalue', array( @@ -135,7 +137,7 @@ class CRM_Stripe_IpnTest extends CRM_Stripe_BaseTest { // Delete the new plan so we can cleanly run the next time. $plan->delete(); - + } /** @@ -155,7 +157,7 @@ class CRM_Stripe_IpnTest extends CRM_Stripe_BaseTest { $this->doPayment($payment_extra_params); // Now check to see if an event was triggered and if so, process it. - $payment_object = $this->getEvent('invoice.payment_succeeded'); + $payment_object = $this->getEvent('invoice.payment_succeeded'); if ($payment_object) { // Now manipulate the transaction so it appears to be a failed one. $payment_object->type = 'invoice.payment_failed'; @@ -192,7 +194,7 @@ class CRM_Stripe_IpnTest extends CRM_Stripe_BaseTest { $this->doPayment($payment_extra_params); // Now check to see if an event was triggered and if so, process it. - $payment_object = $this->getEvent('invoice.payment_succeeded'); + $payment_object = $this->getEvent('invoice.payment_succeeded'); if ($payment_object) { $this->ipn($payment_object); } @@ -201,15 +203,17 @@ class CRM_Stripe_IpnTest extends CRM_Stripe_BaseTest { $this->assertEquals(1, $contribution_status_id, "Recurring payment was properly processed via a stripe event."); // Now, cancel the subscription and ensure it is properly cancelled. - \Stripe\Stripe::setApiKey($this->_sk); + $processor = new CRM_Core_Payment_Stripe('', civicrm_api3('PaymentProcessor', 'getsingle', ['id' => $this->_paymentProcessorID])); + $processor->setAPIParams(); + $sub = \Stripe\Subscription::retrieve($this->_subscriptionID); $sub->cancel(); - $sub_object = $this->getEvent('customer.subscription.deleted'); + $sub_object = $this->getEvent('customer.subscription.deleted'); if ($sub_object) { $this->ipn($sub_object); } - $this->assertContributionRecurIsCancelled(); + $this->assertContributionRecurIsCancelled(); } public function assertContributionRecurIsCancelled() { @@ -239,7 +243,7 @@ class CRM_Stripe_IpnTest extends CRM_Stripe_BaseTest { $params['output'] = 'raw'; // Now try to retrieve this transaction. - $transactions = civicrm_api3('Stripe', 'listevents', $params ); + $transactions = civicrm_api3('Stripe', 'listevents', $params ); foreach($transactions['values']['data'] as $transaction) { if ($transaction->data->object->$property == $this->_subscriptionID) { return $transaction; @@ -254,7 +258,7 @@ class CRM_Stripe_IpnTest extends CRM_Stripe_BaseTest { * */ public function ipn($data, $verify = TRUE) { - // The $_GET['processor_id'] value is normally set by + // The $_GET['processor_id'] value is normally set by // CRM_Core_Payment::handlePaymentMethod $_GET['processor_id'] = $this->_paymentProcessorID; $ipnClass = new CRM_Core_Payment_StripeIPN($data, $verify); @@ -272,7 +276,7 @@ class CRM_Stripe_IpnTest extends CRM_Stripe_BaseTest { 'amount' => $this->_total, 'sequential' => 1, 'installments' => $this->_installments, - 'frequency_unit' => $this->_frequency_unit, + 'frequency_unit' => $this->_frequency_unit, 'frequency_interval' => $this->_frequency_interval, 'invoice_id' => $this->_invoiceID, 'contribution_status_id' => 2, @@ -293,5 +297,5 @@ class CRM_Stripe_IpnTest extends CRM_Stripe_BaseTest { $this->assertEquals(0, $contributionRecur['is_error']); $this->_contributionRecurID = $contributionRecur['id']; $this->_contributionID = $contributionRecur['values']['0']['api.contribution.create']['id']; - } + } } diff --git a/utils/fix-issue-44.php b/utils/fix-issue-44.php index 53c127f73822a5de7e443f38e9f7c87eb1dd5972..a2d6d768779975d2330c9eee473c8eef9c671e7b 100644 --- a/utils/fix-issue-44.php +++ b/utils/fix-issue-44.php @@ -51,7 +51,8 @@ while ($dao->fetch()) { if (!$paymentProcessor) { echo "Failed to find a stripe payment processor for recurring contrib $dao->contribution_recur_id\n"; } - Stripe::setApiKey(CRM_Core_Payment_Stripe::getSecretKey($paymentProcessor)); + $processor = new CRM_Core_Payment_Stripe('', civicrm_api3('PaymentProcessor', 'getsingle', ['id' => $paymentProcessor['id']])); + $processor->setAPIParams(); try { $results = Charge::retrieve(['id' => $dao->trxn_id]); diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index 151112a5e753f9ac2985156e637b392a743bdbd1..eae21a68a560b15fed848122b36c89c063c27ee2 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -1,17 +1,17 @@ [ { "name": "stripe/stripe-php", - "version": "v6.40.0", - "version_normalized": "6.40.0.0", + "version": "v6.43.1", + "version_normalized": "6.43.1.0", "source": { "type": "git", "url": "https://github.com/stripe/stripe-php.git", - "reference": "9c22ffab790ef4dae0f371929de50e8b53c9ec8d" + "reference": "42fcdaf99c44bb26937223f8eae1f263491d5ab8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/stripe/stripe-php/zipball/9c22ffab790ef4dae0f371929de50e8b53c9ec8d", - "reference": "9c22ffab790ef4dae0f371929de50e8b53c9ec8d", + "url": "https://api.github.com/repos/stripe/stripe-php/zipball/42fcdaf99c44bb26937223f8eae1f263491d5ab8", + "reference": "42fcdaf99c44bb26937223f8eae1f263491d5ab8", "shasum": "" }, "require": { @@ -26,7 +26,7 @@ "squizlabs/php_codesniffer": "~2.0", "symfony/process": "~2.8" }, - "time": "2019-06-27T23:24:51+00:00", + "time": "2019-08-29T16:56:12+00:00", "type": "library", "extra": { "branch-alias": { diff --git a/vendor/stripe/stripe-php/CHANGELOG.md b/vendor/stripe/stripe-php/CHANGELOG.md index b918de61fd4885a4f9202a16a24619567140db0a..5554f4ceb1fadc611e15b9c36a922817ba813fab 100644 --- a/vendor/stripe/stripe-php/CHANGELOG.md +++ b/vendor/stripe/stripe-php/CHANGELOG.md @@ -1,5 +1,22 @@ # Changelog +## 6.43.1 - 2019-08-29 +* [#722](https://github.com/stripe/stripe-php/pull/722) Make `LoggerInterface::error` compatible with its PSR-3 counterpart +* [#714](https://github.com/stripe/stripe-php/pull/714) Add `pending_setup_intent` property in `Subscription` +* [#713](https://github.com/stripe/stripe-php/pull/713) Add typehint to `ApiResponse` +* [#712](https://github.com/stripe/stripe-php/pull/712) Fix comment +* [#701](https://github.com/stripe/stripe-php/pull/701) Start testing PHP 7.3 + +## 6.43.0 - 2019-08-09 +* [#694](https://github.com/stripe/stripe-php/pull/694) Add `SubscriptionItem::createUsageRecord` method + +## 6.42.0 - 2019-08-09 +* [#688](https://github.com/stripe/stripe-php/pull/688) Remove `SubscriptionScheduleRevision` + * Note that this is technically a breaking change, however we've chosen to release it as a minor version in light of the fact that this resource and its API methods were virtually unused. + +## 6.41.0 - 2019-07-31 +* [#683](https://github.com/stripe/stripe-php/pull/683) Move the List Balance History API to `/v1/balance_transactions` + ## 6.40.0 - 2019-06-27 * [#675](https://github.com/stripe/stripe-php/pull/675) Add support for `SetupIntent` resource and APIs @@ -57,7 +74,7 @@ * [#642](https://github.com/stripe/stripe-php/pull/642) Fix an issue where existing idempotency keys would be overwritten when using automatic retries ## 6.34.1 - 2019-04-25 -* [#640](https://github.com/stripe/stripe-php/pull/640) Add missing phpdocs +* [#640](https://github.com/stripe/stripe-php/pull/640) Add missing phpdocs ## 6.34.0 - 2019-04-24 * [#626](https://github.com/stripe/stripe-php/pull/626) Add support for the `TaxRate` resource and APIs @@ -145,7 +162,7 @@ * [#564](https://github.com/stripe/stripe-php/pull/564) Add event name constants for subscription schedule aborted/expiring ## 6.23.0 - 2018-11-27 -* [#542](https://github.com/stripe/stripe-php/pull/542) Add support for `ValueList` and `ValueListItem` for Radar +* [#542](https://github.com/stripe/stripe-php/pull/542) Add support for `ValueList` and `ValueListItem` for Radar ## 6.22.1 - 2018-11-20 * [#561](https://github.com/stripe/stripe-php/pull/561) Add cast and some docs to telemetry introduced in 6.22.0/#549 @@ -399,7 +416,7 @@ Pull requests included in this release: * Add support for payouts and recipient transfers ## 4.6.0 - 2017-04-06 -* Please see 4.7.0 instead (no-op release) +* Please see 4.7.0 instead (no-op release) ## 4.5.1 - 2017-03-22 * Remove hard dependency on cURL diff --git a/vendor/stripe/stripe-php/VERSION b/vendor/stripe/stripe-php/VERSION index 353819c7ea3c30b1aba04dc9d639ec7100f93d8c..eb34ef6cbc3aff7ca93ca07ec2b61e5f0b7652cf 100644 --- a/vendor/stripe/stripe-php/VERSION +++ b/vendor/stripe/stripe-php/VERSION @@ -1 +1 @@ -6.40.0 +6.43.1 diff --git a/vendor/stripe/stripe-php/init.php b/vendor/stripe/stripe-php/init.php index e893cfcda90b8a3cf7dcb4e3f9f65f48d8ee76fe..05243999637672b8af6e4732b2a890947439c15b 100644 --- a/vendor/stripe/stripe-php/init.php +++ b/vendor/stripe/stripe-php/init.php @@ -122,7 +122,6 @@ require(dirname(__FILE__) . '/lib/SourceTransaction.php'); require(dirname(__FILE__) . '/lib/Subscription.php'); require(dirname(__FILE__) . '/lib/SubscriptionItem.php'); require(dirname(__FILE__) . '/lib/SubscriptionSchedule.php'); -require(dirname(__FILE__) . '/lib/SubscriptionScheduleRevision.php'); require(dirname(__FILE__) . '/lib/TaxId.php'); require(dirname(__FILE__) . '/lib/TaxRate.php'); require(dirname(__FILE__) . '/lib/Terminal/ConnectionToken.php'); diff --git a/vendor/stripe/stripe-php/lib/Account.php b/vendor/stripe/stripe-php/lib/Account.php index 1adc6b79d53ba128a3fcd1ac75891d07d9354643..4993ca3541436b10d428193a9b35ad6934a5c5e7 100644 --- a/vendor/stripe/stripe-php/lib/Account.php +++ b/vendor/stripe/stripe-php/lib/Account.php @@ -30,7 +30,6 @@ namespace Stripe; */ class Account extends ApiResource { - const OBJECT_NAME = "account"; use ApiOperations\All; diff --git a/vendor/stripe/stripe-php/lib/AccountLink.php b/vendor/stripe/stripe-php/lib/AccountLink.php index f2975ae38343b80516880e29c1e743e487e89916..692447dbf69ec131187e3394cc87e2266f70f9f6 100644 --- a/vendor/stripe/stripe-php/lib/AccountLink.php +++ b/vendor/stripe/stripe-php/lib/AccountLink.php @@ -14,7 +14,6 @@ namespace Stripe; */ class AccountLink extends ApiResource { - const OBJECT_NAME = "account_link"; use ApiOperations\Create; diff --git a/vendor/stripe/stripe-php/lib/AlipayAccount.php b/vendor/stripe/stripe-php/lib/AlipayAccount.php index 70a281bd768af5b55f7b05ec1c88acec20482e18..a79705fa4ebb459a9020a32676f070688e39262a 100644 --- a/vendor/stripe/stripe-php/lib/AlipayAccount.php +++ b/vendor/stripe/stripe-php/lib/AlipayAccount.php @@ -12,7 +12,6 @@ namespace Stripe; */ class AlipayAccount extends ApiResource { - const OBJECT_NAME = "alipay_account"; use ApiOperations\Delete; diff --git a/vendor/stripe/stripe-php/lib/ApiRequestor.php b/vendor/stripe/stripe-php/lib/ApiRequestor.php index 7cf851877deb1073854abd79d963afbd87f4010f..1f75a007b327c0eece940222d0fada955f642312 100644 --- a/vendor/stripe/stripe-php/lib/ApiRequestor.php +++ b/vendor/stripe/stripe-php/lib/ApiRequestor.php @@ -205,7 +205,7 @@ class ApiRequestor return new Error\Idempotency($msg, $rcode, $rbody, $resp, $rheaders); } - // intentional fall-through + // no break case 404: return new Error\InvalidRequest($msg, $param, $rcode, $rbody, $resp, $rheaders); case 401: diff --git a/vendor/stripe/stripe-php/lib/ApiResponse.php b/vendor/stripe/stripe-php/lib/ApiResponse.php index 31f54a50df00888d67ef4e4484ae656f8255ad97..acca75123e67ee1b94a0ea57d01bbcd2bf092f2c 100644 --- a/vendor/stripe/stripe-php/lib/ApiResponse.php +++ b/vendor/stripe/stripe-php/lib/ApiResponse.php @@ -2,6 +2,8 @@ namespace Stripe; +use Stripe\Util\CaseInsensitiveArray; + /** * Class ApiResponse * @@ -17,7 +19,7 @@ class ApiResponse /** * @param string $body * @param integer $code - * @param array|null $headers + * @param array|CaseInsensitiveArray|null $headers * @param array|null $json * * @return obj An APIResponse diff --git a/vendor/stripe/stripe-php/lib/ApplePayDomain.php b/vendor/stripe/stripe-php/lib/ApplePayDomain.php index ea84220a7a93d6ff82ab7b4f642e86e09a9688d1..57687c4f2e4bd0e76a850c577e61e0d2fd36071b 100644 --- a/vendor/stripe/stripe-php/lib/ApplePayDomain.php +++ b/vendor/stripe/stripe-php/lib/ApplePayDomain.php @@ -9,7 +9,6 @@ namespace Stripe; */ class ApplePayDomain extends ApiResource { - const OBJECT_NAME = "apple_pay_domain"; use ApiOperations\All; diff --git a/vendor/stripe/stripe-php/lib/ApplicationFee.php b/vendor/stripe/stripe-php/lib/ApplicationFee.php index 0d9fde4c521d6328655b3be74b3b3050864f8868..4b83071ade40b4a14dda663eb9b5bae379fc162c 100644 --- a/vendor/stripe/stripe-php/lib/ApplicationFee.php +++ b/vendor/stripe/stripe-php/lib/ApplicationFee.php @@ -24,7 +24,6 @@ namespace Stripe; */ class ApplicationFee extends ApiResource { - const OBJECT_NAME = "application_fee"; use ApiOperations\All; diff --git a/vendor/stripe/stripe-php/lib/ApplicationFeeRefund.php b/vendor/stripe/stripe-php/lib/ApplicationFeeRefund.php index 91d7e9d2bc665b3988173c9c60490401157bacf2..b242f2024b48b2a93ad224b987dc32017cf8236d 100644 --- a/vendor/stripe/stripe-php/lib/ApplicationFeeRefund.php +++ b/vendor/stripe/stripe-php/lib/ApplicationFeeRefund.php @@ -18,7 +18,6 @@ namespace Stripe; */ class ApplicationFeeRefund extends ApiResource { - const OBJECT_NAME = "fee_refund"; use ApiOperations\Update { diff --git a/vendor/stripe/stripe-php/lib/Balance.php b/vendor/stripe/stripe-php/lib/Balance.php index 25f88ae74c7886e83bfbc1d6728faa5b32eb1078..b99871adb85a9bf8aa154b566e1e5f3c7d805416 100644 --- a/vendor/stripe/stripe-php/lib/Balance.php +++ b/vendor/stripe/stripe-php/lib/Balance.php @@ -15,7 +15,6 @@ namespace Stripe; */ class Balance extends SingletonApiResource { - const OBJECT_NAME = "balance"; /** diff --git a/vendor/stripe/stripe-php/lib/BalanceTransaction.php b/vendor/stripe/stripe-php/lib/BalanceTransaction.php index c306ab067912ff223856d0df82334e423249f0f2..78805d4d448a3de900751d0c170aed26678595ef 100644 --- a/vendor/stripe/stripe-php/lib/BalanceTransaction.php +++ b/vendor/stripe/stripe-php/lib/BalanceTransaction.php @@ -24,7 +24,6 @@ namespace Stripe; */ class BalanceTransaction extends ApiResource { - const OBJECT_NAME = "balance_transaction"; use ApiOperations\All; @@ -63,13 +62,4 @@ class BalanceTransaction extends ApiResource const TYPE_TRANSFER_CANCEL = 'transfer_cancel'; const TYPE_TRANSFER_FAILURE = 'transfer_failure'; const TYPE_TRANSFER_REFUND = 'transfer_refund'; - - /** - * @return string The class URL for this resource. It needs to be special - * cased because it doesn't fit into the standard resource pattern. - */ - public static function classUrl() - { - return "/v1/balance/history"; - } } diff --git a/vendor/stripe/stripe-php/lib/BankAccount.php b/vendor/stripe/stripe-php/lib/BankAccount.php index 3fdc9188c648f6d8a046109e28d1bf28fa1a96e2..a77f3054ba5570fd14d4d43a3625bc75f016e322 100644 --- a/vendor/stripe/stripe-php/lib/BankAccount.php +++ b/vendor/stripe/stripe-php/lib/BankAccount.php @@ -25,7 +25,6 @@ namespace Stripe; */ class BankAccount extends ApiResource { - const OBJECT_NAME = "bank_account"; use ApiOperations\Delete; @@ -93,12 +92,12 @@ class BankAccount extends ApiResource throw new Error\InvalidRequest($msg, null); } - /** - * @param array|null $params - * @param array|string|null $options - * - * @return BankAccount The verified bank account. - */ + /** + * @param array|null $params + * @param array|string|null $options + * + * @return BankAccount The verified bank account. + */ public function verify($params = null, $options = null) { $url = $this->instanceUrl() . '/verify'; diff --git a/vendor/stripe/stripe-php/lib/BitcoinReceiver.php b/vendor/stripe/stripe-php/lib/BitcoinReceiver.php index b4cc5291b5623ffa7c252d39f508a45ce91deb15..a9272dfa28880f6efa59c40be949526e55c97143 100644 --- a/vendor/stripe/stripe-php/lib/BitcoinReceiver.php +++ b/vendor/stripe/stripe-php/lib/BitcoinReceiver.php @@ -12,7 +12,6 @@ namespace Stripe; */ class BitcoinReceiver extends ApiResource { - const OBJECT_NAME = "bitcoin_receiver"; use ApiOperations\All; diff --git a/vendor/stripe/stripe-php/lib/BitcoinTransaction.php b/vendor/stripe/stripe-php/lib/BitcoinTransaction.php index 8269fd216eb043e89ccbeafd14e7714272da303b..122be42314f95da3e1471a71ece0187dea9ae73e 100644 --- a/vendor/stripe/stripe-php/lib/BitcoinTransaction.php +++ b/vendor/stripe/stripe-php/lib/BitcoinTransaction.php @@ -9,6 +9,5 @@ namespace Stripe; */ class BitcoinTransaction extends ApiResource { - const OBJECT_NAME = "bitcoin_transaction"; } diff --git a/vendor/stripe/stripe-php/lib/Capability.php b/vendor/stripe/stripe-php/lib/Capability.php index 78cb03936956dc8ef71089e2414fcf12033a3d17..aabf54b1f1b72363d50b966c06f191240a3f6116 100644 --- a/vendor/stripe/stripe-php/lib/Capability.php +++ b/vendor/stripe/stripe-php/lib/Capability.php @@ -17,7 +17,6 @@ namespace Stripe; */ class Capability extends ApiResource { - const OBJECT_NAME = "capability"; use ApiOperations\Update; diff --git a/vendor/stripe/stripe-php/lib/Card.php b/vendor/stripe/stripe-php/lib/Card.php index 40de733e7807792a44787ad9244677edef8a45e7..51eb5add1c9fdd6cf17a9b0ffaf73b9ded14d6e4 100644 --- a/vendor/stripe/stripe-php/lib/Card.php +++ b/vendor/stripe/stripe-php/lib/Card.php @@ -38,7 +38,6 @@ namespace Stripe; */ class Card extends ApiResource { - const OBJECT_NAME = "card"; use ApiOperations\Delete; diff --git a/vendor/stripe/stripe-php/lib/Charge.php b/vendor/stripe/stripe-php/lib/Charge.php index 43274c5559c2b5eca8aaefecaa94f4d04b47010e..0c3853c9678751b4d84bb274ab3dd927f139f9da 100644 --- a/vendor/stripe/stripe-php/lib/Charge.php +++ b/vendor/stripe/stripe-php/lib/Charge.php @@ -53,7 +53,6 @@ namespace Stripe; */ class Charge extends ApiResource { - const OBJECT_NAME = "charge"; use ApiOperations\All; diff --git a/vendor/stripe/stripe-php/lib/Checkout/Session.php b/vendor/stripe/stripe-php/lib/Checkout/Session.php index 33fc6a08ab29baad0b0402c2c882d49fe9efa486..227dd7b68166ae4c57cc09d53bd7b9faec81bd92 100644 --- a/vendor/stripe/stripe-php/lib/Checkout/Session.php +++ b/vendor/stripe/stripe-php/lib/Checkout/Session.php @@ -23,7 +23,6 @@ namespace Stripe\Checkout; */ class Session extends \Stripe\ApiResource { - const OBJECT_NAME = "checkout.session"; use \Stripe\ApiOperations\Create; diff --git a/vendor/stripe/stripe-php/lib/Collection.php b/vendor/stripe/stripe-php/lib/Collection.php index 986cd33090a17c4061258c1a26918152c1a36a5b..38d3835ff03ab2a7f072ffa5fb40f80cbf770f84 100644 --- a/vendor/stripe/stripe-php/lib/Collection.php +++ b/vendor/stripe/stripe-php/lib/Collection.php @@ -14,7 +14,6 @@ namespace Stripe; */ class Collection extends StripeObject implements \IteratorAggregate { - const OBJECT_NAME = "list"; use ApiOperations\Request; diff --git a/vendor/stripe/stripe-php/lib/CountrySpec.php b/vendor/stripe/stripe-php/lib/CountrySpec.php index 668bfe62ed9f811638950628ac25f712ecf4920a..ef02c340b5e77cb9fdbb14c17e75bbcea27df2fc 100644 --- a/vendor/stripe/stripe-php/lib/CountrySpec.php +++ b/vendor/stripe/stripe-php/lib/CountrySpec.php @@ -18,7 +18,6 @@ namespace Stripe; */ class CountrySpec extends ApiResource { - const OBJECT_NAME = "country_spec"; use ApiOperations\All; diff --git a/vendor/stripe/stripe-php/lib/Coupon.php b/vendor/stripe/stripe-php/lib/Coupon.php index 51d4fd8659346656cd9a35714b93771bccf51a5e..76d549ad90d8bac2e3e196c327d107d07ce00ab3 100644 --- a/vendor/stripe/stripe-php/lib/Coupon.php +++ b/vendor/stripe/stripe-php/lib/Coupon.php @@ -25,7 +25,6 @@ namespace Stripe; */ class Coupon extends ApiResource { - const OBJECT_NAME = "coupon"; use ApiOperations\All; diff --git a/vendor/stripe/stripe-php/lib/CreditNote.php b/vendor/stripe/stripe-php/lib/CreditNote.php index 66351ffb8287758b2e9fbd1a2c2fcd15b6dc133f..c81d86ee66d1fef2aafb0bb1a418e046ed63beb9 100644 --- a/vendor/stripe/stripe-php/lib/CreditNote.php +++ b/vendor/stripe/stripe-php/lib/CreditNote.php @@ -27,7 +27,6 @@ namespace Stripe; */ class CreditNote extends ApiResource { - const OBJECT_NAME = "credit_note"; use ApiOperations\All; diff --git a/vendor/stripe/stripe-php/lib/Customer.php b/vendor/stripe/stripe-php/lib/Customer.php index 6e78f981ee4124eb963e3ccbb33eabd8a4d256d3..a3f7f6311da0084a484fc01475782483ea47d22c 100644 --- a/vendor/stripe/stripe-php/lib/Customer.php +++ b/vendor/stripe/stripe-php/lib/Customer.php @@ -33,7 +33,6 @@ namespace Stripe; */ class Customer extends ApiResource { - const OBJECT_NAME = "customer"; use ApiOperations\All; diff --git a/vendor/stripe/stripe-php/lib/Discount.php b/vendor/stripe/stripe-php/lib/Discount.php index a72d12bc6e3c128d539b391d743d4082d20f4e54..68bfc0fd74a6ffc7277708eed12b93090cf93845 100644 --- a/vendor/stripe/stripe-php/lib/Discount.php +++ b/vendor/stripe/stripe-php/lib/Discount.php @@ -16,6 +16,5 @@ namespace Stripe; */ class Discount extends StripeObject { - const OBJECT_NAME = "discount"; } diff --git a/vendor/stripe/stripe-php/lib/Dispute.php b/vendor/stripe/stripe-php/lib/Dispute.php index 2d58daa380a436f20ff47afc33c7ed7f6659bbbe..8ffc2d4cc73b4e5f37ab669a26741f6d423f659a 100644 --- a/vendor/stripe/stripe-php/lib/Dispute.php +++ b/vendor/stripe/stripe-php/lib/Dispute.php @@ -24,7 +24,6 @@ namespace Stripe; */ class Dispute extends ApiResource { - const OBJECT_NAME = "dispute"; use ApiOperations\All; diff --git a/vendor/stripe/stripe-php/lib/EphemeralKey.php b/vendor/stripe/stripe-php/lib/EphemeralKey.php index 5ed4646b4f73a8c32240358a54735a5ecaedb9b3..ea4cc756ccd05b8d101f069ea62dc98c8351d17c 100644 --- a/vendor/stripe/stripe-php/lib/EphemeralKey.php +++ b/vendor/stripe/stripe-php/lib/EphemeralKey.php @@ -17,7 +17,6 @@ namespace Stripe; */ class EphemeralKey extends ApiResource { - const OBJECT_NAME = "ephemeral_key"; use ApiOperations\Create { diff --git a/vendor/stripe/stripe-php/lib/Event.php b/vendor/stripe/stripe-php/lib/Event.php index a986c35dfec2401fe7932b706d97bcb2bd69c1f1..f9a004aa6a9750957d0bb1cf1b6b33987d419da1 100644 --- a/vendor/stripe/stripe-php/lib/Event.php +++ b/vendor/stripe/stripe-php/lib/Event.php @@ -20,7 +20,6 @@ namespace Stripe; */ class Event extends ApiResource { - const OBJECT_NAME = "event"; /** diff --git a/vendor/stripe/stripe-php/lib/ExchangeRate.php b/vendor/stripe/stripe-php/lib/ExchangeRate.php index 803a5f87700fb8382af9bfd02df62817ff10c823..6a7e7a36a3807f9c52eba2f01407d40f3545159c 100644 --- a/vendor/stripe/stripe-php/lib/ExchangeRate.php +++ b/vendor/stripe/stripe-php/lib/ExchangeRate.php @@ -9,7 +9,6 @@ namespace Stripe; */ class ExchangeRate extends ApiResource { - const OBJECT_NAME = "exchange_rate"; use ApiOperations\All; diff --git a/vendor/stripe/stripe-php/lib/FileLink.php b/vendor/stripe/stripe-php/lib/FileLink.php index 2a012b361052fc03b75a4b76a3be107b6029faca..385971f7d82c7753fbcd52bb098508e369eebf1f 100644 --- a/vendor/stripe/stripe-php/lib/FileLink.php +++ b/vendor/stripe/stripe-php/lib/FileLink.php @@ -19,7 +19,6 @@ namespace Stripe; */ class FileLink extends ApiResource { - const OBJECT_NAME = "file_link"; use ApiOperations\All; diff --git a/vendor/stripe/stripe-php/lib/Invoice.php b/vendor/stripe/stripe-php/lib/Invoice.php index 31a84585485033c6a418f71f853fca70c07c266b..a309dd9e477e54b4fccfcfb182a80165404c4af2 100644 --- a/vendor/stripe/stripe-php/lib/Invoice.php +++ b/vendor/stripe/stripe-php/lib/Invoice.php @@ -70,7 +70,6 @@ namespace Stripe; */ class Invoice extends ApiResource { - const OBJECT_NAME = "invoice"; use ApiOperations\All; diff --git a/vendor/stripe/stripe-php/lib/InvoiceItem.php b/vendor/stripe/stripe-php/lib/InvoiceItem.php index 02ca0f2d7ee3689afbfff257f8385a091432448d..2f74dabec5ce4ffd12960596d5de76c984d8701b 100644 --- a/vendor/stripe/stripe-php/lib/InvoiceItem.php +++ b/vendor/stripe/stripe-php/lib/InvoiceItem.php @@ -29,7 +29,6 @@ namespace Stripe; */ class InvoiceItem extends ApiResource { - const OBJECT_NAME = "invoiceitem"; use ApiOperations\All; diff --git a/vendor/stripe/stripe-php/lib/IssuerFraudRecord.php b/vendor/stripe/stripe-php/lib/IssuerFraudRecord.php index 8db4b31b0b4a067381ddc979bdf27c3c0d6155b3..805dc502885f1abbc12ea71a3a041e8ebcae64c8 100644 --- a/vendor/stripe/stripe-php/lib/IssuerFraudRecord.php +++ b/vendor/stripe/stripe-php/lib/IssuerFraudRecord.php @@ -17,7 +17,6 @@ namespace Stripe; */ class IssuerFraudRecord extends ApiResource { - const OBJECT_NAME = "issuer_fraud_record"; use ApiOperations\All; diff --git a/vendor/stripe/stripe-php/lib/LoginLink.php b/vendor/stripe/stripe-php/lib/LoginLink.php index 3a443bf16817dacbd204260a513a4e6708de694e..9f677364b173c91543833d859ed7e10911cdabed 100644 --- a/vendor/stripe/stripe-php/lib/LoginLink.php +++ b/vendor/stripe/stripe-php/lib/LoginLink.php @@ -13,6 +13,5 @@ namespace Stripe; */ class LoginLink extends ApiResource { - const OBJECT_NAME = "login_link"; } diff --git a/vendor/stripe/stripe-php/lib/Order.php b/vendor/stripe/stripe-php/lib/Order.php index 0688c6ef937d8c7492de09ca6d5a493605780f6c..1f2ce2563acb995f40ae015ff9ce1e71d46756fd 100644 --- a/vendor/stripe/stripe-php/lib/Order.php +++ b/vendor/stripe/stripe-php/lib/Order.php @@ -33,7 +33,6 @@ namespace Stripe; */ class Order extends ApiResource { - const OBJECT_NAME = "order"; use ApiOperations\All; diff --git a/vendor/stripe/stripe-php/lib/OrderItem.php b/vendor/stripe/stripe-php/lib/OrderItem.php index 26d49b4e75114fe1cbe64d83f8b7ab8e6e4a3007..6ef1242fc34aceb3eeecf416a39d513586b09d2b 100644 --- a/vendor/stripe/stripe-php/lib/OrderItem.php +++ b/vendor/stripe/stripe-php/lib/OrderItem.php @@ -17,6 +17,5 @@ namespace Stripe; */ class OrderItem extends StripeObject { - const OBJECT_NAME = "order_item"; } diff --git a/vendor/stripe/stripe-php/lib/OrderReturn.php b/vendor/stripe/stripe-php/lib/OrderReturn.php index f3705e86166db6f7779158b957736a55555afe54..3b1ca8efa1281a2ec96d58b884d7814e73ad5a41 100644 --- a/vendor/stripe/stripe-php/lib/OrderReturn.php +++ b/vendor/stripe/stripe-php/lib/OrderReturn.php @@ -19,7 +19,6 @@ namespace Stripe; */ class OrderReturn extends ApiResource { - const OBJECT_NAME = "order_return"; use ApiOperations\All; diff --git a/vendor/stripe/stripe-php/lib/PaymentIntent.php b/vendor/stripe/stripe-php/lib/PaymentIntent.php index 47cbf8fc6cb70ba398eb8a2012ea3b01f4ea3cfe..b70b1577899ec6ba7b88f4054ff138f2cb969547 100644 --- a/vendor/stripe/stripe-php/lib/PaymentIntent.php +++ b/vendor/stripe/stripe-php/lib/PaymentIntent.php @@ -42,7 +42,6 @@ namespace Stripe; */ class PaymentIntent extends ApiResource { - const OBJECT_NAME = "payment_intent"; use ApiOperations\All; diff --git a/vendor/stripe/stripe-php/lib/PaymentMethod.php b/vendor/stripe/stripe-php/lib/PaymentMethod.php index 1a0bc371d5d9fe3febc1dae77e971b9b80f2de82..c0557e8c39470a86fad8bd307310dca6f8dc0757 100644 --- a/vendor/stripe/stripe-php/lib/PaymentMethod.php +++ b/vendor/stripe/stripe-php/lib/PaymentMethod.php @@ -22,7 +22,6 @@ namespace Stripe; */ class PaymentMethod extends ApiResource { - const OBJECT_NAME = "payment_method"; use ApiOperations\All; diff --git a/vendor/stripe/stripe-php/lib/Payout.php b/vendor/stripe/stripe-php/lib/Payout.php index 365d7e47ee99d417b4edd6ef3c6c783a3bd7df53..131bd4ae06d4b1e8ca55f0a56cdb4bb4edbdc621 100644 --- a/vendor/stripe/stripe-php/lib/Payout.php +++ b/vendor/stripe/stripe-php/lib/Payout.php @@ -30,7 +30,6 @@ namespace Stripe; */ class Payout extends ApiResource { - const OBJECT_NAME = "payout"; use ApiOperations\All; diff --git a/vendor/stripe/stripe-php/lib/Person.php b/vendor/stripe/stripe-php/lib/Person.php index f79b80da060f26288af180f4b928364c617ba547..5540f7c5f2840081fdfe79eff80d41015b90b7fc 100644 --- a/vendor/stripe/stripe-php/lib/Person.php +++ b/vendor/stripe/stripe-php/lib/Person.php @@ -35,7 +35,6 @@ namespace Stripe; */ class Person extends ApiResource { - const OBJECT_NAME = "person"; use ApiOperations\Delete; diff --git a/vendor/stripe/stripe-php/lib/Plan.php b/vendor/stripe/stripe-php/lib/Plan.php index 54a2b58eb35fafcbb9180fb7ec41a3acde65ecb6..1a7ae87dda8b7373ec089c8caab41b83f12fa1c3 100644 --- a/vendor/stripe/stripe-php/lib/Plan.php +++ b/vendor/stripe/stripe-php/lib/Plan.php @@ -29,7 +29,6 @@ namespace Stripe; */ class Plan extends ApiResource { - const OBJECT_NAME = "plan"; use ApiOperations\All; diff --git a/vendor/stripe/stripe-php/lib/Product.php b/vendor/stripe/stripe-php/lib/Product.php index cb27ef42d8ccfc580bf0a7254175fc33d2badd07..6c60d20c4a7fe0c4f00bf8fd9811f660e708d38f 100644 --- a/vendor/stripe/stripe-php/lib/Product.php +++ b/vendor/stripe/stripe-php/lib/Product.php @@ -29,7 +29,6 @@ namespace Stripe; */ class Product extends ApiResource { - const OBJECT_NAME = "product"; use ApiOperations\All; diff --git a/vendor/stripe/stripe-php/lib/Recipient.php b/vendor/stripe/stripe-php/lib/Recipient.php index 2d5eda5cf7f77ff788ad8decfe4a1d35531fc9a0..35a695f5084bd859089af3acf0a43136ecf8d72b 100644 --- a/vendor/stripe/stripe-php/lib/Recipient.php +++ b/vendor/stripe/stripe-php/lib/Recipient.php @@ -24,7 +24,6 @@ namespace Stripe; */ class Recipient extends ApiResource { - const OBJECT_NAME = "recipient"; use ApiOperations\All; diff --git a/vendor/stripe/stripe-php/lib/RecipientTransfer.php b/vendor/stripe/stripe-php/lib/RecipientTransfer.php index 17de5a52e51541e9a81ec14992d7306957aa6eb5..7a2ec1f3fb70d4d7046476136e61da4035c5d760 100644 --- a/vendor/stripe/stripe-php/lib/RecipientTransfer.php +++ b/vendor/stripe/stripe-php/lib/RecipientTransfer.php @@ -34,6 +34,5 @@ namespace Stripe; */ class RecipientTransfer extends ApiResource { - const OBJECT_NAME = "recipient_transfer"; } diff --git a/vendor/stripe/stripe-php/lib/Refund.php b/vendor/stripe/stripe-php/lib/Refund.php index 6f3fdfaf44550e9e66da6953acbac39575cb15b2..f8b4529b72e732d8b11c118bbbb8789da42d1f1c 100644 --- a/vendor/stripe/stripe-php/lib/Refund.php +++ b/vendor/stripe/stripe-php/lib/Refund.php @@ -26,7 +26,6 @@ namespace Stripe; */ class Refund extends ApiResource { - const OBJECT_NAME = "refund"; use ApiOperations\All; diff --git a/vendor/stripe/stripe-php/lib/SKU.php b/vendor/stripe/stripe-php/lib/SKU.php index 5b50df8a8479122a224ffab09f50e73ce111d3fb..cb3aa6f73f1e5b3334b17714d3de969c7764762b 100644 --- a/vendor/stripe/stripe-php/lib/SKU.php +++ b/vendor/stripe/stripe-php/lib/SKU.php @@ -24,7 +24,6 @@ namespace Stripe; */ class SKU extends ApiResource { - const OBJECT_NAME = "sku"; use ApiOperations\All; diff --git a/vendor/stripe/stripe-php/lib/SetupIntent.php b/vendor/stripe/stripe-php/lib/SetupIntent.php index f476fb35fa9a4e6f5a6da90d8562f418a7ee1cc0..176a411b67fc06e5d152841238c5807fe223a1af 100644 --- a/vendor/stripe/stripe-php/lib/SetupIntent.php +++ b/vendor/stripe/stripe-php/lib/SetupIntent.php @@ -25,7 +25,6 @@ namespace Stripe; */ class SetupIntent extends ApiResource { - const OBJECT_NAME = "setup_intent"; use ApiOperations\All; diff --git a/vendor/stripe/stripe-php/lib/Source.php b/vendor/stripe/stripe-php/lib/Source.php index 1e2c8c7356591d2ce6145c604ad0850f670d0d2d..ec5cc42127e78df9a423ee84f2cd3c82655fd58f 100644 --- a/vendor/stripe/stripe-php/lib/Source.php +++ b/vendor/stripe/stripe-php/lib/Source.php @@ -43,7 +43,6 @@ namespace Stripe; */ class Source extends ApiResource { - const OBJECT_NAME = "source"; use ApiOperations\Create; diff --git a/vendor/stripe/stripe-php/lib/SourceTransaction.php b/vendor/stripe/stripe-php/lib/SourceTransaction.php index 018a896240e8c3f26c9291d82545ea1d039ace4c..d004032c569a375805e2cadf6f08a5d11ec49aee 100644 --- a/vendor/stripe/stripe-php/lib/SourceTransaction.php +++ b/vendor/stripe/stripe-php/lib/SourceTransaction.php @@ -18,6 +18,5 @@ namespace Stripe; */ class SourceTransaction extends ApiResource { - const OBJECT_NAME = "source_transaction"; } diff --git a/vendor/stripe/stripe-php/lib/Stripe.php b/vendor/stripe/stripe-php/lib/Stripe.php index 5b67435c007bbafc77a24faf5d64908eb177e966..0b533e0eb5cbdc7d104d310d41136b4c1da7aa28 100644 --- a/vendor/stripe/stripe-php/lib/Stripe.php +++ b/vendor/stripe/stripe-php/lib/Stripe.php @@ -46,7 +46,7 @@ class Stripe // @var int Maximum number of request retries public static $maxNetworkRetries = 0; - // @var boolean Whether client telemetry is enabled. Defaults to false. + // @var boolean Whether client telemetry is enabled. Defaults to true. public static $enableTelemetry = true; // @var float Maximum delay between retries, in seconds @@ -55,7 +55,7 @@ class Stripe // @var float Initial delay between retries, in seconds private static $initialNetworkRetryDelay = 0.5; - const VERSION = '6.40.0'; + const VERSION = '6.43.1'; /** * @return string The API key used for requests. diff --git a/vendor/stripe/stripe-php/lib/StripeObject.php b/vendor/stripe/stripe-php/lib/StripeObject.php index 432e95b7f0b703b22ba3b294b1f2b7ea7cefd7db..62e02c0e0cc03f2cd1b0df46c5cb0a7b9d827879 100644 --- a/vendor/stripe/stripe-php/lib/StripeObject.php +++ b/vendor/stripe/stripe-php/lib/StripeObject.php @@ -153,7 +153,7 @@ class StripeObject implements \ArrayAccess, \Countable, \JsonSerializable $nullval = null; if (!empty($this->_values) && array_key_exists($k, $this->_values)) { return $this->_values[$k]; - } else if (!empty($this->_transientValues) && $this->_transientValues->includes($k)) { + } elseif (!empty($this->_transientValues) && $this->_transientValues->includes($k)) { $class = get_class($this); $attrs = join(', ', array_keys($this->_values)); $message = "Stripe Notice: Undefined property of $class instance: $k. " diff --git a/vendor/stripe/stripe-php/lib/Subscription.php b/vendor/stripe/stripe-php/lib/Subscription.php index f5a46171d81e9da96b1fd9bfcb89d5b9a1fed66d..a1087a56a4ca6d7ce8b0f41ba58007ec9036cbe4 100644 --- a/vendor/stripe/stripe-php/lib/Subscription.php +++ b/vendor/stripe/stripe-php/lib/Subscription.php @@ -28,6 +28,7 @@ namespace Stripe; * @property string $latest_invoice * @property boolean $livemode * @property StripeObject $metadata + * @property string $pending_setup_intent * @property Plan $plan * @property int $quantity * @property SubscriptionSchedule $schedule @@ -42,7 +43,6 @@ namespace Stripe; */ class Subscription extends ApiResource { - const OBJECT_NAME = "subscription"; use ApiOperations\All; diff --git a/vendor/stripe/stripe-php/lib/SubscriptionItem.php b/vendor/stripe/stripe-php/lib/SubscriptionItem.php index 5baa540fe9835d9fd6e214f9b098fa61e24969af..d0d2dea8fcf82aa7d07c8e1bb6a2f0a6eb8e7f79 100644 --- a/vendor/stripe/stripe-php/lib/SubscriptionItem.php +++ b/vendor/stripe/stripe-php/lib/SubscriptionItem.php @@ -19,20 +19,34 @@ namespace Stripe; */ class SubscriptionItem extends ApiResource { - const OBJECT_NAME = "subscription_item"; + const PATH_USAGE_RECORDS = '/usage_records'; + use ApiOperations\All; use ApiOperations\Create; use ApiOperations\Delete; + use ApiOperations\NestedResource; use ApiOperations\Retrieve; use ApiOperations\Update; + /** + * @param string|null $id The ID of the subscription item on which to create the usage record. + * @param array|null $params + * @param array|string|null $opts + * + * @return ApiResource + */ + public static function createUsageRecord($id, $params = null, $opts = null) + { + return self::_createNestedResource($id, static::PATH_USAGE_RECORDS, $params, $opts); + } + /** * @param array|null $params * @param array|string|null $options * - * @return Collection The list of source transactions. + * @return Collection The list of usage record summaries. */ public function usageRecordSummaries($params = null, $options = null) { diff --git a/vendor/stripe/stripe-php/lib/SubscriptionSchedule.php b/vendor/stripe/stripe-php/lib/SubscriptionSchedule.php index ac3686f1db81be189b4bb01a05692a9c5883f2a1..e41f279a036448f446c62541dce20e492fbc49ee 100644 --- a/vendor/stripe/stripe-php/lib/SubscriptionSchedule.php +++ b/vendor/stripe/stripe-php/lib/SubscriptionSchedule.php @@ -30,7 +30,6 @@ namespace Stripe; */ class SubscriptionSchedule extends ApiResource { - const OBJECT_NAME = "subscription_schedule"; use ApiOperations\All; @@ -39,8 +38,6 @@ class SubscriptionSchedule extends ApiResource use ApiOperations\Update; use ApiOperations\NestedResource; - const PATH_REVISIONS = '/revisions'; - /** * @param array|null $params * @param array|string|null $opts @@ -68,44 +65,4 @@ class SubscriptionSchedule extends ApiResource $this->refreshFrom($response, $opts); return $this; } - - /** - * @param array|null $params - * @param array|string|null $options - * - * @return Collection The list of subscription schedule revisions. - */ - public function revisions($params = null, $options = null) - { - $url = $this->instanceUrl() . '/revisions'; - list($response, $opts) = $this->_request('get', $url, $params, $options); - $obj = Util\Util::convertToStripeObject($response, $opts); - $obj->setLastResponse($response); - return $obj; - } - - /** - * @param array|null $id The ID of the subscription schedule to which the person belongs. - * @param array|null $personId The ID of the person to retrieve. - * @param array|null $params - * @param array|string|null $opts - * - * @return Revision - */ - public static function retrieveRevision($id, $personId, $params = null, $opts = null) - { - return self::_retrieveNestedResource($id, static::PATH_REVISIONS, $personId, $params, $opts); - } - - /** - * @param array|null $id The ID of the subscription schedule on which to retrieve the persons. - * @param array|null $params - * @param array|string|null $opts - * - * @return Collection The list of revisions. - */ - public static function allRevisions($id, $params = null, $opts = null) - { - return self::_allNestedResources($id, static::PATH_REVISIONS, $params, $opts); - } } diff --git a/vendor/stripe/stripe-php/lib/SubscriptionScheduleRevision.php b/vendor/stripe/stripe-php/lib/SubscriptionScheduleRevision.php deleted file mode 100644 index 12177236530b3f37f786241087707cb5347c725f..0000000000000000000000000000000000000000 --- a/vendor/stripe/stripe-php/lib/SubscriptionScheduleRevision.php +++ /dev/null @@ -1,77 +0,0 @@ -<?php - -namespace Stripe; - -/** - * Class SubscriptionScheduleRevision - * - * @property string $id - * @property string $object - * @property int $created - * @property mixed $invoice_settings - * @property boolean $livemode - * @property mixed $phases - * @property string $previous_revision - * @property string $renewal_behavior - * @property mixed $renewal_interval - * @property string $schedule - * - * @package Stripe - */ -class SubscriptionScheduleRevision extends ApiResource -{ - - const OBJECT_NAME = "subscription_schedule_revision"; - - use ApiOperations\All; - use ApiOperations\Retrieve; - - /** - * @return string The API URL for this Subscription Schedule Revision. - */ - public function instanceUrl() - { - $id = $this['id']; - $schedule = $this['schedule']; - if (!$id) { - throw new Error\InvalidRequest( - "Could not determine which URL to request: " . - "class instance has invalid ID: $id", - null - ); - } - $id = Util\Util::utf8($id); - $schedule = Util\Util::utf8($schedule); - - $base = SubscriptionSchedule::classUrl(); - $scheduleExtn = urlencode($schedule); - $extn = urlencode($id); - return "$base/$scheduleExtn/revisions/$extn"; - } - - /** - * @param array|string $_id - * @param array|string|null $_opts - * - * @throws \Stripe\Error\InvalidRequest - */ - public static function retrieve($_id, $_opts = null) - { - $msg = "Subscription Schedule Revisions cannot be accessed without a Subscription Schedule ID. " . - "Retrieve one using \$schedule->retrieveRevision('revision_id') instead."; - throw new Error\InvalidRequest($msg, null); - } - - /** - * @param array|string $_id - * @param array|string|null $_opts - * - * @throws \Stripe\Error\InvalidRequest - */ - public static function all($params = null, $opts = null) - { - $msg = "Subscription Schedule Revisions cannot be listed without a Subscription Schedule ID. " . - "List those using \$schedule->allRevisions('revision_id') instead."; - throw new Error\InvalidRequest($msg, null); - } -} diff --git a/vendor/stripe/stripe-php/lib/TaxId.php b/vendor/stripe/stripe-php/lib/TaxId.php index 0f72a2ac95f337d2130417f75427e8bcae6f1bb2..8d607132e6e977f6766d6baed8d9f087fe0635f0 100644 --- a/vendor/stripe/stripe-php/lib/TaxId.php +++ b/vendor/stripe/stripe-php/lib/TaxId.php @@ -19,7 +19,6 @@ namespace Stripe; */ class TaxId extends ApiResource { - const OBJECT_NAME = "tax_id"; use ApiOperations\Delete; diff --git a/vendor/stripe/stripe-php/lib/TaxRate.php b/vendor/stripe/stripe-php/lib/TaxRate.php index 25884dd840f0fc857db469df9f938a51789aab6e..9f416507cf4547d5f32a3d1ade8317d086306b46 100644 --- a/vendor/stripe/stripe-php/lib/TaxRate.php +++ b/vendor/stripe/stripe-php/lib/TaxRate.php @@ -21,7 +21,6 @@ namespace Stripe; */ class TaxRate extends ApiResource { - const OBJECT_NAME = "tax_rate"; use ApiOperations\All; diff --git a/vendor/stripe/stripe-php/lib/ThreeDSecure.php b/vendor/stripe/stripe-php/lib/ThreeDSecure.php index 5e67e351ba73d364015e30eae181587b58007654..3869eba85b3687dc2c8770c1aaba6d6f4dad0da9 100644 --- a/vendor/stripe/stripe-php/lib/ThreeDSecure.php +++ b/vendor/stripe/stripe-php/lib/ThreeDSecure.php @@ -4,7 +4,6 @@ namespace Stripe; class ThreeDSecure extends ApiResource { - const OBJECT_NAME = "three_d_secure"; use ApiOperations\Create; diff --git a/vendor/stripe/stripe-php/lib/Token.php b/vendor/stripe/stripe-php/lib/Token.php index 989219fc7895d0fa68448429f0fd89b5970927a8..629d51f6867eccb62d839c2d68d20a83ed71d7ec 100644 --- a/vendor/stripe/stripe-php/lib/Token.php +++ b/vendor/stripe/stripe-php/lib/Token.php @@ -19,7 +19,6 @@ namespace Stripe; */ class Token extends ApiResource { - const OBJECT_NAME = "token"; use ApiOperations\Create; diff --git a/vendor/stripe/stripe-php/lib/Topup.php b/vendor/stripe/stripe-php/lib/Topup.php index 9ad96670a5565861bd534ae9c1570e1bc1505403..37625a329ff6971d425090a09d4e0d5a7a1c9fbd 100644 --- a/vendor/stripe/stripe-php/lib/Topup.php +++ b/vendor/stripe/stripe-php/lib/Topup.php @@ -26,7 +26,6 @@ namespace Stripe; */ class Topup extends ApiResource { - const OBJECT_NAME = "topup"; use ApiOperations\All; diff --git a/vendor/stripe/stripe-php/lib/Transfer.php b/vendor/stripe/stripe-php/lib/Transfer.php index dff2faa33296bdf2a4b4dbf1b2df8832665ffe64..bf9fa25ce37839fdb38f5ee3f1b38144da991e60 100644 --- a/vendor/stripe/stripe-php/lib/Transfer.php +++ b/vendor/stripe/stripe-php/lib/Transfer.php @@ -27,7 +27,6 @@ namespace Stripe; */ class Transfer extends ApiResource { - const OBJECT_NAME = "transfer"; use ApiOperations\All; diff --git a/vendor/stripe/stripe-php/lib/TransferReversal.php b/vendor/stripe/stripe-php/lib/TransferReversal.php index c945e5a93603b976491e0a3cd93e64c2182edec9..a4f94f2fb43b66270b5982fdcda9bacea2f83df1 100644 --- a/vendor/stripe/stripe-php/lib/TransferReversal.php +++ b/vendor/stripe/stripe-php/lib/TransferReversal.php @@ -20,7 +20,6 @@ namespace Stripe; */ class TransferReversal extends ApiResource { - const OBJECT_NAME = "transfer_reversal"; use ApiOperations\Update { diff --git a/vendor/stripe/stripe-php/lib/UsageRecord.php b/vendor/stripe/stripe-php/lib/UsageRecord.php index a9e3a25e3e91aaf05b0fe119e4a4048893c0e85a..1239222d49d8f8e0f79cf29928f700b7cba6bc06 100644 --- a/vendor/stripe/stripe-php/lib/UsageRecord.php +++ b/vendor/stripe/stripe-php/lib/UsageRecord.php @@ -16,7 +16,6 @@ namespace Stripe; */ class UsageRecord extends ApiResource { - const OBJECT_NAME = "usage_record"; /** diff --git a/vendor/stripe/stripe-php/lib/UsageRecordSummary.php b/vendor/stripe/stripe-php/lib/UsageRecordSummary.php index b8f4aebe974fa8ef922a62bd3d1c6b4cf39df856..9d3e7a396edf9a2477814bfebe0dbcd38c66a0dd 100644 --- a/vendor/stripe/stripe-php/lib/UsageRecordSummary.php +++ b/vendor/stripe/stripe-php/lib/UsageRecordSummary.php @@ -17,6 +17,5 @@ namespace Stripe; */ class UsageRecordSummary extends ApiResource { - const OBJECT_NAME = "usage_record_summary"; } diff --git a/vendor/stripe/stripe-php/lib/Util/LoggerInterface.php b/vendor/stripe/stripe-php/lib/Util/LoggerInterface.php index bbdfc929982b474dd7f3786045a46f9c52fd29ec..cf04e1d2baddfabf99f35984b2aa33a94a570a28 100644 --- a/vendor/stripe/stripe-php/lib/Util/LoggerInterface.php +++ b/vendor/stripe/stripe-php/lib/Util/LoggerInterface.php @@ -30,7 +30,7 @@ interface LoggerInterface * * @param string $message * @param array $context - * @return null + * @return void */ public function error($message, array $context = []); } diff --git a/vendor/stripe/stripe-php/lib/Util/Util.php b/vendor/stripe/stripe-php/lib/Util/Util.php index f9f15440023b688fb878fae8ad69f6fae6e59a2a..dd8677aa0995fcfe92e2a2cbea770c25b172db80 100644 --- a/vendor/stripe/stripe-php/lib/Util/Util.php +++ b/vendor/stripe/stripe-php/lib/Util/Util.php @@ -135,7 +135,6 @@ abstract class Util \Stripe\Subscription::OBJECT_NAME => 'Stripe\\Subscription', \Stripe\SubscriptionItem::OBJECT_NAME => 'Stripe\\SubscriptionItem', \Stripe\SubscriptionSchedule::OBJECT_NAME => 'Stripe\\SubscriptionSchedule', - \Stripe\SubscriptionScheduleRevision::OBJECT_NAME => 'Stripe\\SubscriptionScheduleRevision', \Stripe\TaxId::OBJECT_NAME => 'Stripe\\TaxId', \Stripe\TaxRate::OBJECT_NAME => 'Stripe\\TaxRate', \Stripe\ThreeDSecure::OBJECT_NAME => 'Stripe\\ThreeDSecure', diff --git a/vendor/stripe/stripe-php/lib/WebhookEndpoint.php b/vendor/stripe/stripe-php/lib/WebhookEndpoint.php index 27ecacf690c358961bb9ce99026f01b5e376da57..636c7e1927db59325338a9d457bcfc4aec182743 100644 --- a/vendor/stripe/stripe-php/lib/WebhookEndpoint.php +++ b/vendor/stripe/stripe-php/lib/WebhookEndpoint.php @@ -18,7 +18,6 @@ namespace Stripe; */ class WebhookEndpoint extends ApiResource { - const OBJECT_NAME = "webhook_endpoint"; use ApiOperations\All; diff --git a/xml/Menu/stripe.xml b/xml/Menu/stripe.xml index 23cac580e0dab8ddf17d5a6cd0a4a33a2a56f96c..21a2c2ccc2a22e49dcda3916c9e6d930517ee3c8 100644 --- a/xml/Menu/stripe.xml +++ b/xml/Menu/stripe.xml @@ -6,4 +6,16 @@ <title>Check and Fix Stripe Webhooks</title> <access_arguments>access CiviCRM</access_arguments> </item> + <item> + <path>civicrm/stripe/confirm-payment</path> + <page_callback>CRM_Stripe_AJAX::confirmPayment</page_callback> + <title>Client Secret</title> + <access_callback>1</access_callback> + </item> + <item> + <path>civicrm/admin/setting/stripe</path> + <title>Stripe Settings</title> + <page_callback>CRM_Admin_Form_Generic</page_callback> + <access_arguments>administer CiviCRM</access_arguments> + </item> </menu>