diff --git a/api/v3/Stripe/Processpaymentintent.php b/api/v3/Stripe/Processpaymentintent.php new file mode 100644 index 0000000000000000000000000000000000000000..30cea1f6477256f86429f132cbf954852fca5dd6 --- /dev/null +++ b/api/v3/Stripe/Processpaymentintent.php @@ -0,0 +1,160 @@ +<?php +/** + * https://civicrm.org/licensing + */ + +use CRM_Stripe_ExtensionUtil as E; + +/** + * Stripe.ProcessPaymentIntent API specification + * + * @param array $spec description of fields supported by this API call + * + * @return void + */ +function _civicrm_api3_stripe_ProcessPaymentIntent_spec(&$spec) { + $spec['payment_method_id']['title'] = E::ts("Stripe generated code used to create a payment intent."); + $spec['payment_method_id']['type'] = CRM_Utils_Type::T_STRING; + $spec['payment_method_id']['api.default'] = NULL; + $spec['payment_intent_id']['title'] = ts("The payment intent id itself, if available."); + $spec['payment_intent_id']['type'] = CRM_Utils_Type::T_STRING; + $spec['payment_intent_id']['api.default'] = NULL; + $spec['amount']['title'] = ts("The payment amount."); + $spec['amount']['type'] = CRM_Utils_Type::T_STRING; + $spec['amount']['api.default'] = NULL; + $spec['capture']['title'] = ts("Whether we should try to capture the amount, not just confirm it."); + $spec['capture']['type'] = CRM_Utils_Type::T_BOOLEAN; + $spec['capture']['api.default'] = FALSE; + $spec['description']['title'] = ts("Describe the payment."); + $spec['description']['type'] = CRM_Utils_Type::T_STRING; + $spec['description']['api.default'] = NULL; + $spec['currency']['title'] = ts("Whether we should try to capture the amount, not just confirm it."); + $spec['currency']['type'] = CRM_Utils_Type::T_STRING; + $spec['currency']['api.default'] = CRM_Core_Config::singleton()->defaultCurrency; + $spec['payment_processor_id']['title'] = ts("The stripe payment processor id."); + $spec['payment_processor_id']['type'] = CRM_Utils_Type::T_INT; + $spec['payment_processor_id']['api.required'] = TRUE; +} + +/** + * Stripe.ProcessPaymentIntent API + * + * Generate the paymentIntent for civicrm_stripe.js + * + * In the normal flow of a CiviContribute form, this will be called with a + * payment_method_id (which is generated by Stripe via its javascript code), + * in which case it will create a PaymentIntent using that and *attempt* to + * 'confirm' it. + * + * This can also be called with a payment_intent_id instead, in which case it + * will retrieve the PaymentIntent and attempt (again) to 'confirm' it. This + * is useful to confirm funds after a user has completed SCA in their + * browser. + * + * 'confirming' a PaymentIntent refers to the process by which the funds are + * reserved in the cardholder's account, but not actually taken yet. + * + * Taking the funds ('capturing') should go through without problems once the + * transaction has been confirmed - this is done later on in the process. + * + * Nb. confirmed funds are released and will become available to the + * cardholder again if the PaymentIntent is cancelled or is not captured + * within 1 week. + * + * @param array $params + * + * @return array API result descriptor + * @throws \API_Exception + * @throws \CiviCRM_API3_Exception + * @throws \Stripe\Error\Api + */ +function civicrm_api3_stripe_ProcessPaymentIntent($params) { + $paymentMethodID = $params['payment_method_id']; + $paymentIntentID = $params['payment_intent_id']; + $amount = $params['amount']; + $capture = $params['capture']; + $title = $params['description']; + $confirm = TRUE; + if (empty($amount)) { + $amount = 1; + $confirm = FALSE; + } + $currency = $params['currency']; + $processorID = $params['payment_processor_id']; + $processor = new CRM_Core_Payment_Stripe('', civicrm_api3('PaymentProcessor', 'getsingle', ['id' => $processorID])); + $processor->setAPIParams(); + + if ($paymentIntentID) { + // We already have a PaymentIntent, retrieve and attempt confirm. + $intent = \Stripe\PaymentIntent::retrieve($paymentIntentID); + if ($intent->status === 'requires_confirmation') { + $intent->confirm(); + } + if ($capture && $intent->status === 'requires_capture') { + $intent->capture(); + } + } + else { + // We don't yet have a PaymentIntent, create one using the + // Payment Method ID and attempt to confirm it too. + $intent = \Stripe\PaymentIntent::create([ + 'payment_method' => $paymentMethodID, + 'amount' => $processor->getAmount(['amount' => $amount, 'currency' => $currency]), + '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' => $confirm, + ]); + } + + // Save the generated paymentIntent in the CiviCRM database for later tracking + $intentParams = [ + 'paymentintent_id' => $intent->id, + 'payment_processor_id' => $processorID, + 'status' => $intent->status, + 'description' => $title, + ]; + CRM_Stripe_BAO_StripePaymentintent::create($intentParams); + + $out = civicrm_api3_stripe_GeneratePaymentResponse($intent); + return civicrm_api3_create_success($out); +} + +/** + * Generate the json response for civicrm_stripe.js + * + * @param \Stripe\PaymentIntent $intent + */ +function civicrm_api3_stripe_GeneratePaymentResponse($intent) { + if ($intent->status === 'requires_action' && + $intent->next_action->type === 'use_stripe_sdk') { + // Tell the client to handle the action + return [ + 'requires_action' => true, + 'payment_intent_client_secret' => $intent->client_secret, + ]; + } + elseif (($intent->status === 'requires_capture') || ($intent->status === 'requires_confirmation')) { + // paymentIntent = requires_capture / requires_confirmation + // The payment intent has been confirmed, we just need to capture the payment + // Handle post-payment fulfillment + return [ + 'success' => true, + 'paymentIntent' => ['id' => $intent->id], + ]; + } + elseif ($intent->status === 'succeeded') { + return [ + 'success' => true, + 'paymentIntent' => ['id' => $intent->id], + ]; + } + else { + // Invalid status + throw new API_Exception('Invalid PaymentIntent status'); + } +} +