From 28f3858fdcc53702c240347e5711411ee3618375 Mon Sep 17 00:00:00 2001 From: Gerson Date: Thu, 6 Nov 2025 16:22:03 +1000 Subject: [PATCH] Payment gateways and vouchers 20.0.28 --- controllers.csv | 1 + templates/blocks/slideshow.liquid | 2 +- templates/pages/product_category.liquid | 4 +- templates/pages/voucher.liquid | 97 +++-- templates/resources/package-lock.json | 408 +++++++++--------- templates/resources/package.json | 8 +- .../src/scripts/packs/gateways/afterpay.js | 364 ++++++++++++++++ .../src/scripts/packs/gateways/apple-pay.js | 13 +- .../src/scripts/packs/gateways/google-pay.js | 24 +- .../src/scripts/packs/gateways/stripe.js | 145 ++++--- .../src/scripts/packs/gateways/wallet.js | 24 +- .../account/subscriptions/index.liquid | 41 +- .../subscriptions/subscriptions.liquid | 46 ++ .../checkout/customer_information/form.liquid | 2 +- .../payment_providers/adyen_form.liquid | 22 +- .../payment_providers/afterpay_form.liquid | 75 ++++ .../authorize_net_ach_form.liquid | 7 +- .../authorize_net_form.liquid | 7 +- .../payment_providers/eway_form.liquid | 7 +- .../payment_providers/square_form.liquid | 33 +- .../payment_providers/stripe_form.liquid | 18 +- .../westpac_online_pay_form.liquid | 18 +- .../select_and_payment.liquid | 2 + .../show_wallets_only.liquid | 2 + templates/snippets/orders/order.liquid | 1 + .../snippets/orders/subscriptions.liquid | 11 + .../products/product/add_to_cart.liquid | 8 +- .../products/product/express_checkout.liquid | 6 + templates/snippets/shared/order_total.liquid | 2 +- translations/en.default.csv | 26 +- 30 files changed, 1015 insertions(+), 409 deletions(-) create mode 100644 templates/resources/src/scripts/packs/gateways/afterpay.js create mode 100644 templates/snippets/account/subscriptions/subscriptions.liquid create mode 100644 templates/snippets/checkout/payment_information/payment_providers/afterpay_form.liquid create mode 100644 templates/snippets/orders/subscriptions.liquid diff --git a/controllers.csv b/controllers.csv index fe5a687a..388c2543 100644 --- a/controllers.csv +++ b/controllers.csv @@ -147,3 +147,4 @@ sitemap,show product_categories,index product_categories,show vouchers,show +voucher_activations,create diff --git a/templates/blocks/slideshow.liquid b/templates/blocks/slideshow.liquid index 46b94d0d..bc55ca77 100644 --- a/templates/blocks/slideshow.liquid +++ b/templates/blocks/slideshow.liquid @@ -9,7 +9,7 @@ {%- if children_content != blank %} {%- require "scripts/slider.js" -%} -
+
{{ children_content }}
{%- else %} diff --git a/templates/pages/product_category.liquid b/templates/pages/product_category.liquid index 9fe1819a..456b3bbc 100644 --- a/templates/pages/product_category.liquid +++ b/templates/pages/product_category.liquid @@ -31,7 +31,7 @@ {%- render "search/filters/sort_dropdown" %} - {%- if current_product_category.introduction_content %} + {%- if current_product_category.introduction_content != blank %}
{{ current_product_category.introduction_content }}
@@ -41,7 +41,7 @@ {%- render "products/results", allow_comparisons: true %}
- {%- if current_product_category.information_content %} + {%- if current_product_category.information_content != blank %}
{{ current_product_category.information_content }}
diff --git a/templates/pages/voucher.liquid b/templates/pages/voucher.liquid index 47c2345c..480820ac 100644 --- a/templates/pages/voucher.liquid +++ b/templates/pages/voucher.liquid @@ -1,8 +1,6 @@ {%- default voucher: nil %}
- {%- render "shared/page_header", heading: "t.vouchers.show.heading", sub_heading: current_request.params["code"] %} - {%- if voucher == blank %} {% if current_request.params.code != blank %}
@@ -10,47 +8,82 @@
{%- endif %} {%- else %} - - - - - - - - - -
{{ "vouchers.show.balance" | t }}{{ "vouchers.show.expires" | t }}
{{ voucher.current_balance | money }} - {%- if voucher.expires_at == blank %} - {{ "vouchers.show.no_expiry" | t }} - {%- else %} - {% render "shared/date", timestamp: voucher.expires_at %} - {%- endif %} -
- {%- if voucher.orders.size > 0 %} -

{{ "vouchers.show.orders.heading" | t }}

- + {%- if voucher.requires_activation? %} + {%- render "shared/page_header", heading: "t.vouchers.show.activation_form.heading", sub_heading: current_request.params["code"] %} + {%- else %} + {%- render "shared/page_header", heading: "t.vouchers.show.heading", sub_heading: current_request.params["code"] %} + {%- endif %} + + {%- if voucher.requires_activation? %} +
+ {% form "activate-voucher", voucher: voucher %} + + + + + + + {% endform %} +
+ {%- else %} +
+ + + + - - - + + - {% for order in voucher.orders %} +
{{ "vouchers.show.balance" | t }}{{ "vouchers.show.expires" | t }}
{{ "vouchers.show.orders.reference" | t }}{{ "vouchers.show.orders.date" | t }}{{ "vouchers.show.orders.total" | t }}

{{ voucher.current_balance | money }}

+

+ {%- if voucher.expires_at == blank %} + {{ "vouchers.show.no_expiry" | t }} + {%- else %} + {% render "shared/date", timestamp: voucher.expires_at %} + {%- endif %} +

+
+ {%- if voucher.orders.size > 0 %} +

{{ "vouchers.show.orders.heading" | t }}

+ - - - + + + - {%- endfor %} -
{{ order.reference }}{% render "shared/date", timestamp: order.ordered_at %} - {% if order.quotable? %}-{% else %}{{ order.total | money }}{% endif %} - {{ "vouchers.show.orders.reference" | t }}{{ "vouchers.show.orders.date" | t }}{{ "vouchers.show.orders.total" | t }}
+ {% for order in voucher.orders %} + + {{ order.reference }} + {% render "shared/date", timestamp: order.ordered_at %} + + {% if order.quotable? %}-{% else %}{{ order.total | money }}{% endif %} + + + {%- endfor %} + + {%- endif %} {%- endif %} {%- endif %} {%- if current_request.params["code"] != blank %}

{{ "vouchers.show.form.heading" | t }}

+ {%- else %} +

{{ "vouchers.show.check_voucher_heading" | t }}

{%- endif %} +
diff --git a/templates/resources/package-lock.json b/templates/resources/package-lock.json index b410c4ac..8b3dc5bf 100644 --- a/templates/resources/package-lock.json +++ b/templates/resources/package-lock.json @@ -8,9 +8,9 @@ "@adyen/adyen-web": "<6.0.0", "@bugsnag/js": "^8.6.0", "@paypal/paypal-js": "^9.0.1", - "@rails/ujs": "^7.1.502", + "@rails/ujs": "^7.1.600", "@stripe/stripe-js": "^3.5.0", - "braintree-web": "^3.130.0", + "braintree-web": "^3.132.0", "hammerjs": "^2.0.8", "hoverintent": "^2.2.1", "js-cookie": "^3.0.5", @@ -22,8 +22,8 @@ }, "devDependencies": { "colors": "^1.4.0", - "cssnano": "^7.1.1", - "esbuild": "^0.25.10", + "cssnano": "^7.1.2", + "esbuild": "^0.25.11", "esbuild-plugin-manifest": "^1.0.5", "esbuild-sass-plugin": "^3.3.1", "postcss": "^8.5.6", @@ -79,10 +79,13 @@ "license": "MIT" }, "node_modules/@braintree/browser-detection": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@braintree/browser-detection/-/browser-detection-2.0.2.tgz", - "integrity": "sha512-Zrv/pyodvwv/hsjsBKXKVcwHZOkx4A/5Cy2hViXtqghAhLd3483bYUIfHZJE5JKTrd018ny1FI5pN1PHFtW7vw==", - "license": "MIT" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@braintree/browser-detection/-/browser-detection-2.1.0.tgz", + "integrity": "sha512-TbWESQre3wXBC3uag8X8xdr0zfD+FuhgUiuRxp2nkJagHB+NgA40zDOI8McGKijYFG7HeeAzNHW1MhFdEE1Blg==", + "license": "MIT", + "dependencies": { + "detectincognitojs": "1.6.0" + } }, "node_modules/@braintree/event-emitter": { "version": "0.4.1", @@ -184,9 +187,9 @@ "license": "MIT" }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz", - "integrity": "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz", + "integrity": "sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==", "cpu": [ "ppc64" ], @@ -201,9 +204,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.10.tgz", - "integrity": "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.11.tgz", + "integrity": "sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==", "cpu": [ "arm" ], @@ -218,9 +221,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.10.tgz", - "integrity": "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.11.tgz", + "integrity": "sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==", "cpu": [ "arm64" ], @@ -235,9 +238,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.10.tgz", - "integrity": "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.11.tgz", + "integrity": "sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==", "cpu": [ "x64" ], @@ -252,9 +255,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.10.tgz", - "integrity": "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.11.tgz", + "integrity": "sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==", "cpu": [ "arm64" ], @@ -269,9 +272,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.10.tgz", - "integrity": "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.11.tgz", + "integrity": "sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==", "cpu": [ "x64" ], @@ -286,9 +289,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.10.tgz", - "integrity": "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.11.tgz", + "integrity": "sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==", "cpu": [ "arm64" ], @@ -303,9 +306,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.10.tgz", - "integrity": "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.11.tgz", + "integrity": "sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==", "cpu": [ "x64" ], @@ -320,9 +323,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.10.tgz", - "integrity": "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.11.tgz", + "integrity": "sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==", "cpu": [ "arm" ], @@ -337,9 +340,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.10.tgz", - "integrity": "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.11.tgz", + "integrity": "sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==", "cpu": [ "arm64" ], @@ -354,9 +357,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.10.tgz", - "integrity": "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.11.tgz", + "integrity": "sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==", "cpu": [ "ia32" ], @@ -371,9 +374,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.10.tgz", - "integrity": "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.11.tgz", + "integrity": "sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==", "cpu": [ "loong64" ], @@ -388,9 +391,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.10.tgz", - "integrity": "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.11.tgz", + "integrity": "sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==", "cpu": [ "mips64el" ], @@ -405,9 +408,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.10.tgz", - "integrity": "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.11.tgz", + "integrity": "sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==", "cpu": [ "ppc64" ], @@ -422,9 +425,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.10.tgz", - "integrity": "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.11.tgz", + "integrity": "sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==", "cpu": [ "riscv64" ], @@ -439,9 +442,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.10.tgz", - "integrity": "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.11.tgz", + "integrity": "sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==", "cpu": [ "s390x" ], @@ -456,9 +459,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.10.tgz", - "integrity": "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.11.tgz", + "integrity": "sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==", "cpu": [ "x64" ], @@ -473,9 +476,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.10.tgz", - "integrity": "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.11.tgz", + "integrity": "sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==", "cpu": [ "arm64" ], @@ -490,9 +493,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.10.tgz", - "integrity": "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.11.tgz", + "integrity": "sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==", "cpu": [ "x64" ], @@ -507,9 +510,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.10.tgz", - "integrity": "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.11.tgz", + "integrity": "sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==", "cpu": [ "arm64" ], @@ -524,9 +527,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.10.tgz", - "integrity": "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.11.tgz", + "integrity": "sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==", "cpu": [ "x64" ], @@ -541,9 +544,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.10.tgz", - "integrity": "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.11.tgz", + "integrity": "sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==", "cpu": [ "arm64" ], @@ -558,9 +561,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.10.tgz", - "integrity": "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.11.tgz", + "integrity": "sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==", "cpu": [ "x64" ], @@ -575,9 +578,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.10.tgz", - "integrity": "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.11.tgz", + "integrity": "sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==", "cpu": [ "arm64" ], @@ -592,9 +595,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.10.tgz", - "integrity": "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.11.tgz", + "integrity": "sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==", "cpu": [ "ia32" ], @@ -609,9 +612,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz", - "integrity": "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.11.tgz", + "integrity": "sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==", "cpu": [ "x64" ], @@ -626,9 +629,9 @@ } }, "node_modules/@paypal/accelerated-checkout-loader": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@paypal/accelerated-checkout-loader/-/accelerated-checkout-loader-1.2.0.tgz", - "integrity": "sha512-RYVGTCYQ8dgTx9033ueVhU6psmU5J0TvHhnIQLB7nbeIAlxbTir8thJ35O89EHLGRVT4CUtPhIwH9+6mSuwiXQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@paypal/accelerated-checkout-loader/-/accelerated-checkout-loader-1.2.1.tgz", + "integrity": "sha512-tO7CbodhsG8YRMTQTu2TW3wSTXbMWBigI/xvnrgXt20Ror8j6WdEbhavseFv4U4MYC2UYItehGtmpHSfyxY58Q==", "license": "MIT", "dependencies": { "@braintree/asset-loader": "2.0.0", @@ -652,9 +655,9 @@ } }, "node_modules/@rails/ujs": { - "version": "7.1.502", - "resolved": "https://registry.npmjs.org/@rails/ujs/-/ujs-7.1.502.tgz", - "integrity": "sha512-SFtA5X59BimNG8cy60pgZhPf1bGpg5KXVYF6Jj5H5hrwwNXKNmD2yNZhHC+WfuLqBN+972cZHR9Fto6nJQz+Xw==", + "version": "7.1.600", + "resolved": "https://registry.npmjs.org/@rails/ujs/-/ujs-7.1.600.tgz", + "integrity": "sha512-ntMYJ8++n1zcIJ1g75or7Va9IU3ueCHgp9IJk7Ny7eFEbu0Kx40EHZ2FNhoxoUEjlF5NifCFCbvoK5rDK5HpIQ==", "license": "MIT" }, "node_modules/@stripe/stripe-js": { @@ -696,6 +699,16 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.23", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.23.tgz", + "integrity": "sha512-616V5YX4bepJFzNyOfce5Fa8fDJMfoxzOIzDCZwaGL8MKVpFrXqfNUoIpRn9YMI5pXf/VKgzjB4htFMsFKKdiQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -739,20 +752,20 @@ } }, "node_modules/braintree-web": { - "version": "3.130.0", - "resolved": "https://registry.npmjs.org/braintree-web/-/braintree-web-3.130.0.tgz", - "integrity": "sha512-7zShmd+/nK55GTcck1H2urHoaq2BL9E6uhi/Kr79U6+mY0XXiy+A4r9pG6nlZ1jyKcPrCc7xxD8tFiMXeh55Aw==", + "version": "3.132.0", + "resolved": "https://registry.npmjs.org/braintree-web/-/braintree-web-3.132.0.tgz", + "integrity": "sha512-Xzhv/8QkbJiNEuE4oU06rzarEZOMigS2R1Vc+1a5u88Mwym5deVfWBQzXaQbW7MvcAl+2T/LO/ECncJN2th1RA==", "license": "MIT", "dependencies": { "@braintree/asset-loader": "2.0.3", - "@braintree/browser-detection": "2.0.2", + "@braintree/browser-detection": "2.1.0", "@braintree/event-emitter": "0.4.1", "@braintree/extended-promise": "1.0.0", "@braintree/iframer": "2.0.1", "@braintree/sanitize-url": "7.0.4", "@braintree/uuid": "1.0.1", "@braintree/wrap-promise": "2.1.0", - "@paypal/accelerated-checkout-loader": "1.2.0", + "@paypal/accelerated-checkout-loader": "1.2.1", "card-validator": "10.0.3", "credit-card-type": "10.1.0", "framebus": "6.0.3", @@ -767,9 +780,9 @@ "integrity": "sha512-Og0+jCRQetV84U8wVjMNccfGCnMQ9mGs9Hv78QFe+pSDD3gWTpz0y+1QCuxy5d/vBFuZ3iwP2eycAkvqIMPmWg==" }, "node_modules/browserslist": { - "version": "4.25.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.3.tgz", - "integrity": "sha512-cDGv1kkDI4/0e5yON9yM5G/0A5u8sf5TnmdX5C9qHzI9PPu++sQ9zjm1k9NiOrf3riY4OkK0zSGqfvJyJsgCBQ==", + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz", + "integrity": "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==", "dev": true, "funding": [ { @@ -787,10 +800,11 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001735", - "electron-to-chromium": "^1.5.204", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" + "baseline-browser-mapping": "^2.8.19", + "caniuse-lite": "^1.0.30001751", + "electron-to-chromium": "^1.5.238", + "node-releases": "^2.0.26", + "update-browserslist-db": "^1.1.4" }, "bin": { "browserslist": "cli.js" @@ -829,9 +843,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001737", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001737.tgz", - "integrity": "sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw==", + "version": "1.0.30001752", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001752.tgz", + "integrity": "sha512-vKUk7beoukxE47P5gcVNKkDRzXdVofotshHwfR9vmpeFKxmI5PBpgOMC18LUJUA/DvJ70Y7RveasIBraqsyO/g==", "dev": true, "funding": [ { @@ -935,9 +949,9 @@ "license": "MIT" }, "node_modules/css-declaration-sorter": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz", - "integrity": "sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.3.0.tgz", + "integrity": "sha512-LQF6N/3vkAMYF4xoHLJfG718HRJh34Z8BnNhd6bosOMIVjMlhuZK5++oZa3uYAgrI5+7x2o27gUqTR2U/KjUOQ==", "dev": true, "license": "ISC", "engines": { @@ -1004,13 +1018,13 @@ } }, "node_modules/cssnano": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.1.1.tgz", - "integrity": "sha512-fm4D8ti0dQmFPeF8DXSAA//btEmqCOgAc/9Oa3C1LW94h5usNrJEfrON7b4FkPZgnDEn6OUs5NdxiJZmAtGOpQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.1.2.tgz", + "integrity": "sha512-HYOPBsNvoiFeR1eghKD5C3ASm64v9YVyJB4Ivnl2gqKoQYvjjN/G0rztvKQq8OxocUtC6sjqY8jwYngIB4AByA==", "dev": true, "license": "MIT", "dependencies": { - "cssnano-preset-default": "^7.0.9", + "cssnano-preset-default": "^7.0.10", "lilconfig": "^3.1.3" }, "engines": { @@ -1025,27 +1039,27 @@ } }, "node_modules/cssnano-preset-default": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-7.0.9.tgz", - "integrity": "sha512-tCD6AAFgYBOVpMBX41KjbvRh9c2uUjLXRyV7KHSIrwHiq5Z9o0TFfUCoM3TwVrRsRteN3sVXGNvjVNxYzkpTsA==", + "version": "7.0.10", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-7.0.10.tgz", + "integrity": "sha512-6ZBjW0Lf1K1Z+0OKUAUpEN62tSXmYChXWi2NAA0afxEVsj9a+MbcB1l5qel6BHJHmULai2fCGRthCeKSFbScpA==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.25.1", + "browserslist": "^4.27.0", "css-declaration-sorter": "^7.2.0", "cssnano-utils": "^5.0.1", "postcss-calc": "^10.1.1", - "postcss-colormin": "^7.0.4", - "postcss-convert-values": "^7.0.7", - "postcss-discard-comments": "^7.0.4", + "postcss-colormin": "^7.0.5", + "postcss-convert-values": "^7.0.8", + "postcss-discard-comments": "^7.0.5", "postcss-discard-duplicates": "^7.0.2", "postcss-discard-empty": "^7.0.1", "postcss-discard-overridden": "^7.0.1", "postcss-merge-longhand": "^7.0.5", - "postcss-merge-rules": "^7.0.6", + "postcss-merge-rules": "^7.0.7", "postcss-minify-font-values": "^7.0.1", "postcss-minify-gradients": "^7.0.1", - "postcss-minify-params": "^7.0.4", + "postcss-minify-params": "^7.0.5", "postcss-minify-selectors": "^7.0.5", "postcss-normalize-charset": "^7.0.1", "postcss-normalize-display-values": "^7.0.1", @@ -1053,11 +1067,11 @@ "postcss-normalize-repeat-style": "^7.0.1", "postcss-normalize-string": "^7.0.1", "postcss-normalize-timing-functions": "^7.0.1", - "postcss-normalize-unicode": "^7.0.4", + "postcss-normalize-unicode": "^7.0.5", "postcss-normalize-url": "^7.0.1", "postcss-normalize-whitespace": "^7.0.1", "postcss-ordered-values": "^7.0.2", - "postcss-reduce-initial": "^7.0.4", + "postcss-reduce-initial": "^7.0.5", "postcss-reduce-transforms": "^7.0.1", "postcss-svgo": "^7.1.0", "postcss-unique-selectors": "^7.0.4" @@ -1124,6 +1138,12 @@ "integrity": "sha512-d4ZVpCW31eWwCMe1YT3ur7mUDnTXbgwyzaL320DrcRT45rfjYxkt5QWLrmOJ+/UEAI2+fQgKe/fCjR8l4TpRgw==", "dev": true }, + "node_modules/detectincognitojs": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/detectincognitojs/-/detectincognitojs-1.6.0.tgz", + "integrity": "sha512-aOmVmzcPdm1Vovuc0ZHr5Vk86ZSMBUXyvTD2S4JRFZZzg0/gbW8FmM+jDFxKSaDwvlpgH3B103rnxJfAX2ZGqA==", + "license": "MIT" + }, "node_modules/dom-serializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", @@ -1184,9 +1204,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.208", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.208.tgz", - "integrity": "sha512-ozZyibehoe7tOhNaf16lKmljVf+3npZcJIEbJRVftVsmAg5TeA1mGS9dVCZzOwr2xT7xK15V0p7+GZqSPgkuPg==", + "version": "1.5.244", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.244.tgz", + "integrity": "sha512-OszpBN7xZX4vWMPJwB9illkN/znA8M36GQqQxi6MNy9axWxhOfJyZZJtSLQCpEFLHP2xK33BiWx9aIuIEXVCcw==", "dev": true, "license": "ISC" }, @@ -1235,9 +1255,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz", - "integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.11.tgz", + "integrity": "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1248,32 +1268,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.10", - "@esbuild/android-arm": "0.25.10", - "@esbuild/android-arm64": "0.25.10", - "@esbuild/android-x64": "0.25.10", - "@esbuild/darwin-arm64": "0.25.10", - "@esbuild/darwin-x64": "0.25.10", - "@esbuild/freebsd-arm64": "0.25.10", - "@esbuild/freebsd-x64": "0.25.10", - "@esbuild/linux-arm": "0.25.10", - "@esbuild/linux-arm64": "0.25.10", - "@esbuild/linux-ia32": "0.25.10", - "@esbuild/linux-loong64": "0.25.10", - "@esbuild/linux-mips64el": "0.25.10", - "@esbuild/linux-ppc64": "0.25.10", - "@esbuild/linux-riscv64": "0.25.10", - "@esbuild/linux-s390x": "0.25.10", - "@esbuild/linux-x64": "0.25.10", - "@esbuild/netbsd-arm64": "0.25.10", - "@esbuild/netbsd-x64": "0.25.10", - "@esbuild/openbsd-arm64": "0.25.10", - "@esbuild/openbsd-x64": "0.25.10", - "@esbuild/openharmony-arm64": "0.25.10", - "@esbuild/sunos-x64": "0.25.10", - "@esbuild/win32-arm64": "0.25.10", - "@esbuild/win32-ia32": "0.25.10", - "@esbuild/win32-x64": "0.25.10" + "@esbuild/aix-ppc64": "0.25.11", + "@esbuild/android-arm": "0.25.11", + "@esbuild/android-arm64": "0.25.11", + "@esbuild/android-x64": "0.25.11", + "@esbuild/darwin-arm64": "0.25.11", + "@esbuild/darwin-x64": "0.25.11", + "@esbuild/freebsd-arm64": "0.25.11", + "@esbuild/freebsd-x64": "0.25.11", + "@esbuild/linux-arm": "0.25.11", + "@esbuild/linux-arm64": "0.25.11", + "@esbuild/linux-ia32": "0.25.11", + "@esbuild/linux-loong64": "0.25.11", + "@esbuild/linux-mips64el": "0.25.11", + "@esbuild/linux-ppc64": "0.25.11", + "@esbuild/linux-riscv64": "0.25.11", + "@esbuild/linux-s390x": "0.25.11", + "@esbuild/linux-x64": "0.25.11", + "@esbuild/netbsd-arm64": "0.25.11", + "@esbuild/netbsd-x64": "0.25.11", + "@esbuild/openbsd-arm64": "0.25.11", + "@esbuild/openbsd-x64": "0.25.11", + "@esbuild/openharmony-arm64": "0.25.11", + "@esbuild/sunos-x64": "0.25.11", + "@esbuild/win32-arm64": "0.25.11", + "@esbuild/win32-ia32": "0.25.11", + "@esbuild/win32-x64": "0.25.11" } }, "node_modules/esbuild-plugin-manifest": { @@ -1590,9 +1610,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "dev": true, "license": "MIT" }, @@ -1709,13 +1729,13 @@ } }, "node_modules/postcss-colormin": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-7.0.4.tgz", - "integrity": "sha512-ziQuVzQZBROpKpfeDwmrG+Vvlr0YWmY/ZAk99XD+mGEBuEojoFekL41NCsdhyNUtZI7DPOoIWIR7vQQK9xwluw==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-7.0.5.tgz", + "integrity": "sha512-ekIBP/nwzRWhEMmIxHHbXHcMdzd1HIUzBECaj5KEdLz9DVP2HzT065sEhvOx1dkLjYW7jyD0CngThx6bpFi2fA==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.25.1", + "browserslist": "^4.27.0", "caniuse-api": "^3.0.0", "colord": "^2.9.3", "postcss-value-parser": "^4.2.0" @@ -1728,13 +1748,13 @@ } }, "node_modules/postcss-convert-values": { - "version": "7.0.7", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-7.0.7.tgz", - "integrity": "sha512-HR9DZLN04Xbe6xugRH6lS4ZQH2zm/bFh/ZyRkpedZozhvh+awAfbA0P36InO4fZfDhvYfNJeNvlTf1sjwGbw/A==", + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-7.0.8.tgz", + "integrity": "sha512-+XNKuPfkHTCEo499VzLMYn94TiL3r9YqRE3Ty+jP7UX4qjewUONey1t7CG21lrlTLN07GtGM8MqFVp86D4uKJg==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.25.1", + "browserslist": "^4.27.0", "postcss-value-parser": "^4.2.0" }, "engines": { @@ -1745,9 +1765,9 @@ } }, "node_modules/postcss-discard-comments": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-7.0.4.tgz", - "integrity": "sha512-6tCUoql/ipWwKtVP/xYiFf1U9QgJ0PUvxN7pTcsQ8Ns3Fnwq1pU5D5s1MhT/XySeLq6GXNvn37U46Ded0TckWg==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-7.0.5.tgz", + "integrity": "sha512-IR2Eja8WfYgN5n32vEGSctVQ1+JARfu4UH8M7bgGh1bC+xI/obsPJXaBpQF7MAByvgwZinhpHpdrmXtvVVlKcQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1817,13 +1837,13 @@ } }, "node_modules/postcss-merge-rules": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-7.0.6.tgz", - "integrity": "sha512-2jIPT4Tzs8K87tvgCpSukRQ2jjd+hH6Bb8rEEOUDmmhOeTcqDg5fEFK8uKIu+Pvc3//sm3Uu6FRqfyv7YF7+BQ==", + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-7.0.7.tgz", + "integrity": "sha512-njWJrd/Ms6XViwowaaCc+/vqhPG3SmXn725AGrnl+BgTuRPEacjiLEaGq16J6XirMJbtKkTwnt67SS+e2WGoew==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.25.1", + "browserslist": "^4.27.0", "caniuse-api": "^3.0.0", "cssnano-utils": "^5.0.1", "postcss-selector-parser": "^7.1.0" @@ -1870,13 +1890,13 @@ } }, "node_modules/postcss-minify-params": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-7.0.4.tgz", - "integrity": "sha512-3OqqUddfH8c2e7M35W6zIwv7jssM/3miF9cbCSb1iJiWvtguQjlxZGIHK9JRmc8XAKmE2PFGtHSM7g/VcW97sw==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-7.0.5.tgz", + "integrity": "sha512-FGK9ky02h6Ighn3UihsyeAH5XmLEE2MSGH5Tc4tXMFtEDx7B+zTG6hD/+/cT+fbF7PbYojsmmWjyTwFwW1JKQQ==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.25.1", + "browserslist": "^4.27.0", "cssnano-utils": "^5.0.1", "postcss-value-parser": "^4.2.0" }, @@ -2024,13 +2044,13 @@ } }, "node_modules/postcss-normalize-unicode": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-7.0.4.tgz", - "integrity": "sha512-LvIURTi1sQoZqj8mEIE8R15yvM+OhbR1avynMtI9bUzj5gGKR/gfZFd8O7VMj0QgJaIFzxDwxGl/ASMYAkqO8g==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-7.0.5.tgz", + "integrity": "sha512-X6BBwiRxVaFHrb2WyBMddIeB5HBjJcAaUHyhLrM2FsxSq5TFqcHSsK7Zu1otag+o0ZphQGJewGH1tAyrD0zX1Q==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.25.1", + "browserslist": "^4.27.0", "postcss-value-parser": "^4.2.0" }, "engines": { @@ -2090,13 +2110,13 @@ } }, "node_modules/postcss-reduce-initial": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-7.0.4.tgz", - "integrity": "sha512-rdIC9IlMBn7zJo6puim58Xd++0HdbvHeHaPgXsimMfG1ijC5A9ULvNLSE0rUKVJOvNMcwewW4Ga21ngyJjY/+Q==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-7.0.5.tgz", + "integrity": "sha512-RHagHLidG8hTZcnr4FpyMB2jtgd/OcyAazjMhoy5qmWJOx1uxKh4ntk0Pb46ajKM0rkf32lRH4C8c9qQiPR6IA==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.25.1", + "browserslist": "^4.27.0", "caniuse-api": "^3.0.0" }, "engines": { @@ -2772,13 +2792,13 @@ "license": "MIT" }, "node_modules/stylehacks": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-7.0.6.tgz", - "integrity": "sha512-iitguKivmsueOmTO0wmxURXBP8uqOO+zikLGZ7Mm9e/94R4w5T999Js2taS/KBOnQ/wdC3jN3vNSrkGDrlnqQg==", + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-7.0.7.tgz", + "integrity": "sha512-bJkD0JkEtbRrMFtwgpJyBbFIwfDDONQ1Ov3sDLZQP8HuJ73kBOyx66H4bOcAbVWmnfLdvQ0AJwXxOMkpujcO6g==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.25.1", + "browserslist": "^4.27.0", "postcss-selector-parser": "^7.1.0" }, "engines": { @@ -2886,9 +2906,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", + "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", "dev": true, "funding": [ { diff --git a/templates/resources/package.json b/templates/resources/package.json index 3a0f5931..fb68c117 100644 --- a/templates/resources/package.json +++ b/templates/resources/package.json @@ -3,9 +3,9 @@ "@adyen/adyen-web": "<6.0.0", "@bugsnag/js": "^8.6.0", "@paypal/paypal-js": "^9.0.1", - "@rails/ujs": "^7.1.502", + "@rails/ujs": "^7.1.600", "@stripe/stripe-js": "^3.5.0", - "braintree-web": "^3.130.0", + "braintree-web": "^3.132.0", "hammerjs": "^2.0.8", "hoverintent": "^2.2.1", "js-cookie": "^3.0.5", @@ -17,8 +17,8 @@ }, "devDependencies": { "colors": "^1.4.0", - "cssnano": "^7.1.1", - "esbuild": "^0.25.10", + "cssnano": "^7.1.2", + "esbuild": "^0.25.11", "esbuild-plugin-manifest": "^1.0.5", "esbuild-sass-plugin": "^3.3.1", "postcss": "^8.5.6", diff --git a/templates/resources/src/scripts/packs/gateways/afterpay.js b/templates/resources/src/scripts/packs/gateways/afterpay.js new file mode 100644 index 00000000..f18bc78a --- /dev/null +++ b/templates/resources/src/scripts/packs/gateways/afterpay.js @@ -0,0 +1,364 @@ +import { PaymentForm } from './payment-form' +import { Wallet } from './wallet' +import { onDomChange } from '../../theme/utils/init' +import storePathUrl from '../../theme/store-path-url' +import fetchWithResponseHandler from '../../theme/utils/fetch' + +onDomChange((node) => { + const forms = node.querySelectorAll('form[data-provider="Afterpay"]') + forms.forEach((form) => { + const providerId = form.dataset.providerId + if (providerId) { + initAfterpay({ form, providerId }) + } + }) +}) + +function initAfterpay({ form, providerId }) { + const paymentForm = new PaymentForm(form) + const wallet = new Wallet(paymentForm) + const countryCode = form.dataset.countryCode || 'AU' + + // Helper function to build callback URL with additional params + const buildCallbackUrl = (additionalParams = {}) => { + const params = { + popupOriginUrl: window.location.href, + ...additionalParams, + } + + const queryString = Object.entries(params) + .map(([key, value]) => `${key}=${encodeURIComponent(value)}`) + .join('&') + + return `${paymentForm.callbackUrl()}&${queryString}` + } + + // Determine the Afterpay SDK URL based on environment + const afterpayUrl = paymentForm.isProduction() + ? 'https://portal.afterpay.com/afterpay.js' + : 'https://portal.sandbox.afterpay.com/afterpay.js' + + // Load the Afterpay SDK + paymentForm.loadScript({ + url: afterpayUrl, + onload: function () { + // Check if this is express checkout mode + const isExpressCheckout = paymentForm.onlyExpressCheckout() + + if (isExpressCheckout) { + initializeExpressCheckout({ paymentForm, wallet, countryCode, buildCallbackUrl }) + } else { + initializePopupCheckout({ paymentForm, form, providerId, countryCode, buildCallbackUrl }) + } + }, + }) +} + +function initializePopupCheckout({ paymentForm, form, providerId, countryCode, buildCallbackUrl }) { + const buttonId = `#AfterpayPaymentButton${providerId}` + const button = form.querySelector(buttonId) + + if (!button) { + console.error(`Afterpay button not found: ${buttonId}`) + return + } + + AfterPay.initializeForPopup({ + countryCode, + target: buttonId, + onCommenceCheckout: async function (actions) { + // Fetch the Afterpay token from our server + paymentForm.hideError() + + try { + const url = buildCallbackUrl() + const result = await fetchWithResponseHandler(url, { + method: 'POST', + }) + + if (result.message) { + paymentForm.showError(result.message) + actions.reject() + } else if (result.token) { + // Cache form params before opening popup + paymentForm.cacheFormParamsAndOnSubmit(() => { + // Resolve with the token to open the Afterpay popup + actions.resolve(result.token) + }) + } else { + paymentForm.showError('Failed to create Afterpay checkout. Please try again.') + actions.reject() + } + } catch (error) { + console.error('Afterpay error:', error) + paymentForm.showError('Something went wrong. Please try again.') + actions.reject() + } + }, + onComplete: function (event) { + // Handle the completion of the Afterpay popup + if (event.data.status === 'SUCCESS') { + // User approved the payment, submit to our server + const payload = { + payment_source: { + orderToken: event.data.orderToken, + status: event.data.status, + }, + } + paymentForm.submitData({ payload }) + } else { + // User cancelled or there was an error + paymentForm.showError('Payment was cancelled or failed. Please try again.') + } + }, + }) +} + +function initializeExpressCheckout({ paymentForm, wallet, countryCode, buildCallbackUrl }) { + // Find the Afterpay Express button in the template + const walletsElement = wallet.walletsElement() + + const button = walletsElement?.querySelector('[data-ref="express-checkout"]') + + if (!button) { + console.error('Afterpay Express: express-checkout button not found') + return + } + + // Get button ID for target parameter + const target = button.id ? `#${button.id}` : '[data-ref="express-checkout"]' + + // Track whether we've already shown an error in onCommenceCheckout + let commenceCheckoutFailed = false + // Store the selected shipping rate ID to use in onComplete + let selectedShippingRateId = null + + const addressMode = paymentForm.offerShipping() ? 'ADDRESS_WITH_SHIPPING_OPTIONS' : 'NO_ADDRESS' + + // Initialize Afterpay with integrated shipping - SDK will handle button clicks + const config = { + countryCode, + target, + addressMode, + buyNow: true, + onCommenceCheckout: async function (actions) { + paymentForm.hideError() + commenceCheckoutFailed = false + + try { + // If product page express checkout, prepare dedicated cart + if (paymentForm.dedicatedCartProductId) { + const result = await wallet.prepareProductCartWithAddToCartData( + paymentForm.dedicatedCartProductId + ) + if (result.error) { + console.error('[Afterpay Express] prepareProductCart error:', result.error) + wallet.showWalletsError(result.error.message) + commenceCheckoutFailed = true + actions.reject() + return + } + } + + const url = buildCallbackUrl({ + mode: 'express', + }) + const res = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-Token': Rails.csrfToken(), + }, + }) + + const result = await res.json() + + if (result.message) { + console.error('[Afterpay Express] Error message:', result.message) + commenceCheckoutFailed = true + actions.reject() + wallet.showWalletsError(result.message) + AfterPay.close() + } else if (result.token) { + actions.resolve(result.token) + } else { + console.error('[Afterpay Express] No token received') + commenceCheckoutFailed = true + actions.reject() + wallet.showWalletsError('Failed to create Afterpay checkout. Please try again.') + AfterPay.close() + } + } catch (error) { + console.error('[Afterpay Express] Exception in onCommenceCheckout:', error) + commenceCheckoutFailed = true + actions.reject() + wallet.showWalletsError('Something went wrong. Please try again.') + AfterPay.close() + } + }, + onShippingAddressChange: async function (data, actions) { + if (!paymentForm.offerShipping()) { + // No shipping required (e.g., digital products or click & collect) + actions.resolve([]) + return + } + + try { + // Fetch shipping rates for the selected address + const result = await wallet.fetchShippingRates({ + country: data.countryCode, + postal_code: data.postcode, + city: data.suburb, + state: data.state, + street: data.address1, + }) + + if (result.error) { + console.error('[Afterpay Express] Shipping rates error:', result.error) + // Reject if shipping not available to this address + actions.reject(AfterPay.CONSTANTS.SHIPPING_UNSUPPORTED) + return + } + + // Convert our shipping rates to Afterpay format + const shippingOptions = result.shippingRates.map((rate) => ({ + id: rate.id, + name: rate.displayName, + description: rate.deliveryEstimate || '', + shippingAmount: { + amount: (rate.amount / 100).toFixed(2), + currency: paymentForm.currency(), + }, + orderAmount: { + amount: (result.amount / 100).toFixed(2), + currency: paymentForm.currency(), + }, + })) + + actions.resolve(shippingOptions) + } catch (error) { + console.error('[Afterpay Express] Exception in onShippingAddressChange:', error) + actions.reject(AfterPay.CONSTANTS.SHIPPING_UNSUPPORTED) + } + }, + onShippingOptionChange: async function (data, actions) { + try { + // Store the selected shipping rate ID for use in onComplete + selectedShippingRateId = data.id || data.shippingOptionId + const result = await wallet.setShippingRate({ id: selectedShippingRateId }) + + if (result.error) { + console.error('[Afterpay Express] Set shipping rate error:', result.error) + wallet.showWalletsError(result.error.message) + actions.reject() + return + } + + // Use the updated cart amount from the server instead of the original orderAmount + const updatedOrderAmount = { + amount: (result.amount / 100).toFixed(2), + currency: data.orderAmount.currency, + } + + // Return complete response with updated order amount from server + const response = { + id: data.id, + shippingAmount: data.shippingAmount, + taxAmount: { + amount: '0.00', // TODO: Calculate tax if needed + currency: updatedOrderAmount.currency, + }, + orderAmount: updatedOrderAmount, + } + + actions.resolve(response) + } catch (error) { + console.error('[Afterpay Express] Exception in onShippingOptionChange:', error) + actions.reject() + } + }, + onComplete: async function (event) { + if (event.data.status === 'SUCCESS') { + // Reset the flag on success + commenceCheckoutFailed = false + try { + const orderInfo = event.data.orderInfo || {} + const consumer = orderInfo.consumer || {} + const shippingAddress = orderInfo.shippingAddress || {} + + // Create/update cart with billing and shipping information from Afterpay + const payload = { + billing_details: { + name: + consumer.givenNames && consumer.surname + ? `${consumer.givenNames} ${consumer.surname}` + : 'Not provided', + email: consumer.email || '', + phone: shippingAddress.phoneNumber || '', + address: { + line1: shippingAddress.line1 || '', + line2: shippingAddress.line2 || '', + city: shippingAddress.area1 || '', + state: shippingAddress.region || '', + postal_code: shippingAddress.postcode || '', + country: shippingAddress.countryCode || '', + }, + }, + shipping_address: { + name: shippingAddress.name || 'Not provided', + address: { + line1: shippingAddress.line1 || '', + line2: shippingAddress.line2 || '', + city: shippingAddress.area1 || '', + state: shippingAddress.region || '', + postal_code: shippingAddress.postcode || '', + country: shippingAddress.countryCode || '', + }, + }, + shipping_rate: { id: selectedShippingRateId }, + authenticity_token: paymentForm.formAuthentityToken(), + dedicated_cart_product_id: paymentForm.dedicatedCartProductId, + } + + const res = await fetch(storePathUrl(`/express_checkout/carts`), { + method: 'PUT', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify(payload), + }) + + if (!res.ok) { + const { error } = await res.json() + console.error('[Afterpay Express] Cart creation error:', error) + if (error) { + wallet.showWalletsError(error.message) + return + } + } + + // Submit payment to our server + paymentForm.submitData({ + payload: { + payment_source: { + orderToken: event.data.orderToken, + status: event.data.status, + }, + mode: 'express', + dedicated_cart_product_id: paymentForm.dedicatedCartProductId, + }, + }) + } catch (error) { + console.error('[Afterpay Express] Exception in onComplete:', error) + wallet.showWalletsError('Payment processing failed. Please try again.') + } + } else { + // Only show error if we haven't already shown one in onCommenceCheckout + if (!commenceCheckoutFailed) { + console.log('[Afterpay Express] Payment not successful, status:', event.data.status) + wallet.showWalletsError('Payment was cancelled or failed. Please try again.') + } + } + }, + } + + AfterPay.initializeForPopup(config) +} diff --git a/templates/resources/src/scripts/packs/gateways/apple-pay.js b/templates/resources/src/scripts/packs/gateways/apple-pay.js index 44ad1e89..7d1acffa 100644 --- a/templates/resources/src/scripts/packs/gateways/apple-pay.js +++ b/templates/resources/src/scripts/packs/gateways/apple-pay.js @@ -121,7 +121,7 @@ export class ApplePay { /** * Create Apple Pay payment request */ - createPaymentRequest() { + createPaymentRequest(amount) { const paymentRequest = { countryCode: this.paymentForm.merchantCountryCode() || 'US', currencyCode: this.paymentForm.currency(), @@ -129,7 +129,7 @@ export class ApplePay { merchantCapabilities: this.merchantCapabilities, total: { label: this.merchantName, - amount: this.paymentForm.totalPayable(), + amount: (amount / 100).toFixed(2), type: 'final', }, } @@ -153,8 +153,13 @@ export class ApplePay { /** * Handle Apple Pay button click */ - onApplePayButtonClicked() { - const paymentRequest = this.createPaymentRequest() + async onApplePayButtonClicked() { + const { amount, didError } = await this.wallet.prepareProductCartWithAddToCartData() + if (didError) { + return + } + + const paymentRequest = this.createPaymentRequest(amount) const session = new ApplePaySession(this.applePayVersion, paymentRequest) session.onvalidatemerchant = async (event) => { diff --git a/templates/resources/src/scripts/packs/gateways/google-pay.js b/templates/resources/src/scripts/packs/gateways/google-pay.js index 8ec4542d..6b1b6548 100644 --- a/templates/resources/src/scripts/packs/gateways/google-pay.js +++ b/templates/resources/src/scripts/packs/gateways/google-pay.js @@ -167,17 +167,13 @@ export class GooglePay { * Provide Google Pay API with a payment amount, currency, and amount status * * @see {@link https://developers.google.com/pay/api/web/reference/request-objects#TransactionInfo} + * @param {number} amount - Amount in cents * @returns {object} transaction info, suitable for use as transactionInfo property of PaymentDataRequest */ - getGoogleTransactionInfo() { - const totalPrice = this.paymentForm.totalPayable() - if (totalPrice === null || totalPrice === undefined || isNaN(parseFloat(totalPrice))) { - throw new Error('👛 Google Pay requires a valid total price') - } - + getGoogleTransactionInfo({ amount }) { return Object.assign({}, this.baseTransactionInfo(), { // The format of the string should follow the regex format: ^[0-9]+(\.[0-9][0-9])?$ - totalPrice: parseFloat(totalPrice).toFixed(2), + totalPrice: (amount / 100).toFixed(2), }) } /** @@ -228,13 +224,14 @@ export class GooglePay { * Configure support for the Google Pay API * * @see {@link https://developers.google.com/pay/api/web/reference/request-objects#PaymentDataRequest} + * @param {number} [amount] - Optional amount in cents to override paymentForm.totalPayable() * @returns {object} PaymentDataRequest fields */ - getGooglePaymentDataRequest() { + getGooglePaymentDataRequest({ amount }) { const paymentDataRequest = Object.assign({}, this.baseRequest) paymentDataRequest.allowedPaymentMethods = [this.cardPaymentMethod] - paymentDataRequest.transactionInfo = this.getGoogleTransactionInfo() + paymentDataRequest.transactionInfo = this.getGoogleTransactionInfo({ amount }) paymentDataRequest.merchantInfo = { merchantId: this.merchantId, merchantName: this.merchantName, @@ -543,8 +540,13 @@ export class GooglePay { /** * Show Google Pay payment sheet when Google Pay payment button is clicked */ - onGooglePaymentButtonClicked() { - const paymentDataRequest = this.getGooglePaymentDataRequest() + async onGooglePaymentButtonClicked() { + const { amount, didError } = await this.wallet.prepareProductCartWithAddToCartData() + if (didError) { + return + } + + const paymentDataRequest = this.getGooglePaymentDataRequest({ amount }) const paymentsClient = this.getGooglePaymentsClient() paymentsClient .loadPaymentData(paymentDataRequest) diff --git a/templates/resources/src/scripts/packs/gateways/stripe.js b/templates/resources/src/scripts/packs/gateways/stripe.js index 1dd09552..87aeda5f 100644 --- a/templates/resources/src/scripts/packs/gateways/stripe.js +++ b/templates/resources/src/scripts/packs/gateways/stripe.js @@ -1,8 +1,9 @@ import { PaymentForm } from './payment-form' import { Wallet } from './wallet' import { loadStripe } from '@stripe/stripe-js/pure' -import storePathUrl from '../../theme/store-path-url' import { onDomChange } from '../../theme/utils/init' +import fetchWithResponseHandler from '../../theme/utils/fetch' +import storePathUrl from '../../theme/store-path-url' onDomChange((node) => { const forms = node.querySelectorAll('form[data-provider="Stripe"]') @@ -34,10 +35,6 @@ function initStripe({ form }) { return url.toString() } - function intentsUrl() { - return form.dataset.intentsUrl - } - function stripeCreateToken(_form) { // We're bypassing the form here because we're using Stripe Elements stripe @@ -116,10 +113,19 @@ function initStripe({ form }) { return } + const amount = Math.round(paymentForm.totalPayable() * 100) + + // Don't initialize wallets if amount is 0 (e.g., subscription update pages) + // Stripe requires amount to be greater than 0 + if (amount <= 0) { + wallet.removeWalletsContainer() + return + } + // Passing StripeElementsOptions; returns StripeElements const elements = stripe.elements({ mode: 'payment', - amount: Math.round(paymentForm.totalPayable() * 100), + amount, currency: paymentForm.currency().toLowerCase(), }) @@ -168,11 +174,9 @@ function initStripe({ form }) { } if (paymentForm.dedicatedCartProductId) { - const { amount, error } = await wallet.prepareProductCartWithAddToCartData( - paymentForm.dedicatedCartProductId - ) - if (error) { - handleWalletError({ error, event }) + const { amount, didError } = await wallet.prepareProductCartWithAddToCartData() + if (didError) { + event.reject() return } elements.update({ amount }) @@ -185,16 +189,32 @@ function initStripe({ form }) { // https://docs.stripe.com/js/elements_object/express_checkout_element_shippingaddresschange_event#express_checkout_element_on_shipping_address_change expressCheckoutElement.on('shippingaddresschange', async (event) => { const { address } = event - const { amount, shippingRates, error } = await wallet.fetchShippingRates(address) + const { amount, shippingRates, defaultShippingRateId, error } = + await wallet.fetchShippingRates(address) if (error) { handleWalletError({ error, event }) return } - // Price might have changed if we adjusted tax applicability - elements.update({ amount }) + // Get the full cart total including default shipping rate + const defaultRate = shippingRates.find((rate) => rate.id === defaultShippingRateId) + let finalAmount = amount + if (defaultRate) { + const { amount: updatedAmount, error: setRateError } = + await wallet.setShippingRate(defaultRate) + if (!setRateError) { + finalAmount = updatedAmount + } + } + + // Update the element with the final amount including shipping + elements.update({ amount: finalAmount }) - event.resolve({ shippingRates }) + // Resolve with the shipping rates + event.resolve({ + shippingRates, + selectedShippingRate: { id: defaultShippingRateId }, + }) }) // https://docs.stripe.com/js/elements_object/express_checkout_element_shippingratechange_event @@ -235,39 +255,40 @@ function initStripe({ form }) { } } - const clientSecret = await fetchClientSecret() + try { + const response = await fetchWithResponseHandler(form.dataset.callbackUrl, { + method: 'post', + headers: { 'content-type': 'application/json' }, + }) - const { error } = await stripe.confirmPayment({ - elements, - clientSecret, - confirmParams: { - // https://docs.stripe.com/js/payment_intents/confirm_payment#confirm_payment_intent-options-confirmParams-return_url - return_url: paymentsUrl(), - }, - }) + if (response.message) { + showError(response.message) + setPayButton(false) + return + } - if (error) { + const clientSecret = response.token.client_secret + + const { error } = await stripe.confirmPayment({ + elements, + clientSecret, + confirmParams: { + // https://docs.stripe.com/js/payment_intents/confirm_payment#confirm_payment_intent-options-confirmParams-return_url + return_url: paymentsUrl(), + }, + }) + + if (error) { + handleWalletError({ error }) + } else { + // Customer is redirected to the callback URL + } + } catch (error) { handleWalletError({ error }) - } else { - // Customer is redirected to the callback URL } }) } - // Creates Stripe PaymentIntent and returns client_secret - async function fetchClientSecret() { - const res = await fetch(intentsUrl(), { - method: 'POST', - headers: { - 'content-type': 'application/json', - }, - }) - - const { client_secret: clientSecret } = await res.json() - - return clientSecret - } - async function initializeStripe() { stripe = await loadStripe(paymentForm.apiKey()) @@ -299,10 +320,8 @@ function initStripe({ form }) { // On wallet click, we send the 'add-to-cart' form data to the server // to create a dedicated cart for the product - const { amount, error } = - await wallet.prepareProductCartWithAddToCartData(dedicatedProductId) - if (error) { - handleWalletError({ error }) + const { didError } = await wallet.prepareProductCartWithAddToCartData() + if (didError) { return } } @@ -323,18 +342,23 @@ function initStripe({ form }) { handleWalletError({ error: { message: 'starting...' } }) - const { shippingRates, error: error1 } = await wallet.fetchShippingRates(address) + const { + shippingRates, + defaultShippingRateId, + error: error1, + } = await wallet.fetchShippingRates(address) handleWalletError({ error: { message: 'fetched rates' } }) if (error1) { handleWalletError({ error: error1 }) return } - // Simulate shippingratechange event + // Simulate shippingratechange event (or use default from shippingaddresschange) + // Priority: 1) Test-provided shippingRateId, 2) Backend default, 3) First rate const shippingRate = shippingRateId ? shippingRates.find((rate) => rate.id === shippingRateId) - : shippingRates[0] - const { amount, error: error2 } = await wallet.setShippingRate(shippingRate) + : shippingRates.find((rate) => rate.id === defaultShippingRateId) || shippingRates[0] + const { error: error2 } = await wallet.setShippingRate(shippingRate) if (error2) { handleWalletError({ error: error2 }) return @@ -370,11 +394,26 @@ function initStripe({ form }) { } window.testStripeWalletCallback = async () => { - const clientSecret = await fetchClientSecret() + try { + const response = await fetchWithResponseHandler(form.dataset.callbackUrl, { + method: 'post', + headers: { 'content-type': 'application/json' }, + }) - const url = new URL(paymentsUrl()) - url.searchParams.set('payment_intent', clientSecret) - window.location = url + if (response.message) { + showError(response.message) + setPayButton(false) + return + } + + const clientSecret = response.token.client_secret + + const url = new URL(paymentsUrl()) + url.searchParams.set('payment_intent', clientSecret) + window.location = url + } catch (error) { + showError('Payment processing failed') + } } } } diff --git a/templates/resources/src/scripts/packs/gateways/wallet.js b/templates/resources/src/scripts/packs/gateways/wallet.js index 00f00a2f..5be83840 100644 --- a/templates/resources/src/scripts/packs/gateways/wallet.js +++ b/templates/resources/src/scripts/packs/gateways/wallet.js @@ -64,9 +64,12 @@ export class Wallet { this.paymentForm.showError(error, options) } - async prepareProductCartWithAddToCartData(dedicatedCartProductId) { - if (!dedicatedCartProductId) { - return + async prepareProductCartWithAddToCartData() { + if (!this.paymentForm.dedicatedCartProductId) { + return { + amount: Math.round(this.paymentForm.totalPayable() * 100), + didError: false, + } } const res = await fetch(storePathUrl(`/express_checkout/carts`), { @@ -75,21 +78,28 @@ export class Wallet { body: JSON.stringify({ authenticity_token: this.paymentForm.formAuthentityToken(), add_to_cart_form_data: this.addToCartFormData(), - dedicated_cart_product_id: dedicatedCartProductId, + dedicated_cart_product_id: this.paymentForm.dedicatedCartProductId, }), }) if (!res.ok) { + let errorMessage try { - return await res.json() // Expects {error:{message: "..."}} + const errorResponse = await res.json() + errorMessage = + errorResponse.error?.message || 'An error has occurred, please try again shortly.' } catch { - return { error: { message: 'An error has occurred, please try again shortly.' } } + errorMessage = 'An error has occurred, please try again shortly.' } + + this.showWalletsError(errorMessage) + return { amount: null, didError: true } } const response = await res.json() return { amount: Math.round(response.cart.amount * 100), + didError: false, } } @@ -243,7 +253,7 @@ export function loadingShippingRates() { throw new Error('loadingShippingRates should now be called as a method on a Wallet instance') } -export function prepareProductCartWithAddToCartData(dedicatedCartProductId) { +export function prepareProductCartWithAddToCartData() { throw new Error( 'prepareProductCartWithAddToCartData should now be called as a method on a Wallet instance' ) diff --git a/templates/snippets/account/subscriptions/index.liquid b/templates/snippets/account/subscriptions/index.liquid index d2b3f079..8685a530 100644 --- a/templates/snippets/account/subscriptions/index.liquid +++ b/templates/snippets/account/subscriptions/index.liquid @@ -1,40 +1,3 @@ {%- render "shared/page_header", heading: "t.accounts.subscriptions.index.heading" %} -{%- if current_customer.subscriptions.size > 0 %} - {%- paginate current_customer.subscriptions by 10 %} - - - - - - - - - - - - {% for subscription in current_customer.subscriptions %} - - - - - - - - - - - {%- endfor %} -
{{ "accounts.subscriptions.index.product" | t }}{{ "accounts.subscriptions.index.order" | t }}{{ "accounts.subscriptions.index.renewal_date" | t }}{{ "accounts.subscriptions.index.next_due" | t }}{{ "accounts.subscriptions.index.status" | t }}{{ "accounts.subscriptions.index.price" | t }}{{ "accounts.subscriptions.index.renewal_order" | t }} 
{{ subscription.product.name }}{{ subscription.order.reference }}{% render "shared/date", timestamp: subscription.renewal_date, time_style: "none" %}{% render "shared/date", timestamp: subscription.next_due_date, time_style: "none" %}{{ "accounts.subscriptions.status.enum." | append: subscription.status | t }}{{ subscription.price | money }} - {%- if subscription.renewal_order != nil %} - {{ subscription.renewal_order.reference }} - {%- else %} - {{ "accounts.subscriptions.index.no_renewal_order" | t }} - {%- endif %} - - {{ "accounts.subscriptions.index.view" | t }} -
- {%- render 'shared/pagination-nav', paginate: paginate %} - {%- endpaginate %} -{%- else %} -
{{ "accounts.subscriptions.index.no_subscriptions" | t }}
-{%- endif %} +{%- assign subscriptions = current_customer.subscriptions %} +{%- render "account/subscriptions/subscriptions", subscriptions: subscriptions %} diff --git a/templates/snippets/account/subscriptions/subscriptions.liquid b/templates/snippets/account/subscriptions/subscriptions.liquid new file mode 100644 index 00000000..299ac2bc --- /dev/null +++ b/templates/snippets/account/subscriptions/subscriptions.liquid @@ -0,0 +1,46 @@ +{%- default subscriptions: nil %} +{%- default hide_order: nil %} + +{%- if subscriptions.size > 0 %} + {%- paginate subscriptions by 10 %} + + + + {%- if hide_order != true %} + + {%- endif %} + + + + + + + + {% for subscription in subscriptions %} + + + {%- if hide_order != true %} + + {%- endif %} + + + + + + + + {%- endfor %} +
{{ "accounts.subscriptions.index.product" | t }}{{ "accounts.subscriptions.index.order" | t }}{{ "accounts.subscriptions.index.renewal_date" | t }}{{ "accounts.subscriptions.index.next_due" | t }}{{ "accounts.subscriptions.index.status" | t }}{{ "accounts.subscriptions.index.price" | t }}{{ "accounts.subscriptions.index.renewal_order" | t }} 
{{ subscription.product.name }}{{ subscription.order.reference }}{% render "shared/date", timestamp: subscription.renewal_date, time_style: "none" %}{% render "shared/date", timestamp: subscription.next_due_date, time_style: "none" %}{{ "accounts.subscriptions.status.enum." | append: subscription.status | t }}{{ subscription.price | money }} + {%- if subscription.renewal_order != nil %} + {{ subscription.renewal_order.reference }} + {%- else %} + {{ "accounts.subscriptions.index.no_renewal_order" | t }} + {%- endif %} + + {{ "accounts.subscriptions.index.view" | t }} +
+ {%- render 'shared/pagination-nav', paginate: paginate %} + {%- endpaginate %} +{%- else %} +
{{ "accounts.subscriptions.index.no_subscriptions" | t }}
+{%- endif %} diff --git a/templates/snippets/checkout/customer_information/form.liquid b/templates/snippets/checkout/customer_information/form.liquid index e1a8690a..0526824e 100644 --- a/templates/snippets/checkout/customer_information/form.liquid +++ b/templates/snippets/checkout/customer_information/form.liquid @@ -134,7 +134,7 @@ {{ "checkout.customer_information.buttons.next" | t }} {% endif %} {% endcapture %} - +
{% if current_cart.quotable? %} diff --git a/templates/snippets/checkout/payment_information/payment_providers/adyen_form.liquid b/templates/snippets/checkout/payment_information/payment_providers/adyen_form.liquid index 6ba15fff..d8c93359 100644 --- a/templates/snippets/checkout/payment_information/payment_providers/adyen_form.liquid +++ b/templates/snippets/checkout/payment_information/payment_providers/adyen_form.liquid @@ -2,10 +2,15 @@ {%- assign provider = payment_provider.code %} {%- assign form_id = provider | append: "PaymentForm" | append: provider_id %} {%- assign button_id = provider | append: "PaymentButton" | append: provider_id %} +{%- assign button_label = 'checkout.gateways.form.pay_now' | t %} {%- assign wallets_container_id = provider | append: "WalletsContainer" | append: provider_id %} {%- assign script_block_id = provider | append: "ScriptBlock" | append: provider_id %} {%- assign payment_error_id = provider | append: "PaymentError" | append: provider_id %} +{%- if current_subscription %} + {%- assign button_label = 'accounts.subscriptions.show.submit_update_button_label' | t %} +{%- endif %} + {%- default only_express_checkout: false %} {%- default dedicated_cart_product_id: nil %} {%# Need unique IDs for per-product Wallets; since Cart dropdown is on same page %} @@ -17,19 +22,12 @@ {%- assign payment_error_id = payment_error_id | append: "Product" | append: dedicated_cart_product_id %} {% endif %} -{%- assign total_subscriptions = current_cart.total_subscriptions %} -{%- if current_order %} - {%- assign total_subscriptions = current_order.total_subscriptions %} -{%- endif %} -
-{%- if total_subscriptions == 0 and current_subscription == blank %} -
-
-
-
-{%- endif %} +
+
+
+
{%- form "payment", provider: payment_provider, class: "SC-Panel", id: form_id, @@ -47,7 +45,7 @@ {% render "checkout/payment_information/additional_info", form: form %}
+ +{% unless hide_wallet %} + {%- if total_subscriptions == 0 and current_subscription == blank %} +
+
+ {%# Afterpay Express button - only shown in express checkout mode %} + +
+
+
+ {%- endif %} +{%- endunless %} + +{%- form "payment", provider: payment_provider, class: "SC-Panel", + id: form_id, + only-express-checkout: only_express_checkout, + dedicated-cart-product-id: dedicated_cart_product_id + %} + + {% unless only_express_checkout %} + {{ payment_provider.description_content }} + +
+ + {% render "checkout/payment_information/additional_info", form: form %} + + + {% endunless %} + +
+{%- endform %} + +{%- require 'scripts/gateways/afterpay.js' %} diff --git a/templates/snippets/checkout/payment_information/payment_providers/authorize_net_ach_form.liquid b/templates/snippets/checkout/payment_information/payment_providers/authorize_net_ach_form.liquid index 9894a8fb..6611c9f4 100644 --- a/templates/snippets/checkout/payment_information/payment_providers/authorize_net_ach_form.liquid +++ b/templates/snippets/checkout/payment_information/payment_providers/authorize_net_ach_form.liquid @@ -1,6 +1,11 @@ {%- assign provider_id = payment_provider.id %} {%- assign provider = payment_provider.code %} {%- assign button_id = provider | append: "PaymentButton" | append: provider_id %} +{%- assign button_label = 'checkout.gateways.form.pay_now' | t %} + +{%- if current_subscription %} + {%- assign button_label = 'accounts.subscriptions.show.submit_update_button_label' | t %} +{%- endif %} {%- form "payment", provider: payment_provider, class: "SC-Panel" %}
@@ -10,7 +15,7 @@ {% render "checkout/payment_information/payment_providers/authorize_net_ach/form", form: form, payment_provider: payment_provider %} @@ -10,7 +15,7 @@ {% render "checkout/payment_information/payment_providers/authorize_net/form", form: form, payment_provider: payment_provider %} - .square-apple-pay-button { - height: 48px; - width: 300px; - display: inline-block; - -webkit-appearance: -apple-pay-button; - -apple-pay-button-type: plain; - -apple-pay-button-style: black; - } - - .gpay-card-info-container { - height: 48px; - width: 300px; - } + .square-apple-pay-button { + height: 48px; + width: 300px; + display: inline-block; + -webkit-appearance: -apple-pay-button; + -apple-pay-button-type: plain; + -apple-pay-button-style: black; + } + + .gpay-card-info-container { + height: 48px; + width: 300px; + }
@@ -44,7 +49,7 @@ diff --git a/templates/snippets/checkout/payment_information/payment_providers/stripe_form.liquid b/templates/snippets/checkout/payment_information/payment_providers/stripe_form.liquid index 7ed4a1c6..f9cf1df4 100644 --- a/templates/snippets/checkout/payment_information/payment_providers/stripe_form.liquid +++ b/templates/snippets/checkout/payment_information/payment_providers/stripe_form.liquid @@ -2,6 +2,7 @@ {%- assign provider = payment_provider.code %} {%- assign form_id = provider | append: "PaymentForm" | append: provider_id %} {%- assign button_id = provider | append: "PaymentButton" | append: provider_id %} +{%- assign button_label = 'checkout.gateways.form.pay_now' | t %} {%- assign wallets_container_id = provider | append: "WalletsContainer" | append: provider_id %} {%- assign wallets_checkout_id = provider | append: "WalletsCheckout" | append: provider_id %} {%- assign script_block_id = provider | append: "ScriptBlock" | append: provider_id %} @@ -19,19 +20,12 @@ {%- assign payment_error_id = payment_error_id | append: "Product" | append: dedicated_cart_product_id %} {% endif %} -{%- assign total_subscriptions = current_cart.total_subscriptions %} -{%- if current_order %} - {%- assign total_subscriptions = current_order.total_subscriptions %} -{%- endif %} -
-{%- if total_subscriptions == 0 and current_subscription == blank %} -
-
-
-
-{%- endif %} +
+
+
+
{%- form "payment", provider: payment_provider, class: "SC-Panel", id: form_id, @@ -48,7 +42,7 @@ {% render "checkout/payment_information/additional_info", form: form %}
-{%- if total_subscriptions == 0 and current_subscription == blank %} -
-
-
-
-{%- endif %} +
+
+
+
{%- form "payment", provider: payment_provider, class: "SC-Panel", id: form_id, @@ -46,7 +40,7 @@ {% render "checkout/payment_information/additional_info", form: form %} 0 %} +
+
+

{{ "accounts.orders.subscriptions.heading" | t }}

+
+ {% render 'account/subscriptions/subscriptions', subscriptions: subscriptions, hide_order: true %} +
+{%- endif %} + diff --git a/templates/snippets/products/product/add_to_cart.liquid b/templates/snippets/products/product/add_to_cart.liquid index c7c7ccc0..98407ecf 100644 --- a/templates/snippets/products/product/add_to_cart.liquid +++ b/templates/snippets/products/product/add_to_cart.liquid @@ -69,11 +69,9 @@ {%- endif %} {% endform %} - {%- unless product.subscription? %} - {%- if product.pricing.can_purchase_with_currency? and theme_variables["products.card.hide_purchase_button"] == false and button_disabled == false %} - {% render "products/product/express_checkout", product: product %} - {%- endif %} - {%- endunless %} + {%- if product.pricing.can_purchase_with_currency? and theme_variables["products.card.hide_purchase_button"] == false and button_disabled == false %} + {% render "products/product/express_checkout", product: product %} + {%- endif %} {%- else %}
{{ product.unavailable_text }} diff --git a/templates/snippets/products/product/express_checkout.liquid b/templates/snippets/products/product/express_checkout.liquid index 653e31eb..bb128c89 100644 --- a/templates/snippets/products/product/express_checkout.liquid +++ b/templates/snippets/products/product/express_checkout.liquid @@ -13,6 +13,12 @@ only_express_checkout: true, dedicated_cart_product_id: product.id %} + {% when "Afterpay" %} + {% render "checkout/payment_information/payment_providers/afterpay_form", + payment_provider: payment_provider, + only_express_checkout: true, + dedicated_cart_product_id: product.id + %} {% when "CyberSource" %} {% render "checkout/payment_information/payment_providers/cyber_source_form", payment_provider: payment_provider, diff --git a/templates/snippets/shared/order_total.liquid b/templates/snippets/shared/order_total.liquid index e56e69f3..75054a68 100644 --- a/templates/snippets/shared/order_total.liquid +++ b/templates/snippets/shared/order_total.liquid @@ -198,7 +198,7 @@ {%- comment %} Pay Balance Button {% endcomment %} {%- if compact == false and source_type == "order" and current_request.path != source.additional_payment_path %} {% unless source.checkout_step contains "pending_renewal" %} - {%- assign amount = source.total_payable | minus: source.total_remaining_subscriptions %} + {%- assign amount = source.total_payable %} {%- if amount > 0 %} {%- assign amount = amount | money %}
diff --git a/translations/en.default.csv b/translations/en.default.csv index 9f835616..e7650b02 100644 --- a/translations/en.default.csv +++ b/translations/en.default.csv @@ -100,6 +100,7 @@ accounts.orders.fulfillments.status.enum.info_received,Info received accounts.orders.fulfillments.status.enum.failed_attempt,Failed attempt accounts.orders.fulfillments.status.enum.available_for_pickup,Available for pickup accounts.orders.fulfillments.status.enum.exception,Exception +accounts.orders.subscriptions.heading,Subscriptions accounts.orders.index.currency_and_points_connector,and accounts.orders.index.date,Date accounts.orders.index.heading,Orders @@ -381,7 +382,7 @@ checkout.customer_information.billing_address.heading,Billing Address checkout.customer_information.buttons.finish_quote,Submit Quote Request checkout.customer_information.buttons.next,Continue checkout.customer_information.form.company_name,Company name (if applicable) -checkout.customer_information.form.company_name_hint,You can edit your comapny name in your account settings +checkout.customer_information.form.company_name_hint,You can edit your company name in your account settings checkout.customer_information.form.email_address,Email address checkout.customer_information.form.email_address_confirmation,Confirm email address checkout.customer_information.form.email_address_explanation,(for your order confirmation) @@ -1336,6 +1337,23 @@ sc.vouchers.apply.notices.success,Voucher has been applied. sc.vouchers.descriptor,Voucher sc.vouchers.remove.errors.fail,The voucher could not be removed from your cart. Please contact support for assistance. sc.vouchers.remove.notices.success,Voucher removed +sc.vouchers.activation.errors.notice.status_not_for_activation,This voucher cannot be activated. Unexpected status. +sc.vouchers.activation.errors.notice.status_already_active,This voucher is already active. +sc.vouchers.activation.errors.notice.status_cancelled,This voucher is cancelled. +sc.vouchers.activation.errors.notice.status_locked,This voucher is locked. +sc.vouchers.activation.errors.notice.passed_activation_period,This voucher can no longer be activated. +sc.vouchers.activation.errors.notice.voucher_expired,This voucher has expired. +sc.vouchers.activation.errors.notice.invalid_code,The activation code you entered is incorrect. +sc.vouchers.activation.errors.notice.not_found,Voucher record could not be found. +sc.vouchers.activation.errors.notice.no_activation_code_supplied,Please enter an activation code. +sc.vouchers.activation.errors.log.status_not_for_activation,Unexpected status for activation. +sc.vouchers.activation.errors.log.status_already_active,Cannot activate already active Voucher. +sc.vouchers.activation.errors.log.status_cancelled,Cannot activate cancelled voucher. +sc.vouchers.activation.errors.log.status_locked,Cannot activate locked voucher. +sc.vouchers.activation.errors.log.passed_activation_period,Voucher activation window has passed. +sc.vouchers.activation.errors.log.voucher_expired,Voucher has expired. +sc.vouchers.activation.errors.log.invalid_code,Activation code did not match. +sc.vouchers.activation.success,Successfully activated voucher sc.shipments.errors.provider_not_enabled,%{provider} shipping provider is not configured or not active sc.shipments.errors.not_ready_to_ship,Shipment is not ready to ship sc.shipments.errors.provider_shipment_id_is_shipping_rate,Provider Shipment ID references a custom Shipping Rate @@ -1401,7 +1419,11 @@ vouchers.show.expires,Expires vouchers.show.form.code,Enter voucher code vouchers.show.form.heading,Check another code vouchers.show.form.submit,Check -vouchers.show.heading,Check Voucher Balance +vouchers.show.activation_form.activation_code,Enter activation code +vouchers.show.activation_form.heading,Activate Voucher +vouchers.show.activation_form.submit,Activate +vouchers.show.heading,Voucher Balance +vouchers.show.check_voucher_heading,Check Voucher Balance vouchers.show.no_expiry,Does not expire vouchers.show.not_found,Voucher not found vouchers.show.orders.date,Date