@shoppexio/storefront 1.0.71 → 1.0.73

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -23,6 +23,7 @@ __export(src_exports, {
23
23
  ApiError: () => ApiError,
24
24
  CATALOG_UNIT_PRICE_DECIMAL_PLACES: () => CATALOG_UNIT_PRICE_DECIMAL_PLACES,
25
25
  CATALOG_UNIT_PRICE_FORMAT_OPTIONS: () => CATALOG_UNIT_PRICE_FORMAT_OPTIONS,
26
+ CURRENCY_UNAVAILABLE_ERROR_CODE: () => CURRENCY_UNAVAILABLE_ERROR_CODE,
26
27
  CartError: () => CartError,
27
28
  CheckoutCreateError: () => CheckoutCreateError,
28
29
  NetworkError: () => NetworkError,
@@ -71,7 +72,6 @@ __export(src_exports, {
71
72
  loyalty: () => loyalty,
72
73
  me: () => me,
73
74
  mergeSettings: () => mergeSettings,
74
- mountCheckoutChallenge: () => mountCheckoutChallenge,
75
75
  normalizeSearchQuery: () => normalizeSearchQuery,
76
76
  normalizeStorefrontCustomFields: () => normalizeStorefrontCustomFields,
77
77
  order: () => order,
@@ -119,93 +119,8 @@ __export(src_exports, {
119
119
  });
120
120
  module.exports = __toCommonJS(src_exports);
121
121
 
122
- // ../sdk/src/core/cache.ts
123
- var cache = /* @__PURE__ */ new Map();
124
- var pending = /* @__PURE__ */ new Map();
125
- var stats = {
126
- hits: 0,
127
- misses: 0
128
- };
129
- function isExpired(entry) {
130
- return Date.now() > entry.expiresAt;
131
- }
132
- function getCacheStats() {
133
- return {
134
- hits: stats.hits,
135
- misses: stats.misses,
136
- pendingRequests: pending.size,
137
- entries: cache.size
138
- };
139
- }
140
- function clearCache() {
141
- cache.clear();
142
- pending.clear();
143
- }
144
- function invalidateCache(prefixOrKey) {
145
- for (const key of cache.keys()) {
146
- if (key === prefixOrKey || key.startsWith(prefixOrKey)) {
147
- cache.delete(key);
148
- }
149
- }
150
- }
151
- function setCacheEntry(key, data, ttl) {
152
- const now = Date.now();
153
- cache.set(key, {
154
- data,
155
- ttl,
156
- updatedAt: now,
157
- expiresAt: now + ttl
158
- });
159
- }
160
- function getCacheEntry(key) {
161
- const entry = cache.get(key);
162
- if (!entry) return null;
163
- return entry;
164
- }
165
- async function getOrFetch(key, fetcher, options, shouldCache = () => true) {
166
- const entry = getCacheEntry(key);
167
- if (entry && !isExpired(entry)) {
168
- stats.hits += 1;
169
- return entry.data;
170
- }
171
- if (entry && options.staleWhileRevalidate) {
172
- stats.hits += 1;
173
- if (!pending.has(key)) {
174
- const refreshPromise = (async () => {
175
- try {
176
- const data = await fetcher();
177
- if (shouldCache(data)) {
178
- setCacheEntry(key, data, options.ttl);
179
- }
180
- return data;
181
- } finally {
182
- pending.delete(key);
183
- }
184
- })();
185
- pending.set(key, refreshPromise);
186
- }
187
- return entry.data;
188
- }
189
- if (pending.has(key)) {
190
- return pending.get(key);
191
- }
192
- stats.misses += 1;
193
- const promise2 = (async () => {
194
- try {
195
- const data = await fetcher();
196
- if (shouldCache(data)) {
197
- setCacheEntry(key, data, options.ttl);
198
- }
199
- return data;
200
- } finally {
201
- pending.delete(key);
202
- }
203
- })();
204
- pending.set(key, promise2);
205
- return promise2;
206
- }
207
-
208
122
  // ../sdk/src/core/errors.ts
123
+ var CURRENCY_UNAVAILABLE_ERROR_CODE = "errors.storefront.currency_unavailable";
209
124
  var ShoppexError = class _ShoppexError extends Error {
210
125
  constructor(message, code, statusCode) {
211
126
  super(message);
@@ -15640,7 +15555,7 @@ var PaymentGatewayStateSchema = object({
15640
15555
 
15641
15556
  // ../contracts/src/style-center.ts
15642
15557
  var CHECKOUT_PLATFORM_BRAND_COLOR = "#7c3aed";
15643
- var CHECKOUT_PLATFORM_FONT_FAMILY = "Geist";
15558
+ var CHECKOUT_PLATFORM_FONT_FAMILY = "Inter";
15644
15559
  var checkoutStyleSurfaceValues = ["checkout", "payment_link", "embed"];
15645
15560
  var checkoutStyleDensityValues = ["comfortable", "compact"];
15646
15561
  var checkoutStyleModeValues = ["light", "dark", "system"];
@@ -15687,7 +15602,7 @@ var checkoutStyleTokenDefinitions = [
15687
15602
  // `var(--spx-checkout-border, …)` chains loose. The hosted checkout draws
15688
15603
  // almost no hairlines any more — the slots that still do read this, the rest
15689
15604
  // resolve their border to `transparent` in the system CSS.
15690
- { key: "color.border", cssVar: "--spx-checkout-border", group: "color", type: "color", default: "#2c2c2c" },
15605
+ { key: "color.border", cssVar: "--spx-checkout-border", group: "color", type: "color", default: "#3a3a3a" },
15691
15606
  { key: "color.focus", cssVar: "--spx-checkout-focus", group: "color", type: "color", default: CHECKOUT_PLATFORM_BRAND_COLOR, protected: true },
15692
15607
  { key: "color.success", cssVar: "--spx-checkout-success", group: "color", type: "color", default: "#22c55e", protected: true },
15693
15608
  { key: "color.warning", cssVar: "--spx-checkout-warning", group: "color", type: "color", default: "#f59e0b", protected: true },
@@ -15699,16 +15614,22 @@ var checkoutStyleTokenDefinitions = [
15699
15614
  // actually loaded.
15700
15615
  { key: "typography.googleFontFamily", cssVar: "--spx-checkout-google-font", group: "typography", type: "font", default: "" },
15701
15616
  { key: "typography.baseSize", cssVar: "--spx-checkout-font-size", group: "typography", type: "number", default: 14, min: 12, max: 18, step: 1, unit: "px" },
15702
- // 12px, not 8, for the same reason as the input below: the hosted pay CTA is
15703
- // `rounded-xl` and this token is materialised over every slotted button. At 8
15704
- // the store-credit and manual actions rendered one step tighter than the
15705
- // field directly above them and than the CTA they stand in for.
15706
- { key: "shape.buttonRadius", cssVar: "--spx-checkout-button-radius", group: "shape", type: "number", default: 10, min: 0, max: 24, step: 1, unit: "px" },
15707
- // 12px, not 8: the hosted `Input` primitive is `rounded-lg`, and this token is
15708
- // materialised over it. At 8 the system CSS rounded every field one step
15709
- // tighter than the component that drew it.
15710
- { key: "shape.inputRadius", cssVar: "--spx-checkout-input-radius", group: "shape", type: "number", default: 10, min: 0, max: 24, step: 1, unit: "px" },
15711
- { key: "shape.cardRadius", cssVar: "--spx-checkout-card-radius", group: "shape", type: "number", default: 10, min: 0, max: 28, step: 1, unit: "px" },
15617
+ // ALL THREE ARE 8, and they have to be this is one edge, stated in three
15618
+ // places, and the whole reason it is worth a comment is that the three places
15619
+ // are read by three different renderers:
15620
+ // 1. these defaults, materialised into the live checkout root (the `shape`
15621
+ // group is a foundation group, so an untouched shop still gets them);
15622
+ // 2. `--radius-sm` / `--radius-lg` in apps/checkout/app/globals.css, which
15623
+ // every component class paints from, both 0.5rem;
15624
+ // 3. the Stripe Appearance object (`buildStripeAppearanceFromCheckoutStyle`),
15625
+ // which cannot resolve `var()` and is handed these same values as plain
15626
+ // strings for `borderRadius` and the `.Block` / `.AccordionItem` rules.
15627
+ // A disagreement between any two of them is visible as a card inside the
15628
+ // provider iframe rounded differently from the card around it. Pinned by
15629
+ // `__tests__/checkout-radius-chain.test.ts` in apps/checkout.
15630
+ { key: "shape.buttonRadius", cssVar: "--spx-checkout-button-radius", group: "shape", type: "number", default: 8, min: 0, max: 24, step: 1, unit: "px" },
15631
+ { key: "shape.inputRadius", cssVar: "--spx-checkout-input-radius", group: "shape", type: "number", default: 8, min: 0, max: 24, step: 1, unit: "px" },
15632
+ { key: "shape.cardRadius", cssVar: "--spx-checkout-card-radius", group: "shape", type: "number", default: 8, min: 0, max: 28, step: 1, unit: "px" },
15712
15633
  {
15713
15634
  key: "spacing.density",
15714
15635
  cssVar: "--spx-checkout-density",
@@ -15721,7 +15642,7 @@ var checkoutStyleTokenDefinitions = [
15721
15642
  { key: "component.primaryButton.background", cssVar: "--spx-checkout-button-bg", group: "component", type: "color", default: "", protected: true },
15722
15643
  { key: "component.primaryButton.text", cssVar: "--spx-checkout-button-text", group: "component", type: "color", default: "#ffffff", protected: true },
15723
15644
  { key: "component.input.background", cssVar: "--spx-checkout-input-bg", group: "component", type: "color", default: "#292929" },
15724
- { key: "component.input.border", cssVar: "--spx-checkout-input-border", group: "component", type: "color", default: "#2c2c2c" },
15645
+ { key: "component.input.border", cssVar: "--spx-checkout-input-border", group: "component", type: "color", default: "#3a3a3a" },
15725
15646
  { key: "component.input.focusRing", cssVar: "--spx-checkout-input-focus-ring", group: "component", type: "color", default: "" },
15726
15647
  // Default '' is filtered out by the preview-iframe normaliser, so the
15727
15648
  // CSS fallback chain on [data-spx-slot="summary.panel"] resolves to
@@ -15735,9 +15656,28 @@ var checkoutStyleTokenDefinitions = [
15735
15656
  { key: "component.paymentMethod.selectedBackground", cssVar: "--spx-checkout-payment-method-selected-bg", group: "component", type: "color", default: "" },
15736
15657
  { key: "component.checkoutHeader.background", cssVar: "--spx-checkout-embed-header-bg", group: "component", type: "color", default: "" },
15737
15658
  { key: "component.checkoutHeader.text", cssVar: "--spx-checkout-embed-header-text", group: "component", type: "color", default: "" },
15738
- { key: "component.productCard.background", cssVar: "--spx-checkout-product-card-bg", group: "component", type: "color", default: "rgba(255,255,255,0.03)" },
15739
- { key: "component.productCard.border", cssVar: "--spx-checkout-product-card-border", group: "component", type: "color", default: "rgba(255,255,255,0.06)" },
15740
- { key: "component.productCard.shadow", cssVar: "--spx-checkout-product-card-shadow", group: "component", type: "shadow", default: "none" },
15659
+ // THE PRODUCT CARD IS GONE FROM THE HOSTED CHECKOUT, and these three tokens
15660
+ // are what is left of it.
15661
+ //
15662
+ // The 2026 rework replaced the boxed summary line item with a FLAT ROW
15663
+ // (`data-spx-slot="product.line"`, apps/checkout/components/checkout/
15664
+ // product-list.tsx). A flat row has no fill of its own, no edge and no cast —
15665
+ // that is the design, not an omission — so there is nothing on the hosted or
15666
+ // payment-link surface for a border or a shadow token to land on. They are
15667
+ // NOT re-pointed at `product.line`: handing a merchant a border control for a
15668
+ // row that must not have a border is a control that can only make the page
15669
+ // worse, and the slot layer would paint it with `!important` where no call
15670
+ // site could undo it.
15671
+ //
15672
+ // `background` is the exception and stays LIVE: the EMBED surface reads
15673
+ // `--spx-checkout-product-card-bg` in `applyCheckoutStyleToEmbedStyles`
15674
+ // (apps/checkout/lib/checkout-style.tsx), where it is the card fill AND the
15675
+ // input to the embed's light/dark inference. It is marked `surface: 'embed'`
15676
+ // so the Style Center offers it where it still does something, and it keeps
15677
+ // being emitted for every stored theme.
15678
+ { key: "component.productCard.background", cssVar: "--spx-checkout-product-card-bg", group: "component", type: "color", default: "rgba(255,255,255,0.03)", surface: "embed" },
15679
+ { key: "component.productCard.border", cssVar: "--spx-checkout-product-card-border", group: "component", type: "color", default: "rgba(255,255,255,0.06)", deprecated: true },
15680
+ { key: "component.productCard.shadow", cssVar: "--spx-checkout-product-card-shadow", group: "component", type: "shadow", default: "none", deprecated: true },
15741
15681
  { key: "component.productImage.background", cssVar: "--spx-checkout-product-image-bg", group: "component", type: "color", default: "rgba(255,255,255,0.06)" },
15742
15682
  { key: "component.productImage.border", cssVar: "--spx-checkout-product-image-border", group: "component", type: "color", default: "rgba(255,255,255,0.04)" },
15743
15683
  { key: "component.productImage.icon", cssVar: "--spx-checkout-product-image-icon", group: "component", type: "color", default: "rgba(250,250,250,0.4)" },
@@ -15817,28 +15757,91 @@ var checkoutStyleControlTokenDefinitions = [
15817
15757
  ];
15818
15758
  var checkoutStyleSlotValues = [
15819
15759
  "checkout.shell",
15760
+ "checkout.chrome",
15820
15761
  "checkout.panel",
15821
15762
  "checkout.header",
15763
+ "checkout.footer",
15764
+ "checkout.trust",
15765
+ "checkout.section.active",
15766
+ "checkout.section.collapsed",
15767
+ "checkout.mobile_pay_bar",
15768
+ // The spacer under the bar, carrying the working column's ground so the last
15769
+ // scroll does not step down onto the shell's.
15770
+ "checkout.mobile_pay_bar_floor",
15822
15771
  "brand.logo",
15772
+ "brand.hero",
15773
+ // RETIRED, AND DELIBERATELY STILL HERE (see the note above). No element
15774
+ // carries `product.card` any more (see `component.productCard.*` above), so a
15775
+ // rule aimed at it is inert, which is the harmless half of the trade.
15823
15776
  "product.card",
15777
+ // What replaced it: the flat summary row. A merchant can reach it for type
15778
+ // and spacing; it has no card material to override, by design.
15779
+ "product.line",
15824
15780
  "product.image",
15825
15781
  "product.title",
15826
15782
  "product.description",
15827
15783
  "product.price",
15828
15784
  "product.quantity",
15785
+ "product.quantity_menu",
15786
+ "product.remove",
15829
15787
  "product.addon",
15788
+ // The "Included" marker a SELECTED add-on carries in place of its "+$9.00".
15789
+ // Its own slot rather than `product.price`, because it is not a figure: the
15790
+ // amount is already inside the line total above it, and a merchant styling
15791
+ // their price column must not accidentally style a word.
15792
+ "product.addon_included",
15793
+ // The line of glyph-led metadata under the product's title in the embed's
15794
+ // order bar — the description trigger plus whichever of the facts below the
15795
+ // product has. Its own slot so a merchant can retune the whole row's rhythm
15796
+ // (or hide it) without reaching into each fact one at a time.
15797
+ "product.facts",
15798
+ // How many are left. It appears in TWO places and deliberately shares one
15799
+ // slot: on the facts row when the product has no variants, and on each option
15800
+ // tile when it does — because with options there is no single stock figure,
15801
+ // only one per option. A merchant styling scarcity means the same thing in
15802
+ // both, and a shop is unlikely to have configured only the one it can see.
15803
+ "product.stock",
15804
+ "product.delivery",
15805
+ // The billing period a recurring line states — "/month" beside the figure,
15806
+ // or the full phrase on the metadata line when the period is not one-per-unit.
15807
+ "product.recurrence",
15808
+ "product.warranty",
15830
15809
  "paymentLink.hero",
15831
15810
  "summary.panel",
15832
15811
  "summary.line",
15833
15812
  "summary.total",
15813
+ // The row that CLOSES the ledger, distinct from `summary.total` (the column's
15814
+ // headline figure) and from `summary.line` (one part of the sum): it is the
15815
+ // sum itself, and it is the only row in the table that draws a rule.
15816
+ "summary.total_line",
15817
+ // What the total does next month, stated directly under it.
15818
+ "summary.recurrence",
15819
+ "summary.total_note",
15820
+ "summary.order_number",
15821
+ "summary.tracking",
15822
+ "summary.tip",
15823
+ "summary.after_payment",
15824
+ // The mobile order summary: a persistent bar that expands into the panel.
15825
+ "summary.mobile_bar",
15826
+ "summary.disclosure",
15834
15827
  "form.field",
15835
15828
  "form.label",
15836
15829
  "form.help",
15830
+ "form.value",
15831
+ "form.error",
15832
+ "form.panel",
15833
+ "form.save",
15834
+ "form.saved",
15837
15835
  "coupon.input",
15836
+ "coupon.applied",
15838
15837
  "input.base",
15839
15838
  "input.error",
15839
+ // The reserved line under an input that holds its message. It is present
15840
+ // whether or not there is something to say, which is the point: no reflow.
15841
+ "input.message_slot",
15840
15842
  "button.primary",
15841
15843
  "button.secondary",
15844
+ "button.blocked",
15842
15845
  "payment.methods",
15843
15846
  "payment.method",
15844
15847
  "payment.method.icon",
@@ -15846,13 +15849,35 @@ var checkoutStyleSlotValues = [
15846
15849
  "payment.method.meta",
15847
15850
  "payment.method.fee",
15848
15851
  "payment.method.indicator",
15852
+ "payment.method.trailing",
15853
+ "payment.method.change",
15854
+ "payment.selected_method",
15849
15855
  "payment.provider_widget",
15856
+ "payment.express_checkout",
15857
+ "payment.paypal_shell",
15858
+ "payment.acknowledgement",
15859
+ "payment.free_completion",
15860
+ "payment.notice",
15861
+ "payment.trial_notice",
15862
+ "payment.blocker_hint",
15850
15863
  "payment.loading",
15851
15864
  "payment.error",
15852
15865
  "payment.warning",
15853
15866
  "legal.terms",
15854
15867
  "status.success",
15855
15868
  "status.processing",
15869
+ "status.details",
15870
+ "status.wait_hint",
15871
+ // The open order kept its gateway: the buyer is told which one, and why the
15872
+ // picker is gone.
15873
+ "status.method_locked",
15874
+ "status.payment_rescue",
15875
+ "status.recovery",
15876
+ "status.delivery_destination",
15877
+ "embed.overlay_root",
15878
+ // The rest of the embed group belongs to the standalone widget bundle, which
15879
+ // no longer carries these names on an element. Kept for the same reason
15880
+ // `product.card` is kept: a stored theme that targets one must still save.
15856
15881
  "embed.launcher",
15857
15882
  "embed.launcher.icon",
15858
15883
  "embed.productCard",
@@ -15875,6 +15900,18 @@ var checkoutStyleProtectedSlotValues = [
15875
15900
  "summary.total",
15876
15901
  "payment.method.label",
15877
15902
  "payment.provider_widget",
15903
+ // The pay control on mobile. It is the only one in the viewport once the
15904
+ // page scrolls, so hiding it hides checkout.
15905
+ "checkout.mobile_pay_bar",
15906
+ // Whole payment routes: express wallets, the PayPal button host, and the
15907
+ // completion control a zero-total order has instead of a gateway.
15908
+ "payment.express_checkout",
15909
+ "payment.paypal_shell",
15910
+ "payment.free_completion",
15911
+ // Disclosures. The acknowledgement is the buyer's consent to an offline
15912
+ // payment; the trial notice is what they will be charged and when.
15913
+ "payment.acknowledgement",
15914
+ "payment.trial_notice",
15878
15915
  "payment.loading",
15879
15916
  "payment.error",
15880
15917
  "payment.warning",
@@ -16609,6 +16646,7 @@ var embedPaymentSessionWireSchema = external_exports.object({
16609
16646
  confirmations_needed: external_exports.number().optional()
16610
16647
  }).passthrough();
16611
16648
  var publicInvoiceWireCustomFieldDefinitionSchema = external_exports.object({
16649
+ collect_after_payment: external_exports.boolean().optional(),
16612
16650
  default_value: external_exports.string().optional(),
16613
16651
  min_length: external_exports.number().optional(),
16614
16652
  name: external_exports.string(),
@@ -16617,6 +16655,17 @@ var publicInvoiceWireCustomFieldDefinitionSchema = external_exports.object({
16617
16655
  required: external_exports.boolean(),
16618
16656
  type: external_exports.string()
16619
16657
  });
16658
+ var pendingPostPaymentCustomFieldsSchema = external_exports.object({
16659
+ version: external_exports.literal(1),
16660
+ line_items: external_exports.array(external_exports.object({
16661
+ line_item_id: external_exports.string().uuid(),
16662
+ product_id: external_exports.string().uuid(),
16663
+ product_title: external_exports.string(),
16664
+ fields: external_exports.array(publicInvoiceWireCustomFieldDefinitionSchema.extend({
16665
+ collect_after_payment: external_exports.literal(true)
16666
+ }))
16667
+ }))
16668
+ });
16620
16669
  var publicInvoiceWireProductAddonSchema = external_exports.object({
16621
16670
  id: external_exports.string(),
16622
16671
  price: external_exports.number(),
@@ -16653,6 +16702,7 @@ var publicInvoiceWireProductSchema = external_exports.object({
16653
16702
  currency: external_exports.string(),
16654
16703
  custom_fields: unknownRecordSchema.nullable().optional(),
16655
16704
  custom_fields_config: external_exports.array(publicInvoiceWireCustomFieldDefinitionSchema).nullable().optional(),
16705
+ post_payment_custom_fields_config: external_exports.array(publicInvoiceWireCustomFieldDefinitionSchema).nullable().optional(),
16656
16706
  delivery_instruction: external_exports.unknown().optional(),
16657
16707
  delivery_instruction_config: external_exports.unknown().optional(),
16658
16708
  delivery_instruction_label: external_exports.unknown().optional(),
@@ -16961,6 +17011,20 @@ var publicInvoiceWireSchema = external_exports.object({
16961
17011
  buyer_identity: buyerIdentitySchema.nullable().optional(),
16962
17012
  checkout_tipping: checkoutTippingSchema.nullable().optional(),
16963
17013
  country_regulations: external_exports.string().nullable().optional(),
17014
+ /**
17015
+ * THAT a coupon is on the order — never WHICH one.
17016
+ *
17017
+ * The redeemed code has no key on this contract, and none is coming back to
17018
+ * it: this is the wire of the anonymous invoice read, whose only credential
17019
+ * is knowledge of the invoice URL. Coupon codes are merchant-authored and
17020
+ * routinely private (campaign, influencer, single-buyer codes), so naming one
17021
+ * to an unproven reader discloses the merchant's code. The producer withholds
17022
+ * it too — `coupon_code`, `coupon_id` and the raw `discount_breakdown` are all
17023
+ * outside `PUBLIC_INVOICE_FIELDS` — so the summary prints "Coupon −$9.80".
17024
+ *
17025
+ * A buyer-bound lane may name the coupon, but only from a source that proves
17026
+ * the reader: this schema is not it.
17027
+ */
16964
17028
  coupon_applied: external_exports.boolean().optional(),
16965
17029
  crypto_mode: external_exports.string().nullable().optional(),
16966
17030
  crypto_transactions: external_exports.array(cryptoTransactionSchema).optional(),
@@ -16989,6 +17053,7 @@ var publicInvoiceWireSchema = external_exports.object({
16989
17053
  paddle_transaction_id: external_exports.unknown().optional(),
16990
17054
  payment_link_id: external_exports.string().nullable().optional(),
16991
17055
  payment_method_overrides: external_exports.array(paymentMethodOverrideSchema).optional(),
17056
+ pending_custom_fields: pendingPostPaymentCustomFieldsSchema.nullable().optional(),
16992
17057
  payment_session_state: invoicePaymentSessionStateSchema.nullable().optional(),
16993
17058
  polling: pollingSchema,
16994
17059
  pricing_breakdown: pricingBreakdownSchema.nullable().optional(),
@@ -17092,7 +17157,18 @@ var publicInvoiceWireSchema = external_exports.object({
17092
17157
  telegram_stars_payment_note: external_exports.string().nullable().optional(),
17093
17158
  updated_at: external_exports.string().optional(),
17094
17159
  virtual_payments_id: external_exports.string().nullable().optional(),
17095
- void_details: external_exports.unknown().optional()
17160
+ void_details: external_exports.unknown().optional(),
17161
+ // EU right-of-withdrawal consent (§ 356 Abs. 5 BGB / CRD Art. 16(m)).
17162
+ // `withdrawal_consent_required` is the server's verdict for THIS invoice
17163
+ // (shop opt-in + buyer country + digital deliverables, renewals exempt) and is
17164
+ // REQUIRED on the wire: a missing verdict is a producer bug, not a licence for
17165
+ // the checkout to assume `false` and show a pay button the backend refuses.
17166
+ // The other two are the stored proof — always present, null until the buyer
17167
+ // confirms. The checkout gates on these three and never infers the
17168
+ // requirement itself.
17169
+ withdrawal_consent_at: external_exports.string().nullable(),
17170
+ withdrawal_consent_required: external_exports.boolean(),
17171
+ withdrawal_consent_text_version: external_exports.string().nullable()
17096
17172
  }).catchall(external_exports.unknown());
17097
17173
  var embedCheckoutProductWireSchema = publicInvoiceWireProductSchema.extend({
17098
17174
  available_addons: external_exports.array(publicInvoiceWireAvailableAddonSchema).optional(),
@@ -17117,6 +17193,15 @@ var embedCheckoutInvoiceWireSchema = publicInvoiceWireSchema.extend({
17117
17193
  customer_email: external_exports.string().nullable(),
17118
17194
  checkout_style: checkoutStyleStateSchema.nullable().optional(),
17119
17195
  coupon_id: external_exports.string().nullable().optional(),
17196
+ // Merchant toggle (shop_commerce_settings.coupon_field_always_visible):
17197
+ // render the coupon input expanded instead of behind the "Add coupon code"
17198
+ // trigger. Presentation only — coupon validation is unchanged.
17199
+ //
17200
+ // Required, not optional: the public producer (`InvoiceEnricher`) always
17201
+ // resolves this to a boolean, so an absent field means the producer broke,
17202
+ // not that the merchant left it unset. Failing here beats the checkout
17203
+ // quietly rendering the collapsed default for a shop that turned it on.
17204
+ coupon_field_always_visible: external_exports.boolean(),
17120
17205
  discount_breakdown: unknownRecordSchema.nullable().optional(),
17121
17206
  affiliate_code: external_exports.string().nullable().optional(),
17122
17207
  country: external_exports.string().nullable().optional(),
@@ -17984,6 +18069,32 @@ function resetTypedClient() {
17984
18069
  cachedBaseUrl = null;
17985
18070
  }
17986
18071
 
18072
+ // ../sdk/src/utils/requested-currency.ts
18073
+ function normalizeRequestedCurrency(value) {
18074
+ const normalized = value?.trim().toUpperCase();
18075
+ return normalized && /^[A-Z]{3}$/.test(normalized) ? normalized : null;
18076
+ }
18077
+ function getRequestedCurrencyFromLocation() {
18078
+ if (typeof window === "undefined" || !window.location) {
18079
+ return null;
18080
+ }
18081
+ const search = typeof window.location.search === "string" ? window.location.search : "";
18082
+ if (search) {
18083
+ return normalizeRequestedCurrency(new URLSearchParams(search).get("currency"));
18084
+ }
18085
+ const href = typeof window.location.href === "string" ? window.location.href : "";
18086
+ if (!href) {
18087
+ return null;
18088
+ }
18089
+ try {
18090
+ return normalizeRequestedCurrency(
18091
+ new URL(href, "https://storefront.shoppex.local").searchParams.get("currency")
18092
+ );
18093
+ } catch {
18094
+ return null;
18095
+ }
18096
+ }
18097
+
17987
18098
  // ../sdk/src/core/config.ts
17988
18099
  var DEFAULT_API_BASE_URL = "https://api.shoppex.io";
17989
18100
  var currentConfig = null;
@@ -17992,20 +18103,29 @@ var DEFAULT_CHECKOUT_BASE_URL = "https://checkout.shoppex.io";
17992
18103
  function initConfig(storeSlug, options) {
17993
18104
  const normalizedShopId = options?.shopId?.trim();
17994
18105
  cachedShopId = normalizedShopId ? normalizedShopId : null;
17995
- const previousLocale = currentConfig?.locale;
17996
18106
  currentConfig = {
17997
18107
  storeSlug,
17998
18108
  locale: options?.locale,
17999
- currency: options?.currency,
18109
+ currency: normalizeInitCurrency(options?.currency),
18000
18110
  apiBaseUrl: options?.apiBaseUrl ?? DEFAULT_API_BASE_URL,
18001
18111
  checkoutBaseUrl: options?.checkoutBaseUrl ?? DEFAULT_CHECKOUT_BASE_URL
18002
18112
  };
18003
- if (previousLocale !== currentConfig.locale) {
18004
- clearCache();
18005
- }
18006
18113
  resetTypedClient();
18007
18114
  return currentConfig;
18008
18115
  }
18116
+ function normalizeInitCurrency(value) {
18117
+ if (value === void 0 || value.trim() === "") {
18118
+ return void 0;
18119
+ }
18120
+ const normalized = normalizeRequestedCurrency(value);
18121
+ if (!normalized) {
18122
+ throw new ValidationError(
18123
+ `Invalid currency "${value}": pass an ISO 4217 code such as "EUR".`,
18124
+ ["currency"]
18125
+ );
18126
+ }
18127
+ return normalized;
18128
+ }
18009
18129
  function getConfig() {
18010
18130
  if (!currentConfig) {
18011
18131
  throw new NotInitializedError();
@@ -18417,6 +18537,92 @@ function resolveStorefrontSocialLinks(store) {
18417
18537
  };
18418
18538
  }
18419
18539
 
18540
+ // ../sdk/src/core/cache.ts
18541
+ var cache = /* @__PURE__ */ new Map();
18542
+ var pending = /* @__PURE__ */ new Map();
18543
+ var stats = {
18544
+ hits: 0,
18545
+ misses: 0
18546
+ };
18547
+ function isExpired(entry) {
18548
+ return Date.now() > entry.expiresAt;
18549
+ }
18550
+ function getCacheStats() {
18551
+ return {
18552
+ hits: stats.hits,
18553
+ misses: stats.misses,
18554
+ pendingRequests: pending.size,
18555
+ entries: cache.size
18556
+ };
18557
+ }
18558
+ function clearCache() {
18559
+ cache.clear();
18560
+ pending.clear();
18561
+ }
18562
+ function invalidateCache(prefixOrKey) {
18563
+ for (const key of cache.keys()) {
18564
+ if (key === prefixOrKey || key.startsWith(prefixOrKey)) {
18565
+ cache.delete(key);
18566
+ }
18567
+ }
18568
+ }
18569
+ function setCacheEntry(key, data, ttl) {
18570
+ const now = Date.now();
18571
+ cache.set(key, {
18572
+ data,
18573
+ ttl,
18574
+ updatedAt: now,
18575
+ expiresAt: now + ttl
18576
+ });
18577
+ }
18578
+ function getCacheEntry(key) {
18579
+ const entry = cache.get(key);
18580
+ if (!entry) return null;
18581
+ return entry;
18582
+ }
18583
+ async function getOrFetch(key, fetcher, options, shouldCache = () => true) {
18584
+ const entry = getCacheEntry(key);
18585
+ if (entry && !isExpired(entry)) {
18586
+ stats.hits += 1;
18587
+ return entry.data;
18588
+ }
18589
+ if (entry && options.staleWhileRevalidate) {
18590
+ stats.hits += 1;
18591
+ if (!pending.has(key)) {
18592
+ const refreshPromise = (async () => {
18593
+ try {
18594
+ const data = await fetcher();
18595
+ if (shouldCache(data)) {
18596
+ setCacheEntry(key, data, options.ttl);
18597
+ }
18598
+ return data;
18599
+ } finally {
18600
+ pending.delete(key);
18601
+ }
18602
+ })();
18603
+ pending.set(key, refreshPromise);
18604
+ }
18605
+ return entry.data;
18606
+ }
18607
+ if (pending.has(key)) {
18608
+ return pending.get(key);
18609
+ }
18610
+ stats.misses += 1;
18611
+ const promise2 = (async () => {
18612
+ try {
18613
+ const data = await fetcher();
18614
+ if (shouldCache(data)) {
18615
+ setCacheEntry(key, data, options.ttl);
18616
+ }
18617
+ return data;
18618
+ } finally {
18619
+ pending.delete(key);
18620
+ }
18621
+ })();
18622
+ pending.set(key, promise2);
18623
+ return promise2;
18624
+ }
18625
+
18420
18626
  // ../sdk/src/core/endpoint.ts
18421
18627
  var PARAM_PATTERN = /:([A-Za-z0-9_]+)/g;
18422
18628
  function buildEndpoint(template, params) {
@@ -18550,9 +18756,13 @@ var MAX_RETRIES = 2;
18550
18756
  async function sleep(ms) {
18551
18757
  return new Promise((resolve) => setTimeout(resolve, ms));
18552
18758
  }
18759
+ function appendQueryParam(url2, key, value) {
18760
+ const separator = url2.includes("?") ? "&" : "?";
18761
+ return `${url2}${separator}${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
18762
+ }
18553
18763
  async function request(endpoint, options = {}) {
18554
18764
  const config2 = options.baseUrl ? null : getConfig();
18555
- const localeConfig = config2 ?? (isInitialized() ? getConfig() : null);
18765
+ const audience = config2 ?? (isInitialized() ? getConfig() : null);
18556
18766
  const {
18557
18767
  method = "GET",
18558
18768
  body,
@@ -18560,25 +18770,27 @@ async function request(endpoint, options = {}) {
18560
18770
  retries,
18561
18771
  baseUrl,
18562
18772
  headers: requestHeaders,
18563
- cache: cache2
18773
+ cache: cache2,
18774
+ buyerCurrency: pricesInBuyerCurrency = false
18564
18775
  } = options;
18565
18776
  const retryCount = retries ?? (method === "GET" ? MAX_RETRIES : 0);
18566
18777
  const apiBaseUrl = baseUrl ?? config2?.apiBaseUrl ?? "";
18567
- const url2 = `${apiBaseUrl}${endpoint}`;
18778
+ const buyerCurrency = pricesInBuyerCurrency && audience?.currency ? audience.currency : null;
18779
+ const url2 = buyerCurrency ? appendQueryParam(`${apiBaseUrl}${endpoint}`, "currency", buyerCurrency) : `${apiBaseUrl}${endpoint}`;
18568
18780
  const headers = {
18569
18781
  "Content-Type": "application/json",
18570
18782
  Accept: "application/json",
18571
18783
  ...requestHeaders
18572
18784
  };
18573
- if (typeof localeConfig?.locale === "string" && localeConfig.locale.trim()) {
18574
- headers["x-shoppex-locale"] = localeConfig.locale.trim();
18785
+ const locale = typeof audience?.locale === "string" && audience.locale.trim() ? audience.locale.trim() : null;
18786
+ if (locale) {
18787
+ headers["x-shoppex-locale"] = locale;
18575
18788
  }
18576
18789
  let lastFailure = null;
18577
18790
  const executeRequest = async () => {
18578
18791
  for (let attempt = 0; attempt <= retryCount; attempt++) {
18579
18792
  let responseReceived = false;
18580
18793
  let responseDefinitive = false;
18581
- let responseChallenge;
18582
18794
  try {
18583
18795
  const controller = new AbortController();
18584
18796
  const timeoutId = setTimeout(() => controller.abort(), timeout);
@@ -18591,7 +18803,6 @@ async function request(endpoint, options = {}) {
18591
18803
  responseReceived = true;
18592
18804
  clearTimeout(timeoutId);
18593
18805
  const payload = await parseResponsePayload(response);
18594
- responseChallenge = readResponseChallenge(payload.data);
18595
18806
  if (!response.ok) {
18596
18807
  responseDefinitive = isDefinitiveHttpRefusal(payload.data) && response.status >= 400 && response.status < 500 && response.status !== 408;
18597
18808
  const fallbackHttpMessage = response.statusText ? `HTTP ${response.status}: ${response.statusText}` : `HTTP ${response.status}`;
@@ -18616,8 +18827,7 @@ async function request(endpoint, options = {}) {
18616
18827
  ...mapped,
18617
18828
  responseReceived: true,
18618
18829
  responseDefinitive: data.status >= 400 && data.status < 500 && data.status !== 408,
18619
- status: response.status,
18620
- ...responseChallenge ? { challenge: responseChallenge } : {}
18830
+ status: response.status
18621
18831
  };
18622
18832
  } catch (error51) {
18623
18833
  let normalizedError = error51 instanceof Error ? error51 : new Error(String(error51));
@@ -18631,7 +18841,6 @@ async function request(endpoint, options = {}) {
18631
18841
  isTransport: statusCode === void 0 || statusCode === 408,
18632
18842
  responseReceived,
18633
18843
  responseDefinitive,
18634
- ...responseChallenge ? { challenge: responseChallenge } : {},
18635
18844
  ...normalizedError instanceof ApiError ? {
18636
18845
  code: normalizedError.code,
18637
18846
  ...normalizedError.errorParams ? { errorParams: normalizedError.errorParams } : {}
@@ -18653,13 +18862,12 @@ async function request(endpoint, options = {}) {
18653
18862
  ...lastFailure ? { responseReceived: lastFailure.responseReceived } : {},
18654
18863
  ...lastFailure?.responseDefinitive ? { responseDefinitive: true } : {},
18655
18864
  ...lastFailure?.responseReceived && lastFailure.statusCode !== void 0 ? { status: lastFailure.statusCode } : {},
18656
- ...lastFailure?.challenge ? { challenge: lastFailure.challenge } : {},
18657
18865
  ...lastFailure?.code ? { code: lastFailure.code } : {},
18658
18866
  ...lastFailure?.errorParams ? { errorParams: lastFailure.errorParams } : {}
18659
18867
  };
18660
18868
  };
18661
18869
  const result = method === "GET" && cache2 && cache2.ttl > 0 ? await getOrFetch(
18662
- cache2.key ?? `GET:${url2}`,
18870
+ `${locale ?? ""}|${buyerCurrency ?? ""}|${cache2.key ?? `GET:${url2}`}`,
18663
18871
  executeRequest,
18664
18872
  { ttl: cache2.ttl, staleWhileRevalidate: cache2.staleWhileRevalidate },
18665
18873
  (value) => value.success
@@ -18714,25 +18922,6 @@ async function parseResponsePayload(response) {
18714
18922
  return { data: null, rawText: null };
18715
18923
  }
18716
18924
  }
18717
- function readResponseChallenge(payload) {
18718
- if (!payload || typeof payload !== "object") {
18719
- return void 0;
18720
- }
18721
- const data = payload.data;
18722
- if (!data || typeof data !== "object") {
18723
- return void 0;
18724
- }
18725
- const challenge = data.challenge;
18726
- if (!challenge || typeof challenge !== "object") {
18727
- return void 0;
18728
- }
18729
- const provider = challenge.provider;
18730
- const siteKey = challenge.site_key;
18731
- if (provider !== "turnstile" || typeof siteKey !== "string" || !siteKey.trim()) {
18732
- return void 0;
18733
- }
18734
- return { provider, siteKey: siteKey.trim() };
18735
- }
18736
18925
  function isDefinitiveHttpRefusal(payload) {
18737
18926
  if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
18738
18927
  return false;
@@ -18788,6 +18977,7 @@ async function getStore() {
18788
18977
  storeSlug: config2.storeSlug
18789
18978
  }),
18790
18979
  {
18980
+ buyerCurrency: true,
18791
18981
  cache: {
18792
18982
  key: `store:${config2.storeSlug}`,
18793
18983
  ttl: STORE_CACHE_TTL,
@@ -18825,6 +19015,7 @@ async function resolveStoreByDomain(domain2, apiBaseUrl) {
18825
19015
  }),
18826
19016
  {
18827
19017
  baseUrl,
19018
+ buyerCurrency: true,
18828
19019
  cache: {
18829
19020
  key: `store:domain:${cleanDomain}`,
18830
19021
  ttl: STORE_CACHE_TTL,
@@ -18861,6 +19052,7 @@ async function getStorefront(options) {
18861
19052
  storeSlug: config2.storeSlug
18862
19053
  })}${querySuffix}`,
18863
19054
  {
19055
+ buyerCurrency: true,
18864
19056
  cache: {
18865
19057
  key: `storefront:${config2.storeSlug}:${options?.productsLimit ?? "full"}:${options?.productsCursor ?? "start"}`,
18866
19058
  ttl: STORE_CACHE_TTL,
@@ -18958,6 +19150,7 @@ async function getProducts() {
18958
19150
  storeSlug: config2.storeSlug
18959
19151
  }),
18960
19152
  {
19153
+ buyerCurrency: true,
18961
19154
  cache: {
18962
19155
  key: `products:${config2.storeSlug}`,
18963
19156
  ttl: PRODUCTS_CACHE_TTL,
@@ -19001,6 +19194,7 @@ async function getStorefrontProductsPage(options) {
19001
19194
  storeSlug: config2.storeSlug
19002
19195
  })}${querySuffix}`,
19003
19196
  {
19197
+ buyerCurrency: true,
19004
19198
  cache: {
19005
19199
  key: `products:page:${config2.storeSlug}:${options?.limit ?? "default"}:${options?.cursor ?? "start"}:${options?.sort ?? "featured"}:${getStorefrontProductsPageCategoryCacheKey(options?.category)}:${options?.hideOutOfStock === true ? "in-stock" : "all-stock"}`,
19006
19200
  ttl: PRODUCTS_CACHE_TTL,
@@ -19032,6 +19226,7 @@ async function getProduct(idOrSlug) {
19032
19226
  const response = await get(
19033
19227
  `${buildEndpoint("/v1/storefront/products/unique/:idOrSlug", { idOrSlug })}${queryParams}`,
19034
19228
  {
19229
+ buyerCurrency: true,
19035
19230
  cache: {
19036
19231
  key: `product:${idOrSlug}:${shopId ?? "no-shop"}`,
19037
19232
  ttl: PRODUCTS_CACHE_TTL,
@@ -19327,32 +19522,6 @@ function ensureCartLineId(item) {
19327
19522
  return { ...item, line_id: computeCartLineId(item) };
19328
19523
  }
19329
19524
 
19330
- // ../sdk/src/utils/requested-currency.ts
19331
- function normalizeRequestedCurrency(value) {
19332
- const normalized = value?.trim().toUpperCase();
19333
- return normalized && /^[A-Z]{3}$/.test(normalized) ? normalized : null;
19334
- }
19335
- function getRequestedCurrencyFromLocation() {
19336
- if (typeof window === "undefined" || !window.location) {
19337
- return null;
19338
- }
19339
- const search = typeof window.location.search === "string" ? window.location.search : "";
19340
- if (search) {
19341
- return normalizeRequestedCurrency(new URLSearchParams(search).get("currency"));
19342
- }
19343
- const href = typeof window.location.href === "string" ? window.location.href : "";
19344
- if (!href) {
19345
- return null;
19346
- }
19347
- try {
19348
- return normalizeRequestedCurrency(
19349
- new URL(href, "https://storefront.shoppex.local").searchParams.get("currency")
19350
- );
19351
- } catch {
19352
- return null;
19353
- }
19354
- }
19355
-
19356
19525
  // ../sdk/src/modules/cart.ts
19357
19526
  var STORAGE_KEYS = {
19358
19527
  cart: "cart",
@@ -19934,158 +20103,11 @@ async function quoteCart(coupon, currency) {
19934
20103
  return response;
19935
20104
  }
19936
20105
 
19937
- // ../sdk/src/modules/checkout-challenge.ts
19938
- var TURNSTILE_FRAME_MESSAGE_SOURCE = "shoppex-turnstile";
19939
- var TURNSTILE_FRAME_MESSAGE_VERSION = 1;
19940
- var TURNSTILE_FRAME_READY_TIMEOUT_MS = 1e4;
19941
- function readFrameMessage(value, nonce) {
19942
- if (!value || typeof value !== "object" || Array.isArray(value)) return null;
19943
- const record2 = value;
19944
- if (record2.source !== TURNSTILE_FRAME_MESSAGE_SOURCE || record2.version !== TURNSTILE_FRAME_MESSAGE_VERSION || record2.nonce !== nonce || !["ready", "visible", "hidden", "success", "expired", "timeout", "error"].includes(String(record2.type))) return null;
19945
- if (record2.type === "success" && (typeof record2.token !== "string" || !record2.token.trim())) {
19946
- return null;
19947
- }
19948
- return {
19949
- type: record2.type,
19950
- ...typeof record2.token === "string" ? { token: record2.token.trim() } : {}
19951
- };
19952
- }
19953
- var AUTO_CHALLENGE_INVISIBLE_TIMEOUT_MS = 3e4;
19954
- async function resolveCheckoutChallengeProof(challenge) {
19955
- if (typeof document === "undefined" || challenge.provider !== "turnstile" || !challenge.siteKey.trim()) {
19956
- return null;
19957
- }
19958
- const host = document.createElement("div");
19959
- host.setAttribute("data-shoppex-checkout-challenge", "");
19960
- host.style.position = "fixed";
19961
- host.style.inset = "0";
19962
- host.style.display = "none";
19963
- host.style.alignItems = "center";
19964
- host.style.justifyContent = "center";
19965
- host.style.background = "rgba(0, 0, 0, 0.55)";
19966
- host.style.zIndex = "2147483646";
19967
- const card = document.createElement("div");
19968
- card.style.width = "min(340px, 90vw)";
19969
- card.style.background = "#ffffff";
19970
- card.style.borderRadius = "12px";
19971
- card.style.padding = "16px";
19972
- card.style.boxShadow = "0 12px 40px rgba(0, 0, 0, 0.35)";
19973
- host.appendChild(card);
19974
- document.body.appendChild(host);
19975
- return new Promise((resolve) => {
19976
- let settled = false;
19977
- let timeoutId = null;
19978
- let frame = null;
19979
- const finish = (token) => {
19980
- if (settled) return;
19981
- settled = true;
19982
- if (timeoutId !== null) window.clearTimeout(timeoutId);
19983
- frame?.dispose();
19984
- host.remove();
19985
- resolve(token);
19986
- };
19987
- const armTimeout = () => {
19988
- timeoutId = window.setTimeout(() => finish(null), AUTO_CHALLENGE_INVISIBLE_TIMEOUT_MS);
19989
- };
19990
- try {
19991
- frame = mountCheckoutChallenge(card, challenge, {
19992
- onSuccess: (token) => finish(token),
19993
- // `refresh-expired: auto` renews expired runs on its own; the
19994
- // invisible-run timeout stays the bound.
19995
- onExpired: () => {
19996
- },
19997
- onUnavailable: () => finish(null),
19998
- onVisibilityChange: (visible) => {
19999
- host.style.display = visible ? "flex" : "none";
20000
- if (visible) {
20001
- if (timeoutId !== null) {
20002
- window.clearTimeout(timeoutId);
20003
- timeoutId = null;
20004
- }
20005
- } else if (timeoutId === null && !settled) {
20006
- armTimeout();
20007
- }
20008
- }
20009
- });
20010
- } catch {
20011
- host.remove();
20012
- resolve(null);
20013
- return;
20014
- }
20015
- armTimeout();
20016
- });
20017
- }
20018
- function mountCheckoutChallenge(container, challenge, callbacks) {
20019
- if (challenge.provider !== "turnstile" || !challenge.siteKey.trim()) {
20020
- throw new Error("Checkout challenge is invalid.");
20021
- }
20022
- const win = container.ownerDocument.defaultView;
20023
- if (!win) throw new Error("Checkout challenge requires a browser document.");
20024
- const checkoutBaseUrl = getConfig().checkoutBaseUrl;
20025
- const frameUrl = new URL("/turnstile", checkoutBaseUrl);
20026
- if (frameUrl.protocol !== "https:" && frameUrl.protocol !== "http:") {
20027
- throw new Error("Checkout base URL must use http or https.");
20028
- }
20029
- const nonce = win.crypto.randomUUID();
20030
- frameUrl.searchParams.set("site_key", challenge.siteKey.trim());
20031
- frameUrl.searchParams.set("nonce", nonce);
20032
- const frame = container.ownerDocument.createElement("iframe");
20033
- frame.src = frameUrl.toString();
20034
- frame.title = "Checkout verification";
20035
- frame.referrerPolicy = "no-referrer";
20036
- frame.style.border = "0";
20037
- frame.style.width = "100%";
20038
- frame.style.height = "0";
20039
- let disposed = false;
20040
- let ready = false;
20041
- const readyTimeout = win.setTimeout(() => {
20042
- if (!disposed && !ready) callbacks.onUnavailable?.();
20043
- }, TURNSTILE_FRAME_READY_TIMEOUT_MS);
20044
- const onMessage = (event) => {
20045
- if (disposed || event.origin !== frameUrl.origin || event.source !== frame.contentWindow) return;
20046
- const message = readFrameMessage(event.data, nonce);
20047
- if (!message) return;
20048
- ready = true;
20049
- win.clearTimeout(readyTimeout);
20050
- if (message.type === "visible") {
20051
- frame.style.height = "72px";
20052
- callbacks.onVisibilityChange?.(true);
20053
- }
20054
- if (message.type === "hidden") {
20055
- frame.style.height = "0";
20056
- callbacks.onVisibilityChange?.(false);
20057
- }
20058
- if (message.type === "success") {
20059
- frame.style.height = "0";
20060
- callbacks.onVisibilityChange?.(false);
20061
- callbacks.onSuccess(message.token);
20062
- }
20063
- if (message.type === "expired" || message.type === "timeout") callbacks.onExpired?.();
20064
- if (message.type === "error") callbacks.onUnavailable?.();
20065
- };
20066
- const onFrameError = () => callbacks.onUnavailable?.();
20067
- win.addEventListener("message", onMessage);
20068
- frame.addEventListener("error", onFrameError, { once: true });
20069
- container.appendChild(frame);
20070
- return {
20071
- element: frame,
20072
- dispose() {
20073
- if (disposed) return;
20074
- disposed = true;
20075
- win.clearTimeout(readyTimeout);
20076
- win.removeEventListener("message", onMessage);
20077
- frame.removeEventListener("error", onFrameError);
20078
- frame.remove();
20079
- }
20080
- };
20081
- }
20082
-
20083
20106
  // ../sdk/src/modules/checkout.ts
20084
20107
  var CheckoutCreateError = class _CheckoutCreateError extends Error {
20085
20108
  constructor(message, options = {}) {
20086
20109
  super(message);
20087
20110
  this.name = "CheckoutCreateError";
20088
- this.challenge = options.challenge;
20089
20111
  this.code = options.code;
20090
20112
  this.status = options.status;
20091
20113
  Object.setPrototypeOf(this, _CheckoutCreateError.prototype);
@@ -20136,7 +20158,7 @@ function acquireCheckoutCreateIdempotency(requestTarget, createIntent) {
20136
20158
  return attempt;
20137
20159
  }
20138
20160
  function releaseCheckoutCreateIdempotency(attempt, outcomeDefinitive) {
20139
- if (outcomeDefinitive && pendingCheckoutCreates.get(attempt.fingerprint) === attempt) {
20161
+ if (outcomeDefinitive && pendingCheckoutCreates.get(attempt.fingerprint)?.key === attempt.key) {
20140
20162
  pendingCheckoutCreates.delete(attempt.fingerprint);
20141
20163
  }
20142
20164
  }
@@ -20150,7 +20172,13 @@ function resolveCustomerCheckoutRequestTarget(options) {
20150
20172
  return { endpoint: "/v1/storefront/invoices/from-cart" };
20151
20173
  }
20152
20174
  function resolveRequestedCheckoutCurrency(options) {
20153
- return normalizeRequestedCurrency(options.currency) ?? getRequestedCurrencyFromLocation() ?? normalizeRequestedCurrency(getConfig().currency);
20175
+ if (options.currency !== void 0) {
20176
+ return normalizeRequestedCurrency(options.currency);
20177
+ }
20178
+ return getRequestedCurrencyFromLocation() ?? normalizeRequestedCurrency(getConfig().currency);
20179
+ }
20180
+ function getRequestedCheckoutCurrency() {
20181
+ return getRequestedCurrencyFromLocation() ?? normalizeRequestedCurrency(getConfig().currency);
20154
20182
  }
20155
20183
  function normalizeCheckoutFailureMessage(rawMessage) {
20156
20184
  const message = rawMessage?.trim() ?? "";
@@ -20322,14 +20350,7 @@ function mapCartItemsForApi(items) {
20322
20350
  }
20323
20351
  async function checkout(couponOrOptions, options) {
20324
20352
  const resolvedOptions = resolveCheckoutOptions(couponOrOptions, options);
20325
- const firstAttempt = await performCheckout(resolvedOptions);
20326
- if (!firstAttempt.success && firstAttempt.challenge?.provider === "turnstile" && !resolvedOptions.turnstileToken && typeof document !== "undefined") {
20327
- const proof = await resolveCheckoutChallengeProof(firstAttempt.challenge);
20328
- if (proof) {
20329
- return performCheckout({ ...resolvedOptions, turnstileToken: proof });
20330
- }
20331
- }
20332
- return firstAttempt;
20353
+ return performCheckout(resolvedOptions);
20333
20354
  }
20334
20355
  async function performCheckout(resolvedOptions) {
20335
20356
  const { autoRedirect = true, email: email3 } = resolvedOptions;
@@ -20373,16 +20394,12 @@ async function performCheckout(resolvedOptions) {
20373
20394
  // automatically so the server can refuse an invoice priced above it.
20374
20395
  // Undefined when nothing has been quoted this session — the endpoint
20375
20396
  // treats that exactly as an older SDK.
20376
- quote_token: getLatestQuoteToken() ?? void 0
20377
- };
20378
- const createCommand = {
20379
- ...createIntent,
20380
- turnstile_token: resolvedOptions.turnstileToken?.trim() || void 0
20397
+ quote_token: resolvedOptions.quoteToken !== void 0 ? resolvedOptions.quoteToken ?? void 0 : getLatestQuoteToken() ?? void 0
20381
20398
  };
20382
20399
  const createAttempt = acquireCheckoutCreateIdempotency(checkoutRequestTarget, createIntent);
20383
20400
  const response = await post(
20384
20401
  checkoutRequestTarget.endpoint,
20385
- createCommand,
20402
+ createIntent,
20386
20403
  {
20387
20404
  retries: 0,
20388
20405
  baseUrl: checkoutRequestTarget.baseUrl,
@@ -20402,8 +20419,7 @@ async function performCheckout(resolvedOptions) {
20402
20419
  // The server's machine-readable refusal, preserved so callers can react
20403
20420
  // to e.g. `errors.checkout.price_increased_since_quote` without matching
20404
20421
  // localized copy.
20405
- ...response.code ? { code: response.code } : {},
20406
- ...response.challenge ? { challenge: response.challenge } : {}
20422
+ ...response.code ? { code: response.code } : {}
20407
20423
  };
20408
20424
  }
20409
20425
  const checkoutData = normalizeCheckoutResponse(response.data, config2.checkoutBaseUrl);
@@ -20449,17 +20465,7 @@ async function performCheckout(resolvedOptions) {
20449
20465
  }
20450
20466
  async function buildCheckoutUrl(couponOrOptions, options) {
20451
20467
  const resolvedOptions = resolveCheckoutOptions(couponOrOptions, options);
20452
- try {
20453
- return await performBuildCheckoutUrl(resolvedOptions);
20454
- } catch (error51) {
20455
- if (error51 instanceof CheckoutCreateError && error51.challenge?.provider === "turnstile" && !resolvedOptions.turnstileToken && typeof document !== "undefined") {
20456
- const proof = await resolveCheckoutChallengeProof(error51.challenge);
20457
- if (proof) {
20458
- return performBuildCheckoutUrl({ ...resolvedOptions, turnstileToken: proof });
20459
- }
20460
- }
20461
- throw error51;
20462
- }
20468
+ return performBuildCheckoutUrl(resolvedOptions);
20463
20469
  }
20464
20470
  async function performBuildCheckoutUrl(resolvedOptions) {
20465
20471
  const { email: email3 } = resolvedOptions;
@@ -20495,16 +20501,12 @@ async function performBuildCheckoutUrl(resolvedOptions) {
20495
20501
  // token here left a public path on which the server had nothing to check
20496
20502
  // the invoice against. Same optional semantics: undefined when nothing
20497
20503
  // was quoted this session.
20498
- quote_token: getLatestQuoteToken() ?? void 0
20499
- };
20500
- const createCommand = {
20501
- ...createIntent,
20502
- turnstile_token: resolvedOptions.turnstileToken?.trim() || void 0
20504
+ quote_token: resolvedOptions.quoteToken !== void 0 ? resolvedOptions.quoteToken ?? void 0 : getLatestQuoteToken() ?? void 0
20503
20505
  };
20504
20506
  const createAttempt = acquireCheckoutCreateIdempotency(checkoutRequestTarget, createIntent);
20505
20507
  const response = await post(
20506
20508
  checkoutRequestTarget.endpoint,
20507
- createCommand,
20509
+ createIntent,
20508
20510
  {
20509
20511
  retries: 0,
20510
20512
  baseUrl: checkoutRequestTarget.baseUrl,
@@ -20519,7 +20521,6 @@ async function performBuildCheckoutUrl(resolvedOptions) {
20519
20521
  clearCart();
20520
20522
  }
20521
20523
  throw new CheckoutCreateError(normalizeCheckoutFailureMessage(response.message), {
20522
- ...response.challenge ? { challenge: response.challenge } : {},
20523
20524
  ...response.code ? { code: response.code } : {},
20524
20525
  ...response.status !== void 0 ? { status: response.status } : {}
20525
20526
  });
@@ -21554,12 +21555,13 @@ var shoppex = {
21554
21555
  getCartStats,
21555
21556
  validateCartIntegrity,
21556
21557
  quoteCart,
21558
+ getLatestQuoteToken,
21557
21559
  resolveCartLineId,
21558
21560
  // Checkout
21559
21561
  checkout,
21560
21562
  buildCheckoutUrl,
21561
21563
  buildCheckoutUrlSync,
21562
- mountCheckoutChallenge,
21564
+ getRequestedCheckoutCurrency,
21563
21565
  // Affiliates
21564
21566
  captureAffiliateFromUrl,
21565
21567
  validateAffiliateCode,
@@ -21661,6 +21663,7 @@ if (typeof window !== "undefined") {
21661
21663
  ApiError,
21662
21664
  CATALOG_UNIT_PRICE_DECIMAL_PLACES,
21663
21665
  CATALOG_UNIT_PRICE_FORMAT_OPTIONS,
21666
+ CURRENCY_UNAVAILABLE_ERROR_CODE,
21664
21667
  CartError,
21665
21668
  CheckoutCreateError,
21666
21669
  NetworkError,
@@ -21708,7 +21711,6 @@ if (typeof window !== "undefined") {
21708
21711
  loyalty,
21709
21712
  me,
21710
21713
  mergeSettings,
21711
- mountCheckoutChallenge,
21712
21714
  normalizeSearchQuery,
21713
21715
  normalizeStorefrontCustomFields,
21714
21716
  order,