@asteby/storefront-react 0.16.1 → 0.18.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.js CHANGED
@@ -3,9 +3,10 @@ import { useModalDialog } from './chunk-ZBH7JYU4.js';
3
3
  import { findBrand, BrandMark } from './chunk-JNUJGPJC.js';
4
4
  import { useResolvedLocale } from './chunk-T52H5HL3.js';
5
5
  import './chunk-DQBL2DFJ.js';
6
- export { buildPayChoices, cashProviderData, checkoutToAttempt, choiceValue, parseChoice, payErrorResponse, paymentCsp, paymentCspHeaderName, paymentCspHeaders, paymentCspMode, paymentNonce, paymentNonceFromHeaders, voucherRows } from './chunk-ISEOCKIR.js';
6
+ import { paymentRecoveryMessages } from './chunk-MNLROALF.js';
7
+ export { buildPayChoices, cashProviderData, checkoutToAttempt, choiceValue, parseChoice, payErrorResponse, paymentCsp, paymentCspHeaderName, paymentCspHeaders, paymentCspMode, paymentNonce, paymentNonceFromHeaders, paymentRecoveryMessages, safeHttpStatus, voucherRows } from './chunk-MNLROALF.js';
7
8
  import { forwardRef, useImperativeHandle, useRef, useEffect, useMemo, useId, useState, useCallback, useSyncExternalStore } from 'react';
8
- import { paymentMessages, formatMoney, generateIdempotencyKey, paymentOutcome, paymentErrorView, formatVoucherExpiry } from '@asteby/storefront';
9
+ import { paymentMessages, formatMoney, generateIdempotencyKey, paymentOutcome, paymentErrorView, formatVoucherExpiry, classifyPaymentError } from '@asteby/storefront';
9
10
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
10
11
 
11
12
  // src/payments/sdk-loader.ts
@@ -130,6 +131,13 @@ function mercadoPagoBricksAdapter(options = {}) {
130
131
  const device = getDevice();
131
132
  return { provider_data, ...typeof device === "string" && device ? { device_session_id: device } : {} };
132
133
  },
134
+ async update(next) {
135
+ if (gone) throw new Error("payment form is unmounted");
136
+ if (typeof controller.update !== "function") throw new Error("payment form: update not supported");
137
+ const value = Number.parseFloat(next.amount.amount);
138
+ if (!Number.isFinite(value) || value <= 0) throw new Error("payment form: invalid amount");
139
+ await controller.update({ amount: value });
140
+ },
133
141
  async unmount() {
134
142
  gone = true;
135
143
  try {
@@ -142,6 +150,273 @@ function mercadoPagoBricksAdapter(options = {}) {
142
150
  };
143
151
  }
144
152
 
153
+ // src/payments/mount-queue.ts
154
+ var PaymentMountCancelled = class extends Error {
155
+ name = "PaymentMountCancelled";
156
+ constructor(message = "payment form: mount cancelled") {
157
+ super(message);
158
+ }
159
+ };
160
+ var PaymentMountTimeout = class extends Error {
161
+ name = "PaymentMountTimeout";
162
+ constructor(ms) {
163
+ super(`payment form: create timeout after ${ms}ms`);
164
+ }
165
+ };
166
+ var chains = /* @__PURE__ */ new WeakMap();
167
+ var seq = 0;
168
+ function withTimeout(p, ms) {
169
+ return new Promise((resolve, reject) => {
170
+ const t = setTimeout(() => reject(new PaymentMountTimeout(ms)), ms);
171
+ p.then(
172
+ (v) => {
173
+ clearTimeout(t);
174
+ resolve(v);
175
+ },
176
+ (e) => {
177
+ clearTimeout(t);
178
+ reject(e);
179
+ }
180
+ );
181
+ });
182
+ }
183
+ async function mountQueued(base, opts, queue = {}) {
184
+ const container = opts.container;
185
+ const createTimeoutMs = queue.createTimeoutMs ?? 1e4;
186
+ let chain = chains.get(container);
187
+ if (!chain) {
188
+ chain = { tail: Promise.resolve(), active: null, ticket: 0 };
189
+ chains.set(container, chain);
190
+ }
191
+ const me = chain;
192
+ let release;
193
+ const done = new Promise((r) => release = r);
194
+ const previous = me.tail;
195
+ const ticket = ++me.ticket;
196
+ me.tail = done;
197
+ let released = false;
198
+ const finish = () => {
199
+ if (!released) {
200
+ released = true;
201
+ release();
202
+ }
203
+ };
204
+ const cancelled = () => opts.signal?.aborted === true || !container.isConnected || me.ticket !== ticket;
205
+ let slot = null;
206
+ try {
207
+ if (me.active) await me.active.teardown();
208
+ await previous;
209
+ if (cancelled()) throw new PaymentMountCancelled();
210
+ slot = container.ownerDocument.createElement("div");
211
+ slot.id = `${container.id || "sf-pay"}-m${++seq}`;
212
+ container.replaceChildren(slot);
213
+ const node = slot;
214
+ const pending2 = base.mount({ ...opts, container: node });
215
+ let handle;
216
+ try {
217
+ handle = await withTimeout(pending2, createTimeoutMs);
218
+ } catch (err) {
219
+ void pending2.then((h) => Promise.resolve(h.unmount()).catch(() => {
220
+ }), () => {
221
+ });
222
+ throw err;
223
+ }
224
+ let torn = null;
225
+ const teardown = () => torn ??= (async () => {
226
+ try {
227
+ await handle.unmount();
228
+ } catch {
229
+ }
230
+ node.remove();
231
+ if (me.active === entry) me.active = null;
232
+ finish();
233
+ })();
234
+ const entry = { teardown };
235
+ me.active = entry;
236
+ if (cancelled() || !node.isConnected) {
237
+ await teardown();
238
+ throw new PaymentMountCancelled();
239
+ }
240
+ return {
241
+ collect: () => handle.collect(),
242
+ ...handle.update ? { update: (next) => handle.update(next) } : {},
243
+ unmount: teardown
244
+ };
245
+ } catch (err) {
246
+ slot?.remove();
247
+ finish();
248
+ throw err;
249
+ }
250
+ }
251
+ function withMountQueue(adapter, queue = {}) {
252
+ return { kind: adapter.kind, load: (client, options) => adapter.load(client, options), mount: (opts) => mountQueued(adapter, opts, queue) };
253
+ }
254
+
255
+ // src/payments/adapter.ts
256
+ var PaymentHttpError = class extends Error {
257
+ constructor(status, code, message, details, extra) {
258
+ super(message ?? code);
259
+ this.status = status;
260
+ this.code = code;
261
+ this.details = details;
262
+ this.retryable = extra?.retryable;
263
+ this.kind = extra?.kind;
264
+ }
265
+ status;
266
+ code;
267
+ details;
268
+ name = "PaymentHttpError";
269
+ /** `error.retryable` of the shop body: `true` repeat with the SAME key; `false` it is an answer. `undefined`: not said. */
270
+ retryable;
271
+ /** `error.kind` of the shop body (`classifyPaymentError`). */
272
+ kind;
273
+ };
274
+ async function parse(res) {
275
+ const text = await res.text();
276
+ if (!text) return null;
277
+ try {
278
+ return JSON.parse(text);
279
+ } catch {
280
+ return null;
281
+ }
282
+ }
283
+ function createFetchTransport(url, options = {}) {
284
+ const doFetch = (u, init) => (options.fetch ?? globalThis.fetch.bind(globalThis))(u, init);
285
+ async function call(u, init) {
286
+ const res = await doFetch(u, { credentials: options.credentials ?? "same-origin", ...init });
287
+ const body = await parse(res);
288
+ if (!res.ok) {
289
+ const e = body?.error;
290
+ throw new PaymentHttpError(res.status, e?.code ?? "server_error", e?.message, e?.details, {
291
+ retryable: typeof e?.retryable === "boolean" ? e.retryable : void 0,
292
+ kind: typeof e?.kind === "string" ? e.kind : void 0
293
+ });
294
+ }
295
+ const result = body;
296
+ if (!result || typeof result !== "object" || !result.payment || typeof result.payment.status !== "string") {
297
+ throw new PaymentHttpError(502, "invalid_response", "Unexpected payment response");
298
+ }
299
+ return result;
300
+ }
301
+ return {
302
+ submit: (request, { signal }) => call(url, {
303
+ method: "POST",
304
+ signal,
305
+ headers: { "Content-Type": "application/json", Accept: "application/json", "Idempotency-Key": request.idempotencyKey, ...options.headers },
306
+ body: JSON.stringify(options.buildBody ? options.buildBody(request) : request)
307
+ }),
308
+ ...options.refreshUrl ? {
309
+ refresh: ({ signal }) => call(options.refreshUrl, { method: "POST", signal, headers: { Accept: "application/json", ...options.headers } })
310
+ } : {}
311
+ };
312
+ }
313
+
314
+ // src/payments/resilient-transport.ts
315
+ var realSleep = (ms) => new Promise((r) => setTimeout(r, ms));
316
+ function isResolvedAttempt(result) {
317
+ const p = result.payment;
318
+ if (p.status === "paid" || p.status === "failed" || p.status === "expired" || p.status === "cancelled") return true;
319
+ const type = p.action?.type;
320
+ return type === "voucher" || type === "challenge" || type === "redirect";
321
+ }
322
+ async function readJson(res) {
323
+ const text = await res.text().catch(() => "");
324
+ if (!text) return null;
325
+ try {
326
+ return JSON.parse(text);
327
+ } catch {
328
+ return null;
329
+ }
330
+ }
331
+ function createResilientTransport(opts) {
332
+ const doFetch = opts.fetch ?? ((u, i) => globalThis.fetch(u, i));
333
+ const sleep2 = opts.sleep ?? realSleep;
334
+ const retries = opts.retries ?? 2;
335
+ const backoff = opts.backoffMs ?? [800, 2e3];
336
+ const polls = opts.statusPolls ?? 3;
337
+ const pollMs = opts.statusPollMs ?? 2e3;
338
+ const msgs = paymentRecoveryMessages(opts.locale, opts.messages);
339
+ const text = (k) => msgs[`error.${k}`];
340
+ const issue = (i) => opts.onIssue?.(i);
341
+ const confirming = msgs["notice.confirming.title"];
342
+ async function call(url, init) {
343
+ let res;
344
+ try {
345
+ res = await doFetch(url, { credentials: "same-origin", ...init, headers: { ...opts.headers, ...init.headers } });
346
+ } catch (err) {
347
+ if (err?.name === "AbortError") throw err;
348
+ return { fail: { kind: "transient", status: 0, code: "network", message: text("transient"), retryable: true } };
349
+ }
350
+ const body = await readJson(res);
351
+ if (res.ok) {
352
+ const r = body;
353
+ if (r && typeof r === "object" && r.payment && typeof r.payment.status === "string") return { ok: r };
354
+ return { fail: { kind: "transient", status: 502, code: "invalid_response", message: text("transient"), retryable: true } };
355
+ }
356
+ const e = body?.error;
357
+ if (!e) {
358
+ const info2 = classifyPaymentError({ status: res.status >= 500 || res.status === 0 ? 503 : res.status });
359
+ return { fail: { kind: info2.kind, status: res.status, code: "server_error", message: text(info2.kind), retryable: info2.retryable } };
360
+ }
361
+ const byCode = classifyPaymentError({ status: res.status, code: e.code ?? null, field: e.field ?? null, retryable: typeof e.retryable === "boolean" ? e.retryable : null, checkoutId: e.checkout_id ?? null, details: e.details });
362
+ const info = e.kind && !(e.kind === "transient" && !byCode.retryable) ? { kind: e.kind, retryable: e.retryable === true || byCode.retryable } : byCode;
363
+ return { fail: { kind: info.kind, status: res.status, code: e.code ?? "server_error", message: e.message || text(info.kind), retryable: info.retryable, details: e.details } };
364
+ }
365
+ const toError = (f) => f.kind === "transient" ? new PaymentHttpError(503, "network", f.message, f.details, { retryable: true, kind: "transient" }) : new PaymentHttpError(f.status || 500, f.code, f.message, f.details, { retryable: f.retryable, kind: f.kind });
366
+ async function resolveByStatus(signal) {
367
+ for (let i = 0; i < polls; i++) {
368
+ const r = await call(opts.statusUrl, { method: "POST", signal, headers: { Accept: "application/json" } });
369
+ if ("ok" in r) {
370
+ if (isResolvedAttempt(r.ok)) return r.ok;
371
+ } else if (!r.fail.retryable) {
372
+ return null;
373
+ }
374
+ if (i < polls - 1) await sleep2(pollMs);
375
+ }
376
+ return null;
377
+ }
378
+ return {
379
+ async submit(request, { signal }) {
380
+ const init = {
381
+ method: "POST",
382
+ signal,
383
+ headers: { "Content-Type": "application/json", Accept: "application/json", "Idempotency-Key": request.idempotencyKey },
384
+ body: JSON.stringify({ ...request, fields: opts.fields() })
385
+ };
386
+ issue(null);
387
+ let last = null;
388
+ for (let n = 0; n <= retries; n++) {
389
+ const r = await call(opts.payUrl, init);
390
+ if ("ok" in r) {
391
+ issue(null);
392
+ return r.ok;
393
+ }
394
+ last = r.fail;
395
+ const waitable = last.retryable && (last.kind === "transient" || last.kind === "in_progress");
396
+ if (!waitable || n === retries) break;
397
+ issue({ kind: "confirming", message: confirming });
398
+ await sleep2(backoff[Math.min(n, backoff.length - 1)] ?? 800);
399
+ }
400
+ const failure = last;
401
+ if (failure.retryable && (failure.kind === "transient" || failure.kind === "in_progress") || failure.kind === "stale_checkout") {
402
+ issue({ kind: "verifying", message: confirming });
403
+ const resolved = await resolveByStatus(signal);
404
+ if (resolved) {
405
+ issue(null);
406
+ return resolved;
407
+ }
408
+ }
409
+ issue({ kind: failure.kind, message: failure.message, retryable: failure.retryable, status: failure.status, code: failure.code });
410
+ throw toError(failure);
411
+ },
412
+ refresh: async ({ signal }) => {
413
+ const r = await call(opts.statusUrl, { method: "POST", signal, headers: { Accept: "application/json" } });
414
+ if ("ok" in r) return r.ok;
415
+ throw toError(r.fail);
416
+ }
417
+ };
418
+ }
419
+
145
420
  // src/payments/theme.ts
146
421
  var PAYMENT_THEME_TOKENS = {
147
422
  "--sf-pay-text": "textPrimaryColor",
@@ -210,7 +485,9 @@ function useEmbeddedPayment(options) {
210
485
  autoRetries = 2,
211
486
  retryDelayMs = 600,
212
487
  pollIntervalMs = 2e3,
213
- pollTimeoutMs = 6e4,
488
+ pollTimeoutMs = 3e5,
489
+ verifyTimeoutMs = 12e4,
490
+ verifyIntervalMs = 2e3,
214
491
  scope = "default"
215
492
  } = options;
216
493
  const msgs = useMemo(() => paymentMessages(locale, options.messages), [locale, options.messages]);
@@ -224,15 +501,28 @@ function useEmbeddedPayment(options) {
224
501
  const [attemptId, setAttemptId] = useState(null);
225
502
  const [container, setContainer] = useState(null);
226
503
  const [mountKey, setMountKey] = useState(0);
504
+ const [verifying, setVerifying] = useState(false);
227
505
  const live = useRef({ ...options, msgs });
228
506
  live.current = { ...options, msgs };
229
507
  const form = useRef(null);
230
508
  const inFlight = useRef(null);
231
509
  const pendingData = useRef(null);
510
+ const memAttempt = useRef(null);
511
+ const memOnly = useRef(false);
512
+ const errorRef = useRef(null);
513
+ const retryRef = useRef(() => Promise.resolve());
232
514
  const alive = useRef(true);
233
515
  const generation = useRef(0);
234
516
  const statusRef = useRef("idle");
517
+ const leftChallenge = useRef(false);
235
518
  statusRef.current = status;
519
+ errorRef.current = error;
520
+ const amountKey = `${amount.amount}|${amount.currency_code}`;
521
+ const amountKeyRef = useRef(amountKey);
522
+ amountKeyRef.current = amountKey;
523
+ const appliedAmount = useRef(null);
524
+ const updating = useRef(null);
525
+ const remountRequested = useRef(false);
236
526
  useEffect(() => {
237
527
  alive.current = true;
238
528
  return () => {
@@ -243,19 +533,33 @@ function useEmbeddedPayment(options) {
243
533
  const store = options.storage === void 0 ? safeStorage() : options.storage;
244
534
  const emit = useCallback((e) => live.current.onEvent?.(e), []);
245
535
  const readAttempt = useCallback(() => {
246
- try {
247
- const saved = store?.getItem(storageKey);
248
- if (saved) return saved;
249
- } catch {
536
+ if (!memOnly.current) {
537
+ try {
538
+ const saved = store?.getItem(storageKey);
539
+ if (saved) {
540
+ memAttempt.current = saved;
541
+ return saved;
542
+ }
543
+ } catch {
544
+ memOnly.current = true;
545
+ }
546
+ }
547
+ if (memOnly.current && memAttempt.current) return memAttempt.current;
548
+ if (!store) {
549
+ if (memAttempt.current) return memAttempt.current;
250
550
  }
251
551
  const fresh = (live.current.newAttemptId ?? generateIdempotencyKey)();
552
+ memAttempt.current = fresh;
252
553
  try {
253
554
  store?.setItem(storageKey, fresh);
555
+ if (store && store.getItem(storageKey) !== fresh) memOnly.current = true;
254
556
  } catch {
557
+ memOnly.current = true;
255
558
  }
256
559
  return fresh;
257
560
  }, [store, storageKey]);
258
561
  const rotateAttempt = useCallback(() => {
562
+ memAttempt.current = null;
259
563
  try {
260
564
  store?.removeItem(storageKey);
261
565
  } catch {
@@ -271,7 +575,32 @@ function useEmbeddedPayment(options) {
271
575
  },
272
576
  [emit]
273
577
  );
274
- const amountKey = `${amount.amount}|${amount.currency_code}`;
578
+ const syncAmount = useCallback(() => {
579
+ const m = form.current;
580
+ if (!m || inFlight.current || updating.current || remountRequested.current) return;
581
+ if (statusRef.current === "network_error" || statusRef.current === "blocked" && errorRef.current?.kind === "payment_in_progress") return;
582
+ if (appliedAmount.current === amountKeyRef.current) return;
583
+ const run = (async () => {
584
+ while (form.current === m && alive.current && appliedAmount.current !== amountKeyRef.current) {
585
+ const key = amountKeyRef.current;
586
+ try {
587
+ if (!m.update) throw new Error("payment form: update not supported");
588
+ await m.update({ amount: live.current.amount });
589
+ appliedAmount.current = key;
590
+ } catch {
591
+ if (form.current === m && alive.current) {
592
+ remountRequested.current = true;
593
+ setMountKey((k) => k + 1);
594
+ }
595
+ return;
596
+ }
597
+ }
598
+ })().finally(() => {
599
+ updating.current = null;
600
+ if (alive.current) syncAmount();
601
+ });
602
+ updating.current = run;
603
+ }, []);
275
604
  const kind = client?.kind;
276
605
  const publicKey = client?.public_key;
277
606
  useEffect(() => {
@@ -285,18 +614,22 @@ function useEmbeddedPayment(options) {
285
614
  return;
286
615
  }
287
616
  if (!container) return;
617
+ remountRequested.current = false;
288
618
  const my = ++generation.current;
289
619
  const started = Date.now();
620
+ const abort = new AbortController();
290
621
  let mounted = null;
291
622
  setStatus("loading");
292
623
  const stale = () => my !== generation.current || !alive.current;
293
624
  const fail = (reason) => {
294
625
  if (stale()) return;
295
626
  generation.current++;
627
+ abort.abort();
296
628
  clearTimeout(timer);
297
629
  const m = mounted ?? form.current;
298
630
  mounted = null;
299
631
  form.current = null;
632
+ appliedAmount.current = null;
300
633
  if (m) void Promise.resolve(m.unmount());
301
634
  fallback(reason);
302
635
  };
@@ -310,31 +643,39 @@ function useEmbeddedPayment(options) {
310
643
  if (stale()) return;
311
644
  const theme = live.current.theme ?? readPaymentTheme(container);
312
645
  const colorScheme = live.current.colorScheme ?? readPaymentScheme(container);
313
- const m = await adapter.mount({
314
- container,
315
- client,
316
- amount: live.current.amount,
317
- ...live.current.email ? { email: live.current.email } : {},
318
- ...live.current.locale ? { locale: live.current.locale } : {},
319
- theme,
320
- ...colorScheme ? { colorScheme } : {},
321
- onReady: () => {
322
- if (stale()) return;
323
- clearTimeout(timer);
324
- setStatus((s) => s === "loading" ? "ready" : s);
325
- emit({ type: "form_ready", ms: Date.now() - started });
646
+ const createdWith = amountKeyRef.current;
647
+ const m = await mountQueued(
648
+ adapter,
649
+ {
650
+ container,
651
+ client,
652
+ amount: live.current.amount,
653
+ ...live.current.email ? { email: live.current.email } : {},
654
+ ...live.current.locale ? { locale: live.current.locale } : {},
655
+ theme,
656
+ ...colorScheme ? { colorScheme } : {},
657
+ signal: abort.signal,
658
+ onReady: () => {
659
+ if (stale()) return;
660
+ clearTimeout(timer);
661
+ setStatus((s) => s === "loading" ? "ready" : s);
662
+ emit({ type: "form_ready", ms: Date.now() - started });
663
+ },
664
+ onError: (e) => {
665
+ if (stale()) return;
666
+ if (e.critical) fail("form_error");
667
+ }
326
668
  },
327
- onError: (e) => {
328
- if (stale()) return;
329
- if (e.critical) fail("form_error");
330
- }
331
- });
669
+ { createTimeoutMs: live.current.createTimeoutMs ?? sdkTimeoutMs }
670
+ );
332
671
  if (stale()) {
333
672
  void Promise.resolve(m.unmount());
334
673
  return;
335
674
  }
336
675
  mounted = m;
337
676
  form.current = m;
677
+ appliedAmount.current = createdWith;
678
+ syncAmount();
338
679
  } catch (err) {
339
680
  fail(err instanceof Error && /timeout/i.test(err.message) ? "sdk_timeout" : "sdk_error");
340
681
  }
@@ -342,16 +683,22 @@ function useEmbeddedPayment(options) {
342
683
  return () => {
343
684
  clearTimeout(timer);
344
685
  generation.current++;
686
+ abort.abort();
345
687
  const m = mounted ?? form.current;
346
688
  mounted = null;
347
689
  form.current = null;
690
+ appliedAmount.current = null;
348
691
  if (m) void Promise.resolve(m.unmount());
349
692
  };
350
- }, [enabled, container, kind, publicKey, amountKey, mountKey, sdkTimeoutMs, containerId]);
693
+ }, [enabled, container, kind, publicKey, mountKey, sdkTimeoutMs, containerId]);
694
+ useEffect(() => {
695
+ syncAmount();
696
+ }, [amountKey, syncAmount]);
351
697
  const finish = useCallback(
352
698
  (result) => {
353
699
  const o = paymentOutcome(result, live.current.locale, live.current.messages);
354
700
  pendingData.current = null;
701
+ leftChallenge.current = false;
355
702
  setOutcome(o);
356
703
  setError(null);
357
704
  setAttemptsLeft(o.attempts_left);
@@ -371,7 +718,7 @@ function useEmbeddedPayment(options) {
371
718
  [emit, rotateAttempt]
372
719
  );
373
720
  const send = useCallback(
374
- async (data, attempt) => {
721
+ async (data, attempt, total) => {
375
722
  const ctl = new AbortController();
376
723
  const maxRetries = live.current.autoRetries ?? autoRetries;
377
724
  for (let n = 0; ; n++) {
@@ -383,7 +730,7 @@ function useEmbeddedPayment(options) {
383
730
  idempotencyKey: attempt,
384
731
  provider_data: data.provider_data,
385
732
  ...data.device_session_id ? { device_session_id: data.device_session_id } : {},
386
- expected_total: live.current.amount
733
+ expected_total: total
387
734
  },
388
735
  { signal: ctl.signal }
389
736
  );
@@ -401,7 +748,7 @@ function useEmbeddedPayment(options) {
401
748
  emit({ type: "error", kind: view.kind });
402
749
  setError(view);
403
750
  if (view.kind === "network") {
404
- pendingData.current = data;
751
+ pendingData.current = { data, total };
405
752
  setStatus("network_error");
406
753
  } else if (view.kind === "price_mismatch") {
407
754
  pendingData.current = null;
@@ -427,7 +774,11 @@ function useEmbeddedPayment(options) {
427
774
  if (inFlight.current) return inFlight.current;
428
775
  const m = form.current;
429
776
  if (!m || statusRef.current !== "ready" && statusRef.current !== "network_error") return Promise.resolve();
777
+ if (statusRef.current === "network_error" && pendingData.current) return retryRef.current();
778
+ const total = live.current.amount;
430
779
  const run = (async () => {
780
+ if (updating.current) await updating.current.catch(() => {
781
+ });
431
782
  setStatus("processing");
432
783
  setError(null);
433
784
  setOutcome(null);
@@ -443,30 +794,33 @@ function useEmbeddedPayment(options) {
443
794
  }
444
795
  const attempt = readAttempt();
445
796
  setAttemptId(attempt);
446
- await send(data, attempt);
797
+ await send(data, attempt, total);
447
798
  })().finally(() => {
448
799
  inFlight.current = null;
800
+ syncAmount();
449
801
  });
450
802
  inFlight.current = run;
451
803
  return run;
452
- }, [emit, readAttempt, send]);
804
+ }, [emit, readAttempt, send, syncAmount]);
453
805
  const retry = useCallback(() => {
454
806
  if (inFlight.current) return inFlight.current;
455
- const data = pendingData.current;
456
- if (!data) return submit();
807
+ const pending2 = pendingData.current;
808
+ if (!pending2) return submit();
457
809
  emit({ type: "retry" });
458
810
  const run = (async () => {
459
811
  setStatus("processing");
460
812
  setError(null);
461
813
  const attempt = readAttempt();
462
814
  setAttemptId(attempt);
463
- await send(data, attempt);
815
+ await send(pending2.data, attempt, pending2.total);
464
816
  })().finally(() => {
465
817
  inFlight.current = null;
818
+ syncAmount();
466
819
  });
467
820
  inFlight.current = run;
468
821
  return run;
469
- }, [emit, readAttempt, send, submit]);
822
+ }, [emit, readAttempt, send, submit, syncAmount]);
823
+ retryRef.current = retry;
470
824
  useEffect(() => {
471
825
  if (status !== "challenge" && status !== "in_review" || !transport.refresh) return;
472
826
  let stop = false;
@@ -479,6 +833,7 @@ function useEmbeddedPayment(options) {
479
833
  try {
480
834
  const r = await transport.refresh({ signal: ctl.signal });
481
835
  if (stop || !alive.current) return;
836
+ if (r.payment.status === "none") continue;
482
837
  const o = paymentOutcome(r, live.current.locale, live.current.messages);
483
838
  if (o.kind !== status) {
484
839
  finish(r);
@@ -493,10 +848,57 @@ function useEmbeddedPayment(options) {
493
848
  ctl.abort();
494
849
  };
495
850
  }, [status, transport, pollIntervalMs, pollTimeoutMs, finish]);
851
+ const unknownOutcome = status === "network_error" || status === "blocked" && error?.kind === "payment_in_progress";
852
+ useEffect(() => {
853
+ if (!unknownOutcome) syncAmount();
854
+ }, [unknownOutcome, syncAmount]);
855
+ useEffect(() => {
856
+ if (!unknownOutcome || !transport.refresh) {
857
+ setVerifying(false);
858
+ return;
859
+ }
860
+ let stop = false;
861
+ const ctl = new AbortController();
862
+ const until = Date.now() + (leftChallenge.current ? Math.max(verifyTimeoutMs, pollTimeoutMs) : verifyTimeoutMs);
863
+ setVerifying(true);
864
+ (async () => {
865
+ let wait = verifyIntervalMs;
866
+ while (!stop && Date.now() < until) {
867
+ await sleep(wait);
868
+ wait = Math.min(wait * 2, 15e3);
869
+ if (stop || !alive.current) return;
870
+ try {
871
+ const r = await transport.refresh({ signal: ctl.signal });
872
+ if (stop || !alive.current) return;
873
+ if (isResolvedAttempt(r)) {
874
+ const settled = r.payment.status === "paid" || r.payment.status === "failed" || r.payment.status === "expired" || r.payment.status === "cancelled";
875
+ if (leftChallenge.current && !settled && r.payment.action?.type === "challenge") continue;
876
+ setVerifying(false);
877
+ finish(r);
878
+ return;
879
+ }
880
+ } catch {
881
+ }
882
+ }
883
+ if (!stop && alive.current) setVerifying(false);
884
+ })();
885
+ return () => {
886
+ stop = true;
887
+ ctl.abort();
888
+ setVerifying(false);
889
+ };
890
+ }, [unknownOutcome, transport, verifyTimeoutMs, verifyIntervalMs, pollTimeoutMs, finish]);
496
891
  const dismiss = useCallback(() => {
497
892
  setError(null);
498
893
  setOutcome((o) => o?.kind === "rejected" ? null : o);
499
894
  }, []);
895
+ const cancelChallenge = useCallback(() => {
896
+ if (statusRef.current !== "challenge") return;
897
+ leftChallenge.current = true;
898
+ setOutcome(null);
899
+ setError({ kind: "payment_in_progress", message: live.current.msgs["challenge.left"], retrySameAttempt: false, canRetry: false });
900
+ setStatus("blocked");
901
+ }, []);
500
902
  const message = status === "processing" ? msgs["state.processing"] : status === "fallback" ? msgs["state.fallback"] : error?.message ?? outcome?.message ?? (status === "loading" ? msgs["state.loading"] : null);
501
903
  return {
502
904
  status,
@@ -507,11 +909,13 @@ function useEmbeddedPayment(options) {
507
909
  attemptsLeft,
508
910
  attemptId,
509
911
  busy: status === "loading" || status === "processing",
912
+ verifying,
510
913
  containerRef: setContainer,
511
914
  containerId,
512
915
  submit,
513
916
  retry,
514
- dismiss
917
+ dismiss,
918
+ cancelChallenge
515
919
  };
516
920
  }
517
921
  function VoucherCard({ outcome, locale, messages, className }) {
@@ -548,15 +952,55 @@ function VoucherCard({ outcome, locale, messages, className }) {
548
952
  /* @__PURE__ */ jsx("p", { children: m["voucher.email"] })
549
953
  ] });
550
954
  }
551
- function PaymentChallenge({ outcome, locale, messages, render, onCancel }) {
955
+ function PaymentChallenge({ outcome, locale, messages, render, onCancel, escapeAfterMs = 12e4 }) {
552
956
  const m = paymentMessages(locale, messages);
553
- const { ref } = useModalDialog({ open: true, onClose: () => onCancel?.() });
957
+ const { ref } = useModalDialog({ open: true, onClose: () => {
958
+ } });
959
+ const [canEscape, setCanEscape] = useState(false);
960
+ const [closed, setClosed] = useState(false);
961
+ const cancelRef = useRef(onCancel);
962
+ cancelRef.current = onCancel;
963
+ const hasCancel = !!onCancel;
964
+ useEffect(() => {
965
+ if (!hasCancel) return;
966
+ const t = setTimeout(() => setCanEscape(true), escapeAfterMs);
967
+ return () => clearTimeout(t);
968
+ }, [hasCancel, escapeAfterMs]);
969
+ useEffect(() => {
970
+ const el = ref.current;
971
+ if (!el) return;
972
+ const onClosed = () => setClosed(true);
973
+ el.addEventListener("close", onClosed);
974
+ return () => el.removeEventListener("close", onClosed);
975
+ }, [ref]);
976
+ const reopen = () => {
977
+ const el = ref.current;
978
+ if (!el) return;
979
+ try {
980
+ if (typeof el.showModal === "function") el.showModal();
981
+ else el.setAttribute("open", "");
982
+ } catch {
983
+ el.setAttribute("open", "");
984
+ }
985
+ setClosed(false);
986
+ el.querySelector("[data-sf-autofocus]")?.focus({ preventScroll: true });
987
+ };
554
988
  const url = outcome.challenge?.url;
555
- return /* @__PURE__ */ jsx("dialog", { ref, "aria-labelledby": "sf-challenge-title", "data-sf-challenge": true, children: /* @__PURE__ */ jsxs("div", { children: [
556
- /* @__PURE__ */ jsx("h2", { id: "sf-challenge-title", "data-sf-autofocus": true, tabIndex: -1, children: m["challenge.title"] }),
557
- render ? render(outcome) : url ? /* @__PURE__ */ jsx("iframe", { src: url, title: m["challenge.title"], style: { width: "100%", minHeight: 420, border: 0 } }) : /* @__PURE__ */ jsx("p", { children: m["state.challenge"] }),
558
- onCancel && /* @__PURE__ */ jsx("button", { type: "button", onClick: onCancel, style: { minWidth: 44, minHeight: 44 }, children: m["action.cancel"] })
559
- ] }) });
989
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
990
+ closed && /* @__PURE__ */ jsxs("p", { role: "status", "data-sf-challenge-closed": true, children: [
991
+ m["state.challenge"],
992
+ " ",
993
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: reopen, "data-sf-challenge-reopen": true, style: { minWidth: 44, minHeight: 44 }, children: m["challenge.title"] })
994
+ ] }),
995
+ /* @__PURE__ */ jsx("dialog", { ref, "aria-labelledby": "sf-challenge-title", "data-sf-challenge": true, children: /* @__PURE__ */ jsxs("div", { "data-sf-challenge-body": true, children: [
996
+ /* @__PURE__ */ jsx("h2", { id: "sf-challenge-title", "data-sf-autofocus": true, tabIndex: -1, children: m["challenge.title"] }),
997
+ render ? render(outcome) : url ? /* @__PURE__ */ jsx("iframe", { src: url, title: m["challenge.title"], "data-sf-challenge-frame": true, style: { width: "100%", minHeight: 420, border: 0 } }) : /* @__PURE__ */ jsx("p", { children: m["state.challenge"] }),
998
+ hasCancel && /* @__PURE__ */ jsx("div", { "data-sf-challenge-escape": true, "aria-live": "polite", children: canEscape && /* @__PURE__ */ jsxs(Fragment, { children: [
999
+ /* @__PURE__ */ jsx("p", { children: m["challenge.escape_hint"] }),
1000
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => cancelRef.current?.(), style: { minWidth: 44, minHeight: 44 }, children: m["challenge.escape"] })
1001
+ ] }) })
1002
+ ] }) })
1003
+ ] });
560
1004
  }
561
1005
  var PaymentBrick = forwardRef(function PaymentBrick2(props, ref) {
562
1006
  const { submitLabel, hideAmountInButton, renderVoucher, renderChallenge, minHeight = 360, className, style, ...hookOptions } = props;
@@ -594,7 +1038,7 @@ var PaymentBrick = forwardRef(function PaymentBrick2(props, ref) {
594
1038
  state.status === "network_error" && /* @__PURE__ */ jsx("button", { type: "button", onClick: () => void state.retry(), style: { minHeight: 44 }, children: m["action.retry"] })
595
1039
  ] }) : /* @__PURE__ */ jsx("p", { role: "status", "aria-live": "polite", "data-sf-pay-message": true, children: state.message }),
596
1040
  outcome?.kind === "voucher" && (renderVoucher ? renderVoucher(outcome) : /* @__PURE__ */ jsx(VoucherCard, { outcome, locale, ...props.messages ? { messages: props.messages } : {} })),
597
- outcome?.kind === "challenge" && /* @__PURE__ */ jsx(PaymentChallenge, { outcome, locale, ...props.messages ? { messages: props.messages } : {}, ...renderChallenge ? { render: renderChallenge } : {} }),
1041
+ outcome?.kind === "challenge" && /* @__PURE__ */ jsx(PaymentChallenge, { outcome, locale, onCancel: state.cancelChallenge, ...props.messages ? { messages: props.messages } : {}, ...renderChallenge ? { render: renderChallenge } : {} }),
598
1042
  submitLabel !== void 0 && showForm && /* @__PURE__ */ jsxs(
599
1043
  "button",
600
1044
  {
@@ -616,56 +1060,6 @@ var PaymentBrick = forwardRef(function PaymentBrick2(props, ref) {
616
1060
  ] });
617
1061
  });
618
1062
 
619
- // src/payments/adapter.ts
620
- var PaymentHttpError = class extends Error {
621
- constructor(status, code, message, details) {
622
- super(message ?? code);
623
- this.status = status;
624
- this.code = code;
625
- this.details = details;
626
- }
627
- status;
628
- code;
629
- details;
630
- name = "PaymentHttpError";
631
- };
632
- async function parse(res) {
633
- const text = await res.text();
634
- if (!text) return null;
635
- try {
636
- return JSON.parse(text);
637
- } catch {
638
- return null;
639
- }
640
- }
641
- function createFetchTransport(url, options = {}) {
642
- const doFetch = (u, init) => (options.fetch ?? globalThis.fetch.bind(globalThis))(u, init);
643
- async function call(u, init) {
644
- const res = await doFetch(u, { credentials: options.credentials ?? "same-origin", ...init });
645
- const body = await parse(res);
646
- if (!res.ok) {
647
- const e = body?.error;
648
- throw new PaymentHttpError(res.status, e?.code ?? "server_error", e?.message, e?.details);
649
- }
650
- const result = body;
651
- if (!result || typeof result !== "object" || !result.payment || typeof result.payment.status !== "string") {
652
- throw new PaymentHttpError(502, "invalid_response", "Unexpected payment response");
653
- }
654
- return result;
655
- }
656
- return {
657
- submit: (request, { signal }) => call(url, {
658
- method: "POST",
659
- signal,
660
- headers: { "Content-Type": "application/json", Accept: "application/json", "Idempotency-Key": request.idempotencyKey, ...options.headers },
661
- body: JSON.stringify(options.buildBody ? options.buildBody(request) : request)
662
- }),
663
- ...options.refreshUrl ? {
664
- refresh: ({ signal }) => call(options.refreshUrl, { method: "POST", signal, headers: { Accept: "application/json", ...options.headers } })
665
- } : {}
666
- };
667
- }
668
-
669
1063
  // src/payments/payment-method.messages.ts
670
1064
  var ES = {
671
1065
  legend: "M\xE9todo de pago",
@@ -733,6 +1127,130 @@ function paymentMethodMessages(locale, overrides) {
733
1127
  const { options, ...rest } = overrides;
734
1128
  return { ...base, ...rest, options: { ...base.options, ...options } };
735
1129
  }
1130
+ function alternativeChoices(choices) {
1131
+ return choices.filter((c) => c.mode !== "card");
1132
+ }
1133
+ function pickChoice(form, value) {
1134
+ const input = Array.from(form?.querySelectorAll('input[name="payment_choice"]') ?? []).find((i) => i.value === value);
1135
+ if (!input) return;
1136
+ input.click();
1137
+ input.scrollIntoView?.({ block: "center", behavior: "smooth" });
1138
+ }
1139
+ var INDETERMINATE = /* @__PURE__ */ new Set(["confirming", "verifying", "transient", "in_progress"]);
1140
+ function safeStorage2() {
1141
+ try {
1142
+ return typeof sessionStorage !== "undefined" ? sessionStorage : null;
1143
+ } catch {
1144
+ return null;
1145
+ }
1146
+ }
1147
+ function usePaymentRecovery(options = {}) {
1148
+ const { scope = "default", formAutoRetries = 2 } = options;
1149
+ const [notice, setNotice] = useState(null);
1150
+ const [sectionKey, setSectionKey] = useState(0);
1151
+ const formFailures = useRef(0);
1152
+ const lastIssue = useRef(null);
1153
+ const unknownByEvent = useRef(false);
1154
+ const live = useRef(options);
1155
+ live.current = options;
1156
+ const onIssue = useCallback((issue) => {
1157
+ lastIssue.current = issue;
1158
+ if (!issue) return setNotice((n) => n?.kind === "confirming" ? null : n);
1159
+ if (issue.kind === "confirming" || issue.kind === "verifying") return setNotice({ kind: "confirming" });
1160
+ if (issue.kind === "merchant_config" || issue.kind === "stale_checkout" || issue.kind === "provider_declined") return setNotice({ kind: issue.kind });
1161
+ if (issue.kind === "other") return setNotice({ kind: "other" });
1162
+ setNotice((n) => n?.kind === "confirming" ? null : n);
1163
+ }, []);
1164
+ const retry = useCallback(() => {
1165
+ const issue = lastIssue.current;
1166
+ if (unknownByEvent.current || issue && INDETERMINATE.has(issue.kind)) return false;
1167
+ const store = live.current.storage === void 0 ? safeStorage2() : live.current.storage;
1168
+ try {
1169
+ store?.removeItem(`sf:pay:attempt:${live.current.scope ?? "default"}`);
1170
+ } catch {
1171
+ }
1172
+ formFailures.current = 0;
1173
+ lastIssue.current = null;
1174
+ unknownByEvent.current = false;
1175
+ setNotice(null);
1176
+ setSectionKey((k) => k + 1);
1177
+ return true;
1178
+ }, []);
1179
+ const onEvent = useCallback(
1180
+ (e) => {
1181
+ if (e.type === "form_ready") {
1182
+ formFailures.current = 0;
1183
+ setNotice((n) => n?.kind === "form_failed" ? null : n);
1184
+ }
1185
+ if (e.type === "form_failed") {
1186
+ if (formFailures.current < formAutoRetries) {
1187
+ formFailures.current += 1;
1188
+ setSectionKey((k) => k + 1);
1189
+ } else {
1190
+ setNotice({ kind: "form_failed" });
1191
+ }
1192
+ }
1193
+ if (e.type === "error") unknownByEvent.current = e.kind === "network" || e.kind === "payment_in_progress";
1194
+ if (e.type === "result") {
1195
+ unknownByEvent.current = false;
1196
+ lastIssue.current = null;
1197
+ }
1198
+ if (e.type === "result" && e.status === "rejected") setNotice({ kind: "rejected" });
1199
+ if (e.type === "error" && lastIssue.current?.kind === "store_closed") live.current.onRevalidate?.();
1200
+ },
1201
+ [formAutoRetries]
1202
+ );
1203
+ return useMemo(() => ({ notice, setNotice, sectionKey, onIssue, onEvent, lastIssue, retry, clear: () => setNotice(null) }), [notice, sectionKey, onIssue, onEvent, retry]);
1204
+ }
1205
+ function PaymentRecovery(props) {
1206
+ const { notice, alternatives = [], onRetry, onPick, onRefresh, alternativeLabel, classNames: cx = {}, testIds = {}, children } = props;
1207
+ const locale = useResolvedLocale(props.locale);
1208
+ const m = useMemo(() => paymentRecoveryMessages(locale, props.messages), [locale, props.messages]);
1209
+ const rowNames = useMemo(() => paymentMethodMessages(locale).options, [locale]);
1210
+ const ref = useRef(null);
1211
+ const kind = notice?.kind;
1212
+ useEffect(() => {
1213
+ if (kind && kind !== "confirming") {
1214
+ ref.current?.focus();
1215
+ ref.current?.scrollIntoView?.({ block: "nearest", behavior: "smooth" });
1216
+ }
1217
+ }, [kind]);
1218
+ if (!notice) return null;
1219
+ const confirming = notice.kind === "confirming";
1220
+ const canRetry = notice.kind !== "confirming" && notice.kind !== "stale_checkout";
1221
+ const showAlternatives = notice.kind !== "stale_checkout" && !confirming && alternatives.length > 0;
1222
+ const alternativesFirst = notice.kind === "merchant_config";
1223
+ const nameOf = (c) => c.label ?? (c.option ? rowNames[c.option] : void 0) ?? c.option ?? c.methodKey;
1224
+ const labelOf = (c) => alternativeLabel?.(c) ?? m["action.alternative"].replace("{label}", nameOf(c));
1225
+ const retryButton = canRetry ? /* @__PURE__ */ jsx("button", { type: "button", className: alternativesFirst ? cx.secondary : cx.primary, onClick: onRetry, style: { minHeight: 44 }, "data-sf-pay-retry": true, ...testIds.retry ? { "data-testid": testIds.retry } : {}, children: notice.kind === "form_failed" ? m["action.retry_form"] : m["action.retry"] }) : null;
1226
+ return /* @__PURE__ */ jsxs("div", { ref, tabIndex: -1, role: confirming ? "status" : "alert", "aria-live": confirming ? "polite" : void 0, className: cx.root, "data-sf-pay-recovery": notice.kind, children: [
1227
+ /* @__PURE__ */ jsx("p", { className: cx.title, children: /* @__PURE__ */ jsx("strong", { children: m[`notice.${notice.kind}.title`] }) }),
1228
+ /* @__PURE__ */ jsx("p", { className: cx.body, children: m[`notice.${notice.kind}.body`] }),
1229
+ !confirming ? /* @__PURE__ */ jsxs("div", { className: cx.actions, children: [
1230
+ alternativesFirst ? null : retryButton,
1231
+ notice.kind === "stale_checkout" ? /* @__PURE__ */ jsx("button", { type: "button", className: cx.primary, onClick: onRefresh, style: { minHeight: 44 }, "data-sf-pay-refresh": true, ...testIds.refresh ? { "data-testid": testIds.refresh } : {}, children: m["action.refresh"] }) : null,
1232
+ showAlternatives ? alternatives.map((c) => /* @__PURE__ */ jsx("button", { type: "button", className: alternativesFirst ? cx.primary : cx.secondary, onClick: () => onPick(c), style: { minHeight: 44 }, "data-sf-pay-alternative": c.value, children: labelOf(c) }, c.value)) : null,
1233
+ alternativesFirst ? retryButton : null
1234
+ ] }) : null,
1235
+ children
1236
+ ] });
1237
+ }
1238
+
1239
+ // src/payments/revalidate.ts
1240
+ function checkoutShouldRevalidate(options) {
1241
+ const paths = options.paths.map((p) => p.replace(/\/+$/, "") || "/");
1242
+ return (args) => {
1243
+ const method = args.formMethod?.toUpperCase();
1244
+ if (!method || method === "GET" || !args.formAction) return args.defaultShouldRevalidate;
1245
+ let pathname = args.formAction;
1246
+ try {
1247
+ pathname = new URL(args.formAction, "http://sf.local").pathname;
1248
+ } catch {
1249
+ }
1250
+ pathname = pathname.replace(/\/+$/, "") || "/";
1251
+ return paths.includes(pathname) ? false : args.defaultShouldRevalidate;
1252
+ };
1253
+ }
736
1254
  var PAYMENT_CHOICE_FIELD = "payment_choice";
737
1255
  var CASH_POINT_FIELD = "cash_point";
738
1256
  function formFields(form) {
@@ -859,6 +1377,15 @@ function PaymentMethodSection(props) {
859
1377
  const requote = wantSync && settled && sync.requote === true;
860
1378
  const syncFailed = wantSync && settled && !sync.ok && !requote;
861
1379
  const brickReady = cardActive && deliveryReady && synced && !syncFailed;
1380
+ const [formLatched, setFormLatched] = useState(false);
1381
+ useEffect(() => {
1382
+ if (brickReady) setFormLatched(true);
1383
+ }, [brickReady]);
1384
+ useEffect(() => {
1385
+ if (!cardActive) setFormLatched(false);
1386
+ }, [cardActive]);
1387
+ const showBrick = cardActive && !syncFailed && (formLatched || brickReady);
1388
+ const totalUpdating = showBrick && !brickReady;
862
1389
  const transport = useMemo(
863
1390
  () => props.transport ?? createFetchTransport(payUrl, {
864
1391
  ...statusUrl ? { refreshUrl: statusUrl } : {},
@@ -972,9 +1499,9 @@ function PaymentMethodSection(props) {
972
1499
  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
973
1500
  ] }),
974
1501
  /* @__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: [
1502
+ choice.mode === "card" && checked && cardChosen && hydrated && choices.cardClient ? /* @__PURE__ */ jsxs("div", { className: cx.brickWrap, "data-sf-pay-card": true, ...totalUpdating ? { "data-sf-pay-updating": "" } : {}, "aria-busy": totalUpdating || void 0, ...tid("card"), children: [
976
1503
  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(
1504
+ showBrick || fallback ? /* @__PURE__ */ jsx(
978
1505
  PaymentBrick,
979
1506
  {
980
1507
  ...brickProps,
@@ -986,15 +1513,14 @@ function PaymentMethodSection(props) {
986
1513
  locale,
987
1514
  ...scope ? { scope } : {},
988
1515
  ...payerEmail && !brickProps?.email ? { email: payerEmail } : {},
989
- enabled: brickReady,
1516
+ enabled: showBrick,
990
1517
  ...cx.brick ? { className: cx.brick } : {},
991
1518
  onEvent: handleEvent,
992
1519
  onFallback: () => setFallback(true),
993
1520
  onResult: (o) => {
994
1521
  if (o.kind === "approved" || o.kind === "in_review" || o.kind === "voucher") onDone(o);
995
1522
  }
996
- },
997
- payerEmail || brickProps?.email ? "with-email" : "without-email"
1523
+ }
998
1524
  ) : null,
999
1525
  /* @__PURE__ */ jsx("p", { className: cx.note, children: m.secureCard }),
1000
1526
  expired && rebuildHref ? Link({ href: rebuildHref, ...cx.rebuild ? { className: cx.rebuild } : {}, children: m.rebuildCart, ...testIds.rebuild ? { testId: testIds.rebuild } : {} }) : null
@@ -1013,7 +1539,7 @@ function PaymentMethodSection(props) {
1013
1539
  onClick: onPayClick,
1014
1540
  className: cx.submit,
1015
1541
  disabled: busy || blocked,
1016
- "aria-disabled": blocked || brickReady && (!ready || processing) || void 0,
1542
+ "aria-disabled": blocked || cardActive && !syncFailed && deliveryReady && (!brickReady || !ready || processing) || void 0,
1017
1543
  "aria-busy": processing || void 0,
1018
1544
  "data-sf-pay-method-submit": true,
1019
1545
  ...tid("submit"),
@@ -1104,6 +1630,6 @@ function rowMarks(choice, cardBrands) {
1104
1630
  return [];
1105
1631
  }
1106
1632
 
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 };
1633
+ export { CASH_POINT_FIELD, MERCADOPAGO_BRICKS_KIND, PAYMENT_CHOICE_FIELD, PAYMENT_THEME_TOKENS, PaymentBrick, PaymentChallenge, PaymentHttpError, PaymentMethodSection, PaymentMountCancelled, PaymentMountTimeout, PaymentRecovery, VoucherCard, alternativeChoices, checkoutShouldRevalidate, createFetchTransport, createResilientTransport, formFields, isResolvedAttempt, loadPaymentSdk, mercadoPagoBricksAdapter, mountQueued, paymentMethodMessages, pickChoice, readPaymentScheme, readPaymentTheme, resetPaymentSdkCache, sanitizeMercadoPagoFormData, useEmbeddedPayment, usePaymentRecovery, withMountQueue };
1108
1634
  //# sourceMappingURL=payments.js.map
1109
1635
  //# sourceMappingURL=payments.js.map