@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/CHANGELOG.md +12 -0
- package/README.md +11 -40
- package/dist/index.cjs +353 -351
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +43 -38
- package/dist/index.d.ts +43 -38
- package/dist/index.js +352 -350
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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 = "
|
|
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: "#
|
|
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
|
-
//
|
|
15703
|
-
//
|
|
15704
|
-
//
|
|
15705
|
-
//
|
|
15706
|
-
|
|
15707
|
-
//
|
|
15708
|
-
//
|
|
15709
|
-
//
|
|
15710
|
-
|
|
15711
|
-
|
|
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: "#
|
|
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
|
-
|
|
15739
|
-
|
|
15740
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
18574
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|