@asteby/storefront-react 0.8.0 → 0.9.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/README.md +38 -1
- package/dist/choices-DEPE9KRq.d.cts +130 -0
- package/dist/choices-DEPE9KRq.d.ts +130 -0
- package/dist/chunk-ISEOCKIR.js +160 -0
- package/dist/chunk-ISEOCKIR.js.map +1 -0
- package/dist/payments-server.cjs +369 -0
- package/dist/payments-server.cjs.map +1 -0
- package/dist/payments-server.d.cts +145 -0
- package/dist/payments-server.d.ts +145 -0
- package/dist/payments-server.js +191 -0
- package/dist/payments-server.js.map +1 -0
- package/dist/payments.cjs +429 -2
- package/dist/payments.cjs.map +1 -1
- package/dist/payments.css +30 -0
- package/dist/payments.d.cts +122 -21
- package/dist/payments.d.ts +122 -21
- package/dist/payments.js +291 -36
- package/dist/payments.js.map +1 -1
- package/package.json +12 -1
package/README.md
CHANGED
|
@@ -481,6 +481,43 @@ return Response.json(result) // PaymentAttemptResult; 409/422/429 se devuelve
|
|
|
481
481
|
- **Tema**: `--sf-pay-text`, `-text-muted`, `-background`, `-input-background`, `-primary`, `-primary-contrast`, `-error`, `-success`, `-outline`, `-radius` se leen al montar y se pasan al Brick; `--sf-pay-min-height` reserva el alto (sin saltos).
|
|
482
482
|
- **Otro proveedor**: implementa `PaymentFormAdapter` (`load`, `mount → { collect, unmount }`) y pásalo en `adapter`/`adapters={{ stripe_elements: … }}`.
|
|
483
483
|
|
|
484
|
+
### «Método de pago» híbrido (`PaymentMethodSection` + `/payments/server`)
|
|
485
|
+
|
|
486
|
+
Lo que hace falta alrededor del Brick, listo para usar: filas por opción que publica OPS (tarjeta, efectivo, cuenta del proveedor, pagos sin tarjeta), tarjeta con el formulario seguro DENTRO de la página, efectivo con puntos de pago y ficha, y redirección para el resto. Sin JavaScript, o si el formulario no carga, todo cae al envío normal del formulario de la página (redirección). Textos, rutas, iconos y clases por props; el total es siempre `cart.cost.total` de OPS (nunca se suma ni se recalcula).
|
|
487
|
+
|
|
488
|
+
```tsx
|
|
489
|
+
// loader: filas desde GET payment-methods (la tienda oculta lo que no ofrece)
|
|
490
|
+
import { buildPayChoices } from '@asteby/storefront-react/payments'
|
|
491
|
+
const choices = buildPayChoices(payment.methods, { hide: ['manual'] })
|
|
492
|
+
|
|
493
|
+
// componente, dentro del <form> de /pagar
|
|
494
|
+
import '@asteby/storefront-react/payments.css' // opcional: muestra el panel de la fila marcada sin JS
|
|
495
|
+
<PaymentMethodSection
|
|
496
|
+
choices={choices} amount={cart.cost.total} scope={cart.id} formRef={formRef}
|
|
497
|
+
payUrl="/pagar/cobrar" statusUrl="/pagar/estado" nonce={nonce}
|
|
498
|
+
delivery={delivery} deliveryReady={delivery !== '' && !delivery.startsWith('ship:')}
|
|
499
|
+
syncDelivery={saveDeliveryAndReloadCart} // guarda la entrega en el carrito de OPS y resuelve true al releer el total
|
|
500
|
+
onAmountChanged={revalidate} onDone={() => navigate('/pagar/gracias')}
|
|
501
|
+
rebuildHref="/carrito" messages={{ legend: 'Método de pago' }} icons={{ card: <CardIcon /> }}
|
|
502
|
+
classNames={{ radioLabel: '...', submit: 'btn-primary' }} />
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
```ts
|
|
506
|
+
// servidor: dos rutas de recurso (POST JSON) y el efectivo del formulario
|
|
507
|
+
import { createEmbeddedPayHandlers, paymentCspHeaders, paymentCspMode, paymentNonce } from '@asteby/storefront-react/payments/server'
|
|
508
|
+
export const pay = createEmbeddedPayHandlers({
|
|
509
|
+
client, site, returnPath: '/pagar/gracias', readCartId, readSession, serializeSession,
|
|
510
|
+
validateFields, prepareCart, createDraft, settle, hide: ['manual'],
|
|
511
|
+
})
|
|
512
|
+
// action de /pagar/cobrar -> pay.pay(request); action de /pagar/estado -> pay.status(request)
|
|
513
|
+
// efectivo (en la action de /pagar): const out = await pay.cash({ sf, request, cartId, fields, choice, before })
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
- La tienda aporta lo suyo por callbacks: cookies (`readSession`/`serializeSession`), qué campos exige (`validateFields`), cómo guarda contacto, dirección y entrega (`prepareCart`), el comprobante (`createDraft`) y cuándo queda cerrado el pedido (`settle`: añade las cookies y devuelve `true` si fue aprobado, en revisión o ficha).
|
|
517
|
+
- El servidor decide qué se paga en la página: un `payment_choice` falsificado u oculto se rechaza (422), un punto de efectivo se arma de la lista del proveedor, el monto nunca viaja en `provider_data`, el reintento reutiliza el checkout y la llave de pago, hay límite por IP y validación de origen.
|
|
518
|
+
- CSP solo para `/pagar`: `paymentCspHeaders([client], { mode: paymentCspMode(env.PAY_CSP, isProd), nonce: paymentNonce(), scriptHashes, imgHttps })` y `paymentNonceFromHeaders` para `ServerRouter`.
|
|
519
|
+
- Accesibilidad: un `fieldset` con leyenda y radios nativos, avisos en `role="status"`/`alert`, botón de 44 px que explica su estado ocupado; pruebas con axe.
|
|
520
|
+
|
|
484
521
|
## Promociones y venta cruzada (`@asteby/storefront-react/promotions`)
|
|
485
522
|
|
|
486
523
|
Mecanismos sin contenido: los textos y montos llegan como datos de OPS (`Cart.promotions`, `GET /storefront/promotions`, `carts.recommendations`).
|
|
@@ -566,7 +603,7 @@ La cookie, el formulario y el redirect son de la tienda; `safeRedirect` valida e
|
|
|
566
603
|
| `defineSection`, `SectionRegistry`, `createSectionRegistry`, `Sections`, `resolveSectionProps`, `sectionJsonSchema` | Content sections |
|
|
567
604
|
| `themeToCss`, `themeToCssVariables`, `ThemeStyle`, `themeFontsHref`, `tokenName` | SSR theme |
|
|
568
605
|
| `ProductImage`, `ProductMockup`, `MockupProvider`, `createMockupRegistry`, `registerMockup`, `BUILTIN_MOCKUPS`, `MockupFrame`, `MockupText`, `TireMockup`, `PartMockup`, `SupplementMockup`, `GenericMockup` | Mockups de respaldo (subpath `/mockups`) |
|
|
569
|
-
| `PaymentBrick`, `useEmbeddedPayment`, `VoucherCard`, `PaymentChallenge`, `createFetchTransport`, `mercadoPagoBricksAdapter`, `loadPaymentSdk`, `paymentCsp`, `readPaymentTheme` | Pago integrado (subpath `/payments`) |
|
|
606
|
+
| `PaymentMethodSection`, `buildPayChoices`, `createEmbeddedPayHandlers` (`/payments/server`), `PaymentBrick`, `useEmbeddedPayment`, `VoucherCard`, `PaymentChallenge`, `createFetchTransport`, `mercadoPagoBricksAdapter`, `loadPaymentSdk`, `paymentCsp`, `readPaymentTheme` | Pago integrado (subpath `/payments`) |
|
|
570
607
|
| `PromoPopup`, `CartProgressBars`, `PromoProgressBar`, `CrossSellModal`, `OrderNote`, `isExcludedPath`, `frequencyAllows`, `isCrawler` | Promociones y venta cruzada (subpath `/promotions`) |
|
|
571
608
|
|
|
572
609
|
## `@asteby/storefront-react/ui`
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { PaymentClient, PaymentOptionKey, PaymentPoint, PaymentMethod, CheckoutResult, PaymentAttemptResult } from '@asteby/storefront';
|
|
2
|
+
|
|
3
|
+
type PaymentCspOptions = {
|
|
4
|
+
/** Per-request nonce; also set it on the inserted `<script>` and on `<Scripts nonce>`. */
|
|
5
|
+
nonce?: string;
|
|
6
|
+
/** Extra origins by directive (e.g. your image CDN). */
|
|
7
|
+
extra?: {
|
|
8
|
+
script?: string[];
|
|
9
|
+
frame?: string[];
|
|
10
|
+
connect?: string[];
|
|
11
|
+
img?: string[];
|
|
12
|
+
form?: string[];
|
|
13
|
+
};
|
|
14
|
+
/** `report-uri` / `report-to` target for the report-only rollout. */
|
|
15
|
+
reportUri?: string;
|
|
16
|
+
/** Inline scripts allowed by hash (`'sha256-…'`), e.g. a theme bootstrap that runs before paint. */
|
|
17
|
+
scriptHashes?: string[];
|
|
18
|
+
/** Allow any https image source (product photos from a CDN). */
|
|
19
|
+
imgHttps?: boolean;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Builds the Content-Security-Policy for the payment routes from what each provider declares
|
|
23
|
+
* (`PaymentClient.csp`), so the shop never hard-codes provider hosts. Meant for `/pagar` only,
|
|
24
|
+
* never for the whole site. Roll out as `Content-Security-Policy-Report-Only` first.
|
|
25
|
+
*
|
|
26
|
+
* No SRI on purpose: provider SDK URLs are unversioned and a hash would break payments on any update.
|
|
27
|
+
*/
|
|
28
|
+
declare function paymentCsp(clients: readonly (PaymentClient | null | undefined)[], options?: PaymentCspOptions): string;
|
|
29
|
+
/** `Content-Security-Policy` or, during the rollout week, `Content-Security-Policy-Report-Only`. */
|
|
30
|
+
declare function paymentCspHeaderName(reportOnly: boolean): string;
|
|
31
|
+
type PaymentCspMode = 'off' | 'report-only' | 'enforce';
|
|
32
|
+
/** `PAYMENT_CSP`-style value: `off`, `report-only` or `enforce`. Unset: report-only in production, off elsewhere (dev servers inject scripts). */
|
|
33
|
+
declare function paymentCspMode(value: string | null | undefined, production: boolean): PaymentCspMode;
|
|
34
|
+
/** Fresh per-request nonce (128 bits, base64). Works in Node, edge and browsers. */
|
|
35
|
+
declare function paymentNonce(): string;
|
|
36
|
+
/** Header ready for the response (`{}` when `off`): policy from the providers' `client.csp`, never the whole site. */
|
|
37
|
+
declare function paymentCspHeaders(clients: readonly (PaymentClient | null | undefined)[], options: PaymentCspOptions & {
|
|
38
|
+
mode: PaymentCspMode;
|
|
39
|
+
}): Record<string, string>;
|
|
40
|
+
/** The nonce the route left in the CSP header, to hand to `ServerRouter` / `<Scripts>`. */
|
|
41
|
+
declare function paymentNonceFromHeaders(headers: Headers): string | undefined;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* "Payment method" rows for a hybrid checkout: pure functions, no DOM and no server, so the loader, the action and the
|
|
45
|
+
* browser share them.
|
|
46
|
+
*
|
|
47
|
+
* Rules (embedded-payment contract 1.2):
|
|
48
|
+
* - Rows come ONLY from what the server publishes (`payment-methods[].options` and `.client`). No provider names and no
|
|
49
|
+
* amounts live here.
|
|
50
|
+
* - Card and cash are paid in the page; `wallet` and `credits` redirect to the provider.
|
|
51
|
+
* - Without `client`, or with the card flagged `redirect: true`, the server does not offer `embedded`: the shop falls
|
|
52
|
+
* back to the usual redirect flow.
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
/** One row of "Payment method". `value` is what travels in the form (`payment_choice`). */
|
|
56
|
+
type PayChoice = {
|
|
57
|
+
value: string;
|
|
58
|
+
/** Server method key (`payment_method` of the checkout). */
|
|
59
|
+
methodKey: string;
|
|
60
|
+
/** Provider option; `null` for a method without options (the plain flow). */
|
|
61
|
+
option: PaymentOptionKey | null;
|
|
62
|
+
/** `card` (form in the page), `cash` (voucher), `redirect` (leaves to the gateway) or `plain`. */
|
|
63
|
+
mode: 'card' | 'cash' | 'redirect' | 'plain';
|
|
64
|
+
/** The provider's own text (`option.label`); the shop supplies its own when missing. */
|
|
65
|
+
label: string | null;
|
|
66
|
+
points: PaymentPoint[];
|
|
67
|
+
maxInstallments: number | null;
|
|
68
|
+
};
|
|
69
|
+
type PayChoices = {
|
|
70
|
+
choices: PayChoice[];
|
|
71
|
+
/** Public data to mount the card form; `null` when the server does not offer `embedded`. */
|
|
72
|
+
cardClient: PaymentClient | null;
|
|
73
|
+
defaultValue: string | null;
|
|
74
|
+
};
|
|
75
|
+
type BuildPayChoicesOptions = {
|
|
76
|
+
/** Method keys this shop never offers even when the server publishes them (e.g. a manual transfer). */
|
|
77
|
+
hide?: Iterable<string>;
|
|
78
|
+
};
|
|
79
|
+
declare function choiceValue(methodKey: string, option: PaymentOptionKey | null): string;
|
|
80
|
+
/** `"mercadopago|card"` becomes method and option. A value without `|` is a method without options. */
|
|
81
|
+
declare function parseChoice(value: string | null | undefined): {
|
|
82
|
+
methodKey: string;
|
|
83
|
+
option: PaymentOptionKey | null;
|
|
84
|
+
} | null;
|
|
85
|
+
/**
|
|
86
|
+
* Rows from `GET payment-methods`. A method with `options` unfolds into one row per option (server order); one without
|
|
87
|
+
* is a single plain row. `cardClient` only exists when the card is paid in the page.
|
|
88
|
+
*/
|
|
89
|
+
declare function buildPayChoices(methods: readonly PaymentMethod[], options?: BuildPayChoicesOptions): PayChoices;
|
|
90
|
+
/** Chosen cash point becomes the cash `provider_data`, built on the server from the provider's list. */
|
|
91
|
+
declare function cashProviderData(points: readonly PaymentPoint[], pointId: string | null | undefined): Record<string, string> | null;
|
|
92
|
+
/**
|
|
93
|
+
* A checkout (`GET checkouts/:id`) as an attempt result, for polling a 3DS challenge or a payment under review.
|
|
94
|
+
* It only translates server states: it never decides a payment.
|
|
95
|
+
*/
|
|
96
|
+
declare function checkoutToAttempt(result: CheckoutResult): PaymentAttemptResult;
|
|
97
|
+
/** Body `{ error: { code, message, details } }` that `createFetchTransport` understands. */
|
|
98
|
+
type PayErrorBody = {
|
|
99
|
+
error: {
|
|
100
|
+
code: string;
|
|
101
|
+
message: string;
|
|
102
|
+
details?: unknown;
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
type ErrorLike = {
|
|
106
|
+
status?: number;
|
|
107
|
+
code?: string;
|
|
108
|
+
field?: string;
|
|
109
|
+
message?: string;
|
|
110
|
+
details?: unknown;
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* Server error becomes a JSON response for the browser. Keeps the codes the SDK knows how to read (`price_mismatch`,
|
|
114
|
+
* `payment_in_progress`, `expired`, `too_many_attempts`); the mock sends "in progress" in `field`, so that is checked too.
|
|
115
|
+
*/
|
|
116
|
+
declare function payErrorResponse(err: ErrorLike): {
|
|
117
|
+
status: number;
|
|
118
|
+
body: PayErrorBody;
|
|
119
|
+
};
|
|
120
|
+
type VoucherRowLabels = {
|
|
121
|
+
point: string;
|
|
122
|
+
reference: string;
|
|
123
|
+
clabe: string;
|
|
124
|
+
barcode: string;
|
|
125
|
+
expires: string;
|
|
126
|
+
};
|
|
127
|
+
/** Cookie receipt rows for a payment under review or with a voucher (shown on the thank-you page). */
|
|
128
|
+
declare function voucherRows(voucher: NonNullable<NonNullable<PaymentAttemptResult['payment']['action']>['voucher']> | null | undefined, labels: VoucherRowLabels, locale: string): [label: string, value: string][];
|
|
129
|
+
|
|
130
|
+
export { type BuildPayChoicesOptions as B, type PayChoice as P, type VoucherRowLabels as V, type PayChoices as a, type PayErrorBody as b, type PaymentCspMode as c, type PaymentCspOptions as d, buildPayChoices as e, cashProviderData as f, checkoutToAttempt as g, choiceValue as h, payErrorResponse as i, paymentCsp as j, paymentCspHeaderName as k, paymentCspHeaders as l, paymentCspMode as m, paymentNonce as n, paymentNonceFromHeaders as o, parseChoice as p, voucherRows as v };
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { PaymentClient, PaymentOptionKey, PaymentPoint, PaymentMethod, CheckoutResult, PaymentAttemptResult } from '@asteby/storefront';
|
|
2
|
+
|
|
3
|
+
type PaymentCspOptions = {
|
|
4
|
+
/** Per-request nonce; also set it on the inserted `<script>` and on `<Scripts nonce>`. */
|
|
5
|
+
nonce?: string;
|
|
6
|
+
/** Extra origins by directive (e.g. your image CDN). */
|
|
7
|
+
extra?: {
|
|
8
|
+
script?: string[];
|
|
9
|
+
frame?: string[];
|
|
10
|
+
connect?: string[];
|
|
11
|
+
img?: string[];
|
|
12
|
+
form?: string[];
|
|
13
|
+
};
|
|
14
|
+
/** `report-uri` / `report-to` target for the report-only rollout. */
|
|
15
|
+
reportUri?: string;
|
|
16
|
+
/** Inline scripts allowed by hash (`'sha256-…'`), e.g. a theme bootstrap that runs before paint. */
|
|
17
|
+
scriptHashes?: string[];
|
|
18
|
+
/** Allow any https image source (product photos from a CDN). */
|
|
19
|
+
imgHttps?: boolean;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Builds the Content-Security-Policy for the payment routes from what each provider declares
|
|
23
|
+
* (`PaymentClient.csp`), so the shop never hard-codes provider hosts. Meant for `/pagar` only,
|
|
24
|
+
* never for the whole site. Roll out as `Content-Security-Policy-Report-Only` first.
|
|
25
|
+
*
|
|
26
|
+
* No SRI on purpose: provider SDK URLs are unversioned and a hash would break payments on any update.
|
|
27
|
+
*/
|
|
28
|
+
declare function paymentCsp(clients: readonly (PaymentClient | null | undefined)[], options?: PaymentCspOptions): string;
|
|
29
|
+
/** `Content-Security-Policy` or, during the rollout week, `Content-Security-Policy-Report-Only`. */
|
|
30
|
+
declare function paymentCspHeaderName(reportOnly: boolean): string;
|
|
31
|
+
type PaymentCspMode = 'off' | 'report-only' | 'enforce';
|
|
32
|
+
/** `PAYMENT_CSP`-style value: `off`, `report-only` or `enforce`. Unset: report-only in production, off elsewhere (dev servers inject scripts). */
|
|
33
|
+
declare function paymentCspMode(value: string | null | undefined, production: boolean): PaymentCspMode;
|
|
34
|
+
/** Fresh per-request nonce (128 bits, base64). Works in Node, edge and browsers. */
|
|
35
|
+
declare function paymentNonce(): string;
|
|
36
|
+
/** Header ready for the response (`{}` when `off`): policy from the providers' `client.csp`, never the whole site. */
|
|
37
|
+
declare function paymentCspHeaders(clients: readonly (PaymentClient | null | undefined)[], options: PaymentCspOptions & {
|
|
38
|
+
mode: PaymentCspMode;
|
|
39
|
+
}): Record<string, string>;
|
|
40
|
+
/** The nonce the route left in the CSP header, to hand to `ServerRouter` / `<Scripts>`. */
|
|
41
|
+
declare function paymentNonceFromHeaders(headers: Headers): string | undefined;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* "Payment method" rows for a hybrid checkout: pure functions, no DOM and no server, so the loader, the action and the
|
|
45
|
+
* browser share them.
|
|
46
|
+
*
|
|
47
|
+
* Rules (embedded-payment contract 1.2):
|
|
48
|
+
* - Rows come ONLY from what the server publishes (`payment-methods[].options` and `.client`). No provider names and no
|
|
49
|
+
* amounts live here.
|
|
50
|
+
* - Card and cash are paid in the page; `wallet` and `credits` redirect to the provider.
|
|
51
|
+
* - Without `client`, or with the card flagged `redirect: true`, the server does not offer `embedded`: the shop falls
|
|
52
|
+
* back to the usual redirect flow.
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
/** One row of "Payment method". `value` is what travels in the form (`payment_choice`). */
|
|
56
|
+
type PayChoice = {
|
|
57
|
+
value: string;
|
|
58
|
+
/** Server method key (`payment_method` of the checkout). */
|
|
59
|
+
methodKey: string;
|
|
60
|
+
/** Provider option; `null` for a method without options (the plain flow). */
|
|
61
|
+
option: PaymentOptionKey | null;
|
|
62
|
+
/** `card` (form in the page), `cash` (voucher), `redirect` (leaves to the gateway) or `plain`. */
|
|
63
|
+
mode: 'card' | 'cash' | 'redirect' | 'plain';
|
|
64
|
+
/** The provider's own text (`option.label`); the shop supplies its own when missing. */
|
|
65
|
+
label: string | null;
|
|
66
|
+
points: PaymentPoint[];
|
|
67
|
+
maxInstallments: number | null;
|
|
68
|
+
};
|
|
69
|
+
type PayChoices = {
|
|
70
|
+
choices: PayChoice[];
|
|
71
|
+
/** Public data to mount the card form; `null` when the server does not offer `embedded`. */
|
|
72
|
+
cardClient: PaymentClient | null;
|
|
73
|
+
defaultValue: string | null;
|
|
74
|
+
};
|
|
75
|
+
type BuildPayChoicesOptions = {
|
|
76
|
+
/** Method keys this shop never offers even when the server publishes them (e.g. a manual transfer). */
|
|
77
|
+
hide?: Iterable<string>;
|
|
78
|
+
};
|
|
79
|
+
declare function choiceValue(methodKey: string, option: PaymentOptionKey | null): string;
|
|
80
|
+
/** `"mercadopago|card"` becomes method and option. A value without `|` is a method without options. */
|
|
81
|
+
declare function parseChoice(value: string | null | undefined): {
|
|
82
|
+
methodKey: string;
|
|
83
|
+
option: PaymentOptionKey | null;
|
|
84
|
+
} | null;
|
|
85
|
+
/**
|
|
86
|
+
* Rows from `GET payment-methods`. A method with `options` unfolds into one row per option (server order); one without
|
|
87
|
+
* is a single plain row. `cardClient` only exists when the card is paid in the page.
|
|
88
|
+
*/
|
|
89
|
+
declare function buildPayChoices(methods: readonly PaymentMethod[], options?: BuildPayChoicesOptions): PayChoices;
|
|
90
|
+
/** Chosen cash point becomes the cash `provider_data`, built on the server from the provider's list. */
|
|
91
|
+
declare function cashProviderData(points: readonly PaymentPoint[], pointId: string | null | undefined): Record<string, string> | null;
|
|
92
|
+
/**
|
|
93
|
+
* A checkout (`GET checkouts/:id`) as an attempt result, for polling a 3DS challenge or a payment under review.
|
|
94
|
+
* It only translates server states: it never decides a payment.
|
|
95
|
+
*/
|
|
96
|
+
declare function checkoutToAttempt(result: CheckoutResult): PaymentAttemptResult;
|
|
97
|
+
/** Body `{ error: { code, message, details } }` that `createFetchTransport` understands. */
|
|
98
|
+
type PayErrorBody = {
|
|
99
|
+
error: {
|
|
100
|
+
code: string;
|
|
101
|
+
message: string;
|
|
102
|
+
details?: unknown;
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
type ErrorLike = {
|
|
106
|
+
status?: number;
|
|
107
|
+
code?: string;
|
|
108
|
+
field?: string;
|
|
109
|
+
message?: string;
|
|
110
|
+
details?: unknown;
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* Server error becomes a JSON response for the browser. Keeps the codes the SDK knows how to read (`price_mismatch`,
|
|
114
|
+
* `payment_in_progress`, `expired`, `too_many_attempts`); the mock sends "in progress" in `field`, so that is checked too.
|
|
115
|
+
*/
|
|
116
|
+
declare function payErrorResponse(err: ErrorLike): {
|
|
117
|
+
status: number;
|
|
118
|
+
body: PayErrorBody;
|
|
119
|
+
};
|
|
120
|
+
type VoucherRowLabels = {
|
|
121
|
+
point: string;
|
|
122
|
+
reference: string;
|
|
123
|
+
clabe: string;
|
|
124
|
+
barcode: string;
|
|
125
|
+
expires: string;
|
|
126
|
+
};
|
|
127
|
+
/** Cookie receipt rows for a payment under review or with a voucher (shown on the thank-you page). */
|
|
128
|
+
declare function voucherRows(voucher: NonNullable<NonNullable<PaymentAttemptResult['payment']['action']>['voucher']> | null | undefined, labels: VoucherRowLabels, locale: string): [label: string, value: string][];
|
|
129
|
+
|
|
130
|
+
export { type BuildPayChoicesOptions as B, type PayChoice as P, type VoucherRowLabels as V, type PayChoices as a, type PayErrorBody as b, type PaymentCspMode as c, type PaymentCspOptions as d, buildPayChoices as e, cashProviderData as f, checkoutToAttempt as g, choiceValue as h, payErrorResponse as i, paymentCsp as j, paymentCspHeaderName as k, paymentCspHeaders as l, paymentCspMode as m, paymentNonce as n, paymentNonceFromHeaders as o, parseChoice as p, voucherRows as v };
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// src/payments/csp.ts
|
|
2
|
+
var ORIGIN = /^https:\/\/(\*\.)?[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*(:\d{1,5})?$/i;
|
|
3
|
+
function origins(...lists) {
|
|
4
|
+
const out = /* @__PURE__ */ new Set();
|
|
5
|
+
for (const list of lists) for (const o of list ?? []) if (ORIGIN.test(o)) out.add(o);
|
|
6
|
+
return [...out];
|
|
7
|
+
}
|
|
8
|
+
function paymentCsp(clients, options = {}) {
|
|
9
|
+
const list = clients.filter((c) => !!c);
|
|
10
|
+
const pick = (k) => list.map((c) => c.csp?.[k]);
|
|
11
|
+
const nonce = options.nonce && /^[A-Za-z0-9+/=_-]+$/.test(options.nonce) ? options.nonce : void 0;
|
|
12
|
+
const sdkOrigins = list.flatMap((c) => {
|
|
13
|
+
try {
|
|
14
|
+
const u = new URL(c.sdk_url);
|
|
15
|
+
return u.protocol === "https:" ? [u.origin] : [];
|
|
16
|
+
} catch {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
const dir = (name, values) => `${name} ${values.join(" ")}`;
|
|
21
|
+
const parts = [
|
|
22
|
+
"default-src 'self'",
|
|
23
|
+
dir("script-src", ["'self'", ...(options.scriptHashes ?? []).filter((h) => /^'sha(256|384|512)-[A-Za-z0-9+/=]+'$/.test(h)), ...nonce ? [`'nonce-${nonce}'`] : [], ...origins(sdkOrigins, ...pick("script"), options.extra?.script)]),
|
|
24
|
+
dir("frame-src", origins(...pick("frame"), options.extra?.frame).length ? origins(...pick("frame"), options.extra?.frame) : ["'none'"]),
|
|
25
|
+
dir("connect-src", ["'self'", ...origins(...pick("connect"), options.extra?.connect)]),
|
|
26
|
+
dir("img-src", ["'self'", "data:", ...options.imgHttps ? ["https:"] : [], ...origins(...pick("img"), options.extra?.img)]),
|
|
27
|
+
"style-src 'self' 'unsafe-inline'",
|
|
28
|
+
dir("form-action", ["'self'", ...origins(...pick("form"), options.extra?.form)]),
|
|
29
|
+
"frame-ancestors 'none'",
|
|
30
|
+
"base-uri 'self'",
|
|
31
|
+
"object-src 'none'"
|
|
32
|
+
];
|
|
33
|
+
if (options.reportUri && /^(https:\/\/|\/)[^\s;,]+$/.test(options.reportUri)) parts.push(`report-uri ${options.reportUri}`);
|
|
34
|
+
return parts.join("; ");
|
|
35
|
+
}
|
|
36
|
+
function paymentCspHeaderName(reportOnly) {
|
|
37
|
+
return reportOnly ? "Content-Security-Policy-Report-Only" : "Content-Security-Policy";
|
|
38
|
+
}
|
|
39
|
+
function paymentCspMode(value, production) {
|
|
40
|
+
const raw = (value ?? "").trim().toLowerCase();
|
|
41
|
+
if (raw === "off" || raw === "report-only" || raw === "enforce") return raw;
|
|
42
|
+
return production ? "report-only" : "off";
|
|
43
|
+
}
|
|
44
|
+
function paymentNonce() {
|
|
45
|
+
const bytes = new Uint8Array(16);
|
|
46
|
+
crypto.getRandomValues(bytes);
|
|
47
|
+
let bin = "";
|
|
48
|
+
for (const b of bytes) bin += String.fromCharCode(b);
|
|
49
|
+
return btoa(bin);
|
|
50
|
+
}
|
|
51
|
+
function paymentCspHeaders(clients, options) {
|
|
52
|
+
if (options.mode === "off") return {};
|
|
53
|
+
return { [paymentCspHeaderName(options.mode === "report-only")]: paymentCsp(clients, options) };
|
|
54
|
+
}
|
|
55
|
+
function paymentNonceFromHeaders(headers) {
|
|
56
|
+
const csp = headers.get("Content-Security-Policy") ?? headers.get("Content-Security-Policy-Report-Only") ?? "";
|
|
57
|
+
return /'nonce-([A-Za-z0-9+/=_-]+)'/.exec(csp)?.[1];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// src/payments/choices.ts
|
|
61
|
+
var SEP = "|";
|
|
62
|
+
function choiceValue(methodKey, option) {
|
|
63
|
+
return option ? `${methodKey}${SEP}${option}` : methodKey;
|
|
64
|
+
}
|
|
65
|
+
function parseChoice(value) {
|
|
66
|
+
const raw = (value ?? "").trim();
|
|
67
|
+
if (!raw) return null;
|
|
68
|
+
const i = raw.lastIndexOf(SEP);
|
|
69
|
+
if (i === -1) return { methodKey: raw, option: null };
|
|
70
|
+
const option = raw.slice(i + 1);
|
|
71
|
+
if (option !== "card" && option !== "cash" && option !== "wallet" && option !== "credits") return null;
|
|
72
|
+
return { methodKey: raw.slice(0, i), option };
|
|
73
|
+
}
|
|
74
|
+
function modeOf(option) {
|
|
75
|
+
if (option.redirect || option.key === "wallet" || option.key === "credits") return "redirect";
|
|
76
|
+
return option.key === "cash" ? "cash" : "card";
|
|
77
|
+
}
|
|
78
|
+
function buildPayChoices(methods, options = {}) {
|
|
79
|
+
const hidden = new Set(options.hide ?? []);
|
|
80
|
+
const choices = [];
|
|
81
|
+
let cardClient = null;
|
|
82
|
+
let defaultValue = null;
|
|
83
|
+
for (const method of methods) {
|
|
84
|
+
if (hidden.has(method.key)) continue;
|
|
85
|
+
const opts = method.options ?? [];
|
|
86
|
+
if (opts.length === 0) {
|
|
87
|
+
const value = choiceValue(method.key, null);
|
|
88
|
+
choices.push({ value, methodKey: method.key, option: null, mode: "plain", label: null, points: [], maxInstallments: null });
|
|
89
|
+
if (method.default && !defaultValue) defaultValue = value;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
opts.forEach((option, i) => {
|
|
93
|
+
const mode = modeOf(option);
|
|
94
|
+
const value = choiceValue(method.key, option.key);
|
|
95
|
+
choices.push({
|
|
96
|
+
value,
|
|
97
|
+
methodKey: method.key,
|
|
98
|
+
option: option.key,
|
|
99
|
+
mode,
|
|
100
|
+
label: option.label?.trim() || null,
|
|
101
|
+
points: option.key === "cash" ? option.points ?? [] : [],
|
|
102
|
+
maxInstallments: option.max_installments ?? null
|
|
103
|
+
});
|
|
104
|
+
if (mode === "card" && method.client && !cardClient) cardClient = method.client;
|
|
105
|
+
if (method.default && i === 0 && !defaultValue) defaultValue = value;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
const usable = choices.map((c) => c.mode === "card" && !cardClient ? { ...c, mode: "redirect" } : c);
|
|
109
|
+
return { choices: usable, cardClient, defaultValue: defaultValue ?? usable[0]?.value ?? null };
|
|
110
|
+
}
|
|
111
|
+
function cashProviderData(points, pointId) {
|
|
112
|
+
const point = points.find((p) => p.id === pointId) ?? (pointId ? null : points[0]);
|
|
113
|
+
if (!point) return null;
|
|
114
|
+
return {
|
|
115
|
+
payment_method_id: point.payment_method_id,
|
|
116
|
+
...point.payment_point ? { payment_point: point.payment_point } : {}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function checkoutToAttempt(result) {
|
|
120
|
+
const { checkout, payment, order } = result;
|
|
121
|
+
const action = payment.action ?? null;
|
|
122
|
+
const base = { checkout, ...order ? { order } : {} };
|
|
123
|
+
const status = (checkout.status ?? "").toLowerCase();
|
|
124
|
+
if (status === "paid" || status === "converted") return { ...base, payment: { provider: payment.provider, status: "paid", outcome: "approved" } };
|
|
125
|
+
if (status === "cancelled" || status === "canceled") return { ...base, payment: { provider: payment.provider, status: "cancelled" } };
|
|
126
|
+
if (status === "expired") return { ...base, payment: { provider: payment.provider, status: "expired" } };
|
|
127
|
+
if (status === "failed") {
|
|
128
|
+
const decline = payment.decline_code ?? "declined";
|
|
129
|
+
return { ...base, payment: { provider: payment.provider, status: "failed", outcome: "rejected", decline_code: decline, retryable: true } };
|
|
130
|
+
}
|
|
131
|
+
if (action?.type === "challenge") return { ...base, payment: { provider: payment.provider, status: "requires_action", outcome: "challenge", action } };
|
|
132
|
+
return { ...base, payment: { provider: payment.provider, status: "pending", outcome: "pending", action: action ?? { type: "none" } } };
|
|
133
|
+
}
|
|
134
|
+
function payErrorResponse(err) {
|
|
135
|
+
const status = typeof err.status === "number" && err.status >= 400 ? err.status : 503;
|
|
136
|
+
let code = err.code ?? "server_error";
|
|
137
|
+
if (status === 409 && err.field === "payment_in_progress") code = "payment_in_progress";
|
|
138
|
+
if (status === 429) code = "too_many_attempts";
|
|
139
|
+
if (status === 422 && err.field === "expected_total") code = "price_mismatch";
|
|
140
|
+
return { status, body: { error: { code, message: err.message ?? "", ...err.details !== void 0 ? { details: err.details } : {} } } };
|
|
141
|
+
}
|
|
142
|
+
function voucherRows(voucher, labels, locale) {
|
|
143
|
+
if (!voucher) return [];
|
|
144
|
+
const rows = [];
|
|
145
|
+
if (voucher.payment_point) rows.push([labels.point, String(voucher.payment_point).slice(0, 60)]);
|
|
146
|
+
if (voucher.reference) rows.push([labels.reference, String(voucher.reference).slice(0, 80)]);
|
|
147
|
+
if (voucher.clabe) rows.push([labels.clabe, String(voucher.clabe).slice(0, 40)]);
|
|
148
|
+
if (voucher.barcode) rows.push([labels.barcode, String(voucher.barcode).slice(0, 80)]);
|
|
149
|
+
if (voucher.expires_at) {
|
|
150
|
+
const d = new Date(voucher.expires_at);
|
|
151
|
+
if (!Number.isNaN(d.getTime())) {
|
|
152
|
+
rows.push([labels.expires, new Intl.DateTimeFormat(locale, { dateStyle: "long", timeStyle: "short", timeZone: "UTC" }).format(d) + " UTC"]);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return rows;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export { buildPayChoices, cashProviderData, checkoutToAttempt, choiceValue, parseChoice, payErrorResponse, paymentCsp, paymentCspHeaderName, paymentCspHeaders, paymentCspMode, paymentNonce, paymentNonceFromHeaders, voucherRows };
|
|
159
|
+
//# sourceMappingURL=chunk-ISEOCKIR.js.map
|
|
160
|
+
//# sourceMappingURL=chunk-ISEOCKIR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/payments/csp.ts","../src/payments/choices.ts"],"names":[],"mappings":"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","file":"chunk-ISEOCKIR.js","sourcesContent":["import type { PaymentClient } from '@asteby/storefront'\n\nexport type PaymentCspOptions = {\n /** Per-request nonce; also set it on the inserted `<script>` and on `<Scripts nonce>`. */\n nonce?: string\n /** Extra origins by directive (e.g. your image CDN). */\n extra?: { script?: string[]; frame?: string[]; connect?: string[]; img?: string[]; form?: string[] }\n /** `report-uri` / `report-to` target for the report-only rollout. */\n reportUri?: string\n /** Inline scripts allowed by hash (`'sha256-…'`), e.g. a theme bootstrap that runs before paint. */\n scriptHashes?: string[]\n /** Allow any https image source (product photos from a CDN). */\n imgHttps?: boolean\n}\n\n/** One origin source: `https://host`, `https://*.host`, optional port. Anything else (`;`, spaces, `'unsafe-eval'`) is dropped. */\nconst ORIGIN = /^https:\\/\\/(\\*\\.)?[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*(:\\d{1,5})?$/i\n\nfunction origins(...lists: (readonly string[] | undefined)[]): string[] {\n const out = new Set<string>()\n for (const list of lists) for (const o of list ?? []) if (ORIGIN.test(o)) out.add(o)\n return [...out]\n}\n\n/**\n * Builds the Content-Security-Policy for the payment routes from what each provider declares\n * (`PaymentClient.csp`), so the shop never hard-codes provider hosts. Meant for `/pagar` only,\n * never for the whole site. Roll out as `Content-Security-Policy-Report-Only` first.\n *\n * No SRI on purpose: provider SDK URLs are unversioned and a hash would break payments on any update.\n */\nexport function paymentCsp(clients: readonly (PaymentClient | null | undefined)[], options: PaymentCspOptions = {}): string {\n const list = clients.filter((c): c is PaymentClient => !!c)\n const pick = (k: keyof NonNullable<PaymentClient['csp']>) => list.map((c) => c.csp?.[k])\n const nonce = options.nonce && /^[A-Za-z0-9+/=_-]+$/.test(options.nonce) ? options.nonce : undefined\n const sdkOrigins = list.flatMap((c) => {\n try {\n const u = new URL(c.sdk_url)\n return u.protocol === 'https:' ? [u.origin] : []\n } catch {\n return []\n }\n })\n const dir = (name: string, values: string[]) => `${name} ${values.join(' ')}`\n const parts = [\n \"default-src 'self'\",\n dir('script-src', [\"'self'\", ...(options.scriptHashes ?? []).filter((h) => /^'sha(256|384|512)-[A-Za-z0-9+/=]+'$/.test(h)), ...(nonce ? [`'nonce-${nonce}'`] : []), ...origins(sdkOrigins, ...pick('script'), options.extra?.script)]),\n dir('frame-src', origins(...pick('frame'), options.extra?.frame).length ? origins(...pick('frame'), options.extra?.frame) : [\"'none'\"]),\n dir('connect-src', [\"'self'\", ...origins(...pick('connect'), options.extra?.connect)]),\n dir('img-src', [\"'self'\", 'data:', ...(options.imgHttps ? ['https:'] : []), ...origins(...pick('img'), options.extra?.img)]),\n \"style-src 'self' 'unsafe-inline'\",\n dir('form-action', [\"'self'\", ...origins(...pick('form'), options.extra?.form)]),\n \"frame-ancestors 'none'\",\n \"base-uri 'self'\",\n \"object-src 'none'\",\n ]\n if (options.reportUri && /^(https:\\/\\/|\\/)[^\\s;,]+$/.test(options.reportUri)) parts.push(`report-uri ${options.reportUri}`)\n return parts.join('; ')\n}\n\n/** `Content-Security-Policy` or, during the rollout week, `Content-Security-Policy-Report-Only`. */\nexport function paymentCspHeaderName(reportOnly: boolean): string {\n return reportOnly ? 'Content-Security-Policy-Report-Only' : 'Content-Security-Policy'\n}\n\nexport type PaymentCspMode = 'off' | 'report-only' | 'enforce'\n\n/** `PAYMENT_CSP`-style value: `off`, `report-only` or `enforce`. Unset: report-only in production, off elsewhere (dev servers inject scripts). */\nexport function paymentCspMode(value: string | null | undefined, production: boolean): PaymentCspMode {\n const raw = (value ?? '').trim().toLowerCase()\n if (raw === 'off' || raw === 'report-only' || raw === 'enforce') return raw\n return production ? 'report-only' : 'off'\n}\n\n/** Fresh per-request nonce (128 bits, base64). Works in Node, edge and browsers. */\nexport function paymentNonce(): string {\n const bytes = new Uint8Array(16)\n crypto.getRandomValues(bytes)\n let bin = ''\n for (const b of bytes) bin += String.fromCharCode(b)\n return btoa(bin)\n}\n\n/** Header ready for the response (`{}` when `off`): policy from the providers' `client.csp`, never the whole site. */\nexport function paymentCspHeaders(\n clients: readonly (PaymentClient | null | undefined)[],\n options: PaymentCspOptions & { mode: PaymentCspMode },\n): Record<string, string> {\n if (options.mode === 'off') return {}\n return { [paymentCspHeaderName(options.mode === 'report-only')]: paymentCsp(clients, options) }\n}\n\n/** The nonce the route left in the CSP header, to hand to `ServerRouter` / `<Scripts>`. */\nexport function paymentNonceFromHeaders(headers: Headers): string | undefined {\n const csp = headers.get('Content-Security-Policy') ?? headers.get('Content-Security-Policy-Report-Only') ?? ''\n return /'nonce-([A-Za-z0-9+/=_-]+)'/.exec(csp)?.[1]\n}\n","/**\n * \"Payment method\" rows for a hybrid checkout: pure functions, no DOM and no server, so the loader, the action and the\n * browser share them.\n *\n * Rules (embedded-payment contract 1.2):\n * - Rows come ONLY from what the server publishes (`payment-methods[].options` and `.client`). No provider names and no\n * amounts live here.\n * - Card and cash are paid in the page; `wallet` and `credits` redirect to the provider.\n * - Without `client`, or with the card flagged `redirect: true`, the server does not offer `embedded`: the shop falls\n * back to the usual redirect flow.\n */\nimport type {\n CheckoutResult,\n PaymentAttemptResult,\n PaymentClient,\n PaymentMethod,\n PaymentOption,\n PaymentOptionKey,\n PaymentPoint,\n} from '@asteby/storefront'\n\n/** One row of \"Payment method\". `value` is what travels in the form (`payment_choice`). */\nexport type PayChoice = {\n value: string\n /** Server method key (`payment_method` of the checkout). */\n methodKey: string\n /** Provider option; `null` for a method without options (the plain flow). */\n option: PaymentOptionKey | null\n /** `card` (form in the page), `cash` (voucher), `redirect` (leaves to the gateway) or `plain`. */\n mode: 'card' | 'cash' | 'redirect' | 'plain'\n /** The provider's own text (`option.label`); the shop supplies its own when missing. */\n label: string | null\n points: PaymentPoint[]\n maxInstallments: number | null\n}\n\nexport type PayChoices = {\n choices: PayChoice[]\n /** Public data to mount the card form; `null` when the server does not offer `embedded`. */\n cardClient: PaymentClient | null\n defaultValue: string | null\n}\n\nexport type BuildPayChoicesOptions = {\n /** Method keys this shop never offers even when the server publishes them (e.g. a manual transfer). */\n hide?: Iterable<string>\n}\n\nconst SEP = '|'\n\nexport function choiceValue(methodKey: string, option: PaymentOptionKey | null): string {\n return option ? `${methodKey}${SEP}${option}` : methodKey\n}\n\n/** `\"mercadopago|card\"` becomes method and option. A value without `|` is a method without options. */\nexport function parseChoice(value: string | null | undefined): { methodKey: string; option: PaymentOptionKey | null } | null {\n const raw = (value ?? '').trim()\n if (!raw) return null\n const i = raw.lastIndexOf(SEP)\n if (i === -1) return { methodKey: raw, option: null }\n const option = raw.slice(i + 1)\n if (option !== 'card' && option !== 'cash' && option !== 'wallet' && option !== 'credits') return null\n return { methodKey: raw.slice(0, i), option }\n}\n\nfunction modeOf(option: PaymentOption): PayChoice['mode'] {\n if (option.redirect || option.key === 'wallet' || option.key === 'credits') return 'redirect'\n return option.key === 'cash' ? 'cash' : 'card'\n}\n\n/**\n * Rows from `GET payment-methods`. A method with `options` unfolds into one row per option (server order); one without\n * is a single plain row. `cardClient` only exists when the card is paid in the page.\n */\nexport function buildPayChoices(methods: readonly PaymentMethod[], options: BuildPayChoicesOptions = {}): PayChoices {\n const hidden = new Set(options.hide ?? [])\n const choices: PayChoice[] = []\n let cardClient: PaymentClient | null = null\n let defaultValue: string | null = null\n for (const method of methods) {\n if (hidden.has(method.key)) continue\n const opts = method.options ?? []\n if (opts.length === 0) {\n const value = choiceValue(method.key, null)\n choices.push({ value, methodKey: method.key, option: null, mode: 'plain', label: null, points: [], maxInstallments: null })\n if (method.default && !defaultValue) defaultValue = value\n continue\n }\n opts.forEach((option, i) => {\n const mode = modeOf(option)\n const value = choiceValue(method.key, option.key)\n choices.push({\n value,\n methodKey: method.key,\n option: option.key,\n mode,\n label: option.label?.trim() || null,\n points: option.key === 'cash' ? (option.points ?? []) : [],\n maxInstallments: option.max_installments ?? null,\n })\n if (mode === 'card' && method.client && !cardClient) cardClient = method.client\n if (method.default && i === 0 && !defaultValue) defaultValue = value\n })\n }\n // A card meant for the page but without `client` cannot be mounted: it is paid by redirect.\n const usable = choices.map((c) => (c.mode === 'card' && !cardClient ? { ...c, mode: 'redirect' as const } : c))\n return { choices: usable, cardClient, defaultValue: defaultValue ?? usable[0]?.value ?? null }\n}\n\n/** Chosen cash point becomes the cash `provider_data`, built on the server from the provider's list. */\nexport function cashProviderData(points: readonly PaymentPoint[], pointId: string | null | undefined): Record<string, string> | null {\n const point = points.find((p) => p.id === pointId) ?? (pointId ? null : points[0])\n if (!point) return null\n return {\n payment_method_id: point.payment_method_id,\n ...(point.payment_point ? { payment_point: point.payment_point } : {}),\n }\n}\n\n/**\n * A checkout (`GET checkouts/:id`) as an attempt result, for polling a 3DS challenge or a payment under review.\n * It only translates server states: it never decides a payment.\n */\nexport function checkoutToAttempt(result: CheckoutResult): PaymentAttemptResult {\n const { checkout, payment, order } = result\n const action = payment.action ?? null\n const base = { checkout, ...(order ? { order } : {}) }\n const status = (checkout.status ?? '').toLowerCase()\n if (status === 'paid' || status === 'converted') return { ...base, payment: { provider: payment.provider, status: 'paid', outcome: 'approved' } }\n if (status === 'cancelled' || status === 'canceled') return { ...base, payment: { provider: payment.provider, status: 'cancelled' } }\n if (status === 'expired') return { ...base, payment: { provider: payment.provider, status: 'expired' } }\n if (status === 'failed') {\n const decline = (payment as { decline_code?: PaymentAttemptResult['payment']['decline_code'] }).decline_code ?? 'declined'\n return { ...base, payment: { provider: payment.provider, status: 'failed', outcome: 'rejected', decline_code: decline, retryable: true } }\n }\n // Still pending: same as the last attempt left it (challenge or review), so the screen does not change.\n if (action?.type === 'challenge') return { ...base, payment: { provider: payment.provider, status: 'requires_action', outcome: 'challenge', action } }\n return { ...base, payment: { provider: payment.provider, status: 'pending', outcome: 'pending', action: action ?? { type: 'none' } } }\n}\n\n/** Body `{ error: { code, message, details } }` that `createFetchTransport` understands. */\nexport type PayErrorBody = { error: { code: string; message: string; details?: unknown } }\n\ntype ErrorLike = { status?: number; code?: string; field?: string; message?: string; details?: unknown }\n\n/**\n * Server error becomes a JSON response for the browser. Keeps the codes the SDK knows how to read (`price_mismatch`,\n * `payment_in_progress`, `expired`, `too_many_attempts`); the mock sends \"in progress\" in `field`, so that is checked too.\n */\nexport function payErrorResponse(err: ErrorLike): { status: number; body: PayErrorBody } {\n const status = typeof err.status === 'number' && err.status >= 400 ? err.status : 503\n let code = err.code ?? 'server_error'\n if (status === 409 && err.field === 'payment_in_progress') code = 'payment_in_progress'\n if (status === 429) code = 'too_many_attempts'\n if (status === 422 && err.field === 'expected_total') code = 'price_mismatch'\n return { status, body: { error: { code, message: err.message ?? '', ...(err.details !== undefined ? { details: err.details } : {}) } } }\n}\n\nexport type VoucherRowLabels = { point: string; reference: string; clabe: string; barcode: string; expires: string }\n\n/** Cookie receipt rows for a payment under review or with a voucher (shown on the thank-you page). */\nexport function voucherRows(\n voucher: NonNullable<NonNullable<PaymentAttemptResult['payment']['action']>['voucher']> | null | undefined,\n labels: VoucherRowLabels,\n locale: string,\n): [label: string, value: string][] {\n if (!voucher) return []\n const rows: [string, string][] = []\n if (voucher.payment_point) rows.push([labels.point, String(voucher.payment_point).slice(0, 60)])\n if (voucher.reference) rows.push([labels.reference, String(voucher.reference).slice(0, 80)])\n if (voucher.clabe) rows.push([labels.clabe, String(voucher.clabe).slice(0, 40)])\n if (voucher.barcode) rows.push([labels.barcode, String(voucher.barcode).slice(0, 80)])\n if (voucher.expires_at) {\n const d = new Date(voucher.expires_at)\n if (!Number.isNaN(d.getTime())) {\n rows.push([labels.expires, new Intl.DateTimeFormat(locale, { dateStyle: 'long', timeStyle: 'short', timeZone: 'UTC' }).format(d) + ' UTC'])\n }\n }\n return rows\n}\n"]}
|