@blocklet/payment-react 1.24.4 → 1.25.1
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/es/components/auto-topup/modal.d.ts +2 -0
- package/es/components/auto-topup/modal.js +48 -6
- package/es/components/auto-topup/product-card.d.ts +16 -1
- package/es/components/auto-topup/product-card.js +97 -15
- package/es/components/dynamic-pricing-unavailable.d.ts +9 -0
- package/es/components/dynamic-pricing-unavailable.js +58 -0
- package/es/components/loading-amount.d.ts +17 -0
- package/es/components/loading-amount.js +46 -0
- package/es/components/price-change-confirm.d.ts +18 -0
- package/es/components/price-change-confirm.js +107 -0
- package/es/components/quote-details-panel.d.ts +21 -0
- package/es/components/quote-details-panel.js +170 -0
- package/es/components/quote-lock-banner.d.ts +7 -0
- package/es/components/quote-lock-banner.js +79 -0
- package/es/components/slippage-config.d.ts +20 -0
- package/es/components/slippage-config.js +261 -0
- package/es/history/invoice/list.js +125 -15
- package/es/hooks/dynamic-pricing.d.ts +102 -0
- package/es/hooks/dynamic-pricing.js +393 -0
- package/es/index.d.ts +6 -1
- package/es/index.js +9 -1
- package/es/libs/util.d.ts +42 -5
- package/es/libs/util.js +345 -57
- package/es/locales/en.js +114 -3
- package/es/locales/zh.js +114 -3
- package/es/payment/form/index.d.ts +4 -1
- package/es/payment/form/index.js +454 -22
- package/es/payment/index.d.ts +1 -1
- package/es/payment/index.js +279 -16
- package/es/payment/product-item.d.ts +26 -1
- package/es/payment/product-item.js +330 -51
- package/es/payment/summary-section/promotion-section.d.ts +32 -0
- package/es/payment/summary-section/promotion-section.js +143 -0
- package/es/payment/summary-section/total-section.d.ts +39 -0
- package/es/payment/summary-section/total-section.js +83 -0
- package/es/payment/summary.d.ts +17 -2
- package/es/payment/summary.js +300 -253
- package/es/types/index.d.ts +11 -0
- package/lib/components/auto-topup/modal.d.ts +2 -0
- package/lib/components/auto-topup/modal.js +54 -6
- package/lib/components/auto-topup/product-card.d.ts +16 -1
- package/lib/components/auto-topup/product-card.js +75 -7
- package/lib/components/dynamic-pricing-unavailable.d.ts +9 -0
- package/lib/components/dynamic-pricing-unavailable.js +81 -0
- package/lib/components/loading-amount.d.ts +17 -0
- package/lib/components/loading-amount.js +53 -0
- package/lib/components/price-change-confirm.d.ts +18 -0
- package/lib/components/price-change-confirm.js +157 -0
- package/lib/components/quote-details-panel.d.ts +21 -0
- package/lib/components/quote-details-panel.js +226 -0
- package/lib/components/quote-lock-banner.d.ts +7 -0
- package/lib/components/quote-lock-banner.js +93 -0
- package/lib/components/slippage-config.d.ts +20 -0
- package/lib/components/slippage-config.js +316 -0
- package/lib/history/invoice/list.js +167 -27
- package/lib/hooks/dynamic-pricing.d.ts +102 -0
- package/lib/hooks/dynamic-pricing.js +390 -0
- package/lib/index.d.ts +6 -1
- package/lib/index.js +32 -0
- package/lib/libs/util.d.ts +42 -5
- package/lib/libs/util.js +367 -49
- package/lib/locales/en.js +114 -3
- package/lib/locales/zh.js +114 -3
- package/lib/payment/form/index.d.ts +4 -1
- package/lib/payment/form/index.js +476 -20
- package/lib/payment/index.d.ts +1 -1
- package/lib/payment/index.js +308 -14
- package/lib/payment/product-item.d.ts +26 -1
- package/lib/payment/product-item.js +270 -35
- package/lib/payment/summary-section/promotion-section.d.ts +32 -0
- package/lib/payment/summary-section/promotion-section.js +133 -0
- package/lib/payment/summary-section/total-section.d.ts +39 -0
- package/lib/payment/summary-section/total-section.js +117 -0
- package/lib/payment/summary.d.ts +17 -2
- package/lib/payment/summary.js +205 -127
- package/lib/types/index.d.ts +11 -0
- package/package.json +3 -3
- package/src/components/auto-topup/modal.tsx +59 -6
- package/src/components/auto-topup/product-card.tsx +118 -11
- package/src/components/dynamic-pricing-unavailable.tsx +69 -0
- package/src/components/loading-amount.tsx +66 -0
- package/src/components/price-change-confirm.tsx +136 -0
- package/src/components/quote-details-panel.tsx +218 -0
- package/src/components/quote-lock-banner.tsx +99 -0
- package/src/components/slippage-config.tsx +336 -0
- package/src/history/invoice/list.tsx +143 -9
- package/src/hooks/dynamic-pricing.ts +617 -0
- package/src/index.ts +9 -0
- package/src/libs/util.ts +473 -58
- package/src/locales/en.tsx +117 -0
- package/src/locales/zh.tsx +111 -0
- package/src/payment/form/index.tsx +561 -19
- package/src/payment/index.tsx +349 -10
- package/src/payment/product-item.tsx +451 -37
- package/src/payment/summary-section/promotion-section.tsx +172 -0
- package/src/payment/summary-section/total-section.tsx +141 -0
- package/src/payment/summary.tsx +334 -192
- package/src/types/index.ts +15 -0
|
@@ -23,6 +23,7 @@ var _DID = _interopRequireDefault(require("@arcblock/ux/lib/DID"));
|
|
|
23
23
|
var _isEmpty = _interopRequireDefault(require("lodash/isEmpty"));
|
|
24
24
|
var _iconsMaterial = require("@mui/icons-material");
|
|
25
25
|
var _withTracker = require("@arcblock/ux/lib/withTracker");
|
|
26
|
+
var _trim = _interopRequireDefault(require("lodash/trim"));
|
|
26
27
|
var _input = _interopRequireDefault(require("../../components/input"));
|
|
27
28
|
var _label = _interopRequireDefault(require("../../components/label"));
|
|
28
29
|
var _payment = require("../../contexts/payment");
|
|
@@ -39,8 +40,12 @@ var _loadingButton = _interopRequireDefault(require("../../components/loading-bu
|
|
|
39
40
|
var _overDueInvoicePayment = _interopRequireDefault(require("../../components/over-due-invoice-payment"));
|
|
40
41
|
var _currency2 = require("../../libs/currency");
|
|
41
42
|
var _confirm = _interopRequireDefault(require("../../components/confirm"));
|
|
43
|
+
var _priceChangeConfirm = _interopRequireDefault(require("../../components/price-change-confirm"));
|
|
42
44
|
var _validator = require("../../libs/validator");
|
|
43
45
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
46
|
+
const generateIdempotencyKey = (sessionId, currencyId) => {
|
|
47
|
+
return `${sessionId}-${currencyId}-${Date.now()}-${Math.random().toString(36).substring(2, 10)}`;
|
|
48
|
+
};
|
|
44
49
|
const waitForCheckoutComplete = async sessionId => {
|
|
45
50
|
let result;
|
|
46
51
|
await (0, _pWaitFor.default)(async () => {
|
|
@@ -121,10 +126,13 @@ function PaymentForm({
|
|
|
121
126
|
customer,
|
|
122
127
|
onPaid,
|
|
123
128
|
onError,
|
|
129
|
+
onQuoteUpdated = void 0,
|
|
130
|
+
onPaymentIntentUpdate = void 0,
|
|
124
131
|
// mode,
|
|
125
132
|
action,
|
|
126
133
|
onlyShowBtn = false,
|
|
127
|
-
isDonation = false
|
|
134
|
+
isDonation = false,
|
|
135
|
+
rateUnavailable = false
|
|
128
136
|
}) {
|
|
129
137
|
const {
|
|
130
138
|
t,
|
|
@@ -174,7 +182,8 @@ function PaymentForm({
|
|
|
174
182
|
stripePaying: false,
|
|
175
183
|
fastCheckoutInfo: null,
|
|
176
184
|
creditInsufficientInfo: null,
|
|
177
|
-
showEditForm: false
|
|
185
|
+
showEditForm: false,
|
|
186
|
+
priceChangeConfirm: null
|
|
178
187
|
});
|
|
179
188
|
const currencies = (0, _util2.flattenPaymentMethods)(paymentMethods);
|
|
180
189
|
const searchParams = (0, _util2.getQueryParams)(window.location.href);
|
|
@@ -295,7 +304,192 @@ function PaymentForm({
|
|
|
295
304
|
const method = paymentMethods.find(x => x.id === paymentMethod);
|
|
296
305
|
const paymentCurrency = currencies.find(x => x.id === paymentCurrencyId);
|
|
297
306
|
const showStake = method.type === "arcblock" && !checkoutSession.subscription_data?.no_stake;
|
|
307
|
+
const hasDynamicPricing = (0, _react.useMemo)(() => (checkoutSession.line_items || []).some(item => {
|
|
308
|
+
const price = item.upsell_price || item.price;
|
|
309
|
+
return price && price?.pricing_type === "dynamic";
|
|
310
|
+
}), [checkoutSession.line_items]);
|
|
311
|
+
const rateUnavailableForDynamic = hasDynamicPricing && rateUnavailable;
|
|
312
|
+
const canPay = payable && !rateUnavailableForDynamic;
|
|
298
313
|
const isDonationMode = checkoutSession?.submit_type === "donate" && isDonation;
|
|
314
|
+
const [priceUpdateInfo, setPriceUpdateInfo] = (0, _ahooks.useSetState)({
|
|
315
|
+
open: false,
|
|
316
|
+
total: "",
|
|
317
|
+
usd: null,
|
|
318
|
+
hasQuotes: false,
|
|
319
|
+
baseCurrency: "USD",
|
|
320
|
+
oldTotal: "",
|
|
321
|
+
reason: "recalculated"
|
|
322
|
+
});
|
|
323
|
+
const normalizeExchangeRate = (0, _ahooks.useMemoizedFn)(rate => {
|
|
324
|
+
if (!rate) {
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
const value = Number(rate);
|
|
328
|
+
if (!Number.isFinite(value)) {
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
return value.toFixed(8);
|
|
332
|
+
});
|
|
333
|
+
const getExchangeRateFromSession = (0, _ahooks.useMemoizedFn)(sessionData => {
|
|
334
|
+
if (!sessionData?.line_items?.length) {
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
for (const item of sessionData.line_items) {
|
|
338
|
+
const rate = item?.exchange_rate;
|
|
339
|
+
if (rate) {
|
|
340
|
+
return rate;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return null;
|
|
344
|
+
});
|
|
345
|
+
const quoteAutoRetryRef = (0, _react.useRef)(false);
|
|
346
|
+
const lastRetryKeyRef = (0, _react.useRef)("");
|
|
347
|
+
const buildRetryKey = (0, _ahooks.useMemoizedFn)(sessionData => {
|
|
348
|
+
if (!sessionData?.line_items?.length) {
|
|
349
|
+
return "";
|
|
350
|
+
}
|
|
351
|
+
return sessionData.line_items.map(item => {
|
|
352
|
+
const priceId = item.price_id || item.price?.id || "";
|
|
353
|
+
const quoteId = item?.quote_id || "";
|
|
354
|
+
const quotedAmount = item?.quoted_amount || "";
|
|
355
|
+
const exchangeRate = item?.exchange_rate || "";
|
|
356
|
+
return `${priceId}:${quoteId}:${quotedAmount}:${exchangeRate}`;
|
|
357
|
+
}).join("|");
|
|
358
|
+
});
|
|
359
|
+
const buildPriceUpdateSummary = (0, _ahooks.useMemoizedFn)(sessionData => {
|
|
360
|
+
if (!paymentCurrency) {
|
|
361
|
+
return {
|
|
362
|
+
total: "",
|
|
363
|
+
usd: null,
|
|
364
|
+
hasQuotes: false,
|
|
365
|
+
baseCurrency: "USD",
|
|
366
|
+
totalUnit: null
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
const lineItems = sessionData.line_items || [];
|
|
370
|
+
let baseCurrency = "USD";
|
|
371
|
+
for (const item of lineItems) {
|
|
372
|
+
const price = item.upsell_price || item.price;
|
|
373
|
+
const base = price?.base_currency;
|
|
374
|
+
if (base) {
|
|
375
|
+
baseCurrency = base;
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
const hasQuotes = lineItems.some(item => item?.quoted_amount && item?.exchange_rate);
|
|
380
|
+
if (!hasQuotes) {
|
|
381
|
+
return {
|
|
382
|
+
total: "",
|
|
383
|
+
usd: null,
|
|
384
|
+
hasQuotes: false,
|
|
385
|
+
baseCurrency,
|
|
386
|
+
totalUnit: null
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
let trialInDays = Number(sessionData?.subscription_data?.trial_period_days || 0);
|
|
390
|
+
const trialCurrencyIds = (sessionData?.subscription_data?.trial_currency || "").split(",").map(_trim.default).filter(Boolean);
|
|
391
|
+
if (trialCurrencyIds.length > 0 && paymentCurrencyId && trialCurrencyIds.includes(paymentCurrencyId) === false) {
|
|
392
|
+
trialInDays = 0;
|
|
393
|
+
}
|
|
394
|
+
const {
|
|
395
|
+
total
|
|
396
|
+
} = (0, _util2.getCheckoutAmount)(lineItems, paymentCurrency, trialInDays > 0);
|
|
397
|
+
const discountAmount = new _util.BN(sessionData.total_details?.amount_discount || "0");
|
|
398
|
+
const totalUnit = new _util.BN(total).sub(discountAmount);
|
|
399
|
+
const normalizedTotalUnit = totalUnit.isNeg() ? new _util.BN(0) : totalUnit;
|
|
400
|
+
const totalDisplay = `${(0, _util2.formatNumber)((0, _util.fromUnitToToken)(normalizedTotalUnit.toString(), paymentCurrency.decimal), 6)} ${paymentCurrency.symbol}`;
|
|
401
|
+
const itemUsdReferences = lineItems.map(item => {
|
|
402
|
+
const price = item.upsell_price || item.price;
|
|
403
|
+
const baseAmount = price?.base_amount;
|
|
404
|
+
const hasBaseAmount = baseAmount !== void 0 && baseAmount !== null;
|
|
405
|
+
if (hasBaseAmount) {
|
|
406
|
+
return (0, _util2.getUsdAmountFromBaseAmount)(baseAmount, item.quantity || 0);
|
|
407
|
+
}
|
|
408
|
+
const exchangeRate = item?.exchange_rate;
|
|
409
|
+
const quotedAmount = item?.quoted_amount;
|
|
410
|
+
if (!exchangeRate || !quotedAmount) {
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
413
|
+
return (0, _util2.getUsdAmountFromTokenUnits)(new _util.BN(quotedAmount), paymentCurrency.decimal, exchangeRate);
|
|
414
|
+
});
|
|
415
|
+
const usdValues = itemUsdReferences.filter(value => Boolean(value));
|
|
416
|
+
if (!usdValues.length) {
|
|
417
|
+
return {
|
|
418
|
+
total: totalDisplay,
|
|
419
|
+
usd: null,
|
|
420
|
+
hasQuotes,
|
|
421
|
+
baseCurrency,
|
|
422
|
+
totalUnit: normalizedTotalUnit
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
const sumUnit = usdValues.reduce((acc, value) => acc.add(new _util.BN((0, _util.fromTokenToUnit)(value, 8))), new _util.BN(0));
|
|
426
|
+
const totalUsdReference = (0, _util.fromUnitToToken)(sumUnit.toString(), 8);
|
|
427
|
+
return {
|
|
428
|
+
total: totalDisplay,
|
|
429
|
+
usd: (0, _util2.formatUsdAmount)(totalUsdReference, locale),
|
|
430
|
+
hasQuotes,
|
|
431
|
+
baseCurrency,
|
|
432
|
+
totalUnit: normalizedTotalUnit
|
|
433
|
+
};
|
|
434
|
+
});
|
|
435
|
+
const compareTotals = (0, _ahooks.useMemoizedFn)((prevSession, nextSession) => {
|
|
436
|
+
const prev = buildPriceUpdateSummary(prevSession);
|
|
437
|
+
const next = buildPriceUpdateSummary(nextSession);
|
|
438
|
+
if (!prev.totalUnit || !next.totalUnit) {
|
|
439
|
+
return {
|
|
440
|
+
changed: false,
|
|
441
|
+
prev,
|
|
442
|
+
next
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
const diff = next.totalUnit.sub(prev.totalUnit).abs();
|
|
446
|
+
const epsilon = new _util.BN(1);
|
|
447
|
+
return {
|
|
448
|
+
changed: diff.gt(epsilon),
|
|
449
|
+
prev,
|
|
450
|
+
next
|
|
451
|
+
};
|
|
452
|
+
});
|
|
453
|
+
const applyQuoteUpdate = (0, _ahooks.useMemoizedFn)((payload, options = {}) => {
|
|
454
|
+
if (!payload?.checkoutSession) {
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
onQuoteUpdated?.({
|
|
458
|
+
checkoutSession: payload.checkoutSession,
|
|
459
|
+
quotes: payload.quotes,
|
|
460
|
+
rateUnavailable: payload.rateUnavailable,
|
|
461
|
+
rateError: payload.rateError
|
|
462
|
+
});
|
|
463
|
+
const {
|
|
464
|
+
changed,
|
|
465
|
+
prev,
|
|
466
|
+
next
|
|
467
|
+
} = compareTotals(checkoutSession, payload.checkoutSession);
|
|
468
|
+
const previousRate = normalizeExchangeRate(getExchangeRateFromSession(checkoutSession));
|
|
469
|
+
const nextRate = normalizeExchangeRate(getExchangeRateFromSession(payload.checkoutSession));
|
|
470
|
+
const rateChanged = !!(previousRate && nextRate && previousRate !== nextRate);
|
|
471
|
+
const shouldShowModal = (options.forceConfirm || changed) && next.hasQuotes;
|
|
472
|
+
if (shouldShowModal) {
|
|
473
|
+
setPriceUpdateInfo({
|
|
474
|
+
open: true,
|
|
475
|
+
total: next.total,
|
|
476
|
+
usd: next.usd,
|
|
477
|
+
hasQuotes: next.hasQuotes,
|
|
478
|
+
baseCurrency: next.baseCurrency,
|
|
479
|
+
oldTotal: prev.total,
|
|
480
|
+
reason: options.reason || (rateChanged ? "rateChanged" : "recalculated")
|
|
481
|
+
});
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
setPriceUpdateInfo({
|
|
485
|
+
open: false
|
|
486
|
+
});
|
|
487
|
+
const retryKey = buildRetryKey(payload.checkoutSession);
|
|
488
|
+
if (retryKey && retryKey !== lastRetryKeyRef.current) {
|
|
489
|
+
lastRetryKeyRef.current = retryKey;
|
|
490
|
+
quoteAutoRetryRef.current = true;
|
|
491
|
+
}
|
|
492
|
+
});
|
|
299
493
|
const validateUserInfo = values => {
|
|
300
494
|
if (!values) {
|
|
301
495
|
return false;
|
|
@@ -403,6 +597,16 @@ function PaymentForm({
|
|
|
403
597
|
name: "billing_address"
|
|
404
598
|
});
|
|
405
599
|
const showForm = session?.user ? state.showEditForm : false;
|
|
600
|
+
(0, _react.useEffect)(() => {
|
|
601
|
+
if (!quoteAutoRetryRef.current) {
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
if (state.submitting || state.paying) {
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
quoteAutoRetryRef.current = false;
|
|
608
|
+
onAction();
|
|
609
|
+
}, [state.submitting, state.paying]);
|
|
406
610
|
const handleConnected = async () => {
|
|
407
611
|
if (processingRef.current) {
|
|
408
612
|
return;
|
|
@@ -473,7 +677,10 @@ function PaymentForm({
|
|
|
473
677
|
}
|
|
474
678
|
});
|
|
475
679
|
try {
|
|
476
|
-
const result = await _api.default.post(`/api/checkout-sessions/${checkoutSession.id}/fast-checkout-confirm
|
|
680
|
+
const result = await _api.default.post(`/api/checkout-sessions/${checkoutSession.id}/fast-checkout-confirm`, {});
|
|
681
|
+
if (result.data.paymentIntent) {
|
|
682
|
+
onPaymentIntentUpdate?.(result.data.paymentIntent);
|
|
683
|
+
}
|
|
477
684
|
if (result.data.fastPaid) {
|
|
478
685
|
setState({
|
|
479
686
|
fastCheckoutInfo: null,
|
|
@@ -490,6 +697,57 @@ function PaymentForm({
|
|
|
490
697
|
}
|
|
491
698
|
} catch (err) {
|
|
492
699
|
console.error(err);
|
|
700
|
+
const errorCode = err?.response?.data?.code;
|
|
701
|
+
if (["QUOTE_LOCK_EXPIRED", "QUOTE_AMOUNT_MISMATCH", "QUOTE_EXPIRED_OR_USED", "QUOTE_NOT_FOUND", "QUOTE_REQUIRED"].includes(errorCode)) {
|
|
702
|
+
try {
|
|
703
|
+
const {
|
|
704
|
+
data: refreshed
|
|
705
|
+
} = await _api.default.get(`/api/checkout-sessions/retrieve/${checkoutSession.id}`, {
|
|
706
|
+
params: {
|
|
707
|
+
forceRefresh: "1"
|
|
708
|
+
}
|
|
709
|
+
});
|
|
710
|
+
if (refreshed?.checkoutSession) {
|
|
711
|
+
applyQuoteUpdate(refreshed, {
|
|
712
|
+
reason: "rateChanged"
|
|
713
|
+
});
|
|
714
|
+
_Toast.default.info(t("payment.checkout.quote.updated.pleaseRetry") || "Price updated, please resubmit");
|
|
715
|
+
}
|
|
716
|
+
} catch (refreshError) {
|
|
717
|
+
console.error(refreshError);
|
|
718
|
+
_Toast.default.error((0, _util2.formatError)(refreshError));
|
|
719
|
+
} finally {
|
|
720
|
+
setState({
|
|
721
|
+
fastCheckoutInfo: null
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
if (errorCode === "QUOTE_UPDATED") {
|
|
727
|
+
const payload = err?.response?.data;
|
|
728
|
+
if (payload?.checkoutSession) {
|
|
729
|
+
applyQuoteUpdate(payload);
|
|
730
|
+
}
|
|
731
|
+
setState({
|
|
732
|
+
fastCheckoutInfo: null
|
|
733
|
+
});
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
if (errorCode === "RATE_UNAVAILABLE") {
|
|
737
|
+
const payload = err?.response?.data;
|
|
738
|
+
if (payload?.checkoutSession) {
|
|
739
|
+
onQuoteUpdated?.({
|
|
740
|
+
checkoutSession: payload.checkoutSession,
|
|
741
|
+
quotes: payload.quotes,
|
|
742
|
+
rateUnavailable: payload.rateUnavailable,
|
|
743
|
+
rateError: payload.rateError
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
setState({
|
|
747
|
+
fastCheckoutInfo: null
|
|
748
|
+
});
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
493
751
|
_Toast.default.error((0, _util2.formatError)(err));
|
|
494
752
|
setState({
|
|
495
753
|
fastCheckoutInfo: null
|
|
@@ -506,6 +764,31 @@ function PaymentForm({
|
|
|
506
764
|
creditInsufficientInfo: null
|
|
507
765
|
});
|
|
508
766
|
};
|
|
767
|
+
const handlePriceUpdateConfirm = () => {
|
|
768
|
+
setPriceUpdateInfo({
|
|
769
|
+
open: false
|
|
770
|
+
});
|
|
771
|
+
quoteAutoRetryRef.current = true;
|
|
772
|
+
};
|
|
773
|
+
const handlePriceUpdateCancel = () => {
|
|
774
|
+
setPriceUpdateInfo({
|
|
775
|
+
open: false
|
|
776
|
+
});
|
|
777
|
+
};
|
|
778
|
+
const handlePriceChangeConfirm = () => {
|
|
779
|
+
const formData = state.priceChangeConfirm?.formData;
|
|
780
|
+
setState({
|
|
781
|
+
priceChangeConfirm: null
|
|
782
|
+
});
|
|
783
|
+
if (formData) {
|
|
784
|
+
onFormSubmit(formData);
|
|
785
|
+
}
|
|
786
|
+
};
|
|
787
|
+
const handlePriceChangeCancel = () => {
|
|
788
|
+
setState({
|
|
789
|
+
priceChangeConfirm: null
|
|
790
|
+
});
|
|
791
|
+
};
|
|
509
792
|
const openConnect = () => {
|
|
510
793
|
try {
|
|
511
794
|
if (!["arcblock", "ethereum", "base"].includes(method.type)) {
|
|
@@ -558,6 +841,9 @@ function PaymentForm({
|
|
|
558
841
|
}
|
|
559
842
|
};
|
|
560
843
|
const onFormSubmit = async data => {
|
|
844
|
+
if (state.submitting) {
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
561
847
|
const userInfo = session.user;
|
|
562
848
|
if (!userInfo.sourceAppPid) {
|
|
563
849
|
const hasVendorConfig = checkoutSession.line_items?.some(item => !!item?.price?.product?.vendor_config?.length);
|
|
@@ -571,10 +857,18 @@ function PaymentForm({
|
|
|
571
857
|
});
|
|
572
858
|
try {
|
|
573
859
|
let result;
|
|
860
|
+
const previewRate = checkoutSession.line_items?.find(item => item?.exchange_rate)?.exchange_rate || void 0;
|
|
861
|
+
const payload = {
|
|
862
|
+
...data,
|
|
863
|
+
// Final Freeze: Include these for new quote creation at submit
|
|
864
|
+
idempotency_key: generateIdempotencyKey(checkoutSession.id, paymentCurrency?.id || ""),
|
|
865
|
+
preview_rate: previewRate || void 0,
|
|
866
|
+
price_confirmed: state.priceChangeConfirm?.formData ? true : void 0
|
|
867
|
+
};
|
|
574
868
|
if (isDonationMode) {
|
|
575
|
-
result = await _api.default.put(`/api/checkout-sessions/${checkoutSession.id}/donate-submit`,
|
|
869
|
+
result = await _api.default.put(`/api/checkout-sessions/${checkoutSession.id}/donate-submit`, payload);
|
|
576
870
|
} else {
|
|
577
|
-
result = await _api.default.put(`/api/checkout-sessions/${checkoutSession.id}/submit`,
|
|
871
|
+
result = await _api.default.put(`/api/checkout-sessions/${checkoutSession.id}/submit`, payload);
|
|
578
872
|
}
|
|
579
873
|
setState({
|
|
580
874
|
paymentIntent: result.data.paymentIntent,
|
|
@@ -583,7 +877,30 @@ function PaymentForm({
|
|
|
583
877
|
submitting: false,
|
|
584
878
|
customerLimited: false
|
|
585
879
|
});
|
|
880
|
+
if (result.data.paymentIntent) {
|
|
881
|
+
onPaymentIntentUpdate?.(result.data.paymentIntent);
|
|
882
|
+
}
|
|
586
883
|
if (["arcblock", "ethereum", "base"].includes(method.type)) {
|
|
884
|
+
if (result.data.noPaymentRequired) {
|
|
885
|
+
try {
|
|
886
|
+
const confirmResult = await _api.default.post(`/api/checkout-sessions/${checkoutSession.id}/fast-checkout-confirm`, {});
|
|
887
|
+
if (confirmResult.data.paymentIntent) {
|
|
888
|
+
onPaymentIntentUpdate?.(confirmResult.data.paymentIntent);
|
|
889
|
+
}
|
|
890
|
+
if (confirmResult.data.fastPaid || confirmResult.data.checkoutSession?.status === "complete") {
|
|
891
|
+
setState({
|
|
892
|
+
paying: true
|
|
893
|
+
});
|
|
894
|
+
await handleConnected();
|
|
895
|
+
} else {
|
|
896
|
+
openConnect();
|
|
897
|
+
}
|
|
898
|
+
} catch (confirmErr) {
|
|
899
|
+
console.error("noPaymentRequired confirm failed", confirmErr);
|
|
900
|
+
openConnect();
|
|
901
|
+
}
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
587
904
|
if (paymentCurrency?.type === "credit") {
|
|
588
905
|
if (result.data.creditSufficient === true) {
|
|
589
906
|
setState({
|
|
@@ -634,13 +951,80 @@ function PaymentForm({
|
|
|
634
951
|
} catch (err) {
|
|
635
952
|
console.error(err);
|
|
636
953
|
let shouldToast = true;
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
if (
|
|
954
|
+
const errorCode = err.response?.data?.code;
|
|
955
|
+
if (errorCode) {
|
|
956
|
+
if (!["QUOTE_UPDATED", "RATE_UNAVAILABLE", "QUOTE_LOCK_EXPIRED", "QUOTE_AMOUNT_MISMATCH", "QUOTE_EXPIRED_OR_USED", "QUOTE_NOT_FOUND", "QUOTE_REQUIRED", "QUOTE_MAX_PAYABLE_EXCEEDED"].includes(errorCode)) {
|
|
957
|
+
(0, _useBus.dispatch)(`error.${errorCode}`);
|
|
958
|
+
}
|
|
959
|
+
if (["QUOTE_LOCK_EXPIRED", "QUOTE_AMOUNT_MISMATCH", "QUOTE_EXPIRED_OR_USED", "QUOTE_NOT_FOUND", "QUOTE_REQUIRED", "QUOTE_MAX_PAYABLE_EXCEEDED", "quote_validation_failed"].includes(errorCode)) {
|
|
960
|
+
shouldToast = false;
|
|
961
|
+
try {
|
|
962
|
+
const {
|
|
963
|
+
data: refreshed
|
|
964
|
+
} = await _api.default.get(`/api/checkout-sessions/retrieve/${checkoutSession.id}`, {
|
|
965
|
+
params: {
|
|
966
|
+
forceRefresh: "1"
|
|
967
|
+
}
|
|
968
|
+
});
|
|
969
|
+
if (refreshed?.checkoutSession) {
|
|
970
|
+
applyQuoteUpdate(refreshed, {
|
|
971
|
+
reason: "rateChanged"
|
|
972
|
+
});
|
|
973
|
+
_Toast.default.info(t("payment.checkout.quote.updated.pleaseRetry") || "Price updated, please resubmit");
|
|
974
|
+
}
|
|
975
|
+
} catch (refreshError) {
|
|
976
|
+
console.error(refreshError);
|
|
977
|
+
_Toast.default.error((0, _util2.formatError)(refreshError));
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
if (errorCode === "QUOTE_UPDATED") {
|
|
981
|
+
shouldToast = false;
|
|
982
|
+
const payload = err.response?.data;
|
|
983
|
+
if (payload?.checkoutSession) {
|
|
984
|
+
applyQuoteUpdate(payload);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
if (errorCode === "RATE_UNAVAILABLE") {
|
|
988
|
+
shouldToast = false;
|
|
989
|
+
const payload = err.response?.data;
|
|
990
|
+
if (payload?.checkoutSession) {
|
|
991
|
+
onQuoteUpdated?.({
|
|
992
|
+
checkoutSession: payload.checkoutSession,
|
|
993
|
+
quotes: payload.quotes,
|
|
994
|
+
rateUnavailable: payload.rateUnavailable,
|
|
995
|
+
rateError: payload.rateError
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
if (errorCode === "PRICE_UNAVAILABLE") {
|
|
1000
|
+
shouldToast = false;
|
|
1001
|
+
_Toast.default.error(t("payment.checkout.priceChange.unavailable", {
|
|
1002
|
+
fallback: "Unable to fetch exchange rate. Please try again later."
|
|
1003
|
+
}));
|
|
1004
|
+
}
|
|
1005
|
+
if (errorCode === "PRICE_UNSTABLE") {
|
|
1006
|
+
shouldToast = false;
|
|
1007
|
+
_Toast.default.error(t("payment.checkout.priceChange.unstable", {
|
|
1008
|
+
fallback: "Price is volatile. Please try again later."
|
|
1009
|
+
}));
|
|
1010
|
+
}
|
|
1011
|
+
if (errorCode === "PRICE_CHANGED") {
|
|
1012
|
+
shouldToast = false;
|
|
1013
|
+
const errorData = err.response?.data;
|
|
1014
|
+
setState({
|
|
1015
|
+
priceChangeConfirm: {
|
|
1016
|
+
open: true,
|
|
1017
|
+
changePercent: errorData?.change_percent || 0,
|
|
1018
|
+
formData: data
|
|
1019
|
+
// Save form data for retry
|
|
1020
|
+
}
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
if (errorCode === "UNIFIED_APP_REQUIRED") {
|
|
640
1024
|
shouldToast = false;
|
|
641
1025
|
_Toast.default.error(t("payment.checkout.vendor.accountRequired"));
|
|
642
1026
|
}
|
|
643
|
-
if (
|
|
1027
|
+
if (errorCode === "CUSTOMER_LIMITED") {
|
|
644
1028
|
shouldToast = false;
|
|
645
1029
|
setState({
|
|
646
1030
|
customerLimited: true
|
|
@@ -665,7 +1049,7 @@ function PaymentForm({
|
|
|
665
1049
|
});
|
|
666
1050
|
};
|
|
667
1051
|
const onAction = () => {
|
|
668
|
-
if (state.submitting || state.paying) {
|
|
1052
|
+
if (state.submitting || state.paying || !canPay) {
|
|
669
1053
|
return;
|
|
670
1054
|
}
|
|
671
1055
|
if (errorRef.current && !(0, _isEmpty.default)(errors) && isMobile) {
|
|
@@ -714,7 +1098,7 @@ function PaymentForm({
|
|
|
714
1098
|
};
|
|
715
1099
|
(0, _react.useEffect)(() => {
|
|
716
1100
|
const handleKeyDown = e => {
|
|
717
|
-
if (e.key === "Enter" && !state.submitting && !state.paying && !state.stripePaying && quantityInventoryStatus &&
|
|
1101
|
+
if (e.key === "Enter" && !state.submitting && !state.paying && !state.stripePaying && quantityInventoryStatus && canPay) {
|
|
718
1102
|
onAction();
|
|
719
1103
|
}
|
|
720
1104
|
};
|
|
@@ -722,7 +1106,7 @@ function PaymentForm({
|
|
|
722
1106
|
return () => {
|
|
723
1107
|
window.removeEventListener("keydown", handleKeyDown);
|
|
724
1108
|
};
|
|
725
|
-
}, [state.submitting, state.paying, state.stripePaying, quantityInventoryStatus,
|
|
1109
|
+
}, [state.submitting, state.paying, state.stripePaying, quantityInventoryStatus, canPay]);
|
|
726
1110
|
const balanceLink = (0, _util2.getTokenBalanceLink)(method, state.fastCheckoutInfo?.payer || "");
|
|
727
1111
|
const FastCheckoutConfirmDialog = state.fastCheckoutInfo && /* @__PURE__ */(0, _jsxRuntime.jsx)(_confirm.default, {
|
|
728
1112
|
onConfirm: handleFastCheckoutConfirm,
|
|
@@ -730,7 +1114,7 @@ function PaymentForm({
|
|
|
730
1114
|
title: state.fastCheckoutInfo.sourceType === "credit" ? t("payment.checkout.fastPay.credit.title") : t("payment.checkout.fastPay.title"),
|
|
731
1115
|
message: state.fastCheckoutInfo.sourceType === "credit" ? /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
|
|
732
1116
|
children: t("payment.checkout.fastPay.credit.meteringSubscriptionMessage", {
|
|
733
|
-
available: `${(0,
|
|
1117
|
+
available: `${(0, _util2.formatAmount)(state.fastCheckoutInfo?.balance || "0", paymentCurrency?.decimal || 18)} ${paymentCurrency?.symbol}`
|
|
734
1118
|
})
|
|
735
1119
|
}) : /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
|
|
736
1120
|
children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
|
|
@@ -794,7 +1178,7 @@ function PaymentForm({
|
|
|
794
1178
|
},
|
|
795
1179
|
children: t("payment.checkout.fastPay.amount")
|
|
796
1180
|
}), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, {
|
|
797
|
-
children: [(0,
|
|
1181
|
+
children: [(0, _util2.formatAmount)(state.fastCheckoutInfo.amount, paymentCurrency?.decimal || 18), " ", paymentCurrency?.symbol]
|
|
798
1182
|
})]
|
|
799
1183
|
})]
|
|
800
1184
|
})]
|
|
@@ -811,6 +1195,47 @@ function PaymentForm({
|
|
|
811
1195
|
}),
|
|
812
1196
|
confirm: t("common.confirm")
|
|
813
1197
|
});
|
|
1198
|
+
const PriceUpdatedDialog = priceUpdateInfo.open && /* @__PURE__ */(0, _jsxRuntime.jsx)(_confirm.default, {
|
|
1199
|
+
onConfirm: handlePriceUpdateConfirm,
|
|
1200
|
+
onCancel: handlePriceUpdateCancel,
|
|
1201
|
+
title: t("payment.checkout.quote.priceUpdatedTitle"),
|
|
1202
|
+
message: /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
|
|
1203
|
+
spacing: 1,
|
|
1204
|
+
children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
|
|
1205
|
+
children: t(priceUpdateInfo.reason === "rateChanged" ? "payment.checkout.quote.priceUpdatedDescriptionRate" : "payment.checkout.quote.priceUpdatedDescriptionRecalc")
|
|
1206
|
+
}), priceUpdateInfo.hasQuotes && /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
|
|
1207
|
+
spacing: 0.25,
|
|
1208
|
+
children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
|
|
1209
|
+
sx: {
|
|
1210
|
+
color: "text.secondary",
|
|
1211
|
+
fontSize: "0.7875rem"
|
|
1212
|
+
},
|
|
1213
|
+
children: t("payment.checkout.quote.priceUpdatedNewTotalLabel")
|
|
1214
|
+
}), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
|
|
1215
|
+
sx: {
|
|
1216
|
+
fontWeight: 600
|
|
1217
|
+
},
|
|
1218
|
+
children: priceUpdateInfo.total
|
|
1219
|
+
}), priceUpdateInfo.usd && /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, {
|
|
1220
|
+
sx: {
|
|
1221
|
+
color: "text.secondary"
|
|
1222
|
+
},
|
|
1223
|
+
children: ["\u2248 ", priceUpdateInfo.usd, " ", priceUpdateInfo.baseCurrency]
|
|
1224
|
+
}), priceUpdateInfo.oldTotal && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
|
|
1225
|
+
sx: {
|
|
1226
|
+
color: "text.secondary",
|
|
1227
|
+
fontSize: "0.75rem"
|
|
1228
|
+
},
|
|
1229
|
+
children: t("payment.checkout.quote.priceUpdatedOldTotal", {
|
|
1230
|
+
total: priceUpdateInfo.oldTotal
|
|
1231
|
+
})
|
|
1232
|
+
})]
|
|
1233
|
+
})]
|
|
1234
|
+
}),
|
|
1235
|
+
confirm: t("payment.checkout.quote.priceUpdatedConfirm"),
|
|
1236
|
+
cancel: t("common.cancel"),
|
|
1237
|
+
color: "primary"
|
|
1238
|
+
});
|
|
814
1239
|
const getRedirectUrl = () => {
|
|
815
1240
|
if (searchParams.redirect) {
|
|
816
1241
|
return decodeURIComponent(searchParams.redirect);
|
|
@@ -833,13 +1258,13 @@ function PaymentForm({
|
|
|
833
1258
|
size: "large",
|
|
834
1259
|
className: "cko-submit-button",
|
|
835
1260
|
onClick: () => {
|
|
836
|
-
if (state.submitting || state.paying) {
|
|
1261
|
+
if (state.submitting || state.paying || !canPay) {
|
|
837
1262
|
return;
|
|
838
1263
|
}
|
|
839
1264
|
onAction();
|
|
840
1265
|
},
|
|
841
1266
|
fullWidth: true,
|
|
842
|
-
disabled: state.stripePaying || !quantityInventoryStatus || !
|
|
1267
|
+
disabled: state.stripePaying || !quantityInventoryStatus || !canPay,
|
|
843
1268
|
children: [(state.submitting || state.paying) && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.CircularProgress, {
|
|
844
1269
|
size: 16,
|
|
845
1270
|
sx: {
|
|
@@ -873,7 +1298,13 @@ function PaymentForm({
|
|
|
873
1298
|
}),
|
|
874
1299
|
title: t("payment.customer.pastDue.alert.title")
|
|
875
1300
|
}
|
|
876
|
-
}), FastCheckoutConfirmDialog, CreditInsufficientDialog
|
|
1301
|
+
}), FastCheckoutConfirmDialog, CreditInsufficientDialog, PriceUpdatedDialog, state.priceChangeConfirm?.open && /* @__PURE__ */(0, _jsxRuntime.jsx)(_priceChangeConfirm.default, {
|
|
1302
|
+
open: true,
|
|
1303
|
+
changePercent: state.priceChangeConfirm.changePercent,
|
|
1304
|
+
onConfirm: handlePriceChangeConfirm,
|
|
1305
|
+
onCancel: handlePriceChangeCancel,
|
|
1306
|
+
loading: state.submitting
|
|
1307
|
+
})]
|
|
877
1308
|
});
|
|
878
1309
|
}
|
|
879
1310
|
return /* @__PURE__ */(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
|
|
@@ -915,10 +1346,29 @@ function PaymentForm({
|
|
|
915
1346
|
}) => /* @__PURE__ */(0, _jsxRuntime.jsx)(_currency.default, {
|
|
916
1347
|
value: field.value,
|
|
917
1348
|
currencies,
|
|
918
|
-
onChange: (id, methodId) => {
|
|
1349
|
+
onChange: async (id, methodId) => {
|
|
1350
|
+
const oldCurrencyId = field.value;
|
|
919
1351
|
field.onChange(id);
|
|
920
1352
|
setValue("payment_method", methodId);
|
|
921
1353
|
(0, _currency2.saveCurrencyPreference)(id, session?.user?.did);
|
|
1354
|
+
if (oldCurrencyId && oldCurrencyId !== id) {
|
|
1355
|
+
try {
|
|
1356
|
+
const {
|
|
1357
|
+
data
|
|
1358
|
+
} = await _api.default.put(`/api/checkout-sessions/${checkoutSession.id}/switch-currency`, {
|
|
1359
|
+
currency_id: id,
|
|
1360
|
+
payment_method_id: methodId
|
|
1361
|
+
});
|
|
1362
|
+
if (data.currency_changed && onQuoteUpdated) {
|
|
1363
|
+
onQuoteUpdated({
|
|
1364
|
+
checkoutSession: data,
|
|
1365
|
+
quotes: data.quotes
|
|
1366
|
+
});
|
|
1367
|
+
}
|
|
1368
|
+
} catch (err) {
|
|
1369
|
+
console.error("Failed to switch currency:", err);
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
922
1372
|
}
|
|
923
1373
|
})
|
|
924
1374
|
})
|
|
@@ -1096,7 +1546,7 @@ function PaymentForm({
|
|
|
1096
1546
|
},
|
|
1097
1547
|
fullWidth: true,
|
|
1098
1548
|
loading: state.submitting || state.paying,
|
|
1099
|
-
disabled: state.stripePaying || !quantityInventoryStatus || !
|
|
1549
|
+
disabled: state.stripePaying || !quantityInventoryStatus || !canPay,
|
|
1100
1550
|
children: state.submitting || state.paying ? t("payment.checkout.processing") : buttonText
|
|
1101
1551
|
})
|
|
1102
1552
|
}), ["subscription", "setup"].includes(checkoutSession.mode) && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
|
|
@@ -1158,6 +1608,12 @@ function PaymentForm({
|
|
|
1158
1608
|
}),
|
|
1159
1609
|
title: t("payment.customer.pastDue.alert.title")
|
|
1160
1610
|
}
|
|
1161
|
-
}), FastCheckoutConfirmDialog, CreditInsufficientDialog
|
|
1611
|
+
}), FastCheckoutConfirmDialog, CreditInsufficientDialog, PriceUpdatedDialog, state.priceChangeConfirm?.open && /* @__PURE__ */(0, _jsxRuntime.jsx)(_priceChangeConfirm.default, {
|
|
1612
|
+
open: true,
|
|
1613
|
+
changePercent: state.priceChangeConfirm.changePercent,
|
|
1614
|
+
onConfirm: handlePriceChangeConfirm,
|
|
1615
|
+
onCancel: handlePriceChangeCancel,
|
|
1616
|
+
loading: state.submitting
|
|
1617
|
+
})]
|
|
1162
1618
|
});
|
|
1163
1619
|
}
|
package/lib/payment/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ type Props = CheckoutContext & CheckoutCallbacks & {
|
|
|
6
6
|
error?: any;
|
|
7
7
|
showCheckoutSummary?: boolean;
|
|
8
8
|
};
|
|
9
|
-
export default function Payment({ checkoutSession, paymentMethods, paymentIntent, paymentLink, customer, completed, error, mode, onPaid, onError, onChange, goBack, action, showCheckoutSummary, }: Props): import("react").JSX.Element;
|
|
9
|
+
export default function Payment({ checkoutSession, paymentMethods, paymentIntent, paymentLink, customer, completed, error, mode, onPaid, onError, onChange, goBack, action, showCheckoutSummary, quotes, rateUnavailable, rateError, }: Props): import("react").JSX.Element;
|
|
10
10
|
type RootProps = {
|
|
11
11
|
mode: LiteralUnion<'standalone' | 'inline' | 'popup', string>;
|
|
12
12
|
} & BoxProps;
|