@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.js
CHANGED
|
@@ -3,9 +3,10 @@ import { useModalDialog } from './chunk-ZBH7JYU4.js';
|
|
|
3
3
|
import { findBrand, BrandMark } from './chunk-JNUJGPJC.js';
|
|
4
4
|
import { useResolvedLocale } from './chunk-T52H5HL3.js';
|
|
5
5
|
import './chunk-DQBL2DFJ.js';
|
|
6
|
-
|
|
6
|
+
import { paymentRecoveryMessages } from './chunk-MNLROALF.js';
|
|
7
|
+
export { buildPayChoices, cashProviderData, checkoutToAttempt, choiceValue, parseChoice, payErrorResponse, paymentCsp, paymentCspHeaderName, paymentCspHeaders, paymentCspMode, paymentNonce, paymentNonceFromHeaders, paymentRecoveryMessages, safeHttpStatus, voucherRows } from './chunk-MNLROALF.js';
|
|
7
8
|
import { forwardRef, useImperativeHandle, useRef, useEffect, useMemo, useId, useState, useCallback, useSyncExternalStore } from 'react';
|
|
8
|
-
import { paymentMessages, formatMoney, generateIdempotencyKey, paymentOutcome, paymentErrorView, formatVoucherExpiry } from '@asteby/storefront';
|
|
9
|
+
import { paymentMessages, formatMoney, generateIdempotencyKey, paymentOutcome, paymentErrorView, formatVoucherExpiry, classifyPaymentError } from '@asteby/storefront';
|
|
9
10
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
10
11
|
|
|
11
12
|
// src/payments/sdk-loader.ts
|
|
@@ -130,6 +131,13 @@ function mercadoPagoBricksAdapter(options = {}) {
|
|
|
130
131
|
const device = getDevice();
|
|
131
132
|
return { provider_data, ...typeof device === "string" && device ? { device_session_id: device } : {} };
|
|
132
133
|
},
|
|
134
|
+
async update(next) {
|
|
135
|
+
if (gone) throw new Error("payment form is unmounted");
|
|
136
|
+
if (typeof controller.update !== "function") throw new Error("payment form: update not supported");
|
|
137
|
+
const value = Number.parseFloat(next.amount.amount);
|
|
138
|
+
if (!Number.isFinite(value) || value <= 0) throw new Error("payment form: invalid amount");
|
|
139
|
+
await controller.update({ amount: value });
|
|
140
|
+
},
|
|
133
141
|
async unmount() {
|
|
134
142
|
gone = true;
|
|
135
143
|
try {
|
|
@@ -142,6 +150,273 @@ function mercadoPagoBricksAdapter(options = {}) {
|
|
|
142
150
|
};
|
|
143
151
|
}
|
|
144
152
|
|
|
153
|
+
// src/payments/mount-queue.ts
|
|
154
|
+
var PaymentMountCancelled = class extends Error {
|
|
155
|
+
name = "PaymentMountCancelled";
|
|
156
|
+
constructor(message = "payment form: mount cancelled") {
|
|
157
|
+
super(message);
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
var PaymentMountTimeout = class extends Error {
|
|
161
|
+
name = "PaymentMountTimeout";
|
|
162
|
+
constructor(ms) {
|
|
163
|
+
super(`payment form: create timeout after ${ms}ms`);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
var chains = /* @__PURE__ */ new WeakMap();
|
|
167
|
+
var seq = 0;
|
|
168
|
+
function withTimeout(p, ms) {
|
|
169
|
+
return new Promise((resolve, reject) => {
|
|
170
|
+
const t = setTimeout(() => reject(new PaymentMountTimeout(ms)), ms);
|
|
171
|
+
p.then(
|
|
172
|
+
(v) => {
|
|
173
|
+
clearTimeout(t);
|
|
174
|
+
resolve(v);
|
|
175
|
+
},
|
|
176
|
+
(e) => {
|
|
177
|
+
clearTimeout(t);
|
|
178
|
+
reject(e);
|
|
179
|
+
}
|
|
180
|
+
);
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
async function mountQueued(base, opts, queue = {}) {
|
|
184
|
+
const container = opts.container;
|
|
185
|
+
const createTimeoutMs = queue.createTimeoutMs ?? 1e4;
|
|
186
|
+
let chain = chains.get(container);
|
|
187
|
+
if (!chain) {
|
|
188
|
+
chain = { tail: Promise.resolve(), active: null, ticket: 0 };
|
|
189
|
+
chains.set(container, chain);
|
|
190
|
+
}
|
|
191
|
+
const me = chain;
|
|
192
|
+
let release;
|
|
193
|
+
const done = new Promise((r) => release = r);
|
|
194
|
+
const previous = me.tail;
|
|
195
|
+
const ticket = ++me.ticket;
|
|
196
|
+
me.tail = done;
|
|
197
|
+
let released = false;
|
|
198
|
+
const finish = () => {
|
|
199
|
+
if (!released) {
|
|
200
|
+
released = true;
|
|
201
|
+
release();
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
const cancelled = () => opts.signal?.aborted === true || !container.isConnected || me.ticket !== ticket;
|
|
205
|
+
let slot = null;
|
|
206
|
+
try {
|
|
207
|
+
if (me.active) await me.active.teardown();
|
|
208
|
+
await previous;
|
|
209
|
+
if (cancelled()) throw new PaymentMountCancelled();
|
|
210
|
+
slot = container.ownerDocument.createElement("div");
|
|
211
|
+
slot.id = `${container.id || "sf-pay"}-m${++seq}`;
|
|
212
|
+
container.replaceChildren(slot);
|
|
213
|
+
const node = slot;
|
|
214
|
+
const pending2 = base.mount({ ...opts, container: node });
|
|
215
|
+
let handle;
|
|
216
|
+
try {
|
|
217
|
+
handle = await withTimeout(pending2, createTimeoutMs);
|
|
218
|
+
} catch (err) {
|
|
219
|
+
void pending2.then((h) => Promise.resolve(h.unmount()).catch(() => {
|
|
220
|
+
}), () => {
|
|
221
|
+
});
|
|
222
|
+
throw err;
|
|
223
|
+
}
|
|
224
|
+
let torn = null;
|
|
225
|
+
const teardown = () => torn ??= (async () => {
|
|
226
|
+
try {
|
|
227
|
+
await handle.unmount();
|
|
228
|
+
} catch {
|
|
229
|
+
}
|
|
230
|
+
node.remove();
|
|
231
|
+
if (me.active === entry) me.active = null;
|
|
232
|
+
finish();
|
|
233
|
+
})();
|
|
234
|
+
const entry = { teardown };
|
|
235
|
+
me.active = entry;
|
|
236
|
+
if (cancelled() || !node.isConnected) {
|
|
237
|
+
await teardown();
|
|
238
|
+
throw new PaymentMountCancelled();
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
collect: () => handle.collect(),
|
|
242
|
+
...handle.update ? { update: (next) => handle.update(next) } : {},
|
|
243
|
+
unmount: teardown
|
|
244
|
+
};
|
|
245
|
+
} catch (err) {
|
|
246
|
+
slot?.remove();
|
|
247
|
+
finish();
|
|
248
|
+
throw err;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
function withMountQueue(adapter, queue = {}) {
|
|
252
|
+
return { kind: adapter.kind, load: (client, options) => adapter.load(client, options), mount: (opts) => mountQueued(adapter, opts, queue) };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// src/payments/adapter.ts
|
|
256
|
+
var PaymentHttpError = class extends Error {
|
|
257
|
+
constructor(status, code, message, details, extra) {
|
|
258
|
+
super(message ?? code);
|
|
259
|
+
this.status = status;
|
|
260
|
+
this.code = code;
|
|
261
|
+
this.details = details;
|
|
262
|
+
this.retryable = extra?.retryable;
|
|
263
|
+
this.kind = extra?.kind;
|
|
264
|
+
}
|
|
265
|
+
status;
|
|
266
|
+
code;
|
|
267
|
+
details;
|
|
268
|
+
name = "PaymentHttpError";
|
|
269
|
+
/** `error.retryable` of the shop body: `true` repeat with the SAME key; `false` it is an answer. `undefined`: not said. */
|
|
270
|
+
retryable;
|
|
271
|
+
/** `error.kind` of the shop body (`classifyPaymentError`). */
|
|
272
|
+
kind;
|
|
273
|
+
};
|
|
274
|
+
async function parse(res) {
|
|
275
|
+
const text = await res.text();
|
|
276
|
+
if (!text) return null;
|
|
277
|
+
try {
|
|
278
|
+
return JSON.parse(text);
|
|
279
|
+
} catch {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
function createFetchTransport(url, options = {}) {
|
|
284
|
+
const doFetch = (u, init) => (options.fetch ?? globalThis.fetch.bind(globalThis))(u, init);
|
|
285
|
+
async function call(u, init) {
|
|
286
|
+
const res = await doFetch(u, { credentials: options.credentials ?? "same-origin", ...init });
|
|
287
|
+
const body = await parse(res);
|
|
288
|
+
if (!res.ok) {
|
|
289
|
+
const e = body?.error;
|
|
290
|
+
throw new PaymentHttpError(res.status, e?.code ?? "server_error", e?.message, e?.details, {
|
|
291
|
+
retryable: typeof e?.retryable === "boolean" ? e.retryable : void 0,
|
|
292
|
+
kind: typeof e?.kind === "string" ? e.kind : void 0
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
const result = body;
|
|
296
|
+
if (!result || typeof result !== "object" || !result.payment || typeof result.payment.status !== "string") {
|
|
297
|
+
throw new PaymentHttpError(502, "invalid_response", "Unexpected payment response");
|
|
298
|
+
}
|
|
299
|
+
return result;
|
|
300
|
+
}
|
|
301
|
+
return {
|
|
302
|
+
submit: (request, { signal }) => call(url, {
|
|
303
|
+
method: "POST",
|
|
304
|
+
signal,
|
|
305
|
+
headers: { "Content-Type": "application/json", Accept: "application/json", "Idempotency-Key": request.idempotencyKey, ...options.headers },
|
|
306
|
+
body: JSON.stringify(options.buildBody ? options.buildBody(request) : request)
|
|
307
|
+
}),
|
|
308
|
+
...options.refreshUrl ? {
|
|
309
|
+
refresh: ({ signal }) => call(options.refreshUrl, { method: "POST", signal, headers: { Accept: "application/json", ...options.headers } })
|
|
310
|
+
} : {}
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// src/payments/resilient-transport.ts
|
|
315
|
+
var realSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
316
|
+
function isResolvedAttempt(result) {
|
|
317
|
+
const p = result.payment;
|
|
318
|
+
if (p.status === "paid" || p.status === "failed" || p.status === "expired" || p.status === "cancelled") return true;
|
|
319
|
+
const type = p.action?.type;
|
|
320
|
+
return type === "voucher" || type === "challenge" || type === "redirect";
|
|
321
|
+
}
|
|
322
|
+
async function readJson(res) {
|
|
323
|
+
const text = await res.text().catch(() => "");
|
|
324
|
+
if (!text) return null;
|
|
325
|
+
try {
|
|
326
|
+
return JSON.parse(text);
|
|
327
|
+
} catch {
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
function createResilientTransport(opts) {
|
|
332
|
+
const doFetch = opts.fetch ?? ((u, i) => globalThis.fetch(u, i));
|
|
333
|
+
const sleep2 = opts.sleep ?? realSleep;
|
|
334
|
+
const retries = opts.retries ?? 2;
|
|
335
|
+
const backoff = opts.backoffMs ?? [800, 2e3];
|
|
336
|
+
const polls = opts.statusPolls ?? 3;
|
|
337
|
+
const pollMs = opts.statusPollMs ?? 2e3;
|
|
338
|
+
const msgs = paymentRecoveryMessages(opts.locale, opts.messages);
|
|
339
|
+
const text = (k) => msgs[`error.${k}`];
|
|
340
|
+
const issue = (i) => opts.onIssue?.(i);
|
|
341
|
+
const confirming = msgs["notice.confirming.title"];
|
|
342
|
+
async function call(url, init) {
|
|
343
|
+
let res;
|
|
344
|
+
try {
|
|
345
|
+
res = await doFetch(url, { credentials: "same-origin", ...init, headers: { ...opts.headers, ...init.headers } });
|
|
346
|
+
} catch (err) {
|
|
347
|
+
if (err?.name === "AbortError") throw err;
|
|
348
|
+
return { fail: { kind: "transient", status: 0, code: "network", message: text("transient"), retryable: true } };
|
|
349
|
+
}
|
|
350
|
+
const body = await readJson(res);
|
|
351
|
+
if (res.ok) {
|
|
352
|
+
const r = body;
|
|
353
|
+
if (r && typeof r === "object" && r.payment && typeof r.payment.status === "string") return { ok: r };
|
|
354
|
+
return { fail: { kind: "transient", status: 502, code: "invalid_response", message: text("transient"), retryable: true } };
|
|
355
|
+
}
|
|
356
|
+
const e = body?.error;
|
|
357
|
+
if (!e) {
|
|
358
|
+
const info2 = classifyPaymentError({ status: res.status >= 500 || res.status === 0 ? 503 : res.status });
|
|
359
|
+
return { fail: { kind: info2.kind, status: res.status, code: "server_error", message: text(info2.kind), retryable: info2.retryable } };
|
|
360
|
+
}
|
|
361
|
+
const byCode = classifyPaymentError({ status: res.status, code: e.code ?? null, field: e.field ?? null, retryable: typeof e.retryable === "boolean" ? e.retryable : null, checkoutId: e.checkout_id ?? null, details: e.details });
|
|
362
|
+
const info = e.kind && !(e.kind === "transient" && !byCode.retryable) ? { kind: e.kind, retryable: e.retryable === true || byCode.retryable } : byCode;
|
|
363
|
+
return { fail: { kind: info.kind, status: res.status, code: e.code ?? "server_error", message: e.message || text(info.kind), retryable: info.retryable, details: e.details } };
|
|
364
|
+
}
|
|
365
|
+
const toError = (f) => f.kind === "transient" ? new PaymentHttpError(503, "network", f.message, f.details, { retryable: true, kind: "transient" }) : new PaymentHttpError(f.status || 500, f.code, f.message, f.details, { retryable: f.retryable, kind: f.kind });
|
|
366
|
+
async function resolveByStatus(signal) {
|
|
367
|
+
for (let i = 0; i < polls; i++) {
|
|
368
|
+
const r = await call(opts.statusUrl, { method: "POST", signal, headers: { Accept: "application/json" } });
|
|
369
|
+
if ("ok" in r) {
|
|
370
|
+
if (isResolvedAttempt(r.ok)) return r.ok;
|
|
371
|
+
} else if (!r.fail.retryable) {
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
if (i < polls - 1) await sleep2(pollMs);
|
|
375
|
+
}
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
return {
|
|
379
|
+
async submit(request, { signal }) {
|
|
380
|
+
const init = {
|
|
381
|
+
method: "POST",
|
|
382
|
+
signal,
|
|
383
|
+
headers: { "Content-Type": "application/json", Accept: "application/json", "Idempotency-Key": request.idempotencyKey },
|
|
384
|
+
body: JSON.stringify({ ...request, fields: opts.fields() })
|
|
385
|
+
};
|
|
386
|
+
issue(null);
|
|
387
|
+
let last = null;
|
|
388
|
+
for (let n = 0; n <= retries; n++) {
|
|
389
|
+
const r = await call(opts.payUrl, init);
|
|
390
|
+
if ("ok" in r) {
|
|
391
|
+
issue(null);
|
|
392
|
+
return r.ok;
|
|
393
|
+
}
|
|
394
|
+
last = r.fail;
|
|
395
|
+
const waitable = last.retryable && (last.kind === "transient" || last.kind === "in_progress");
|
|
396
|
+
if (!waitable || n === retries) break;
|
|
397
|
+
issue({ kind: "confirming", message: confirming });
|
|
398
|
+
await sleep2(backoff[Math.min(n, backoff.length - 1)] ?? 800);
|
|
399
|
+
}
|
|
400
|
+
const failure = last;
|
|
401
|
+
if (failure.retryable && (failure.kind === "transient" || failure.kind === "in_progress") || failure.kind === "stale_checkout") {
|
|
402
|
+
issue({ kind: "verifying", message: confirming });
|
|
403
|
+
const resolved = await resolveByStatus(signal);
|
|
404
|
+
if (resolved) {
|
|
405
|
+
issue(null);
|
|
406
|
+
return resolved;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
issue({ kind: failure.kind, message: failure.message, retryable: failure.retryable, status: failure.status, code: failure.code });
|
|
410
|
+
throw toError(failure);
|
|
411
|
+
},
|
|
412
|
+
refresh: async ({ signal }) => {
|
|
413
|
+
const r = await call(opts.statusUrl, { method: "POST", signal, headers: { Accept: "application/json" } });
|
|
414
|
+
if ("ok" in r) return r.ok;
|
|
415
|
+
throw toError(r.fail);
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
|
|
145
420
|
// src/payments/theme.ts
|
|
146
421
|
var PAYMENT_THEME_TOKENS = {
|
|
147
422
|
"--sf-pay-text": "textPrimaryColor",
|
|
@@ -211,6 +486,8 @@ function useEmbeddedPayment(options) {
|
|
|
211
486
|
retryDelayMs = 600,
|
|
212
487
|
pollIntervalMs = 2e3,
|
|
213
488
|
pollTimeoutMs = 6e4,
|
|
489
|
+
verifyTimeoutMs = 12e4,
|
|
490
|
+
verifyIntervalMs = 2e3,
|
|
214
491
|
scope = "default"
|
|
215
492
|
} = options;
|
|
216
493
|
const msgs = useMemo(() => paymentMessages(locale, options.messages), [locale, options.messages]);
|
|
@@ -224,15 +501,27 @@ function useEmbeddedPayment(options) {
|
|
|
224
501
|
const [attemptId, setAttemptId] = useState(null);
|
|
225
502
|
const [container, setContainer] = useState(null);
|
|
226
503
|
const [mountKey, setMountKey] = useState(0);
|
|
504
|
+
const [verifying, setVerifying] = useState(false);
|
|
227
505
|
const live = useRef({ ...options, msgs });
|
|
228
506
|
live.current = { ...options, msgs };
|
|
229
507
|
const form = useRef(null);
|
|
230
508
|
const inFlight = useRef(null);
|
|
231
509
|
const pendingData = useRef(null);
|
|
510
|
+
const memAttempt = useRef(null);
|
|
511
|
+
const memOnly = useRef(false);
|
|
512
|
+
const errorRef = useRef(null);
|
|
513
|
+
const retryRef = useRef(() => Promise.resolve());
|
|
232
514
|
const alive = useRef(true);
|
|
233
515
|
const generation = useRef(0);
|
|
234
516
|
const statusRef = useRef("idle");
|
|
235
517
|
statusRef.current = status;
|
|
518
|
+
errorRef.current = error;
|
|
519
|
+
const amountKey = `${amount.amount}|${amount.currency_code}`;
|
|
520
|
+
const amountKeyRef = useRef(amountKey);
|
|
521
|
+
amountKeyRef.current = amountKey;
|
|
522
|
+
const appliedAmount = useRef(null);
|
|
523
|
+
const updating = useRef(null);
|
|
524
|
+
const remountRequested = useRef(false);
|
|
236
525
|
useEffect(() => {
|
|
237
526
|
alive.current = true;
|
|
238
527
|
return () => {
|
|
@@ -243,19 +532,33 @@ function useEmbeddedPayment(options) {
|
|
|
243
532
|
const store = options.storage === void 0 ? safeStorage() : options.storage;
|
|
244
533
|
const emit = useCallback((e) => live.current.onEvent?.(e), []);
|
|
245
534
|
const readAttempt = useCallback(() => {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
535
|
+
if (!memOnly.current) {
|
|
536
|
+
try {
|
|
537
|
+
const saved = store?.getItem(storageKey);
|
|
538
|
+
if (saved) {
|
|
539
|
+
memAttempt.current = saved;
|
|
540
|
+
return saved;
|
|
541
|
+
}
|
|
542
|
+
} catch {
|
|
543
|
+
memOnly.current = true;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
if (memOnly.current && memAttempt.current) return memAttempt.current;
|
|
547
|
+
if (!store) {
|
|
548
|
+
if (memAttempt.current) return memAttempt.current;
|
|
250
549
|
}
|
|
251
550
|
const fresh = (live.current.newAttemptId ?? generateIdempotencyKey)();
|
|
551
|
+
memAttempt.current = fresh;
|
|
252
552
|
try {
|
|
253
553
|
store?.setItem(storageKey, fresh);
|
|
554
|
+
if (store && store.getItem(storageKey) !== fresh) memOnly.current = true;
|
|
254
555
|
} catch {
|
|
556
|
+
memOnly.current = true;
|
|
255
557
|
}
|
|
256
558
|
return fresh;
|
|
257
559
|
}, [store, storageKey]);
|
|
258
560
|
const rotateAttempt = useCallback(() => {
|
|
561
|
+
memAttempt.current = null;
|
|
259
562
|
try {
|
|
260
563
|
store?.removeItem(storageKey);
|
|
261
564
|
} catch {
|
|
@@ -271,7 +574,32 @@ function useEmbeddedPayment(options) {
|
|
|
271
574
|
},
|
|
272
575
|
[emit]
|
|
273
576
|
);
|
|
274
|
-
const
|
|
577
|
+
const syncAmount = useCallback(() => {
|
|
578
|
+
const m = form.current;
|
|
579
|
+
if (!m || inFlight.current || updating.current || remountRequested.current) return;
|
|
580
|
+
if (statusRef.current === "network_error" || statusRef.current === "blocked" && errorRef.current?.kind === "payment_in_progress") return;
|
|
581
|
+
if (appliedAmount.current === amountKeyRef.current) return;
|
|
582
|
+
const run = (async () => {
|
|
583
|
+
while (form.current === m && alive.current && appliedAmount.current !== amountKeyRef.current) {
|
|
584
|
+
const key = amountKeyRef.current;
|
|
585
|
+
try {
|
|
586
|
+
if (!m.update) throw new Error("payment form: update not supported");
|
|
587
|
+
await m.update({ amount: live.current.amount });
|
|
588
|
+
appliedAmount.current = key;
|
|
589
|
+
} catch {
|
|
590
|
+
if (form.current === m && alive.current) {
|
|
591
|
+
remountRequested.current = true;
|
|
592
|
+
setMountKey((k) => k + 1);
|
|
593
|
+
}
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
})().finally(() => {
|
|
598
|
+
updating.current = null;
|
|
599
|
+
if (alive.current) syncAmount();
|
|
600
|
+
});
|
|
601
|
+
updating.current = run;
|
|
602
|
+
}, []);
|
|
275
603
|
const kind = client?.kind;
|
|
276
604
|
const publicKey = client?.public_key;
|
|
277
605
|
useEffect(() => {
|
|
@@ -285,18 +613,22 @@ function useEmbeddedPayment(options) {
|
|
|
285
613
|
return;
|
|
286
614
|
}
|
|
287
615
|
if (!container) return;
|
|
616
|
+
remountRequested.current = false;
|
|
288
617
|
const my = ++generation.current;
|
|
289
618
|
const started = Date.now();
|
|
619
|
+
const abort = new AbortController();
|
|
290
620
|
let mounted = null;
|
|
291
621
|
setStatus("loading");
|
|
292
622
|
const stale = () => my !== generation.current || !alive.current;
|
|
293
623
|
const fail = (reason) => {
|
|
294
624
|
if (stale()) return;
|
|
295
625
|
generation.current++;
|
|
626
|
+
abort.abort();
|
|
296
627
|
clearTimeout(timer);
|
|
297
628
|
const m = mounted ?? form.current;
|
|
298
629
|
mounted = null;
|
|
299
630
|
form.current = null;
|
|
631
|
+
appliedAmount.current = null;
|
|
300
632
|
if (m) void Promise.resolve(m.unmount());
|
|
301
633
|
fallback(reason);
|
|
302
634
|
};
|
|
@@ -310,31 +642,39 @@ function useEmbeddedPayment(options) {
|
|
|
310
642
|
if (stale()) return;
|
|
311
643
|
const theme = live.current.theme ?? readPaymentTheme(container);
|
|
312
644
|
const colorScheme = live.current.colorScheme ?? readPaymentScheme(container);
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
645
|
+
const createdWith = amountKeyRef.current;
|
|
646
|
+
const m = await mountQueued(
|
|
647
|
+
adapter,
|
|
648
|
+
{
|
|
649
|
+
container,
|
|
650
|
+
client,
|
|
651
|
+
amount: live.current.amount,
|
|
652
|
+
...live.current.email ? { email: live.current.email } : {},
|
|
653
|
+
...live.current.locale ? { locale: live.current.locale } : {},
|
|
654
|
+
theme,
|
|
655
|
+
...colorScheme ? { colorScheme } : {},
|
|
656
|
+
signal: abort.signal,
|
|
657
|
+
onReady: () => {
|
|
658
|
+
if (stale()) return;
|
|
659
|
+
clearTimeout(timer);
|
|
660
|
+
setStatus((s) => s === "loading" ? "ready" : s);
|
|
661
|
+
emit({ type: "form_ready", ms: Date.now() - started });
|
|
662
|
+
},
|
|
663
|
+
onError: (e) => {
|
|
664
|
+
if (stale()) return;
|
|
665
|
+
if (e.critical) fail("form_error");
|
|
666
|
+
}
|
|
326
667
|
},
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
if (e.critical) fail("form_error");
|
|
330
|
-
}
|
|
331
|
-
});
|
|
668
|
+
{ createTimeoutMs: live.current.createTimeoutMs ?? sdkTimeoutMs }
|
|
669
|
+
);
|
|
332
670
|
if (stale()) {
|
|
333
671
|
void Promise.resolve(m.unmount());
|
|
334
672
|
return;
|
|
335
673
|
}
|
|
336
674
|
mounted = m;
|
|
337
675
|
form.current = m;
|
|
676
|
+
appliedAmount.current = createdWith;
|
|
677
|
+
syncAmount();
|
|
338
678
|
} catch (err) {
|
|
339
679
|
fail(err instanceof Error && /timeout/i.test(err.message) ? "sdk_timeout" : "sdk_error");
|
|
340
680
|
}
|
|
@@ -342,12 +682,17 @@ function useEmbeddedPayment(options) {
|
|
|
342
682
|
return () => {
|
|
343
683
|
clearTimeout(timer);
|
|
344
684
|
generation.current++;
|
|
685
|
+
abort.abort();
|
|
345
686
|
const m = mounted ?? form.current;
|
|
346
687
|
mounted = null;
|
|
347
688
|
form.current = null;
|
|
689
|
+
appliedAmount.current = null;
|
|
348
690
|
if (m) void Promise.resolve(m.unmount());
|
|
349
691
|
};
|
|
350
|
-
}, [enabled, container, kind, publicKey,
|
|
692
|
+
}, [enabled, container, kind, publicKey, mountKey, sdkTimeoutMs, containerId]);
|
|
693
|
+
useEffect(() => {
|
|
694
|
+
syncAmount();
|
|
695
|
+
}, [amountKey, syncAmount]);
|
|
351
696
|
const finish = useCallback(
|
|
352
697
|
(result) => {
|
|
353
698
|
const o = paymentOutcome(result, live.current.locale, live.current.messages);
|
|
@@ -371,7 +716,7 @@ function useEmbeddedPayment(options) {
|
|
|
371
716
|
[emit, rotateAttempt]
|
|
372
717
|
);
|
|
373
718
|
const send = useCallback(
|
|
374
|
-
async (data, attempt) => {
|
|
719
|
+
async (data, attempt, total) => {
|
|
375
720
|
const ctl = new AbortController();
|
|
376
721
|
const maxRetries = live.current.autoRetries ?? autoRetries;
|
|
377
722
|
for (let n = 0; ; n++) {
|
|
@@ -383,7 +728,7 @@ function useEmbeddedPayment(options) {
|
|
|
383
728
|
idempotencyKey: attempt,
|
|
384
729
|
provider_data: data.provider_data,
|
|
385
730
|
...data.device_session_id ? { device_session_id: data.device_session_id } : {},
|
|
386
|
-
expected_total:
|
|
731
|
+
expected_total: total
|
|
387
732
|
},
|
|
388
733
|
{ signal: ctl.signal }
|
|
389
734
|
);
|
|
@@ -401,7 +746,7 @@ function useEmbeddedPayment(options) {
|
|
|
401
746
|
emit({ type: "error", kind: view.kind });
|
|
402
747
|
setError(view);
|
|
403
748
|
if (view.kind === "network") {
|
|
404
|
-
pendingData.current = data;
|
|
749
|
+
pendingData.current = { data, total };
|
|
405
750
|
setStatus("network_error");
|
|
406
751
|
} else if (view.kind === "price_mismatch") {
|
|
407
752
|
pendingData.current = null;
|
|
@@ -427,7 +772,11 @@ function useEmbeddedPayment(options) {
|
|
|
427
772
|
if (inFlight.current) return inFlight.current;
|
|
428
773
|
const m = form.current;
|
|
429
774
|
if (!m || statusRef.current !== "ready" && statusRef.current !== "network_error") return Promise.resolve();
|
|
775
|
+
if (statusRef.current === "network_error" && pendingData.current) return retryRef.current();
|
|
776
|
+
const total = live.current.amount;
|
|
430
777
|
const run = (async () => {
|
|
778
|
+
if (updating.current) await updating.current.catch(() => {
|
|
779
|
+
});
|
|
431
780
|
setStatus("processing");
|
|
432
781
|
setError(null);
|
|
433
782
|
setOutcome(null);
|
|
@@ -443,30 +792,33 @@ function useEmbeddedPayment(options) {
|
|
|
443
792
|
}
|
|
444
793
|
const attempt = readAttempt();
|
|
445
794
|
setAttemptId(attempt);
|
|
446
|
-
await send(data, attempt);
|
|
795
|
+
await send(data, attempt, total);
|
|
447
796
|
})().finally(() => {
|
|
448
797
|
inFlight.current = null;
|
|
798
|
+
syncAmount();
|
|
449
799
|
});
|
|
450
800
|
inFlight.current = run;
|
|
451
801
|
return run;
|
|
452
|
-
}, [emit, readAttempt, send]);
|
|
802
|
+
}, [emit, readAttempt, send, syncAmount]);
|
|
453
803
|
const retry = useCallback(() => {
|
|
454
804
|
if (inFlight.current) return inFlight.current;
|
|
455
|
-
const
|
|
456
|
-
if (!
|
|
805
|
+
const pending2 = pendingData.current;
|
|
806
|
+
if (!pending2) return submit();
|
|
457
807
|
emit({ type: "retry" });
|
|
458
808
|
const run = (async () => {
|
|
459
809
|
setStatus("processing");
|
|
460
810
|
setError(null);
|
|
461
811
|
const attempt = readAttempt();
|
|
462
812
|
setAttemptId(attempt);
|
|
463
|
-
await send(data, attempt);
|
|
813
|
+
await send(pending2.data, attempt, pending2.total);
|
|
464
814
|
})().finally(() => {
|
|
465
815
|
inFlight.current = null;
|
|
816
|
+
syncAmount();
|
|
466
817
|
});
|
|
467
818
|
inFlight.current = run;
|
|
468
819
|
return run;
|
|
469
|
-
}, [emit, readAttempt, send, submit]);
|
|
820
|
+
}, [emit, readAttempt, send, submit, syncAmount]);
|
|
821
|
+
retryRef.current = retry;
|
|
470
822
|
useEffect(() => {
|
|
471
823
|
if (status !== "challenge" && status !== "in_review" || !transport.refresh) return;
|
|
472
824
|
let stop = false;
|
|
@@ -479,6 +831,7 @@ function useEmbeddedPayment(options) {
|
|
|
479
831
|
try {
|
|
480
832
|
const r = await transport.refresh({ signal: ctl.signal });
|
|
481
833
|
if (stop || !alive.current) return;
|
|
834
|
+
if (r.payment.status === "none") continue;
|
|
482
835
|
const o = paymentOutcome(r, live.current.locale, live.current.messages);
|
|
483
836
|
if (o.kind !== status) {
|
|
484
837
|
finish(r);
|
|
@@ -493,6 +846,44 @@ function useEmbeddedPayment(options) {
|
|
|
493
846
|
ctl.abort();
|
|
494
847
|
};
|
|
495
848
|
}, [status, transport, pollIntervalMs, pollTimeoutMs, finish]);
|
|
849
|
+
const unknownOutcome = status === "network_error" || status === "blocked" && error?.kind === "payment_in_progress";
|
|
850
|
+
useEffect(() => {
|
|
851
|
+
if (!unknownOutcome) syncAmount();
|
|
852
|
+
}, [unknownOutcome, syncAmount]);
|
|
853
|
+
useEffect(() => {
|
|
854
|
+
if (!unknownOutcome || !transport.refresh) {
|
|
855
|
+
setVerifying(false);
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
let stop = false;
|
|
859
|
+
const ctl = new AbortController();
|
|
860
|
+
const until = Date.now() + verifyTimeoutMs;
|
|
861
|
+
setVerifying(true);
|
|
862
|
+
(async () => {
|
|
863
|
+
let wait = verifyIntervalMs;
|
|
864
|
+
while (!stop && Date.now() < until) {
|
|
865
|
+
await sleep(wait);
|
|
866
|
+
wait = Math.min(wait * 2, 15e3);
|
|
867
|
+
if (stop || !alive.current) return;
|
|
868
|
+
try {
|
|
869
|
+
const r = await transport.refresh({ signal: ctl.signal });
|
|
870
|
+
if (stop || !alive.current) return;
|
|
871
|
+
if (isResolvedAttempt(r)) {
|
|
872
|
+
setVerifying(false);
|
|
873
|
+
finish(r);
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
} catch {
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
if (!stop && alive.current) setVerifying(false);
|
|
880
|
+
})();
|
|
881
|
+
return () => {
|
|
882
|
+
stop = true;
|
|
883
|
+
ctl.abort();
|
|
884
|
+
setVerifying(false);
|
|
885
|
+
};
|
|
886
|
+
}, [unknownOutcome, transport, verifyTimeoutMs, verifyIntervalMs, finish]);
|
|
496
887
|
const dismiss = useCallback(() => {
|
|
497
888
|
setError(null);
|
|
498
889
|
setOutcome((o) => o?.kind === "rejected" ? null : o);
|
|
@@ -507,6 +898,7 @@ function useEmbeddedPayment(options) {
|
|
|
507
898
|
attemptsLeft,
|
|
508
899
|
attemptId,
|
|
509
900
|
busy: status === "loading" || status === "processing",
|
|
901
|
+
verifying,
|
|
510
902
|
containerRef: setContainer,
|
|
511
903
|
containerId,
|
|
512
904
|
submit,
|
|
@@ -616,56 +1008,6 @@ var PaymentBrick = forwardRef(function PaymentBrick2(props, ref) {
|
|
|
616
1008
|
] });
|
|
617
1009
|
});
|
|
618
1010
|
|
|
619
|
-
// src/payments/adapter.ts
|
|
620
|
-
var PaymentHttpError = class extends Error {
|
|
621
|
-
constructor(status, code, message, details) {
|
|
622
|
-
super(message ?? code);
|
|
623
|
-
this.status = status;
|
|
624
|
-
this.code = code;
|
|
625
|
-
this.details = details;
|
|
626
|
-
}
|
|
627
|
-
status;
|
|
628
|
-
code;
|
|
629
|
-
details;
|
|
630
|
-
name = "PaymentHttpError";
|
|
631
|
-
};
|
|
632
|
-
async function parse(res) {
|
|
633
|
-
const text = await res.text();
|
|
634
|
-
if (!text) return null;
|
|
635
|
-
try {
|
|
636
|
-
return JSON.parse(text);
|
|
637
|
-
} catch {
|
|
638
|
-
return null;
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
function createFetchTransport(url, options = {}) {
|
|
642
|
-
const doFetch = (u, init) => (options.fetch ?? globalThis.fetch.bind(globalThis))(u, init);
|
|
643
|
-
async function call(u, init) {
|
|
644
|
-
const res = await doFetch(u, { credentials: options.credentials ?? "same-origin", ...init });
|
|
645
|
-
const body = await parse(res);
|
|
646
|
-
if (!res.ok) {
|
|
647
|
-
const e = body?.error;
|
|
648
|
-
throw new PaymentHttpError(res.status, e?.code ?? "server_error", e?.message, e?.details);
|
|
649
|
-
}
|
|
650
|
-
const result = body;
|
|
651
|
-
if (!result || typeof result !== "object" || !result.payment || typeof result.payment.status !== "string") {
|
|
652
|
-
throw new PaymentHttpError(502, "invalid_response", "Unexpected payment response");
|
|
653
|
-
}
|
|
654
|
-
return result;
|
|
655
|
-
}
|
|
656
|
-
return {
|
|
657
|
-
submit: (request, { signal }) => call(url, {
|
|
658
|
-
method: "POST",
|
|
659
|
-
signal,
|
|
660
|
-
headers: { "Content-Type": "application/json", Accept: "application/json", "Idempotency-Key": request.idempotencyKey, ...options.headers },
|
|
661
|
-
body: JSON.stringify(options.buildBody ? options.buildBody(request) : request)
|
|
662
|
-
}),
|
|
663
|
-
...options.refreshUrl ? {
|
|
664
|
-
refresh: ({ signal }) => call(options.refreshUrl, { method: "POST", signal, headers: { Accept: "application/json", ...options.headers } })
|
|
665
|
-
} : {}
|
|
666
|
-
};
|
|
667
|
-
}
|
|
668
|
-
|
|
669
1011
|
// src/payments/payment-method.messages.ts
|
|
670
1012
|
var ES = {
|
|
671
1013
|
legend: "M\xE9todo de pago",
|
|
@@ -733,6 +1075,130 @@ function paymentMethodMessages(locale, overrides) {
|
|
|
733
1075
|
const { options, ...rest } = overrides;
|
|
734
1076
|
return { ...base, ...rest, options: { ...base.options, ...options } };
|
|
735
1077
|
}
|
|
1078
|
+
function alternativeChoices(choices) {
|
|
1079
|
+
return choices.filter((c) => c.mode !== "card");
|
|
1080
|
+
}
|
|
1081
|
+
function pickChoice(form, value) {
|
|
1082
|
+
const input = Array.from(form?.querySelectorAll('input[name="payment_choice"]') ?? []).find((i) => i.value === value);
|
|
1083
|
+
if (!input) return;
|
|
1084
|
+
input.click();
|
|
1085
|
+
input.scrollIntoView?.({ block: "center", behavior: "smooth" });
|
|
1086
|
+
}
|
|
1087
|
+
var INDETERMINATE = /* @__PURE__ */ new Set(["confirming", "verifying", "transient", "in_progress"]);
|
|
1088
|
+
function safeStorage2() {
|
|
1089
|
+
try {
|
|
1090
|
+
return typeof sessionStorage !== "undefined" ? sessionStorage : null;
|
|
1091
|
+
} catch {
|
|
1092
|
+
return null;
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
function usePaymentRecovery(options = {}) {
|
|
1096
|
+
const { scope = "default", formAutoRetries = 2 } = options;
|
|
1097
|
+
const [notice, setNotice] = useState(null);
|
|
1098
|
+
const [sectionKey, setSectionKey] = useState(0);
|
|
1099
|
+
const formFailures = useRef(0);
|
|
1100
|
+
const lastIssue = useRef(null);
|
|
1101
|
+
const unknownByEvent = useRef(false);
|
|
1102
|
+
const live = useRef(options);
|
|
1103
|
+
live.current = options;
|
|
1104
|
+
const onIssue = useCallback((issue) => {
|
|
1105
|
+
lastIssue.current = issue;
|
|
1106
|
+
if (!issue) return setNotice((n) => n?.kind === "confirming" ? null : n);
|
|
1107
|
+
if (issue.kind === "confirming" || issue.kind === "verifying") return setNotice({ kind: "confirming" });
|
|
1108
|
+
if (issue.kind === "merchant_config" || issue.kind === "stale_checkout" || issue.kind === "provider_declined") return setNotice({ kind: issue.kind });
|
|
1109
|
+
if (issue.kind === "other") return setNotice({ kind: "other" });
|
|
1110
|
+
setNotice((n) => n?.kind === "confirming" ? null : n);
|
|
1111
|
+
}, []);
|
|
1112
|
+
const retry = useCallback(() => {
|
|
1113
|
+
const issue = lastIssue.current;
|
|
1114
|
+
if (unknownByEvent.current || issue && INDETERMINATE.has(issue.kind)) return false;
|
|
1115
|
+
const store = live.current.storage === void 0 ? safeStorage2() : live.current.storage;
|
|
1116
|
+
try {
|
|
1117
|
+
store?.removeItem(`sf:pay:attempt:${live.current.scope ?? "default"}`);
|
|
1118
|
+
} catch {
|
|
1119
|
+
}
|
|
1120
|
+
formFailures.current = 0;
|
|
1121
|
+
lastIssue.current = null;
|
|
1122
|
+
unknownByEvent.current = false;
|
|
1123
|
+
setNotice(null);
|
|
1124
|
+
setSectionKey((k) => k + 1);
|
|
1125
|
+
return true;
|
|
1126
|
+
}, []);
|
|
1127
|
+
const onEvent = useCallback(
|
|
1128
|
+
(e) => {
|
|
1129
|
+
if (e.type === "form_ready") {
|
|
1130
|
+
formFailures.current = 0;
|
|
1131
|
+
setNotice((n) => n?.kind === "form_failed" ? null : n);
|
|
1132
|
+
}
|
|
1133
|
+
if (e.type === "form_failed") {
|
|
1134
|
+
if (formFailures.current < formAutoRetries) {
|
|
1135
|
+
formFailures.current += 1;
|
|
1136
|
+
setSectionKey((k) => k + 1);
|
|
1137
|
+
} else {
|
|
1138
|
+
setNotice({ kind: "form_failed" });
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
if (e.type === "error") unknownByEvent.current = e.kind === "network" || e.kind === "payment_in_progress";
|
|
1142
|
+
if (e.type === "result") {
|
|
1143
|
+
unknownByEvent.current = false;
|
|
1144
|
+
lastIssue.current = null;
|
|
1145
|
+
}
|
|
1146
|
+
if (e.type === "result" && e.status === "rejected") setNotice({ kind: "rejected" });
|
|
1147
|
+
if (e.type === "error" && lastIssue.current?.kind === "store_closed") live.current.onRevalidate?.();
|
|
1148
|
+
},
|
|
1149
|
+
[formAutoRetries]
|
|
1150
|
+
);
|
|
1151
|
+
return useMemo(() => ({ notice, setNotice, sectionKey, onIssue, onEvent, lastIssue, retry, clear: () => setNotice(null) }), [notice, sectionKey, onIssue, onEvent, retry]);
|
|
1152
|
+
}
|
|
1153
|
+
function PaymentRecovery(props) {
|
|
1154
|
+
const { notice, alternatives = [], onRetry, onPick, onRefresh, alternativeLabel, classNames: cx = {}, testIds = {}, children } = props;
|
|
1155
|
+
const locale = useResolvedLocale(props.locale);
|
|
1156
|
+
const m = useMemo(() => paymentRecoveryMessages(locale, props.messages), [locale, props.messages]);
|
|
1157
|
+
const rowNames = useMemo(() => paymentMethodMessages(locale).options, [locale]);
|
|
1158
|
+
const ref = useRef(null);
|
|
1159
|
+
const kind = notice?.kind;
|
|
1160
|
+
useEffect(() => {
|
|
1161
|
+
if (kind && kind !== "confirming") {
|
|
1162
|
+
ref.current?.focus();
|
|
1163
|
+
ref.current?.scrollIntoView?.({ block: "nearest", behavior: "smooth" });
|
|
1164
|
+
}
|
|
1165
|
+
}, [kind]);
|
|
1166
|
+
if (!notice) return null;
|
|
1167
|
+
const confirming = notice.kind === "confirming";
|
|
1168
|
+
const canRetry = notice.kind !== "confirming" && notice.kind !== "stale_checkout";
|
|
1169
|
+
const showAlternatives = notice.kind !== "stale_checkout" && !confirming && alternatives.length > 0;
|
|
1170
|
+
const alternativesFirst = notice.kind === "merchant_config";
|
|
1171
|
+
const nameOf = (c) => c.label ?? (c.option ? rowNames[c.option] : void 0) ?? c.option ?? c.methodKey;
|
|
1172
|
+
const labelOf = (c) => alternativeLabel?.(c) ?? m["action.alternative"].replace("{label}", nameOf(c));
|
|
1173
|
+
const retryButton = canRetry ? /* @__PURE__ */ jsx("button", { type: "button", className: alternativesFirst ? cx.secondary : cx.primary, onClick: onRetry, style: { minHeight: 44 }, "data-sf-pay-retry": true, ...testIds.retry ? { "data-testid": testIds.retry } : {}, children: notice.kind === "form_failed" ? m["action.retry_form"] : m["action.retry"] }) : null;
|
|
1174
|
+
return /* @__PURE__ */ jsxs("div", { ref, tabIndex: -1, role: confirming ? "status" : "alert", "aria-live": confirming ? "polite" : void 0, className: cx.root, "data-sf-pay-recovery": notice.kind, children: [
|
|
1175
|
+
/* @__PURE__ */ jsx("p", { className: cx.title, children: /* @__PURE__ */ jsx("strong", { children: m[`notice.${notice.kind}.title`] }) }),
|
|
1176
|
+
/* @__PURE__ */ jsx("p", { className: cx.body, children: m[`notice.${notice.kind}.body`] }),
|
|
1177
|
+
!confirming ? /* @__PURE__ */ jsxs("div", { className: cx.actions, children: [
|
|
1178
|
+
alternativesFirst ? null : retryButton,
|
|
1179
|
+
notice.kind === "stale_checkout" ? /* @__PURE__ */ jsx("button", { type: "button", className: cx.primary, onClick: onRefresh, style: { minHeight: 44 }, "data-sf-pay-refresh": true, ...testIds.refresh ? { "data-testid": testIds.refresh } : {}, children: m["action.refresh"] }) : null,
|
|
1180
|
+
showAlternatives ? alternatives.map((c) => /* @__PURE__ */ jsx("button", { type: "button", className: alternativesFirst ? cx.primary : cx.secondary, onClick: () => onPick(c), style: { minHeight: 44 }, "data-sf-pay-alternative": c.value, children: labelOf(c) }, c.value)) : null,
|
|
1181
|
+
alternativesFirst ? retryButton : null
|
|
1182
|
+
] }) : null,
|
|
1183
|
+
children
|
|
1184
|
+
] });
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
// src/payments/revalidate.ts
|
|
1188
|
+
function checkoutShouldRevalidate(options) {
|
|
1189
|
+
const paths = options.paths.map((p) => p.replace(/\/+$/, "") || "/");
|
|
1190
|
+
return (args) => {
|
|
1191
|
+
const method = args.formMethod?.toUpperCase();
|
|
1192
|
+
if (!method || method === "GET" || !args.formAction) return args.defaultShouldRevalidate;
|
|
1193
|
+
let pathname = args.formAction;
|
|
1194
|
+
try {
|
|
1195
|
+
pathname = new URL(args.formAction, "http://sf.local").pathname;
|
|
1196
|
+
} catch {
|
|
1197
|
+
}
|
|
1198
|
+
pathname = pathname.replace(/\/+$/, "") || "/";
|
|
1199
|
+
return paths.includes(pathname) ? false : args.defaultShouldRevalidate;
|
|
1200
|
+
};
|
|
1201
|
+
}
|
|
736
1202
|
var PAYMENT_CHOICE_FIELD = "payment_choice";
|
|
737
1203
|
var CASH_POINT_FIELD = "cash_point";
|
|
738
1204
|
function formFields(form) {
|
|
@@ -859,6 +1325,15 @@ function PaymentMethodSection(props) {
|
|
|
859
1325
|
const requote = wantSync && settled && sync.requote === true;
|
|
860
1326
|
const syncFailed = wantSync && settled && !sync.ok && !requote;
|
|
861
1327
|
const brickReady = cardActive && deliveryReady && synced && !syncFailed;
|
|
1328
|
+
const [formLatched, setFormLatched] = useState(false);
|
|
1329
|
+
useEffect(() => {
|
|
1330
|
+
if (brickReady) setFormLatched(true);
|
|
1331
|
+
}, [brickReady]);
|
|
1332
|
+
useEffect(() => {
|
|
1333
|
+
if (!cardActive) setFormLatched(false);
|
|
1334
|
+
}, [cardActive]);
|
|
1335
|
+
const showBrick = cardActive && !syncFailed && (formLatched || brickReady);
|
|
1336
|
+
const totalUpdating = showBrick && !brickReady;
|
|
862
1337
|
const transport = useMemo(
|
|
863
1338
|
() => props.transport ?? createFetchTransport(payUrl, {
|
|
864
1339
|
...statusUrl ? { refreshUrl: statusUrl } : {},
|
|
@@ -972,9 +1447,9 @@ function PaymentMethodSection(props) {
|
|
|
972
1447
|
marks.length ? /* @__PURE__ */ jsx("span", { className: cx.brands, "data-sf-pay-brands": choice.option ?? "plain", "aria-hidden": "true", children: marks.map((mk) => /* @__PURE__ */ jsx(BrandMark, { brand: mk.key, logoUrl: mk.logoUrl ?? null, size: 20 }, mk.key)) }) : null
|
|
973
1448
|
] }),
|
|
974
1449
|
/* @__PURE__ */ jsx(ChoicePanel, { choice, m, invalid, cx, tid, hidden: hydrated && !checked, marks: brandMarks, visible: cashPointsVisible, openMore: storedPoint, onPointChange }),
|
|
975
|
-
choice.mode === "card" && checked && cardChosen && hydrated && choices.cardClient ? /* @__PURE__ */ jsxs("div", { className: cx.brickWrap, "data-sf-pay-card": true, ...tid("card"), children: [
|
|
1450
|
+
choice.mode === "card" && checked && cardChosen && hydrated && choices.cardClient ? /* @__PURE__ */ jsxs("div", { className: cx.brickWrap, "data-sf-pay-card": true, ...totalUpdating ? { "data-sf-pay-updating": "" } : {}, "aria-busy": totalUpdating || void 0, ...tid("card"), children: [
|
|
976
1451
|
fallback ? null : !deliveryReady ? cardNotice("pickDelivery", m.pickDeliveryFirst) : syncFailed ? cardNotice("syncFailed", m.syncFailed) : requote ? cardNotice("requote", m.quoteChanged) : !synced ? cardNotice("syncing", m.updatingTotal) : null,
|
|
977
|
-
|
|
1452
|
+
showBrick || fallback ? /* @__PURE__ */ jsx(
|
|
978
1453
|
PaymentBrick,
|
|
979
1454
|
{
|
|
980
1455
|
...brickProps,
|
|
@@ -986,15 +1461,14 @@ function PaymentMethodSection(props) {
|
|
|
986
1461
|
locale,
|
|
987
1462
|
...scope ? { scope } : {},
|
|
988
1463
|
...payerEmail && !brickProps?.email ? { email: payerEmail } : {},
|
|
989
|
-
enabled:
|
|
1464
|
+
enabled: showBrick,
|
|
990
1465
|
...cx.brick ? { className: cx.brick } : {},
|
|
991
1466
|
onEvent: handleEvent,
|
|
992
1467
|
onFallback: () => setFallback(true),
|
|
993
1468
|
onResult: (o) => {
|
|
994
1469
|
if (o.kind === "approved" || o.kind === "in_review" || o.kind === "voucher") onDone(o);
|
|
995
1470
|
}
|
|
996
|
-
}
|
|
997
|
-
payerEmail || brickProps?.email ? "with-email" : "without-email"
|
|
1471
|
+
}
|
|
998
1472
|
) : null,
|
|
999
1473
|
/* @__PURE__ */ jsx("p", { className: cx.note, children: m.secureCard }),
|
|
1000
1474
|
expired && rebuildHref ? Link({ href: rebuildHref, ...cx.rebuild ? { className: cx.rebuild } : {}, children: m.rebuildCart, ...testIds.rebuild ? { testId: testIds.rebuild } : {} }) : null
|
|
@@ -1013,7 +1487,7 @@ function PaymentMethodSection(props) {
|
|
|
1013
1487
|
onClick: onPayClick,
|
|
1014
1488
|
className: cx.submit,
|
|
1015
1489
|
disabled: busy || blocked,
|
|
1016
|
-
"aria-disabled": blocked ||
|
|
1490
|
+
"aria-disabled": blocked || cardActive && !syncFailed && deliveryReady && (!brickReady || !ready || processing) || void 0,
|
|
1017
1491
|
"aria-busy": processing || void 0,
|
|
1018
1492
|
"data-sf-pay-method-submit": true,
|
|
1019
1493
|
...tid("submit"),
|
|
@@ -1104,6 +1578,6 @@ function rowMarks(choice, cardBrands) {
|
|
|
1104
1578
|
return [];
|
|
1105
1579
|
}
|
|
1106
1580
|
|
|
1107
|
-
export { CASH_POINT_FIELD, MERCADOPAGO_BRICKS_KIND, PAYMENT_CHOICE_FIELD, PAYMENT_THEME_TOKENS, PaymentBrick, PaymentChallenge, PaymentHttpError, PaymentMethodSection, VoucherCard, createFetchTransport, formFields, loadPaymentSdk, mercadoPagoBricksAdapter, paymentMethodMessages, readPaymentScheme, readPaymentTheme, resetPaymentSdkCache, sanitizeMercadoPagoFormData, useEmbeddedPayment };
|
|
1581
|
+
export { CASH_POINT_FIELD, MERCADOPAGO_BRICKS_KIND, PAYMENT_CHOICE_FIELD, PAYMENT_THEME_TOKENS, PaymentBrick, PaymentChallenge, PaymentHttpError, PaymentMethodSection, PaymentMountCancelled, PaymentMountTimeout, PaymentRecovery, VoucherCard, alternativeChoices, checkoutShouldRevalidate, createFetchTransport, createResilientTransport, formFields, isResolvedAttempt, loadPaymentSdk, mercadoPagoBricksAdapter, mountQueued, paymentMethodMessages, pickChoice, readPaymentScheme, readPaymentTheme, resetPaymentSdkCache, sanitizeMercadoPagoFormData, useEmbeddedPayment, usePaymentRecovery, withMountQueue };
|
|
1108
1582
|
//# sourceMappingURL=payments.js.map
|
|
1109
1583
|
//# sourceMappingURL=payments.js.map
|