@asteby/storefront-react 0.16.0 → 0.17.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
@@ -68,3 +68,22 @@
68
68
  display: none !important;
69
69
  }
70
70
  }
71
+
72
+ /* "Updating the total": the card form stays mounted; this layer only dims it and blocks input meanwhile. */
73
+ [data-sf-pay-card][data-sf-pay-updating] [data-sf-pay-container] {
74
+ opacity: 0.5;
75
+ pointer-events: none;
76
+ }
77
+ /* Notice of a payment that did not end well (<PaymentRecovery>). Neutral spacing only; colors come from `classNames`. */
78
+ [data-sf-pay-recovery] {
79
+ display: grid;
80
+ gap: 0.75rem;
81
+ }
82
+ [data-sf-pay-recovery] > p {
83
+ margin: 0;
84
+ }
85
+ [data-sf-pay-recovery] > div {
86
+ display: flex;
87
+ flex-wrap: wrap;
88
+ gap: 0.5rem;
89
+ }
@@ -1,8 +1,8 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode, CSSProperties, RefObject } from 'react';
3
- import { Money, PaymentAttemptResult, PaymentClient, PaymentOutcomeKind, PaymentOutcome, PaymentErrorView, PaymentMessages, PaymentErrorKind, PaymentOptionKey } from '@asteby/storefront';
4
- import { a as PayChoices } from './choices-DEPE9KRq.cjs';
5
- export { B as BuildPayChoicesOptions, P as PayChoice, b as PayErrorBody, c as PaymentCspMode, d as PaymentCspOptions, V as VoucherRowLabels, e as buildPayChoices, f as cashProviderData, g as checkoutToAttempt, h as choiceValue, p as parseChoice, i as payErrorResponse, j as paymentCsp, k as paymentCspHeaderName, l as paymentCspHeaders, m as paymentCspMode, n as paymentNonce, o as paymentNonceFromHeaders, v as voucherRows } from './choices-DEPE9KRq.cjs';
3
+ import { Money, PaymentAttemptResult, PaymentClient, PaymentErrorClassKind, PaymentOutcomeKind, PaymentOutcome, PaymentErrorView, PaymentMessages, PaymentErrorKind, PaymentOptionKey } from '@asteby/storefront';
4
+ import { h as PaymentRecoveryMessages, f as PaymentNoticeKind, P as PayChoice, a as PayChoices } from './csp-DZPuExzu.cjs';
5
+ export { B as BuildPayChoicesOptions, b as PayErrorBody, c as PayErrorOptions, d as PaymentCspMode, e as PaymentCspOptions, g as PaymentRecoveryMessageKey, V as VoucherRowLabels, i as buildPayChoices, j as cashProviderData, k as checkoutToAttempt, l as choiceValue, p as parseChoice, m as payErrorResponse, n as paymentCsp, o as paymentCspHeaderName, q as paymentCspHeaders, r as paymentCspMode, s as paymentNonce, t as paymentNonceFromHeaders, u as paymentRecoveryMessages, v as safeHttpStatus, w as voucherRows } from './csp-DZPuExzu.cjs';
6
6
 
7
7
  /**
8
8
  * Replaceable ports of the embedded payment. The shop (or another provider)
@@ -37,6 +37,8 @@ type PaymentFormMountOptions = {
37
37
  colorScheme?: 'light' | 'dark';
38
38
  onReady: () => void;
39
39
  onError: (error: PaymentFormError) => void;
40
+ /** Aborted when this mount is no longer wanted (the form unmounted or was replaced). Adapters must not create after it. */
41
+ signal?: AbortSignal;
40
42
  };
41
43
  /** What a mounted form hands back at submit time. */
42
44
  type PaymentFormData = {
@@ -48,6 +50,13 @@ type PaymentFormData = {
48
50
  interface MountedPaymentForm {
49
51
  /** Tokenises the card inside the provider iframes. Rejects when the form is invalid (the provider shows the field errors). */
50
52
  collect(): Promise<PaymentFormData>;
53
+ /**
54
+ * Applies a new server total to the form that is already on screen, without remounting it. Rejects when the provider
55
+ * cannot (the hook then remounts, queued). Absent: the hook remounts on every total change.
56
+ */
57
+ update?(next: {
58
+ amount: Money;
59
+ }): Promise<void>;
51
60
  unmount(): void | Promise<void>;
52
61
  }
53
62
  interface PaymentFormAdapter {
@@ -89,7 +98,16 @@ declare class PaymentHttpError extends Error {
89
98
  readonly name = "PaymentHttpError";
90
99
  constructor(status: number, code: string, message?: string,
91
100
  /** Raw `error.details` (e.g. the new `cost` of a `price_mismatch`); untrusted shape. */
92
- details?: unknown | undefined);
101
+ details?: unknown | undefined,
102
+ /** Optional classification from the shop's JSON body (`retryable`, `kind`). */
103
+ extra?: {
104
+ retryable?: boolean | undefined;
105
+ kind?: PaymentErrorClassKind | undefined;
106
+ });
107
+ /** `error.retryable` of the shop body: `true` repeat with the SAME key; `false` it is an answer. `undefined`: not said. */
108
+ readonly retryable: boolean | undefined;
109
+ /** `error.kind` of the shop body (`classifyPaymentError`). */
110
+ readonly kind: PaymentErrorClassKind | undefined;
93
111
  }
94
112
  type FetchTransportOptions = {
95
113
  fetch?: typeof fetch;
@@ -135,7 +153,11 @@ type PaymentEvent = {
135
153
  type UseEmbeddedPaymentOptions = {
136
154
  /** Provider data from `paymentMethods[].client`. `null`/absent: nothing to mount (status `idle`). */
137
155
  client: PaymentClient | null | undefined;
138
- /** The total computed by the server. The Brick is remounted when it changes. Never calculate it in the shop. */
156
+ /**
157
+ * The total computed by the server. When it changes the form on screen is UPDATED in place (`MountedPaymentForm.update`)
158
+ * or, if the provider cannot, mounted again through the queue; never while a charge is in flight (applied after the
159
+ * answer). Never calculate it in the shop.
160
+ */
139
161
  amount: Money;
140
162
  transport: PaymentTransport;
141
163
  /** Mount only while the card option is selected. Default true. */
@@ -154,6 +176,12 @@ type UseEmbeddedPaymentOptions = {
154
176
  autoRetries?: number;
155
177
  /** Base delay for the backoff between automatic retries. Default 600. */
156
178
  retryDelayMs?: number;
179
+ /** Longest wait for the provider's `create` before the form is declared failed. Default `sdkTimeoutMs`. */
180
+ createTimeoutMs?: number;
181
+ /** After an UNKNOWN outcome ("verifying"), how long the hook keeps reading `refresh`, with growing waits. Default 120000. */
182
+ verifyTimeoutMs?: number;
183
+ /** First wait of that check (doubles up to 15 s). Default 2000. */
184
+ verifyIntervalMs?: number;
157
185
  /** Poll interval/timeout for a pending 3DS challenge or review when `transport.refresh` exists. */
158
186
  pollIntervalMs?: number;
159
187
  pollTimeoutMs?: number;
@@ -187,6 +215,8 @@ type EmbeddedPaymentState = {
187
215
  attemptId: string | null;
188
216
  /** `true` while loading, mounting or processing. */
189
217
  busy: boolean;
218
+ /** `true` while the hook reads the payment state in the background after an unknown outcome. The attempt stays the same. */
219
+ verifying: boolean;
190
220
  /** Attach to the element the provider renders into. */
191
221
  containerRef: (el: HTMLElement | null) => void;
192
222
  containerId: string;
@@ -272,11 +302,182 @@ declare const PaymentBrick: react.ForwardRefExoticComponent<Omit<UseEmbeddedPaym
272
302
  style?: CSSProperties;
273
303
  } & react.RefAttributes<EmbeddedPaymentState>>;
274
304
 
305
+ /**
306
+ * Mount queue for provider forms. Provider SDKs (Mercado Pago Bricks among them) resolve their container by id,
307
+ * asynchronously, and unmount asynchronously too. Without care, a remount races the previous unmount on the same node
308
+ * and the provider answers `container_not_found`, leaves an empty form, or draws two.
309
+ *
310
+ * Guarantees, per container element:
311
+ * - ONE mount at a time: a new mount waits for the previous one to finish tearing down (`await unmount`) before `create`.
312
+ * - Each mount gets its OWN inner node with a unique id, so the provider never looks for an id React already replaced.
313
+ * - A mount that was cancelled (`signal`) or whose container left the DOM never calls the provider; one that finishes
314
+ * late is torn down instead of kept.
315
+ * - `create` has a time limit: a provider call that never resolves cannot freeze the chain.
316
+ */
317
+
318
+ declare class PaymentMountCancelled extends Error {
319
+ readonly name = "PaymentMountCancelled";
320
+ constructor(message?: string);
321
+ }
322
+ declare class PaymentMountTimeout extends Error {
323
+ readonly name = "PaymentMountTimeout";
324
+ constructor(ms: number);
325
+ }
326
+ type MountQueueOptions = {
327
+ /** Longest wait for the provider's `create`. Default 10000. */
328
+ createTimeoutMs?: number;
329
+ };
330
+ /** Mounts `base` on `opts.container` through the queue. See the module comment for the guarantees. */
331
+ declare function mountQueued(base: PaymentFormAdapter, opts: PaymentFormMountOptions, queue?: MountQueueOptions): Promise<MountedPaymentForm>;
332
+ /** Wraps an adapter so every `mount` goes through the queue. The hook already does this for any adapter it uses. */
333
+ declare function withMountQueue(adapter: PaymentFormAdapter, queue?: MountQueueOptions): PaymentFormAdapter;
334
+
335
+ /**
336
+ * Browser transport to the shop's pay and status routes, with the retry policy a card payment needs.
337
+ *
338
+ * - A TRANSIENT failure (5xx, timeout, network, a proxy HTML page) is repeated up to `retries` times with the SAME
339
+ * idempotency key (`request.idempotencyKey`): OPS then answers with the result of the first attempt. A payment whose
340
+ * outcome is unknown is NEVER charged with another key.
341
+ * - If it is still unknown, the status route is read `statusPolls` times. A resolved answer (paid, failed, voucher,
342
+ * challenge...) is returned; otherwise a network error is thrown and the hook keeps the SAME attempt (retry button
343
+ * and background checks).
344
+ * - `retryable: false` (an answer from OPS) is ONE request: no repeat, no status read.
345
+ * - A body that is not JSON is treated as transient, never as a definitive error.
346
+ */
347
+
348
+ /** What the screen is told while a payment is being repeated or checked, and when it ends with a problem. */
349
+ type PaymentIssue = {
350
+ kind: 'confirming';
351
+ message: string;
352
+ } | {
353
+ kind: 'verifying';
354
+ message: string;
355
+ } | {
356
+ kind: PaymentErrorClassKind;
357
+ message: string;
358
+ retryable: boolean;
359
+ status: number;
360
+ code: string;
361
+ };
362
+ type ResilientTransportOptions = {
363
+ /** Same-origin route that charges the card (`handlers.pay`). */
364
+ payUrl: string;
365
+ /** Same-origin route that re-reads the payment (`handlers.status`). Enables the status checks and `refresh`. */
366
+ statusUrl: string;
367
+ /** Fields of the page form (delivery, contact, payment choice) that travel with the charge. */
368
+ fields: () => Record<string, string>;
369
+ /** Called with the state of the retry/verification and with the final problem; `null` clears it. */
370
+ onIssue?: (issue: PaymentIssue | null) => void;
371
+ fetch?: typeof fetch;
372
+ sleep?: (ms: number) => Promise<void>;
373
+ /** Automatic repeats after the first request. Default 2. */
374
+ retries?: number;
375
+ /** Wait before each repeat (ms); the last value repeats. Default `[800, 2000]`. */
376
+ backoffMs?: readonly number[];
377
+ /** Status reads before declaring the result "verifying". Default 3. */
378
+ statusPolls?: number;
379
+ /** Wait between status reads (ms). Default 2000. */
380
+ statusPollMs?: number;
381
+ /** Extra headers for both routes. */
382
+ headers?: Record<string, string>;
383
+ locale?: string;
384
+ messages?: Partial<PaymentRecoveryMessages>;
385
+ };
386
+ /** Has OPS decided? `pending` without an action, and `none`, are NOT decisions. */
387
+ declare function isResolvedAttempt(result: PaymentAttemptResult): boolean;
388
+ declare function createResilientTransport(opts: ResilientTransportOptions): PaymentTransport;
389
+
390
+ type PaymentNotice = {
391
+ kind: PaymentNoticeKind;
392
+ };
393
+ /** Payment rows other than the in-page card: what the buyer can switch to. */
394
+ declare function alternativeChoices(choices: readonly PayChoice[]): PayChoice[];
395
+ /** Selects a payment row in the page form (the section's radios are the source of truth) and brings it into view. */
396
+ declare function pickChoice(form: HTMLFormElement | null | undefined, value: string): void;
397
+ type UsePaymentRecoveryOptions = {
398
+ /** Same `scope` as the payment section (usually the cart id): where the attempt key is kept. */
399
+ scope?: string;
400
+ /** Where the attempt key lives. Default `sessionStorage`; `null` = memory only. */
401
+ storage?: Pick<Storage, 'getItem' | 'setItem' | 'removeItem'> | null;
402
+ /** Automatic remounts of the secure form before showing the "form failed" notice. Default 2. */
403
+ formAutoRetries?: number;
404
+ /** The shop is closed (409 `store_not_ready`) or the like: re-read the page data. */
405
+ onRevalidate?: () => void;
406
+ };
407
+ type PaymentRecoveryState = {
408
+ notice: PaymentNotice | null;
409
+ setNotice: (notice: PaymentNotice | null) => void;
410
+ /** Use as `key` of `<PaymentMethodSection>`: a new value mounts a fresh section (and a fresh card form). */
411
+ sectionKey: number;
412
+ /** Pass to `createResilientTransport({ onIssue })`. */
413
+ onIssue: (issue: PaymentIssue | null) => void;
414
+ /** Pass to `<PaymentMethodSection onEvent>` (or compose it with your own handler). */
415
+ onEvent: (event: PaymentEvent) => void;
416
+ /** Last problem reported by the transport. */
417
+ lastIssue: {
418
+ current: PaymentIssue | null;
419
+ };
420
+ /**
421
+ * New attempt: a NEW idempotency key and a fresh form. Only after a definitive failure; returns `false` (and does
422
+ * nothing) while the outcome of the last charge is still unknown.
423
+ */
424
+ retry: () => boolean;
425
+ /** Clears the notice (e.g. after the buyer picked another way to pay). */
426
+ clear: () => void;
427
+ };
428
+ declare function usePaymentRecovery(options?: UsePaymentRecoveryOptions): PaymentRecoveryState;
429
+ type PaymentRecoveryClassNames = Partial<Record<'root' | 'title' | 'body' | 'actions' | 'primary' | 'secondary', string>>;
430
+ type PaymentRecoveryProps = {
431
+ notice: PaymentNotice | null;
432
+ /** Other ways to pay (`alternativeChoices(choices)`); they come from the published choices, never from this component. */
433
+ alternatives?: readonly PayChoice[];
434
+ /** "Retry payment": new key and fresh form (`usePaymentRecovery().retry`). */
435
+ onRetry: () => void;
436
+ /** A way to pay was picked. */
437
+ onPick: (choice: PayChoice) => void;
438
+ /** "Refresh my order": re-read the cart and checkout. */
439
+ onRefresh: () => void;
440
+ locale?: string;
441
+ messages?: Partial<PaymentRecoveryMessages>;
442
+ /** Text of the button of an alternative. Default: the `action.alternative` message with the row name. */
443
+ alternativeLabel?: (choice: PayChoice) => string;
444
+ classNames?: PaymentRecoveryClassNames;
445
+ testIds?: Partial<Record<'retry' | 'refresh', string>>;
446
+ /** Extra content after the buttons. */
447
+ children?: ReactNode;
448
+ };
449
+ /** Notice of a card payment that did not end well. `role="status"` while confirming, `role="alert"` (focused) otherwise. */
450
+ declare function PaymentRecovery(props: PaymentRecoveryProps): react.JSX.Element | null;
451
+
452
+ /**
453
+ * `shouldRevalidate` for the routes AROUND the checkout (root layout, header, cart badge...). The checkout page saves the
454
+ * chosen delivery with a `fetcher.submit` to its own route; by default a router then re-runs EVERY loader on the page
455
+ * (catalog, session, theme...), which is slow and makes the page jump while the buyer is paying. This keeps those loaders
456
+ * quiet for that submission and leaves everything else (their own forms, navigation) as the router decided.
457
+ *
458
+ * Do NOT use it on the checkout route itself: its loader must re-read the cart (the new total) after the submit.
459
+ * Framework-agnostic: it only reads the fields every router passes (`formMethod`, `formAction`, `defaultShouldRevalidate`).
460
+ */
461
+ type RevalidateArgs = {
462
+ formMethod?: string | undefined;
463
+ formAction?: string | undefined;
464
+ defaultShouldRevalidate: boolean;
465
+ };
466
+ type CheckoutRevalidateOptions = {
467
+ /** Paths whose form submissions do not need the other loaders (e.g. `['/pagar']`). Matched on the pathname. */
468
+ paths: readonly string[];
469
+ };
470
+ declare function checkoutShouldRevalidate(options: CheckoutRevalidateOptions): (args: RevalidateArgs) => boolean;
471
+
275
472
  /** The `PaymentClient.kind` the addon `mercadopago` declares. */
276
473
  declare const MERCADOPAGO_BRICKS_KIND = "mercadopago_bricks";
277
474
  type MpController = {
278
475
  getFormData(): Promise<Record<string, unknown> | undefined | null>;
279
476
  unmount(): void | Promise<void>;
477
+ /** Card Payment Brick: changes the amount without recreating the form. Absent in old SDK builds. */
478
+ update?(settings: {
479
+ amount: number;
480
+ }): void | Promise<void>;
280
481
  };
281
482
  type MpBricks = {
282
483
  create(brick: string, containerId: string, settings: Record<string, unknown>): Promise<MpController>;
@@ -513,4 +714,4 @@ declare function readPaymentTheme(el: Element | null | undefined, tokens?: Recor
513
714
  */
514
715
  declare function readPaymentScheme(el: Element | null | undefined): 'light' | 'dark' | null;
515
716
 
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 };
717
+ export { CASH_POINT_FIELD, type CheckoutRevalidateOptions, type EmbeddedPaymentState, type EmbeddedPaymentStatus, type FetchTransportOptions, type LoadScriptOptions, MERCADOPAGO_BRICKS_KIND, type MercadoPagoAdapterOptions, type MercadoPagoCtor, type MountQueueOptions, type MountedPaymentForm, PAYMENT_CHOICE_FIELD, PAYMENT_THEME_TOKENS, PayChoice, PayChoices, PaymentBrick, type PaymentBrickHandle, type PaymentBrickProps, PaymentChallenge, type PaymentChallengeProps, type PaymentEvent, type PaymentFallbackReason, type PaymentFormAdapter, type PaymentFormData, type PaymentFormError, type PaymentFormMountOptions, PaymentHttpError, type PaymentIssue, type PaymentMethodClassNames, type PaymentMethodMessages, PaymentMethodSection, type PaymentMethodSectionProps, type PaymentMethodTestIds, PaymentMountCancelled, PaymentMountTimeout, type PaymentNotice, PaymentNoticeKind, PaymentRecovery, type PaymentRecoveryClassNames, PaymentRecoveryMessages, type PaymentRecoveryProps, type PaymentRecoveryState, type PaymentSubmitRequest, type PaymentTransport, type ResilientTransportOptions, type RevalidateArgs, type UseEmbeddedPaymentOptions, type UsePaymentRecoveryOptions, VoucherCard, type VoucherCardProps, alternativeChoices, checkoutShouldRevalidate, createFetchTransport, createResilientTransport, formFields, isResolvedAttempt, loadPaymentSdk, mercadoPagoBricksAdapter, mountQueued, paymentMethodMessages, pickChoice, readPaymentScheme, readPaymentTheme, resetPaymentSdkCache, sanitizeMercadoPagoFormData, useEmbeddedPayment, usePaymentRecovery, withMountQueue };
@@ -1,8 +1,8 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode, CSSProperties, RefObject } from 'react';
3
- import { Money, PaymentAttemptResult, PaymentClient, PaymentOutcomeKind, PaymentOutcome, PaymentErrorView, PaymentMessages, PaymentErrorKind, PaymentOptionKey } from '@asteby/storefront';
4
- import { a as PayChoices } from './choices-DEPE9KRq.js';
5
- export { B as BuildPayChoicesOptions, P as PayChoice, b as PayErrorBody, c as PaymentCspMode, d as PaymentCspOptions, V as VoucherRowLabels, e as buildPayChoices, f as cashProviderData, g as checkoutToAttempt, h as choiceValue, p as parseChoice, i as payErrorResponse, j as paymentCsp, k as paymentCspHeaderName, l as paymentCspHeaders, m as paymentCspMode, n as paymentNonce, o as paymentNonceFromHeaders, v as voucherRows } from './choices-DEPE9KRq.js';
3
+ import { Money, PaymentAttemptResult, PaymentClient, PaymentErrorClassKind, PaymentOutcomeKind, PaymentOutcome, PaymentErrorView, PaymentMessages, PaymentErrorKind, PaymentOptionKey } from '@asteby/storefront';
4
+ import { h as PaymentRecoveryMessages, f as PaymentNoticeKind, P as PayChoice, a as PayChoices } from './csp-DZPuExzu.js';
5
+ export { B as BuildPayChoicesOptions, b as PayErrorBody, c as PayErrorOptions, d as PaymentCspMode, e as PaymentCspOptions, g as PaymentRecoveryMessageKey, V as VoucherRowLabels, i as buildPayChoices, j as cashProviderData, k as checkoutToAttempt, l as choiceValue, p as parseChoice, m as payErrorResponse, n as paymentCsp, o as paymentCspHeaderName, q as paymentCspHeaders, r as paymentCspMode, s as paymentNonce, t as paymentNonceFromHeaders, u as paymentRecoveryMessages, v as safeHttpStatus, w as voucherRows } from './csp-DZPuExzu.js';
6
6
 
7
7
  /**
8
8
  * Replaceable ports of the embedded payment. The shop (or another provider)
@@ -37,6 +37,8 @@ type PaymentFormMountOptions = {
37
37
  colorScheme?: 'light' | 'dark';
38
38
  onReady: () => void;
39
39
  onError: (error: PaymentFormError) => void;
40
+ /** Aborted when this mount is no longer wanted (the form unmounted or was replaced). Adapters must not create after it. */
41
+ signal?: AbortSignal;
40
42
  };
41
43
  /** What a mounted form hands back at submit time. */
42
44
  type PaymentFormData = {
@@ -48,6 +50,13 @@ type PaymentFormData = {
48
50
  interface MountedPaymentForm {
49
51
  /** Tokenises the card inside the provider iframes. Rejects when the form is invalid (the provider shows the field errors). */
50
52
  collect(): Promise<PaymentFormData>;
53
+ /**
54
+ * Applies a new server total to the form that is already on screen, without remounting it. Rejects when the provider
55
+ * cannot (the hook then remounts, queued). Absent: the hook remounts on every total change.
56
+ */
57
+ update?(next: {
58
+ amount: Money;
59
+ }): Promise<void>;
51
60
  unmount(): void | Promise<void>;
52
61
  }
53
62
  interface PaymentFormAdapter {
@@ -89,7 +98,16 @@ declare class PaymentHttpError extends Error {
89
98
  readonly name = "PaymentHttpError";
90
99
  constructor(status: number, code: string, message?: string,
91
100
  /** Raw `error.details` (e.g. the new `cost` of a `price_mismatch`); untrusted shape. */
92
- details?: unknown | undefined);
101
+ details?: unknown | undefined,
102
+ /** Optional classification from the shop's JSON body (`retryable`, `kind`). */
103
+ extra?: {
104
+ retryable?: boolean | undefined;
105
+ kind?: PaymentErrorClassKind | undefined;
106
+ });
107
+ /** `error.retryable` of the shop body: `true` repeat with the SAME key; `false` it is an answer. `undefined`: not said. */
108
+ readonly retryable: boolean | undefined;
109
+ /** `error.kind` of the shop body (`classifyPaymentError`). */
110
+ readonly kind: PaymentErrorClassKind | undefined;
93
111
  }
94
112
  type FetchTransportOptions = {
95
113
  fetch?: typeof fetch;
@@ -135,7 +153,11 @@ type PaymentEvent = {
135
153
  type UseEmbeddedPaymentOptions = {
136
154
  /** Provider data from `paymentMethods[].client`. `null`/absent: nothing to mount (status `idle`). */
137
155
  client: PaymentClient | null | undefined;
138
- /** The total computed by the server. The Brick is remounted when it changes. Never calculate it in the shop. */
156
+ /**
157
+ * The total computed by the server. When it changes the form on screen is UPDATED in place (`MountedPaymentForm.update`)
158
+ * or, if the provider cannot, mounted again through the queue; never while a charge is in flight (applied after the
159
+ * answer). Never calculate it in the shop.
160
+ */
139
161
  amount: Money;
140
162
  transport: PaymentTransport;
141
163
  /** Mount only while the card option is selected. Default true. */
@@ -154,6 +176,12 @@ type UseEmbeddedPaymentOptions = {
154
176
  autoRetries?: number;
155
177
  /** Base delay for the backoff between automatic retries. Default 600. */
156
178
  retryDelayMs?: number;
179
+ /** Longest wait for the provider's `create` before the form is declared failed. Default `sdkTimeoutMs`. */
180
+ createTimeoutMs?: number;
181
+ /** After an UNKNOWN outcome ("verifying"), how long the hook keeps reading `refresh`, with growing waits. Default 120000. */
182
+ verifyTimeoutMs?: number;
183
+ /** First wait of that check (doubles up to 15 s). Default 2000. */
184
+ verifyIntervalMs?: number;
157
185
  /** Poll interval/timeout for a pending 3DS challenge or review when `transport.refresh` exists. */
158
186
  pollIntervalMs?: number;
159
187
  pollTimeoutMs?: number;
@@ -187,6 +215,8 @@ type EmbeddedPaymentState = {
187
215
  attemptId: string | null;
188
216
  /** `true` while loading, mounting or processing. */
189
217
  busy: boolean;
218
+ /** `true` while the hook reads the payment state in the background after an unknown outcome. The attempt stays the same. */
219
+ verifying: boolean;
190
220
  /** Attach to the element the provider renders into. */
191
221
  containerRef: (el: HTMLElement | null) => void;
192
222
  containerId: string;
@@ -272,11 +302,182 @@ declare const PaymentBrick: react.ForwardRefExoticComponent<Omit<UseEmbeddedPaym
272
302
  style?: CSSProperties;
273
303
  } & react.RefAttributes<EmbeddedPaymentState>>;
274
304
 
305
+ /**
306
+ * Mount queue for provider forms. Provider SDKs (Mercado Pago Bricks among them) resolve their container by id,
307
+ * asynchronously, and unmount asynchronously too. Without care, a remount races the previous unmount on the same node
308
+ * and the provider answers `container_not_found`, leaves an empty form, or draws two.
309
+ *
310
+ * Guarantees, per container element:
311
+ * - ONE mount at a time: a new mount waits for the previous one to finish tearing down (`await unmount`) before `create`.
312
+ * - Each mount gets its OWN inner node with a unique id, so the provider never looks for an id React already replaced.
313
+ * - A mount that was cancelled (`signal`) or whose container left the DOM never calls the provider; one that finishes
314
+ * late is torn down instead of kept.
315
+ * - `create` has a time limit: a provider call that never resolves cannot freeze the chain.
316
+ */
317
+
318
+ declare class PaymentMountCancelled extends Error {
319
+ readonly name = "PaymentMountCancelled";
320
+ constructor(message?: string);
321
+ }
322
+ declare class PaymentMountTimeout extends Error {
323
+ readonly name = "PaymentMountTimeout";
324
+ constructor(ms: number);
325
+ }
326
+ type MountQueueOptions = {
327
+ /** Longest wait for the provider's `create`. Default 10000. */
328
+ createTimeoutMs?: number;
329
+ };
330
+ /** Mounts `base` on `opts.container` through the queue. See the module comment for the guarantees. */
331
+ declare function mountQueued(base: PaymentFormAdapter, opts: PaymentFormMountOptions, queue?: MountQueueOptions): Promise<MountedPaymentForm>;
332
+ /** Wraps an adapter so every `mount` goes through the queue. The hook already does this for any adapter it uses. */
333
+ declare function withMountQueue(adapter: PaymentFormAdapter, queue?: MountQueueOptions): PaymentFormAdapter;
334
+
335
+ /**
336
+ * Browser transport to the shop's pay and status routes, with the retry policy a card payment needs.
337
+ *
338
+ * - A TRANSIENT failure (5xx, timeout, network, a proxy HTML page) is repeated up to `retries` times with the SAME
339
+ * idempotency key (`request.idempotencyKey`): OPS then answers with the result of the first attempt. A payment whose
340
+ * outcome is unknown is NEVER charged with another key.
341
+ * - If it is still unknown, the status route is read `statusPolls` times. A resolved answer (paid, failed, voucher,
342
+ * challenge...) is returned; otherwise a network error is thrown and the hook keeps the SAME attempt (retry button
343
+ * and background checks).
344
+ * - `retryable: false` (an answer from OPS) is ONE request: no repeat, no status read.
345
+ * - A body that is not JSON is treated as transient, never as a definitive error.
346
+ */
347
+
348
+ /** What the screen is told while a payment is being repeated or checked, and when it ends with a problem. */
349
+ type PaymentIssue = {
350
+ kind: 'confirming';
351
+ message: string;
352
+ } | {
353
+ kind: 'verifying';
354
+ message: string;
355
+ } | {
356
+ kind: PaymentErrorClassKind;
357
+ message: string;
358
+ retryable: boolean;
359
+ status: number;
360
+ code: string;
361
+ };
362
+ type ResilientTransportOptions = {
363
+ /** Same-origin route that charges the card (`handlers.pay`). */
364
+ payUrl: string;
365
+ /** Same-origin route that re-reads the payment (`handlers.status`). Enables the status checks and `refresh`. */
366
+ statusUrl: string;
367
+ /** Fields of the page form (delivery, contact, payment choice) that travel with the charge. */
368
+ fields: () => Record<string, string>;
369
+ /** Called with the state of the retry/verification and with the final problem; `null` clears it. */
370
+ onIssue?: (issue: PaymentIssue | null) => void;
371
+ fetch?: typeof fetch;
372
+ sleep?: (ms: number) => Promise<void>;
373
+ /** Automatic repeats after the first request. Default 2. */
374
+ retries?: number;
375
+ /** Wait before each repeat (ms); the last value repeats. Default `[800, 2000]`. */
376
+ backoffMs?: readonly number[];
377
+ /** Status reads before declaring the result "verifying". Default 3. */
378
+ statusPolls?: number;
379
+ /** Wait between status reads (ms). Default 2000. */
380
+ statusPollMs?: number;
381
+ /** Extra headers for both routes. */
382
+ headers?: Record<string, string>;
383
+ locale?: string;
384
+ messages?: Partial<PaymentRecoveryMessages>;
385
+ };
386
+ /** Has OPS decided? `pending` without an action, and `none`, are NOT decisions. */
387
+ declare function isResolvedAttempt(result: PaymentAttemptResult): boolean;
388
+ declare function createResilientTransport(opts: ResilientTransportOptions): PaymentTransport;
389
+
390
+ type PaymentNotice = {
391
+ kind: PaymentNoticeKind;
392
+ };
393
+ /** Payment rows other than the in-page card: what the buyer can switch to. */
394
+ declare function alternativeChoices(choices: readonly PayChoice[]): PayChoice[];
395
+ /** Selects a payment row in the page form (the section's radios are the source of truth) and brings it into view. */
396
+ declare function pickChoice(form: HTMLFormElement | null | undefined, value: string): void;
397
+ type UsePaymentRecoveryOptions = {
398
+ /** Same `scope` as the payment section (usually the cart id): where the attempt key is kept. */
399
+ scope?: string;
400
+ /** Where the attempt key lives. Default `sessionStorage`; `null` = memory only. */
401
+ storage?: Pick<Storage, 'getItem' | 'setItem' | 'removeItem'> | null;
402
+ /** Automatic remounts of the secure form before showing the "form failed" notice. Default 2. */
403
+ formAutoRetries?: number;
404
+ /** The shop is closed (409 `store_not_ready`) or the like: re-read the page data. */
405
+ onRevalidate?: () => void;
406
+ };
407
+ type PaymentRecoveryState = {
408
+ notice: PaymentNotice | null;
409
+ setNotice: (notice: PaymentNotice | null) => void;
410
+ /** Use as `key` of `<PaymentMethodSection>`: a new value mounts a fresh section (and a fresh card form). */
411
+ sectionKey: number;
412
+ /** Pass to `createResilientTransport({ onIssue })`. */
413
+ onIssue: (issue: PaymentIssue | null) => void;
414
+ /** Pass to `<PaymentMethodSection onEvent>` (or compose it with your own handler). */
415
+ onEvent: (event: PaymentEvent) => void;
416
+ /** Last problem reported by the transport. */
417
+ lastIssue: {
418
+ current: PaymentIssue | null;
419
+ };
420
+ /**
421
+ * New attempt: a NEW idempotency key and a fresh form. Only after a definitive failure; returns `false` (and does
422
+ * nothing) while the outcome of the last charge is still unknown.
423
+ */
424
+ retry: () => boolean;
425
+ /** Clears the notice (e.g. after the buyer picked another way to pay). */
426
+ clear: () => void;
427
+ };
428
+ declare function usePaymentRecovery(options?: UsePaymentRecoveryOptions): PaymentRecoveryState;
429
+ type PaymentRecoveryClassNames = Partial<Record<'root' | 'title' | 'body' | 'actions' | 'primary' | 'secondary', string>>;
430
+ type PaymentRecoveryProps = {
431
+ notice: PaymentNotice | null;
432
+ /** Other ways to pay (`alternativeChoices(choices)`); they come from the published choices, never from this component. */
433
+ alternatives?: readonly PayChoice[];
434
+ /** "Retry payment": new key and fresh form (`usePaymentRecovery().retry`). */
435
+ onRetry: () => void;
436
+ /** A way to pay was picked. */
437
+ onPick: (choice: PayChoice) => void;
438
+ /** "Refresh my order": re-read the cart and checkout. */
439
+ onRefresh: () => void;
440
+ locale?: string;
441
+ messages?: Partial<PaymentRecoveryMessages>;
442
+ /** Text of the button of an alternative. Default: the `action.alternative` message with the row name. */
443
+ alternativeLabel?: (choice: PayChoice) => string;
444
+ classNames?: PaymentRecoveryClassNames;
445
+ testIds?: Partial<Record<'retry' | 'refresh', string>>;
446
+ /** Extra content after the buttons. */
447
+ children?: ReactNode;
448
+ };
449
+ /** Notice of a card payment that did not end well. `role="status"` while confirming, `role="alert"` (focused) otherwise. */
450
+ declare function PaymentRecovery(props: PaymentRecoveryProps): react.JSX.Element | null;
451
+
452
+ /**
453
+ * `shouldRevalidate` for the routes AROUND the checkout (root layout, header, cart badge...). The checkout page saves the
454
+ * chosen delivery with a `fetcher.submit` to its own route; by default a router then re-runs EVERY loader on the page
455
+ * (catalog, session, theme...), which is slow and makes the page jump while the buyer is paying. This keeps those loaders
456
+ * quiet for that submission and leaves everything else (their own forms, navigation) as the router decided.
457
+ *
458
+ * Do NOT use it on the checkout route itself: its loader must re-read the cart (the new total) after the submit.
459
+ * Framework-agnostic: it only reads the fields every router passes (`formMethod`, `formAction`, `defaultShouldRevalidate`).
460
+ */
461
+ type RevalidateArgs = {
462
+ formMethod?: string | undefined;
463
+ formAction?: string | undefined;
464
+ defaultShouldRevalidate: boolean;
465
+ };
466
+ type CheckoutRevalidateOptions = {
467
+ /** Paths whose form submissions do not need the other loaders (e.g. `['/pagar']`). Matched on the pathname. */
468
+ paths: readonly string[];
469
+ };
470
+ declare function checkoutShouldRevalidate(options: CheckoutRevalidateOptions): (args: RevalidateArgs) => boolean;
471
+
275
472
  /** The `PaymentClient.kind` the addon `mercadopago` declares. */
276
473
  declare const MERCADOPAGO_BRICKS_KIND = "mercadopago_bricks";
277
474
  type MpController = {
278
475
  getFormData(): Promise<Record<string, unknown> | undefined | null>;
279
476
  unmount(): void | Promise<void>;
477
+ /** Card Payment Brick: changes the amount without recreating the form. Absent in old SDK builds. */
478
+ update?(settings: {
479
+ amount: number;
480
+ }): void | Promise<void>;
280
481
  };
281
482
  type MpBricks = {
282
483
  create(brick: string, containerId: string, settings: Record<string, unknown>): Promise<MpController>;
@@ -513,4 +714,4 @@ declare function readPaymentTheme(el: Element | null | undefined, tokens?: Recor
513
714
  */
514
715
  declare function readPaymentScheme(el: Element | null | undefined): 'light' | 'dark' | null;
515
716
 
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 };
717
+ export { CASH_POINT_FIELD, type CheckoutRevalidateOptions, type EmbeddedPaymentState, type EmbeddedPaymentStatus, type FetchTransportOptions, type LoadScriptOptions, MERCADOPAGO_BRICKS_KIND, type MercadoPagoAdapterOptions, type MercadoPagoCtor, type MountQueueOptions, type MountedPaymentForm, PAYMENT_CHOICE_FIELD, PAYMENT_THEME_TOKENS, PayChoice, PayChoices, PaymentBrick, type PaymentBrickHandle, type PaymentBrickProps, PaymentChallenge, type PaymentChallengeProps, type PaymentEvent, type PaymentFallbackReason, type PaymentFormAdapter, type PaymentFormData, type PaymentFormError, type PaymentFormMountOptions, PaymentHttpError, type PaymentIssue, type PaymentMethodClassNames, type PaymentMethodMessages, PaymentMethodSection, type PaymentMethodSectionProps, type PaymentMethodTestIds, PaymentMountCancelled, PaymentMountTimeout, type PaymentNotice, PaymentNoticeKind, PaymentRecovery, type PaymentRecoveryClassNames, PaymentRecoveryMessages, type PaymentRecoveryProps, type PaymentRecoveryState, type PaymentSubmitRequest, type PaymentTransport, type ResilientTransportOptions, type RevalidateArgs, type UseEmbeddedPaymentOptions, type UsePaymentRecoveryOptions, VoucherCard, type VoucherCardProps, alternativeChoices, checkoutShouldRevalidate, createFetchTransport, createResilientTransport, formFields, isResolvedAttempt, loadPaymentSdk, mercadoPagoBricksAdapter, mountQueued, paymentMethodMessages, pickChoice, readPaymentScheme, readPaymentTheme, resetPaymentSdkCache, sanitizeMercadoPagoFormData, useEmbeddedPayment, usePaymentRecovery, withMountQueue };