@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/README.md +58 -1
- package/dist/checkout.cjs +54 -17
- package/dist/checkout.cjs.map +1 -1
- package/dist/checkout.d.cts +8 -1
- package/dist/checkout.d.ts +8 -1
- package/dist/checkout.js +53 -18
- package/dist/checkout.js.map +1 -1
- package/dist/{chunk-ISEOCKIR.js → chunk-MNLROALF.js} +96 -8
- package/dist/chunk-MNLROALF.js.map +1 -0
- package/dist/{choices-DEPE9KRq.d.cts → csp-DZPuExzu.d.cts} +93 -55
- package/dist/{choices-DEPE9KRq.d.ts → csp-DZPuExzu.d.ts} +93 -55
- package/dist/payments-server.cjs +159 -12
- package/dist/payments-server.cjs.map +1 -1
- package/dist/payments-server.d.cts +23 -3
- package/dist/payments-server.d.ts +23 -3
- package/dist/payments-server.js +70 -13
- package/dist/payments-server.js.map +1 -1
- package/dist/payments.cjs +722 -150
- package/dist/payments.cjs.map +1 -1
- package/dist/payments.css +19 -0
- package/dist/payments.d.cts +207 -6
- package/dist/payments.d.ts +207 -6
- package/dist/payments.js +566 -92
- package/dist/payments.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-ISEOCKIR.js.map +0 -1
package/dist/payments.cjs
CHANGED
|
@@ -193,6 +193,13 @@ function mercadoPagoBricksAdapter(options = {}) {
|
|
|
193
193
|
const device = getDevice();
|
|
194
194
|
return { provider_data, ...typeof device === "string" && device ? { device_session_id: device } : {} };
|
|
195
195
|
},
|
|
196
|
+
async update(next) {
|
|
197
|
+
if (gone) throw new Error("payment form is unmounted");
|
|
198
|
+
if (typeof controller.update !== "function") throw new Error("payment form: update not supported");
|
|
199
|
+
const value = Number.parseFloat(next.amount.amount);
|
|
200
|
+
if (!Number.isFinite(value) || value <= 0) throw new Error("payment form: invalid amount");
|
|
201
|
+
await controller.update({ amount: value });
|
|
202
|
+
},
|
|
196
203
|
async unmount() {
|
|
197
204
|
gone = true;
|
|
198
205
|
try {
|
|
@@ -204,6 +211,341 @@ function mercadoPagoBricksAdapter(options = {}) {
|
|
|
204
211
|
}
|
|
205
212
|
};
|
|
206
213
|
}
|
|
214
|
+
|
|
215
|
+
// src/payments/mount-queue.ts
|
|
216
|
+
var PaymentMountCancelled = class extends Error {
|
|
217
|
+
name = "PaymentMountCancelled";
|
|
218
|
+
constructor(message = "payment form: mount cancelled") {
|
|
219
|
+
super(message);
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
var PaymentMountTimeout = class extends Error {
|
|
223
|
+
name = "PaymentMountTimeout";
|
|
224
|
+
constructor(ms) {
|
|
225
|
+
super(`payment form: create timeout after ${ms}ms`);
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
var chains = /* @__PURE__ */ new WeakMap();
|
|
229
|
+
var seq = 0;
|
|
230
|
+
function withTimeout(p, ms) {
|
|
231
|
+
return new Promise((resolve, reject) => {
|
|
232
|
+
const t = setTimeout(() => reject(new PaymentMountTimeout(ms)), ms);
|
|
233
|
+
p.then(
|
|
234
|
+
(v) => {
|
|
235
|
+
clearTimeout(t);
|
|
236
|
+
resolve(v);
|
|
237
|
+
},
|
|
238
|
+
(e) => {
|
|
239
|
+
clearTimeout(t);
|
|
240
|
+
reject(e);
|
|
241
|
+
}
|
|
242
|
+
);
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
async function mountQueued(base, opts, queue = {}) {
|
|
246
|
+
const container = opts.container;
|
|
247
|
+
const createTimeoutMs = queue.createTimeoutMs ?? 1e4;
|
|
248
|
+
let chain = chains.get(container);
|
|
249
|
+
if (!chain) {
|
|
250
|
+
chain = { tail: Promise.resolve(), active: null, ticket: 0 };
|
|
251
|
+
chains.set(container, chain);
|
|
252
|
+
}
|
|
253
|
+
const me = chain;
|
|
254
|
+
let release;
|
|
255
|
+
const done = new Promise((r) => release = r);
|
|
256
|
+
const previous = me.tail;
|
|
257
|
+
const ticket = ++me.ticket;
|
|
258
|
+
me.tail = done;
|
|
259
|
+
let released = false;
|
|
260
|
+
const finish = () => {
|
|
261
|
+
if (!released) {
|
|
262
|
+
released = true;
|
|
263
|
+
release();
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
const cancelled = () => opts.signal?.aborted === true || !container.isConnected || me.ticket !== ticket;
|
|
267
|
+
let slot = null;
|
|
268
|
+
try {
|
|
269
|
+
if (me.active) await me.active.teardown();
|
|
270
|
+
await previous;
|
|
271
|
+
if (cancelled()) throw new PaymentMountCancelled();
|
|
272
|
+
slot = container.ownerDocument.createElement("div");
|
|
273
|
+
slot.id = `${container.id || "sf-pay"}-m${++seq}`;
|
|
274
|
+
container.replaceChildren(slot);
|
|
275
|
+
const node = slot;
|
|
276
|
+
const pending2 = base.mount({ ...opts, container: node });
|
|
277
|
+
let handle;
|
|
278
|
+
try {
|
|
279
|
+
handle = await withTimeout(pending2, createTimeoutMs);
|
|
280
|
+
} catch (err) {
|
|
281
|
+
void pending2.then((h) => Promise.resolve(h.unmount()).catch(() => {
|
|
282
|
+
}), () => {
|
|
283
|
+
});
|
|
284
|
+
throw err;
|
|
285
|
+
}
|
|
286
|
+
let torn = null;
|
|
287
|
+
const teardown = () => torn ??= (async () => {
|
|
288
|
+
try {
|
|
289
|
+
await handle.unmount();
|
|
290
|
+
} catch {
|
|
291
|
+
}
|
|
292
|
+
node.remove();
|
|
293
|
+
if (me.active === entry) me.active = null;
|
|
294
|
+
finish();
|
|
295
|
+
})();
|
|
296
|
+
const entry = { teardown };
|
|
297
|
+
me.active = entry;
|
|
298
|
+
if (cancelled() || !node.isConnected) {
|
|
299
|
+
await teardown();
|
|
300
|
+
throw new PaymentMountCancelled();
|
|
301
|
+
}
|
|
302
|
+
return {
|
|
303
|
+
collect: () => handle.collect(),
|
|
304
|
+
...handle.update ? { update: (next) => handle.update(next) } : {},
|
|
305
|
+
unmount: teardown
|
|
306
|
+
};
|
|
307
|
+
} catch (err) {
|
|
308
|
+
slot?.remove();
|
|
309
|
+
finish();
|
|
310
|
+
throw err;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
function withMountQueue(adapter, queue = {}) {
|
|
314
|
+
return { kind: adapter.kind, load: (client, options) => adapter.load(client, options), mount: (opts) => mountQueued(adapter, opts, queue) };
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// src/payments/adapter.ts
|
|
318
|
+
var PaymentHttpError = class extends Error {
|
|
319
|
+
constructor(status, code, message, details, extra) {
|
|
320
|
+
super(message ?? code);
|
|
321
|
+
this.status = status;
|
|
322
|
+
this.code = code;
|
|
323
|
+
this.details = details;
|
|
324
|
+
this.retryable = extra?.retryable;
|
|
325
|
+
this.kind = extra?.kind;
|
|
326
|
+
}
|
|
327
|
+
status;
|
|
328
|
+
code;
|
|
329
|
+
details;
|
|
330
|
+
name = "PaymentHttpError";
|
|
331
|
+
/** `error.retryable` of the shop body: `true` repeat with the SAME key; `false` it is an answer. `undefined`: not said. */
|
|
332
|
+
retryable;
|
|
333
|
+
/** `error.kind` of the shop body (`classifyPaymentError`). */
|
|
334
|
+
kind;
|
|
335
|
+
};
|
|
336
|
+
async function parse(res) {
|
|
337
|
+
const text = await res.text();
|
|
338
|
+
if (!text) return null;
|
|
339
|
+
try {
|
|
340
|
+
return JSON.parse(text);
|
|
341
|
+
} catch {
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
function createFetchTransport(url, options = {}) {
|
|
346
|
+
const doFetch = (u, init) => (options.fetch ?? globalThis.fetch.bind(globalThis))(u, init);
|
|
347
|
+
async function call(u, init) {
|
|
348
|
+
const res = await doFetch(u, { credentials: options.credentials ?? "same-origin", ...init });
|
|
349
|
+
const body = await parse(res);
|
|
350
|
+
if (!res.ok) {
|
|
351
|
+
const e = body?.error;
|
|
352
|
+
throw new PaymentHttpError(res.status, e?.code ?? "server_error", e?.message, e?.details, {
|
|
353
|
+
retryable: typeof e?.retryable === "boolean" ? e.retryable : void 0,
|
|
354
|
+
kind: typeof e?.kind === "string" ? e.kind : void 0
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
const result = body;
|
|
358
|
+
if (!result || typeof result !== "object" || !result.payment || typeof result.payment.status !== "string") {
|
|
359
|
+
throw new PaymentHttpError(502, "invalid_response", "Unexpected payment response");
|
|
360
|
+
}
|
|
361
|
+
return result;
|
|
362
|
+
}
|
|
363
|
+
return {
|
|
364
|
+
submit: (request, { signal }) => call(url, {
|
|
365
|
+
method: "POST",
|
|
366
|
+
signal,
|
|
367
|
+
headers: { "Content-Type": "application/json", Accept: "application/json", "Idempotency-Key": request.idempotencyKey, ...options.headers },
|
|
368
|
+
body: JSON.stringify(options.buildBody ? options.buildBody(request) : request)
|
|
369
|
+
}),
|
|
370
|
+
...options.refreshUrl ? {
|
|
371
|
+
refresh: ({ signal }) => call(options.refreshUrl, { method: "POST", signal, headers: { Accept: "application/json", ...options.headers } })
|
|
372
|
+
} : {}
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// src/payments/recovery-messages.ts
|
|
377
|
+
var ES = {
|
|
378
|
+
"error.transient": "Estamos verificando tu pago. Si el cargo se hizo, lo veremos aqu\xED; no te cobraremos dos veces.",
|
|
379
|
+
"error.in_progress": "Ya hay un pago en proceso para este pedido. Estamos verific\xE1ndolo; no vuelvas a pagar.",
|
|
380
|
+
"error.rate_limited": "Hiciste varios intentos seguidos. Espera unos minutos e int\xE9ntalo de nuevo.",
|
|
381
|
+
"error.merchant_config": "Por ahora no podemos procesar pagos con tarjeta. No es un problema con tu tarjeta: puedes pagar de otra forma.",
|
|
382
|
+
"error.provider_declined": "No pudimos procesar tu pago con esa tarjeta. Revisa los datos o prueba con otro m\xE9todo; no se hizo ning\xFAn cobro.",
|
|
383
|
+
"error.stale_checkout": "Este pedido ya qued\xF3 registrado y no se pudo reabrir el pago. Actualiza para ver c\xF3mo va tu pedido.",
|
|
384
|
+
"error.expired": "Tu carrito venci\xF3. Vuelve a armarlo para pagar.",
|
|
385
|
+
"error.price_mismatch": "El total cambi\xF3. Revisa el nuevo total y vuelve a pagar.",
|
|
386
|
+
"error.store_closed": "Esta tienda a\xFAn no est\xE1 abierta para compras. Vuelve pronto.",
|
|
387
|
+
"error.validation": "Completa los campos marcados.",
|
|
388
|
+
"error.other": "No pudimos procesar tu pago. Intenta de nuevo.",
|
|
389
|
+
"notice.confirming.title": "Estamos confirmando tu pago\u2026",
|
|
390
|
+
"notice.confirming.body": "No cierres esta p\xE1gina ni vuelvas a pagar. Si el cargo ya se hizo, lo ver\xE1s reflejado aqu\xED.",
|
|
391
|
+
"notice.form_failed.title": "No pudimos cargar el formulario de tarjeta",
|
|
392
|
+
"notice.form_failed.body": "Suele resolverse al intentar de nuevo. Tambi\xE9n puedes pagar de otra forma.",
|
|
393
|
+
"notice.rejected.title": "El cargo no se complet\xF3",
|
|
394
|
+
"notice.rejected.body": "No se hizo ning\xFAn cobro. Puedes intentar con otra tarjeta o elegir otra forma de pago.",
|
|
395
|
+
"notice.provider_declined.title": "No pudimos procesar tu pago con esa tarjeta",
|
|
396
|
+
"notice.provider_declined.body": "No se hizo ning\xFAn cobro. Revisa los datos o prueba con otra tarjeta u otro m\xE9todo de pago.",
|
|
397
|
+
"notice.merchant_config.title": "Por ahora no podemos cobrar con tarjeta",
|
|
398
|
+
"notice.merchant_config.body": "No es un problema con tu tarjeta: es un tema de nuestro lado. Puedes pagar de otra forma.",
|
|
399
|
+
"notice.stale_checkout.title": "Tu pedido ya qued\xF3 registrado",
|
|
400
|
+
"notice.stale_checkout.body": "No pudimos reabrir el pago de este pedido. Actualiza para ver c\xF3mo va tu pedido; no se cobr\xF3 de nuevo.",
|
|
401
|
+
"notice.other.title": "No pudimos procesar tu pago",
|
|
402
|
+
"notice.other.body": "Intenta de nuevo; si sigue igual, puedes pagar de otra forma.",
|
|
403
|
+
"action.retry": "Reintentar pago",
|
|
404
|
+
"action.retry_form": "Reintentar",
|
|
405
|
+
"action.refresh": "Actualizar mi pedido",
|
|
406
|
+
"action.alternative": "Pagar con {label}"
|
|
407
|
+
};
|
|
408
|
+
var EN = {
|
|
409
|
+
"error.transient": "We're verifying your payment. If the charge went through we'll show it here; you won't be charged twice.",
|
|
410
|
+
"error.in_progress": "A payment is already in progress for this order. We're verifying it; don't pay again.",
|
|
411
|
+
"error.rate_limited": "Too many attempts in a row. Wait a few minutes and try again.",
|
|
412
|
+
"error.merchant_config": "We can't take card payments right now. It isn't a problem with your card: you can pay another way.",
|
|
413
|
+
"error.provider_declined": "We couldn't process your payment with that card. Check the details or try another method; you were not charged.",
|
|
414
|
+
"error.stale_checkout": "This order is already registered and the payment couldn't be reopened. Refresh to see how it's going.",
|
|
415
|
+
"error.expired": "Your cart expired. Rebuild it to pay.",
|
|
416
|
+
"error.price_mismatch": "The total changed. Check the new total and pay again.",
|
|
417
|
+
"error.store_closed": "This store isn't open for purchases yet. Come back soon.",
|
|
418
|
+
"error.validation": "Complete the marked fields.",
|
|
419
|
+
"error.other": "We couldn't process your payment. Try again.",
|
|
420
|
+
"notice.confirming.title": "We're confirming your payment\u2026",
|
|
421
|
+
"notice.confirming.body": "Don't close this page or pay again. If the charge already went through you'll see it here.",
|
|
422
|
+
"notice.form_failed.title": "We couldn't load the card form",
|
|
423
|
+
"notice.form_failed.body": "Trying again usually fixes it. You can also pay another way.",
|
|
424
|
+
"notice.rejected.title": "The charge didn't go through",
|
|
425
|
+
"notice.rejected.body": "You were not charged. Try another card or choose another way to pay.",
|
|
426
|
+
"notice.provider_declined.title": "We couldn't process your payment with that card",
|
|
427
|
+
"notice.provider_declined.body": "You were not charged. Check the details or try another card or method.",
|
|
428
|
+
"notice.merchant_config.title": "We can't charge cards right now",
|
|
429
|
+
"notice.merchant_config.body": "It isn't a problem with your card: it's on our side. You can pay another way.",
|
|
430
|
+
"notice.stale_checkout.title": "Your order is already registered",
|
|
431
|
+
"notice.stale_checkout.body": "We couldn't reopen the payment for this order. Refresh to see how it's going; you were not charged again.",
|
|
432
|
+
"notice.other.title": "We couldn't process your payment",
|
|
433
|
+
"notice.other.body": "Try again; if it keeps failing you can pay another way.",
|
|
434
|
+
"action.retry": "Retry payment",
|
|
435
|
+
"action.retry_form": "Retry",
|
|
436
|
+
"action.refresh": "Refresh my order",
|
|
437
|
+
"action.alternative": "Pay with {label}"
|
|
438
|
+
};
|
|
439
|
+
function paymentRecoveryMessages(locale, overrides) {
|
|
440
|
+
const base = (locale ?? "es-MX").toLowerCase().startsWith("en") ? EN : ES;
|
|
441
|
+
return overrides ? { ...base, ...overrides } : base;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// src/payments/resilient-transport.ts
|
|
445
|
+
var realSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
446
|
+
function isResolvedAttempt(result) {
|
|
447
|
+
const p = result.payment;
|
|
448
|
+
if (p.status === "paid" || p.status === "failed" || p.status === "expired" || p.status === "cancelled") return true;
|
|
449
|
+
const type = p.action?.type;
|
|
450
|
+
return type === "voucher" || type === "challenge" || type === "redirect";
|
|
451
|
+
}
|
|
452
|
+
async function readJson(res) {
|
|
453
|
+
const text = await res.text().catch(() => "");
|
|
454
|
+
if (!text) return null;
|
|
455
|
+
try {
|
|
456
|
+
return JSON.parse(text);
|
|
457
|
+
} catch {
|
|
458
|
+
return null;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
function createResilientTransport(opts) {
|
|
462
|
+
const doFetch = opts.fetch ?? ((u, i) => globalThis.fetch(u, i));
|
|
463
|
+
const sleep2 = opts.sleep ?? realSleep;
|
|
464
|
+
const retries = opts.retries ?? 2;
|
|
465
|
+
const backoff = opts.backoffMs ?? [800, 2e3];
|
|
466
|
+
const polls = opts.statusPolls ?? 3;
|
|
467
|
+
const pollMs = opts.statusPollMs ?? 2e3;
|
|
468
|
+
const msgs = paymentRecoveryMessages(opts.locale, opts.messages);
|
|
469
|
+
const text = (k) => msgs[`error.${k}`];
|
|
470
|
+
const issue = (i) => opts.onIssue?.(i);
|
|
471
|
+
const confirming = msgs["notice.confirming.title"];
|
|
472
|
+
async function call(url, init) {
|
|
473
|
+
let res;
|
|
474
|
+
try {
|
|
475
|
+
res = await doFetch(url, { credentials: "same-origin", ...init, headers: { ...opts.headers, ...init.headers } });
|
|
476
|
+
} catch (err) {
|
|
477
|
+
if (err?.name === "AbortError") throw err;
|
|
478
|
+
return { fail: { kind: "transient", status: 0, code: "network", message: text("transient"), retryable: true } };
|
|
479
|
+
}
|
|
480
|
+
const body = await readJson(res);
|
|
481
|
+
if (res.ok) {
|
|
482
|
+
const r = body;
|
|
483
|
+
if (r && typeof r === "object" && r.payment && typeof r.payment.status === "string") return { ok: r };
|
|
484
|
+
return { fail: { kind: "transient", status: 502, code: "invalid_response", message: text("transient"), retryable: true } };
|
|
485
|
+
}
|
|
486
|
+
const e = body?.error;
|
|
487
|
+
if (!e) {
|
|
488
|
+
const info2 = storefront.classifyPaymentError({ status: res.status >= 500 || res.status === 0 ? 503 : res.status });
|
|
489
|
+
return { fail: { kind: info2.kind, status: res.status, code: "server_error", message: text(info2.kind), retryable: info2.retryable } };
|
|
490
|
+
}
|
|
491
|
+
const byCode = storefront.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 });
|
|
492
|
+
const info = e.kind && !(e.kind === "transient" && !byCode.retryable) ? { kind: e.kind, retryable: e.retryable === true || byCode.retryable } : byCode;
|
|
493
|
+
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 } };
|
|
494
|
+
}
|
|
495
|
+
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 });
|
|
496
|
+
async function resolveByStatus(signal) {
|
|
497
|
+
for (let i = 0; i < polls; i++) {
|
|
498
|
+
const r = await call(opts.statusUrl, { method: "POST", signal, headers: { Accept: "application/json" } });
|
|
499
|
+
if ("ok" in r) {
|
|
500
|
+
if (isResolvedAttempt(r.ok)) return r.ok;
|
|
501
|
+
} else if (!r.fail.retryable) {
|
|
502
|
+
return null;
|
|
503
|
+
}
|
|
504
|
+
if (i < polls - 1) await sleep2(pollMs);
|
|
505
|
+
}
|
|
506
|
+
return null;
|
|
507
|
+
}
|
|
508
|
+
return {
|
|
509
|
+
async submit(request, { signal }) {
|
|
510
|
+
const init = {
|
|
511
|
+
method: "POST",
|
|
512
|
+
signal,
|
|
513
|
+
headers: { "Content-Type": "application/json", Accept: "application/json", "Idempotency-Key": request.idempotencyKey },
|
|
514
|
+
body: JSON.stringify({ ...request, fields: opts.fields() })
|
|
515
|
+
};
|
|
516
|
+
issue(null);
|
|
517
|
+
let last = null;
|
|
518
|
+
for (let n = 0; n <= retries; n++) {
|
|
519
|
+
const r = await call(opts.payUrl, init);
|
|
520
|
+
if ("ok" in r) {
|
|
521
|
+
issue(null);
|
|
522
|
+
return r.ok;
|
|
523
|
+
}
|
|
524
|
+
last = r.fail;
|
|
525
|
+
const waitable = last.retryable && (last.kind === "transient" || last.kind === "in_progress");
|
|
526
|
+
if (!waitable || n === retries) break;
|
|
527
|
+
issue({ kind: "confirming", message: confirming });
|
|
528
|
+
await sleep2(backoff[Math.min(n, backoff.length - 1)] ?? 800);
|
|
529
|
+
}
|
|
530
|
+
const failure = last;
|
|
531
|
+
if (failure.retryable && (failure.kind === "transient" || failure.kind === "in_progress") || failure.kind === "stale_checkout") {
|
|
532
|
+
issue({ kind: "verifying", message: confirming });
|
|
533
|
+
const resolved = await resolveByStatus(signal);
|
|
534
|
+
if (resolved) {
|
|
535
|
+
issue(null);
|
|
536
|
+
return resolved;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
issue({ kind: failure.kind, message: failure.message, retryable: failure.retryable, status: failure.status, code: failure.code });
|
|
540
|
+
throw toError(failure);
|
|
541
|
+
},
|
|
542
|
+
refresh: async ({ signal }) => {
|
|
543
|
+
const r = await call(opts.statusUrl, { method: "POST", signal, headers: { Accept: "application/json" } });
|
|
544
|
+
if ("ok" in r) return r.ok;
|
|
545
|
+
throw toError(r.fail);
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
}
|
|
207
549
|
function isSafeCssValue(value) {
|
|
208
550
|
if (!value || value.length > 200) return false;
|
|
209
551
|
if (/[;{}<>\\]/.test(value)) return false;
|
|
@@ -280,6 +622,8 @@ function useEmbeddedPayment(options) {
|
|
|
280
622
|
retryDelayMs = 600,
|
|
281
623
|
pollIntervalMs = 2e3,
|
|
282
624
|
pollTimeoutMs = 6e4,
|
|
625
|
+
verifyTimeoutMs = 12e4,
|
|
626
|
+
verifyIntervalMs = 2e3,
|
|
283
627
|
scope = "default"
|
|
284
628
|
} = options;
|
|
285
629
|
const msgs = react.useMemo(() => storefront.paymentMessages(locale, options.messages), [locale, options.messages]);
|
|
@@ -293,15 +637,27 @@ function useEmbeddedPayment(options) {
|
|
|
293
637
|
const [attemptId, setAttemptId] = react.useState(null);
|
|
294
638
|
const [container, setContainer] = react.useState(null);
|
|
295
639
|
const [mountKey, setMountKey] = react.useState(0);
|
|
640
|
+
const [verifying, setVerifying] = react.useState(false);
|
|
296
641
|
const live = react.useRef({ ...options, msgs });
|
|
297
642
|
live.current = { ...options, msgs };
|
|
298
643
|
const form = react.useRef(null);
|
|
299
644
|
const inFlight = react.useRef(null);
|
|
300
645
|
const pendingData = react.useRef(null);
|
|
646
|
+
const memAttempt = react.useRef(null);
|
|
647
|
+
const memOnly = react.useRef(false);
|
|
648
|
+
const errorRef = react.useRef(null);
|
|
649
|
+
const retryRef = react.useRef(() => Promise.resolve());
|
|
301
650
|
const alive = react.useRef(true);
|
|
302
651
|
const generation = react.useRef(0);
|
|
303
652
|
const statusRef = react.useRef("idle");
|
|
304
653
|
statusRef.current = status;
|
|
654
|
+
errorRef.current = error;
|
|
655
|
+
const amountKey = `${amount.amount}|${amount.currency_code}`;
|
|
656
|
+
const amountKeyRef = react.useRef(amountKey);
|
|
657
|
+
amountKeyRef.current = amountKey;
|
|
658
|
+
const appliedAmount = react.useRef(null);
|
|
659
|
+
const updating = react.useRef(null);
|
|
660
|
+
const remountRequested = react.useRef(false);
|
|
305
661
|
react.useEffect(() => {
|
|
306
662
|
alive.current = true;
|
|
307
663
|
return () => {
|
|
@@ -312,19 +668,33 @@ function useEmbeddedPayment(options) {
|
|
|
312
668
|
const store = options.storage === void 0 ? safeStorage() : options.storage;
|
|
313
669
|
const emit = react.useCallback((e) => live.current.onEvent?.(e), []);
|
|
314
670
|
const readAttempt = react.useCallback(() => {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
671
|
+
if (!memOnly.current) {
|
|
672
|
+
try {
|
|
673
|
+
const saved = store?.getItem(storageKey);
|
|
674
|
+
if (saved) {
|
|
675
|
+
memAttempt.current = saved;
|
|
676
|
+
return saved;
|
|
677
|
+
}
|
|
678
|
+
} catch {
|
|
679
|
+
memOnly.current = true;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
if (memOnly.current && memAttempt.current) return memAttempt.current;
|
|
683
|
+
if (!store) {
|
|
684
|
+
if (memAttempt.current) return memAttempt.current;
|
|
319
685
|
}
|
|
320
686
|
const fresh = (live.current.newAttemptId ?? storefront.generateIdempotencyKey)();
|
|
687
|
+
memAttempt.current = fresh;
|
|
321
688
|
try {
|
|
322
689
|
store?.setItem(storageKey, fresh);
|
|
690
|
+
if (store && store.getItem(storageKey) !== fresh) memOnly.current = true;
|
|
323
691
|
} catch {
|
|
692
|
+
memOnly.current = true;
|
|
324
693
|
}
|
|
325
694
|
return fresh;
|
|
326
695
|
}, [store, storageKey]);
|
|
327
696
|
const rotateAttempt = react.useCallback(() => {
|
|
697
|
+
memAttempt.current = null;
|
|
328
698
|
try {
|
|
329
699
|
store?.removeItem(storageKey);
|
|
330
700
|
} catch {
|
|
@@ -340,7 +710,32 @@ function useEmbeddedPayment(options) {
|
|
|
340
710
|
},
|
|
341
711
|
[emit]
|
|
342
712
|
);
|
|
343
|
-
const
|
|
713
|
+
const syncAmount = react.useCallback(() => {
|
|
714
|
+
const m = form.current;
|
|
715
|
+
if (!m || inFlight.current || updating.current || remountRequested.current) return;
|
|
716
|
+
if (statusRef.current === "network_error" || statusRef.current === "blocked" && errorRef.current?.kind === "payment_in_progress") return;
|
|
717
|
+
if (appliedAmount.current === amountKeyRef.current) return;
|
|
718
|
+
const run = (async () => {
|
|
719
|
+
while (form.current === m && alive.current && appliedAmount.current !== amountKeyRef.current) {
|
|
720
|
+
const key = amountKeyRef.current;
|
|
721
|
+
try {
|
|
722
|
+
if (!m.update) throw new Error("payment form: update not supported");
|
|
723
|
+
await m.update({ amount: live.current.amount });
|
|
724
|
+
appliedAmount.current = key;
|
|
725
|
+
} catch {
|
|
726
|
+
if (form.current === m && alive.current) {
|
|
727
|
+
remountRequested.current = true;
|
|
728
|
+
setMountKey((k) => k + 1);
|
|
729
|
+
}
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
})().finally(() => {
|
|
734
|
+
updating.current = null;
|
|
735
|
+
if (alive.current) syncAmount();
|
|
736
|
+
});
|
|
737
|
+
updating.current = run;
|
|
738
|
+
}, []);
|
|
344
739
|
const kind = client?.kind;
|
|
345
740
|
const publicKey = client?.public_key;
|
|
346
741
|
react.useEffect(() => {
|
|
@@ -354,18 +749,22 @@ function useEmbeddedPayment(options) {
|
|
|
354
749
|
return;
|
|
355
750
|
}
|
|
356
751
|
if (!container) return;
|
|
752
|
+
remountRequested.current = false;
|
|
357
753
|
const my = ++generation.current;
|
|
358
754
|
const started = Date.now();
|
|
755
|
+
const abort = new AbortController();
|
|
359
756
|
let mounted = null;
|
|
360
757
|
setStatus("loading");
|
|
361
758
|
const stale = () => my !== generation.current || !alive.current;
|
|
362
759
|
const fail = (reason) => {
|
|
363
760
|
if (stale()) return;
|
|
364
761
|
generation.current++;
|
|
762
|
+
abort.abort();
|
|
365
763
|
clearTimeout(timer);
|
|
366
764
|
const m = mounted ?? form.current;
|
|
367
765
|
mounted = null;
|
|
368
766
|
form.current = null;
|
|
767
|
+
appliedAmount.current = null;
|
|
369
768
|
if (m) void Promise.resolve(m.unmount());
|
|
370
769
|
fallback(reason);
|
|
371
770
|
};
|
|
@@ -379,31 +778,39 @@ function useEmbeddedPayment(options) {
|
|
|
379
778
|
if (stale()) return;
|
|
380
779
|
const theme = live.current.theme ?? readPaymentTheme(container);
|
|
381
780
|
const colorScheme = live.current.colorScheme ?? readPaymentScheme(container);
|
|
382
|
-
const
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
781
|
+
const createdWith = amountKeyRef.current;
|
|
782
|
+
const m = await mountQueued(
|
|
783
|
+
adapter,
|
|
784
|
+
{
|
|
785
|
+
container,
|
|
786
|
+
client,
|
|
787
|
+
amount: live.current.amount,
|
|
788
|
+
...live.current.email ? { email: live.current.email } : {},
|
|
789
|
+
...live.current.locale ? { locale: live.current.locale } : {},
|
|
790
|
+
theme,
|
|
791
|
+
...colorScheme ? { colorScheme } : {},
|
|
792
|
+
signal: abort.signal,
|
|
793
|
+
onReady: () => {
|
|
794
|
+
if (stale()) return;
|
|
795
|
+
clearTimeout(timer);
|
|
796
|
+
setStatus((s) => s === "loading" ? "ready" : s);
|
|
797
|
+
emit({ type: "form_ready", ms: Date.now() - started });
|
|
798
|
+
},
|
|
799
|
+
onError: (e) => {
|
|
800
|
+
if (stale()) return;
|
|
801
|
+
if (e.critical) fail("form_error");
|
|
802
|
+
}
|
|
395
803
|
},
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
if (e.critical) fail("form_error");
|
|
399
|
-
}
|
|
400
|
-
});
|
|
804
|
+
{ createTimeoutMs: live.current.createTimeoutMs ?? sdkTimeoutMs }
|
|
805
|
+
);
|
|
401
806
|
if (stale()) {
|
|
402
807
|
void Promise.resolve(m.unmount());
|
|
403
808
|
return;
|
|
404
809
|
}
|
|
405
810
|
mounted = m;
|
|
406
811
|
form.current = m;
|
|
812
|
+
appliedAmount.current = createdWith;
|
|
813
|
+
syncAmount();
|
|
407
814
|
} catch (err) {
|
|
408
815
|
fail(err instanceof Error && /timeout/i.test(err.message) ? "sdk_timeout" : "sdk_error");
|
|
409
816
|
}
|
|
@@ -411,12 +818,17 @@ function useEmbeddedPayment(options) {
|
|
|
411
818
|
return () => {
|
|
412
819
|
clearTimeout(timer);
|
|
413
820
|
generation.current++;
|
|
821
|
+
abort.abort();
|
|
414
822
|
const m = mounted ?? form.current;
|
|
415
823
|
mounted = null;
|
|
416
824
|
form.current = null;
|
|
825
|
+
appliedAmount.current = null;
|
|
417
826
|
if (m) void Promise.resolve(m.unmount());
|
|
418
827
|
};
|
|
419
|
-
}, [enabled, container, kind, publicKey,
|
|
828
|
+
}, [enabled, container, kind, publicKey, mountKey, sdkTimeoutMs, containerId]);
|
|
829
|
+
react.useEffect(() => {
|
|
830
|
+
syncAmount();
|
|
831
|
+
}, [amountKey, syncAmount]);
|
|
420
832
|
const finish = react.useCallback(
|
|
421
833
|
(result) => {
|
|
422
834
|
const o = storefront.paymentOutcome(result, live.current.locale, live.current.messages);
|
|
@@ -440,7 +852,7 @@ function useEmbeddedPayment(options) {
|
|
|
440
852
|
[emit, rotateAttempt]
|
|
441
853
|
);
|
|
442
854
|
const send = react.useCallback(
|
|
443
|
-
async (data, attempt) => {
|
|
855
|
+
async (data, attempt, total) => {
|
|
444
856
|
const ctl = new AbortController();
|
|
445
857
|
const maxRetries = live.current.autoRetries ?? autoRetries;
|
|
446
858
|
for (let n = 0; ; n++) {
|
|
@@ -452,7 +864,7 @@ function useEmbeddedPayment(options) {
|
|
|
452
864
|
idempotencyKey: attempt,
|
|
453
865
|
provider_data: data.provider_data,
|
|
454
866
|
...data.device_session_id ? { device_session_id: data.device_session_id } : {},
|
|
455
|
-
expected_total:
|
|
867
|
+
expected_total: total
|
|
456
868
|
},
|
|
457
869
|
{ signal: ctl.signal }
|
|
458
870
|
);
|
|
@@ -470,7 +882,7 @@ function useEmbeddedPayment(options) {
|
|
|
470
882
|
emit({ type: "error", kind: view.kind });
|
|
471
883
|
setError(view);
|
|
472
884
|
if (view.kind === "network") {
|
|
473
|
-
pendingData.current = data;
|
|
885
|
+
pendingData.current = { data, total };
|
|
474
886
|
setStatus("network_error");
|
|
475
887
|
} else if (view.kind === "price_mismatch") {
|
|
476
888
|
pendingData.current = null;
|
|
@@ -496,7 +908,11 @@ function useEmbeddedPayment(options) {
|
|
|
496
908
|
if (inFlight.current) return inFlight.current;
|
|
497
909
|
const m = form.current;
|
|
498
910
|
if (!m || statusRef.current !== "ready" && statusRef.current !== "network_error") return Promise.resolve();
|
|
911
|
+
if (statusRef.current === "network_error" && pendingData.current) return retryRef.current();
|
|
912
|
+
const total = live.current.amount;
|
|
499
913
|
const run = (async () => {
|
|
914
|
+
if (updating.current) await updating.current.catch(() => {
|
|
915
|
+
});
|
|
500
916
|
setStatus("processing");
|
|
501
917
|
setError(null);
|
|
502
918
|
setOutcome(null);
|
|
@@ -512,30 +928,33 @@ function useEmbeddedPayment(options) {
|
|
|
512
928
|
}
|
|
513
929
|
const attempt = readAttempt();
|
|
514
930
|
setAttemptId(attempt);
|
|
515
|
-
await send(data, attempt);
|
|
931
|
+
await send(data, attempt, total);
|
|
516
932
|
})().finally(() => {
|
|
517
933
|
inFlight.current = null;
|
|
934
|
+
syncAmount();
|
|
518
935
|
});
|
|
519
936
|
inFlight.current = run;
|
|
520
937
|
return run;
|
|
521
|
-
}, [emit, readAttempt, send]);
|
|
938
|
+
}, [emit, readAttempt, send, syncAmount]);
|
|
522
939
|
const retry = react.useCallback(() => {
|
|
523
940
|
if (inFlight.current) return inFlight.current;
|
|
524
|
-
const
|
|
525
|
-
if (!
|
|
941
|
+
const pending2 = pendingData.current;
|
|
942
|
+
if (!pending2) return submit();
|
|
526
943
|
emit({ type: "retry" });
|
|
527
944
|
const run = (async () => {
|
|
528
945
|
setStatus("processing");
|
|
529
946
|
setError(null);
|
|
530
947
|
const attempt = readAttempt();
|
|
531
948
|
setAttemptId(attempt);
|
|
532
|
-
await send(data, attempt);
|
|
949
|
+
await send(pending2.data, attempt, pending2.total);
|
|
533
950
|
})().finally(() => {
|
|
534
951
|
inFlight.current = null;
|
|
952
|
+
syncAmount();
|
|
535
953
|
});
|
|
536
954
|
inFlight.current = run;
|
|
537
955
|
return run;
|
|
538
|
-
}, [emit, readAttempt, send, submit]);
|
|
956
|
+
}, [emit, readAttempt, send, submit, syncAmount]);
|
|
957
|
+
retryRef.current = retry;
|
|
539
958
|
react.useEffect(() => {
|
|
540
959
|
if (status !== "challenge" && status !== "in_review" || !transport.refresh) return;
|
|
541
960
|
let stop = false;
|
|
@@ -548,6 +967,7 @@ function useEmbeddedPayment(options) {
|
|
|
548
967
|
try {
|
|
549
968
|
const r = await transport.refresh({ signal: ctl.signal });
|
|
550
969
|
if (stop || !alive.current) return;
|
|
970
|
+
if (r.payment.status === "none") continue;
|
|
551
971
|
const o = storefront.paymentOutcome(r, live.current.locale, live.current.messages);
|
|
552
972
|
if (o.kind !== status) {
|
|
553
973
|
finish(r);
|
|
@@ -562,6 +982,44 @@ function useEmbeddedPayment(options) {
|
|
|
562
982
|
ctl.abort();
|
|
563
983
|
};
|
|
564
984
|
}, [status, transport, pollIntervalMs, pollTimeoutMs, finish]);
|
|
985
|
+
const unknownOutcome = status === "network_error" || status === "blocked" && error?.kind === "payment_in_progress";
|
|
986
|
+
react.useEffect(() => {
|
|
987
|
+
if (!unknownOutcome) syncAmount();
|
|
988
|
+
}, [unknownOutcome, syncAmount]);
|
|
989
|
+
react.useEffect(() => {
|
|
990
|
+
if (!unknownOutcome || !transport.refresh) {
|
|
991
|
+
setVerifying(false);
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
let stop = false;
|
|
995
|
+
const ctl = new AbortController();
|
|
996
|
+
const until = Date.now() + verifyTimeoutMs;
|
|
997
|
+
setVerifying(true);
|
|
998
|
+
(async () => {
|
|
999
|
+
let wait = verifyIntervalMs;
|
|
1000
|
+
while (!stop && Date.now() < until) {
|
|
1001
|
+
await sleep(wait);
|
|
1002
|
+
wait = Math.min(wait * 2, 15e3);
|
|
1003
|
+
if (stop || !alive.current) return;
|
|
1004
|
+
try {
|
|
1005
|
+
const r = await transport.refresh({ signal: ctl.signal });
|
|
1006
|
+
if (stop || !alive.current) return;
|
|
1007
|
+
if (isResolvedAttempt(r)) {
|
|
1008
|
+
setVerifying(false);
|
|
1009
|
+
finish(r);
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
} catch {
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
if (!stop && alive.current) setVerifying(false);
|
|
1016
|
+
})();
|
|
1017
|
+
return () => {
|
|
1018
|
+
stop = true;
|
|
1019
|
+
ctl.abort();
|
|
1020
|
+
setVerifying(false);
|
|
1021
|
+
};
|
|
1022
|
+
}, [unknownOutcome, transport, verifyTimeoutMs, verifyIntervalMs, finish]);
|
|
565
1023
|
const dismiss = react.useCallback(() => {
|
|
566
1024
|
setError(null);
|
|
567
1025
|
setOutcome((o) => o?.kind === "rejected" ? null : o);
|
|
@@ -576,6 +1034,7 @@ function useEmbeddedPayment(options) {
|
|
|
576
1034
|
attemptsLeft,
|
|
577
1035
|
attemptId,
|
|
578
1036
|
busy: status === "loading" || status === "processing",
|
|
1037
|
+
verifying,
|
|
579
1038
|
containerRef: setContainer,
|
|
580
1039
|
containerId,
|
|
581
1040
|
submit,
|
|
@@ -685,53 +1144,195 @@ var PaymentBrick = react.forwardRef(function PaymentBrick2(props, ref) {
|
|
|
685
1144
|
] });
|
|
686
1145
|
});
|
|
687
1146
|
|
|
688
|
-
// src/payments/
|
|
689
|
-
var
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
1147
|
+
// src/payments/payment-method.messages.ts
|
|
1148
|
+
var ES2 = {
|
|
1149
|
+
legend: "M\xE9todo de pago",
|
|
1150
|
+
pay: "Pagar",
|
|
1151
|
+
placeOrder: "Realizar el pedido",
|
|
1152
|
+
placeOrderCash: "Generar ficha de pago",
|
|
1153
|
+
chooseDelivery: "Elige la entrega para continuar",
|
|
1154
|
+
payRedirect: "Continuar al pago",
|
|
1155
|
+
continue: "Continuar",
|
|
1156
|
+
retryAddress: "Guardar direcci\xF3n y continuar",
|
|
1157
|
+
processing: "Procesando\u2026",
|
|
1158
|
+
redirectNote: "Te llevamos a la p\xE1gina del proveedor para terminar el pago y regresas aqu\xED.",
|
|
1159
|
+
cashPoints: "Elige d\xF3nde pagar en efectivo",
|
|
1160
|
+
cashMore: "Ver m\xE1s puntos de pago",
|
|
1161
|
+
cashNote: "Al hacer el pedido te damos una ficha con la referencia y la fecha l\xEDmite. El pedido se confirma cuando pagas.",
|
|
1162
|
+
secureCard: "Tus datos de tarjeta los captura directamente nuestro proveedor de pagos; la tienda nunca los ve.",
|
|
1163
|
+
pickDeliveryFirst: "Elige primero la entrega para ver el total y pagar con tarjeta.",
|
|
1164
|
+
updatingTotal: "Actualizando el total\u2026",
|
|
1165
|
+
syncFailed: "No pudimos actualizar el total con tu entrega. Puedes continuar al sitio del proveedor para pagar con tarjeta.",
|
|
1166
|
+
quoteChanged: "Las tarifas de env\xEDo se actualizaron. Revisa tu env\xEDo para ver el total.",
|
|
1167
|
+
rebuildCart: "Volver a armar mi carrito",
|
|
1168
|
+
noPaymentTitle: "Por ahora no hay formas de pago disponibles",
|
|
1169
|
+
noPayment: "Intenta de nuevo en unos minutos o escr\xEDbenos para completar tu pedido.",
|
|
1170
|
+
paymentNote: "El pago se procesa de forma segura con nuestro proveedor de pagos.",
|
|
1171
|
+
options: {
|
|
1172
|
+
card: "Tarjeta de cr\xE9dito o d\xE9bito",
|
|
1173
|
+
cash: "Efectivo",
|
|
1174
|
+
wallet: "Cuenta del proveedor de pagos",
|
|
1175
|
+
credits: "Pagos sin tarjeta"
|
|
695
1176
|
}
|
|
696
|
-
status;
|
|
697
|
-
code;
|
|
698
|
-
details;
|
|
699
|
-
name = "PaymentHttpError";
|
|
700
1177
|
};
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
1178
|
+
var EN2 = {
|
|
1179
|
+
legend: "Payment method",
|
|
1180
|
+
pay: "Pay",
|
|
1181
|
+
placeOrder: "Place order",
|
|
1182
|
+
placeOrderCash: "Get payment slip",
|
|
1183
|
+
chooseDelivery: "Choose delivery to continue",
|
|
1184
|
+
payRedirect: "Continue to payment",
|
|
1185
|
+
continue: "Continue",
|
|
1186
|
+
retryAddress: "Save address and continue",
|
|
1187
|
+
processing: "Processing\u2026",
|
|
1188
|
+
redirectNote: "We'll take you to the provider's page to finish paying, then bring you back here.",
|
|
1189
|
+
cashPoints: "Choose where to pay in cash",
|
|
1190
|
+
cashMore: "Show more payment points",
|
|
1191
|
+
cashNote: "When you place the order you get a slip with the reference and the deadline. The order is confirmed once you pay.",
|
|
1192
|
+
secureCard: "Your card details are captured directly by our payment provider; the shop never sees them.",
|
|
1193
|
+
pickDeliveryFirst: "Choose delivery first to see the total and pay by card.",
|
|
1194
|
+
updatingTotal: "Updating the total\u2026",
|
|
1195
|
+
syncFailed: "We couldn't update the total with your delivery. You can continue to the provider's site to pay by card.",
|
|
1196
|
+
quoteChanged: "Shipping rates were updated. Check your delivery to see the total.",
|
|
1197
|
+
rebuildCart: "Rebuild my cart",
|
|
1198
|
+
noPaymentTitle: "No payment methods are available right now",
|
|
1199
|
+
noPayment: "Try again in a few minutes or contact us to complete your order.",
|
|
1200
|
+
paymentNote: "Payment is processed securely by our payment provider.",
|
|
1201
|
+
options: {
|
|
1202
|
+
card: "Credit or debit card",
|
|
1203
|
+
cash: "Cash",
|
|
1204
|
+
wallet: "Payment provider account",
|
|
1205
|
+
credits: "Pay without a card"
|
|
1206
|
+
}
|
|
1207
|
+
};
|
|
1208
|
+
function paymentMethodMessages(locale, overrides) {
|
|
1209
|
+
const base = (locale ?? "es-MX").toLowerCase().startsWith("en") ? EN2 : ES2;
|
|
1210
|
+
if (!overrides) return base;
|
|
1211
|
+
const { options, ...rest } = overrides;
|
|
1212
|
+
return { ...base, ...rest, options: { ...base.options, ...options } };
|
|
1213
|
+
}
|
|
1214
|
+
function alternativeChoices(choices) {
|
|
1215
|
+
return choices.filter((c) => c.mode !== "card");
|
|
1216
|
+
}
|
|
1217
|
+
function pickChoice(form, value) {
|
|
1218
|
+
const input = Array.from(form?.querySelectorAll('input[name="payment_choice"]') ?? []).find((i) => i.value === value);
|
|
1219
|
+
if (!input) return;
|
|
1220
|
+
input.click();
|
|
1221
|
+
input.scrollIntoView?.({ block: "center", behavior: "smooth" });
|
|
1222
|
+
}
|
|
1223
|
+
var INDETERMINATE = /* @__PURE__ */ new Set(["confirming", "verifying", "transient", "in_progress"]);
|
|
1224
|
+
function safeStorage2() {
|
|
704
1225
|
try {
|
|
705
|
-
return
|
|
1226
|
+
return typeof sessionStorage !== "undefined" ? sessionStorage : null;
|
|
706
1227
|
} catch {
|
|
707
1228
|
return null;
|
|
708
1229
|
}
|
|
709
1230
|
}
|
|
710
|
-
function
|
|
711
|
-
const
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
1231
|
+
function usePaymentRecovery(options = {}) {
|
|
1232
|
+
const { scope = "default", formAutoRetries = 2 } = options;
|
|
1233
|
+
const [notice, setNotice] = react.useState(null);
|
|
1234
|
+
const [sectionKey, setSectionKey] = react.useState(0);
|
|
1235
|
+
const formFailures = react.useRef(0);
|
|
1236
|
+
const lastIssue = react.useRef(null);
|
|
1237
|
+
const unknownByEvent = react.useRef(false);
|
|
1238
|
+
const live = react.useRef(options);
|
|
1239
|
+
live.current = options;
|
|
1240
|
+
const onIssue = react.useCallback((issue) => {
|
|
1241
|
+
lastIssue.current = issue;
|
|
1242
|
+
if (!issue) return setNotice((n) => n?.kind === "confirming" ? null : n);
|
|
1243
|
+
if (issue.kind === "confirming" || issue.kind === "verifying") return setNotice({ kind: "confirming" });
|
|
1244
|
+
if (issue.kind === "merchant_config" || issue.kind === "stale_checkout" || issue.kind === "provider_declined") return setNotice({ kind: issue.kind });
|
|
1245
|
+
if (issue.kind === "other") return setNotice({ kind: "other" });
|
|
1246
|
+
setNotice((n) => n?.kind === "confirming" ? null : n);
|
|
1247
|
+
}, []);
|
|
1248
|
+
const retry = react.useCallback(() => {
|
|
1249
|
+
const issue = lastIssue.current;
|
|
1250
|
+
if (unknownByEvent.current || issue && INDETERMINATE.has(issue.kind)) return false;
|
|
1251
|
+
const store = live.current.storage === void 0 ? safeStorage2() : live.current.storage;
|
|
1252
|
+
try {
|
|
1253
|
+
store?.removeItem(`sf:pay:attempt:${live.current.scope ?? "default"}`);
|
|
1254
|
+
} catch {
|
|
718
1255
|
}
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
1256
|
+
formFailures.current = 0;
|
|
1257
|
+
lastIssue.current = null;
|
|
1258
|
+
unknownByEvent.current = false;
|
|
1259
|
+
setNotice(null);
|
|
1260
|
+
setSectionKey((k) => k + 1);
|
|
1261
|
+
return true;
|
|
1262
|
+
}, []);
|
|
1263
|
+
const onEvent = react.useCallback(
|
|
1264
|
+
(e) => {
|
|
1265
|
+
if (e.type === "form_ready") {
|
|
1266
|
+
formFailures.current = 0;
|
|
1267
|
+
setNotice((n) => n?.kind === "form_failed" ? null : n);
|
|
1268
|
+
}
|
|
1269
|
+
if (e.type === "form_failed") {
|
|
1270
|
+
if (formFailures.current < formAutoRetries) {
|
|
1271
|
+
formFailures.current += 1;
|
|
1272
|
+
setSectionKey((k) => k + 1);
|
|
1273
|
+
} else {
|
|
1274
|
+
setNotice({ kind: "form_failed" });
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
if (e.type === "error") unknownByEvent.current = e.kind === "network" || e.kind === "payment_in_progress";
|
|
1278
|
+
if (e.type === "result") {
|
|
1279
|
+
unknownByEvent.current = false;
|
|
1280
|
+
lastIssue.current = null;
|
|
1281
|
+
}
|
|
1282
|
+
if (e.type === "result" && e.status === "rejected") setNotice({ kind: "rejected" });
|
|
1283
|
+
if (e.type === "error" && lastIssue.current?.kind === "store_closed") live.current.onRevalidate?.();
|
|
1284
|
+
},
|
|
1285
|
+
[formAutoRetries]
|
|
1286
|
+
);
|
|
1287
|
+
return react.useMemo(() => ({ notice, setNotice, sectionKey, onIssue, onEvent, lastIssue, retry, clear: () => setNotice(null) }), [notice, sectionKey, onIssue, onEvent, retry]);
|
|
1288
|
+
}
|
|
1289
|
+
function PaymentRecovery(props) {
|
|
1290
|
+
const { notice, alternatives = [], onRetry, onPick, onRefresh, alternativeLabel, classNames: cx = {}, testIds = {}, children } = props;
|
|
1291
|
+
const locale = useResolvedLocale(props.locale);
|
|
1292
|
+
const m = react.useMemo(() => paymentRecoveryMessages(locale, props.messages), [locale, props.messages]);
|
|
1293
|
+
const rowNames = react.useMemo(() => paymentMethodMessages(locale).options, [locale]);
|
|
1294
|
+
const ref = react.useRef(null);
|
|
1295
|
+
const kind = notice?.kind;
|
|
1296
|
+
react.useEffect(() => {
|
|
1297
|
+
if (kind && kind !== "confirming") {
|
|
1298
|
+
ref.current?.focus();
|
|
1299
|
+
ref.current?.scrollIntoView?.({ block: "nearest", behavior: "smooth" });
|
|
722
1300
|
}
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
1301
|
+
}, [kind]);
|
|
1302
|
+
if (!notice) return null;
|
|
1303
|
+
const confirming = notice.kind === "confirming";
|
|
1304
|
+
const canRetry = notice.kind !== "confirming" && notice.kind !== "stale_checkout";
|
|
1305
|
+
const showAlternatives = notice.kind !== "stale_checkout" && !confirming && alternatives.length > 0;
|
|
1306
|
+
const alternativesFirst = notice.kind === "merchant_config";
|
|
1307
|
+
const nameOf = (c) => c.label ?? (c.option ? rowNames[c.option] : void 0) ?? c.option ?? c.methodKey;
|
|
1308
|
+
const labelOf = (c) => alternativeLabel?.(c) ?? m["action.alternative"].replace("{label}", nameOf(c));
|
|
1309
|
+
const retryButton = canRetry ? /* @__PURE__ */ jsxRuntime.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;
|
|
1310
|
+
return /* @__PURE__ */ jsxRuntime.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: [
|
|
1311
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: cx.title, children: /* @__PURE__ */ jsxRuntime.jsx("strong", { children: m[`notice.${notice.kind}.title`] }) }),
|
|
1312
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: cx.body, children: m[`notice.${notice.kind}.body`] }),
|
|
1313
|
+
!confirming ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cx.actions, children: [
|
|
1314
|
+
alternativesFirst ? null : retryButton,
|
|
1315
|
+
notice.kind === "stale_checkout" ? /* @__PURE__ */ jsxRuntime.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,
|
|
1316
|
+
showAlternatives ? alternatives.map((c) => /* @__PURE__ */ jsxRuntime.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,
|
|
1317
|
+
alternativesFirst ? retryButton : null
|
|
1318
|
+
] }) : null,
|
|
1319
|
+
children
|
|
1320
|
+
] });
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
// src/payments/revalidate.ts
|
|
1324
|
+
function checkoutShouldRevalidate(options) {
|
|
1325
|
+
const paths = options.paths.map((p) => p.replace(/\/+$/, "") || "/");
|
|
1326
|
+
return (args) => {
|
|
1327
|
+
const method = args.formMethod?.toUpperCase();
|
|
1328
|
+
if (!method || method === "GET" || !args.formAction) return args.defaultShouldRevalidate;
|
|
1329
|
+
let pathname = args.formAction;
|
|
1330
|
+
try {
|
|
1331
|
+
pathname = new URL(args.formAction, "http://sf.local").pathname;
|
|
1332
|
+
} catch {
|
|
1333
|
+
}
|
|
1334
|
+
pathname = pathname.replace(/\/+$/, "") || "/";
|
|
1335
|
+
return paths.includes(pathname) ? false : args.defaultShouldRevalidate;
|
|
735
1336
|
};
|
|
736
1337
|
}
|
|
737
1338
|
|
|
@@ -794,74 +1395,6 @@ function paymentNonceFromHeaders(headers) {
|
|
|
794
1395
|
return /'nonce-([A-Za-z0-9+/=_-]+)'/.exec(csp)?.[1];
|
|
795
1396
|
}
|
|
796
1397
|
|
|
797
|
-
// src/payments/payment-method.messages.ts
|
|
798
|
-
var ES = {
|
|
799
|
-
legend: "M\xE9todo de pago",
|
|
800
|
-
pay: "Pagar",
|
|
801
|
-
placeOrder: "Realizar el pedido",
|
|
802
|
-
placeOrderCash: "Generar ficha de pago",
|
|
803
|
-
chooseDelivery: "Elige la entrega para continuar",
|
|
804
|
-
payRedirect: "Continuar al pago",
|
|
805
|
-
continue: "Continuar",
|
|
806
|
-
retryAddress: "Guardar direcci\xF3n y continuar",
|
|
807
|
-
processing: "Procesando\u2026",
|
|
808
|
-
redirectNote: "Te llevamos a la p\xE1gina del proveedor para terminar el pago y regresas aqu\xED.",
|
|
809
|
-
cashPoints: "Elige d\xF3nde pagar en efectivo",
|
|
810
|
-
cashMore: "Ver m\xE1s puntos de pago",
|
|
811
|
-
cashNote: "Al hacer el pedido te damos una ficha con la referencia y la fecha l\xEDmite. El pedido se confirma cuando pagas.",
|
|
812
|
-
secureCard: "Tus datos de tarjeta los captura directamente nuestro proveedor de pagos; la tienda nunca los ve.",
|
|
813
|
-
pickDeliveryFirst: "Elige primero la entrega para ver el total y pagar con tarjeta.",
|
|
814
|
-
updatingTotal: "Actualizando el total\u2026",
|
|
815
|
-
syncFailed: "No pudimos actualizar el total con tu entrega. Puedes continuar al sitio del proveedor para pagar con tarjeta.",
|
|
816
|
-
quoteChanged: "Las tarifas de env\xEDo se actualizaron. Revisa tu env\xEDo para ver el total.",
|
|
817
|
-
rebuildCart: "Volver a armar mi carrito",
|
|
818
|
-
noPaymentTitle: "Por ahora no hay formas de pago disponibles",
|
|
819
|
-
noPayment: "Intenta de nuevo en unos minutos o escr\xEDbenos para completar tu pedido.",
|
|
820
|
-
paymentNote: "El pago se procesa de forma segura con nuestro proveedor de pagos.",
|
|
821
|
-
options: {
|
|
822
|
-
card: "Tarjeta de cr\xE9dito o d\xE9bito",
|
|
823
|
-
cash: "Efectivo",
|
|
824
|
-
wallet: "Cuenta del proveedor de pagos",
|
|
825
|
-
credits: "Pagos sin tarjeta"
|
|
826
|
-
}
|
|
827
|
-
};
|
|
828
|
-
var EN = {
|
|
829
|
-
legend: "Payment method",
|
|
830
|
-
pay: "Pay",
|
|
831
|
-
placeOrder: "Place order",
|
|
832
|
-
placeOrderCash: "Get payment slip",
|
|
833
|
-
chooseDelivery: "Choose delivery to continue",
|
|
834
|
-
payRedirect: "Continue to payment",
|
|
835
|
-
continue: "Continue",
|
|
836
|
-
retryAddress: "Save address and continue",
|
|
837
|
-
processing: "Processing\u2026",
|
|
838
|
-
redirectNote: "We'll take you to the provider's page to finish paying, then bring you back here.",
|
|
839
|
-
cashPoints: "Choose where to pay in cash",
|
|
840
|
-
cashMore: "Show more payment points",
|
|
841
|
-
cashNote: "When you place the order you get a slip with the reference and the deadline. The order is confirmed once you pay.",
|
|
842
|
-
secureCard: "Your card details are captured directly by our payment provider; the shop never sees them.",
|
|
843
|
-
pickDeliveryFirst: "Choose delivery first to see the total and pay by card.",
|
|
844
|
-
updatingTotal: "Updating the total\u2026",
|
|
845
|
-
syncFailed: "We couldn't update the total with your delivery. You can continue to the provider's site to pay by card.",
|
|
846
|
-
quoteChanged: "Shipping rates were updated. Check your delivery to see the total.",
|
|
847
|
-
rebuildCart: "Rebuild my cart",
|
|
848
|
-
noPaymentTitle: "No payment methods are available right now",
|
|
849
|
-
noPayment: "Try again in a few minutes or contact us to complete your order.",
|
|
850
|
-
paymentNote: "Payment is processed securely by our payment provider.",
|
|
851
|
-
options: {
|
|
852
|
-
card: "Credit or debit card",
|
|
853
|
-
cash: "Cash",
|
|
854
|
-
wallet: "Payment provider account",
|
|
855
|
-
credits: "Pay without a card"
|
|
856
|
-
}
|
|
857
|
-
};
|
|
858
|
-
function paymentMethodMessages(locale, overrides) {
|
|
859
|
-
const base = (locale ?? "es-MX").toLowerCase().startsWith("en") ? EN : ES;
|
|
860
|
-
if (!overrides) return base;
|
|
861
|
-
const { options, ...rest } = overrides;
|
|
862
|
-
return { ...base, ...rest, options: { ...base.options, ...options } };
|
|
863
|
-
}
|
|
864
|
-
|
|
865
1398
|
// src/checkout/brands/logos.generated.ts
|
|
866
1399
|
var LOGOS = {
|
|
867
1400
|
"7eleven": { src: "data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 xmlns:xlink=%22http://www.w3.org/1999/xlink%22 xml:space=%22preserve%22 viewBox=%220 0 45 59.89%22%3E%3Cdefs%3E%3Cpath id=%22a%22 d=%22M0 0h45v59.9H0z%22/%3E%3C/defs%3E%3CclipPath id=%22b%22%3E%3Cuse xlink:href=%22%23a%22 style=%22overflow:visible%22/%3E%3C/clipPath%3E%3Cpath d=%22M34.2 45c.9 0 1.7.8 1.7 1.7s-.7 1.7-1.7 1.7c-.9 0-1.6-.8-1.6-1.7s.7-1.7 1.6-1.7m0 3c.7 0 1.3-.6 1.3-1.3 0-.8-.6-1.3-1.3-1.3s-1.3.6-1.3 1.3c0 .8.6 1.3 1.3 1.3m-.2-.4h-.3v-1.8h.6q.6 0 .6.6c0 .3-.2.5-.4.5l.4.7h-.4l-.4-.7H34zm.2-1q.3 0 .3-.3t-.3-.3H34v.5h.2zm8-14.3c-.9 0-1.5.4-2.1.9l-.4.6-.1-.1.5-.8v-.5h-3.2V43h2.9v-6.7c0-1.1.7-1.4 1.2-1.5s1.1.2 1.1.8V43H45v-7.3c0-2.3-1-3.4-2.8-3.4M9.9 40.6h3v2.3H7V32.3h2.9zM0 42.9h5.9v-2.1h-3v-2.3h3v-2h-3v-2h3v-2.2H0zm13.9 0h6v-2.1h-3.1v-2.3h3.1v-2h-3.1v-2h3.1v-2.2h-6zm16 0h6v-2.1h-3.1v-2.3h3.1v-2h-3.1v-2h3.1v-2.2h-6zm-3.8-10.6L25 39l-.1 1.2v.1h-.1v-.1l-.1-1.2-1.1-6.7h-2.9l2.4 10.6h3.6l2.4-10.6z%22 style=%22clip-path:url(%23b);fill:%23147350%22/%3E%3Cpath d=%22M37.7 0H0v15h19.9C24.4 8.5 30.5 3.3 37.7 0%22 style=%22clip-path:url(%23b);fill:%23ff6c00%22/%3E%3Cpath d=%22M15 59.9h15v-15H15zM45 15V0a40.7 40.7 0 0 0-28.9 30h15.3A25.8 25.8 0 0 1 45 15%22 style=%22clip-path:url(%23b);fill:%23eb0f2a%22/%3E%3C/svg%3E", ar: 0.751, mono: false },
|
|
@@ -1255,6 +1788,15 @@ function PaymentMethodSection(props) {
|
|
|
1255
1788
|
const requote = wantSync && settled && sync.requote === true;
|
|
1256
1789
|
const syncFailed = wantSync && settled && !sync.ok && !requote;
|
|
1257
1790
|
const brickReady = cardActive && deliveryReady && synced && !syncFailed;
|
|
1791
|
+
const [formLatched, setFormLatched] = react.useState(false);
|
|
1792
|
+
react.useEffect(() => {
|
|
1793
|
+
if (brickReady) setFormLatched(true);
|
|
1794
|
+
}, [brickReady]);
|
|
1795
|
+
react.useEffect(() => {
|
|
1796
|
+
if (!cardActive) setFormLatched(false);
|
|
1797
|
+
}, [cardActive]);
|
|
1798
|
+
const showBrick = cardActive && !syncFailed && (formLatched || brickReady);
|
|
1799
|
+
const totalUpdating = showBrick && !brickReady;
|
|
1258
1800
|
const transport = react.useMemo(
|
|
1259
1801
|
() => props.transport ?? createFetchTransport(payUrl, {
|
|
1260
1802
|
...statusUrl ? { refreshUrl: statusUrl } : {},
|
|
@@ -1368,9 +1910,9 @@ function PaymentMethodSection(props) {
|
|
|
1368
1910
|
marks.length ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: cx.brands, "data-sf-pay-brands": choice.option ?? "plain", "aria-hidden": "true", children: marks.map((mk) => /* @__PURE__ */ jsxRuntime.jsx(BrandMark, { brand: mk.key, logoUrl: mk.logoUrl ?? null, size: 20 }, mk.key)) }) : null
|
|
1369
1911
|
] }),
|
|
1370
1912
|
/* @__PURE__ */ jsxRuntime.jsx(ChoicePanel, { choice, m, invalid, cx, tid, hidden: hydrated && !checked, marks: brandMarks, visible: cashPointsVisible, openMore: storedPoint, onPointChange }),
|
|
1371
|
-
choice.mode === "card" && checked && cardChosen && hydrated && choices.cardClient ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cx.brickWrap, "data-sf-pay-card": true, ...tid("card"), children: [
|
|
1913
|
+
choice.mode === "card" && checked && cardChosen && hydrated && choices.cardClient ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cx.brickWrap, "data-sf-pay-card": true, ...totalUpdating ? { "data-sf-pay-updating": "" } : {}, "aria-busy": totalUpdating || void 0, ...tid("card"), children: [
|
|
1372
1914
|
fallback ? null : !deliveryReady ? cardNotice("pickDelivery", m.pickDeliveryFirst) : syncFailed ? cardNotice("syncFailed", m.syncFailed) : requote ? cardNotice("requote", m.quoteChanged) : !synced ? cardNotice("syncing", m.updatingTotal) : null,
|
|
1373
|
-
|
|
1915
|
+
showBrick || fallback ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1374
1916
|
PaymentBrick,
|
|
1375
1917
|
{
|
|
1376
1918
|
...brickProps,
|
|
@@ -1382,15 +1924,14 @@ function PaymentMethodSection(props) {
|
|
|
1382
1924
|
locale,
|
|
1383
1925
|
...scope ? { scope } : {},
|
|
1384
1926
|
...payerEmail && !brickProps?.email ? { email: payerEmail } : {},
|
|
1385
|
-
enabled:
|
|
1927
|
+
enabled: showBrick,
|
|
1386
1928
|
...cx.brick ? { className: cx.brick } : {},
|
|
1387
1929
|
onEvent: handleEvent,
|
|
1388
1930
|
onFallback: () => setFallback(true),
|
|
1389
1931
|
onResult: (o) => {
|
|
1390
1932
|
if (o.kind === "approved" || o.kind === "in_review" || o.kind === "voucher") onDone(o);
|
|
1391
1933
|
}
|
|
1392
|
-
}
|
|
1393
|
-
payerEmail || brickProps?.email ? "with-email" : "without-email"
|
|
1934
|
+
}
|
|
1394
1935
|
) : null,
|
|
1395
1936
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: cx.note, children: m.secureCard }),
|
|
1396
1937
|
expired && rebuildHref ? Link({ href: rebuildHref, ...cx.rebuild ? { className: cx.rebuild } : {}, children: m.rebuildCart, ...testIds.rebuild ? { testId: testIds.rebuild } : {} }) : null
|
|
@@ -1409,7 +1950,7 @@ function PaymentMethodSection(props) {
|
|
|
1409
1950
|
onClick: onPayClick,
|
|
1410
1951
|
className: cx.submit,
|
|
1411
1952
|
disabled: busy || blocked,
|
|
1412
|
-
"aria-disabled": blocked ||
|
|
1953
|
+
"aria-disabled": blocked || cardActive && !syncFailed && deliveryReady && (!brickReady || !ready || processing) || void 0,
|
|
1413
1954
|
"aria-busy": processing || void 0,
|
|
1414
1955
|
"data-sf-pay-method-submit": true,
|
|
1415
1956
|
...tid("submit"),
|
|
@@ -1499,8 +2040,6 @@ function rowMarks(choice, cardBrands) {
|
|
|
1499
2040
|
}
|
|
1500
2041
|
return [];
|
|
1501
2042
|
}
|
|
1502
|
-
|
|
1503
|
-
// src/payments/choices.ts
|
|
1504
2043
|
var SEP = "|";
|
|
1505
2044
|
function choiceValue(methodKey, option) {
|
|
1506
2045
|
return option ? `${methodKey}${SEP}${option}` : methodKey;
|
|
@@ -1574,13 +2113,33 @@ function checkoutToAttempt(result) {
|
|
|
1574
2113
|
if (action?.type === "challenge") return { ...base, payment: { provider: payment.provider, status: "requires_action", outcome: "challenge", action } };
|
|
1575
2114
|
return { ...base, payment: { provider: payment.provider, status: "pending", outcome: "pending", action: action ?? { type: "none" } } };
|
|
1576
2115
|
}
|
|
1577
|
-
function
|
|
2116
|
+
function safeHttpStatus(status) {
|
|
2117
|
+
return status === 502 || status === 503 || status === 504 ? 500 : status;
|
|
2118
|
+
}
|
|
2119
|
+
function payErrorResponse(err, options = {}) {
|
|
1578
2120
|
const status = typeof err.status === "number" && err.status >= 400 ? err.status : 503;
|
|
1579
|
-
|
|
2121
|
+
const { kind: classified, retryable } = storefront.classifyPaymentError({ ...err, status, checkoutId: err.checkoutId ?? null });
|
|
2122
|
+
const kind = options.kind ?? classified;
|
|
2123
|
+
let code = err.code ?? (kind === "transient" ? "unavailable" : "server_error");
|
|
1580
2124
|
if (status === 409 && err.field === "payment_in_progress") code = "payment_in_progress";
|
|
1581
2125
|
if (status === 429) code = "too_many_attempts";
|
|
1582
2126
|
if (status === 422 && err.field === "expected_total") code = "price_mismatch";
|
|
1583
|
-
|
|
2127
|
+
const own = (options.ownMessage || kind === "validation" || kind === "expired" || kind === "price_mismatch" || kind === "other" && status < 500) && err.message;
|
|
2128
|
+
const message = own || paymentRecoveryMessages(options.locale, options.messages)[`error.${kind}`];
|
|
2129
|
+
return {
|
|
2130
|
+
status: safeHttpStatus(status),
|
|
2131
|
+
body: {
|
|
2132
|
+
error: {
|
|
2133
|
+
code,
|
|
2134
|
+
message,
|
|
2135
|
+
retryable: options.kind ? options.kind === "transient" || options.kind === "in_progress" || options.kind === "rate_limited" : retryable,
|
|
2136
|
+
kind,
|
|
2137
|
+
...err.field ? { field: err.field } : {},
|
|
2138
|
+
...err.retryAfter ? { retry_after: err.retryAfter } : {},
|
|
2139
|
+
...err.details !== void 0 ? { details: err.details } : {}
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
2142
|
+
};
|
|
1584
2143
|
}
|
|
1585
2144
|
function voucherRows(voucher, labels, locale) {
|
|
1586
2145
|
if (!voucher) return [];
|
|
@@ -1606,15 +2165,23 @@ exports.PaymentBrick = PaymentBrick;
|
|
|
1606
2165
|
exports.PaymentChallenge = PaymentChallenge;
|
|
1607
2166
|
exports.PaymentHttpError = PaymentHttpError;
|
|
1608
2167
|
exports.PaymentMethodSection = PaymentMethodSection;
|
|
2168
|
+
exports.PaymentMountCancelled = PaymentMountCancelled;
|
|
2169
|
+
exports.PaymentMountTimeout = PaymentMountTimeout;
|
|
2170
|
+
exports.PaymentRecovery = PaymentRecovery;
|
|
1609
2171
|
exports.VoucherCard = VoucherCard;
|
|
2172
|
+
exports.alternativeChoices = alternativeChoices;
|
|
1610
2173
|
exports.buildPayChoices = buildPayChoices;
|
|
1611
2174
|
exports.cashProviderData = cashProviderData;
|
|
2175
|
+
exports.checkoutShouldRevalidate = checkoutShouldRevalidate;
|
|
1612
2176
|
exports.checkoutToAttempt = checkoutToAttempt;
|
|
1613
2177
|
exports.choiceValue = choiceValue;
|
|
1614
2178
|
exports.createFetchTransport = createFetchTransport;
|
|
2179
|
+
exports.createResilientTransport = createResilientTransport;
|
|
1615
2180
|
exports.formFields = formFields;
|
|
2181
|
+
exports.isResolvedAttempt = isResolvedAttempt;
|
|
1616
2182
|
exports.loadPaymentSdk = loadPaymentSdk;
|
|
1617
2183
|
exports.mercadoPagoBricksAdapter = mercadoPagoBricksAdapter;
|
|
2184
|
+
exports.mountQueued = mountQueued;
|
|
1618
2185
|
exports.parseChoice = parseChoice;
|
|
1619
2186
|
exports.payErrorResponse = payErrorResponse;
|
|
1620
2187
|
exports.paymentCsp = paymentCsp;
|
|
@@ -1624,11 +2191,16 @@ exports.paymentCspMode = paymentCspMode;
|
|
|
1624
2191
|
exports.paymentMethodMessages = paymentMethodMessages;
|
|
1625
2192
|
exports.paymentNonce = paymentNonce;
|
|
1626
2193
|
exports.paymentNonceFromHeaders = paymentNonceFromHeaders;
|
|
2194
|
+
exports.paymentRecoveryMessages = paymentRecoveryMessages;
|
|
2195
|
+
exports.pickChoice = pickChoice;
|
|
1627
2196
|
exports.readPaymentScheme = readPaymentScheme;
|
|
1628
2197
|
exports.readPaymentTheme = readPaymentTheme;
|
|
1629
2198
|
exports.resetPaymentSdkCache = resetPaymentSdkCache;
|
|
2199
|
+
exports.safeHttpStatus = safeHttpStatus;
|
|
1630
2200
|
exports.sanitizeMercadoPagoFormData = sanitizeMercadoPagoFormData;
|
|
1631
2201
|
exports.useEmbeddedPayment = useEmbeddedPayment;
|
|
2202
|
+
exports.usePaymentRecovery = usePaymentRecovery;
|
|
1632
2203
|
exports.voucherRows = voucherRows;
|
|
2204
|
+
exports.withMountQueue = withMountQueue;
|
|
1633
2205
|
//# sourceMappingURL=payments.cjs.map
|
|
1634
2206
|
//# sourceMappingURL=payments.cjs.map
|