@asteby/storefront-react 0.11.0 → 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.css CHANGED
@@ -45,13 +45,26 @@
45
45
  display: inline-block;
46
46
  vertical-align: middle;
47
47
  }
48
+ /* The mark of a provider row (Mercado Pago…) is as wide as its logo: in line between the radio and the text, never over them. */
49
+ [data-sf-pay-method] [data-sf-pay-icon='mark'] {
50
+ display: inline-flex;
51
+ flex: none;
52
+ align-items: center;
53
+ width: auto;
54
+ height: auto;
55
+ }
48
56
  /* The open cash row already lists every point with its mark: the preview would repeat them. */
49
57
  [data-sf-pay-method] [data-sf-pay-choice]:has(input[name='payment_choice']:checked) [data-sf-pay-brands='cash'] {
50
58
  display: none;
51
59
  }
60
+ /* The card form opens inside its row: a little air between the row and the provider form. */
61
+ [data-sf-pay-method] [data-sf-pay-card] [data-sf-pay-container] {
62
+ margin-block: 0.25rem;
63
+ }
52
64
  @media (max-width: 24rem) {
53
65
  /* Narrow phones: the row text wins over the preview marks. */
66
+ /* !important: the logo plate sets its own inline `display`. */
54
67
  [data-sf-pay-method] [data-sf-pay-choice] [data-sf-pay-brands] [data-sf-brand-mark]:nth-child(n + 3) {
55
- display: none;
68
+ display: none !important;
56
69
  }
57
70
  }
@@ -33,6 +33,8 @@ type PaymentFormMountOptions = {
33
33
  locale?: string;
34
34
  /** Visual tokens read from CSS variables (see `readPaymentTheme`). */
35
35
  theme?: Record<string, string>;
36
+ /** Light or dark page (see `readPaymentScheme`): the adapter picks the provider's matching base theme. */
37
+ colorScheme?: 'light' | 'dark';
36
38
  onReady: () => void;
37
39
  onError: (error: PaymentFormError) => void;
38
40
  };
@@ -168,6 +170,8 @@ type UseEmbeddedPaymentOptions = {
168
170
  onEvent?: (event: PaymentEvent) => void;
169
171
  /** Overrides the theme read from `--sf-pay-*` tokens. */
170
172
  theme?: Record<string, string>;
173
+ /** Overrides the light / dark scheme read from the page (`--sf-pay-scheme` or `color-scheme`). */
174
+ colorScheme?: 'light' | 'dark';
171
175
  /** Test hooks. */
172
176
  newAttemptId?: () => string;
173
177
  };
@@ -326,6 +330,10 @@ type PaymentMethodMessages = {
326
330
  pay: string;
327
331
  /** Button label for card and cash, before the total. */
328
332
  placeOrder: string;
333
+ /** Button label for cash (the buyer gets a payment slip and pays later), before the total. */
334
+ placeOrderCash: string;
335
+ /** Button label while no delivery is chosen yet. */
336
+ chooseDelivery: string;
329
337
  /** Button label when the buyer leaves for the provider's page. */
330
338
  payRedirect: string;
331
339
  /** Button label for the first step (address) of a two-step checkout. */
@@ -443,6 +451,22 @@ type PaymentMethodSectionProps = {
443
451
  transport?: PaymentTransport;
444
452
  /** Extra props for the card form (`adapter`, `adapters`, `messages`, `minHeight`, `storage`, timeouts...). */
445
453
  brickProps?: Partial<Omit<PaymentBrickProps, 'client' | 'amount' | 'transport' | 'onResult' | 'onEvent' | 'onFallback'>>;
454
+ /**
455
+ * Remember the chosen row (and cash point) for this `scope` in `sessionStorage`, so a reload, the "back" button from
456
+ * the provider or the address step does not ask to choose again. Used only while that row is still published.
457
+ * Default true (needs `scope`); `false` turns it off. Nothing personal is stored: only the row value.
458
+ */
459
+ rememberChoice?: boolean;
460
+ /**
461
+ * Cash points shown before "more payment points". Default: every point the provider marks `main`. With a number, the
462
+ * first N `main` points show and the rest join the "more" list (a remembered point inside it opens the list).
463
+ */
464
+ cashPointsVisible?: number;
465
+ /**
466
+ * Name of the page form field with the buyer's e-mail. Its value is handed to the card form, so the provider does not
467
+ * ask for the e-mail again. Default `email`; `null` to never pass it. `brickProps.email` wins.
468
+ */
469
+ payerEmailField?: string | null;
446
470
  };
447
471
  /**
448
472
  * "Payment method", hybrid. Card: the provider's secure form INSIDE the page (`PaymentBrick`) with the shop's own
@@ -470,11 +494,23 @@ declare const PAYMENT_THEME_TOKENS: {
470
494
  readonly '--sf-pay-success': "successColor";
471
495
  readonly '--sf-pay-outline': "outlinePrimaryColor";
472
496
  readonly '--sf-pay-radius': "borderRadiusMedium";
497
+ readonly '--sf-pay-radius-small': "borderRadiusSmall";
498
+ readonly '--sf-pay-radius-large': "borderRadiusLarge";
499
+ readonly '--sf-pay-outline-secondary': "outlineSecondaryColor";
500
+ readonly '--sf-pay-primary-variant': "baseColorFirstVariant";
501
+ readonly '--sf-pay-input-border-width': "inputBorderWidth";
502
+ readonly '--sf-pay-form-padding': "formPadding";
473
503
  };
474
504
  /**
475
505
  * Reads the `--sf-pay-*` tokens from an element's computed style, so the provider form follows the shop's
476
506
  * theme without the shop copying colors into JavaScript. Unsafe or empty values are skipped.
477
507
  */
478
508
  declare function readPaymentTheme(el: Element | null | undefined, tokens?: Record<string, string>): Record<string, string>;
509
+ /**
510
+ * Light or dark for the provider form, so it does not paint a white card inside a dark page. Order: the
511
+ * `--sf-pay-scheme` token (`light` / `dark`), then the element's `color-scheme` (`light dark` follows the system
512
+ * preference). `null` when nothing says it (the provider default).
513
+ */
514
+ declare function readPaymentScheme(el: Element | null | undefined): 'light' | 'dark' | null;
479
515
 
480
- export { CASH_POINT_FIELD, type EmbeddedPaymentState, type EmbeddedPaymentStatus, type FetchTransportOptions, type LoadScriptOptions, MERCADOPAGO_BRICKS_KIND, type MercadoPagoAdapterOptions, type MercadoPagoCtor, type MountedPaymentForm, PAYMENT_CHOICE_FIELD, PAYMENT_THEME_TOKENS, PayChoices, PaymentBrick, type PaymentBrickHandle, type PaymentBrickProps, PaymentChallenge, type PaymentChallengeProps, type PaymentEvent, type PaymentFallbackReason, type PaymentFormAdapter, type PaymentFormData, type PaymentFormError, type PaymentFormMountOptions, PaymentHttpError, type PaymentMethodClassNames, type PaymentMethodMessages, PaymentMethodSection, type PaymentMethodSectionProps, type PaymentMethodTestIds, type PaymentSubmitRequest, type PaymentTransport, type UseEmbeddedPaymentOptions, VoucherCard, type VoucherCardProps, createFetchTransport, formFields, loadPaymentSdk, mercadoPagoBricksAdapter, paymentMethodMessages, readPaymentTheme, resetPaymentSdkCache, sanitizeMercadoPagoFormData, useEmbeddedPayment };
516
+ export { CASH_POINT_FIELD, type EmbeddedPaymentState, type EmbeddedPaymentStatus, type FetchTransportOptions, type LoadScriptOptions, MERCADOPAGO_BRICKS_KIND, type MercadoPagoAdapterOptions, type MercadoPagoCtor, type MountedPaymentForm, PAYMENT_CHOICE_FIELD, PAYMENT_THEME_TOKENS, PayChoices, PaymentBrick, type PaymentBrickHandle, type PaymentBrickProps, PaymentChallenge, type PaymentChallengeProps, type PaymentEvent, type PaymentFallbackReason, type PaymentFormAdapter, type PaymentFormData, type PaymentFormError, type PaymentFormMountOptions, PaymentHttpError, type PaymentMethodClassNames, type PaymentMethodMessages, PaymentMethodSection, type PaymentMethodSectionProps, type PaymentMethodTestIds, type PaymentSubmitRequest, type PaymentTransport, type UseEmbeddedPaymentOptions, VoucherCard, type VoucherCardProps, createFetchTransport, formFields, loadPaymentSdk, mercadoPagoBricksAdapter, paymentMethodMessages, readPaymentScheme, readPaymentTheme, resetPaymentSdkCache, sanitizeMercadoPagoFormData, useEmbeddedPayment };
@@ -33,6 +33,8 @@ type PaymentFormMountOptions = {
33
33
  locale?: string;
34
34
  /** Visual tokens read from CSS variables (see `readPaymentTheme`). */
35
35
  theme?: Record<string, string>;
36
+ /** Light or dark page (see `readPaymentScheme`): the adapter picks the provider's matching base theme. */
37
+ colorScheme?: 'light' | 'dark';
36
38
  onReady: () => void;
37
39
  onError: (error: PaymentFormError) => void;
38
40
  };
@@ -168,6 +170,8 @@ type UseEmbeddedPaymentOptions = {
168
170
  onEvent?: (event: PaymentEvent) => void;
169
171
  /** Overrides the theme read from `--sf-pay-*` tokens. */
170
172
  theme?: Record<string, string>;
173
+ /** Overrides the light / dark scheme read from the page (`--sf-pay-scheme` or `color-scheme`). */
174
+ colorScheme?: 'light' | 'dark';
171
175
  /** Test hooks. */
172
176
  newAttemptId?: () => string;
173
177
  };
@@ -326,6 +330,10 @@ type PaymentMethodMessages = {
326
330
  pay: string;
327
331
  /** Button label for card and cash, before the total. */
328
332
  placeOrder: string;
333
+ /** Button label for cash (the buyer gets a payment slip and pays later), before the total. */
334
+ placeOrderCash: string;
335
+ /** Button label while no delivery is chosen yet. */
336
+ chooseDelivery: string;
329
337
  /** Button label when the buyer leaves for the provider's page. */
330
338
  payRedirect: string;
331
339
  /** Button label for the first step (address) of a two-step checkout. */
@@ -443,6 +451,22 @@ type PaymentMethodSectionProps = {
443
451
  transport?: PaymentTransport;
444
452
  /** Extra props for the card form (`adapter`, `adapters`, `messages`, `minHeight`, `storage`, timeouts...). */
445
453
  brickProps?: Partial<Omit<PaymentBrickProps, 'client' | 'amount' | 'transport' | 'onResult' | 'onEvent' | 'onFallback'>>;
454
+ /**
455
+ * Remember the chosen row (and cash point) for this `scope` in `sessionStorage`, so a reload, the "back" button from
456
+ * the provider or the address step does not ask to choose again. Used only while that row is still published.
457
+ * Default true (needs `scope`); `false` turns it off. Nothing personal is stored: only the row value.
458
+ */
459
+ rememberChoice?: boolean;
460
+ /**
461
+ * Cash points shown before "more payment points". Default: every point the provider marks `main`. With a number, the
462
+ * first N `main` points show and the rest join the "more" list (a remembered point inside it opens the list).
463
+ */
464
+ cashPointsVisible?: number;
465
+ /**
466
+ * Name of the page form field with the buyer's e-mail. Its value is handed to the card form, so the provider does not
467
+ * ask for the e-mail again. Default `email`; `null` to never pass it. `brickProps.email` wins.
468
+ */
469
+ payerEmailField?: string | null;
446
470
  };
447
471
  /**
448
472
  * "Payment method", hybrid. Card: the provider's secure form INSIDE the page (`PaymentBrick`) with the shop's own
@@ -470,11 +494,23 @@ declare const PAYMENT_THEME_TOKENS: {
470
494
  readonly '--sf-pay-success': "successColor";
471
495
  readonly '--sf-pay-outline': "outlinePrimaryColor";
472
496
  readonly '--sf-pay-radius': "borderRadiusMedium";
497
+ readonly '--sf-pay-radius-small': "borderRadiusSmall";
498
+ readonly '--sf-pay-radius-large': "borderRadiusLarge";
499
+ readonly '--sf-pay-outline-secondary': "outlineSecondaryColor";
500
+ readonly '--sf-pay-primary-variant': "baseColorFirstVariant";
501
+ readonly '--sf-pay-input-border-width': "inputBorderWidth";
502
+ readonly '--sf-pay-form-padding': "formPadding";
473
503
  };
474
504
  /**
475
505
  * Reads the `--sf-pay-*` tokens from an element's computed style, so the provider form follows the shop's
476
506
  * theme without the shop copying colors into JavaScript. Unsafe or empty values are skipped.
477
507
  */
478
508
  declare function readPaymentTheme(el: Element | null | undefined, tokens?: Record<string, string>): Record<string, string>;
509
+ /**
510
+ * Light or dark for the provider form, so it does not paint a white card inside a dark page. Order: the
511
+ * `--sf-pay-scheme` token (`light` / `dark`), then the element's `color-scheme` (`light dark` follows the system
512
+ * preference). `null` when nothing says it (the provider default).
513
+ */
514
+ declare function readPaymentScheme(el: Element | null | undefined): 'light' | 'dark' | null;
479
515
 
480
- export { CASH_POINT_FIELD, type EmbeddedPaymentState, type EmbeddedPaymentStatus, type FetchTransportOptions, type LoadScriptOptions, MERCADOPAGO_BRICKS_KIND, type MercadoPagoAdapterOptions, type MercadoPagoCtor, type MountedPaymentForm, PAYMENT_CHOICE_FIELD, PAYMENT_THEME_TOKENS, PayChoices, PaymentBrick, type PaymentBrickHandle, type PaymentBrickProps, PaymentChallenge, type PaymentChallengeProps, type PaymentEvent, type PaymentFallbackReason, type PaymentFormAdapter, type PaymentFormData, type PaymentFormError, type PaymentFormMountOptions, PaymentHttpError, type PaymentMethodClassNames, type PaymentMethodMessages, PaymentMethodSection, type PaymentMethodSectionProps, type PaymentMethodTestIds, type PaymentSubmitRequest, type PaymentTransport, type UseEmbeddedPaymentOptions, VoucherCard, type VoucherCardProps, createFetchTransport, formFields, loadPaymentSdk, mercadoPagoBricksAdapter, paymentMethodMessages, readPaymentTheme, resetPaymentSdkCache, sanitizeMercadoPagoFormData, useEmbeddedPayment };
516
+ export { CASH_POINT_FIELD, type EmbeddedPaymentState, type EmbeddedPaymentStatus, type FetchTransportOptions, type LoadScriptOptions, MERCADOPAGO_BRICKS_KIND, type MercadoPagoAdapterOptions, type MercadoPagoCtor, type MountedPaymentForm, PAYMENT_CHOICE_FIELD, PAYMENT_THEME_TOKENS, PayChoices, PaymentBrick, type PaymentBrickHandle, type PaymentBrickProps, PaymentChallenge, type PaymentChallengeProps, type PaymentEvent, type PaymentFallbackReason, type PaymentFormAdapter, type PaymentFormData, type PaymentFormError, type PaymentFormMountOptions, PaymentHttpError, type PaymentMethodClassNames, type PaymentMethodMessages, PaymentMethodSection, type PaymentMethodSectionProps, type PaymentMethodTestIds, type PaymentSubmitRequest, type PaymentTransport, type UseEmbeddedPaymentOptions, VoucherCard, type VoucherCardProps, createFetchTransport, formFields, loadPaymentSdk, mercadoPagoBricksAdapter, paymentMethodMessages, readPaymentScheme, readPaymentTheme, resetPaymentSdkCache, sanitizeMercadoPagoFormData, useEmbeddedPayment };
package/dist/payments.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { isSafeCssValue } from './chunk-672OTUM2.js';
2
- import { BrandMark, findBrand } from './chunk-3ZJTDFHE.js';
2
+ import { findBrand, BrandMark } from './chunk-ODIQR6TG.js';
3
3
  export { buildPayChoices, cashProviderData, checkoutToAttempt, choiceValue, parseChoice, payErrorResponse, paymentCsp, paymentCspHeaderName, paymentCspHeaders, paymentCspMode, paymentNonce, paymentNonceFromHeaders, voucherRows } from './chunk-ISEOCKIR.js';
4
4
  import { useModalDialog } from './chunk-ZBH7JYU4.js';
5
5
  import { useResolvedLocale } from './chunk-T52H5HL3.js';
@@ -108,7 +108,8 @@ function mercadoPagoBricksAdapter(options = {}) {
108
108
  // fila de método de pago y se veía cortado (p. ej. AMEX partido). hideFormTitle oculta
109
109
  // la línea del título y las banderas.
110
110
  hideFormTitle: true,
111
- style: { theme: "default", ...opts.theme && Object.keys(opts.theme).length ? { customVariables: opts.theme } : {} }
111
+ // Dark page: the Brick's `dark` base theme, so it does not draw a white card; the shop tokens refine it.
112
+ style: { theme: opts.colorScheme === "dark" ? "dark" : "default", ...opts.theme && Object.keys(opts.theme).length ? { customVariables: opts.theme } : {} }
112
113
  }
113
114
  },
114
115
  callbacks: {
@@ -152,7 +153,13 @@ var PAYMENT_THEME_TOKENS = {
152
153
  "--sf-pay-error": "errorColor",
153
154
  "--sf-pay-success": "successColor",
154
155
  "--sf-pay-outline": "outlinePrimaryColor",
155
- "--sf-pay-radius": "borderRadiusMedium"
156
+ "--sf-pay-radius": "borderRadiusMedium",
157
+ "--sf-pay-radius-small": "borderRadiusSmall",
158
+ "--sf-pay-radius-large": "borderRadiusLarge",
159
+ "--sf-pay-outline-secondary": "outlineSecondaryColor",
160
+ "--sf-pay-primary-variant": "baseColorFirstVariant",
161
+ "--sf-pay-input-border-width": "inputBorderWidth",
162
+ "--sf-pay-form-padding": "formPadding"
156
163
  };
157
164
  function readPaymentTheme(el, tokens = PAYMENT_THEME_TOKENS) {
158
165
  const out = {};
@@ -164,6 +171,19 @@ function readPaymentTheme(el, tokens = PAYMENT_THEME_TOKENS) {
164
171
  }
165
172
  return out;
166
173
  }
174
+ function readPaymentScheme(el) {
175
+ if (!el || typeof getComputedStyle !== "function") return null;
176
+ const style = getComputedStyle(el);
177
+ const token = style.getPropertyValue("--sf-pay-scheme").trim();
178
+ if (token === "dark" || token === "light") return token;
179
+ const cs = (style.getPropertyValue("color-scheme") || style.colorScheme || "").toLowerCase();
180
+ const dark = /\bdark\b/.test(cs);
181
+ const light = /\blight\b/.test(cs);
182
+ if (dark && !light) return "dark";
183
+ if (light && !dark) return "light";
184
+ if (dark && light && typeof matchMedia === "function") return matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
185
+ return null;
186
+ }
167
187
 
168
188
  // src/payments/use-embedded-payment.ts
169
189
  var DEFAULT_ADAPTERS = {};
@@ -289,6 +309,7 @@ function useEmbeddedPayment(options) {
289
309
  await adapter.load(client, { ...live.current.nonce ? { nonce: live.current.nonce } : {}, timeoutMs: sdkTimeoutMs });
290
310
  if (stale()) return;
291
311
  const theme = live.current.theme ?? readPaymentTheme(container);
312
+ const colorScheme = live.current.colorScheme ?? readPaymentScheme(container);
292
313
  const m = await adapter.mount({
293
314
  container,
294
315
  client,
@@ -296,6 +317,7 @@ function useEmbeddedPayment(options) {
296
317
  ...live.current.email ? { email: live.current.email } : {},
297
318
  ...live.current.locale ? { locale: live.current.locale } : {},
298
319
  theme,
320
+ ...colorScheme ? { colorScheme } : {},
299
321
  onReady: () => {
300
322
  if (stale()) return;
301
323
  clearTimeout(timer);
@@ -649,6 +671,8 @@ var ES = {
649
671
  legend: "M\xE9todo de pago",
650
672
  pay: "Pagar",
651
673
  placeOrder: "Realizar el pedido",
674
+ placeOrderCash: "Generar ficha de pago",
675
+ chooseDelivery: "Elige la entrega para continuar",
652
676
  payRedirect: "Continuar al pago",
653
677
  continue: "Continuar",
654
678
  retryAddress: "Guardar direcci\xF3n y continuar",
@@ -677,6 +701,8 @@ var EN = {
677
701
  legend: "Payment method",
678
702
  pay: "Pay",
679
703
  placeOrder: "Place order",
704
+ placeOrderCash: "Get payment slip",
705
+ chooseDelivery: "Choose delivery to continue",
680
706
  payRedirect: "Continue to payment",
681
707
  continue: "Continue",
682
708
  retryAddress: "Save address and continue",
@@ -715,6 +741,26 @@ function formFields(form) {
715
741
  for (const [k, v] of new FormData(form).entries()) if (typeof v === "string") out[k] = v;
716
742
  return out;
717
743
  }
744
+ var CHOICE_STORAGE_PREFIX = "sf:pay-choice:";
745
+ function readStoredChoice(scope) {
746
+ if (!scope || typeof window === "undefined") return null;
747
+ try {
748
+ const raw = window.sessionStorage.getItem(CHOICE_STORAGE_PREFIX + scope);
749
+ if (!raw) return null;
750
+ const v = JSON.parse(raw);
751
+ return typeof v?.choice === "string" ? v : null;
752
+ } catch {
753
+ return null;
754
+ }
755
+ }
756
+ function writeStoredChoice(scope, value) {
757
+ if (!scope || typeof window === "undefined") return;
758
+ try {
759
+ window.sessionStorage.setItem(CHOICE_STORAGE_PREFIX + scope, JSON.stringify(value));
760
+ } catch {
761
+ }
762
+ }
763
+ var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
718
764
  var useHydrated = () => useSyncExternalStore(
719
765
  () => () => {
720
766
  },
@@ -751,12 +797,37 @@ function PaymentMethodSection(props) {
751
797
  classNames: cx = {},
752
798
  testIds = {},
753
799
  hideSubmit,
754
- brickProps
800
+ brickProps,
801
+ rememberChoice = true,
802
+ cashPointsVisible,
803
+ payerEmailField = "email"
755
804
  } = props;
756
805
  const locale = useResolvedLocale(props.locale);
757
806
  const m = useMemo(() => paymentMethodMessages(locale, props.messages), [locale, props.messages]);
758
807
  const hydrated = useHydrated();
759
- const [choiceValue2, setChoiceValue] = useState(props.defaultChoice ?? choices.defaultValue);
808
+ const [choiceValue2, setChoiceValueState] = useState(props.defaultChoice ?? choices.defaultValue);
809
+ const remember = rememberChoice && Boolean(scope);
810
+ const rootRef = useRef(null);
811
+ const [storedPoint, setStoredPoint] = useState(null);
812
+ const setChoiceValue = (v) => {
813
+ setChoiceValueState(v);
814
+ if (remember) writeStoredChoice(scope, { choice: v, point: readCheckedPoint(rootRef.current) });
815
+ };
816
+ useEffect(() => {
817
+ if (!remember) return;
818
+ const stored = readStoredChoice(scope);
819
+ if (!stored) return;
820
+ if (choices.choices.some((c) => c.value === stored.choice)) setChoiceValueState(stored.choice);
821
+ if (stored.point) setStoredPoint(stored.point);
822
+ }, [remember, scope]);
823
+ useEffect(() => {
824
+ if (!storedPoint || !rootRef.current) return;
825
+ const input = Array.from(rootRef.current.querySelectorAll(`input[name="${CASH_POINT_FIELD}"]`)).find((i) => i.value === storedPoint);
826
+ if (input) input.checked = true;
827
+ }, [storedPoint, choiceValue2]);
828
+ const onPointChange = (point) => {
829
+ if (remember && choiceValue2) writeStoredChoice(scope, { choice: choiceValue2, point });
830
+ };
760
831
  const selected = choices.choices.find((c) => c.value === choiceValue2) ?? null;
761
832
  const noPayment = choices.choices.length === 0;
762
833
  const tid = (k) => testIds[k] ? { "data-testid": testIds[k] } : {};
@@ -807,8 +878,33 @@ function PaymentMethodSection(props) {
807
878
  }
808
879
  onEvent?.(e);
809
880
  };
881
+ const [payerEmail, setPayerEmail] = useState(void 0);
882
+ useEffect(() => {
883
+ const form = formRef.current;
884
+ if (!payerEmailField || !form) return;
885
+ const selector = `[name="${typeof CSS !== "undefined" && CSS.escape ? CSS.escape(payerEmailField) : payerEmailField}"]`;
886
+ const read = () => {
887
+ const v = String(form.querySelector(selector)?.value ?? "").trim();
888
+ setPayerEmail(EMAIL_RE.test(v) ? v : void 0);
889
+ };
890
+ read();
891
+ const onChange = (e) => {
892
+ if (e.target?.name === payerEmailField) read();
893
+ };
894
+ form.addEventListener("change", onChange);
895
+ return () => form.removeEventListener("change", onChange);
896
+ }, [formRef, payerEmailField]);
810
897
  const total = wantSync && (!settled || requote) ? null : renderAmount ? renderAmount(amount) : formatMoney(amount, locale);
811
- const label = retryAddress ? m.retryAddress : addressFirst ? m.continue : selected?.mode === "plain" || !selected ? /* @__PURE__ */ jsxs(Fragment, { children: [
898
+ const label = retryAddress ? m.retryAddress : addressFirst ? m.continue : !deliveryReady && selected ? (
899
+ // Says what is missing instead of a button that silently does nothing.
900
+ m.chooseDelivery
901
+ ) : selected?.mode === "cash" ? /* @__PURE__ */ jsxs(Fragment, { children: [
902
+ m.placeOrderCash,
903
+ total ? /* @__PURE__ */ jsxs(Fragment, { children: [
904
+ " ",
905
+ total
906
+ ] }) : null
907
+ ] }) : selected?.mode === "plain" || !selected ? /* @__PURE__ */ jsxs(Fragment, { children: [
812
908
  m.pay,
813
909
  total ? /* @__PURE__ */ jsxs(Fragment, { children: [
814
910
  " ",
@@ -822,6 +918,11 @@ function PaymentMethodSection(props) {
822
918
  ] }) : null
823
919
  ] });
824
920
  function onPayClick(event) {
921
+ if (!addressFirst && !retryAddress && !deliveryReady && selected) {
922
+ event.preventDefault();
923
+ formRef.current?.reportValidity();
924
+ return;
925
+ }
825
926
  if (!cardActive || syncFailed) return;
826
927
  event.preventDefault();
827
928
  const form = formRef.current;
@@ -830,7 +931,7 @@ function PaymentMethodSection(props) {
830
931
  }
831
932
  const cardNotice = (k, node) => k === "syncFailed" ? /* @__PURE__ */ jsx("p", { role: "alert", className: cx.alert, "data-sf-pay-notice": "sync-failed", ...tid("syncFailed"), children: node }) : /* @__PURE__ */ jsx("p", { role: "status", className: cx.status, "data-sf-pay-notice": k, ...k === "syncing" ? tid("syncing") : {}, children: node });
832
933
  const Link = renderLink ?? (({ href, className, children, testId }) => /* @__PURE__ */ jsx("a", { href, className, ...testId ? { "data-testid": testId } : {}, children }));
833
- return /* @__PURE__ */ jsxs("fieldset", { id: "pago", className: cx.root, "data-sf-pay-method": true, children: [
934
+ return /* @__PURE__ */ jsxs("fieldset", { id: "pago", ref: rootRef, className: cx.root, "data-sf-pay-method": true, children: [
834
935
  /* @__PURE__ */ jsx("legend", { className: cx.legend, children: m.legend }),
835
936
  choices.choices.map((choice) => {
836
937
  const { label: rowLabel, hint } = rowText(choice, m, methodLabel);
@@ -838,6 +939,7 @@ function PaymentMethodSection(props) {
838
939
  const slot = choice.option ?? "plain";
839
940
  const icon = icons?.[slot] ?? (brandMarks ? defaultIcon(choice) : null);
840
941
  const marks = brandMarks ? rowMarks(choice, cardBrands) : [];
942
+ const brandMarkIcon = !icons?.[slot] && brandMarks && choice.option !== "card" && choice.option !== "cash" && !!findBrand(choice.methodKey);
841
943
  return /* @__PURE__ */ jsxs("div", { className: cx.choice, "data-sf-pay-choice": true, children: [
842
944
  /* @__PURE__ */ jsxs("label", { className: cx.radioLabel, children: [
843
945
  /* @__PURE__ */ jsx(
@@ -855,7 +957,11 @@ function PaymentMethodSection(props) {
855
957
  ...tid("method")
856
958
  }
857
959
  ),
858
- icon ? /* @__PURE__ */ jsx("span", { className: cx.icon, "aria-hidden": "true", children: icon }) : null,
960
+ icon ? (
961
+ // The default mark of a provider row is as wide as its logo: it does not take the shop's `icon` box (a fixed
962
+ // square would let the logo spill over the radio and the text).
963
+ /* @__PURE__ */ jsx("span", { className: brandMarkIcon ? void 0 : cx.icon, "data-sf-pay-icon": brandMarkIcon ? "mark" : void 0, "aria-hidden": "true", children: icon })
964
+ ) : null,
859
965
  /* @__PURE__ */ jsxs("span", { className: cx.labelText, children: [
860
966
  rowLabel,
861
967
  hint ? /* @__PURE__ */ jsxs("span", { className: cx.hint, children: [
@@ -865,38 +971,40 @@ function PaymentMethodSection(props) {
865
971
  ] }),
866
972
  marks.length ? /* @__PURE__ */ jsx("span", { className: cx.brands, "data-sf-pay-brands": choice.option ?? "plain", "aria-hidden": "true", children: marks.map((mk) => /* @__PURE__ */ jsx(BrandMark, { brand: mk.key, logoUrl: mk.logoUrl ?? null, size: 20 }, mk.key)) }) : null
867
973
  ] }),
868
- /* @__PURE__ */ jsx(ChoicePanel, { choice, m, invalid, cx, tid, hidden: hydrated && !checked, marks: brandMarks })
974
+ /* @__PURE__ */ jsx(ChoicePanel, { choice, m, invalid, cx, tid, hidden: hydrated && !checked, marks: brandMarks, visible: cashPointsVisible, openMore: storedPoint, onPointChange }),
975
+ choice.mode === "card" && checked && cardChosen && hydrated && choices.cardClient ? /* @__PURE__ */ jsxs("div", { className: cx.brickWrap, "data-sf-pay-card": true, ...tid("card"), children: [
976
+ fallback ? null : !deliveryReady ? cardNotice("pickDelivery", m.pickDeliveryFirst) : syncFailed ? cardNotice("syncFailed", m.syncFailed) : requote ? cardNotice("requote", m.quoteChanged) : !synced ? cardNotice("syncing", m.updatingTotal) : null,
977
+ brickReady || fallback ? /* @__PURE__ */ jsx(
978
+ PaymentBrick,
979
+ {
980
+ ...brickProps,
981
+ ref: brick,
982
+ client: choices.cardClient,
983
+ amount,
984
+ transport,
985
+ ...nonce ? { nonce } : {},
986
+ locale,
987
+ ...scope ? { scope } : {},
988
+ ...payerEmail && !brickProps?.email ? { email: payerEmail } : {},
989
+ enabled: brickReady,
990
+ ...cx.brick ? { className: cx.brick } : {},
991
+ onEvent: handleEvent,
992
+ onFallback: () => setFallback(true),
993
+ onResult: (o) => {
994
+ if (o.kind === "approved" || o.kind === "in_review" || o.kind === "voucher") onDone(o);
995
+ }
996
+ },
997
+ payerEmail || brickProps?.email ? "with-email" : "without-email"
998
+ ) : null,
999
+ /* @__PURE__ */ jsx("p", { className: cx.note, children: m.secureCard }),
1000
+ expired && rebuildHref ? Link({ href: rebuildHref, ...cx.rebuild ? { className: cx.rebuild } : {}, children: m.rebuildCart, ...testIds.rebuild ? { testId: testIds.rebuild } : {} }) : null
1001
+ ] }) : null
869
1002
  ] }, choice.value);
870
1003
  }),
871
1004
  noPayment ? /* @__PURE__ */ jsxs("div", { role: "alert", className: cx.alert, "data-sf-pay-notice": "no-payment", ...tid("noPayment"), children: [
872
1005
  /* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsx("strong", { children: m.noPaymentTitle }) }),
873
1006
  /* @__PURE__ */ jsx("p", { children: m.noPayment })
874
1007
  ] }) : null,
875
- cardChosen && hydrated && choices.cardClient ? /* @__PURE__ */ jsxs("div", { className: cx.brickWrap, "data-sf-pay-card": true, ...tid("card"), children: [
876
- fallback ? null : !deliveryReady ? cardNotice("pickDelivery", m.pickDeliveryFirst) : syncFailed ? cardNotice("syncFailed", m.syncFailed) : requote ? cardNotice("requote", m.quoteChanged) : !synced ? cardNotice("syncing", m.updatingTotal) : null,
877
- brickReady || fallback ? /* @__PURE__ */ jsx(
878
- PaymentBrick,
879
- {
880
- ...brickProps,
881
- ref: brick,
882
- client: choices.cardClient,
883
- amount,
884
- transport,
885
- ...nonce ? { nonce } : {},
886
- locale,
887
- ...scope ? { scope } : {},
888
- enabled: brickReady,
889
- ...cx.brick ? { className: cx.brick } : {},
890
- onEvent: handleEvent,
891
- onFallback: () => setFallback(true),
892
- onResult: (o) => {
893
- if (o.kind === "approved" || o.kind === "in_review" || o.kind === "voucher") onDone(o);
894
- }
895
- }
896
- ) : null,
897
- /* @__PURE__ */ jsx("p", { className: cx.note, children: m.secureCard }),
898
- expired && rebuildHref ? Link({ href: rebuildHref, ...cx.rebuild ? { className: cx.rebuild } : {}, children: m.rebuildCart, ...testIds.rebuild ? { testId: testIds.rebuild } : {} }) : null
899
- ] }) : null,
900
1008
  noPayment || choices.choices.some((c) => c.option) ? null : /* @__PURE__ */ jsx("p", { className: cx.note, children: m.paymentNote }),
901
1009
  hideSubmit ? null : /* @__PURE__ */ jsx(
902
1010
  "button",
@@ -925,16 +1033,33 @@ function ChoicePanel({
925
1033
  cx,
926
1034
  tid,
927
1035
  hidden,
928
- marks = true
1036
+ marks = true,
1037
+ visible,
1038
+ openMore,
1039
+ onPointChange
929
1040
  }) {
930
1041
  if (choice.mode === "redirect") {
931
1042
  return /* @__PURE__ */ jsx("p", { className: cx.panel, hidden, "data-sf-pay-panel": "redirect", ...tid("redirectNote"), children: m.redirectNote });
932
1043
  }
933
1044
  if (choice.mode !== "cash") return null;
934
- const main = choice.points.filter((p) => p.group === "main");
935
- const more = choice.points.filter((p) => p.group !== "main");
1045
+ const allMain = choice.points.filter((p) => p.group === "main");
1046
+ const cut = typeof visible === "number" && visible > 0 ? visible : allMain.length;
1047
+ const main = allMain.slice(0, cut);
1048
+ const more = [...allMain.slice(cut), ...choice.points.filter((p) => p.group !== "main")];
1049
+ const moreOpen = Boolean(openMore && more.some((p) => p.id === openMore));
936
1050
  const row = (p, i, first) => /* @__PURE__ */ jsxs("label", { className: cx.cashPoint, "data-sf-pay-point": true, children: [
937
- /* @__PURE__ */ 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 }),
1051
+ /* @__PURE__ */ jsx(
1052
+ "input",
1053
+ {
1054
+ type: "radio",
1055
+ name: CASH_POINT_FIELD,
1056
+ value: p.id,
1057
+ defaultChecked: first && i === 0,
1058
+ className: cx.cashPointInput,
1059
+ "aria-invalid": invalid?.has("cash_point") || void 0,
1060
+ ...onPointChange ? { onChange: () => onPointChange(p.id) } : {}
1061
+ }
1062
+ ),
938
1063
  marks ? /* @__PURE__ */ jsx(BrandMark, { brand: p.payment_point || p.id, logoUrl: p.logo_url ?? null, size: 22 }) : null,
939
1064
  /* @__PURE__ */ jsx("span", { children: p.label })
940
1065
  ] }, p.id);
@@ -942,7 +1067,7 @@ function ChoicePanel({
942
1067
  /* @__PURE__ */ jsxs("fieldset", { children: [
943
1068
  /* @__PURE__ */ jsx("legend", { children: m.cashPoints }),
944
1069
  /* @__PURE__ */ jsx("div", { "data-sf-pay-points": true, children: main.map((p, i) => row(p, i, true)) }),
945
- more.length ? /* @__PURE__ */ jsxs("details", { children: [
1070
+ more.length ? /* @__PURE__ */ jsxs("details", { ...moreOpen ? { open: true } : {}, children: [
946
1071
  /* @__PURE__ */ jsx("summary", { className: cx.cashMore, style: { minHeight: 44 }, children: m.cashMore }),
947
1072
  /* @__PURE__ */ jsx("div", { "data-sf-pay-points": true, children: more.map((p, i) => row(p, i, false)) })
948
1073
  ] }) : null
@@ -958,6 +1083,10 @@ function defaultIcon(choice) {
958
1083
  if (choice.option === "cash") return glyph(CASH_GLYPH);
959
1084
  return findBrand(choice.methodKey) ? /* @__PURE__ */ jsx(BrandMark, { brand: choice.methodKey, size: 24 }) : null;
960
1085
  }
1086
+ function readCheckedPoint(root) {
1087
+ const el = root?.querySelector(`input[name="${CASH_POINT_FIELD}"]:checked`);
1088
+ return el?.value ?? null;
1089
+ }
961
1090
  function rowMarks(choice, cardBrands) {
962
1091
  if (choice.option === "card") return cardBrands.map((key) => ({ key }));
963
1092
  if (choice.option === "cash") {
@@ -975,6 +1104,6 @@ function rowMarks(choice, cardBrands) {
975
1104
  return [];
976
1105
  }
977
1106
 
978
- export { CASH_POINT_FIELD, MERCADOPAGO_BRICKS_KIND, PAYMENT_CHOICE_FIELD, PAYMENT_THEME_TOKENS, PaymentBrick, PaymentChallenge, PaymentHttpError, PaymentMethodSection, VoucherCard, createFetchTransport, formFields, loadPaymentSdk, mercadoPagoBricksAdapter, paymentMethodMessages, readPaymentTheme, resetPaymentSdkCache, sanitizeMercadoPagoFormData, useEmbeddedPayment };
1107
+ export { CASH_POINT_FIELD, MERCADOPAGO_BRICKS_KIND, PAYMENT_CHOICE_FIELD, PAYMENT_THEME_TOKENS, PaymentBrick, PaymentChallenge, PaymentHttpError, PaymentMethodSection, VoucherCard, createFetchTransport, formFields, loadPaymentSdk, mercadoPagoBricksAdapter, paymentMethodMessages, readPaymentScheme, readPaymentTheme, resetPaymentSdkCache, sanitizeMercadoPagoFormData, useEmbeddedPayment };
979
1108
  //# sourceMappingURL=payments.js.map
980
1109
  //# sourceMappingURL=payments.js.map