@asteby/storefront-react 0.16.1 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -1
- package/dist/checkout.cjs +108 -20
- package/dist/checkout.cjs.map +1 -1
- package/dist/checkout.d.cts +15 -1
- package/dist/checkout.d.ts +15 -1
- package/dist/checkout.js +107 -21
- 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 +784 -160
- package/dist/payments.cjs.map +1 -1
- package/dist/payments.css +84 -0
- package/dist/payments.d.cts +227 -8
- package/dist/payments.d.ts +227 -8
- package/dist/payments.js +628 -102
- 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;
|
|
@@ -279,7 +621,9 @@ function useEmbeddedPayment(options) {
|
|
|
279
621
|
autoRetries = 2,
|
|
280
622
|
retryDelayMs = 600,
|
|
281
623
|
pollIntervalMs = 2e3,
|
|
282
|
-
pollTimeoutMs =
|
|
624
|
+
pollTimeoutMs = 3e5,
|
|
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,28 @@ 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");
|
|
653
|
+
const leftChallenge = react.useRef(false);
|
|
304
654
|
statusRef.current = status;
|
|
655
|
+
errorRef.current = error;
|
|
656
|
+
const amountKey = `${amount.amount}|${amount.currency_code}`;
|
|
657
|
+
const amountKeyRef = react.useRef(amountKey);
|
|
658
|
+
amountKeyRef.current = amountKey;
|
|
659
|
+
const appliedAmount = react.useRef(null);
|
|
660
|
+
const updating = react.useRef(null);
|
|
661
|
+
const remountRequested = react.useRef(false);
|
|
305
662
|
react.useEffect(() => {
|
|
306
663
|
alive.current = true;
|
|
307
664
|
return () => {
|
|
@@ -312,19 +669,33 @@ function useEmbeddedPayment(options) {
|
|
|
312
669
|
const store = options.storage === void 0 ? safeStorage() : options.storage;
|
|
313
670
|
const emit = react.useCallback((e) => live.current.onEvent?.(e), []);
|
|
314
671
|
const readAttempt = react.useCallback(() => {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
672
|
+
if (!memOnly.current) {
|
|
673
|
+
try {
|
|
674
|
+
const saved = store?.getItem(storageKey);
|
|
675
|
+
if (saved) {
|
|
676
|
+
memAttempt.current = saved;
|
|
677
|
+
return saved;
|
|
678
|
+
}
|
|
679
|
+
} catch {
|
|
680
|
+
memOnly.current = true;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
if (memOnly.current && memAttempt.current) return memAttempt.current;
|
|
684
|
+
if (!store) {
|
|
685
|
+
if (memAttempt.current) return memAttempt.current;
|
|
319
686
|
}
|
|
320
687
|
const fresh = (live.current.newAttemptId ?? storefront.generateIdempotencyKey)();
|
|
688
|
+
memAttempt.current = fresh;
|
|
321
689
|
try {
|
|
322
690
|
store?.setItem(storageKey, fresh);
|
|
691
|
+
if (store && store.getItem(storageKey) !== fresh) memOnly.current = true;
|
|
323
692
|
} catch {
|
|
693
|
+
memOnly.current = true;
|
|
324
694
|
}
|
|
325
695
|
return fresh;
|
|
326
696
|
}, [store, storageKey]);
|
|
327
697
|
const rotateAttempt = react.useCallback(() => {
|
|
698
|
+
memAttempt.current = null;
|
|
328
699
|
try {
|
|
329
700
|
store?.removeItem(storageKey);
|
|
330
701
|
} catch {
|
|
@@ -340,7 +711,32 @@ function useEmbeddedPayment(options) {
|
|
|
340
711
|
},
|
|
341
712
|
[emit]
|
|
342
713
|
);
|
|
343
|
-
const
|
|
714
|
+
const syncAmount = react.useCallback(() => {
|
|
715
|
+
const m = form.current;
|
|
716
|
+
if (!m || inFlight.current || updating.current || remountRequested.current) return;
|
|
717
|
+
if (statusRef.current === "network_error" || statusRef.current === "blocked" && errorRef.current?.kind === "payment_in_progress") return;
|
|
718
|
+
if (appliedAmount.current === amountKeyRef.current) return;
|
|
719
|
+
const run = (async () => {
|
|
720
|
+
while (form.current === m && alive.current && appliedAmount.current !== amountKeyRef.current) {
|
|
721
|
+
const key = amountKeyRef.current;
|
|
722
|
+
try {
|
|
723
|
+
if (!m.update) throw new Error("payment form: update not supported");
|
|
724
|
+
await m.update({ amount: live.current.amount });
|
|
725
|
+
appliedAmount.current = key;
|
|
726
|
+
} catch {
|
|
727
|
+
if (form.current === m && alive.current) {
|
|
728
|
+
remountRequested.current = true;
|
|
729
|
+
setMountKey((k) => k + 1);
|
|
730
|
+
}
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
})().finally(() => {
|
|
735
|
+
updating.current = null;
|
|
736
|
+
if (alive.current) syncAmount();
|
|
737
|
+
});
|
|
738
|
+
updating.current = run;
|
|
739
|
+
}, []);
|
|
344
740
|
const kind = client?.kind;
|
|
345
741
|
const publicKey = client?.public_key;
|
|
346
742
|
react.useEffect(() => {
|
|
@@ -354,18 +750,22 @@ function useEmbeddedPayment(options) {
|
|
|
354
750
|
return;
|
|
355
751
|
}
|
|
356
752
|
if (!container) return;
|
|
753
|
+
remountRequested.current = false;
|
|
357
754
|
const my = ++generation.current;
|
|
358
755
|
const started = Date.now();
|
|
756
|
+
const abort = new AbortController();
|
|
359
757
|
let mounted = null;
|
|
360
758
|
setStatus("loading");
|
|
361
759
|
const stale = () => my !== generation.current || !alive.current;
|
|
362
760
|
const fail = (reason) => {
|
|
363
761
|
if (stale()) return;
|
|
364
762
|
generation.current++;
|
|
763
|
+
abort.abort();
|
|
365
764
|
clearTimeout(timer);
|
|
366
765
|
const m = mounted ?? form.current;
|
|
367
766
|
mounted = null;
|
|
368
767
|
form.current = null;
|
|
768
|
+
appliedAmount.current = null;
|
|
369
769
|
if (m) void Promise.resolve(m.unmount());
|
|
370
770
|
fallback(reason);
|
|
371
771
|
};
|
|
@@ -379,31 +779,39 @@ function useEmbeddedPayment(options) {
|
|
|
379
779
|
if (stale()) return;
|
|
380
780
|
const theme = live.current.theme ?? readPaymentTheme(container);
|
|
381
781
|
const colorScheme = live.current.colorScheme ?? readPaymentScheme(container);
|
|
382
|
-
const
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
782
|
+
const createdWith = amountKeyRef.current;
|
|
783
|
+
const m = await mountQueued(
|
|
784
|
+
adapter,
|
|
785
|
+
{
|
|
786
|
+
container,
|
|
787
|
+
client,
|
|
788
|
+
amount: live.current.amount,
|
|
789
|
+
...live.current.email ? { email: live.current.email } : {},
|
|
790
|
+
...live.current.locale ? { locale: live.current.locale } : {},
|
|
791
|
+
theme,
|
|
792
|
+
...colorScheme ? { colorScheme } : {},
|
|
793
|
+
signal: abort.signal,
|
|
794
|
+
onReady: () => {
|
|
795
|
+
if (stale()) return;
|
|
796
|
+
clearTimeout(timer);
|
|
797
|
+
setStatus((s) => s === "loading" ? "ready" : s);
|
|
798
|
+
emit({ type: "form_ready", ms: Date.now() - started });
|
|
799
|
+
},
|
|
800
|
+
onError: (e) => {
|
|
801
|
+
if (stale()) return;
|
|
802
|
+
if (e.critical) fail("form_error");
|
|
803
|
+
}
|
|
395
804
|
},
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
if (e.critical) fail("form_error");
|
|
399
|
-
}
|
|
400
|
-
});
|
|
805
|
+
{ createTimeoutMs: live.current.createTimeoutMs ?? sdkTimeoutMs }
|
|
806
|
+
);
|
|
401
807
|
if (stale()) {
|
|
402
808
|
void Promise.resolve(m.unmount());
|
|
403
809
|
return;
|
|
404
810
|
}
|
|
405
811
|
mounted = m;
|
|
406
812
|
form.current = m;
|
|
813
|
+
appliedAmount.current = createdWith;
|
|
814
|
+
syncAmount();
|
|
407
815
|
} catch (err) {
|
|
408
816
|
fail(err instanceof Error && /timeout/i.test(err.message) ? "sdk_timeout" : "sdk_error");
|
|
409
817
|
}
|
|
@@ -411,16 +819,22 @@ function useEmbeddedPayment(options) {
|
|
|
411
819
|
return () => {
|
|
412
820
|
clearTimeout(timer);
|
|
413
821
|
generation.current++;
|
|
822
|
+
abort.abort();
|
|
414
823
|
const m = mounted ?? form.current;
|
|
415
824
|
mounted = null;
|
|
416
825
|
form.current = null;
|
|
826
|
+
appliedAmount.current = null;
|
|
417
827
|
if (m) void Promise.resolve(m.unmount());
|
|
418
828
|
};
|
|
419
|
-
}, [enabled, container, kind, publicKey,
|
|
829
|
+
}, [enabled, container, kind, publicKey, mountKey, sdkTimeoutMs, containerId]);
|
|
830
|
+
react.useEffect(() => {
|
|
831
|
+
syncAmount();
|
|
832
|
+
}, [amountKey, syncAmount]);
|
|
420
833
|
const finish = react.useCallback(
|
|
421
834
|
(result) => {
|
|
422
835
|
const o = storefront.paymentOutcome(result, live.current.locale, live.current.messages);
|
|
423
836
|
pendingData.current = null;
|
|
837
|
+
leftChallenge.current = false;
|
|
424
838
|
setOutcome(o);
|
|
425
839
|
setError(null);
|
|
426
840
|
setAttemptsLeft(o.attempts_left);
|
|
@@ -440,7 +854,7 @@ function useEmbeddedPayment(options) {
|
|
|
440
854
|
[emit, rotateAttempt]
|
|
441
855
|
);
|
|
442
856
|
const send = react.useCallback(
|
|
443
|
-
async (data, attempt) => {
|
|
857
|
+
async (data, attempt, total) => {
|
|
444
858
|
const ctl = new AbortController();
|
|
445
859
|
const maxRetries = live.current.autoRetries ?? autoRetries;
|
|
446
860
|
for (let n = 0; ; n++) {
|
|
@@ -452,7 +866,7 @@ function useEmbeddedPayment(options) {
|
|
|
452
866
|
idempotencyKey: attempt,
|
|
453
867
|
provider_data: data.provider_data,
|
|
454
868
|
...data.device_session_id ? { device_session_id: data.device_session_id } : {},
|
|
455
|
-
expected_total:
|
|
869
|
+
expected_total: total
|
|
456
870
|
},
|
|
457
871
|
{ signal: ctl.signal }
|
|
458
872
|
);
|
|
@@ -470,7 +884,7 @@ function useEmbeddedPayment(options) {
|
|
|
470
884
|
emit({ type: "error", kind: view.kind });
|
|
471
885
|
setError(view);
|
|
472
886
|
if (view.kind === "network") {
|
|
473
|
-
pendingData.current = data;
|
|
887
|
+
pendingData.current = { data, total };
|
|
474
888
|
setStatus("network_error");
|
|
475
889
|
} else if (view.kind === "price_mismatch") {
|
|
476
890
|
pendingData.current = null;
|
|
@@ -496,7 +910,11 @@ function useEmbeddedPayment(options) {
|
|
|
496
910
|
if (inFlight.current) return inFlight.current;
|
|
497
911
|
const m = form.current;
|
|
498
912
|
if (!m || statusRef.current !== "ready" && statusRef.current !== "network_error") return Promise.resolve();
|
|
913
|
+
if (statusRef.current === "network_error" && pendingData.current) return retryRef.current();
|
|
914
|
+
const total = live.current.amount;
|
|
499
915
|
const run = (async () => {
|
|
916
|
+
if (updating.current) await updating.current.catch(() => {
|
|
917
|
+
});
|
|
500
918
|
setStatus("processing");
|
|
501
919
|
setError(null);
|
|
502
920
|
setOutcome(null);
|
|
@@ -512,30 +930,33 @@ function useEmbeddedPayment(options) {
|
|
|
512
930
|
}
|
|
513
931
|
const attempt = readAttempt();
|
|
514
932
|
setAttemptId(attempt);
|
|
515
|
-
await send(data, attempt);
|
|
933
|
+
await send(data, attempt, total);
|
|
516
934
|
})().finally(() => {
|
|
517
935
|
inFlight.current = null;
|
|
936
|
+
syncAmount();
|
|
518
937
|
});
|
|
519
938
|
inFlight.current = run;
|
|
520
939
|
return run;
|
|
521
|
-
}, [emit, readAttempt, send]);
|
|
940
|
+
}, [emit, readAttempt, send, syncAmount]);
|
|
522
941
|
const retry = react.useCallback(() => {
|
|
523
942
|
if (inFlight.current) return inFlight.current;
|
|
524
|
-
const
|
|
525
|
-
if (!
|
|
943
|
+
const pending2 = pendingData.current;
|
|
944
|
+
if (!pending2) return submit();
|
|
526
945
|
emit({ type: "retry" });
|
|
527
946
|
const run = (async () => {
|
|
528
947
|
setStatus("processing");
|
|
529
948
|
setError(null);
|
|
530
949
|
const attempt = readAttempt();
|
|
531
950
|
setAttemptId(attempt);
|
|
532
|
-
await send(data, attempt);
|
|
951
|
+
await send(pending2.data, attempt, pending2.total);
|
|
533
952
|
})().finally(() => {
|
|
534
953
|
inFlight.current = null;
|
|
954
|
+
syncAmount();
|
|
535
955
|
});
|
|
536
956
|
inFlight.current = run;
|
|
537
957
|
return run;
|
|
538
|
-
}, [emit, readAttempt, send, submit]);
|
|
958
|
+
}, [emit, readAttempt, send, submit, syncAmount]);
|
|
959
|
+
retryRef.current = retry;
|
|
539
960
|
react.useEffect(() => {
|
|
540
961
|
if (status !== "challenge" && status !== "in_review" || !transport.refresh) return;
|
|
541
962
|
let stop = false;
|
|
@@ -548,6 +969,7 @@ function useEmbeddedPayment(options) {
|
|
|
548
969
|
try {
|
|
549
970
|
const r = await transport.refresh({ signal: ctl.signal });
|
|
550
971
|
if (stop || !alive.current) return;
|
|
972
|
+
if (r.payment.status === "none") continue;
|
|
551
973
|
const o = storefront.paymentOutcome(r, live.current.locale, live.current.messages);
|
|
552
974
|
if (o.kind !== status) {
|
|
553
975
|
finish(r);
|
|
@@ -562,10 +984,57 @@ function useEmbeddedPayment(options) {
|
|
|
562
984
|
ctl.abort();
|
|
563
985
|
};
|
|
564
986
|
}, [status, transport, pollIntervalMs, pollTimeoutMs, finish]);
|
|
987
|
+
const unknownOutcome = status === "network_error" || status === "blocked" && error?.kind === "payment_in_progress";
|
|
988
|
+
react.useEffect(() => {
|
|
989
|
+
if (!unknownOutcome) syncAmount();
|
|
990
|
+
}, [unknownOutcome, syncAmount]);
|
|
991
|
+
react.useEffect(() => {
|
|
992
|
+
if (!unknownOutcome || !transport.refresh) {
|
|
993
|
+
setVerifying(false);
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
let stop = false;
|
|
997
|
+
const ctl = new AbortController();
|
|
998
|
+
const until = Date.now() + (leftChallenge.current ? Math.max(verifyTimeoutMs, pollTimeoutMs) : verifyTimeoutMs);
|
|
999
|
+
setVerifying(true);
|
|
1000
|
+
(async () => {
|
|
1001
|
+
let wait = verifyIntervalMs;
|
|
1002
|
+
while (!stop && Date.now() < until) {
|
|
1003
|
+
await sleep(wait);
|
|
1004
|
+
wait = Math.min(wait * 2, 15e3);
|
|
1005
|
+
if (stop || !alive.current) return;
|
|
1006
|
+
try {
|
|
1007
|
+
const r = await transport.refresh({ signal: ctl.signal });
|
|
1008
|
+
if (stop || !alive.current) return;
|
|
1009
|
+
if (isResolvedAttempt(r)) {
|
|
1010
|
+
const settled = r.payment.status === "paid" || r.payment.status === "failed" || r.payment.status === "expired" || r.payment.status === "cancelled";
|
|
1011
|
+
if (leftChallenge.current && !settled && r.payment.action?.type === "challenge") continue;
|
|
1012
|
+
setVerifying(false);
|
|
1013
|
+
finish(r);
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
} catch {
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
if (!stop && alive.current) setVerifying(false);
|
|
1020
|
+
})();
|
|
1021
|
+
return () => {
|
|
1022
|
+
stop = true;
|
|
1023
|
+
ctl.abort();
|
|
1024
|
+
setVerifying(false);
|
|
1025
|
+
};
|
|
1026
|
+
}, [unknownOutcome, transport, verifyTimeoutMs, verifyIntervalMs, pollTimeoutMs, finish]);
|
|
565
1027
|
const dismiss = react.useCallback(() => {
|
|
566
1028
|
setError(null);
|
|
567
1029
|
setOutcome((o) => o?.kind === "rejected" ? null : o);
|
|
568
1030
|
}, []);
|
|
1031
|
+
const cancelChallenge = react.useCallback(() => {
|
|
1032
|
+
if (statusRef.current !== "challenge") return;
|
|
1033
|
+
leftChallenge.current = true;
|
|
1034
|
+
setOutcome(null);
|
|
1035
|
+
setError({ kind: "payment_in_progress", message: live.current.msgs["challenge.left"], retrySameAttempt: false, canRetry: false });
|
|
1036
|
+
setStatus("blocked");
|
|
1037
|
+
}, []);
|
|
569
1038
|
const message = status === "processing" ? msgs["state.processing"] : status === "fallback" ? msgs["state.fallback"] : error?.message ?? outcome?.message ?? (status === "loading" ? msgs["state.loading"] : null);
|
|
570
1039
|
return {
|
|
571
1040
|
status,
|
|
@@ -576,11 +1045,13 @@ function useEmbeddedPayment(options) {
|
|
|
576
1045
|
attemptsLeft,
|
|
577
1046
|
attemptId,
|
|
578
1047
|
busy: status === "loading" || status === "processing",
|
|
1048
|
+
verifying,
|
|
579
1049
|
containerRef: setContainer,
|
|
580
1050
|
containerId,
|
|
581
1051
|
submit,
|
|
582
1052
|
retry,
|
|
583
|
-
dismiss
|
|
1053
|
+
dismiss,
|
|
1054
|
+
cancelChallenge
|
|
584
1055
|
};
|
|
585
1056
|
}
|
|
586
1057
|
function VoucherCard({ outcome, locale, messages, className }) {
|
|
@@ -617,15 +1088,55 @@ function VoucherCard({ outcome, locale, messages, className }) {
|
|
|
617
1088
|
/* @__PURE__ */ jsxRuntime.jsx("p", { children: m["voucher.email"] })
|
|
618
1089
|
] });
|
|
619
1090
|
}
|
|
620
|
-
function PaymentChallenge({ outcome, locale, messages, render, onCancel }) {
|
|
1091
|
+
function PaymentChallenge({ outcome, locale, messages, render, onCancel, escapeAfterMs = 12e4 }) {
|
|
621
1092
|
const m = storefront.paymentMessages(locale, messages);
|
|
622
|
-
const { ref } = useModalDialog({ open: true, onClose: () =>
|
|
1093
|
+
const { ref } = useModalDialog({ open: true, onClose: () => {
|
|
1094
|
+
} });
|
|
1095
|
+
const [canEscape, setCanEscape] = react.useState(false);
|
|
1096
|
+
const [closed, setClosed] = react.useState(false);
|
|
1097
|
+
const cancelRef = react.useRef(onCancel);
|
|
1098
|
+
cancelRef.current = onCancel;
|
|
1099
|
+
const hasCancel = !!onCancel;
|
|
1100
|
+
react.useEffect(() => {
|
|
1101
|
+
if (!hasCancel) return;
|
|
1102
|
+
const t = setTimeout(() => setCanEscape(true), escapeAfterMs);
|
|
1103
|
+
return () => clearTimeout(t);
|
|
1104
|
+
}, [hasCancel, escapeAfterMs]);
|
|
1105
|
+
react.useEffect(() => {
|
|
1106
|
+
const el = ref.current;
|
|
1107
|
+
if (!el) return;
|
|
1108
|
+
const onClosed = () => setClosed(true);
|
|
1109
|
+
el.addEventListener("close", onClosed);
|
|
1110
|
+
return () => el.removeEventListener("close", onClosed);
|
|
1111
|
+
}, [ref]);
|
|
1112
|
+
const reopen = () => {
|
|
1113
|
+
const el = ref.current;
|
|
1114
|
+
if (!el) return;
|
|
1115
|
+
try {
|
|
1116
|
+
if (typeof el.showModal === "function") el.showModal();
|
|
1117
|
+
else el.setAttribute("open", "");
|
|
1118
|
+
} catch {
|
|
1119
|
+
el.setAttribute("open", "");
|
|
1120
|
+
}
|
|
1121
|
+
setClosed(false);
|
|
1122
|
+
el.querySelector("[data-sf-autofocus]")?.focus({ preventScroll: true });
|
|
1123
|
+
};
|
|
623
1124
|
const url = outcome.challenge?.url;
|
|
624
|
-
return /* @__PURE__ */ jsxRuntime.
|
|
625
|
-
/* @__PURE__ */ jsxRuntime.
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
1125
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1126
|
+
closed && /* @__PURE__ */ jsxRuntime.jsxs("p", { role: "status", "data-sf-challenge-closed": true, children: [
|
|
1127
|
+
m["state.challenge"],
|
|
1128
|
+
" ",
|
|
1129
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: reopen, "data-sf-challenge-reopen": true, style: { minWidth: 44, minHeight: 44 }, children: m["challenge.title"] })
|
|
1130
|
+
] }),
|
|
1131
|
+
/* @__PURE__ */ jsxRuntime.jsx("dialog", { ref, "aria-labelledby": "sf-challenge-title", "data-sf-challenge": true, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-sf-challenge-body": true, children: [
|
|
1132
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { id: "sf-challenge-title", "data-sf-autofocus": true, tabIndex: -1, children: m["challenge.title"] }),
|
|
1133
|
+
render ? render(outcome) : url ? /* @__PURE__ */ jsxRuntime.jsx("iframe", { src: url, title: m["challenge.title"], "data-sf-challenge-frame": true, style: { width: "100%", minHeight: 420, border: 0 } }) : /* @__PURE__ */ jsxRuntime.jsx("p", { children: m["state.challenge"] }),
|
|
1134
|
+
hasCancel && /* @__PURE__ */ jsxRuntime.jsx("div", { "data-sf-challenge-escape": true, "aria-live": "polite", children: canEscape && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1135
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { children: m["challenge.escape_hint"] }),
|
|
1136
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => cancelRef.current?.(), style: { minWidth: 44, minHeight: 44 }, children: m["challenge.escape"] })
|
|
1137
|
+
] }) })
|
|
1138
|
+
] }) })
|
|
1139
|
+
] });
|
|
629
1140
|
}
|
|
630
1141
|
var PaymentBrick = react.forwardRef(function PaymentBrick2(props, ref) {
|
|
631
1142
|
const { submitLabel, hideAmountInButton, renderVoucher, renderChallenge, minHeight = 360, className, style, ...hookOptions } = props;
|
|
@@ -663,7 +1174,7 @@ var PaymentBrick = react.forwardRef(function PaymentBrick2(props, ref) {
|
|
|
663
1174
|
state.status === "network_error" && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => void state.retry(), style: { minHeight: 44 }, children: m["action.retry"] })
|
|
664
1175
|
] }) : /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", "aria-live": "polite", "data-sf-pay-message": true, children: state.message }),
|
|
665
1176
|
outcome?.kind === "voucher" && (renderVoucher ? renderVoucher(outcome) : /* @__PURE__ */ jsxRuntime.jsx(VoucherCard, { outcome, locale, ...props.messages ? { messages: props.messages } : {} })),
|
|
666
|
-
outcome?.kind === "challenge" && /* @__PURE__ */ jsxRuntime.jsx(PaymentChallenge, { outcome, locale, ...props.messages ? { messages: props.messages } : {}, ...renderChallenge ? { render: renderChallenge } : {} }),
|
|
1177
|
+
outcome?.kind === "challenge" && /* @__PURE__ */ jsxRuntime.jsx(PaymentChallenge, { outcome, locale, onCancel: state.cancelChallenge, ...props.messages ? { messages: props.messages } : {}, ...renderChallenge ? { render: renderChallenge } : {} }),
|
|
667
1178
|
submitLabel !== void 0 && showForm && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
668
1179
|
"button",
|
|
669
1180
|
{
|
|
@@ -685,53 +1196,195 @@ var PaymentBrick = react.forwardRef(function PaymentBrick2(props, ref) {
|
|
|
685
1196
|
] });
|
|
686
1197
|
});
|
|
687
1198
|
|
|
688
|
-
// src/payments/
|
|
689
|
-
var
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
1199
|
+
// src/payments/payment-method.messages.ts
|
|
1200
|
+
var ES2 = {
|
|
1201
|
+
legend: "M\xE9todo de pago",
|
|
1202
|
+
pay: "Pagar",
|
|
1203
|
+
placeOrder: "Realizar el pedido",
|
|
1204
|
+
placeOrderCash: "Generar ficha de pago",
|
|
1205
|
+
chooseDelivery: "Elige la entrega para continuar",
|
|
1206
|
+
payRedirect: "Continuar al pago",
|
|
1207
|
+
continue: "Continuar",
|
|
1208
|
+
retryAddress: "Guardar direcci\xF3n y continuar",
|
|
1209
|
+
processing: "Procesando\u2026",
|
|
1210
|
+
redirectNote: "Te llevamos a la p\xE1gina del proveedor para terminar el pago y regresas aqu\xED.",
|
|
1211
|
+
cashPoints: "Elige d\xF3nde pagar en efectivo",
|
|
1212
|
+
cashMore: "Ver m\xE1s puntos de pago",
|
|
1213
|
+
cashNote: "Al hacer el pedido te damos una ficha con la referencia y la fecha l\xEDmite. El pedido se confirma cuando pagas.",
|
|
1214
|
+
secureCard: "Tus datos de tarjeta los captura directamente nuestro proveedor de pagos; la tienda nunca los ve.",
|
|
1215
|
+
pickDeliveryFirst: "Elige primero la entrega para ver el total y pagar con tarjeta.",
|
|
1216
|
+
updatingTotal: "Actualizando el total\u2026",
|
|
1217
|
+
syncFailed: "No pudimos actualizar el total con tu entrega. Puedes continuar al sitio del proveedor para pagar con tarjeta.",
|
|
1218
|
+
quoteChanged: "Las tarifas de env\xEDo se actualizaron. Revisa tu env\xEDo para ver el total.",
|
|
1219
|
+
rebuildCart: "Volver a armar mi carrito",
|
|
1220
|
+
noPaymentTitle: "Por ahora no hay formas de pago disponibles",
|
|
1221
|
+
noPayment: "Intenta de nuevo en unos minutos o escr\xEDbenos para completar tu pedido.",
|
|
1222
|
+
paymentNote: "El pago se procesa de forma segura con nuestro proveedor de pagos.",
|
|
1223
|
+
options: {
|
|
1224
|
+
card: "Tarjeta de cr\xE9dito o d\xE9bito",
|
|
1225
|
+
cash: "Efectivo",
|
|
1226
|
+
wallet: "Cuenta del proveedor de pagos",
|
|
1227
|
+
credits: "Pagos sin tarjeta"
|
|
695
1228
|
}
|
|
696
|
-
status;
|
|
697
|
-
code;
|
|
698
|
-
details;
|
|
699
|
-
name = "PaymentHttpError";
|
|
700
1229
|
};
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
1230
|
+
var EN2 = {
|
|
1231
|
+
legend: "Payment method",
|
|
1232
|
+
pay: "Pay",
|
|
1233
|
+
placeOrder: "Place order",
|
|
1234
|
+
placeOrderCash: "Get payment slip",
|
|
1235
|
+
chooseDelivery: "Choose delivery to continue",
|
|
1236
|
+
payRedirect: "Continue to payment",
|
|
1237
|
+
continue: "Continue",
|
|
1238
|
+
retryAddress: "Save address and continue",
|
|
1239
|
+
processing: "Processing\u2026",
|
|
1240
|
+
redirectNote: "We'll take you to the provider's page to finish paying, then bring you back here.",
|
|
1241
|
+
cashPoints: "Choose where to pay in cash",
|
|
1242
|
+
cashMore: "Show more payment points",
|
|
1243
|
+
cashNote: "When you place the order you get a slip with the reference and the deadline. The order is confirmed once you pay.",
|
|
1244
|
+
secureCard: "Your card details are captured directly by our payment provider; the shop never sees them.",
|
|
1245
|
+
pickDeliveryFirst: "Choose delivery first to see the total and pay by card.",
|
|
1246
|
+
updatingTotal: "Updating the total\u2026",
|
|
1247
|
+
syncFailed: "We couldn't update the total with your delivery. You can continue to the provider's site to pay by card.",
|
|
1248
|
+
quoteChanged: "Shipping rates were updated. Check your delivery to see the total.",
|
|
1249
|
+
rebuildCart: "Rebuild my cart",
|
|
1250
|
+
noPaymentTitle: "No payment methods are available right now",
|
|
1251
|
+
noPayment: "Try again in a few minutes or contact us to complete your order.",
|
|
1252
|
+
paymentNote: "Payment is processed securely by our payment provider.",
|
|
1253
|
+
options: {
|
|
1254
|
+
card: "Credit or debit card",
|
|
1255
|
+
cash: "Cash",
|
|
1256
|
+
wallet: "Payment provider account",
|
|
1257
|
+
credits: "Pay without a card"
|
|
1258
|
+
}
|
|
1259
|
+
};
|
|
1260
|
+
function paymentMethodMessages(locale, overrides) {
|
|
1261
|
+
const base = (locale ?? "es-MX").toLowerCase().startsWith("en") ? EN2 : ES2;
|
|
1262
|
+
if (!overrides) return base;
|
|
1263
|
+
const { options, ...rest } = overrides;
|
|
1264
|
+
return { ...base, ...rest, options: { ...base.options, ...options } };
|
|
1265
|
+
}
|
|
1266
|
+
function alternativeChoices(choices) {
|
|
1267
|
+
return choices.filter((c) => c.mode !== "card");
|
|
1268
|
+
}
|
|
1269
|
+
function pickChoice(form, value) {
|
|
1270
|
+
const input = Array.from(form?.querySelectorAll('input[name="payment_choice"]') ?? []).find((i) => i.value === value);
|
|
1271
|
+
if (!input) return;
|
|
1272
|
+
input.click();
|
|
1273
|
+
input.scrollIntoView?.({ block: "center", behavior: "smooth" });
|
|
1274
|
+
}
|
|
1275
|
+
var INDETERMINATE = /* @__PURE__ */ new Set(["confirming", "verifying", "transient", "in_progress"]);
|
|
1276
|
+
function safeStorage2() {
|
|
704
1277
|
try {
|
|
705
|
-
return
|
|
1278
|
+
return typeof sessionStorage !== "undefined" ? sessionStorage : null;
|
|
706
1279
|
} catch {
|
|
707
1280
|
return null;
|
|
708
1281
|
}
|
|
709
1282
|
}
|
|
710
|
-
function
|
|
711
|
-
const
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
1283
|
+
function usePaymentRecovery(options = {}) {
|
|
1284
|
+
const { scope = "default", formAutoRetries = 2 } = options;
|
|
1285
|
+
const [notice, setNotice] = react.useState(null);
|
|
1286
|
+
const [sectionKey, setSectionKey] = react.useState(0);
|
|
1287
|
+
const formFailures = react.useRef(0);
|
|
1288
|
+
const lastIssue = react.useRef(null);
|
|
1289
|
+
const unknownByEvent = react.useRef(false);
|
|
1290
|
+
const live = react.useRef(options);
|
|
1291
|
+
live.current = options;
|
|
1292
|
+
const onIssue = react.useCallback((issue) => {
|
|
1293
|
+
lastIssue.current = issue;
|
|
1294
|
+
if (!issue) return setNotice((n) => n?.kind === "confirming" ? null : n);
|
|
1295
|
+
if (issue.kind === "confirming" || issue.kind === "verifying") return setNotice({ kind: "confirming" });
|
|
1296
|
+
if (issue.kind === "merchant_config" || issue.kind === "stale_checkout" || issue.kind === "provider_declined") return setNotice({ kind: issue.kind });
|
|
1297
|
+
if (issue.kind === "other") return setNotice({ kind: "other" });
|
|
1298
|
+
setNotice((n) => n?.kind === "confirming" ? null : n);
|
|
1299
|
+
}, []);
|
|
1300
|
+
const retry = react.useCallback(() => {
|
|
1301
|
+
const issue = lastIssue.current;
|
|
1302
|
+
if (unknownByEvent.current || issue && INDETERMINATE.has(issue.kind)) return false;
|
|
1303
|
+
const store = live.current.storage === void 0 ? safeStorage2() : live.current.storage;
|
|
1304
|
+
try {
|
|
1305
|
+
store?.removeItem(`sf:pay:attempt:${live.current.scope ?? "default"}`);
|
|
1306
|
+
} catch {
|
|
718
1307
|
}
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
1308
|
+
formFailures.current = 0;
|
|
1309
|
+
lastIssue.current = null;
|
|
1310
|
+
unknownByEvent.current = false;
|
|
1311
|
+
setNotice(null);
|
|
1312
|
+
setSectionKey((k) => k + 1);
|
|
1313
|
+
return true;
|
|
1314
|
+
}, []);
|
|
1315
|
+
const onEvent = react.useCallback(
|
|
1316
|
+
(e) => {
|
|
1317
|
+
if (e.type === "form_ready") {
|
|
1318
|
+
formFailures.current = 0;
|
|
1319
|
+
setNotice((n) => n?.kind === "form_failed" ? null : n);
|
|
1320
|
+
}
|
|
1321
|
+
if (e.type === "form_failed") {
|
|
1322
|
+
if (formFailures.current < formAutoRetries) {
|
|
1323
|
+
formFailures.current += 1;
|
|
1324
|
+
setSectionKey((k) => k + 1);
|
|
1325
|
+
} else {
|
|
1326
|
+
setNotice({ kind: "form_failed" });
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
if (e.type === "error") unknownByEvent.current = e.kind === "network" || e.kind === "payment_in_progress";
|
|
1330
|
+
if (e.type === "result") {
|
|
1331
|
+
unknownByEvent.current = false;
|
|
1332
|
+
lastIssue.current = null;
|
|
1333
|
+
}
|
|
1334
|
+
if (e.type === "result" && e.status === "rejected") setNotice({ kind: "rejected" });
|
|
1335
|
+
if (e.type === "error" && lastIssue.current?.kind === "store_closed") live.current.onRevalidate?.();
|
|
1336
|
+
},
|
|
1337
|
+
[formAutoRetries]
|
|
1338
|
+
);
|
|
1339
|
+
return react.useMemo(() => ({ notice, setNotice, sectionKey, onIssue, onEvent, lastIssue, retry, clear: () => setNotice(null) }), [notice, sectionKey, onIssue, onEvent, retry]);
|
|
1340
|
+
}
|
|
1341
|
+
function PaymentRecovery(props) {
|
|
1342
|
+
const { notice, alternatives = [], onRetry, onPick, onRefresh, alternativeLabel, classNames: cx = {}, testIds = {}, children } = props;
|
|
1343
|
+
const locale = useResolvedLocale(props.locale);
|
|
1344
|
+
const m = react.useMemo(() => paymentRecoveryMessages(locale, props.messages), [locale, props.messages]);
|
|
1345
|
+
const rowNames = react.useMemo(() => paymentMethodMessages(locale).options, [locale]);
|
|
1346
|
+
const ref = react.useRef(null);
|
|
1347
|
+
const kind = notice?.kind;
|
|
1348
|
+
react.useEffect(() => {
|
|
1349
|
+
if (kind && kind !== "confirming") {
|
|
1350
|
+
ref.current?.focus();
|
|
1351
|
+
ref.current?.scrollIntoView?.({ block: "nearest", behavior: "smooth" });
|
|
722
1352
|
}
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
1353
|
+
}, [kind]);
|
|
1354
|
+
if (!notice) return null;
|
|
1355
|
+
const confirming = notice.kind === "confirming";
|
|
1356
|
+
const canRetry = notice.kind !== "confirming" && notice.kind !== "stale_checkout";
|
|
1357
|
+
const showAlternatives = notice.kind !== "stale_checkout" && !confirming && alternatives.length > 0;
|
|
1358
|
+
const alternativesFirst = notice.kind === "merchant_config";
|
|
1359
|
+
const nameOf = (c) => c.label ?? (c.option ? rowNames[c.option] : void 0) ?? c.option ?? c.methodKey;
|
|
1360
|
+
const labelOf = (c) => alternativeLabel?.(c) ?? m["action.alternative"].replace("{label}", nameOf(c));
|
|
1361
|
+
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;
|
|
1362
|
+
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: [
|
|
1363
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: cx.title, children: /* @__PURE__ */ jsxRuntime.jsx("strong", { children: m[`notice.${notice.kind}.title`] }) }),
|
|
1364
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: cx.body, children: m[`notice.${notice.kind}.body`] }),
|
|
1365
|
+
!confirming ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cx.actions, children: [
|
|
1366
|
+
alternativesFirst ? null : retryButton,
|
|
1367
|
+
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,
|
|
1368
|
+
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,
|
|
1369
|
+
alternativesFirst ? retryButton : null
|
|
1370
|
+
] }) : null,
|
|
1371
|
+
children
|
|
1372
|
+
] });
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
// src/payments/revalidate.ts
|
|
1376
|
+
function checkoutShouldRevalidate(options) {
|
|
1377
|
+
const paths = options.paths.map((p) => p.replace(/\/+$/, "") || "/");
|
|
1378
|
+
return (args) => {
|
|
1379
|
+
const method = args.formMethod?.toUpperCase();
|
|
1380
|
+
if (!method || method === "GET" || !args.formAction) return args.defaultShouldRevalidate;
|
|
1381
|
+
let pathname = args.formAction;
|
|
1382
|
+
try {
|
|
1383
|
+
pathname = new URL(args.formAction, "http://sf.local").pathname;
|
|
1384
|
+
} catch {
|
|
1385
|
+
}
|
|
1386
|
+
pathname = pathname.replace(/\/+$/, "") || "/";
|
|
1387
|
+
return paths.includes(pathname) ? false : args.defaultShouldRevalidate;
|
|
735
1388
|
};
|
|
736
1389
|
}
|
|
737
1390
|
|
|
@@ -794,74 +1447,6 @@ function paymentNonceFromHeaders(headers) {
|
|
|
794
1447
|
return /'nonce-([A-Za-z0-9+/=_-]+)'/.exec(csp)?.[1];
|
|
795
1448
|
}
|
|
796
1449
|
|
|
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
1450
|
// src/checkout/brands/logos.generated.ts
|
|
866
1451
|
var LOGOS = {
|
|
867
1452
|
"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 +1840,15 @@ function PaymentMethodSection(props) {
|
|
|
1255
1840
|
const requote = wantSync && settled && sync.requote === true;
|
|
1256
1841
|
const syncFailed = wantSync && settled && !sync.ok && !requote;
|
|
1257
1842
|
const brickReady = cardActive && deliveryReady && synced && !syncFailed;
|
|
1843
|
+
const [formLatched, setFormLatched] = react.useState(false);
|
|
1844
|
+
react.useEffect(() => {
|
|
1845
|
+
if (brickReady) setFormLatched(true);
|
|
1846
|
+
}, [brickReady]);
|
|
1847
|
+
react.useEffect(() => {
|
|
1848
|
+
if (!cardActive) setFormLatched(false);
|
|
1849
|
+
}, [cardActive]);
|
|
1850
|
+
const showBrick = cardActive && !syncFailed && (formLatched || brickReady);
|
|
1851
|
+
const totalUpdating = showBrick && !brickReady;
|
|
1258
1852
|
const transport = react.useMemo(
|
|
1259
1853
|
() => props.transport ?? createFetchTransport(payUrl, {
|
|
1260
1854
|
...statusUrl ? { refreshUrl: statusUrl } : {},
|
|
@@ -1368,9 +1962,9 @@ function PaymentMethodSection(props) {
|
|
|
1368
1962
|
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
1963
|
] }),
|
|
1370
1964
|
/* @__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: [
|
|
1965
|
+
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
1966
|
fallback ? null : !deliveryReady ? cardNotice("pickDelivery", m.pickDeliveryFirst) : syncFailed ? cardNotice("syncFailed", m.syncFailed) : requote ? cardNotice("requote", m.quoteChanged) : !synced ? cardNotice("syncing", m.updatingTotal) : null,
|
|
1373
|
-
|
|
1967
|
+
showBrick || fallback ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1374
1968
|
PaymentBrick,
|
|
1375
1969
|
{
|
|
1376
1970
|
...brickProps,
|
|
@@ -1382,15 +1976,14 @@ function PaymentMethodSection(props) {
|
|
|
1382
1976
|
locale,
|
|
1383
1977
|
...scope ? { scope } : {},
|
|
1384
1978
|
...payerEmail && !brickProps?.email ? { email: payerEmail } : {},
|
|
1385
|
-
enabled:
|
|
1979
|
+
enabled: showBrick,
|
|
1386
1980
|
...cx.brick ? { className: cx.brick } : {},
|
|
1387
1981
|
onEvent: handleEvent,
|
|
1388
1982
|
onFallback: () => setFallback(true),
|
|
1389
1983
|
onResult: (o) => {
|
|
1390
1984
|
if (o.kind === "approved" || o.kind === "in_review" || o.kind === "voucher") onDone(o);
|
|
1391
1985
|
}
|
|
1392
|
-
}
|
|
1393
|
-
payerEmail || brickProps?.email ? "with-email" : "without-email"
|
|
1986
|
+
}
|
|
1394
1987
|
) : null,
|
|
1395
1988
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: cx.note, children: m.secureCard }),
|
|
1396
1989
|
expired && rebuildHref ? Link({ href: rebuildHref, ...cx.rebuild ? { className: cx.rebuild } : {}, children: m.rebuildCart, ...testIds.rebuild ? { testId: testIds.rebuild } : {} }) : null
|
|
@@ -1409,7 +2002,7 @@ function PaymentMethodSection(props) {
|
|
|
1409
2002
|
onClick: onPayClick,
|
|
1410
2003
|
className: cx.submit,
|
|
1411
2004
|
disabled: busy || blocked,
|
|
1412
|
-
"aria-disabled": blocked ||
|
|
2005
|
+
"aria-disabled": blocked || cardActive && !syncFailed && deliveryReady && (!brickReady || !ready || processing) || void 0,
|
|
1413
2006
|
"aria-busy": processing || void 0,
|
|
1414
2007
|
"data-sf-pay-method-submit": true,
|
|
1415
2008
|
...tid("submit"),
|
|
@@ -1499,8 +2092,6 @@ function rowMarks(choice, cardBrands) {
|
|
|
1499
2092
|
}
|
|
1500
2093
|
return [];
|
|
1501
2094
|
}
|
|
1502
|
-
|
|
1503
|
-
// src/payments/choices.ts
|
|
1504
2095
|
var SEP = "|";
|
|
1505
2096
|
function choiceValue(methodKey, option) {
|
|
1506
2097
|
return option ? `${methodKey}${SEP}${option}` : methodKey;
|
|
@@ -1574,13 +2165,33 @@ function checkoutToAttempt(result) {
|
|
|
1574
2165
|
if (action?.type === "challenge") return { ...base, payment: { provider: payment.provider, status: "requires_action", outcome: "challenge", action } };
|
|
1575
2166
|
return { ...base, payment: { provider: payment.provider, status: "pending", outcome: "pending", action: action ?? { type: "none" } } };
|
|
1576
2167
|
}
|
|
1577
|
-
function
|
|
2168
|
+
function safeHttpStatus(status) {
|
|
2169
|
+
return status === 502 || status === 503 || status === 504 ? 500 : status;
|
|
2170
|
+
}
|
|
2171
|
+
function payErrorResponse(err, options = {}) {
|
|
1578
2172
|
const status = typeof err.status === "number" && err.status >= 400 ? err.status : 503;
|
|
1579
|
-
|
|
2173
|
+
const { kind: classified, retryable } = storefront.classifyPaymentError({ ...err, status, checkoutId: err.checkoutId ?? null });
|
|
2174
|
+
const kind = options.kind ?? classified;
|
|
2175
|
+
let code = err.code ?? (kind === "transient" ? "unavailable" : "server_error");
|
|
1580
2176
|
if (status === 409 && err.field === "payment_in_progress") code = "payment_in_progress";
|
|
1581
2177
|
if (status === 429) code = "too_many_attempts";
|
|
1582
2178
|
if (status === 422 && err.field === "expected_total") code = "price_mismatch";
|
|
1583
|
-
|
|
2179
|
+
const own = (options.ownMessage || kind === "validation" || kind === "expired" || kind === "price_mismatch" || kind === "other" && status < 500) && err.message;
|
|
2180
|
+
const message = own || paymentRecoveryMessages(options.locale, options.messages)[`error.${kind}`];
|
|
2181
|
+
return {
|
|
2182
|
+
status: safeHttpStatus(status),
|
|
2183
|
+
body: {
|
|
2184
|
+
error: {
|
|
2185
|
+
code,
|
|
2186
|
+
message,
|
|
2187
|
+
retryable: options.kind ? options.kind === "transient" || options.kind === "in_progress" || options.kind === "rate_limited" : retryable,
|
|
2188
|
+
kind,
|
|
2189
|
+
...err.field ? { field: err.field } : {},
|
|
2190
|
+
...err.retryAfter ? { retry_after: err.retryAfter } : {},
|
|
2191
|
+
...err.details !== void 0 ? { details: err.details } : {}
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
};
|
|
1584
2195
|
}
|
|
1585
2196
|
function voucherRows(voucher, labels, locale) {
|
|
1586
2197
|
if (!voucher) return [];
|
|
@@ -1606,15 +2217,23 @@ exports.PaymentBrick = PaymentBrick;
|
|
|
1606
2217
|
exports.PaymentChallenge = PaymentChallenge;
|
|
1607
2218
|
exports.PaymentHttpError = PaymentHttpError;
|
|
1608
2219
|
exports.PaymentMethodSection = PaymentMethodSection;
|
|
2220
|
+
exports.PaymentMountCancelled = PaymentMountCancelled;
|
|
2221
|
+
exports.PaymentMountTimeout = PaymentMountTimeout;
|
|
2222
|
+
exports.PaymentRecovery = PaymentRecovery;
|
|
1609
2223
|
exports.VoucherCard = VoucherCard;
|
|
2224
|
+
exports.alternativeChoices = alternativeChoices;
|
|
1610
2225
|
exports.buildPayChoices = buildPayChoices;
|
|
1611
2226
|
exports.cashProviderData = cashProviderData;
|
|
2227
|
+
exports.checkoutShouldRevalidate = checkoutShouldRevalidate;
|
|
1612
2228
|
exports.checkoutToAttempt = checkoutToAttempt;
|
|
1613
2229
|
exports.choiceValue = choiceValue;
|
|
1614
2230
|
exports.createFetchTransport = createFetchTransport;
|
|
2231
|
+
exports.createResilientTransport = createResilientTransport;
|
|
1615
2232
|
exports.formFields = formFields;
|
|
2233
|
+
exports.isResolvedAttempt = isResolvedAttempt;
|
|
1616
2234
|
exports.loadPaymentSdk = loadPaymentSdk;
|
|
1617
2235
|
exports.mercadoPagoBricksAdapter = mercadoPagoBricksAdapter;
|
|
2236
|
+
exports.mountQueued = mountQueued;
|
|
1618
2237
|
exports.parseChoice = parseChoice;
|
|
1619
2238
|
exports.payErrorResponse = payErrorResponse;
|
|
1620
2239
|
exports.paymentCsp = paymentCsp;
|
|
@@ -1624,11 +2243,16 @@ exports.paymentCspMode = paymentCspMode;
|
|
|
1624
2243
|
exports.paymentMethodMessages = paymentMethodMessages;
|
|
1625
2244
|
exports.paymentNonce = paymentNonce;
|
|
1626
2245
|
exports.paymentNonceFromHeaders = paymentNonceFromHeaders;
|
|
2246
|
+
exports.paymentRecoveryMessages = paymentRecoveryMessages;
|
|
2247
|
+
exports.pickChoice = pickChoice;
|
|
1627
2248
|
exports.readPaymentScheme = readPaymentScheme;
|
|
1628
2249
|
exports.readPaymentTheme = readPaymentTheme;
|
|
1629
2250
|
exports.resetPaymentSdkCache = resetPaymentSdkCache;
|
|
2251
|
+
exports.safeHttpStatus = safeHttpStatus;
|
|
1630
2252
|
exports.sanitizeMercadoPagoFormData = sanitizeMercadoPagoFormData;
|
|
1631
2253
|
exports.useEmbeddedPayment = useEmbeddedPayment;
|
|
2254
|
+
exports.usePaymentRecovery = usePaymentRecovery;
|
|
1632
2255
|
exports.voucherRows = voucherRows;
|
|
2256
|
+
exports.withMountQueue = withMountQueue;
|
|
1633
2257
|
//# sourceMappingURL=payments.cjs.map
|
|
1634
2258
|
//# sourceMappingURL=payments.cjs.map
|