@asteby/storefront 0.5.0 → 0.6.0
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/README.md +40 -0
- package/dist/chunk-HBSB7XS2.js +220 -0
- package/dist/chunk-HBSB7XS2.js.map +1 -0
- package/dist/index.cjs +705 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +268 -3
- package/dist/index.d.ts +268 -3
- package/dist/index.js +469 -3
- package/dist/index.js.map +1 -1
- package/dist/mock.cjs +233 -12
- package/dist/mock.cjs.map +1 -1
- package/dist/mock.d.cts +18 -1
- package/dist/mock.d.ts +18 -1
- package/dist/mock.js +214 -12
- package/dist/mock.js.map +1 -1
- package/dist/mockup.d.cts +1 -1
- package/dist/mockup.d.ts +1 -1
- package/dist/{types-D300wdPF.d.cts → types-CSMuPSQA.d.cts} +321 -1
- package/dist/{types-BmC-RDo8.d.ts → types-iGGZ9EiC.d.ts} +321 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -6,14 +6,26 @@ var StorefrontError = class extends Error {
|
|
|
6
6
|
status;
|
|
7
7
|
code;
|
|
8
8
|
field;
|
|
9
|
+
details;
|
|
9
10
|
url;
|
|
10
11
|
constructor(init) {
|
|
11
12
|
super(init.message, init.cause === void 0 ? void 0 : { cause: init.cause });
|
|
12
13
|
this.status = init.status;
|
|
13
14
|
this.code = init.code;
|
|
14
15
|
this.field = init.field;
|
|
16
|
+
this.details = init.details;
|
|
15
17
|
this.url = init.url;
|
|
16
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Affected lines of a `409 insufficient_stock` (empty for any other error or a
|
|
21
|
+
* server older than contract 1.1). The cart is untouched: show the lines and
|
|
22
|
+
* let the buyer adjust or remove them.
|
|
23
|
+
*/
|
|
24
|
+
get stockLines() {
|
|
25
|
+
const lines = this.details?.lines;
|
|
26
|
+
if (this.code !== "insufficient_stock" || !Array.isArray(lines)) return [];
|
|
27
|
+
return lines.filter((l) => typeof l === "object" && l !== null && typeof l.sku === "string");
|
|
28
|
+
}
|
|
17
29
|
toJSON() {
|
|
18
30
|
return { name: this.name, status: this.status, code: this.code, message: this.message, field: this.field };
|
|
19
31
|
}
|
|
@@ -50,7 +62,7 @@ function formatMoney(money, locale, options = {}) {
|
|
|
50
62
|
const whole = Number.isInteger(value);
|
|
51
63
|
const digits = options.withoutTrailingZeros && whole ? 0 : void 0;
|
|
52
64
|
try {
|
|
53
|
-
const
|
|
65
|
+
const fmt2 = new Intl.NumberFormat(locale, {
|
|
54
66
|
...options.withoutCurrency ? { style: "decimal", minimumFractionDigits: digits ?? 2, maximumFractionDigits: digits ?? 2 } : {
|
|
55
67
|
style: "currency",
|
|
56
68
|
currency: money.currency_code,
|
|
@@ -58,7 +70,7 @@ function formatMoney(money, locale, options = {}) {
|
|
|
58
70
|
...digits === 0 ? { minimumFractionDigits: 0, maximumFractionDigits: 0 } : {}
|
|
59
71
|
}
|
|
60
72
|
});
|
|
61
|
-
return
|
|
73
|
+
return fmt2.format(value);
|
|
62
74
|
} catch {
|
|
63
75
|
const amount = digits === 0 ? String(value) : value.toFixed(2);
|
|
64
76
|
return options.withoutCurrency ? amount : `${amount} ${money.currency_code}`;
|
|
@@ -171,6 +183,7 @@ async function readError(res, url) {
|
|
|
171
183
|
code: e?.code ?? defaultCodeForStatus(res.status),
|
|
172
184
|
message: e?.message ?? `Storefront API responded ${res.status}${res.statusText ? " " + res.statusText : ""}`,
|
|
173
185
|
...e?.field ? { field: e.field } : {},
|
|
186
|
+
...e?.details !== void 0 ? { details: e.details } : {},
|
|
174
187
|
url
|
|
175
188
|
});
|
|
176
189
|
}
|
|
@@ -302,6 +315,13 @@ function createStorefrontClient(config) {
|
|
|
302
315
|
/** Stock for up to 250 SKUs. */
|
|
303
316
|
get: (skus, options) => get("/storefront/stock", { sku: skus }, options)
|
|
304
317
|
},
|
|
318
|
+
restock: {
|
|
319
|
+
/**
|
|
320
|
+
* "Notify me when it is back". Idempotent per contact + SKU; always answers `202 pending`.
|
|
321
|
+
* Only offered when `availability.notify_available` is true. Needs `customer:write`.
|
|
322
|
+
*/
|
|
323
|
+
subscribe: (input, options) => write("POST", "/storefront/restock-subscriptions", input, options)
|
|
324
|
+
},
|
|
305
325
|
carts: {
|
|
306
326
|
create: (input = {}, options) => write("POST", "/storefront/carts", input, options),
|
|
307
327
|
/** `null` when the cart does not exist (e.g. expired cookie). */
|
|
@@ -317,13 +337,37 @@ function createStorefrontClient(config) {
|
|
|
317
337
|
/** Payment providers active for the cart's market; send the chosen `key` as `payment_method`. */
|
|
318
338
|
paymentMethods: (id, options) => get(cartPath(id, "/payment-methods"), void 0, options),
|
|
319
339
|
setDelivery: (id, delivery, options) => write("PUT", cartPath(id, "/delivery"), delivery, options),
|
|
340
|
+
/** Order note (up to 1000 characters). `null` or empty clears it. */
|
|
341
|
+
setNote: (id, note, options) => write("PUT", cartPath(id, "/note"), { note }, options),
|
|
342
|
+
/**
|
|
343
|
+
* Cross-sell suggestions: only sellable variants, with `unlocks` when adding one reaches the next
|
|
344
|
+
* discount tier. `first` 1-8 (default 4). `private, no-store`; ask on demand, not on every render.
|
|
345
|
+
*/
|
|
346
|
+
recommendations: (id, params = {}, options) => get(cartPath(id, "/recommendations"), params, options),
|
|
320
347
|
/** Reserves stock and creates the order. Sends `Idempotency-Key` (auto-generated if omitted). */
|
|
321
348
|
checkout: (id, input, options = {}) => write("POST", cartPath(id, "/checkout"), input, {
|
|
322
349
|
...options,
|
|
323
350
|
idempotencyKey: options.idempotencyKey ?? generateIdempotencyKey()
|
|
324
351
|
})
|
|
325
352
|
},
|
|
353
|
+
promotions: {
|
|
354
|
+
/** Public promotions and the entry pop-up announcement for the request's market (cacheable). */
|
|
355
|
+
list: (options) => get("/storefront/promotions", void 0, options)
|
|
356
|
+
},
|
|
326
357
|
checkouts: {
|
|
358
|
+
/** What to mount for an embedded checkout (public key, options, amount) and whether it can still be paid. Never cache. */
|
|
359
|
+
paymentConfig: (id, options) => getOrNull(`/storefront/checkouts/${enc(id)}/payment-config`, void 0, options),
|
|
360
|
+
/**
|
|
361
|
+
* Pays a checkout in the page. The browser brings only what the provider SDK returned (`provider_data`);
|
|
362
|
+
* the amount is the server's. `idempotencyKey` is REQUIRED (use `paymentIdempotencyKey(checkoutId, attempt)`):
|
|
363
|
+
* repeating a call with the same key never charges twice. Needs a server token (`checkout:write`).
|
|
364
|
+
*/
|
|
365
|
+
pay: (id, input, options) => {
|
|
366
|
+
if (!options || typeof options.idempotencyKey !== "string" || !options.idempotencyKey.trim()) {
|
|
367
|
+
throw new TypeError("checkouts.pay: idempotencyKey is required");
|
|
368
|
+
}
|
|
369
|
+
return write("POST", `/storefront/checkouts/${enc(id)}/payment`, input, options);
|
|
370
|
+
},
|
|
327
371
|
/**
|
|
328
372
|
* Reads a placed checkout (same shape as the `carts.checkout` result). `order.number` is `null` until OPS
|
|
329
373
|
* has created the order (a few hundred ms after checkout); poll once or twice and, if it is still `null`,
|
|
@@ -364,18 +408,677 @@ function createStorefrontClient(config) {
|
|
|
364
408
|
return client;
|
|
365
409
|
}
|
|
366
410
|
|
|
411
|
+
// src/availability.ts
|
|
412
|
+
var ES = {
|
|
413
|
+
"status.in_stock": "Disponible",
|
|
414
|
+
"status.low_stock": "Pocas piezas",
|
|
415
|
+
"status.low_stock_n": "Quedan {n}",
|
|
416
|
+
"status.out_of_stock": "Agotado",
|
|
417
|
+
"status.backorder": "Bajo pedido",
|
|
418
|
+
"status.preorder": "Preventa",
|
|
419
|
+
"sub.backorder_eta": "Se env\xEDa a partir del {date}",
|
|
420
|
+
"sub.preorder_date": "Sale a la venta el {date}",
|
|
421
|
+
"cta.add": "Agregar al carrito",
|
|
422
|
+
"cta.out_of_stock": "Agotado",
|
|
423
|
+
"cta.backorder": "Pedir bajo pedido",
|
|
424
|
+
"cta.preorder": "Apartar en preventa",
|
|
425
|
+
"sr.in_stock": "Disponible",
|
|
426
|
+
"sr.low_stock": "Quedan pocas piezas",
|
|
427
|
+
"sr.low_stock_n": "Quedan solo {n} piezas",
|
|
428
|
+
"sr.out_of_stock": "Producto agotado",
|
|
429
|
+
"sr.backorder": "Disponible bajo pedido",
|
|
430
|
+
"sr.preorder": "Disponible en preventa",
|
|
431
|
+
"option.sold_out": "agotado",
|
|
432
|
+
"option.backorder": "bajo pedido",
|
|
433
|
+
"option.preorder": "preventa",
|
|
434
|
+
max_per_order: "M\xE1ximo {n} por pedido",
|
|
435
|
+
"notify.title": "Av\xEDsame cuando vuelva",
|
|
436
|
+
"notify.email": "Correo electr\xF3nico",
|
|
437
|
+
"notify.phone": "WhatsApp o tel\xE9fono",
|
|
438
|
+
"notify.consent": "Acepto recibir un aviso cuando vuelva a estar disponible",
|
|
439
|
+
"notify.submit": "Av\xEDsame",
|
|
440
|
+
"notify.done": "Listo. Te avisaremos cuando vuelva a estar disponible.",
|
|
441
|
+
"notify.error": "No pudimos guardar tu aviso. Revisa tus datos e int\xE9ntalo de nuevo.",
|
|
442
|
+
"problem.out_of_stock": "Ya no hay existencias",
|
|
443
|
+
"problem.insufficient_stock": "No hay suficientes piezas para esta cantidad",
|
|
444
|
+
"problem.insufficient_stock_n": "Solo puedes llevar {n}",
|
|
445
|
+
"problem.adjust": "Ajustar a {n}",
|
|
446
|
+
"problem.remove": "Quitar",
|
|
447
|
+
"conflict.title": "Algunos productos cambiaron de existencias",
|
|
448
|
+
"conflict.body": "Revisa las l\xEDneas marcadas. Tu carrito y tus datos se conservaron; ajusta o quita lo que ya no est\xE1 disponible y contin\xFAa.",
|
|
449
|
+
"conflict.dismiss": "Entendido"
|
|
450
|
+
};
|
|
451
|
+
var EN = {
|
|
452
|
+
"status.in_stock": "In stock",
|
|
453
|
+
"status.low_stock": "Low stock",
|
|
454
|
+
"status.low_stock_n": "Only {n} left",
|
|
455
|
+
"status.out_of_stock": "Sold out",
|
|
456
|
+
"status.backorder": "Made to order",
|
|
457
|
+
"status.preorder": "Pre-order",
|
|
458
|
+
"sub.backorder_eta": "Ships from {date}",
|
|
459
|
+
"sub.preorder_date": "Available on {date}",
|
|
460
|
+
"cta.add": "Add to cart",
|
|
461
|
+
"cta.out_of_stock": "Sold out",
|
|
462
|
+
"cta.backorder": "Order now, ships later",
|
|
463
|
+
"cta.preorder": "Pre-order",
|
|
464
|
+
"sr.in_stock": "In stock",
|
|
465
|
+
"sr.low_stock": "Low stock",
|
|
466
|
+
"sr.low_stock_n": "Only {n} left in stock",
|
|
467
|
+
"sr.out_of_stock": "Sold out",
|
|
468
|
+
"sr.backorder": "Available to order, ships later",
|
|
469
|
+
"sr.preorder": "Available for pre-order",
|
|
470
|
+
"option.sold_out": "sold out",
|
|
471
|
+
"option.backorder": "made to order",
|
|
472
|
+
"option.preorder": "pre-order",
|
|
473
|
+
max_per_order: "Maximum {n} per order",
|
|
474
|
+
"notify.title": "Notify me when it is back",
|
|
475
|
+
"notify.email": "Email",
|
|
476
|
+
"notify.phone": "WhatsApp or phone",
|
|
477
|
+
"notify.consent": "I agree to receive a notice when it is back in stock",
|
|
478
|
+
"notify.submit": "Notify me",
|
|
479
|
+
"notify.done": "Done. We will let you know when it is back in stock.",
|
|
480
|
+
"notify.error": "We could not save your request. Check your details and try again.",
|
|
481
|
+
"problem.out_of_stock": "No longer in stock",
|
|
482
|
+
"problem.insufficient_stock": "Not enough stock for this quantity",
|
|
483
|
+
"problem.insufficient_stock_n": "You can take up to {n}",
|
|
484
|
+
"problem.adjust": "Adjust to {n}",
|
|
485
|
+
"problem.remove": "Remove",
|
|
486
|
+
"conflict.title": "Some items changed stock",
|
|
487
|
+
"conflict.body": "Review the marked lines. Your cart and your details were kept; adjust or remove what is no longer available and continue.",
|
|
488
|
+
"conflict.dismiss": "Got it"
|
|
489
|
+
};
|
|
490
|
+
var CATALOG = { es: ES, en: EN };
|
|
491
|
+
function availabilityLocale(locale) {
|
|
492
|
+
return (locale ?? "").toLowerCase().startsWith("en") ? "en" : "es";
|
|
493
|
+
}
|
|
494
|
+
function availabilityMessages(locale, overrides) {
|
|
495
|
+
const base = CATALOG[availabilityLocale(locale)];
|
|
496
|
+
if (!overrides) return base;
|
|
497
|
+
const out = { ...base };
|
|
498
|
+
for (const [k, v] of Object.entries(overrides)) if (typeof v === "string" && k in base) out[k] = v;
|
|
499
|
+
return out;
|
|
500
|
+
}
|
|
501
|
+
function formatAvailabilityMessage(template, values = {}) {
|
|
502
|
+
return template.replace(/\{(\w+)\}/g, (m, k) => k in values ? String(values[k]) : m);
|
|
503
|
+
}
|
|
504
|
+
function formatDate(value, locale) {
|
|
505
|
+
if (!value) return null;
|
|
506
|
+
const d = /^\d{4}-\d{2}-\d{2}$/.test(value) ? /* @__PURE__ */ new Date(`${value}T00:00:00Z`) : new Date(value);
|
|
507
|
+
if (Number.isNaN(d.getTime())) return null;
|
|
508
|
+
try {
|
|
509
|
+
return new Intl.DateTimeFormat(locale || "es", { dateStyle: "long", timeZone: "UTC" }).format(d);
|
|
510
|
+
} catch {
|
|
511
|
+
return value.slice(0, 10);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
function resolveAvailability(source, options = {}) {
|
|
515
|
+
const a = source.availability;
|
|
516
|
+
if (a && typeof a === "object" && typeof a.status === "string") return a;
|
|
517
|
+
const qty = typeof source.quantity_available === "number" ? source.quantity_available : null;
|
|
518
|
+
const sellable = source.available_for_sale && (qty === null || qty > 0);
|
|
519
|
+
const threshold = options.lowStockThreshold;
|
|
520
|
+
const low = sellable && qty !== null && typeof threshold === "number" && threshold > 0 && qty <= threshold;
|
|
521
|
+
const status = !sellable ? "out_of_stock" : low ? "low_stock" : "in_stock";
|
|
522
|
+
return {
|
|
523
|
+
status,
|
|
524
|
+
quantity: sellable ? qty : null,
|
|
525
|
+
max_per_order: sellable ? qty : null,
|
|
526
|
+
restock_eta: null,
|
|
527
|
+
preorder_release_at: null,
|
|
528
|
+
// Old servers have no subscription route: never offer a form that cannot work.
|
|
529
|
+
notify_available: false
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
var TONES = {
|
|
533
|
+
in_stock: "success",
|
|
534
|
+
low_stock: "warning",
|
|
535
|
+
out_of_stock: "danger",
|
|
536
|
+
backorder: "info",
|
|
537
|
+
preorder: "info"
|
|
538
|
+
};
|
|
539
|
+
function availabilityView(availability, locale, options = {}) {
|
|
540
|
+
const m = availabilityMessages(locale, options.messages);
|
|
541
|
+
const known = ["in_stock", "low_stock", "out_of_stock", "backorder", "preorder"].includes(availability.status);
|
|
542
|
+
const status = known ? availability.status : "out_of_stock";
|
|
543
|
+
const qty = typeof availability.quantity === "number" && availability.quantity > 0 ? availability.quantity : null;
|
|
544
|
+
const f = formatAvailabilityMessage;
|
|
545
|
+
let label;
|
|
546
|
+
let sr;
|
|
547
|
+
let cta;
|
|
548
|
+
let sublabel = null;
|
|
549
|
+
let enabled = true;
|
|
550
|
+
switch (status) {
|
|
551
|
+
case "in_stock":
|
|
552
|
+
label = m["status.in_stock"];
|
|
553
|
+
sr = m["sr.in_stock"];
|
|
554
|
+
cta = m["cta.add"];
|
|
555
|
+
break;
|
|
556
|
+
case "low_stock":
|
|
557
|
+
label = qty !== null ? f(m["status.low_stock_n"], { n: qty }) : m["status.low_stock"];
|
|
558
|
+
sr = qty !== null ? f(m["sr.low_stock_n"], { n: qty }) : m["sr.low_stock"];
|
|
559
|
+
cta = m["cta.add"];
|
|
560
|
+
break;
|
|
561
|
+
case "backorder": {
|
|
562
|
+
label = m["status.backorder"];
|
|
563
|
+
sr = m["sr.backorder"];
|
|
564
|
+
cta = m["cta.backorder"];
|
|
565
|
+
const date = formatDate(availability.restock_eta, locale);
|
|
566
|
+
if (date) sublabel = f(m["sub.backorder_eta"], { date });
|
|
567
|
+
break;
|
|
568
|
+
}
|
|
569
|
+
case "preorder": {
|
|
570
|
+
label = m["status.preorder"];
|
|
571
|
+
sr = m["sr.preorder"];
|
|
572
|
+
cta = m["cta.preorder"];
|
|
573
|
+
const date = formatDate(availability.preorder_release_at ?? availability.restock_eta, locale);
|
|
574
|
+
if (date) sublabel = f(m["sub.preorder_date"], { date });
|
|
575
|
+
break;
|
|
576
|
+
}
|
|
577
|
+
default:
|
|
578
|
+
label = m["status.out_of_stock"];
|
|
579
|
+
sr = m["sr.out_of_stock"];
|
|
580
|
+
cta = m["cta.out_of_stock"];
|
|
581
|
+
enabled = false;
|
|
582
|
+
}
|
|
583
|
+
return {
|
|
584
|
+
status,
|
|
585
|
+
tone: TONES[status],
|
|
586
|
+
label,
|
|
587
|
+
sublabel,
|
|
588
|
+
ctaLabel: cta,
|
|
589
|
+
ctaEnabled: enabled,
|
|
590
|
+
showNotify: status === "out_of_stock" && availability.notify_available === true,
|
|
591
|
+
srText: sublabel ? `${sr}. ${sublabel}` : sr,
|
|
592
|
+
maxPerOrder: typeof availability.max_per_order === "number" && availability.max_per_order > 0 ? availability.max_per_order : null,
|
|
593
|
+
showOnCard: status !== "in_stock"
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
function cartLineProblemView(problem, locale, options = {}) {
|
|
597
|
+
const m = availabilityMessages(locale, options.messages);
|
|
598
|
+
const code = "code" in problem ? problem.code : problem.status === "out_of_stock" ? "out_of_stock" : "insufficient_stock";
|
|
599
|
+
const max = typeof problem.max_quantity === "number" && problem.max_quantity >= 0 ? problem.max_quantity : null;
|
|
600
|
+
const canAdjust = max !== null && max > 0 && (options.lineQuantity === void 0 || max !== options.lineQuantity);
|
|
601
|
+
const message = code === "out_of_stock" || max === 0 ? m["problem.out_of_stock"] : max !== null ? formatAvailabilityMessage(m["problem.insufficient_stock_n"], { n: max }) : m["problem.insufficient_stock"];
|
|
602
|
+
return {
|
|
603
|
+
code,
|
|
604
|
+
message,
|
|
605
|
+
adjustTo: canAdjust ? max : null,
|
|
606
|
+
adjustLabel: canAdjust ? formatAvailabilityMessage(m["problem.adjust"], { n: max }) : null,
|
|
607
|
+
removeLabel: m["problem.remove"]
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
var SCHEMA_AVAILABILITY = {
|
|
611
|
+
in_stock: "https://schema.org/InStock",
|
|
612
|
+
low_stock: "https://schema.org/LimitedAvailability",
|
|
613
|
+
out_of_stock: "https://schema.org/OutOfStock",
|
|
614
|
+
backorder: "https://schema.org/BackOrder",
|
|
615
|
+
preorder: "https://schema.org/PreOrder"
|
|
616
|
+
};
|
|
617
|
+
function schemaAvailability(status) {
|
|
618
|
+
return SCHEMA_AVAILABILITY[status] ?? SCHEMA_AVAILABILITY.out_of_stock;
|
|
619
|
+
}
|
|
620
|
+
function productAvailability(product, options = {}) {
|
|
621
|
+
if (product.availability) return product.availability;
|
|
622
|
+
const all = product.variants.map((v) => resolveAvailability(v, options));
|
|
623
|
+
const rank = ["in_stock", "low_stock", "backorder", "preorder", "out_of_stock"];
|
|
624
|
+
const best = all.slice().sort((a, b) => rank.indexOf(a.status) - rank.indexOf(b.status))[0];
|
|
625
|
+
return best ?? resolveAvailability(product, options);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// src/payments.ts
|
|
629
|
+
var ES2 = {
|
|
630
|
+
"decline.insufficient_funds": "Tu tarjeta no tiene saldo suficiente. Prueba con otra tarjeta o paga en efectivo.",
|
|
631
|
+
"decline.invalid_cvc": "El c\xF3digo de seguridad no es correcto. Rev\xEDsalo e intenta de nuevo.",
|
|
632
|
+
"decline.invalid_expiry": "La fecha de vencimiento no es correcta.",
|
|
633
|
+
"decline.invalid_card_data": "Revisa los datos de tu tarjeta.",
|
|
634
|
+
"decline.call_issuer": "Tu banco necesita que autorices este pago. Llama al n\xFAmero al reverso de tu tarjeta y vuelve a intentarlo.",
|
|
635
|
+
"decline.card_disabled": "Tu tarjeta no est\xE1 activa. Act\xEDvala con tu banco o usa otra.",
|
|
636
|
+
"decline.duplicate": "Ya hiciste un pago igual hace un momento. Revisa tu correo antes de volver a pagar.",
|
|
637
|
+
"decline.max_attempts": "Llegaste al l\xEDmite de intentos con esta tarjeta. Usa otra o paga en efectivo.",
|
|
638
|
+
"decline.authentication_failed": "Tu banco no pudo verificar el pago. Intenta de nuevo o usa otra tarjeta.",
|
|
639
|
+
"decline.declined": "Tu banco rechaz\xF3 el pago. Usa otra tarjeta o paga en efectivo.",
|
|
640
|
+
"state.loading": "Cargando pago seguro\u2026",
|
|
641
|
+
"state.processing": "Procesando pago\u2026",
|
|
642
|
+
"state.approved": "\xA1Pago aprobado!",
|
|
643
|
+
"state.in_review": "Tu pago est\xE1 en revisi\xF3n. Te avisamos por correo en m\xE1ximo 2 d\xEDas h\xE1biles. No vuelvas a pagar.",
|
|
644
|
+
"state.voucher": "Tu ficha de pago est\xE1 lista. El pedido se confirma cuando pagues.",
|
|
645
|
+
"state.challenge": "Tu banco necesita verificar el pago. Termina la verificaci\xF3n para continuar.",
|
|
646
|
+
"state.expired": "La ficha venci\xF3. Vuelve a armar tu carrito para pagar.",
|
|
647
|
+
"state.cancelled": "El pago se cancel\xF3. Puedes intentarlo de nuevo.",
|
|
648
|
+
"state.redirect": "Te llevamos a la p\xE1gina de pago\u2026",
|
|
649
|
+
"state.fallback": "No pudimos cargar el formulario seguro de tarjeta. Puedes pagar con tarjeta en el sitio del proveedor y regresar aqu\xED.",
|
|
650
|
+
"error.network": "No pudimos conectar. Revisa tu conexi\xF3n e intenta de nuevo; no se te cobrar\xE1 dos veces.",
|
|
651
|
+
"error.card_data": "Revisa los datos de tu tarjeta.",
|
|
652
|
+
"error.price_mismatch": "El total cambi\xF3. Revisa el nuevo total y vuelve a pagar.",
|
|
653
|
+
"error.payment_in_progress": "Ya hay un pago en proceso para este pedido. No vuelvas a pagar; revisa tu correo.",
|
|
654
|
+
"error.expired": "Tu apartado venci\xF3. Vuelve a armar tu carrito.",
|
|
655
|
+
"error.too_many_attempts": "Por seguridad no podemos intentar con m\xE1s tarjetas. Paga en efectivo o escr\xEDbenos.",
|
|
656
|
+
"error.generic": "No pudimos procesar tu pago. Intenta de nuevo.",
|
|
657
|
+
"action.retry": "Reintentar",
|
|
658
|
+
"action.try_again": "Intentar de nuevo",
|
|
659
|
+
"action.pay": "Pagar",
|
|
660
|
+
"action.cancel": "Cancelar",
|
|
661
|
+
"voucher.title": "Ficha de pago",
|
|
662
|
+
"voucher.point": "Punto de pago",
|
|
663
|
+
"voucher.reference": "Referencia",
|
|
664
|
+
"voucher.amount": "Monto exacto",
|
|
665
|
+
"voucher.expires": "Vence el {date}",
|
|
666
|
+
"voucher.clabe": "CLABE",
|
|
667
|
+
"voucher.barcode": "C\xF3digo de barras",
|
|
668
|
+
"voucher.open": "Ver o imprimir ficha",
|
|
669
|
+
"voucher.email": "Tambi\xE9n te la enviamos a tu correo.",
|
|
670
|
+
"form.label": "Datos de tu tarjeta",
|
|
671
|
+
"challenge.title": "Verificaci\xF3n de tu banco"
|
|
672
|
+
};
|
|
673
|
+
var EN2 = {
|
|
674
|
+
"decline.insufficient_funds": "Your card has insufficient funds. Try another card or pay in cash.",
|
|
675
|
+
"decline.invalid_cvc": "The security code is not correct. Check it and try again.",
|
|
676
|
+
"decline.invalid_expiry": "The expiry date is not correct.",
|
|
677
|
+
"decline.invalid_card_data": "Check your card details.",
|
|
678
|
+
"decline.call_issuer": "Your bank needs you to authorize this payment. Call the number on the back of your card and try again.",
|
|
679
|
+
"decline.card_disabled": "Your card is not active. Activate it with your bank or use another one.",
|
|
680
|
+
"decline.duplicate": "You already made the same payment a moment ago. Check your email before paying again.",
|
|
681
|
+
"decline.max_attempts": "You reached the attempt limit for this card. Use another card or pay in cash.",
|
|
682
|
+
"decline.authentication_failed": "Your bank could not verify the payment. Try again or use another card.",
|
|
683
|
+
"decline.declined": "Your bank declined the payment. Use another card or pay in cash.",
|
|
684
|
+
"state.loading": "Loading secure payment\u2026",
|
|
685
|
+
"state.processing": "Processing payment\u2026",
|
|
686
|
+
"state.approved": "Payment approved!",
|
|
687
|
+
"state.in_review": "Your payment is under review. We will email you within 2 business days. Do not pay again.",
|
|
688
|
+
"state.voucher": "Your payment voucher is ready. The order is confirmed once you pay.",
|
|
689
|
+
"state.challenge": "Your bank needs to verify the payment. Finish the verification to continue.",
|
|
690
|
+
"state.expired": "The voucher expired. Rebuild your cart to pay.",
|
|
691
|
+
"state.cancelled": "The payment was cancelled. You can try again.",
|
|
692
|
+
"state.redirect": "Taking you to the payment page\u2026",
|
|
693
|
+
"state.fallback": "We could not load the secure card form. You can pay by card on the provider site and come back here.",
|
|
694
|
+
"error.network": "We could not connect. Check your connection and try again; you will not be charged twice.",
|
|
695
|
+
"error.card_data": "Check your card details.",
|
|
696
|
+
"error.price_mismatch": "The total changed. Review the new total and pay again.",
|
|
697
|
+
"error.payment_in_progress": "A payment is already in progress for this order. Do not pay again; check your email.",
|
|
698
|
+
"error.expired": "Your reservation expired. Rebuild your cart.",
|
|
699
|
+
"error.too_many_attempts": "For security we cannot try more cards. Pay in cash or contact us.",
|
|
700
|
+
"error.generic": "We could not process your payment. Please try again.",
|
|
701
|
+
"action.retry": "Retry",
|
|
702
|
+
"action.try_again": "Try again",
|
|
703
|
+
"action.pay": "Pay",
|
|
704
|
+
"action.cancel": "Cancel",
|
|
705
|
+
"voucher.title": "Payment voucher",
|
|
706
|
+
"voucher.point": "Payment point",
|
|
707
|
+
"voucher.reference": "Reference",
|
|
708
|
+
"voucher.amount": "Exact amount",
|
|
709
|
+
"voucher.expires": "Expires on {date}",
|
|
710
|
+
"voucher.clabe": "CLABE",
|
|
711
|
+
"voucher.barcode": "Barcode",
|
|
712
|
+
"voucher.open": "View or print voucher",
|
|
713
|
+
"voucher.email": "We also emailed it to you.",
|
|
714
|
+
"form.label": "Your card details",
|
|
715
|
+
"challenge.title": "Your bank verification"
|
|
716
|
+
};
|
|
717
|
+
var CATALOG2 = { es: ES2, en: EN2 };
|
|
718
|
+
function paymentMessages(locale, overrides) {
|
|
719
|
+
const base = CATALOG2[availabilityLocale(locale)];
|
|
720
|
+
if (!overrides) return base;
|
|
721
|
+
const out = { ...base };
|
|
722
|
+
for (const [k, v] of Object.entries(overrides)) if (typeof v === "string" && k in base) out[k] = v;
|
|
723
|
+
return out;
|
|
724
|
+
}
|
|
725
|
+
var DECLINE_CODES = /* @__PURE__ */ new Set([
|
|
726
|
+
"insufficient_funds",
|
|
727
|
+
"invalid_cvc",
|
|
728
|
+
"invalid_expiry",
|
|
729
|
+
"invalid_card_data",
|
|
730
|
+
"call_issuer",
|
|
731
|
+
"card_disabled",
|
|
732
|
+
"duplicate",
|
|
733
|
+
"max_attempts",
|
|
734
|
+
"authentication_failed",
|
|
735
|
+
"declined"
|
|
736
|
+
]);
|
|
737
|
+
function declineMessageKey(code) {
|
|
738
|
+
return `decline.${code && DECLINE_CODES.has(code) ? code : "declined"}`;
|
|
739
|
+
}
|
|
740
|
+
function declineMessage(code, locale, overrides) {
|
|
741
|
+
return paymentMessages(locale, overrides)[declineMessageKey(code)];
|
|
742
|
+
}
|
|
743
|
+
function safeHttpsUrl(value) {
|
|
744
|
+
if (typeof value !== "string" || value.length > 2048) return null;
|
|
745
|
+
try {
|
|
746
|
+
const u = new URL(value);
|
|
747
|
+
if (u.protocol !== "https:" || u.username || u.password) return null;
|
|
748
|
+
return u.toString();
|
|
749
|
+
} catch {
|
|
750
|
+
return null;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
var NEXT_STEP = {
|
|
754
|
+
insufficient_funds: "use_other_card",
|
|
755
|
+
invalid_cvc: "fix_card_data",
|
|
756
|
+
invalid_expiry: "fix_card_data",
|
|
757
|
+
invalid_card_data: "fix_card_data",
|
|
758
|
+
call_issuer: "contact_issuer",
|
|
759
|
+
card_disabled: "use_other_card",
|
|
760
|
+
duplicate: "check_email",
|
|
761
|
+
max_attempts: "use_other_card",
|
|
762
|
+
authentication_failed: "retry",
|
|
763
|
+
declined: "use_other_card"
|
|
764
|
+
};
|
|
765
|
+
function voucherMoney(v) {
|
|
766
|
+
const a = v.amount;
|
|
767
|
+
if (a && typeof a === "object" && typeof a.amount === "string") return a;
|
|
768
|
+
const text = typeof a === "number" ? String(a) : typeof a === "string" ? a : "";
|
|
769
|
+
if (!text || !/^\d+(\.\d+)?$/.test(text) || !v.currency) return null;
|
|
770
|
+
return { amount: text.includes(".") ? text : `${text}.00`, currency_code: v.currency };
|
|
771
|
+
}
|
|
772
|
+
function paymentOutcome(result, locale, overrides) {
|
|
773
|
+
const m = paymentMessages(locale, overrides);
|
|
774
|
+
const p = result.payment;
|
|
775
|
+
const action = p.action ?? null;
|
|
776
|
+
const base = {
|
|
777
|
+
decline_code: null,
|
|
778
|
+
retryable: false,
|
|
779
|
+
redirect_url: null,
|
|
780
|
+
challenge: null,
|
|
781
|
+
voucher: null,
|
|
782
|
+
attempts_left: typeof result.attempts_left === "number" ? result.attempts_left : null,
|
|
783
|
+
next_step: p.next_step ?? null
|
|
784
|
+
};
|
|
785
|
+
const out = (kind, message, extra = {}) => ({ ...base, kind, message, ...extra });
|
|
786
|
+
if (p.status === "paid") return out("approved", m["state.approved"]);
|
|
787
|
+
if (p.status === "failed") {
|
|
788
|
+
const code = p.decline_code ?? "declined";
|
|
789
|
+
const known = DECLINE_CODES.has(code) ? code : "declined";
|
|
790
|
+
return out("rejected", m[declineMessageKey(code)], {
|
|
791
|
+
decline_code: known,
|
|
792
|
+
next_step: p.next_step ?? NEXT_STEP[known],
|
|
793
|
+
retryable: p.retryable !== false && base.attempts_left !== 0
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
if (p.status === "expired") return out("expired", m["state.expired"], { next_step: "rebuild_cart" });
|
|
797
|
+
if (p.status === "cancelled") return out("cancelled", m["state.cancelled"], { retryable: true, next_step: "rebuild_cart" });
|
|
798
|
+
if (action?.type === "redirect") {
|
|
799
|
+
const url = safeHttpsUrl(action.redirect_url);
|
|
800
|
+
if (url) return out("redirect", m["state.redirect"], { redirect_url: url });
|
|
801
|
+
}
|
|
802
|
+
if (p.status === "requires_action" || p.status === "pending") {
|
|
803
|
+
if (action?.type === "voucher" && action.voucher) {
|
|
804
|
+
const { amount: _a, currency: _c, voucher_url, ...rest } = action.voucher;
|
|
805
|
+
return out("voucher", m["state.voucher"], {
|
|
806
|
+
next_step: "pay_cash",
|
|
807
|
+
voucher: { ...rest, voucher_url: safeHttpsUrl(voucher_url), amount: voucherMoney(action.voucher) }
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
if (action?.type === "challenge" && action.challenge) {
|
|
811
|
+
return out("challenge", m["state.challenge"], {
|
|
812
|
+
challenge: { url: safeHttpsUrl(action.challenge.url), creq: typeof action.challenge.creq === "string" ? action.challenge.creq : null }
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
return out("in_review", m["state.in_review"]);
|
|
817
|
+
}
|
|
818
|
+
function paymentErrorView(err, locale, overrides) {
|
|
819
|
+
const m = paymentMessages(locale, overrides);
|
|
820
|
+
const e = err ?? {};
|
|
821
|
+
const status = typeof e.status === "number" ? e.status : 0;
|
|
822
|
+
const code = typeof e.code === "string" ? e.code : "";
|
|
823
|
+
if (code === "price_mismatch") {
|
|
824
|
+
return { kind: "price_mismatch", message: m["error.price_mismatch"], retrySameAttempt: false, canRetry: true };
|
|
825
|
+
}
|
|
826
|
+
if (status === 409 && code === "expired") return { kind: "expired", message: m["error.expired"], retrySameAttempt: false, canRetry: false };
|
|
827
|
+
if (status === 409 && code === "payment_in_progress") {
|
|
828
|
+
return { kind: "payment_in_progress", message: m["error.payment_in_progress"], retrySameAttempt: false, canRetry: false };
|
|
829
|
+
}
|
|
830
|
+
if (status === 409) return { kind: "generic", message: m["error.generic"], retrySameAttempt: false, canRetry: true };
|
|
831
|
+
if (status === 429 || code === "too_many_attempts") {
|
|
832
|
+
return { kind: "too_many_attempts", message: m["error.too_many_attempts"], retrySameAttempt: false, canRetry: false };
|
|
833
|
+
}
|
|
834
|
+
if (status === 0 || status === 502 || status === 503 || status === 504 || code === "network" || code === "timeout") {
|
|
835
|
+
return { kind: "network", message: m["error.network"], retrySameAttempt: true, canRetry: true };
|
|
836
|
+
}
|
|
837
|
+
return { kind: "generic", message: m["error.generic"], retrySameAttempt: false, canRetry: true };
|
|
838
|
+
}
|
|
839
|
+
function formatVoucherExpiry(expiresAt, locale, overrides) {
|
|
840
|
+
if (!expiresAt) return null;
|
|
841
|
+
const d = new Date(expiresAt);
|
|
842
|
+
if (Number.isNaN(d.getTime())) return null;
|
|
843
|
+
let date;
|
|
844
|
+
try {
|
|
845
|
+
date = new Intl.DateTimeFormat(locale ?? void 0, { dateStyle: "long", timeStyle: "short" }).format(d);
|
|
846
|
+
} catch {
|
|
847
|
+
date = d.toISOString();
|
|
848
|
+
}
|
|
849
|
+
return formatAvailabilityMessage(paymentMessages(locale, overrides)["voucher.expires"], { date });
|
|
850
|
+
}
|
|
851
|
+
function paymentIdempotencyKey(checkoutId, attempt) {
|
|
852
|
+
return `pay-${checkoutId}-${attempt}`;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
// src/promotions.ts
|
|
856
|
+
var ES3 = {
|
|
857
|
+
"discount.next": "A\xF1ade {remaining} para activar tu descuento autom\xE1tico",
|
|
858
|
+
"discount.next_upgrade": "A\xF1ade {remaining} para subir a {reward}",
|
|
859
|
+
"discount.applied": "\xA1Tienes {reward} de descuento!",
|
|
860
|
+
"discount.max": "\xA1Tienes {reward} de descuento, el m\xE1ximo!",
|
|
861
|
+
"discount.suppressed": "Tu c\xF3digo de descuento ya te da un mejor precio.",
|
|
862
|
+
"discount.unlocks": "Desbloqueas {reward} de descuento",
|
|
863
|
+
"discount.remaining": "Te faltan {remaining}",
|
|
864
|
+
"shipping.progress": "A\xF1ade {remaining} para env\xEDo gratis",
|
|
865
|
+
"shipping.reached": "\xA1Felicidades! Tienes env\xEDo gratis",
|
|
866
|
+
"shipping.always": "Env\xEDo gratis en todo tu pedido",
|
|
867
|
+
"reward.free_shipping": "Env\xEDo gratis",
|
|
868
|
+
"bar.discount": "Progreso del descuento autom\xE1tico",
|
|
869
|
+
"bar.shipping": "Progreso del env\xEDo gratis",
|
|
870
|
+
"popup.close": "Cerrar",
|
|
871
|
+
"popup.valid_until": "V\xE1lido hasta el {date}",
|
|
872
|
+
"popup.limited_time": "Por tiempo limitado",
|
|
873
|
+
"popup.discount_tier": "{reward} comprando {threshold}",
|
|
874
|
+
"popup.free_shipping_min": "Env\xEDo gratis desde {threshold}",
|
|
875
|
+
"popup.free_shipping_always": "Env\xEDos gratis sin m\xEDnimo de compra",
|
|
876
|
+
"upsell.title": "Completa tu compra y desbloquea m\xE1s",
|
|
877
|
+
"upsell.subtitle": "Descuento autom\xE1tico",
|
|
878
|
+
"upsell.next_level": "Siguiente nivel",
|
|
879
|
+
"upsell.you_miss": "Te faltan {remaining}",
|
|
880
|
+
"upsell.unlocks_reward": "Desbloqueas {reward} de descuento",
|
|
881
|
+
"upsell.total_saved": "Total ahorrado",
|
|
882
|
+
"upsell.current_benefit": "Beneficio actual",
|
|
883
|
+
"upsell.status": "Estado",
|
|
884
|
+
"upsell.status_pending": "Pendiente",
|
|
885
|
+
"upsell.status_active": "Activo",
|
|
886
|
+
"upsell.next_reward": "Siguiente recompensa {reward} \xB7 {threshold}",
|
|
887
|
+
"upsell.suggestions": "Suma y desbloquea \xB7 Productos que pueden interesarte",
|
|
888
|
+
"upsell.add": "Agregar y avanzar +",
|
|
889
|
+
"upsell.adding": "Agregando\u2026",
|
|
890
|
+
"upsell.flavor": "Sabor",
|
|
891
|
+
"upsell.unlocks_badge": "Desbloquea tu {reward}",
|
|
892
|
+
"upsell.unlocked": "\xA1Desbloqueaste {reward}!",
|
|
893
|
+
"upsell.best": "Tienes el mejor descuento: {reward}",
|
|
894
|
+
"upsell.skip": "Continuar sin agregar",
|
|
895
|
+
"upsell.continue": "Continuar al pago",
|
|
896
|
+
"upsell.footer": "Tu mejor descuento se aplica autom\xE1ticamente",
|
|
897
|
+
"upsell.sold_out": "Se agot\xF3 este sabor, elige otro.",
|
|
898
|
+
"upsell.error": "No pudimos agregarlo. Intenta de nuevo.",
|
|
899
|
+
"note.label": "A\xF1adir nota",
|
|
900
|
+
"note.placeholder": "Instrucciones para tu pedido",
|
|
901
|
+
"note.save": "Guardar nota",
|
|
902
|
+
"note.saving": "Guardando\u2026",
|
|
903
|
+
"note.saved": "Guardada",
|
|
904
|
+
"note.error": "No se pudo guardar la nota. Intenta de nuevo.",
|
|
905
|
+
"note.counter": "{n} de {max} caracteres"
|
|
906
|
+
};
|
|
907
|
+
var EN3 = {
|
|
908
|
+
"discount.next": "Add {remaining} to unlock your automatic discount",
|
|
909
|
+
"discount.next_upgrade": "Add {remaining} to move up to {reward}",
|
|
910
|
+
"discount.applied": "You have {reward} off!",
|
|
911
|
+
"discount.max": "You have {reward} off, the maximum!",
|
|
912
|
+
"discount.suppressed": "Your discount code already gives you a better price.",
|
|
913
|
+
"discount.unlocks": "Unlock {reward} off",
|
|
914
|
+
"discount.remaining": "{remaining} to go",
|
|
915
|
+
"shipping.progress": "Add {remaining} for free shipping",
|
|
916
|
+
"shipping.reached": "Congratulations! You get free shipping",
|
|
917
|
+
"shipping.always": "Free shipping on your whole order",
|
|
918
|
+
"reward.free_shipping": "Free shipping",
|
|
919
|
+
"bar.discount": "Automatic discount progress",
|
|
920
|
+
"bar.shipping": "Free shipping progress",
|
|
921
|
+
"popup.close": "Close",
|
|
922
|
+
"popup.valid_until": "Valid until {date}",
|
|
923
|
+
"popup.limited_time": "Limited time",
|
|
924
|
+
"popup.discount_tier": "{reward} when you spend {threshold}",
|
|
925
|
+
"popup.free_shipping_min": "Free shipping from {threshold}",
|
|
926
|
+
"popup.free_shipping_always": "Free shipping, no minimum purchase",
|
|
927
|
+
"upsell.title": "Complete your purchase and unlock more",
|
|
928
|
+
"upsell.subtitle": "Automatic discount",
|
|
929
|
+
"upsell.next_level": "Next level",
|
|
930
|
+
"upsell.you_miss": "{remaining} to go",
|
|
931
|
+
"upsell.unlocks_reward": "Unlock {reward} off",
|
|
932
|
+
"upsell.total_saved": "Total saved",
|
|
933
|
+
"upsell.current_benefit": "Current benefit",
|
|
934
|
+
"upsell.status": "Status",
|
|
935
|
+
"upsell.status_pending": "Pending",
|
|
936
|
+
"upsell.status_active": "Active",
|
|
937
|
+
"upsell.next_reward": "Next reward {reward} \xB7 {threshold}",
|
|
938
|
+
"upsell.suggestions": "Add and unlock \xB7 Products you may like",
|
|
939
|
+
"upsell.add": "Add and move up +",
|
|
940
|
+
"upsell.adding": "Adding\u2026",
|
|
941
|
+
"upsell.flavor": "Flavor",
|
|
942
|
+
"upsell.unlocks_badge": "Unlocks your {reward}",
|
|
943
|
+
"upsell.unlocked": "You unlocked {reward}!",
|
|
944
|
+
"upsell.best": "You have the best discount: {reward}",
|
|
945
|
+
"upsell.skip": "Continue without adding",
|
|
946
|
+
"upsell.continue": "Continue to payment",
|
|
947
|
+
"upsell.footer": "Your best discount is applied automatically",
|
|
948
|
+
"upsell.sold_out": "This flavor sold out, pick another one.",
|
|
949
|
+
"upsell.error": "We could not add it. Please try again.",
|
|
950
|
+
"note.label": "Add a note",
|
|
951
|
+
"note.placeholder": "Instructions for your order",
|
|
952
|
+
"note.save": "Save note",
|
|
953
|
+
"note.saving": "Saving\u2026",
|
|
954
|
+
"note.saved": "Saved",
|
|
955
|
+
"note.error": "The note could not be saved. Please try again.",
|
|
956
|
+
"note.counter": "{n} of {max} characters"
|
|
957
|
+
};
|
|
958
|
+
var CATALOG3 = { es: ES3, en: EN3 };
|
|
959
|
+
function promotionMessages(locale, overrides) {
|
|
960
|
+
const base = CATALOG3[availabilityLocale(locale)];
|
|
961
|
+
if (!overrides) return base;
|
|
962
|
+
const out = { ...base };
|
|
963
|
+
for (const [k, v] of Object.entries(overrides)) if (typeof v === "string" && k in base) out[k] = v;
|
|
964
|
+
return out;
|
|
965
|
+
}
|
|
966
|
+
var NOTE_MAX_LENGTH = 1e3;
|
|
967
|
+
function normalizeNote(note) {
|
|
968
|
+
const t = (note ?? "").trim();
|
|
969
|
+
return t ? t.slice(0, NOTE_MAX_LENGTH) : null;
|
|
970
|
+
}
|
|
971
|
+
function rewardLabel(reward, locale, overrides) {
|
|
972
|
+
const m = promotionMessages(locale, overrides);
|
|
973
|
+
if (reward.kind === "free_shipping") return m["reward.free_shipping"];
|
|
974
|
+
if (reward.kind === "fixed") return formatMoney(reward.amount, locale ?? void 0, { withoutTrailingZeros: true });
|
|
975
|
+
const n = Number.parseFloat(reward.percent);
|
|
976
|
+
let num = reward.percent;
|
|
977
|
+
try {
|
|
978
|
+
if (Number.isFinite(n)) num = new Intl.NumberFormat(locale ?? void 0, { maximumFractionDigits: 4 }).format(n);
|
|
979
|
+
} catch {
|
|
980
|
+
}
|
|
981
|
+
return availabilityLocale(locale) === "en" ? `${num}%` : `${num} %`;
|
|
982
|
+
}
|
|
983
|
+
function clampPercent(value) {
|
|
984
|
+
const n = typeof value === "number" ? value : Number.parseFloat(String(value));
|
|
985
|
+
if (!Number.isFinite(n)) return 0;
|
|
986
|
+
return Math.min(100, Math.max(0, Math.round(n)));
|
|
987
|
+
}
|
|
988
|
+
var fmt = (money, locale) => formatMoney(money, locale ?? void 0, { withoutTrailingZeros: true });
|
|
989
|
+
function promoProgressView(promotions, locale, overrides) {
|
|
990
|
+
if (!promotions) return { discount: null, shipping: null };
|
|
991
|
+
const m = promotionMessages(locale, overrides);
|
|
992
|
+
const f = (tpl, vars) => formatAvailabilityMessage(tpl, vars);
|
|
993
|
+
let discount = null;
|
|
994
|
+
const hasTiers = promotions.tiers.length > 0 || promotions.next_tier !== null || promotions.applied !== null;
|
|
995
|
+
if (hasTiers) {
|
|
996
|
+
const next = promotions.next_tier;
|
|
997
|
+
const applied = promotions.applied;
|
|
998
|
+
const appliedReward = applied ? rewardLabel(applied.reward, locale, overrides) : null;
|
|
999
|
+
let state;
|
|
1000
|
+
let text;
|
|
1001
|
+
if (promotions.suppressed?.reason === "code_better" && !applied) {
|
|
1002
|
+
state = "suppressed";
|
|
1003
|
+
text = m["discount.suppressed"];
|
|
1004
|
+
} else if (!next) {
|
|
1005
|
+
state = "max";
|
|
1006
|
+
text = appliedReward ? f(m["discount.max"], { reward: appliedReward }) : m["discount.suppressed"];
|
|
1007
|
+
} else if (applied) {
|
|
1008
|
+
state = "upgrade";
|
|
1009
|
+
text = f(m["discount.next_upgrade"], { remaining: fmt(next.remaining, locale), reward: rewardLabel(next.reward, locale, overrides) });
|
|
1010
|
+
} else {
|
|
1011
|
+
state = "next";
|
|
1012
|
+
text = f(m["discount.next"], { remaining: fmt(next.remaining, locale) });
|
|
1013
|
+
}
|
|
1014
|
+
discount = {
|
|
1015
|
+
state,
|
|
1016
|
+
percent: clampPercent(promotions.percent_of_max),
|
|
1017
|
+
text,
|
|
1018
|
+
valueText: text,
|
|
1019
|
+
remaining: next?.remaining ?? null,
|
|
1020
|
+
rewardText: next ? rewardLabel(next.reward, locale, overrides) : appliedReward,
|
|
1021
|
+
appliedText: appliedReward ? f(m["discount.applied"], { reward: appliedReward }) : null,
|
|
1022
|
+
nextText: next ? f(m["discount.unlocks"], { reward: rewardLabel(next.reward, locale, overrides) }) : null,
|
|
1023
|
+
tiers: promotions.tiers.map((t) => ({
|
|
1024
|
+
id: t.id,
|
|
1025
|
+
label: rewardLabel(t.reward, locale, overrides),
|
|
1026
|
+
threshold: fmt(t.threshold, locale),
|
|
1027
|
+
reached: t.reached,
|
|
1028
|
+
position: clampPercent(t.percent_of_max)
|
|
1029
|
+
}))
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
let shipping = null;
|
|
1033
|
+
const fs = promotions.free_shipping;
|
|
1034
|
+
if (fs) {
|
|
1035
|
+
const noMinimum = Number.parseFloat(fs.threshold.amount) === 0;
|
|
1036
|
+
if (noMinimum) {
|
|
1037
|
+
shipping = { state: "always", percent: 100, text: m["shipping.always"], valueText: m["shipping.always"], remaining: null, showBar: false };
|
|
1038
|
+
} else if (fs.reached) {
|
|
1039
|
+
shipping = { state: "reached", percent: 100, text: m["shipping.reached"], valueText: m["shipping.reached"], remaining: null, showBar: true };
|
|
1040
|
+
} else {
|
|
1041
|
+
const text = f(m["shipping.progress"], { remaining: fmt(fs.remaining, locale) });
|
|
1042
|
+
shipping = { state: "progress", percent: clampPercent(fs.percent), text, valueText: text, remaining: fs.remaining, showBar: true };
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
return { discount, shipping };
|
|
1046
|
+
}
|
|
1047
|
+
|
|
367
1048
|
exports.CLIENT_IP_HEADER = CLIENT_IP_HEADER;
|
|
1049
|
+
exports.NOTE_MAX_LENGTH = NOTE_MAX_LENGTH;
|
|
368
1050
|
exports.StorefrontError = StorefrontError;
|
|
369
1051
|
exports.appendParams = appendParams;
|
|
1052
|
+
exports.availabilityLocale = availabilityLocale;
|
|
1053
|
+
exports.availabilityMessages = availabilityMessages;
|
|
1054
|
+
exports.availabilityView = availabilityView;
|
|
1055
|
+
exports.cartLineProblemView = cartLineProblemView;
|
|
1056
|
+
exports.clampPercent = clampPercent;
|
|
370
1057
|
exports.createStorefrontClient = createStorefrontClient;
|
|
1058
|
+
exports.declineMessage = declineMessage;
|
|
1059
|
+
exports.declineMessageKey = declineMessageKey;
|
|
1060
|
+
exports.formatAvailabilityMessage = formatAvailabilityMessage;
|
|
371
1061
|
exports.formatMoney = formatMoney;
|
|
1062
|
+
exports.formatVoucherExpiry = formatVoucherExpiry;
|
|
372
1063
|
exports.generateIdempotencyKey = generateIdempotencyKey;
|
|
373
1064
|
exports.isStorefrontError = isStorefrontError;
|
|
374
1065
|
exports.isTokenPublishable = isTokenPublishable;
|
|
375
1066
|
exports.normalizeClientIp = normalizeClientIp;
|
|
1067
|
+
exports.normalizeNote = normalizeNote;
|
|
376
1068
|
exports.parseMoney = parseMoney;
|
|
377
1069
|
exports.parseProductFilters = parseProductFilters;
|
|
1070
|
+
exports.paymentErrorView = paymentErrorView;
|
|
1071
|
+
exports.paymentIdempotencyKey = paymentIdempotencyKey;
|
|
1072
|
+
exports.paymentMessages = paymentMessages;
|
|
1073
|
+
exports.paymentOutcome = paymentOutcome;
|
|
1074
|
+
exports.productAvailability = productAvailability;
|
|
378
1075
|
exports.productListQuery = productListQuery;
|
|
1076
|
+
exports.promoProgressView = promoProgressView;
|
|
1077
|
+
exports.promotionMessages = promotionMessages;
|
|
1078
|
+
exports.resolveAvailability = resolveAvailability;
|
|
1079
|
+
exports.rewardLabel = rewardLabel;
|
|
1080
|
+
exports.safeHttpsUrl = safeHttpsUrl;
|
|
1081
|
+
exports.schemaAvailability = schemaAvailability;
|
|
379
1082
|
exports.toMoney = toMoney;
|
|
380
1083
|
//# sourceMappingURL=index.cjs.map
|
|
381
1084
|
//# sourceMappingURL=index.cjs.map
|