@nevios/storefront-kit 1.0.0 → 1.2.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/dist/index.js CHANGED
@@ -179,6 +179,37 @@ var en = {
179
179
  "order.terminal.cancelled": "This order was cancelled.",
180
180
  "order.terminal.refunded": "This order was refunded.",
181
181
  "order.terminal.returned": "This order was returned.",
182
+ // payment page — the four situations it can be in
183
+ "payment.title": "Pay for order {order}",
184
+ "payment.amountDue": "Amount due",
185
+ "payment.methodLabel": "Payment method",
186
+ "payment.payNow": "Pay now",
187
+ "payment.finish": "Finish payment",
188
+ "payment.loading": "Loading payment\u2026",
189
+ "payment.loadError": "Couldn't load this payment.",
190
+ "payment.actionError": "That didn't go through. Please try again.",
191
+ // …paid
192
+ "payment.paidTitle": "Paid",
193
+ "payment.paidBody": "Order {order} is paid. Nothing further is needed.",
194
+ "payment.paidAmount": "Paid",
195
+ // …returning from a gateway, callback still in flight
196
+ "payment.verifyingTitle": "Checking your payment\u2026",
197
+ "payment.verifyingBody": "This takes a few seconds. Don't pay again \u2014 we'll update this page.",
198
+ // …locked, one sentence per reason
199
+ "payment.blockedTitle": "This order can't be paid",
200
+ "payment.blocked.orderCancelled": "This order was cancelled, so there is nothing to pay.",
201
+ "payment.blocked.alreadyPaid": "This order is already paid.",
202
+ "payment.blocked.authorizationHeld": "Your bank is holding the funds for this order. Once the payment settles this page will show it as paid.",
203
+ "payment.blocked.documentIssued": "An invoice has been issued for this order, so the amount is fixed. Contact us to change how you pay.",
204
+ // …bank transfer
205
+ "payment.account": "Account",
206
+ "payment.beneficiary": "Beneficiary",
207
+ "payment.variableSymbol": "Variable symbol",
208
+ "payment.amount": "Amount",
209
+ "payment.copy": "Copy",
210
+ "payment.copied": "Copied",
211
+ "payment.qrAlt": "QR code for the payment",
212
+ "payment.qrCaption": "Scan in your banking app",
182
213
  // error pages
183
214
  "error.notFound.title": "Page not found",
184
215
  "error.notFound.body": "This page doesn't exist or isn't available in your region.",
@@ -335,6 +366,33 @@ var cs = {
335
366
  "order.terminal.cancelled": "Tato objedn\xE1vka byla zru\u0161ena.",
336
367
  "order.terminal.refunded": "Tato objedn\xE1vka byla refundov\xE1na.",
337
368
  "order.terminal.returned": "Tato objedn\xE1vka byla vr\xE1cena.",
369
+ // platební stránka — čtyři situace, ve kterých může být
370
+ "payment.title": "Zaplatit objedn\xE1vku {order}",
371
+ "payment.amountDue": "K \xFAhrad\u011B",
372
+ "payment.methodLabel": "Zp\u016Fsob platby",
373
+ "payment.payNow": "Zaplatit",
374
+ "payment.finish": "Dokon\u010Dit platbu",
375
+ "payment.loading": "Na\u010D\xEDt\xE1n\xED platby\u2026",
376
+ "payment.loadError": "Platbu se nepoda\u0159ilo na\u010D\xEDst.",
377
+ "payment.actionError": "Nepovedlo se to. Zkuste to pros\xEDm znovu.",
378
+ "payment.paidTitle": "Zaplaceno",
379
+ "payment.paidBody": "Objedn\xE1vka {order} je zaplacen\xE1. Nic dal\u0161\xEDho d\u011Blat nemus\xEDte.",
380
+ "payment.paidAmount": "Uhrazeno",
381
+ "payment.verifyingTitle": "Ov\u011B\u0159ujeme platbu\u2026",
382
+ "payment.verifyingBody": "Chvilku to trv\xE1. Nepla\u0165te pros\xEDm znovu \u2014 str\xE1nka se sama aktualizuje.",
383
+ "payment.blockedTitle": "Tuhle objedn\xE1vku te\u010F zaplatit nejde",
384
+ "payment.blocked.orderCancelled": "Objedn\xE1vka byla zru\u0161en\xE1, tak\u017Ee nen\xED co platit.",
385
+ "payment.blocked.alreadyPaid": "Objedn\xE1vka je u\u017E zaplacen\xE1.",
386
+ "payment.blocked.authorizationHeld": "Banka na tuhle objedn\xE1vku dr\u017E\xED pen\xEDze. Jakmile se platba dokon\u010D\xED, uvid\xEDte to tady.",
387
+ "payment.blocked.documentIssued": "K objedn\xE1vce je vystaven\xE1 faktura, tak\u017Ee je \u010D\xE1stka pevn\xE1. Pokud chcete platit jinak, ozv\u011Bte se n\xE1m.",
388
+ "payment.account": "\xDA\u010Det",
389
+ "payment.beneficiary": "P\u0159\xEDjemce",
390
+ "payment.variableSymbol": "Variabiln\xED symbol",
391
+ "payment.amount": "\u010C\xE1stka",
392
+ "payment.copy": "Kop\xEDrovat",
393
+ "payment.copied": "Zkop\xEDrov\xE1no",
394
+ "payment.qrAlt": "QR k\xF3d k platb\u011B",
395
+ "payment.qrCaption": "Na\u010Dt\u011Bte v bankovn\xED aplikaci",
338
396
  "error.notFound.title": "Str\xE1nka nenalezena",
339
397
  "error.notFound.body": "Tato str\xE1nka neexistuje nebo nen\xED ve va\u0161em regionu dostupn\xE1.",
340
398
  "error.generic.title": "N\u011Bco se pokazilo",
@@ -1142,6 +1200,7 @@ function AddToCart({
1142
1200
  }
1143
1201
  function CartLineItem({ line, locale }) {
1144
1202
  const t = useT();
1203
+ const loc = useFormatLocale(locale);
1145
1204
  const { updateLine, removeLine } = useCart();
1146
1205
  return /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "0.85rem", alignItems: "flex-start", padding: "0.75rem 0" }, children: [
1147
1206
  /* @__PURE__ */ jsx(
@@ -1213,7 +1272,7 @@ function CartLineItem({ line, locale }) {
1213
1272
  )
1214
1273
  ] })
1215
1274
  ] }),
1216
- /* @__PURE__ */ jsx("div", { style: { fontWeight: 500, whiteSpace: "nowrap" }, children: formatCents(line.line_total_cents, line.unit_price.currency, locale) })
1275
+ /* @__PURE__ */ jsx("div", { style: { fontWeight: 500, whiteSpace: "nowrap" }, children: formatCents(line.line_total_cents, line.unit_price.currency, loc) })
1217
1276
  ] });
1218
1277
  }
1219
1278
  function StepBtn({ label, onClick }) {
@@ -1245,6 +1304,7 @@ function CartSummary({
1245
1304
  locale
1246
1305
  }) {
1247
1306
  const tr = useT();
1307
+ const loc = useFormatLocale(locale);
1248
1308
  const { cart } = useCart();
1249
1309
  if (cart == null) return null;
1250
1310
  const t = cart.totals;
@@ -1265,10 +1325,10 @@ function CartSummary({
1265
1325
  opacity: empty ? 0.55 : 1
1266
1326
  };
1267
1327
  return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.75rem" }, children: [
1268
- /* @__PURE__ */ jsx(Row, { label: tr("cart.subtotal"), value: formatCents(t.subtotal_with_tax_cents, t.currency, locale) }),
1269
- t.tax_cents > 0 ? /* @__PURE__ */ jsx(Row, { label: tr("cart.tax"), value: formatCents(t.tax_cents, t.currency, locale), muted: true }) : null,
1328
+ /* @__PURE__ */ jsx(Row, { label: tr("cart.subtotal"), value: formatCents(t.subtotal_with_tax_cents, t.currency, loc) }),
1329
+ t.tax_cents > 0 ? /* @__PURE__ */ jsx(Row, { label: tr("cart.tax"), value: formatCents(t.tax_cents, t.currency, loc), muted: true }) : null,
1270
1330
  /* @__PURE__ */ jsx("div", { style: { height: 1, background: "var(--nv-border)" } }),
1271
- /* @__PURE__ */ jsx(Row, { label: tr("cart.total"), value: formatCents(t.total_cents, t.currency, locale), strong: true }),
1331
+ /* @__PURE__ */ jsx(Row, { label: tr("cart.total"), value: formatCents(t.total_cents, t.currency, loc), strong: true }),
1272
1332
  checkoutHref && !empty ? /* @__PURE__ */ jsx("a", { href: checkoutHref, style: ctaStyle, children: cta }) : /* @__PURE__ */ jsx("button", { type: "button", disabled: empty, onClick: onCheckout, style: ctaStyle, children: cta })
1273
1333
  ] });
1274
1334
  }
@@ -1671,6 +1731,7 @@ function ShippingPicker({
1671
1731
  locale
1672
1732
  }) {
1673
1733
  const t = useT();
1734
+ const loc = useFormatLocale(locale);
1674
1735
  const { checkout, shippingQuotes, setShippingMethod, loading } = useCheckout();
1675
1736
  const heading = title ?? t("checkout.shippingMethod");
1676
1737
  const [quotes, setQuotes] = React10.useState(null);
@@ -1711,7 +1772,7 @@ function ShippingPicker({
1711
1772
  {
1712
1773
  selected: selected === q.rate_id,
1713
1774
  onSelect: () => setSelected(q.rate_id),
1714
- trailing: q.is_free ? t("common.free") : formatCents(q.price_cents, q.currency, locale),
1775
+ trailing: q.is_free ? t("common.free") : formatCents(q.price_cents, q.currency, loc),
1715
1776
  children: /* @__PURE__ */ jsxs("span", { style: { display: "flex", flexDirection: "column" }, children: [
1716
1777
  /* @__PURE__ */ jsx("span", { style: { fontWeight: 500 }, children: q.label }),
1717
1778
  days ? /* @__PURE__ */ jsx("span", { style: { fontSize: "0.8125rem", color: "var(--nv-muted)" }, children: days }) : null
@@ -1732,6 +1793,39 @@ function estDays(q, t) {
1732
1793
  const n = lo ?? hi;
1733
1794
  return t("shipping.days", { count: n });
1734
1795
  }
1796
+ function PaymentMethodPicker({
1797
+ methods,
1798
+ value,
1799
+ onSelect,
1800
+ currency,
1801
+ locale,
1802
+ disabled,
1803
+ label
1804
+ }) {
1805
+ const loc = useFormatLocale(locale);
1806
+ return /* @__PURE__ */ jsx(
1807
+ "div",
1808
+ {
1809
+ role: "radiogroup",
1810
+ "aria-label": label,
1811
+ style: { display: "flex", flexDirection: "column", gap: "0.6rem" },
1812
+ children: methods.map((m) => /* @__PURE__ */ jsx(
1813
+ SelectRow,
1814
+ {
1815
+ selected: value === m.method,
1816
+ disabled,
1817
+ onSelect: () => onSelect(m.method),
1818
+ trailing: m.surcharge_cents > 0 ? `+${formatCents(m.surcharge_cents, currency, loc)}` : void 0,
1819
+ children: /* @__PURE__ */ jsxs("span", { style: { display: "flex", flexDirection: "column" }, children: [
1820
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: 500 }, children: m.display_label }),
1821
+ m.display_description ? /* @__PURE__ */ jsx("span", { style: { fontSize: "0.8125rem", color: "var(--nv-muted)" }, children: m.display_description }) : null
1822
+ ] })
1823
+ },
1824
+ m.method
1825
+ ))
1826
+ }
1827
+ );
1828
+ }
1735
1829
  function PaymentSection({
1736
1830
  title,
1737
1831
  returnUrl,
@@ -1772,19 +1866,17 @@ function PaymentSection({
1772
1866
  return /* @__PURE__ */ jsx(Section, { title: heading, children: /* @__PURE__ */ jsx("p", { style: { color: "var(--nv-muted)", margin: 0 }, children: t("checkout.noPayment") }) });
1773
1867
  }
1774
1868
  return /* @__PURE__ */ jsxs(Section, { title: heading, children: [
1775
- /* @__PURE__ */ jsx("div", { role: "radiogroup", style: { display: "flex", flexDirection: "column", gap: "0.6rem" }, children: methods.map((m) => /* @__PURE__ */ jsx(
1776
- SelectRow,
1869
+ /* @__PURE__ */ jsx(
1870
+ PaymentMethodPicker,
1777
1871
  {
1778
- selected: selected === m.method,
1779
- onSelect: () => void setPaymentMethod(m.method),
1780
- trailing: m.surcharge_cents > 0 && checkout ? `+${formatCents(m.surcharge_cents, checkout.totals.currency, locale)}` : void 0,
1781
- children: /* @__PURE__ */ jsxs("span", { style: { display: "flex", flexDirection: "column" }, children: [
1782
- /* @__PURE__ */ jsx("span", { style: { fontWeight: 500 }, children: m.display_label }),
1783
- m.display_description ? /* @__PURE__ */ jsx("span", { style: { fontSize: "0.8125rem", color: "var(--nv-muted)" }, children: m.display_description }) : null
1784
- ] })
1785
- },
1786
- m.method
1787
- )) }),
1872
+ methods,
1873
+ value: selected,
1874
+ onSelect: (method) => void setPaymentMethod(method),
1875
+ currency: checkout?.totals.currency ?? "CZK",
1876
+ locale,
1877
+ label: heading
1878
+ }
1879
+ ),
1788
1880
  /* @__PURE__ */ jsxs("div", { children: [
1789
1881
  /* @__PURE__ */ jsx(Button, { onClick: placeOrder, busy: loading, disabled: selected == null, children: isGateway ? t("checkout.payNow") : t("checkout.placeOrder") }),
1790
1882
  isGateway ? /* @__PURE__ */ jsx("p", { style: { fontSize: "0.8125rem", color: "var(--nv-muted)", marginTop: "0.6rem" }, children: t("checkout.gatewayRedirectNote") }) : null
@@ -1797,12 +1889,13 @@ function OrderSummary({
1797
1889
  locale
1798
1890
  }) {
1799
1891
  const tr = useT();
1892
+ const loc = useFormatLocale(locale);
1800
1893
  const ctx = useCheckout();
1801
1894
  const checkout = override ?? ctx.checkout;
1802
1895
  if (checkout == null) return null;
1803
1896
  const t = checkout.totals;
1804
1897
  const c = t.currency;
1805
- const money = (cents) => formatCents(cents, c, locale);
1898
+ const money = (cents) => formatCents(cents, c, loc);
1806
1899
  return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "1rem" }, children: [
1807
1900
  !totalsOnly && checkout.line_items.length > 0 ? /* @__PURE__ */ jsx("ul", { style: { listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: "0.75rem" }, children: checkout.line_items.map((li) => /* @__PURE__ */ jsxs("li", { style: { display: "flex", gap: "0.75rem", alignItems: "center" }, children: [
1808
1901
  li.image_url ? /* @__PURE__ */ jsx(
@@ -2034,6 +2127,79 @@ function useOrderStatus(statusToken, initial) {
2034
2127
  }, [load, initial]);
2035
2128
  return { order, loading, error, reload: () => void load() };
2036
2129
  }
2130
+ function usePayment(token, initial) {
2131
+ const client = useStorefront();
2132
+ const [payment, setPayment] = React10.useState(initial ?? null);
2133
+ const [methods, setMethods] = React10.useState([]);
2134
+ const [loading, setLoading] = React10.useState(false);
2135
+ const [busy, setBusy] = React10.useState(false);
2136
+ const [error, setError] = React10.useState(null);
2137
+ const load = React10.useCallback(async () => {
2138
+ if (!token) return;
2139
+ setLoading(true);
2140
+ try {
2141
+ const [state, list] = await Promise.all([
2142
+ client.payment.get(token),
2143
+ client.payment.methods(token)
2144
+ ]);
2145
+ setPayment(state);
2146
+ setMethods(list);
2147
+ setError(null);
2148
+ } catch (e) {
2149
+ setError(e instanceof Error ? e : new Error(String(e)));
2150
+ } finally {
2151
+ setLoading(false);
2152
+ }
2153
+ }, [client, token]);
2154
+ React10.useEffect(() => {
2155
+ if (!token) return;
2156
+ if (initial && initial.token === token) {
2157
+ setPayment(initial);
2158
+ client.payment.methods(token).then(setMethods).catch(() => setMethods([]));
2159
+ return;
2160
+ }
2161
+ void load();
2162
+ }, [token, load]);
2163
+ const selectMethod = React10.useCallback(
2164
+ async (method) => {
2165
+ if (!token) return null;
2166
+ setBusy(true);
2167
+ try {
2168
+ const next = await client.payment.selectMethod(token, method);
2169
+ setPayment(next);
2170
+ setError(null);
2171
+ return next;
2172
+ } catch (e) {
2173
+ setError(e instanceof Error ? e : new Error(String(e)));
2174
+ return null;
2175
+ } finally {
2176
+ setBusy(false);
2177
+ }
2178
+ },
2179
+ [client, token]
2180
+ );
2181
+ const startPayment = React10.useCallback(
2182
+ async (input) => {
2183
+ if (!token) return null;
2184
+ setBusy(true);
2185
+ try {
2186
+ const result = await client.payment.start(token, {
2187
+ return_url: input?.returnUrl,
2188
+ cancel_url: input?.cancelUrl
2189
+ });
2190
+ setError(null);
2191
+ return result.redirect_url;
2192
+ } catch (e) {
2193
+ setError(e instanceof Error ? e : new Error(String(e)));
2194
+ return null;
2195
+ } finally {
2196
+ setBusy(false);
2197
+ }
2198
+ },
2199
+ [client, token]
2200
+ );
2201
+ return { payment, methods, loading, busy, error, reload: load, selectMethod, startPayment };
2202
+ }
2037
2203
  var PHASE_RANK = {
2038
2204
  placed: 0,
2039
2205
  paid: 1,
@@ -2129,6 +2295,7 @@ function OrderLines({
2129
2295
  order,
2130
2296
  locale
2131
2297
  }) {
2298
+ const loc = useFormatLocale(locale);
2132
2299
  return /* @__PURE__ */ jsxs("ul", { style: { listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: "0.75rem" }, children: [
2133
2300
  order.line_items.map((li) => /* @__PURE__ */ jsxs("li", { style: { display: "flex", gap: "0.75rem", alignItems: "center" }, children: [
2134
2301
  li.image_url ? /* @__PURE__ */ jsx(
@@ -2149,11 +2316,11 @@ function OrderLines({
2149
2316
  li.quantity
2150
2317
  ] })
2151
2318
  ] }),
2152
- /* @__PURE__ */ jsx("span", { style: { fontVariantNumeric: "tabular-nums" }, children: formatCents(li.total_with_tax_cents, order.currency, locale) })
2319
+ /* @__PURE__ */ jsx("span", { style: { fontVariantNumeric: "tabular-nums" }, children: formatCents(li.total_with_tax_cents, order.currency, loc) })
2153
2320
  ] }, li.id)),
2154
2321
  order.fee_lines.map((f, i) => /* @__PURE__ */ jsxs("li", { style: { display: "flex", gap: "0.75rem", alignItems: "center" }, children: [
2155
2322
  /* @__PURE__ */ jsx("span", { style: { flex: 1, minWidth: 0 }, children: /* @__PURE__ */ jsx("span", { style: { display: "block", fontWeight: 500 }, children: f.title }) }),
2156
- /* @__PURE__ */ jsx("span", { style: { fontVariantNumeric: "tabular-nums" }, children: formatCents(f.total_with_tax_cents, order.currency, locale) })
2323
+ /* @__PURE__ */ jsx("span", { style: { fontVariantNumeric: "tabular-nums" }, children: formatCents(f.total_with_tax_cents, order.currency, loc) })
2157
2324
  ] }, `fee-${i}`))
2158
2325
  ] });
2159
2326
  }
@@ -2162,9 +2329,10 @@ function OrderTotals({
2162
2329
  locale
2163
2330
  }) {
2164
2331
  const tr = useT();
2332
+ const loc = useFormatLocale(locale);
2165
2333
  const t = order.totals;
2166
2334
  const c = order.currency;
2167
- const money = (cents) => formatCents(cents, c, locale);
2335
+ const money = (cents) => formatCents(cents, c, loc);
2168
2336
  return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
2169
2337
  /* @__PURE__ */ jsx(Row3, { label: tr("totals.subtotal"), value: money(t.subtotal_cents) }),
2170
2338
  t.discount_cents > 0 ? /* @__PURE__ */ jsx(Row3, { label: tr("totals.discount"), value: `\u2212${money(t.discount_cents)}`, muted: true }) : null,
@@ -2270,6 +2438,7 @@ function OrderStatusPage({
2270
2438
  statusToken,
2271
2439
  order: initial,
2272
2440
  onContactSupport,
2441
+ payHref,
2273
2442
  locale
2274
2443
  }) {
2275
2444
  const t = useT();
@@ -2321,6 +2490,7 @@ function OrderStatusPage({
2321
2490
  /* @__PURE__ */ jsxs(Card, { title: t("order.payment"), children: [
2322
2491
  /* @__PURE__ */ jsx(Line, { label: t("order.method"), value: order.payment.method ?? "\u2014" }),
2323
2492
  /* @__PURE__ */ jsx(Line, { label: t("order.status"), value: order.payment.status }),
2493
+ order.payment.token && payHref ? /* @__PURE__ */ jsx("a", { href: payHref(order.payment.token), style: { color: "var(--nv-brand)", fontWeight: 500 }, children: t("payment.finish") }) : null,
2324
2494
  order.payment.invoice_available && order.payment.invoice_url ? /* @__PURE__ */ jsx("a", { href: order.payment.invoice_url, style: { color: "var(--nv-brand)" }, children: t("order.downloadInvoice") }) : null
2325
2495
  ] }),
2326
2496
  order.actions.can_contact_support && onContactSupport ? /* @__PURE__ */ jsx(
@@ -2372,6 +2542,265 @@ function Line({ label, value }) {
2372
2542
  /* @__PURE__ */ jsx("span", { style: { textAlign: "right" }, children: value })
2373
2543
  ] });
2374
2544
  }
2545
+ function TransferInstructions({
2546
+ instructions: i,
2547
+ locale
2548
+ }) {
2549
+ const t = useT();
2550
+ const loc = useFormatLocale(locale);
2551
+ const account = i.iban || i.account_number;
2552
+ return /* @__PURE__ */ jsxs(
2553
+ "div",
2554
+ {
2555
+ style: {
2556
+ display: "flex",
2557
+ flexWrap: "wrap",
2558
+ gap: "1.25rem",
2559
+ alignItems: "flex-start",
2560
+ padding: "1rem",
2561
+ borderRadius: "var(--nv-radius)",
2562
+ border: "1px solid var(--nv-border)"
2563
+ },
2564
+ children: [
2565
+ /* @__PURE__ */ jsxs("dl", { style: { flex: "1 1 15rem", margin: 0, display: "flex", flexDirection: "column", gap: "0.7rem" }, children: [
2566
+ account ? /* @__PURE__ */ jsx(CopyRow, { label: t("payment.account"), value: account }) : null,
2567
+ i.beneficiary_name ? /* @__PURE__ */ jsx(Row4, { label: t("payment.beneficiary"), value: i.beneficiary_name }) : null,
2568
+ /* @__PURE__ */ jsx(CopyRow, { label: t("payment.variableSymbol"), value: i.variable_symbol }),
2569
+ /* @__PURE__ */ jsx(
2570
+ Row4,
2571
+ {
2572
+ label: t("payment.amount"),
2573
+ value: formatCents(i.amount_cents, i.currency, loc),
2574
+ strong: true
2575
+ }
2576
+ )
2577
+ ] }),
2578
+ i.qr_svg_data_uri ? /* @__PURE__ */ jsxs("figure", { style: { margin: 0, textAlign: "center" }, children: [
2579
+ /* @__PURE__ */ jsx(
2580
+ "img",
2581
+ {
2582
+ src: i.qr_svg_data_uri,
2583
+ alt: t("payment.qrAlt"),
2584
+ width: 148,
2585
+ height: 148,
2586
+ style: { display: "block", borderRadius: "var(--nv-radius)" }
2587
+ }
2588
+ ),
2589
+ /* @__PURE__ */ jsx("figcaption", { style: { fontSize: "0.75rem", color: "var(--nv-muted)", marginTop: "0.4rem" }, children: t("payment.qrCaption") })
2590
+ ] }) : null,
2591
+ i.instructions ? /* @__PURE__ */ jsx("p", { style: { flexBasis: "100%", margin: 0, fontSize: "0.8125rem", color: "var(--nv-muted)" }, children: i.instructions }) : null
2592
+ ]
2593
+ }
2594
+ );
2595
+ }
2596
+ function Row4({
2597
+ label,
2598
+ value,
2599
+ strong
2600
+ }) {
2601
+ return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.1rem" }, children: [
2602
+ /* @__PURE__ */ jsx("dt", { style: { fontSize: "0.75rem", color: "var(--nv-muted)" }, children: label }),
2603
+ /* @__PURE__ */ jsx(
2604
+ "dd",
2605
+ {
2606
+ style: {
2607
+ margin: 0,
2608
+ fontWeight: strong ? 600 : 500,
2609
+ fontVariantNumeric: "tabular-nums"
2610
+ },
2611
+ children: value
2612
+ }
2613
+ )
2614
+ ] });
2615
+ }
2616
+ function CopyRow({ label, value }) {
2617
+ const t = useT();
2618
+ const [copied, setCopied] = React10.useState(false);
2619
+ React10.useEffect(() => {
2620
+ if (!copied) return;
2621
+ const id = setTimeout(() => setCopied(false), 1800);
2622
+ return () => clearTimeout(id);
2623
+ }, [copied]);
2624
+ async function copy() {
2625
+ try {
2626
+ await navigator.clipboard.writeText(value);
2627
+ setCopied(true);
2628
+ } catch {
2629
+ }
2630
+ }
2631
+ return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.1rem" }, children: [
2632
+ /* @__PURE__ */ jsx("dt", { style: { fontSize: "0.75rem", color: "var(--nv-muted)" }, children: label }),
2633
+ /* @__PURE__ */ jsxs("dd", { style: { margin: 0, display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
2634
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: 500, fontVariantNumeric: "tabular-nums" }, children: value }),
2635
+ /* @__PURE__ */ jsx(
2636
+ "button",
2637
+ {
2638
+ type: "button",
2639
+ onClick: () => void copy(),
2640
+ style: {
2641
+ font: "inherit",
2642
+ fontSize: "0.75rem",
2643
+ cursor: "pointer",
2644
+ padding: "0.15rem 0.5rem",
2645
+ borderRadius: "var(--nv-radius)",
2646
+ border: "1px solid var(--nv-border)",
2647
+ background: "transparent",
2648
+ color: "var(--nv-muted)"
2649
+ },
2650
+ children: copied ? t("payment.copied") : t("payment.copy")
2651
+ }
2652
+ )
2653
+ ] })
2654
+ ] });
2655
+ }
2656
+ var HANDOFF_TTL_MS = 30 * 60 * 1e3;
2657
+ var POLL_MS = 2e3;
2658
+ var POLL_ATTEMPTS = 6;
2659
+ var BLOCKED_MESSAGE = {
2660
+ order_cancelled: "payment.blocked.orderCancelled",
2661
+ already_paid: "payment.blocked.alreadyPaid",
2662
+ authorization_held: "payment.blocked.authorizationHeld",
2663
+ document_issued: "payment.blocked.documentIssued"
2664
+ };
2665
+ function handoffKey(token) {
2666
+ return `nv_payment_handoff_${token}`;
2667
+ }
2668
+ function rememberHandoff(token) {
2669
+ try {
2670
+ sessionStorage.setItem(handoffKey(token), String(Date.now()));
2671
+ } catch {
2672
+ }
2673
+ }
2674
+ function consumeHandoff(token) {
2675
+ try {
2676
+ const raw = sessionStorage.getItem(handoffKey(token));
2677
+ if (!raw) return false;
2678
+ const fresh = Date.now() - Number(raw) < HANDOFF_TTL_MS;
2679
+ if (!fresh) sessionStorage.removeItem(handoffKey(token));
2680
+ return fresh;
2681
+ } catch {
2682
+ return false;
2683
+ }
2684
+ }
2685
+ function forgetHandoff(token) {
2686
+ try {
2687
+ sessionStorage.removeItem(handoffKey(token));
2688
+ } catch {
2689
+ }
2690
+ }
2691
+ function PaymentPage({
2692
+ token,
2693
+ payment: initial,
2694
+ returnUrl,
2695
+ cancelUrl,
2696
+ statusHref,
2697
+ locale,
2698
+ onPaid
2699
+ }) {
2700
+ const t = useT();
2701
+ const loc = useFormatLocale(locale);
2702
+ const { payment, methods, loading, busy, error, reload, selectMethod, startPayment } = usePayment(token, initial);
2703
+ const [verifying, setVerifying] = React10.useState(false);
2704
+ React10.useEffect(() => {
2705
+ if (token) setVerifying(consumeHandoff(token));
2706
+ }, [token]);
2707
+ const settled = payment != null && !payment.can_pay && payment.blocked_reason === "already_paid";
2708
+ React10.useEffect(() => {
2709
+ if (!verifying || !token || settled) return;
2710
+ let attempts = 0;
2711
+ const id = setInterval(() => {
2712
+ attempts += 1;
2713
+ if (attempts > POLL_ATTEMPTS) {
2714
+ setVerifying(false);
2715
+ forgetHandoff(token);
2716
+ clearInterval(id);
2717
+ return;
2718
+ }
2719
+ void reload();
2720
+ }, POLL_MS);
2721
+ return () => clearInterval(id);
2722
+ }, [verifying, token, settled, reload]);
2723
+ React10.useEffect(() => {
2724
+ if (!settled || !token || !payment) return;
2725
+ setVerifying(false);
2726
+ forgetHandoff(token);
2727
+ onPaid?.(payment);
2728
+ }, [settled, token, payment, onPaid]);
2729
+ if (!token) return null;
2730
+ if (payment == null) {
2731
+ return /* @__PURE__ */ jsx(Shell2, { children: /* @__PURE__ */ jsx("p", { style: { margin: 0, color: "var(--nv-muted)" }, children: loading ? t("payment.loading") : t("payment.loadError") }) });
2732
+ }
2733
+ const money = (cents) => formatCents(cents, payment.currency, loc);
2734
+ if (settled) {
2735
+ return /* @__PURE__ */ jsxs(Shell2, { title: t("payment.paidTitle"), children: [
2736
+ /* @__PURE__ */ jsx("p", { style: { margin: 0 }, children: t("payment.paidBody", { order: payment.order_name ?? payment.order_number }) }),
2737
+ /* @__PURE__ */ jsx(Amount, { label: t("payment.paidAmount"), value: money(payment.total_cents) }),
2738
+ statusHref ? /* @__PURE__ */ jsx(Link, { href: statusHref, children: t("order.trackOrder") }) : null
2739
+ ] });
2740
+ }
2741
+ if (payment.blocked_reason) {
2742
+ return /* @__PURE__ */ jsxs(Shell2, { title: t("payment.blockedTitle"), children: [
2743
+ /* @__PURE__ */ jsx("p", { style: { margin: 0 }, children: t(BLOCKED_MESSAGE[payment.blocked_reason]) }),
2744
+ statusHref ? /* @__PURE__ */ jsx(Link, { href: statusHref, children: t("order.trackOrder") }) : null
2745
+ ] });
2746
+ }
2747
+ if (verifying) {
2748
+ return /* @__PURE__ */ jsx(Shell2, { title: t("payment.verifyingTitle"), children: /* @__PURE__ */ jsx("p", { style: { margin: 0, color: "var(--nv-muted)" }, children: t("payment.verifyingBody") }) });
2749
+ }
2750
+ const isBank = payment.category === "bank";
2751
+ const isGateway = payment.category === "gateway";
2752
+ async function pay() {
2753
+ if (!token) return;
2754
+ rememberHandoff(token);
2755
+ const url = await startPayment({ returnUrl, cancelUrl });
2756
+ if (url) {
2757
+ window.location.assign(url);
2758
+ return;
2759
+ }
2760
+ forgetHandoff(token);
2761
+ await reload();
2762
+ }
2763
+ return /* @__PURE__ */ jsxs(Shell2, { title: t("payment.title", { order: payment.order_name ?? payment.order_number }), children: [
2764
+ /* @__PURE__ */ jsx(Amount, { label: t("payment.amountDue"), value: money(payment.amount_due_cents) }),
2765
+ methods.length > 0 ? /* @__PURE__ */ jsx(
2766
+ PaymentMethodPicker,
2767
+ {
2768
+ methods,
2769
+ value: payment.method,
2770
+ onSelect: (method) => void selectMethod(method),
2771
+ currency: payment.currency,
2772
+ locale: loc,
2773
+ disabled: busy,
2774
+ label: t("payment.methodLabel")
2775
+ }
2776
+ ) : null,
2777
+ isBank && payment.instructions ? /* @__PURE__ */ jsx(TransferInstructions, { instructions: payment.instructions, locale: loc }) : null,
2778
+ !isBank ? /* @__PURE__ */ jsxs("div", { children: [
2779
+ /* @__PURE__ */ jsx(Button, { onClick: () => void pay(), busy, children: t("payment.payNow") }),
2780
+ isGateway ? /* @__PURE__ */ jsx("p", { style: { fontSize: "0.8125rem", color: "var(--nv-muted)", marginTop: "0.6rem" }, children: t("checkout.gatewayRedirectNote") }) : null
2781
+ ] }) : null,
2782
+ error ? /* @__PURE__ */ jsx("p", { role: "alert", style: { margin: 0, color: "var(--nv-danger, #b3261e)" }, children: t("payment.actionError") }) : null,
2783
+ statusHref ? /* @__PURE__ */ jsx(Link, { href: statusHref, children: t("order.trackOrder") }) : null
2784
+ ] });
2785
+ }
2786
+ function Shell2({
2787
+ title,
2788
+ children
2789
+ }) {
2790
+ return /* @__PURE__ */ jsxs("section", { style: { display: "flex", flexDirection: "column", gap: "1rem", maxWidth: "34rem" }, children: [
2791
+ title ? /* @__PURE__ */ jsx("h1", { style: { margin: 0, fontSize: "1.25rem", fontWeight: 600 }, children: title }) : null,
2792
+ children
2793
+ ] });
2794
+ }
2795
+ function Amount({ label, value }) {
2796
+ return /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "baseline" }, children: [
2797
+ /* @__PURE__ */ jsx("span", { style: { color: "var(--nv-muted)" }, children: label }),
2798
+ /* @__PURE__ */ jsx("strong", { style: { fontSize: "1.35rem", fontVariantNumeric: "tabular-nums" }, children: value })
2799
+ ] });
2800
+ }
2801
+ function Link({ href, children }) {
2802
+ return /* @__PURE__ */ jsx("a", { href, style: { color: "var(--nv-brand)", fontSize: "0.875rem" }, children });
2803
+ }
2375
2804
  var AccountContext = React10.createContext(null);
2376
2805
  function AccountProvider({ children }) {
2377
2806
  const client = useStorefront();
@@ -2974,6 +3403,6 @@ function OrganizationJsonLd({
2974
3403
  return /* @__PURE__ */ jsx(JsonLd, { data });
2975
3404
  }
2976
3405
 
2977
- export { AccountDashboard, AccountProvider, AddToCart, AddressBook, AddressForm, AvailabilityFilter, BackInStockForm, BreadcrumbJsonLd, CartDrawer, CartLineItem, CartProvider, CartSummary, CheckoutFlow, CheckoutProvider, CollectionPage, I18nProvider, JsonLd, LoginForm, MarketSwitcher, NeviosProvider, OrderConfirmation, OrderHistory, OrderStatusPage, OrderSummary, OrganizationJsonLd, PaymentSection, PriceMoney, PriceRangeFilter, ProductCard, ProductDetailPage, ProductGallery, ProductJsonLd, ProfileEditor, ShippingPicker, SortSelect, StockBadge, TagFilter, ThemeProvider, VariantPicker, absoluteUrl, bundledMessages, cs, en, formatCents, formatMoney, serializeJsonLd, stripHtml, themeToCssVars, useAccount, useCart, useCheckout, useI18n, useMagicLinkVerify, useOrderStatus, useStorefront, useStorefrontOptional, useT };
3406
+ export { AccountDashboard, AccountProvider, AddToCart, AddressBook, AddressForm, AvailabilityFilter, BackInStockForm, BreadcrumbJsonLd, CartDrawer, CartLineItem, CartProvider, CartSummary, CheckoutFlow, CheckoutProvider, CollectionPage, I18nProvider, JsonLd, LoginForm, MarketSwitcher, NeviosProvider, OrderConfirmation, OrderHistory, OrderLines, OrderStatusPage, OrderSummary, OrderTotals, OrganizationJsonLd, PaymentMethodPicker, PaymentPage, PaymentSection, PhaseTimeline, PriceMoney, PriceRangeFilter, ProductCard, ProductDetailPage, ProductGallery, ProductJsonLd, ProfileEditor, ShippingPicker, SortSelect, StatusBanner, StockBadge, TagFilter, ThemeProvider, TransferInstructions, VariantPicker, absoluteUrl, bundledMessages, cs, en, formatCents, formatMoney, isTerminal, serializeJsonLd, stripHtml, themeToCssVars, useAccount, useCart, useCheckout, useI18n, useMagicLinkVerify, useOrderStatus, usePayment, useStorefront, useStorefrontOptional, useT };
2978
3407
  //# sourceMappingURL=index.js.map
2979
3408
  //# sourceMappingURL=index.js.map