@blocklet/payment-react 1.25.10 → 1.26.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/es/checkout-v2/checkout-v2.d.ts +2 -0
- package/es/checkout-v2/checkout-v2.js +121 -0
- package/es/checkout-v2/components/dialogs/checkout-dialogs.d.ts +1 -0
- package/es/checkout-v2/components/dialogs/checkout-dialogs.js +106 -0
- package/es/checkout-v2/components/left/billing-toggle.d.ts +6 -0
- package/es/checkout-v2/components/left/billing-toggle.js +118 -0
- package/es/checkout-v2/components/left/cross-sell-card.d.ts +10 -0
- package/es/checkout-v2/components/left/cross-sell-card.js +167 -0
- package/es/checkout-v2/components/left/product-item-card.d.ts +26 -0
- package/es/checkout-v2/components/left/product-item-card.js +571 -0
- package/es/checkout-v2/components/left/promotion-input.d.ts +19 -0
- package/es/checkout-v2/components/left/promotion-input.js +178 -0
- package/es/checkout-v2/components/left/staking-breakdown.d.ts +9 -0
- package/es/checkout-v2/components/left/staking-breakdown.js +48 -0
- package/es/checkout-v2/components/left/trial-info.d.ts +13 -0
- package/es/checkout-v2/components/left/trial-info.js +48 -0
- package/es/checkout-v2/components/right/currency-grid.d.ts +8 -0
- package/es/checkout-v2/components/right/currency-grid.js +48 -0
- package/es/checkout-v2/components/right/customer-info-card.d.ts +17 -0
- package/es/checkout-v2/components/right/customer-info-card.js +156 -0
- package/es/checkout-v2/components/right/status-feedback.d.ts +7 -0
- package/es/checkout-v2/components/right/status-feedback.js +17 -0
- package/es/checkout-v2/components/right/submit-button.d.ts +10 -0
- package/es/checkout-v2/components/right/submit-button.js +29 -0
- package/es/checkout-v2/components/right/subscription-disclaimer.d.ts +11 -0
- package/es/checkout-v2/components/right/subscription-disclaimer.js +8 -0
- package/es/checkout-v2/components/shared/exchange-rate-footer.d.ts +23 -0
- package/es/checkout-v2/components/shared/exchange-rate-footer.js +182 -0
- package/es/checkout-v2/components/shared/scenario-badge.d.ts +6 -0
- package/es/checkout-v2/components/shared/scenario-badge.js +47 -0
- package/es/checkout-v2/components/shared/total-display.d.ts +7 -0
- package/es/checkout-v2/components/shared/total-display.js +84 -0
- package/es/checkout-v2/index.d.ts +2 -0
- package/es/checkout-v2/index.js +1 -0
- package/es/checkout-v2/layouts/checkout-layout.d.ts +7 -0
- package/es/checkout-v2/layouts/checkout-layout.js +226 -0
- package/es/checkout-v2/panels/left/composite-panel.d.ts +1 -0
- package/es/checkout-v2/panels/left/composite-panel.js +423 -0
- package/es/checkout-v2/panels/left/credit-topup-panel.d.ts +1 -0
- package/es/checkout-v2/panels/left/credit-topup-panel.js +615 -0
- package/es/checkout-v2/panels/left/scenario-router.d.ts +1 -0
- package/es/checkout-v2/panels/left/scenario-router.js +19 -0
- package/es/checkout-v2/panels/right/payment-panel.d.ts +1 -0
- package/es/checkout-v2/panels/right/payment-panel.js +644 -0
- package/es/checkout-v2/types.d.ts +15 -0
- package/es/checkout-v2/types.js +0 -0
- package/es/checkout-v2/utils/format.d.ts +59 -0
- package/es/checkout-v2/utils/format.js +125 -0
- package/es/checkout-v2/utils/scenario-detector.d.ts +3 -0
- package/es/checkout-v2/utils/scenario-detector.js +17 -0
- package/es/checkout-v2/views/error-view.d.ts +7 -0
- package/es/checkout-v2/views/error-view.js +269 -0
- package/es/checkout-v2/views/loading-view.d.ts +5 -0
- package/es/checkout-v2/views/loading-view.js +158 -0
- package/es/checkout-v2/views/success-view.d.ts +29 -0
- package/es/checkout-v2/views/success-view.js +614 -0
- package/es/components/phone-field.d.ts +14 -0
- package/es/components/phone-field.js +96 -0
- package/es/index.d.ts +3 -1
- package/es/index.js +3 -1
- package/es/locales/en.js +45 -6
- package/es/locales/zh.js +45 -6
- package/es/payment/form/index.js +10 -1
- package/lib/checkout-v2/checkout-v2.d.ts +2 -0
- package/lib/checkout-v2/checkout-v2.js +151 -0
- package/lib/checkout-v2/components/dialogs/checkout-dialogs.d.ts +1 -0
- package/lib/checkout-v2/components/dialogs/checkout-dialogs.js +131 -0
- package/lib/checkout-v2/components/left/billing-toggle.d.ts +6 -0
- package/lib/checkout-v2/components/left/billing-toggle.js +126 -0
- package/lib/checkout-v2/components/left/cross-sell-card.d.ts +10 -0
- package/lib/checkout-v2/components/left/cross-sell-card.js +257 -0
- package/lib/checkout-v2/components/left/product-item-card.d.ts +26 -0
- package/lib/checkout-v2/components/left/product-item-card.js +738 -0
- package/lib/checkout-v2/components/left/promotion-input.d.ts +19 -0
- package/lib/checkout-v2/components/left/promotion-input.js +220 -0
- package/lib/checkout-v2/components/left/staking-breakdown.d.ts +9 -0
- package/lib/checkout-v2/components/left/staking-breakdown.js +96 -0
- package/lib/checkout-v2/components/left/trial-info.d.ts +13 -0
- package/lib/checkout-v2/components/left/trial-info.js +82 -0
- package/lib/checkout-v2/components/right/currency-grid.d.ts +8 -0
- package/lib/checkout-v2/components/right/currency-grid.js +96 -0
- package/lib/checkout-v2/components/right/customer-info-card.d.ts +17 -0
- package/lib/checkout-v2/components/right/customer-info-card.js +246 -0
- package/lib/checkout-v2/components/right/status-feedback.d.ts +7 -0
- package/lib/checkout-v2/components/right/status-feedback.js +30 -0
- package/lib/checkout-v2/components/right/submit-button.d.ts +10 -0
- package/lib/checkout-v2/components/right/submit-button.js +35 -0
- package/lib/checkout-v2/components/right/subscription-disclaimer.d.ts +11 -0
- package/lib/checkout-v2/components/right/subscription-disclaimer.js +33 -0
- package/lib/checkout-v2/components/shared/exchange-rate-footer.d.ts +23 -0
- package/lib/checkout-v2/components/shared/exchange-rate-footer.js +282 -0
- package/lib/checkout-v2/components/shared/scenario-badge.d.ts +6 -0
- package/lib/checkout-v2/components/shared/scenario-badge.js +57 -0
- package/lib/checkout-v2/components/shared/total-display.d.ts +7 -0
- package/lib/checkout-v2/components/shared/total-display.js +154 -0
- package/lib/checkout-v2/index.d.ts +2 -0
- package/lib/checkout-v2/index.js +13 -0
- package/lib/checkout-v2/layouts/checkout-layout.d.ts +7 -0
- package/lib/checkout-v2/layouts/checkout-layout.js +308 -0
- package/lib/checkout-v2/panels/left/composite-panel.d.ts +1 -0
- package/lib/checkout-v2/panels/left/composite-panel.js +515 -0
- package/lib/checkout-v2/panels/left/credit-topup-panel.d.ts +1 -0
- package/lib/checkout-v2/panels/left/credit-topup-panel.js +799 -0
- package/lib/checkout-v2/panels/left/scenario-router.d.ts +1 -0
- package/lib/checkout-v2/panels/left/scenario-router.js +29 -0
- package/lib/checkout-v2/panels/right/payment-panel.d.ts +1 -0
- package/lib/checkout-v2/panels/right/payment-panel.js +906 -0
- package/lib/checkout-v2/types.d.ts +15 -0
- package/lib/checkout-v2/types.js +1 -0
- package/lib/checkout-v2/utils/format.d.ts +59 -0
- package/lib/checkout-v2/utils/format.js +158 -0
- package/lib/checkout-v2/utils/scenario-detector.d.ts +3 -0
- package/lib/checkout-v2/utils/scenario-detector.js +23 -0
- package/lib/checkout-v2/views/error-view.d.ts +7 -0
- package/lib/checkout-v2/views/error-view.js +321 -0
- package/lib/checkout-v2/views/loading-view.d.ts +5 -0
- package/lib/checkout-v2/views/loading-view.js +168 -0
- package/lib/checkout-v2/views/success-view.d.ts +29 -0
- package/lib/checkout-v2/views/success-view.js +735 -0
- package/lib/components/phone-field.d.ts +14 -0
- package/lib/components/phone-field.js +130 -0
- package/lib/index.d.ts +3 -1
- package/lib/index.js +8 -0
- package/lib/locales/en.js +45 -6
- package/lib/locales/zh.js +45 -6
- package/lib/payment/form/index.js +10 -1
- package/package.json +4 -3
- package/src/checkout-v2/checkout-v2.tsx +155 -0
- package/src/checkout-v2/components/dialogs/checkout-dialogs.tsx +134 -0
- package/src/checkout-v2/components/left/billing-toggle.tsx +122 -0
- package/src/checkout-v2/components/left/cross-sell-card.tsx +170 -0
- package/src/checkout-v2/components/left/product-item-card.tsx +634 -0
- package/src/checkout-v2/components/left/promotion-input.tsx +207 -0
- package/src/checkout-v2/components/left/staking-breakdown.tsx +57 -0
- package/src/checkout-v2/components/left/trial-info.tsx +63 -0
- package/src/checkout-v2/components/right/currency-grid.tsx +59 -0
- package/src/checkout-v2/components/right/customer-info-card.tsx +214 -0
- package/src/checkout-v2/components/right/status-feedback.tsx +35 -0
- package/src/checkout-v2/components/right/submit-button.tsx +37 -0
- package/src/checkout-v2/components/right/subscription-disclaimer.tsx +27 -0
- package/src/checkout-v2/components/shared/exchange-rate-footer.tsx +221 -0
- package/src/checkout-v2/components/shared/scenario-badge.tsx +51 -0
- package/src/checkout-v2/components/shared/total-display.tsx +112 -0
- package/src/checkout-v2/index.ts +2 -0
- package/src/checkout-v2/layouts/checkout-layout.tsx +232 -0
- package/src/checkout-v2/panels/left/composite-panel.tsx +465 -0
- package/src/checkout-v2/panels/left/credit-topup-panel.tsx +681 -0
- package/src/checkout-v2/panels/left/scenario-router.tsx +22 -0
- package/src/checkout-v2/panels/right/payment-panel.tsx +703 -0
- package/src/checkout-v2/types.ts +18 -0
- package/src/checkout-v2/utils/format.ts +204 -0
- package/src/checkout-v2/utils/scenario-detector.ts +30 -0
- package/src/checkout-v2/views/error-view.tsx +293 -0
- package/src/checkout-v2/views/loading-view.tsx +162 -0
- package/src/checkout-v2/views/success-view.tsx +770 -0
- package/src/components/phone-field.tsx +119 -0
- package/src/index.ts +3 -0
- package/src/locales/en.tsx +45 -4
- package/src/locales/zh.tsx +43 -4
- package/src/payment/form/index.tsx +16 -1
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { useEffect, useRef, useCallback } from 'react';
|
|
2
|
+
import { Box, InputBase, InputAdornment, Typography } from '@mui/material';
|
|
3
|
+
import { defaultCountries, usePhoneInput } from 'react-international-phone';
|
|
4
|
+
import { useMount } from 'ahooks';
|
|
5
|
+
|
|
6
|
+
import CountrySelect from './country-select';
|
|
7
|
+
import { isValidCountry } from '../libs/util';
|
|
8
|
+
import { getPhoneUtil } from '../libs/phone-validator';
|
|
9
|
+
|
|
10
|
+
export interface PhoneFieldProps {
|
|
11
|
+
value: string;
|
|
12
|
+
country: string;
|
|
13
|
+
onChange: (phone: string) => void;
|
|
14
|
+
onCountryChange: (country: string) => void;
|
|
15
|
+
label: string;
|
|
16
|
+
error?: string;
|
|
17
|
+
onBlur?: () => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Phone input with country flag + dial code selector.
|
|
22
|
+
* Standalone version of V1 PhoneInput — no react-hook-form dependency.
|
|
23
|
+
*/
|
|
24
|
+
export default function PhoneField({
|
|
25
|
+
value,
|
|
26
|
+
country: externalCountry,
|
|
27
|
+
onChange,
|
|
28
|
+
onCountryChange,
|
|
29
|
+
label,
|
|
30
|
+
error = undefined,
|
|
31
|
+
onBlur = undefined,
|
|
32
|
+
}: PhoneFieldProps) {
|
|
33
|
+
const isUpdatingRef = useRef(false);
|
|
34
|
+
|
|
35
|
+
const safeUpdate = useCallback((callback: () => void) => {
|
|
36
|
+
if (isUpdatingRef.current) return;
|
|
37
|
+
try {
|
|
38
|
+
isUpdatingRef.current = true;
|
|
39
|
+
callback();
|
|
40
|
+
} finally {
|
|
41
|
+
requestAnimationFrame(() => {
|
|
42
|
+
isUpdatingRef.current = false;
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}, []);
|
|
46
|
+
|
|
47
|
+
const { phone, handlePhoneValueChange, inputRef, country, setCountry } = usePhoneInput({
|
|
48
|
+
defaultCountry: isValidCountry(externalCountry) ? externalCountry : 'us',
|
|
49
|
+
value: value || '',
|
|
50
|
+
countries: defaultCountries,
|
|
51
|
+
onChange: (data) => {
|
|
52
|
+
safeUpdate(() => {
|
|
53
|
+
onChange(data.phone);
|
|
54
|
+
onCountryChange(data.country);
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Preload phone validator (matches V1 behavior)
|
|
60
|
+
useMount(() => {
|
|
61
|
+
getPhoneUtil().catch((err) => {
|
|
62
|
+
console.error('Failed to preload phone validator:', err);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Sync external country changes (e.g. from postal code CountrySelect)
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
if (!externalCountry || externalCountry === country) return;
|
|
69
|
+
safeUpdate(() => {
|
|
70
|
+
setCountry(externalCountry);
|
|
71
|
+
});
|
|
72
|
+
}, [externalCountry, country, setCountry, safeUpdate]);
|
|
73
|
+
|
|
74
|
+
const handleCountryChange = useCallback(
|
|
75
|
+
(v: string) => {
|
|
76
|
+
safeUpdate(() => {
|
|
77
|
+
setCountry(v);
|
|
78
|
+
onCountryChange(v);
|
|
79
|
+
});
|
|
80
|
+
},
|
|
81
|
+
[setCountry, safeUpdate, onCountryChange]
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<Box sx={{ mb: 1.5 }}>
|
|
86
|
+
<Typography sx={{ fontSize: 13, fontWeight: 600, color: 'text.primary', mb: 0.5 }}>{label}</Typography>
|
|
87
|
+
<InputBase
|
|
88
|
+
fullWidth
|
|
89
|
+
value={phone}
|
|
90
|
+
onChange={handlePhoneValueChange}
|
|
91
|
+
onBlur={onBlur}
|
|
92
|
+
type="tel"
|
|
93
|
+
inputRef={inputRef}
|
|
94
|
+
startAdornment={
|
|
95
|
+
<InputAdornment position="start" sx={{ mr: 0.25, ml: -0.5 }}>
|
|
96
|
+
<CountrySelect
|
|
97
|
+
value={country}
|
|
98
|
+
onChange={handleCountryChange}
|
|
99
|
+
sx={{
|
|
100
|
+
'.MuiOutlinedInput-notchedOutline': { borderColor: 'transparent !important' },
|
|
101
|
+
'& .MuiSelect-select': { py: 0, pr: '20px !important' },
|
|
102
|
+
}}
|
|
103
|
+
showDialCode
|
|
104
|
+
/>
|
|
105
|
+
</InputAdornment>
|
|
106
|
+
}
|
|
107
|
+
sx={{
|
|
108
|
+
bgcolor: (theme) => (theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.06)' : 'grey.50'),
|
|
109
|
+
borderRadius: '8px',
|
|
110
|
+
px: 1.5,
|
|
111
|
+
py: 0.75,
|
|
112
|
+
fontSize: 14,
|
|
113
|
+
'& .MuiInputBase-input': { p: 0 },
|
|
114
|
+
}}
|
|
115
|
+
/>
|
|
116
|
+
{error && <Typography sx={{ fontSize: 12, color: 'error.main', mt: 0.25 }}>{error}</Typography>}
|
|
117
|
+
</Box>
|
|
118
|
+
);
|
|
119
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -46,6 +46,7 @@ import PromotionCode from './components/promotion-code';
|
|
|
46
46
|
import SourceDataViewer from './components/source-data-viewer';
|
|
47
47
|
import SlippageConfig from './components/slippage-config';
|
|
48
48
|
import DynamicPricingUnavailable from './components/dynamic-pricing-unavailable';
|
|
49
|
+
import { CheckoutV2 } from './checkout-v2';
|
|
49
50
|
|
|
50
51
|
export { PaymentThemeProvider } from './theme';
|
|
51
52
|
|
|
@@ -116,8 +117,10 @@ export {
|
|
|
116
117
|
DynamicPricingUnavailable,
|
|
117
118
|
PromotionSection,
|
|
118
119
|
TotalSection,
|
|
120
|
+
CheckoutV2,
|
|
119
121
|
};
|
|
120
122
|
|
|
121
123
|
export type { CountrySelectProps } from './components/country-select';
|
|
122
124
|
export type { StripePaymentActionProps } from './components/stripe-payment-action';
|
|
123
125
|
export type { SlippageConfigValue, SlippageConfigProps } from './components/slippage-config';
|
|
126
|
+
export type { CheckoutV2Props } from './checkout-v2';
|
package/src/locales/en.tsx
CHANGED
|
@@ -21,6 +21,7 @@ export default flat({
|
|
|
21
21
|
setup: 'Setup',
|
|
22
22
|
amount: 'Amount',
|
|
23
23
|
total: 'Total',
|
|
24
|
+
totalDue: 'Total Due',
|
|
24
25
|
subtotal: 'Subtotal',
|
|
25
26
|
status: 'Status',
|
|
26
27
|
livemode: 'Test mode',
|
|
@@ -87,6 +88,7 @@ export default flat({
|
|
|
87
88
|
viewConsumptionDetail: 'Consumption Detail',
|
|
88
89
|
customer: 'Customer',
|
|
89
90
|
currency: 'Currency',
|
|
91
|
+
network: 'Network',
|
|
90
92
|
custom: 'Custom',
|
|
91
93
|
description: 'Description',
|
|
92
94
|
statementDescriptor: 'Statement descriptor',
|
|
@@ -282,11 +284,14 @@ export default flat({
|
|
|
282
284
|
free: "{count} {interval}{count > 1 ? 's' : ''} free",
|
|
283
285
|
least: 'continue with at least',
|
|
284
286
|
completed: {
|
|
285
|
-
payment: '
|
|
286
|
-
subscription: '
|
|
287
|
-
setup: '
|
|
288
|
-
donate: '
|
|
287
|
+
payment: 'Purchase successful',
|
|
288
|
+
subscription: 'Subscription successful',
|
|
289
|
+
setup: 'Subscription successful',
|
|
290
|
+
donate: 'Donation successful',
|
|
289
291
|
tip: 'A payment to {payee} has been completed. You can view the details of this payment in your account.',
|
|
292
|
+
summary: {
|
|
293
|
+
paid: "You've paid {amount}",
|
|
294
|
+
},
|
|
290
295
|
},
|
|
291
296
|
vendor: {
|
|
292
297
|
accountRequired: 'This action requires a unified account. Please switch accounts and try again.',
|
|
@@ -375,6 +380,7 @@ export default flat({
|
|
|
375
380
|
periodic: 'Grant {amount} every {interval}.',
|
|
376
381
|
withRefresh: 'Grant {amount} every {interval}; unused credits expire with the next grant.',
|
|
377
382
|
},
|
|
383
|
+
topupDescription: 'Purchase {creditName} at {unitPrice} per credit.',
|
|
378
384
|
},
|
|
379
385
|
expired: {
|
|
380
386
|
title: 'Expired Link',
|
|
@@ -390,9 +396,44 @@ export default flat({
|
|
|
390
396
|
title: 'Nothing to show here',
|
|
391
397
|
description: 'It seems this checkout session is not configured properly',
|
|
392
398
|
},
|
|
399
|
+
error: {
|
|
400
|
+
title: 'Something went wrong',
|
|
401
|
+
},
|
|
393
402
|
orderSummary: 'Order Summary',
|
|
403
|
+
orderSummarySubtitle: 'Items included in this purchase',
|
|
394
404
|
paymentDetails: 'Payment Details',
|
|
395
405
|
productListTotal: 'Includes {total} items',
|
|
406
|
+
headerTitle: {
|
|
407
|
+
subscribe: 'Subscribe to {name}',
|
|
408
|
+
purchase: 'Purchase {name}',
|
|
409
|
+
},
|
|
410
|
+
planFeatures: 'Plan Features',
|
|
411
|
+
typeBadge: {
|
|
412
|
+
subscription: 'SUBSCRIPTION',
|
|
413
|
+
topup: 'TOP-UP',
|
|
414
|
+
oneTime: 'ONE-TIME',
|
|
415
|
+
},
|
|
416
|
+
subtitle: {
|
|
417
|
+
subscriptionInterval: '{interval} subscription',
|
|
418
|
+
creditsTopup: 'Credits top-up',
|
|
419
|
+
addonFor: 'Add-on for {product}',
|
|
420
|
+
oneTime: 'One-time purchase',
|
|
421
|
+
},
|
|
422
|
+
creditTopup: {
|
|
423
|
+
title: 'Get {name}',
|
|
424
|
+
question: 'How many {symbol} do you want?',
|
|
425
|
+
credits: 'Credits',
|
|
426
|
+
increment: 'Increments of {step} {symbol}',
|
|
427
|
+
validFor: 'Credits are valid for {duration} {unit} after purchase.',
|
|
428
|
+
willReceive: "You'll receive",
|
|
429
|
+
packInfo: 'Includes {packs} credit packs ({perPack} per pack)',
|
|
430
|
+
autoMatch: "We'll automatically match the closest credit pack.",
|
|
431
|
+
autoMatchTooltip: '{symbol} are sold in packs of {step}',
|
|
432
|
+
pendingWarning:
|
|
433
|
+
'You have a usage overage of {pendingAmount}. You need at least {minCredits} to restore access.',
|
|
434
|
+
pendingEnough:
|
|
435
|
+
'You have a usage overage of {pendingAmount}. After covering the overage, your available balance will be {availableAmount}.',
|
|
436
|
+
},
|
|
396
437
|
promotion: {
|
|
397
438
|
add_code: 'Add promotion code',
|
|
398
439
|
enter_code: 'Enter promotion code',
|
package/src/locales/zh.tsx
CHANGED
|
@@ -21,6 +21,7 @@ export default flat({
|
|
|
21
21
|
accessDenied: '您无权访问其他客户的数据',
|
|
22
22
|
amount: '金额',
|
|
23
23
|
total: '总计',
|
|
24
|
+
totalDue: '应付总额',
|
|
24
25
|
subtotal: '小计',
|
|
25
26
|
status: '状态',
|
|
26
27
|
livemode: '测试模式',
|
|
@@ -87,6 +88,7 @@ export default flat({
|
|
|
87
88
|
viewConsumptionDetail: '消费详情',
|
|
88
89
|
customer: '客户',
|
|
89
90
|
currency: '币种',
|
|
91
|
+
network: '网络',
|
|
90
92
|
custom: '自定义',
|
|
91
93
|
description: '描述',
|
|
92
94
|
statementDescriptor: '声明描述',
|
|
@@ -278,11 +280,14 @@ export default flat({
|
|
|
278
280
|
free: '免费试用 {count} {interval}',
|
|
279
281
|
least: '至少',
|
|
280
282
|
completed: {
|
|
281
|
-
payment: '
|
|
282
|
-
subscription: '
|
|
283
|
-
setup: '
|
|
284
|
-
donate: '
|
|
283
|
+
payment: '购买成功',
|
|
284
|
+
subscription: '订阅成功',
|
|
285
|
+
setup: '订阅成功',
|
|
286
|
+
donate: '捐赠成功',
|
|
285
287
|
tip: '向 {payee} 的付款已完成。您可以在您的账户中查看此付款的详细信息。',
|
|
288
|
+
summary: {
|
|
289
|
+
paid: '本次支付 {amount}',
|
|
290
|
+
},
|
|
286
291
|
},
|
|
287
292
|
vendor: {
|
|
288
293
|
accountRequired: '您当前使用的是非统一账户登录,此服务需要您使用统一账户。请切换到统一账户登录后重试。',
|
|
@@ -405,14 +410,48 @@ export default flat({
|
|
|
405
410
|
periodic: '每{interval}发放 {amount}。',
|
|
406
411
|
withRefresh: '每{interval}发放 {amount},未使用额度将在下次发放时过期。',
|
|
407
412
|
},
|
|
413
|
+
topupDescription: '购买 {creditName},单价 {unitPrice}/额度。',
|
|
408
414
|
},
|
|
409
415
|
emptyItems: {
|
|
410
416
|
title: '没有任何购买项目',
|
|
411
417
|
description: '可能这个付款链接没有正确配置',
|
|
412
418
|
},
|
|
419
|
+
error: {
|
|
420
|
+
title: '出了点问题',
|
|
421
|
+
},
|
|
413
422
|
orderSummary: '订单概览',
|
|
423
|
+
orderSummarySubtitle: '本次交易包含以下项目',
|
|
414
424
|
paymentDetails: '支付信息',
|
|
415
425
|
productListTotal: '包括 {total} 项',
|
|
426
|
+
headerTitle: {
|
|
427
|
+
subscribe: '订阅 {name}',
|
|
428
|
+
purchase: '购买 {name}',
|
|
429
|
+
},
|
|
430
|
+
planFeatures: '功能特性',
|
|
431
|
+
typeBadge: {
|
|
432
|
+
subscription: '订阅',
|
|
433
|
+
topup: '充值',
|
|
434
|
+
oneTime: '一次性购买',
|
|
435
|
+
},
|
|
436
|
+
subtitle: {
|
|
437
|
+
subscriptionInterval: '{interval}订阅',
|
|
438
|
+
creditsTopup: '余额充值',
|
|
439
|
+
addonFor: '{product} 的附加项',
|
|
440
|
+
oneTime: '一次性购买',
|
|
441
|
+
},
|
|
442
|
+
creditTopup: {
|
|
443
|
+
title: '购买 {name}',
|
|
444
|
+
question: '你需要多少 {symbol}?',
|
|
445
|
+
credits: '额度',
|
|
446
|
+
increment: '每次增减 {step} {symbol}',
|
|
447
|
+
validFor: '额度在购买后 {duration} {unit}内有效。',
|
|
448
|
+
willReceive: '你将获得',
|
|
449
|
+
packInfo: '包含 {packs} 个额度包(每包 {perPack})',
|
|
450
|
+
autoMatch: '我们将自动匹配最接近的额度包。',
|
|
451
|
+
autoMatchTooltip: '{symbol} 按每包 {step} 个出售',
|
|
452
|
+
pendingWarning: '您有 {pendingAmount} 的使用超额,至少需要购买 {minCredits} 才能恢复访问。',
|
|
453
|
+
pendingEnough: '您有 {pendingAmount} 的使用超额,扣除后您的可用余额将为 {availableAmount}。',
|
|
454
|
+
},
|
|
416
455
|
connectModal: {
|
|
417
456
|
title: '{action}',
|
|
418
457
|
scan: '使用以下方式完成本次支付',
|
|
@@ -242,6 +242,9 @@ export default function PaymentForm({
|
|
|
242
242
|
} = useFormContext();
|
|
243
243
|
const errorRef = useRef<HTMLDivElement | null>(null);
|
|
244
244
|
const processingRef = useRef(false);
|
|
245
|
+
// Stable idempotency key: reuse same Quote on retry (intent: "retry with same Quote")
|
|
246
|
+
const idempotencyKeyRef = useRef('');
|
|
247
|
+
const sessionFingerprintRef = useRef('');
|
|
245
248
|
const quantityInventoryStatus = useMemo(() => {
|
|
246
249
|
let status = true;
|
|
247
250
|
for (const item of checkoutSession.line_items) {
|
|
@@ -975,10 +978,22 @@ export default function PaymentForm({
|
|
|
975
978
|
checkoutSession.line_items?.find((item: TLineItemExpanded) => (item as any)?.exchange_rate)?.exchange_rate ||
|
|
976
979
|
undefined;
|
|
977
980
|
|
|
981
|
+
// Stable idempotency key: only regenerate when payment context changes
|
|
982
|
+
// Same context retry → reuse Quote (intent: "Failed Payments don't invalidate Quote")
|
|
983
|
+
const items = (checkoutSession.line_items || []) as TLineItemExpanded[];
|
|
984
|
+
const itemsSig = items
|
|
985
|
+
.map((i: TLineItemExpanded) => `${(i as any).upsell_price_id || i.price_id}:${i.quantity}`)
|
|
986
|
+
.join('|');
|
|
987
|
+
const fingerprint = `${checkoutSession.id}-${paymentCurrency?.id}-${itemsSig}`;
|
|
988
|
+
if (fingerprint !== sessionFingerprintRef.current || !idempotencyKeyRef.current) {
|
|
989
|
+
sessionFingerprintRef.current = fingerprint;
|
|
990
|
+
idempotencyKeyRef.current = generateIdempotencyKey(checkoutSession.id, paymentCurrency?.id || '');
|
|
991
|
+
}
|
|
992
|
+
|
|
978
993
|
const payload = {
|
|
979
994
|
...data,
|
|
980
995
|
// Final Freeze: Include these for new quote creation at submit
|
|
981
|
-
idempotency_key:
|
|
996
|
+
idempotency_key: idempotencyKeyRef.current,
|
|
982
997
|
preview_rate: (previewRate as unknown as string) || undefined,
|
|
983
998
|
price_confirmed: state.priceChangeConfirm?.formData ? true : undefined,
|
|
984
999
|
};
|