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 %}
+
\ 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();