From b10503188c4c64efe3f20baf300a669cfbcac16e Mon Sep 17 00:00:00 2001 From: Luke Holder Date: Wed, 4 Oct 2023 12:31:56 +0200 Subject: [PATCH 1/6] Keep old payment form around for now --- src/gateways/PaymentIntents.php | 49 +++++ .../paymentForms/oldIntentsForm.twig | 108 ++++++++++ .../assets/intentsform/IntentsFormAsset.php | 39 ++++ .../assets/intentsform/css/paymentForm.css | 3 + src/web/assets/intentsform/js/paymentForm.js | 193 ++++++++++++++++++ 5 files changed, 392 insertions(+) create mode 100644 src/templates/paymentForms/oldIntentsForm.twig create mode 100644 src/web/assets/intentsform/IntentsFormAsset.php create mode 100644 src/web/assets/intentsform/css/paymentForm.css create mode 100644 src/web/assets/intentsform/js/paymentForm.js diff --git a/src/gateways/PaymentIntents.php b/src/gateways/PaymentIntents.php index 9e2a5cb..277a5c1 100644 --- a/src/gateways/PaymentIntents.php +++ b/src/gateways/PaymentIntents.php @@ -31,6 +31,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 +82,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/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..52382c0 --- /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(); From d72340afb66fda8bbe5801a6adb57ab5596728d5 Mon Sep 17 00:00:00 2001 From: Nathaniel Hammond Date: Tue, 24 Oct 2023 12:28:14 +0100 Subject: [PATCH 2/6] Fixed #260 `icon-mask.svg` causing extra logs --- src/utilities/Sync.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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'; } /** From 595defb08d2ab749c910d4be9bf9960f8e368a3f Mon Sep 17 00:00:00 2001 From: Nathaniel Hammond Date: Tue, 24 Oct 2023 12:32:36 +0100 Subject: [PATCH 3/6] Fix PHPStan --- src/gateways/PaymentIntents.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gateways/PaymentIntents.php b/src/gateways/PaymentIntents.php index 277a5c1..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; From 3bf5f8185f02fccee1f780490dc26692b82ff27b Mon Sep 17 00:00:00 2001 From: Nathaniel Hammond Date: Tue, 24 Oct 2023 12:37:16 +0100 Subject: [PATCH 4/6] Prettier --- src/web/assets/intentsform/js/paymentForm.js | 334 +++++++++---------- 1 file changed, 167 insertions(+), 167 deletions(-) diff --git a/src/web/assets/intentsform/js/paymentForm.js b/src/web/assets/intentsform/js/paymentForm.js index 52382c0..5885798 100644 --- a/src/web/assets/intentsform/js/paymentForm.js +++ b/src/web/assets/intentsform/js/paymentForm.js @@ -1,193 +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.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.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; - this.displayMessage = function (message) { - var messageContainer = $('.card-errors', this.container).get(0); - messageContainer.textContent = message; + if ($('.modal').data('modal')) { + $('.modal').data('modal').updateSizeAndPosition(); + } + }; - 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', + }, }; - this.displayPaymentForm = function () { - $('.payment-form-fields').removeClass('hidden'); + // Create an instance of the card Element + var card = elements.create('card', { + style: style, + hidePostalCode: true, + }); - var elements = this.stripeInstance.elements(); + card.addEventListener('change', this.displayStripeMessage.bind(this)); - var style = { - base: { - // Add your base input styles here. For example: - fontSize: '16px', - lineHeight: '21px', - }, - }; + // Add an instance of the card Element into the `card-element`
+ card.mount($('.card-data', this.container).empty().get(0)); - // Create an instance of the card Element - var card = elements.create('card', { - style: style, - hidePostalCode: true, - }); + var $form = $('form', this.container); - card.addEventListener('change', this.displayStripeMessage.bind(this)); + if ($form.length === 0) { + $form = this.container.parents('form'); + } - // Add an instance of the card Element into the `card-element`
- card.mount($('.card-data', this.container).empty().get(0)); + // Remove already bound events + $form.off('submit'); + $form.data('processing', false); - var $form = $('form', this.container); + $form.on( + 'submit', + function (ev) { + ev.preventDefault(); - if ($form.length === 0) { - $form = this.container.parents('form'); + // If form submitted already, disregard. + if ($form.data('processing')) { + return false; } - // 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) - ); + $form.data('processing', true); + + // Compose card holder info + var cardHolderName, orderEmail, ownerAddress; - if ($('.modal').data('modal')) { - $('.modal').data('modal').updateSizeAndPosition(); + 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(); } - }; -} -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); + 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(), + }; + } - var handlerInstance = new PaymentIntents( - $container.data('publishablekey'), - $container - ); - $container.data('handlerInstance', handlerInstance); + // 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); - if ($container.data('scenario') == 'payment') { - handlerInstance.displayPaymentForm(); - } + return; + } - if ($container.data('scenario') == '3ds') { - handlerInstance.perform3dsAuthentication(); + // 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(); From b1c7749efe22c8442d63788cfdf3173f0a9512fc Mon Sep 17 00:00:00 2001 From: Luke Holder Date: Wed, 25 Oct 2023 10:42:19 +0800 Subject: [PATCH 5/6] Update changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42afca8..69a3a7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Release Notes for Stripe for Craft Commerce +## Unreleased + +- 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. ## 4.0.0 - 2023-09-13 From dbc7e65b5e044bc86d01d4243f73697493ad67eb Mon Sep 17 00:00:00 2001 From: Luke Holder Date: Wed, 25 Oct 2023 10:43:14 +0800 Subject: [PATCH 6/6] Finish release --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 69a3a7a..24a09d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Release Notes for Stripe for Craft Commerce -## Unreleased +## 4.0.1.1 - 2023-10-25 - Restored support for backend payments using the old payment form. - Fixed missing icon.