From 97f15fe0098763e3d474c9fa3dcf02fe5833710a Mon Sep 17 00:00:00 2001 From: Matthew Wire <mjw@mjwconsult.co.uk> Date: Mon, 20 Mar 2023 16:19:56 +0000 Subject: [PATCH] Add Stripe Checkout --- CRM/Core/Payment/StripeCheckout.php | 228 ++++++++++++++++++++++++++++ stripe.mgd.php | 23 +++ 2 files changed, 251 insertions(+) diff --git a/CRM/Core/Payment/StripeCheckout.php b/CRM/Core/Payment/StripeCheckout.php index e69de29b..65fa2dba 100644 --- a/CRM/Core/Payment/StripeCheckout.php +++ b/CRM/Core/Payment/StripeCheckout.php @@ -0,0 +1,228 @@ +<?php +/* + +--------------------------------------------------------------------+ + | Copyright CiviCRM LLC. All rights reserved. | + | | + | This work is published under the GNU AGPLv3 license with some | + | permitted exceptions and without any warranty. For full license | + | and copyright information, see https://civicrm.org/licensing | + +--------------------------------------------------------------------+ + */ + +use Civi\Api4\PaymentprocessorWebhook; +use CRM_Stripe_ExtensionUtil as E; +use Civi\Payment\PropertyBag; +use Stripe\Stripe; +use Civi\Payment\Exception\PaymentProcessorException; +use Stripe\StripeObject; +use Stripe\Webhook; + +/** + * Class CRM_Core_Payment_Stripe + */ +class CRM_Core_Payment_StripeCheckout extends CRM_Core_Payment_Stripe { + + use CRM_Core_Payment_MJWTrait; + + /** + * Override CRM_Core_Payment function + * + * @return string + */ + public function getPaymentTypeName() { + return 'stripe-checkout'; + } + + /** + * Override CRM_Core_Payment function + * + * @return string + */ + public function getPaymentTypeLabel() { + return E::ts('Stripe Checkout'); + } + + /** + * We can use the stripe processor on the backend + * + * @return bool + */ + public function supportsBackOffice() { + return FALSE; + } + + /** + * We can edit stripe recurring contributions + * @return bool + */ + public function supportsEditRecurringContribution() { + return FALSE; + } + + public function supportsRecurring() { + return TRUE; + } + + /** + * Does this payment processor support refund? + * + * @return bool + */ + public function supportsRefund() { + return TRUE; + } + + /** + * Can we set a future recur start date? + * + * @return bool + */ + public function supportsFutureRecurStartDate() { + return FALSE; + } + + /** + * Is an authorize-capture flow supported. + * + * @return bool + */ + protected function supportsPreApproval() { + return FALSE; + } + + /** + * Does this processor support cancelling recurring contributions through code. + * + * If the processor returns true it must be possible to take action from within CiviCRM + * that will result in no further payments being processed. + * + * @return bool + */ + protected function supportsCancelRecurring() { + return TRUE; + } + + /** + * Does the processor support the user having a choice as to whether to cancel the recurring with the processor? + * + * If this returns TRUE then there will be an option to send a cancellation request in the cancellation form. + * + * This would normally be false for processors where CiviCRM maintains the schedule. + * + * @return bool + */ + protected function supportsCancelRecurringNotifyOptional() { + return TRUE; + } + + /** + * Set default values when loading the (payment) form + * + * @param \CRM_Core_Form $form + */ + public function buildForm(&$form) {} + + /** + * Process payment + * Submit a payment using Stripe's PHP API: + * https://stripe.com/docs/api?lang=php + * Payment processors should set payment_status_id/payment_status. + * + * @param array|PropertyBag $paymentParams + * Assoc array of input parameters for this transaction. + * @param string $component + * + * @return array + * Result array + * + * @throws \CRM_Core_Exception + * @throws \CiviCRM_API3_Exception + * @throws \Civi\Payment\Exception\PaymentProcessorException + */ + public function doPayment(&$paymentParams, $component = 'contribute') { + /* @var \Civi\Payment\PropertyBag $propertyBag */ + $propertyBag = \Civi\Payment\PropertyBag::cast($paymentParams); + + $zeroAmountPayment = $this->processZeroAmountPayment($propertyBag); + if ($zeroAmountPayment) { + return $zeroAmountPayment; + } + $propertyBag = $this->beginDoPayment($propertyBag); + + // Not sure what the point of this next line is. + $this->_component = $component; + $successUrl = $this->getReturnSuccessUrl($paymentParams['qfKey']); + $failUrl = $this->getCancelUrl($paymentParams['qfKey'], NULL); + + // Get existing/saved Stripe customer or create a new one + $stripeCustomerID = NULL; + $existingStripeCustomer = \Civi\Api4\StripeCustomer::get(FALSE) + ->addWhere('contact_id', '=', $propertyBag->getContactID()) + ->addWhere('processor_id', '=', $this->getPaymentProcessor()['id']) + ->execute() + ->first(); + if (empty($existingStripeCustomer)) { + $stripeCustomer = $this->getStripeCustomer($propertyBag); + $stripeCustomerID = $stripeCustomer->id; + } + else { + $stripeCustomerID = $existingStripeCustomer['customer_id']; + } + + // Build the checkout session parameters + $checkoutSessionParams = [ + 'line_items' => $this->buildCheckoutLineItems($paymentParams['line_item'], $propertyBag), + 'mode' => $propertyBag->getIsRecur() ? 'subscription' : 'payment', + 'success_url' => $successUrl, + 'cancel_url' => $failUrl, + // 'customer_email' => $propertyBag->getEmail(), + 'customer' => $stripeCustomerID, + // 'submit_type' => one of 'auto', pay, book, donate + ]; + $checkoutSession = $this->stripeClient->checkout->sessions->create($checkoutSessionParams); + + CRM_Stripe_BAO_StripeCustomer::updateMetadata(['contact_id' => $propertyBag->getContactID()], $this, $checkoutSession['customer']); + + // Allow each CMS to do a pre-flight check before redirecting to PayPal. + CRM_Core_Config::singleton()->userSystem->prePostRedirect(); + CRM_Utils_System::setHttpHeader("HTTP/1.1 303 See Other", ''); + CRM_Utils_System::redirect($checkoutSession->url); + } + + /** + * Takes the lineitems passed into doPayment and converts them into an array suitable for passing to Stripe Checkout + * + * @param array $civicrmLineItems + * @param string $currency + * + * @return array + * @throws \Brick\Money\Exception\UnknownCurrencyException + */ + private function buildCheckoutLineItems(array $civicrmLineItems, PropertyBag $propertyBag) { + foreach ($civicrmLineItems as $priceSetLines) { + foreach ($priceSetLines as $lineItem) { + $checkoutLineItem = [ + 'price_data' => [ + 'currency' => $propertyBag->getCurrency(), + 'unit_amount' => $this->getAmountFormattedForStripeAPI(PropertyBag::cast(['amount' => $lineItem['unit_price'], 'currency' => $propertyBag->getCurrency()])), + 'product_data' => [ + 'name' => $lineItem['field_title'], + 'description' => $lineItem['label'], + //'images' => ['https://example.com/t-shirt.png'], + ], + ], + 'quantity' => $lineItem['qty'], + ]; + if ($propertyBag->getIsRecur()) { + $checkoutLineItem['price_data']['recurring'] = [ + 'interval' => $propertyBag->getRecurFrequencyUnit(), + 'interval_count' => $propertyBag->getRecurFrequencyInterval(), + ]; + } + $checkoutLineItems[] = $checkoutLineItem; + } + } + return $checkoutLineItems ?? []; + } + +} diff --git a/stripe.mgd.php b/stripe.mgd.php index 165f3e5c..0a844e00 100644 --- a/stripe.mgd.php +++ b/stripe.mgd.php @@ -42,4 +42,27 @@ return [ ], ], ], + 1 => [ + 'name' => 'StripeCheckout', + 'entity' => 'PaymentProcessorType', + 'params' => [ + 'version' => 3, + 'name' => 'StripeCheckout', + 'title' => 'Stripe Checkout', + 'description' => 'Stripe Checkout Payment Processor', + 'class_name' => 'Payment_StripeCheckout', + 'user_name_label' => 'Publishable key', + 'password_label' => 'Secret Key', + 'signature_label' => 'Webhook Secret', + 'url_site_default' => 'http://unused.com', + 'url_site_test_default' => 'http://unused.com', + 'billing_mode' => 1, + 'payment_type' => 1, + 'is_recur' => 1, + ], + 'match' => [ + 'name', + ], + ], + ]; -- GitLab