@asteby/storefront-react 0.18.0 → 0.20.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
@@ -89,16 +89,20 @@
89
89
  }
90
90
 
91
91
  /*
92
- * 3DS challenge (`<PaymentChallenge>`): a near full-screen modal. Colors are fixed on purpose: the bank page inside
92
+ * 3DS challenge (`<PaymentChallenge>`): a square-ish centered modal (36rem wide, max min(90dvh, 40rem) tall). Colors are fixed on purpose: the bank page inside
93
93
  * the iframe is always white, and the dialog often lives in a themed (dark) panel. Override with a more specific rule.
94
94
  */
95
95
  dialog[data-sf-challenge] {
96
96
  margin: auto;
97
97
  box-sizing: border-box;
98
- width: min(60rem, calc(100% - 2rem));
98
+ inset: 0;
99
+ width: min(36rem, 100% - 2rem);
99
100
  max-width: none;
100
- max-height: 94dvh;
101
- overflow: auto;
101
+ max-height: min(90dvh, 40rem);
102
+ display: flex;
103
+ flex-direction: column;
104
+ /* Solo el cuerpo (el iframe) scrollea; el diálogo no. */
105
+ overflow: hidden;
102
106
  border: 1px solid #c9d2ee;
103
107
  border-radius: 0.75rem;
104
108
  background: #ffffff;
@@ -120,7 +124,10 @@ dialog[data-sf-challenge] h2:focus {
120
124
  }
121
125
  dialog[data-sf-challenge] [data-sf-challenge-frame] {
122
126
  display: block;
123
- height: min(70dvh, 40rem);
127
+ flex: 1 1 auto;
128
+ min-height: 16rem;
129
+ width: 100%;
130
+ overflow: auto;
124
131
  background: #ffffff;
125
132
  }
126
133
  dialog[data-sf-challenge] [data-sf-challenge-escape] {
@@ -139,16 +146,10 @@ dialog[data-sf-challenge] [data-sf-challenge-escape] button {
139
146
  border-radius: 0.5rem;
140
147
  font-weight: 600;
141
148
  }
142
- @media (max-width: 40rem) {
149
+ @media (max-width: 30rem) {
143
150
  dialog[data-sf-challenge] {
144
- width: 100%;
145
- height: 100dvh;
146
- max-height: 100dvh;
147
- border-radius: 0;
148
- border: 0;
151
+ width: calc(100% - 2rem);
152
+ max-height: calc(100dvh - 2rem);
149
153
  padding: 1rem;
150
154
  }
151
- dialog[data-sf-challenge] [data-sf-challenge-frame] {
152
- height: calc(100dvh - 8rem);
153
- }
154
155
  }
@@ -512,6 +512,8 @@ type MercadoPagoAdapterOptions = {
512
512
  getSdk?: () => MercadoPagoCtor | undefined;
513
513
  /** Reads the anti-fraud device id. Default: `window.MP_DEVICE_SESSION_ID`. */
514
514
  getDeviceSessionId?: () => string | undefined;
515
+ /** Wait before the single internal re-read of the form data (see `collect`). Default 400. */
516
+ collectRetryDelayMs?: number;
515
517
  };
516
518
  /**
517
519
  * The only fields that may leave the browser, and the only place that decides it. Anything else the SDK returns
@@ -520,7 +522,8 @@ type MercadoPagoAdapterOptions = {
520
522
  declare function sanitizeMercadoPagoFormData(data: Record<string, unknown> | null | undefined): Record<string, unknown>;
521
523
  /**
522
524
  * Card Payment Brick adapter. The payment button is hidden: the shop's own button calls `collect()` (MP's
523
- * `getFormData()`), which tokenises inside MP's iframes. Card data never touches this code.
525
+ * `getFormData()`), which tokenises inside MP's iframes. Card data never touches this code. `collect()` re-reads the
526
+ * form once if the first read fails, before anything is sent to the shop's server.
524
527
  */
525
528
  declare function mercadoPagoBricksAdapter(options?: MercadoPagoAdapterOptions): PaymentFormAdapter;
526
529
 
@@ -512,6 +512,8 @@ type MercadoPagoAdapterOptions = {
512
512
  getSdk?: () => MercadoPagoCtor | undefined;
513
513
  /** Reads the anti-fraud device id. Default: `window.MP_DEVICE_SESSION_ID`. */
514
514
  getDeviceSessionId?: () => string | undefined;
515
+ /** Wait before the single internal re-read of the form data (see `collect`). Default 400. */
516
+ collectRetryDelayMs?: number;
515
517
  };
516
518
  /**
517
519
  * The only fields that may leave the browser, and the only place that decides it. Anything else the SDK returns
@@ -520,7 +522,8 @@ type MercadoPagoAdapterOptions = {
520
522
  declare function sanitizeMercadoPagoFormData(data: Record<string, unknown> | null | undefined): Record<string, unknown>;
521
523
  /**
522
524
  * Card Payment Brick adapter. The payment button is hidden: the shop's own button calls `collect()` (MP's
523
- * `getFormData()`), which tokenises inside MP's iframes. Card data never touches this code.
525
+ * `getFormData()`), which tokenises inside MP's iframes. Card data never touches this code. `collect()` re-reads the
526
+ * form once if the first read fails, before anything is sent to the shop's server.
524
527
  */
525
528
  declare function mercadoPagoBricksAdapter(options?: MercadoPagoAdapterOptions): PaymentFormAdapter;
526
529
 
package/dist/payments.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { isSafeCssValue } from './chunk-672OTUM2.js';
2
2
  import { useModalDialog } from './chunk-ZBH7JYU4.js';
3
3
  import { findBrand, BrandMark } from './chunk-JNUJGPJC.js';
4
- import { useResolvedLocale } from './chunk-T52H5HL3.js';
5
- import './chunk-DQBL2DFJ.js';
6
4
  import { paymentRecoveryMessages } from './chunk-MNLROALF.js';
7
5
  export { buildPayChoices, cashProviderData, checkoutToAttempt, choiceValue, parseChoice, payErrorResponse, paymentCsp, paymentCspHeaderName, paymentCspHeaders, paymentCspMode, paymentNonce, paymentNonceFromHeaders, paymentRecoveryMessages, safeHttpStatus, voucherRows } from './chunk-MNLROALF.js';
6
+ import { useResolvedLocale } from './chunk-T52H5HL3.js';
7
+ import './chunk-DQBL2DFJ.js';
8
8
  import { forwardRef, useImperativeHandle, useRef, useEffect, useMemo, useId, useState, useCallback, useSyncExternalStore } from 'react';
9
9
  import { paymentMessages, formatMoney, generateIdempotencyKey, paymentOutcome, paymentErrorView, formatVoucherExpiry, classifyPaymentError } from '@asteby/storefront';
10
10
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
@@ -84,6 +84,7 @@ function sanitizeMercadoPagoFormData(data) {
84
84
  function mercadoPagoBricksAdapter(options = {}) {
85
85
  const allowed = options.allowedSdkOrigins ?? ["https://sdk.mercadopago.com"];
86
86
  const getSdk = options.getSdk ?? (() => typeof window !== "undefined" ? window.MercadoPago : void 0);
87
+ const retryDelay = options.collectRetryDelayMs ?? 400;
87
88
  const getDevice = options.getDeviceSessionId ?? (() => typeof window !== "undefined" ? window.MP_DEVICE_SESSION_ID : void 0);
88
89
  return {
89
90
  kind: MERCADOPAGO_BRICKS_KIND,
@@ -125,9 +126,21 @@ function mercadoPagoBricksAdapter(options = {}) {
125
126
  return {
126
127
  async collect() {
127
128
  if (gone) throw new Error("payment form is unmounted");
128
- const raw = await controller.getFormData();
129
- const provider_data = sanitizeMercadoPagoFormData(raw);
130
- if (!provider_data.token) throw new Error("payment form incomplete");
129
+ const read = async () => {
130
+ const raw = await controller.getFormData();
131
+ const data = sanitizeMercadoPagoFormData(raw);
132
+ if (!data.token) throw new Error("payment form incomplete");
133
+ return data;
134
+ };
135
+ let provider_data;
136
+ try {
137
+ provider_data = await read();
138
+ } catch (first) {
139
+ if (gone) throw first;
140
+ await new Promise((r) => setTimeout(r, retryDelay));
141
+ if (gone) throw first;
142
+ provider_data = await read();
143
+ }
131
144
  const device = getDevice();
132
145
  return { provider_data, ...typeof device === "string" && device ? { device_session_id: device } : {} };
133
146
  },
@@ -783,15 +796,21 @@ function useEmbeddedPayment(options) {
783
796
  setError(null);
784
797
  setOutcome(null);
785
798
  emit({ type: "submit" });
799
+ const withdrawn = () => !alive.current || form.current !== m;
800
+ const settle = () => {
801
+ if (alive.current) setStatus((s) => s === "processing" ? "idle" : s);
802
+ };
786
803
  let data;
787
804
  try {
788
805
  data = await m.collect();
789
806
  } catch {
807
+ if (withdrawn()) return settle();
790
808
  emit({ type: "tokenize_error" });
791
809
  setError({ kind: "generic", message: live.current.msgs["error.card_data"], retrySameAttempt: false, canRetry: true });
792
810
  setStatus("ready");
793
811
  return;
794
812
  }
813
+ if (withdrawn()) return settle();
795
814
  const attempt = readAttempt();
796
815
  setAttemptId(attempt);
797
816
  await send(data, attempt, total);