diff --git a/CHANGELOG.md b/CHANGELOG.md index 794b9c9ced..6067379fd4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - Fixed a bug where address changes weren’t being synced to carts using them as a source. ([#3178](https://github.com/craftcms/commerce/issues/3178)) - Added `craft\commerce\services\Orders::afterSaveAddressHandler()`. - Added `craft\commerce\elements\Order::$orderCompletedEmail`. ([#3138](https://github.com/craftcms/commerce/issues/3138)) +- Added the `commerce/cart/forget-cart` action. ([#3206](https://github.com/craftcms/commerce/issues/3206)) ## 4.2.11 - 2023-06-05 diff --git a/example-templates/dist/shop/_private/layouts/includes/nav-main.twig b/example-templates/dist/shop/_private/layouts/includes/nav-main.twig index 408b20b1a0..efb9299d82 100644 --- a/example-templates/dist/shop/_private/layouts/includes/nav-main.twig +++ b/example-templates/dist/shop/_private/layouts/includes/nav-main.twig @@ -36,8 +36,8 @@ Outputs the site’s global main navigation based on path and included `pages` a
{% if currentUser %} - {{- currentUser.email -}} + class="relative text-lg cursor-pointer inline-block mx-4 my-5 px-2 py-1 bg-white rounded-lg hover:shadow"> + 👤 My Account {% else %} - {% if cart.totalQty %} - {{- cart.totalQty -}} + {% if cart.totalQty %} + {{- cart.totalQty -}} + {% else %} + Empty + {% endif %} - {% endif %} -

🛒

+ + 🛒 Cart
diff --git a/example-templates/dist/shop/checkout/payment.twig b/example-templates/dist/shop/checkout/payment.twig index 7827c07916..b029f1637e 100644 --- a/example-templates/dist/shop/checkout/payment.twig +++ b/example-templates/dist/shop/checkout/payment.twig @@ -73,25 +73,24 @@ {% set params = { currency: cart.paymentCurrency } %} {% endif %} - {# Special params for Stripe Elements and Checkout#} - {# see https://stripe.com/docs/elements/appearance-api #} {% if className(cart.gateway) == 'craft\\commerce\\stripe\\gateways\\PaymentIntents' %} {% set params = { paymentFormType: 'elements', appearance: { theme: 'stripe' }, - layout: { - type: 'tabs', - defaultCollapsed: false, - radios: false, - spacedAccordionItems: false + elementOptions: { + layout: { + type: 'accordion', + defaultCollapsed: false, + radios: false, + spacedAccordionItems: false + } }, - 'submitButtonClasses': 'cursor-pointer rounded px-4 py-2 inline-block bg-blue-500 hover:bg-blue-600 text-white hover:text-white my-2', - 'submitButtonLabel': 'Pay', - 'errorMessageClasses': 'bg-red-200 text-red-600 my-2 p-2 rounded', + submitButtonClasses: 'cursor-pointer rounded px-4 py-2 inline-block bg-blue-500 hover:bg-blue-600 text-white hover:text-white my-2', + submitButtonLabel: 'Pay', + errorMessageClasses: 'bg-red-200 text-red-600 my-2 p-2 rounded', } %} - {% dump params %} {% endif %}
{% namespace cart.gateway.handle|commercePaymentFormNamespace %} @@ -131,7 +130,7 @@ {{ include('shop/checkout/_includes/partial-payment') }} - {% if cart.gateway.showPaymentFormSubmitButton() %} + {% if cart.paymentSourceId or cart.gateway.showPaymentFormSubmitButton() %}
{{ tag('button', { type: 'submit', diff --git a/example-templates/dist/shop/customer/cards.twig b/example-templates/dist/shop/customer/cards.twig index 4fa73de70f..d66304bd5b 100644 --- a/example-templates/dist/shop/customer/cards.twig +++ b/example-templates/dist/shop/customer/cards.twig @@ -14,13 +14,18 @@ {% for gateway in gateways %} +
+ {% if className(gateway) == 'craft\\commerce\\stripe\\gateways\\PaymentIntents' %} + Manage cards on Stripe Billing portal → + {% endif %} +
{% set gatewayPaymentSources = craft.commerce.paymentSources.getAllPaymentSourcesByCustomerId(currentUser.id, gateway.id) %} {% for paymentSource in gatewayPaymentSources %}
- {{ paymentSource.description }} {% if paymentSource.id == currentUser.primaryPaymentSourceId %}({{ 'Primary'|t }}){% endif %} + {{ paymentSource.description }} {% if paymentSource.id == currentUser.primaryPaymentSourceId %}{{ 'Primary'|t }}{% endif %} {% if paymentSource.gateway %}
{{ paymentSource.gateway.name }} ({{ paymentSource.token }})
{% endif %} @@ -106,30 +111,33 @@ {{ hiddenInput('cancelUrl', '/shop/customer/cards'|hash) }} {{ redirectInput('/shop/customer/cards') }} -
- {{ gateway.getPaymentFormHtml({})|raw }} -
- - - - {# Force in some basic styling for the gateway-provided form markup (better to build your own form markup!) #} - + {% set params = {} %} + + {% if className(gateway) == 'craft\\commerce\\stripe\\gateways\\PaymentIntents' %} + {% set params = { + paymentFormType: 'elements', + appearance: { + theme: 'stripe' + }, + elementOptions: { + layout: { + type: 'accordion', + defaultCollapsed: false, + radios: false, + spacedAccordionItems: false + } + }, + submitButtonClasses: 'cursor-pointer rounded px-4 py-2 inline-block bg-blue-500 hover:bg-blue-600 text-white hover:text-white my-2', + submitButtonText: 'Create', + errorMessageClasses: 'bg-red-200 text-red-600 my-2 p-2 rounded', + } %} + {% endif %}
{{ input('text', 'description', '', { maxlength: 70, autocomplete: 'off', - placeholder: 'Card description'|t, + placeholder: 'Payment source description'|t, class: ['w-full', 'border border-gray-300 hover:border-gray-500 px-4 py-2 leading-tight rounded'] }) }}
@@ -140,13 +148,21 @@
-
- {{ tag('button', { - type: 'submit', - class: 'cursor-pointer rounded px-4 py-2 inline-block bg-blue-500 hover:bg-blue-600 text-white hover:text-white', - text: 'Add card'|t - }) }} +
+ {% namespace gateway.handle|commercePaymentFormNamespace %} + {{ gateway.getPaymentFormHtml(params)|raw }} + {% endnamespace %}
+ + {% if gateway.showPaymentFormSubmitButton() %} +
+ {{ tag('button', { + type: 'submit', + class: 'cursor-pointer rounded px-4 py-2 inline-block bg-blue-500 hover:bg-blue-600 text-white hover:text-white', + text: 'Add card'|t + }) }} +
+ {% endif %}
{% endif %} diff --git a/example-templates/src/shop/_private/layouts/includes/nav-main.twig b/example-templates/src/shop/_private/layouts/includes/nav-main.twig index 7805e4e438..9f51a94ddd 100644 --- a/example-templates/src/shop/_private/layouts/includes/nav-main.twig +++ b/example-templates/src/shop/_private/layouts/includes/nav-main.twig @@ -36,8 +36,8 @@ Outputs the site’s global main navigation based on path and included `pages` a diff --git a/example-templates/src/shop/checkout/payment.twig b/example-templates/src/shop/checkout/payment.twig index 8e7f2c3152..973991a1ee 100755 --- a/example-templates/src/shop/checkout/payment.twig +++ b/example-templates/src/shop/checkout/payment.twig @@ -73,25 +73,24 @@ {% set params = { currency: cart.paymentCurrency } %} {% endif %} - {# Special params for Stripe Elements and Checkout#} - {# see https://stripe.com/docs/elements/appearance-api #} {% if className(cart.gateway) == 'craft\\commerce\\stripe\\gateways\\PaymentIntents' %} {% set params = { paymentFormType: 'elements', appearance: { theme: 'stripe' }, - layout: { - type: 'tabs', - defaultCollapsed: false, - radios: false, - spacedAccordionItems: false + elementOptions: { + layout: { + type: 'accordion', + defaultCollapsed: false, + radios: false, + spacedAccordionItems: false + } }, - 'submitButtonClasses': '[[classes.btn.base]] [[classes.btn.mainColor]] my-2', - 'submitButtonLabel': 'Pay', - 'errorMessageClasses': 'bg-red-200 text-red-600 my-2 p-2 rounded', + submitButtonClasses: '[[classes.btn.base]] [[classes.btn.mainColor]] my-2', + submitButtonLabel: 'Pay', + errorMessageClasses: 'bg-red-200 text-red-600 my-2 p-2 rounded', } %} - {% dump params %} {% endif %}
{% namespace cart.gateway.handle|commercePaymentFormNamespace %} @@ -131,7 +130,7 @@ {{ include('[[folderName]]/checkout/_includes/partial-payment') }} - {% if cart.gateway.showPaymentFormSubmitButton() %} + {% if cart.paymentSourceId or cart.gateway.showPaymentFormSubmitButton() %}
{{ tag('button', { type: 'submit', diff --git a/example-templates/src/shop/customer/cards.twig b/example-templates/src/shop/customer/cards.twig index 217ffdaf19..e9fbe87e91 100755 --- a/example-templates/src/shop/customer/cards.twig +++ b/example-templates/src/shop/customer/cards.twig @@ -14,13 +14,18 @@ {% for gateway in gateways %} +
+ {% if className(gateway) == 'craft\\commerce\\stripe\\gateways\\PaymentIntents' %} + Manage cards on Stripe Billing portal → + {% endif %} +
{% set gatewayPaymentSources = craft.commerce.paymentSources.getAllPaymentSourcesByCustomerId(currentUser.id, gateway.id) %} {% for paymentSource in gatewayPaymentSources %}
- {{ paymentSource.description }} {% if paymentSource.id == currentUser.primaryPaymentSourceId %}({{ 'Primary'|t }}){% endif %} + {{ paymentSource.description }} {% if paymentSource.id == currentUser.primaryPaymentSourceId %}{{ 'Primary'|t }}{% endif %} {% if paymentSource.gateway %}
{{ paymentSource.gateway.name }} ({{ paymentSource.token }})
{% endif %} @@ -106,30 +111,33 @@ {{ hiddenInput('cancelUrl', '/[[folderName]]/customer/cards'|hash) }} {{ redirectInput('/[[folderName]]/customer/cards') }} -
- {{ gateway.getPaymentFormHtml({})|raw }} -
- - - - {# Force in some basic styling for the gateway-provided form markup (better to build your own form markup!) #} - + {% set params = {} %} + + {% if className(gateway) == 'craft\\commerce\\stripe\\gateways\\PaymentIntents' %} + {% set params = { + paymentFormType: 'elements', + appearance: { + theme: 'stripe' + }, + elementOptions: { + layout: { + type: 'accordion', + defaultCollapsed: false, + radios: false, + spacedAccordionItems: false + } + }, + submitButtonClasses: '[[classes.btn.base]] [[classes.btn.mainColor]] my-2', + submitButtonText: 'Create', + errorMessageClasses: 'bg-red-200 text-red-600 my-2 p-2 rounded', + } %} + {% endif %}
{{ input('text', 'description', '', { maxlength: 70, autocomplete: 'off', - placeholder: 'Card description'|t, + placeholder: 'Payment source description'|t, class: ['w-full', '[[classes.input]]'] }) }}
@@ -140,13 +148,21 @@
-
- {{ tag('button', { - type: 'submit', - class: '[[classes.btn.base]] [[classes.btn.mainColor]]', - text: 'Add card'|t - }) }} +
+ {% namespace gateway.handle|commercePaymentFormNamespace %} + {{ gateway.getPaymentFormHtml(params)|raw }} + {% endnamespace %}
+ + {% if gateway.showPaymentFormSubmitButton() %} +
+ {{ tag('button', { + type: 'submit', + class: '[[classes.btn.base]] [[classes.btn.mainColor]]', + text: 'Add card'|t + }) }} +
+ {% endif %}
{% endif %} diff --git a/src/console/controllers/ExampleTemplatesController.php b/src/console/controllers/ExampleTemplatesController.php index f80acb6eb4..0423e40691 100644 --- a/src/console/controllers/ExampleTemplatesController.php +++ b/src/console/controllers/ExampleTemplatesController.php @@ -236,6 +236,8 @@ private function _addCssClassesToReplacementData(): void $this->_replacementData = ArrayHelper::merge($this->_replacementData, [ '[[color]]' => $mainColor, '[[dangerColor]]' => $dangerColor, + '[[classes.text.color]]' => "text-$mainColor-500", + '[[classes.text.dangerColor]]' => "text-$dangerColor-500", '[[classes.a]]' => "text-$mainColor-500 hover:text-$mainColor-600", '[[classes.input]]' => "border border-gray-300 hover:border-gray-500 px-4 py-2 leading-tight rounded", '[[classes.box.base]]' => "bg-gray-100 border-$mainColor-300 border-b-2 p-6", diff --git a/src/controllers/CartController.php b/src/controllers/CartController.php index 3754a074a7..aad1586e95 100644 --- a/src/controllers/CartController.php +++ b/src/controllers/CartController.php @@ -277,6 +277,20 @@ public function actionUpdateCart(): ?Response return $this->_returnCart(); } + /** + * @return Response|null + * @throws BadRequestHttpException + * @throws InvalidConfigException + * @since 4.3 + */ + public function actionForgetCart(): ?Response + { + $this->requirePostRequest(); + Plugin::getInstance()->getCarts()->forgetCart(); + $this->setSuccessFlash(Craft::t('commerce', 'Cart forgotten.')); + return $this->redirectToPostedUrl(); + } + /** * @throws BadRequestHttpException * @throws Exception diff --git a/src/controllers/PaymentSourcesController.php b/src/controllers/PaymentSourcesController.php index f3f074fcbf..684d0ae707 100644 --- a/src/controllers/PaymentSourcesController.php +++ b/src/controllers/PaymentSourcesController.php @@ -64,7 +64,7 @@ public function actionAdd(): ?Response $description = (string)$this->request->getBodyParam('description'); try { - $paymentSource = $plugin->getPaymentSources()->createPaymentSource($customer->id, $gateway, $paymentForm, $description); + $paymentSource = $plugin->getPaymentSources()->createPaymentSource($customer->id, $gateway, $paymentForm, $description, $isPrimaryPaymentSource); } catch (Throwable $exception) { Craft::$app->getErrorHandler()->logException($exception); return $this->asModelFailure( diff --git a/src/services/PaymentSources.php b/src/services/PaymentSources.php index a28553f654..530f2ed175 100644 --- a/src/services/PaymentSources.php +++ b/src/services/PaymentSources.php @@ -266,7 +266,7 @@ public function getPaymentSourceByIdAndUserId(int $sourceId, int $userId): ?Paym * @throws InvalidConfigException * @throws PaymentSourceException If unable to create the payment source */ - public function createPaymentSource(int $customerId, GatewayInterface $gateway, BasePaymentForm $paymentForm, string $sourceDescription = null): PaymentSource + public function createPaymentSource(int $customerId, GatewayInterface $gateway, BasePaymentForm $paymentForm, string $sourceDescription = null, bool $makePrimarySource = false): PaymentSource { try { $source = $gateway->createPaymentSource($paymentForm, $customerId); @@ -284,6 +284,10 @@ public function createPaymentSource(int $customerId, GatewayInterface $gateway, throw new PaymentSourceException(Craft::t('commerce', 'Could not create the payment source.')); } + if($makePrimarySource) { + Plugin::getInstance()->getCustomers()->savePrimaryPaymentSourceId($source->getCustomer(), $source->id); + } + return $source; } diff --git a/src/templates/subscriptions/_edit.twig b/src/templates/subscriptions/_edit.twig index 8872536bb4..0ff0d76e52 100644 --- a/src/templates/subscriptions/_edit.twig +++ b/src/templates/subscriptions/_edit.twig @@ -206,11 +206,6 @@
{{ subscription.nextPaymentDate|datetime }}
-
-
{{ 'Expiry'|t('commerce') }}
-
{{ subscription.nextPaymentDate|datetime }}
-
-
{{ 'Expiry'|t('commerce') }}
{{ subscription.dateExpired ? subscription.dateExpired|datetime : '' }}