diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c1bb514c29..a3819fbc12 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,7 +5,7 @@ on: branches: - develop - v3 - - '4.2' + - '4.3' pull_request: permissions: contents: read diff --git a/CHANGELOG.md b/CHANGELOG.md index e035f98610..98977dab78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,41 @@ # Release Notes for Craft Commerce +## 4.3.0 - 2023-09-13 + +- Sales and discounts now support using related entries in their matching item conditions. ([#3134](https://github.com/craftcms/commerce/issues/3134), [#2717](https://github.com/craftcms/commerce/issues/2717)) +- It’s now possible to query products by shipping category and tax category. ([#3219](https://github.com/craftcms/commerce/issues/3219)) +- Guest customers registering during checkout now have their addresses saved to their account. ([#3203](https://github.com/craftcms/commerce/pull/3203)) +- Product conditions can now have “Product Type”, “Variant SKU”, “Variant Has Unlimited Stock”, “Variant Price”, and “Variant Stock” rules. ([#3209](https://github.com/craftcms/commerce/issues/3209)) +- Improved the performance of discount recalculation. +- Improved the performance of the `commerce/upgrade` command. ([#3208](https://github.com/craftcms/commerce/pull/3208)) +- Added the `commerce/cart/forget-cart` action. ([#3206](https://github.com/craftcms/commerce/issues/3206)) +- The `commerce/cart/update-cart` action now accepts `firstName` and `lastName` address parameters. ([#3015](https://github.com/craftcms/commerce/issues/3015)) +- Added `craft\commerce\controllers\OrdersController::EVENT_MODIFY_PURCHASABLES_TABLE_QUERY`. ([#3198](https://github.com/craftcms/commerce/pull/3198)) +- Added `craft\commerce\elements\Order::$orderCompletedEmail`. ([#3138](https://github.com/craftcms/commerce/issues/3138)) +- Added `craft\commerce\elements\db\ProductQuery::$shippingCategoryId`. +- Added `craft\commerce\elements\db\ProductQuery::$taxCategoryId`. +- Added `craft\commerce\elements\db\ProductQuery::shippingCategory()`. +- Added `craft\commerce\elements\db\ProductQuery::shippingCategoryId()`. +- Added `craft\commerce\elements\db\ProductQuery::taxCategory()`. +- Added `craft\commerce\elements\db\ProductQuery::taxCategoryId()`. +- Added `craft\commerce\models\Discount::hasBillingAddressCondition()`. +- Added `craft\commerce\models\Discount::hasCustomerCondition()`. +- Added `craft\commerce\models\Discount::hasOrderCondition()`. +- Added `craft\commerce\models\Discount::hasShippingAddressCondition()`. +- Deprecated payment source creation via the `commerce/subscriptions/subscribe` action. +- Deprecated `craft\commerce\elements\Order::setEmail()`. `Order::setCustomer()` should be used instead. +- Removed the `htmx` option from the`commerce/example-templates` command. +- Removed the `color` option from the`commerce/example-templates` command. +- Added `craft\commerce\events\ModifyPurchasablesTableQueryEvent`. ([#3198](https://github.com/craftcms/commerce/pull/3198)) +- Fixed a bug where products/variants could be saved with a minimum quantity that was set higher than the maximum quantity. ([#3234](https://github.com/craftcms/commerce/issues/3234)) +- Fixed a bug where `craft\commerce\elements\Order::hasMatchingAddresses()` could incorrectly return `false`. ([#3183](https://github.com/craftcms/commerce/issues/3183)) +- Fixed a bug where changing a user’s email could cause additional user elements to be created. ([#3138](https://github.com/craftcms/commerce/issues/3138)) +- Fixed a bug where related sales were displaying when creating a new product. +- Fixed a bug where Commerce wasn’t invoking `craft\services\Elements::EVENT_AUTHORIZE_*` event handlers. +- Fixed a bug where discounts’ per user usage counters weren’t getting migrated properly when upgrading to Commerce 4. +- Fixed a bug where address changes weren’t being synced to carts that were using them. ([#3178](https://github.com/craftcms/commerce/issues/3178)) +- Fixed an XSS vulnerability. + ## 4.2.11 - 2023-06-05 - Fixed a bug where “Send Email” option text wasn’t getting translated. ([#3172](https://github.com/craftcms/commerce/issues/3172)) diff --git a/example-templates/dist/shop/_private/address/fields.twig b/example-templates/dist/shop/_private/address/fields.twig index d17b516d64..9f04efa630 100644 --- a/example-templates/dist/shop/_private/address/fields.twig +++ b/example-templates/dist/shop/_private/address/fields.twig @@ -2,6 +2,7 @@ {# Outputs address form fields for editing an address. #} +{% set showPrimaryCheckboxes = showPrimaryCheckboxes is defined ? showPrimaryCheckboxes : false %} {% set addressFieldLayout = craft.app.getAddresses().getLayout() %} {% set addressCustomFields = addressFieldLayout.getCustomFields()|filter(f => className(f) == 'craft\\fields\\PlainText') %} {# @var address \craft\elements\Address #} @@ -193,9 +194,34 @@ Outputs address form fields for editing an address. {% endfor %} {% endif %} + + {% if showPrimaryCheckboxes %} +
+
+ {{ input('text', 'isPrimaryBilling', address.isPrimarybilling ? 1 : 0) }} + +
+
+ {{ input('text', 'isPrimaryShipping', address.isPrimaryShipping ? 1 : 0) }} + +
+ {% endif %} {% js %} +{% if showPrimaryCheckboxes %} +document.querySelectorAll('input[type=checkbox][data-primary-input]').forEach(el => { + el.addEventListener('change', ev => { + let primaryInput = document.querySelector(`input[name="${ev.target.dataset.primaryInput}"]`); + if (ev.target.checked) { + primaryInput.value = 1; + } else { + primaryInput.value = 0; + } + }); +}); +{% endif %} + document.querySelector('select#{{ 'countryCode'|namespaceInputId(addressName) }}').addEventListener('change', ev => { const countryCode = ev.target.value; const stateSelect = document.querySelector('select#{{ 'administrativeArea'|namespaceInputId(addressName) }}'); diff --git a/example-templates/dist/shop/_private/address/fieldset.twig b/example-templates/dist/shop/_private/address/fieldset.twig index fb9aa9df27..6f910bcfa2 100644 --- a/example-templates/dist/shop/_private/address/fieldset.twig +++ b/example-templates/dist/shop/_private/address/fieldset.twig @@ -9,6 +9,7 @@ Outputs a fieldset for selecting one of a user’s available addresses or creati @var title string @var currentUser \craft\elements\User #} +{% set sourceAttribute = 'source' ~ (name|slice(0, 1)|capitalize) ~ (name|slice(1) ~ 'Id') %}

{{- title -}} @@ -19,22 +20,24 @@ Outputs a fieldset for selecting one of a user’s available addresses or creati selectable: true, primaryBillingAddressId: cart and cart.customer ? cart.customer.primaryBillingAddressId : null, primaryShippingAddressId: cart and cart.customer ? cart.customer.primaryShippingAddressId : null, + showAdd: true, }) }} -
+
{% if attribute(cart, name ~ 'Id') %} {% set addressHasErrors = attribute(cart, name) and attribute(cart, name).hasErrors() %} {% else %} {% set addressHasErrors = false %} {% endif %} - {% if currentUser %} + {# Show the custom toggle if there is a custom address on the order #} + {% if currentUser and cart and attribute(cart, name ~ 'Id') and not attribute(cart, sourceAttribute) %}
{% endif %} diff --git a/example-templates/dist/shop/_private/address/list.twig b/example-templates/dist/shop/_private/address/list.twig index 14814712a5..f73974af96 100644 --- a/example-templates/dist/shop/_private/address/list.twig +++ b/example-templates/dist/shop/_private/address/list.twig @@ -2,72 +2,74 @@ {% set selectable = selectable ?? false %} {% set primaryBillingAddressId = primaryBillingAddressId ?? null %} {% set primaryShippingAddressId = primaryShippingAddressId ?? null %} -{% set cardWidth = cardWidth ?? 'md:w-1/2' %} {% set showDelete = showDelete ?? false %} +{% set showAdd = showAdd ?? false %} +{% set addUrl = '/shop/customer/addresses/edit?redirect=' ~ craft.app.request.fullPath %} -{% if addresses and currentUser %} -
- {% for address in addresses %} - {% set editUrl = '/shop/customer/addresses/edit?addressId=' ~ address.id ~ '&redirect=' ~ craft.app.request.fullPath %} -
- {% tag selectable ? 'label' : 'div' with { - class: 'block relative address-select js-address-select border-blue-300 border-b-2 px-6 py-4 rounded-md shadow-md hover:shadow-lg', - data: { - 'address-id': address.id, - }, - } %} - - {% if selectable %} - {{ input('radio', name ~ 'Id', address.id, { - checked: (attribute(cart, sourceIdName) == address.id) or (not attribute(cart, sourceIdName) and address.id == attribute(_context, primaryIdName)), - }) }} - {% endif %} - - {{ address|address }} - - - - - {{- 'Edit'|t -}} - - {% if showDelete and not selectable %} -
- {{ csrfInput() }} - {{ actionInput('users/delete-address') }} - {{ redirectInput('shop/customer/addresses') }} - {{ hiddenInput('addressId', address.id) }} - {{ tag('button', { - type: 'submit', - class: 'cursor-pointer rounded px-2 py-1 text-sm inline-block bg-gray-500 hover:bg-gray-600 text-white hover:text-white', - text: 'Delete'|t - }) }} -
- {% endif %} -
- - {% if primaryBillingAddressId == address.id or primaryShippingAddressId == address.id %} - - {% if primaryBillingAddressId == address.id %} - 💳 +{% if currentUser %} + {% if addresses|length %} +
+ {% for address in addresses %} + {% set editUrl = '/shop/customer/addresses/edit?addressId=' ~ address.id ~ '&redirect=' ~ craft.app.request.fullPath %} +
+ {% tag selectable ? 'label' : 'div' with { + class: 'block relative address-select js-address-select', + data: { + 'address-id': address.id, + }, + } %} + + {% if selectable %} + {{ input('radio', name ~ 'Id', address.id, { + data: { + 'model-name': name, + }, + checked: (attribute(cart, sourceIdName) == address.id) or (not attribute(cart, sourceIdName) and address.id == attribute(_context, primaryIdName)), + }) }} {% endif %} - {% if primaryShippingAddressId == address.id %} - 📦 + + {{ address|address }} + + + + + {{- 'Edit'|t -}} + + {% if showDelete and not selectable %} +
+ {{ csrfInput() }} + {{ actionInput('users/delete-address') }} + {{ redirectInput('shop/customer/addresses') }} + {{ hiddenInput('addressId', address.id) }} + {{ tag('button', { + type: 'submit', + class: 'cursor-pointer rounded px-2 py-1 text-sm inline-block bg-gray-500 hover:bg-gray-600 text-white hover:text-white', + text: 'Delete'|t + }) }} +
{% endif %}
- {% endif %} - {% endtag %} -
- {% endfor %} -
+ {% endtag %} +
+ {% endfor %} + {% if showAdd %} + +
+ Add Address +
+
+ {% endif %} +
+ {% endif %} {% endif %} {% js %} -const addressDeletes = document.querySelectorAll('.js-address-delete'); -for (let i = 0; i < addressDeletes.length; i++) { - addressDeletes[i].addEventListener('submit', ev => { - if (!confirm('{{ 'Are you sure you want to delete this address?'|t }}')) { - ev.preventDefault(); + const addressDeletes = document.querySelectorAll('.js-address-delete'); + for (let i = 0; i < addressDeletes.length; i++) { + addressDeletes[i].addEventListener('submit', ev => { + if (!confirm('{{ 'Are you sure you want to delete this address?'|t }}')) { + ev.preventDefault(); + } + }); } - }); -} -{% endjs %} \ No newline at end of file +{% endjs %} diff --git a/example-templates/dist/shop/_private/images/placeholder.svg b/example-templates/dist/shop/_private/images/placeholder.svg index e7b1427138..60b778c648 100644 --- a/example-templates/dist/shop/_private/images/placeholder.svg +++ b/example-templates/dist/shop/_private/images/placeholder.svg @@ -34,3 +34,5 @@ Outputs an SVG to be used as an image placeholder, optionally including target d {% endif -%} + + diff --git a/example-templates/dist/shop/_private/images/shopping-cart.svg b/example-templates/dist/shop/_private/images/shopping-cart.svg deleted file mode 100644 index e1af466eed..0000000000 --- a/example-templates/dist/shop/_private/images/shopping-cart.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/example-templates/dist/shop/_private/layouts/includes/footer.twig b/example-templates/dist/shop/_private/layouts/includes/footer.twig index 9777844318..bc93d123e2 100644 --- a/example-templates/dist/shop/_private/layouts/includes/footer.twig +++ b/example-templates/dist/shop/_private/layouts/includes/footer.twig @@ -64,9 +64,6 @@ Outputs the global site footer. version: craft.commerce.version, })|raw }}

-

- {{ 'Use these templates as a starting point for learning and customizing—you’ll want to customize them for your site.'|t }} -

diff --git a/example-templates/dist/shop/_private/layouts/includes/header.twig b/example-templates/dist/shop/_private/layouts/includes/header.twig new file mode 100644 index 0000000000..4c479235f4 --- /dev/null +++ b/example-templates/dist/shop/_private/layouts/includes/header.twig @@ -0,0 +1,18 @@ +
+
+

+ + {{- siteName ~ ' Shop' -}} + +

+ {% if craft.app.sites.getAllSites()|length > 1 %} +
+ +
+ {% endif %} +
+
\ No newline at end of file diff --git a/example-templates/dist/shop/_private/layouts/includes/nav-checkout.twig b/example-templates/dist/shop/_private/layouts/includes/nav-checkout.twig index 032fe3a4ef..4c0b17c8d7 100644 --- a/example-templates/dist/shop/_private/layouts/includes/nav-checkout.twig +++ b/example-templates/dist/shop/_private/layouts/includes/nav-checkout.twig @@ -23,6 +23,10 @@ Outputs the checkout progress navigation using the request path and included `ch label: 'Payment Method', url: 'shop/checkout/payment-method' }, + { + label: 'Options', + url: 'shop/checkout/options' + }, { label: 'Payment', url: 'shop/checkout/payment' diff --git a/example-templates/dist/shop/_private/layouts/includes/nav-customer.twig b/example-templates/dist/shop/_private/layouts/includes/nav-customer.twig new file mode 100644 index 0000000000..971790ccf1 --- /dev/null +++ b/example-templates/dist/shop/_private/layouts/includes/nav-customer.twig @@ -0,0 +1,40 @@ + +{# +Outputs the site’s global main navigation based on path and included `pages` array. + +@var cart \craft\commerce\elements\Order +@var currentUser \craft\elements\User +#} +{% set pages = [ + { + label: 'My Orders', + url: 'shop/customer/orders' + }, + { + label: 'My Addresses', + url: 'shop/customer/addresses' + }, + { + label: 'My Stored Cards', + url: 'shop/customer/cards' + } +] %} +{% set currentPath = craft.app.request.pathInfo %} +{# Show the nav if we in a customers page #} +{% if currentPath in pages|column('url') %} +{% set activeClasses = 'bg-gray-200' %} +
+
+ +
+
+{% endif %} 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..21c6374319 100644 --- a/example-templates/dist/shop/_private/layouts/includes/nav-main.twig +++ b/example-templates/dist/shop/_private/layouts/includes/nav-main.twig @@ -21,6 +21,7 @@ Outputs the site’s global main navigation based on path and included `pages` a ] %} {% set currentPath = craft.app.request.pathInfo %} {% set activeClasses = 'bg-white' %} +{# Show the nav if we are not in a checkout page #} {% if 'checkout' not in currentPath %}
@@ -36,8 +37,8 @@ Outputs the site’s global main navigation based on path and included `pages` a diff --git a/example-templates/dist/shop/_private/layouts/index.twig b/example-templates/dist/shop/_private/layouts/index.twig index 653b807a64..f74daf7a33 100644 --- a/example-templates/dist/shop/_private/layouts/index.twig +++ b/example-templates/dist/shop/_private/layouts/index.twig @@ -20,7 +20,7 @@ Common, top-level layout template. {% endif %} -
+
{% if cart is not defined %} {% set cart = craft.commerce.carts.cart %} {% endif %} @@ -28,28 +28,22 @@ Common, top-level layout template. {% set flashNotice = craft.app.session.getFlash('notice') %} {% set flashError = craft.app.session.getFlash('error') %} -
-
-
-

- - {{- siteName ~ ' Shop' -}} - -

- {% if craft.app.sites.getAllSites()|length > 1 %} -
- -
- {% endif %} -
-
+{% macro docs(text, link) %} + + {{ tag('a', { + text: 'ℹ︎ ' ~ text, + href: link, + class: 'text-gray-400 hover:text-gray-600 hover:underline', + target: '_blank', + }) }} + +{% endmacro %} +
+ {{ include('shop/_private/layouts/includes/header') }} {{ include('shop/_private/layouts/includes/nav-main') }} {{ include('shop/_private/layouts/includes/nav-checkout') }} + {{ include('shop/_private/layouts/includes/nav-customer') }}
@@ -140,7 +134,6 @@ let states = {{ craft.commerce.store.getStore().getAdministrativeAreasListByCoun ]) }) %} {% endfor %} let usedAddressFieldsByCountryCode = {{ usedFields|json_encode|raw }}; -console.log(usedAddressFieldsByCountryCode); function hideAddressFields(selectorTemplate) { const fields = document.querySelectorAll('.' + selectorTemplate.replace('placeHolder', 'js-address-field')); if (!fields.length) { diff --git a/example-templates/dist/shop/cart/index.twig b/example-templates/dist/shop/cart/index.twig index bd9ace0f52..f29aa0e14c 100644 --- a/example-templates/dist/shop/cart/index.twig +++ b/example-templates/dist/shop/cart/index.twig @@ -89,7 +89,7 @@ Outputs cart. {{ input('text', 'lineItems[' ~ item.id ~ '][note]', item.note, { id: 'lineitem-note-' ~ item.id, - class: 'border border-gray-300 hover:border-gray-500 px-4 py-2 leading-tight rounded', + class: 'border border-gray-300 hover:border-gray-500 px-4 py-2 leading-tight rounded w-full', size: 20, placeholder: 'My Note'|t }) }} diff --git a/example-templates/dist/shop/cart/load.twig b/example-templates/dist/shop/cart/load.twig index b8872a779a..ff95d1c12e 100644 --- a/example-templates/dist/shop/cart/load.twig +++ b/example-templates/dist/shop/cart/load.twig @@ -21,9 +21,9 @@ Outputs form for collecting a cart number to be loaded. {{- 'Cart Number'|t -}}
- {{ input('text', 'number', 'border border-gray-300 hover:border-gray-500 px-4 py-2 leading-tight rounded', { + {{ input('text', 'number', null, { id: 'number', - class: ['w-full', ''], + class: ['w-full', 'border border-gray-300 hover:border-gray-500 px-4 py-2 leading-tight rounded'], placeholder: '7e89fh0ew8034…' }) }}
diff --git a/example-templates/dist/shop/checkout/_includes/base-payment-form-styles.twig b/example-templates/dist/shop/checkout/_includes/base-payment-form-styles.twig deleted file mode 100644 index 43a6ccb2e8..0000000000 --- a/example-templates/dist/shop/checkout/_includes/base-payment-form-styles.twig +++ /dev/null @@ -1,11 +0,0 @@ - \ No newline at end of file diff --git a/example-templates/dist/shop/checkout/_paymentLegacyStripeExample.twig b/example-templates/dist/shop/checkout/_paymentLegacyStripeExample.twig new file mode 100644 index 0000000000..d3b2193133 --- /dev/null +++ b/example-templates/dist/shop/checkout/_paymentLegacyStripeExample.twig @@ -0,0 +1,218 @@ +{% extends 'shop/_private/layouts' %} + +{# @var cart \craft\commerce\elements\Order #} + +{% block main %} + + {% if not cart.gateway %} + {% redirect 'shop/checkout/payment-method' %} + {% endif %} + +
+
+ {% if craft.commerce.settings.allowCheckoutWithoutPayment %} +
+

+ {{- 'Pay Later'|t -}} +

+

{{ 'Commit to buy now, and make payment later...'|t }}

+
+
+ {{ csrfInput() }} + {{ actionInput('commerce/cart/complete') }} + {{ redirectInput(siteUrl('/shop/customer/order', { + number: cart.number, + success: 'true' + })) }} + {{ successMessageInput('Thank you for your order. We’ve received it and are awaiting payment.'|t) }} + {{ 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: 'Commit'|t + }) }} +
+
+
+ {% endif %} + +

+ {{- 'Payment'|t -}} +

+ + {% if cart.gatewayId or cart.paymentSourceId %} + {% if paymentForm is defined %} + {% for key, errors in paymentForm.getErrors() %} + {% for error in errors %} + {% if loop.first %}
    {% endif %} +
  • {{ key }} {{ error }}
  • + {% if loop.last %}
{% endif %} + {% endfor %} + {% endfor %} + {% endif %} + +
+
+ {{ csrfInput() }} + {{ actionInput('commerce/payments/pay') }} + {{ redirectInput(siteUrl('/shop/customer/order', { + number: cart.number, + success: 'true' + })) }} + {{ hiddenInput('cancelUrl', siteUrl('/shop/checkout/payment')|hash) }} + {{ hiddenInput('orderEmail', cart.email) }} + + {% if cart.gatewayId %} + {{ hiddenInput('gatewayId', cart.gatewayId) }} + + + + + + {% namespace cart.gateway.handle|commercePaymentFormNamespace %} +
+ +
+ {# Stripe’s JavaScript will insert Stripe Elements here #} +
+ {# Used to display form errors #} + +
+ {% endnamespace %} + + + + + + + {# Force in some basic styling for the gateway-provided form markup (better to build your own form markup!) #} + {% include 'shop/checkout/_includes/base-payment-form-styles' %} + + {% if cart.gateway.supportsPaymentSources() and currentUser %} +
+ +
+ {% endif %} + {% else %} + {{ cart.gateway.getPaymentConfirmationFormHtml({})|raw }} + {% endif %} + + {% set user = cart.email ? craft.users.email(cart.email).one() : null %} + {% if not user or not user.getIsCredentialed() %} +
+ +
+ {% endif %} + + {{ include('shop/checkout/_includes/partial-payment') }} + + {% if cart.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: 'Pay'|t + }) }} +
+ {% endif %} +
+
+ {% endif %} +
+ +
+ {{ include('shop/checkout/_includes/order-summary', { + showShippingAddress: true, + showShippingMethod: true + }) }} +
+
+ + + +{% endblock %} diff --git a/example-templates/dist/shop/checkout/addresses.twig b/example-templates/dist/shop/checkout/addresses.twig index 1fbfef9ada..e22bceb778 100644 --- a/example-templates/dist/shop/checkout/addresses.twig +++ b/example-templates/dist/shop/checkout/addresses.twig @@ -43,6 +43,17 @@ Outputs a form for collecting an order’s shipping and billing address. sourceIdName: 'sourceShippingAddressId', }) }} + {% if currentUser and addresses|length %} +
+ +
+ {% endif %} +
{{ hiddenInput('billingAddressSameAsShipping', 0) }} @@ -57,17 +68,6 @@ Outputs a form for collecting an order’s shipping and billing address.
- {% if currentUser %} -
- -
- {% endif %} -
{{ include('shop/_private/address/fieldset', { title: 'Billing Address'|t, @@ -78,19 +78,19 @@ Outputs a form for collecting an order’s shipping and billing address. }) }}
-
- - {% if currentUser %} -
- -
+ {% if currentUser and addresses|length %} +
+ +
{% endif %} +
+
{{ tag('button', { type: 'submit', @@ -123,6 +123,7 @@ Outputs a form for collecting an order’s shipping and billing address. if ($body) { // Creating new address $radio.addEventListener('change', function(ev) { + console.log('changing'); if (ev.target.checked) { $body.classList.remove('hidden'); } else { @@ -136,6 +137,7 @@ Outputs a form for collecting an order’s shipping and billing address. } else { // Selecting existing address $radio.addEventListener('change', function(ev) { + console.log(ev.target.checked); if (ev.target.checked) { var $newBox = document.querySelector('.js-address-select[data-model-name="' + ev.target.dataset.modelName + '"]'); if ($newBox) { @@ -156,7 +158,7 @@ Outputs a form for collecting an order’s shipping and billing address. var $billingFieldSet = document.querySelector('.js-address-fieldset.BillingAddress'); $billingFieldSet.classList.toggle('hidden'); - for (let input of document.querySelector('.js-address-box.BillingAddress').querySelectorAll('input')) { + for (let input of $billingFieldSet.querySelectorAll('.js-address-select input')) { if ($billingSameAs.checked === true) { input.setAttribute('disabled', ''); } else if (input.hasAttribute('disabled')) { diff --git a/example-templates/dist/shop/checkout/options.twig b/example-templates/dist/shop/checkout/options.twig new file mode 100644 index 0000000000..2c845d937d --- /dev/null +++ b/example-templates/dist/shop/checkout/options.twig @@ -0,0 +1,108 @@ +{% extends 'shop/_private/layouts' %} + +{# @var cart \craft\commerce\elements\Order #} + +{% if cart is not defined %} + {% set cart = craft.commerce.carts.cart %} +{% endif %} + +{# + We can skip the "options" step if the customer is a logged in user and + the addresses have come from the address book +#} +{% if currentUser and cart.sourceBillingAddressId and cart.sourceShippingAddressId %} + {% redirect 'shop/checkout/payment' %} +{% endif %} + +{% if not cart.getCustomer() %} + {% redirect 'shop/checkout/email' %} +{% endif %} + +{% if not cart.gateway %} + {% redirect 'shop/checkout/payment-method' %} +{% endif %} + +{% block main %} +
+
+ +

+ {{- 'Options'|t -}} +

+ +
+ {{ csrfInput() }} + {{ actionInput('commerce/cart/update-cart') }} + {{ redirectInput(siteUrl('shop/checkout/payment')) }} + {{ successMessageInput('Options saved.') }} + + {% set user = cart.email ? craft.users.email(cart.email).one() : null %} + {% if not user or not user.getIsCredentialed() %} +
+ +
+ {{ _self.docs('Registering a user on order complete.', 'https://craftcms.com/docs/commerce/4.x/customers.html#registration-at-checkout') }} +
+
+ {% endif %} + + {% set saveAddressCheckboxesShown = false %} + {% if currentUser and cart.billingAddressId and not cart.sourceBillingAddressId %} + {% set saveAddressCheckboxesShown = true %} +
+ +
+ {% endif %} + + {% if currentUser and cart.shippingAddressId and not cart.sourceShippingAddressId %} +
+ {% set saveAddressCheckboxesShown = true %} + +
+ {% endif %} + + {% if saveAddressCheckboxesShown %} +
+ {{ _self.docs('Saving addresses on order complete.', '#') }} +
+ {% endif %} + +
+ {{ tag('button', { + type: 'submit', + name: 'submit', + class: 'cursor-pointer rounded px-4 py-2 inline-block bg-blue-500 hover:bg-blue-600 text-white hover:text-white', + text: 'Next'|t + }) }} +
+
+
+ +
+ {{ include('shop/checkout/_includes/order-summary', { + showShippingAddress: true, + showShippingMethod: true + }) }} +
+
+{% endblock %} diff --git a/example-templates/dist/shop/checkout/payment-method.twig b/example-templates/dist/shop/checkout/payment-method.twig index aea54601a8..5a55567cc6 100644 --- a/example-templates/dist/shop/checkout/payment-method.twig +++ b/example-templates/dist/shop/checkout/payment-method.twig @@ -20,7 +20,7 @@
{{ csrfInput() }} {{ actionInput('commerce/cart/update-cart') }} - {{ redirectInput(siteUrl('shop/checkout/payment')) }} + {{ redirectInput(siteUrl('shop/checkout/options')) }} {{ successMessageInput('Payment options selected.') }}
diff --git a/example-templates/dist/shop/checkout/payment.twig b/example-templates/dist/shop/checkout/payment.twig index 905298af6b..9ff0511cc7 100644 --- a/example-templates/dist/shop/checkout/payment.twig +++ b/example-templates/dist/shop/checkout/payment.twig @@ -8,7 +8,7 @@ {% redirect 'shop/checkout/payment-method' %} {% endif %} -
+
{% if craft.commerce.settings.allowCheckoutWithoutPayment %}
@@ -73,31 +73,30 @@ {% 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\\PaymentIntentsElementsCheckout' %} + {% if className(cart.gateway) == 'craft\\commerce\\stripe\\gateways\\PaymentIntents' %} {% set params = { 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', } %} {% endif %} -
{% namespace cart.gateway.handle|commercePaymentFormNamespace %} {{ cart.gateway.getPaymentFormHtml(params)|raw }} {% endnamespace %}
- {# Force in some basic styling for the gateway-provided form markup (better to build your own form markup!) #} - {% include 'shop/checkout/_includes/base-payment-form-styles' %} - {% if cart.gateway.supportsPaymentSources() and currentUser %}