@asteby/storefront-react 0.11.1 → 0.12.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/payments.cjs CHANGED
@@ -171,7 +171,8 @@ function mercadoPagoBricksAdapter(options = {}) {
171
171
  // fila de método de pago y se veía cortado (p. ej. AMEX partido). hideFormTitle oculta
172
172
  // la línea del título y las banderas.
173
173
  hideFormTitle: true,
174
- style: { theme: "default", ...opts.theme && Object.keys(opts.theme).length ? { customVariables: opts.theme } : {} }
174
+ // Dark page: the Brick's `dark` base theme, so it does not draw a white card; the shop tokens refine it.
175
+ style: { theme: opts.colorScheme === "dark" ? "dark" : "default", ...opts.theme && Object.keys(opts.theme).length ? { customVariables: opts.theme } : {} }
175
176
  }
176
177
  },
177
178
  callbacks: {
@@ -221,7 +222,13 @@ var PAYMENT_THEME_TOKENS = {
221
222
  "--sf-pay-error": "errorColor",
222
223
  "--sf-pay-success": "successColor",
223
224
  "--sf-pay-outline": "outlinePrimaryColor",
224
- "--sf-pay-radius": "borderRadiusMedium"
225
+ "--sf-pay-radius": "borderRadiusMedium",
226
+ "--sf-pay-radius-small": "borderRadiusSmall",
227
+ "--sf-pay-radius-large": "borderRadiusLarge",
228
+ "--sf-pay-outline-secondary": "outlineSecondaryColor",
229
+ "--sf-pay-primary-variant": "baseColorFirstVariant",
230
+ "--sf-pay-input-border-width": "inputBorderWidth",
231
+ "--sf-pay-form-padding": "formPadding"
225
232
  };
226
233
  function readPaymentTheme(el, tokens = PAYMENT_THEME_TOKENS) {
227
234
  const out = {};
@@ -233,6 +240,19 @@ function readPaymentTheme(el, tokens = PAYMENT_THEME_TOKENS) {
233
240
  }
234
241
  return out;
235
242
  }
243
+ function readPaymentScheme(el) {
244
+ if (!el || typeof getComputedStyle !== "function") return null;
245
+ const style = getComputedStyle(el);
246
+ const token = style.getPropertyValue("--sf-pay-scheme").trim();
247
+ if (token === "dark" || token === "light") return token;
248
+ const cs = (style.getPropertyValue("color-scheme") || style.colorScheme || "").toLowerCase();
249
+ const dark = /\bdark\b/.test(cs);
250
+ const light = /\blight\b/.test(cs);
251
+ if (dark && !light) return "dark";
252
+ if (light && !dark) return "light";
253
+ if (dark && light && typeof matchMedia === "function") return matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
254
+ return null;
255
+ }
236
256
 
237
257
  // src/payments/use-embedded-payment.ts
238
258
  var DEFAULT_ADAPTERS = {};
@@ -358,6 +378,7 @@ function useEmbeddedPayment(options) {
358
378
  await adapter.load(client, { ...live.current.nonce ? { nonce: live.current.nonce } : {}, timeoutMs: sdkTimeoutMs });
359
379
  if (stale()) return;
360
380
  const theme = live.current.theme ?? readPaymentTheme(container);
381
+ const colorScheme = live.current.colorScheme ?? readPaymentScheme(container);
361
382
  const m = await adapter.mount({
362
383
  container,
363
384
  client,
@@ -365,6 +386,7 @@ function useEmbeddedPayment(options) {
365
386
  ...live.current.email ? { email: live.current.email } : {},
366
387
  ...live.current.locale ? { locale: live.current.locale } : {},
367
388
  theme,
389
+ ...colorScheme ? { colorScheme } : {},
368
390
  onReady: () => {
369
391
  if (stale()) return;
370
392
  clearTimeout(timer);
@@ -777,6 +799,8 @@ var ES = {
777
799
  legend: "M\xE9todo de pago",
778
800
  pay: "Pagar",
779
801
  placeOrder: "Realizar el pedido",
802
+ placeOrderCash: "Generar ficha de pago",
803
+ chooseDelivery: "Elige la entrega para continuar",
780
804
  payRedirect: "Continuar al pago",
781
805
  continue: "Continuar",
782
806
  retryAddress: "Guardar direcci\xF3n y continuar",
@@ -805,6 +829,8 @@ var EN = {
805
829
  legend: "Payment method",
806
830
  pay: "Pay",
807
831
  placeOrder: "Place order",
832
+ placeOrderCash: "Get payment slip",
833
+ chooseDelivery: "Choose delivery to continue",
808
834
  payRedirect: "Continue to payment",
809
835
  continue: "Continue",
810
836
  retryAddress: "Save address and continue",
@@ -1101,6 +1127,26 @@ function formFields(form) {
1101
1127
  for (const [k, v] of new FormData(form).entries()) if (typeof v === "string") out[k] = v;
1102
1128
  return out;
1103
1129
  }
1130
+ var CHOICE_STORAGE_PREFIX = "sf:pay-choice:";
1131
+ function readStoredChoice(scope) {
1132
+ if (!scope || typeof window === "undefined") return null;
1133
+ try {
1134
+ const raw = window.sessionStorage.getItem(CHOICE_STORAGE_PREFIX + scope);
1135
+ if (!raw) return null;
1136
+ const v = JSON.parse(raw);
1137
+ return typeof v?.choice === "string" ? v : null;
1138
+ } catch {
1139
+ return null;
1140
+ }
1141
+ }
1142
+ function writeStoredChoice(scope, value) {
1143
+ if (!scope || typeof window === "undefined") return;
1144
+ try {
1145
+ window.sessionStorage.setItem(CHOICE_STORAGE_PREFIX + scope, JSON.stringify(value));
1146
+ } catch {
1147
+ }
1148
+ }
1149
+ var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1104
1150
  var useHydrated = () => react.useSyncExternalStore(
1105
1151
  () => () => {
1106
1152
  },
@@ -1137,12 +1183,37 @@ function PaymentMethodSection(props) {
1137
1183
  classNames: cx = {},
1138
1184
  testIds = {},
1139
1185
  hideSubmit,
1140
- brickProps
1186
+ brickProps,
1187
+ rememberChoice = true,
1188
+ cashPointsVisible,
1189
+ payerEmailField = "email"
1141
1190
  } = props;
1142
1191
  const locale = useResolvedLocale(props.locale);
1143
1192
  const m = react.useMemo(() => paymentMethodMessages(locale, props.messages), [locale, props.messages]);
1144
1193
  const hydrated = useHydrated();
1145
- const [choiceValue2, setChoiceValue] = react.useState(props.defaultChoice ?? choices.defaultValue);
1194
+ const [choiceValue2, setChoiceValueState] = react.useState(props.defaultChoice ?? choices.defaultValue);
1195
+ const remember = rememberChoice && Boolean(scope);
1196
+ const rootRef = react.useRef(null);
1197
+ const [storedPoint, setStoredPoint] = react.useState(null);
1198
+ const setChoiceValue = (v) => {
1199
+ setChoiceValueState(v);
1200
+ if (remember) writeStoredChoice(scope, { choice: v, point: readCheckedPoint(rootRef.current) });
1201
+ };
1202
+ react.useEffect(() => {
1203
+ if (!remember) return;
1204
+ const stored = readStoredChoice(scope);
1205
+ if (!stored) return;
1206
+ if (choices.choices.some((c) => c.value === stored.choice)) setChoiceValueState(stored.choice);
1207
+ if (stored.point) setStoredPoint(stored.point);
1208
+ }, [remember, scope]);
1209
+ react.useEffect(() => {
1210
+ if (!storedPoint || !rootRef.current) return;
1211
+ const input = Array.from(rootRef.current.querySelectorAll(`input[name="${CASH_POINT_FIELD}"]`)).find((i) => i.value === storedPoint);
1212
+ if (input) input.checked = true;
1213
+ }, [storedPoint, choiceValue2]);
1214
+ const onPointChange = (point) => {
1215
+ if (remember && choiceValue2) writeStoredChoice(scope, { choice: choiceValue2, point });
1216
+ };
1146
1217
  const selected = choices.choices.find((c) => c.value === choiceValue2) ?? null;
1147
1218
  const noPayment = choices.choices.length === 0;
1148
1219
  const tid = (k) => testIds[k] ? { "data-testid": testIds[k] } : {};
@@ -1193,8 +1264,33 @@ function PaymentMethodSection(props) {
1193
1264
  }
1194
1265
  onEvent?.(e);
1195
1266
  };
1267
+ const [payerEmail, setPayerEmail] = react.useState(void 0);
1268
+ react.useEffect(() => {
1269
+ const form = formRef.current;
1270
+ if (!payerEmailField || !form) return;
1271
+ const selector = `[name="${typeof CSS !== "undefined" && CSS.escape ? CSS.escape(payerEmailField) : payerEmailField}"]`;
1272
+ const read = () => {
1273
+ const v = String(form.querySelector(selector)?.value ?? "").trim();
1274
+ setPayerEmail(EMAIL_RE.test(v) ? v : void 0);
1275
+ };
1276
+ read();
1277
+ const onChange = (e) => {
1278
+ if (e.target?.name === payerEmailField) read();
1279
+ };
1280
+ form.addEventListener("change", onChange);
1281
+ return () => form.removeEventListener("change", onChange);
1282
+ }, [formRef, payerEmailField]);
1196
1283
  const total = wantSync && (!settled || requote) ? null : renderAmount ? renderAmount(amount) : storefront.formatMoney(amount, locale);
1197
- const label = retryAddress ? m.retryAddress : addressFirst ? m.continue : selected?.mode === "plain" || !selected ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1284
+ const label = retryAddress ? m.retryAddress : addressFirst ? m.continue : !deliveryReady && selected ? (
1285
+ // Says what is missing instead of a button that silently does nothing.
1286
+ m.chooseDelivery
1287
+ ) : selected?.mode === "cash" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1288
+ m.placeOrderCash,
1289
+ total ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1290
+ " ",
1291
+ total
1292
+ ] }) : null
1293
+ ] }) : selected?.mode === "plain" || !selected ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1198
1294
  m.pay,
1199
1295
  total ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1200
1296
  " ",
@@ -1208,6 +1304,11 @@ function PaymentMethodSection(props) {
1208
1304
  ] }) : null
1209
1305
  ] });
1210
1306
  function onPayClick(event) {
1307
+ if (!addressFirst && !retryAddress && !deliveryReady && selected) {
1308
+ event.preventDefault();
1309
+ formRef.current?.reportValidity();
1310
+ return;
1311
+ }
1211
1312
  if (!cardActive || syncFailed) return;
1212
1313
  event.preventDefault();
1213
1314
  const form = formRef.current;
@@ -1216,7 +1317,7 @@ function PaymentMethodSection(props) {
1216
1317
  }
1217
1318
  const cardNotice = (k, node) => k === "syncFailed" ? /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: cx.alert, "data-sf-pay-notice": "sync-failed", ...tid("syncFailed"), children: node }) : /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", className: cx.status, "data-sf-pay-notice": k, ...k === "syncing" ? tid("syncing") : {}, children: node });
1218
1319
  const Link = renderLink ?? (({ href, className, children, testId }) => /* @__PURE__ */ jsxRuntime.jsx("a", { href, className, ...testId ? { "data-testid": testId } : {}, children }));
1219
- return /* @__PURE__ */ jsxRuntime.jsxs("fieldset", { id: "pago", className: cx.root, "data-sf-pay-method": true, children: [
1320
+ return /* @__PURE__ */ jsxRuntime.jsxs("fieldset", { id: "pago", ref: rootRef, className: cx.root, "data-sf-pay-method": true, children: [
1220
1321
  /* @__PURE__ */ jsxRuntime.jsx("legend", { className: cx.legend, children: m.legend }),
1221
1322
  choices.choices.map((choice) => {
1222
1323
  const { label: rowLabel, hint } = rowText(choice, m, methodLabel);
@@ -1256,38 +1357,40 @@ function PaymentMethodSection(props) {
1256
1357
  ] }),
1257
1358
  marks.length ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: cx.brands, "data-sf-pay-brands": choice.option ?? "plain", "aria-hidden": "true", children: marks.map((mk) => /* @__PURE__ */ jsxRuntime.jsx(BrandMark, { brand: mk.key, logoUrl: mk.logoUrl ?? null, size: 20 }, mk.key)) }) : null
1258
1359
  ] }),
1259
- /* @__PURE__ */ jsxRuntime.jsx(ChoicePanel, { choice, m, invalid, cx, tid, hidden: hydrated && !checked, marks: brandMarks })
1360
+ /* @__PURE__ */ jsxRuntime.jsx(ChoicePanel, { choice, m, invalid, cx, tid, hidden: hydrated && !checked, marks: brandMarks, visible: cashPointsVisible, openMore: storedPoint, onPointChange }),
1361
+ choice.mode === "card" && checked && cardChosen && hydrated && choices.cardClient ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cx.brickWrap, "data-sf-pay-card": true, ...tid("card"), children: [
1362
+ fallback ? null : !deliveryReady ? cardNotice("pickDelivery", m.pickDeliveryFirst) : syncFailed ? cardNotice("syncFailed", m.syncFailed) : requote ? cardNotice("requote", m.quoteChanged) : !synced ? cardNotice("syncing", m.updatingTotal) : null,
1363
+ brickReady || fallback ? /* @__PURE__ */ jsxRuntime.jsx(
1364
+ PaymentBrick,
1365
+ {
1366
+ ...brickProps,
1367
+ ref: brick,
1368
+ client: choices.cardClient,
1369
+ amount,
1370
+ transport,
1371
+ ...nonce ? { nonce } : {},
1372
+ locale,
1373
+ ...scope ? { scope } : {},
1374
+ ...payerEmail && !brickProps?.email ? { email: payerEmail } : {},
1375
+ enabled: brickReady,
1376
+ ...cx.brick ? { className: cx.brick } : {},
1377
+ onEvent: handleEvent,
1378
+ onFallback: () => setFallback(true),
1379
+ onResult: (o) => {
1380
+ if (o.kind === "approved" || o.kind === "in_review" || o.kind === "voucher") onDone(o);
1381
+ }
1382
+ },
1383
+ payerEmail || brickProps?.email ? "with-email" : "without-email"
1384
+ ) : null,
1385
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: cx.note, children: m.secureCard }),
1386
+ expired && rebuildHref ? Link({ href: rebuildHref, ...cx.rebuild ? { className: cx.rebuild } : {}, children: m.rebuildCart, ...testIds.rebuild ? { testId: testIds.rebuild } : {} }) : null
1387
+ ] }) : null
1260
1388
  ] }, choice.value);
1261
1389
  }),
1262
1390
  noPayment ? /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "alert", className: cx.alert, "data-sf-pay-notice": "no-payment", ...tid("noPayment"), children: [
1263
1391
  /* @__PURE__ */ jsxRuntime.jsx("p", { children: /* @__PURE__ */ jsxRuntime.jsx("strong", { children: m.noPaymentTitle }) }),
1264
1392
  /* @__PURE__ */ jsxRuntime.jsx("p", { children: m.noPayment })
1265
1393
  ] }) : null,
1266
- cardChosen && hydrated && choices.cardClient ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cx.brickWrap, "data-sf-pay-card": true, ...tid("card"), children: [
1267
- fallback ? null : !deliveryReady ? cardNotice("pickDelivery", m.pickDeliveryFirst) : syncFailed ? cardNotice("syncFailed", m.syncFailed) : requote ? cardNotice("requote", m.quoteChanged) : !synced ? cardNotice("syncing", m.updatingTotal) : null,
1268
- brickReady || fallback ? /* @__PURE__ */ jsxRuntime.jsx(
1269
- PaymentBrick,
1270
- {
1271
- ...brickProps,
1272
- ref: brick,
1273
- client: choices.cardClient,
1274
- amount,
1275
- transport,
1276
- ...nonce ? { nonce } : {},
1277
- locale,
1278
- ...scope ? { scope } : {},
1279
- enabled: brickReady,
1280
- ...cx.brick ? { className: cx.brick } : {},
1281
- onEvent: handleEvent,
1282
- onFallback: () => setFallback(true),
1283
- onResult: (o) => {
1284
- if (o.kind === "approved" || o.kind === "in_review" || o.kind === "voucher") onDone(o);
1285
- }
1286
- }
1287
- ) : null,
1288
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: cx.note, children: m.secureCard }),
1289
- expired && rebuildHref ? Link({ href: rebuildHref, ...cx.rebuild ? { className: cx.rebuild } : {}, children: m.rebuildCart, ...testIds.rebuild ? { testId: testIds.rebuild } : {} }) : null
1290
- ] }) : null,
1291
1394
  noPayment || choices.choices.some((c) => c.option) ? null : /* @__PURE__ */ jsxRuntime.jsx("p", { className: cx.note, children: m.paymentNote }),
1292
1395
  hideSubmit ? null : /* @__PURE__ */ jsxRuntime.jsx(
1293
1396
  "button",
@@ -1316,16 +1419,33 @@ function ChoicePanel({
1316
1419
  cx,
1317
1420
  tid,
1318
1421
  hidden,
1319
- marks = true
1422
+ marks = true,
1423
+ visible,
1424
+ openMore,
1425
+ onPointChange
1320
1426
  }) {
1321
1427
  if (choice.mode === "redirect") {
1322
1428
  return /* @__PURE__ */ jsxRuntime.jsx("p", { className: cx.panel, hidden, "data-sf-pay-panel": "redirect", ...tid("redirectNote"), children: m.redirectNote });
1323
1429
  }
1324
1430
  if (choice.mode !== "cash") return null;
1325
- const main = choice.points.filter((p) => p.group === "main");
1326
- const more = choice.points.filter((p) => p.group !== "main");
1431
+ const allMain = choice.points.filter((p) => p.group === "main");
1432
+ const cut = typeof visible === "number" && visible > 0 ? visible : allMain.length;
1433
+ const main = allMain.slice(0, cut);
1434
+ const more = [...allMain.slice(cut), ...choice.points.filter((p) => p.group !== "main")];
1435
+ const moreOpen = Boolean(openMore && more.some((p) => p.id === openMore));
1327
1436
  const row = (p, i, first) => /* @__PURE__ */ jsxRuntime.jsxs("label", { className: cx.cashPoint, "data-sf-pay-point": true, children: [
1328
- /* @__PURE__ */ jsxRuntime.jsx("input", { type: "radio", name: CASH_POINT_FIELD, value: p.id, defaultChecked: first && i === 0, className: cx.cashPointInput, "aria-invalid": invalid?.has("cash_point") || void 0 }),
1437
+ /* @__PURE__ */ jsxRuntime.jsx(
1438
+ "input",
1439
+ {
1440
+ type: "radio",
1441
+ name: CASH_POINT_FIELD,
1442
+ value: p.id,
1443
+ defaultChecked: first && i === 0,
1444
+ className: cx.cashPointInput,
1445
+ "aria-invalid": invalid?.has("cash_point") || void 0,
1446
+ ...onPointChange ? { onChange: () => onPointChange(p.id) } : {}
1447
+ }
1448
+ ),
1329
1449
  marks ? /* @__PURE__ */ jsxRuntime.jsx(BrandMark, { brand: p.payment_point || p.id, logoUrl: p.logo_url ?? null, size: 22 }) : null,
1330
1450
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: p.label })
1331
1451
  ] }, p.id);
@@ -1333,7 +1453,7 @@ function ChoicePanel({
1333
1453
  /* @__PURE__ */ jsxRuntime.jsxs("fieldset", { children: [
1334
1454
  /* @__PURE__ */ jsxRuntime.jsx("legend", { children: m.cashPoints }),
1335
1455
  /* @__PURE__ */ jsxRuntime.jsx("div", { "data-sf-pay-points": true, children: main.map((p, i) => row(p, i, true)) }),
1336
- more.length ? /* @__PURE__ */ jsxRuntime.jsxs("details", { children: [
1456
+ more.length ? /* @__PURE__ */ jsxRuntime.jsxs("details", { ...moreOpen ? { open: true } : {}, children: [
1337
1457
  /* @__PURE__ */ jsxRuntime.jsx("summary", { className: cx.cashMore, style: { minHeight: 44 }, children: m.cashMore }),
1338
1458
  /* @__PURE__ */ jsxRuntime.jsx("div", { "data-sf-pay-points": true, children: more.map((p, i) => row(p, i, false)) })
1339
1459
  ] }) : null
@@ -1349,6 +1469,10 @@ function defaultIcon(choice) {
1349
1469
  if (choice.option === "cash") return glyph(CASH_GLYPH);
1350
1470
  return findBrand(choice.methodKey) ? /* @__PURE__ */ jsxRuntime.jsx(BrandMark, { brand: choice.methodKey, size: 24 }) : null;
1351
1471
  }
1472
+ function readCheckedPoint(root) {
1473
+ const el = root?.querySelector(`input[name="${CASH_POINT_FIELD}"]:checked`);
1474
+ return el?.value ?? null;
1475
+ }
1352
1476
  function rowMarks(choice, cardBrands) {
1353
1477
  if (choice.option === "card") return cardBrands.map((key) => ({ key }));
1354
1478
  if (choice.option === "cash") {
@@ -1490,6 +1614,7 @@ exports.paymentCspMode = paymentCspMode;
1490
1614
  exports.paymentMethodMessages = paymentMethodMessages;
1491
1615
  exports.paymentNonce = paymentNonce;
1492
1616
  exports.paymentNonceFromHeaders = paymentNonceFromHeaders;
1617
+ exports.readPaymentScheme = readPaymentScheme;
1493
1618
  exports.readPaymentTheme = readPaymentTheme;
1494
1619
  exports.resetPaymentSdkCache = resetPaymentSdkCache;
1495
1620
  exports.sanitizeMercadoPagoFormData = sanitizeMercadoPagoFormData;