diff --git a/CHANGELOG.md b/CHANGELOG.md index a111ee2..24a09d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Release Notes for Stripe for Craft Commerce +## 4.0.1.1 - 2023-10-25 + +- Restored support for backend payments using the old payment form. +- Fixed missing icon. + ## 4.0.1 - 2023-09-28 - Fixed a PHP error that occurred when switching a subscription’s plan. diff --git a/src/gateways/PaymentIntents.php b/src/gateways/PaymentIntents.php index 9e2a5cb..9c040e3 100644 --- a/src/gateways/PaymentIntents.php +++ b/src/gateways/PaymentIntents.php @@ -11,6 +11,7 @@ use craft\commerce\base\Plan as BasePlan; use craft\commerce\base\RequestResponseInterface; use craft\commerce\base\SubscriptionResponseInterface; +use craft\commerce\behaviors\CustomerBehavior; use craft\commerce\elements\Subscription; use craft\commerce\errors\PaymentSourceCreatedLaterException; use craft\commerce\errors\SubscriptionException; @@ -31,6 +32,7 @@ use craft\commerce\stripe\responses\CheckoutSessionResponse; use craft\commerce\stripe\responses\PaymentIntentResponse; use craft\commerce\stripe\web\assets\elementsform\ElementsFormAsset; +use craft\commerce\stripe\web\assets\intentsform\IntentsFormAsset; use craft\elements\User; use craft\helpers\Json; use craft\helpers\StringHelper; @@ -81,6 +83,54 @@ public function showPaymentFormSubmitButton(): bool return false; } + /** + * @inheritdoc + */ + public function getOldPaymentFormHtml(array $params): ?string + { + $defaults = [ + 'gateway' => $this, + 'paymentForm' => $this->getPaymentFormModel(), + 'scenario' => 'payment', + 'handle' => $this->handle, + ]; + + $params = array_merge($defaults, $params); + + // If there's no order passed, add the current cart if we're not messing around in backend. + if (!isset($params['order']) && !Craft::$app->getRequest()->getIsCpRequest()) { + if ($cart = Commerce::getInstance()->getCarts()->getCart()) { + $billingAddress = $cart->getBillingAddress(); + + /** @var User|CustomerBehavior|null $user */ + $user = $cart->getCustomer(); + if (!$billingAddress && $user) { + $billingAddress = $user->getPrimaryBillingAddress(); + } + } + } else { + $billingAddress = $params['order']->getBillingAddress(); + } + + if ($billingAddress) { + $params['billingAddress'] = $billingAddress; + } + + $view = Craft::$app->getView(); + + $previousMode = $view->getTemplateMode(); + $view->setTemplateMode(View::TEMPLATE_MODE_CP); + + $view->registerScript('', View::POS_END, ['src' => 'https://js.stripe.com/v3/']); // we need this to load at end of body + $view->registerAssetBundle(IntentsFormAsset::class); + + $html = $view->renderTemplate('commerce-stripe/paymentForms/oldIntentsForm', $params); + + $view->setTemplateMode($previousMode); + + return $html; + } + /** * @inheritdoc */ diff --git a/src/templates/paymentForms/oldIntentsForm.twig b/src/templates/paymentForms/oldIntentsForm.twig new file mode 100644 index 0000000..0645db4 --- /dev/null +++ b/src/templates/paymentForms/oldIntentsForm.twig @@ -0,0 +1,108 @@ +{% set paymentFormNamespace = handle|commercePaymentFormNamespace %} +
+ + + + + {% if billingAddress is defined %} + + {% endif %} +
\ No newline at end of file diff --git a/src/utilities/Sync.php b/src/utilities/Sync.php index c7f283a..0058d39 100644 --- a/src/utilities/Sync.php +++ b/src/utilities/Sync.php @@ -41,7 +41,7 @@ public static function id(): string */ public static function iconPath(): ?string { - return Craft::getAlias('@vendor') . '/craftcms/stripe/src/icon-mask.svg'; + return Craft::getAlias('@vendor') . '/craftcms/commerce-stripe/src/icon-mask.svg'; } /** diff --git a/src/web/assets/intentsform/IntentsFormAsset.php b/src/web/assets/intentsform/IntentsFormAsset.php new file mode 100644 index 0000000..8f27641 --- /dev/null +++ b/src/web/assets/intentsform/IntentsFormAsset.php @@ -0,0 +1,39 @@ +sourcePath = __DIR__; + + $this->css = [ + 'css/paymentForm.css', + ]; + + $this->js = [ + 'js/paymentForm.js', + ]; + + $this->depends = [ + JqueryAsset::class, + ]; + + parent::init(); + } +} diff --git a/src/web/assets/intentsform/css/paymentForm.css b/src/web/assets/intentsform/css/paymentForm.css new file mode 100644 index 0000000..7c6d5a8 --- /dev/null +++ b/src/web/assets/intentsform/css/paymentForm.css @@ -0,0 +1,3 @@ +.stripe-payment-intents-form { + width: 310px; +} diff --git a/src/web/assets/intentsform/js/paymentForm.js b/src/web/assets/intentsform/js/paymentForm.js new file mode 100644 index 0000000..5885798 --- /dev/null +++ b/src/web/assets/intentsform/js/paymentForm.js @@ -0,0 +1,193 @@ +function PaymentIntents(publishableKey, container) { + this.container = container; + this.stripeInstance = Stripe(publishableKey); + this.paymentFormNamespace = this.container.data('payment-form-namespace'); + + this.perform3dsAuthentication = function (card) { + this.displayMessage('Please wait, processing payment...'); + this.stripeInstance + .handleCardPayment(this.container.data('client-secret'), card) + .then( + function (result) { + if (result.error) { + this.displayMessage(result.error.message); + this.displayPaymentForm(); + } else { + location.reload(); + } + }.bind(this) + ); + }; + + this.displayStripeMessage = function (event) { + if (event.error) { + this.displayMessage(event.error.message); + } else { + this.displayMessage(''); + } + }; + + this.displayMessage = function (message) { + var messageContainer = $('.card-errors', this.container).get(0); + messageContainer.textContent = message; + + if ($('.modal').data('modal')) { + $('.modal').data('modal').updateSizeAndPosition(); + } + }; + + this.displayPaymentForm = function () { + $('.payment-form-fields').removeClass('hidden'); + + var elements = this.stripeInstance.elements(); + + var style = { + base: { + // Add your base input styles here. For example: + fontSize: '16px', + lineHeight: '21px', + }, + }; + + // Create an instance of the card Element + var card = elements.create('card', { + style: style, + hidePostalCode: true, + }); + + card.addEventListener('change', this.displayStripeMessage.bind(this)); + + // Add an instance of the card Element into the `card-element`
+ card.mount($('.card-data', this.container).empty().get(0)); + + var $form = $('form', this.container); + + if ($form.length === 0) { + $form = this.container.parents('form'); + } + + // Remove already bound events + $form.off('submit'); + $form.data('processing', false); + + $form.on( + 'submit', + function (ev) { + ev.preventDefault(); + + // If form submitted already, disregard. + if ($form.data('processing')) { + return false; + } + + $form.data('processing', true); + + // Compose card holder info + var cardHolderName, orderEmail, ownerAddress; + + if ( + $('.card-holder-first-name', $form).length > 0 && + $('.card-holder-last-name', $form).length > 0 + ) { + cardHolderName = + $('.card-holder-first-name', $form).val() + + ' ' + + $('.card-holder-last-name', $form).val(); + } + + if ($('.stripe-address', $form).length > 0) { + ownerAddress = { + line1: $( + `input[name="${this.paymentFormNamespace}[stripe-line1]"]`, + $form + ).val(), + city: $( + `input[name="${this.paymentFormNamespace}[stripe-city]"]`, + $form + ).val(), + postal_code: $( + `input[name="${this.paymentFormNamespace}[stripe-postal-code]"]`, + $form + ).val(), + country: $( + `input[name="${this.paymentFormNamespace}[stripe-country]"]`, + $form + ).val(), + state: $( + `input[name="${this.paymentFormNamespace}[stripe-state]"]`, + $form + ).val(), + }; + } + + // If client secret is present, that's a pretty good indicator that things should be handled on page. + if (this.container.data('client-secret')) { + this.perform3dsAuthentication(card); + + return; + } + + // This section is for handling things on server end + orderEmail = $('input[name=orderEmail]').val(); + var paymentData = { + billing_details: { + name: cardHolderName, + email: orderEmail, + address: ownerAddress, + }, + }; + + // Tokenize the credit card details and create a payment source + this.stripeInstance.createPaymentMethod('card', card, paymentData).then( + function (result) { + if (result.error) { + this.displayMessage(result.error.message); + $form.data('processing', false); + } else { + // Add the payment source token to the form. + $form.append( + $( + '' + ).val(result.paymentMethod.id) + ); + $form.get(0).submit(); + } + }.bind(this) + ); + }.bind(this) + ); + + if ($('.modal').data('modal')) { + $('.modal').data('modal').updateSizeAndPosition(); + } + }; +} + +function initStripe() { + // Because this might get executed before Stripe is loaded. + if (typeof Stripe === 'undefined') { + setTimeout(initStripe, 200); + } else { + $('.stripe-payment-intents-form').each(function () { + $container = $(this); + + var handlerInstance = new PaymentIntents( + $container.data('publishablekey'), + $container + ); + $container.data('handlerInstance', handlerInstance); + + if ($container.data('scenario') == 'payment') { + handlerInstance.displayPaymentForm(); + } + + if ($container.data('scenario') == '3ds') { + handlerInstance.perform3dsAuthentication(); + } + }); + } +} + +initStripe();