@blocklet/payment-react 1.19.18 → 1.19.20
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 +313 -0
- package/es/checkout/form.js +18 -2
- package/es/components/auto-topup/index.d.ts +14 -0
- package/es/components/auto-topup/index.js +417 -0
- package/es/components/auto-topup/modal.d.ts +35 -0
- package/es/components/auto-topup/modal.js +734 -0
- package/es/components/auto-topup/product-card.d.ts +13 -0
- package/es/components/auto-topup/product-card.js +173 -0
- package/es/components/collapse.d.ts +13 -0
- package/es/components/collapse.js +76 -0
- package/es/components/input.d.ts +2 -1
- package/es/components/input.js +64 -13
- package/es/components/label.d.ts +2 -1
- package/es/components/label.js +2 -1
- package/es/index.d.ts +4 -1
- package/es/index.js +7 -1
- package/es/libs/util.js +2 -1
- package/es/locales/en.js +56 -0
- package/es/locales/zh.js +56 -0
- package/es/payment/form/index.js +17 -1
- package/es/payment/product-item.js +17 -10
- package/lib/checkout/form.js +18 -2
- package/lib/components/auto-topup/index.d.ts +14 -0
- package/lib/components/auto-topup/index.js +451 -0
- package/lib/components/auto-topup/modal.d.ts +35 -0
- package/lib/components/auto-topup/modal.js +803 -0
- package/lib/components/auto-topup/product-card.d.ts +13 -0
- package/lib/components/auto-topup/product-card.js +149 -0
- package/lib/components/collapse.d.ts +13 -0
- package/lib/components/collapse.js +74 -0
- package/lib/components/input.d.ts +2 -1
- package/lib/components/input.js +66 -24
- package/lib/components/label.d.ts +2 -1
- package/lib/components/label.js +3 -1
- package/lib/index.d.ts +4 -1
- package/lib/index.js +24 -0
- package/lib/libs/util.js +2 -1
- package/lib/locales/en.js +56 -0
- package/lib/locales/zh.js +56 -0
- package/lib/payment/form/index.js +17 -1
- package/lib/payment/product-item.js +18 -10
- package/package.json +9 -9
- package/src/checkout/form.tsx +21 -2
- package/src/components/auto-topup/index.tsx +449 -0
- package/src/components/auto-topup/modal.tsx +773 -0
- package/src/components/auto-topup/product-card.tsx +156 -0
- package/src/components/collapse.tsx +82 -0
- package/src/components/input.tsx +71 -22
- package/src/components/label.tsx +8 -2
- package/src/index.ts +7 -0
- package/src/libs/util.ts +1 -0
- package/src/locales/en.tsx +59 -0
- package/src/locales/zh.tsx +57 -0
- package/src/payment/form/index.tsx +19 -1
- package/src/payment/product-item.tsx +18 -11
|
@@ -21,6 +21,7 @@ var _util = require("@ocap/util");
|
|
|
21
21
|
var _DID = _interopRequireDefault(require("@arcblock/ux/lib/DID"));
|
|
22
22
|
var _isEmpty = _interopRequireDefault(require("lodash/isEmpty"));
|
|
23
23
|
var _iconsMaterial = require("@mui/icons-material");
|
|
24
|
+
var _withTracker = require("@arcblock/ux/lib/withTracker");
|
|
24
25
|
var _input = _interopRequireDefault(require("../../components/input"));
|
|
25
26
|
var _label = _interopRequireDefault(require("../../components/label"));
|
|
26
27
|
var _payment = require("../../contexts/payment");
|
|
@@ -136,6 +137,7 @@ function PaymentForm({
|
|
|
136
137
|
trigger
|
|
137
138
|
} = (0, _reactHookForm.useFormContext)();
|
|
138
139
|
const errorRef = (0, _react.useRef)(null);
|
|
140
|
+
const processingRef = (0, _react.useRef)(false);
|
|
139
141
|
const quantityInventoryStatus = (0, _react.useMemo)(() => {
|
|
140
142
|
let status = true;
|
|
141
143
|
for (const item of checkoutSession.line_items) {
|
|
@@ -287,6 +289,10 @@ function PaymentForm({
|
|
|
287
289
|
return true;
|
|
288
290
|
}, [session?.user, method, checkoutSession]);
|
|
289
291
|
const handleConnected = async () => {
|
|
292
|
+
if (processingRef.current) {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
processingRef.current = true;
|
|
290
296
|
setState({
|
|
291
297
|
paying: true
|
|
292
298
|
});
|
|
@@ -300,11 +306,21 @@ function PaymentForm({
|
|
|
300
306
|
onPaid(result);
|
|
301
307
|
}
|
|
302
308
|
} catch (err) {
|
|
303
|
-
|
|
309
|
+
const errorMessage = (0, _util2.formatError)(err);
|
|
310
|
+
const payFailedEvent = {
|
|
311
|
+
action: "payFailed",
|
|
312
|
+
// @ts-ignore 后续升级的话就会报错了,移除这个 lint 即可
|
|
313
|
+
mode: checkoutSession.mode,
|
|
314
|
+
errorMessage,
|
|
315
|
+
success: false
|
|
316
|
+
};
|
|
317
|
+
_withTracker.ReactGA.event(payFailedEvent.action, payFailedEvent);
|
|
318
|
+
_Toast.default.error(errorMessage);
|
|
304
319
|
} finally {
|
|
305
320
|
setState({
|
|
306
321
|
paying: false
|
|
307
322
|
});
|
|
323
|
+
processingRef.current = false;
|
|
308
324
|
}
|
|
309
325
|
};
|
|
310
326
|
(0, _react.useEffect)(() => {
|
|
@@ -37,7 +37,8 @@ function ProductItem({
|
|
|
37
37
|
locale
|
|
38
38
|
} = (0, _context.useLocaleContext)();
|
|
39
39
|
const {
|
|
40
|
-
settings
|
|
40
|
+
settings,
|
|
41
|
+
setPayable
|
|
41
42
|
} = (0, _payment.usePaymentContext)();
|
|
42
43
|
const pricing = (0, _util.formatLineItemPricing)(item, currency, {
|
|
43
44
|
trialEnd,
|
|
@@ -56,7 +57,14 @@ function ProductItem({
|
|
|
56
57
|
const minQuantity = Math.max(adjustableQuantity.minimum || 1, 1);
|
|
57
58
|
const quantityAvailable = Math.min(item.price.quantity_limit_per_checkout, item.price.quantity_available);
|
|
58
59
|
const maxQuantity = quantityAvailable ? Math.min(adjustableQuantity.maximum || Infinity, quantityAvailable) : adjustableQuantity.maximum || Infinity;
|
|
60
|
+
const localQuantityNum = localQuantity || 0;
|
|
59
61
|
const handleQuantityChange = newQuantity => {
|
|
62
|
+
if (!newQuantity) {
|
|
63
|
+
setLocalQuantity(void 0);
|
|
64
|
+
setPayable(false);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
setPayable(true);
|
|
60
68
|
if (newQuantity >= minQuantity && newQuantity <= maxQuantity) {
|
|
61
69
|
if ((0, _util.formatQuantityInventory)(item.price, newQuantity, locale)) {
|
|
62
70
|
return;
|
|
@@ -66,17 +74,17 @@ function ProductItem({
|
|
|
66
74
|
}
|
|
67
75
|
};
|
|
68
76
|
const handleQuantityIncrease = () => {
|
|
69
|
-
if (
|
|
70
|
-
handleQuantityChange(
|
|
77
|
+
if (localQuantityNum < maxQuantity) {
|
|
78
|
+
handleQuantityChange(localQuantityNum + 1);
|
|
71
79
|
}
|
|
72
80
|
};
|
|
73
81
|
const handleQuantityDecrease = () => {
|
|
74
|
-
if (
|
|
75
|
-
handleQuantityChange(
|
|
82
|
+
if (localQuantityNum > minQuantity) {
|
|
83
|
+
handleQuantityChange(localQuantityNum - 1);
|
|
76
84
|
}
|
|
77
85
|
};
|
|
78
86
|
const handleQuantityInputChange = event => {
|
|
79
|
-
const value = parseInt(event.target.value, 10);
|
|
87
|
+
const value = parseInt(event.target.value || "0", 10);
|
|
80
88
|
if (!Number.isNaN(value)) {
|
|
81
89
|
handleQuantityChange(value);
|
|
82
90
|
}
|
|
@@ -84,7 +92,7 @@ function ProductItem({
|
|
|
84
92
|
const formatCreditInfo = () => {
|
|
85
93
|
if (!isCreditProduct) return null;
|
|
86
94
|
const isRecurring = item.price.type === "recurring";
|
|
87
|
-
const totalCredit = (0, _util.formatNumber)(creditAmount * localQuantity);
|
|
95
|
+
const totalCredit = (0, _util.formatNumber)(creditAmount * (localQuantity || 0));
|
|
88
96
|
let message = "";
|
|
89
97
|
if (isRecurring) {
|
|
90
98
|
message = t("payment.checkout.credit.recurringInfo", {
|
|
@@ -114,7 +122,7 @@ function ProductItem({
|
|
|
114
122
|
}
|
|
115
123
|
return pricing.primary;
|
|
116
124
|
}, [trialInDays, trialEnd, pricing, item, locale]);
|
|
117
|
-
const quantityInventoryError = (0, _util.formatQuantityInventory)(item.price,
|
|
125
|
+
const quantityInventoryError = (0, _util.formatQuantityInventory)(item.price, localQuantityNum, locale);
|
|
118
126
|
return /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
|
|
119
127
|
direction: "column",
|
|
120
128
|
spacing: 1,
|
|
@@ -203,7 +211,7 @@ function ProductItem({
|
|
|
203
211
|
}), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.IconButton, {
|
|
204
212
|
size: "small",
|
|
205
213
|
onClick: handleQuantityDecrease,
|
|
206
|
-
disabled:
|
|
214
|
+
disabled: localQuantityNum <= minQuantity,
|
|
207
215
|
sx: {
|
|
208
216
|
minWidth: 32,
|
|
209
217
|
width: 32,
|
|
@@ -231,7 +239,7 @@ function ProductItem({
|
|
|
231
239
|
}), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.IconButton, {
|
|
232
240
|
size: "small",
|
|
233
241
|
onClick: handleQuantityIncrease,
|
|
234
|
-
disabled:
|
|
242
|
+
disabled: localQuantityNum >= maxQuantity,
|
|
235
243
|
sx: {
|
|
236
244
|
minWidth: 32,
|
|
237
245
|
width: 32,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blocklet/payment-react",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.20",
|
|
4
4
|
"description": "Reusable react components for payment kit v2",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -54,16 +54,16 @@
|
|
|
54
54
|
}
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@arcblock/did-connect-react": "^3.1.
|
|
58
|
-
"@arcblock/ux": "^3.1.
|
|
59
|
-
"@arcblock/ws": "^1.
|
|
60
|
-
"@blocklet/theme": "^3.1.
|
|
61
|
-
"@blocklet/ui-react": "^3.1.
|
|
57
|
+
"@arcblock/did-connect-react": "^3.1.31",
|
|
58
|
+
"@arcblock/ux": "^3.1.31",
|
|
59
|
+
"@arcblock/ws": "^1.23.1",
|
|
60
|
+
"@blocklet/theme": "^3.1.31",
|
|
61
|
+
"@blocklet/ui-react": "^3.1.31",
|
|
62
62
|
"@mui/icons-material": "^7.1.2",
|
|
63
63
|
"@mui/lab": "7.0.0-beta.14",
|
|
64
64
|
"@mui/material": "^7.1.2",
|
|
65
65
|
"@mui/system": "^7.1.1",
|
|
66
|
-
"@ocap/util": "^1.
|
|
66
|
+
"@ocap/util": "^1.23.1",
|
|
67
67
|
"@stripe/react-stripe-js": "^2.9.0",
|
|
68
68
|
"@stripe/stripe-js": "^2.4.0",
|
|
69
69
|
"@vitejs/plugin-legacy": "^7.0.0",
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
"@babel/core": "^7.27.4",
|
|
95
95
|
"@babel/preset-env": "^7.27.2",
|
|
96
96
|
"@babel/preset-react": "^7.27.1",
|
|
97
|
-
"@blocklet/payment-types": "1.19.
|
|
97
|
+
"@blocklet/payment-types": "1.19.20",
|
|
98
98
|
"@storybook/addon-essentials": "^7.6.20",
|
|
99
99
|
"@storybook/addon-interactions": "^7.6.20",
|
|
100
100
|
"@storybook/addon-links": "^7.6.20",
|
|
@@ -125,5 +125,5 @@
|
|
|
125
125
|
"vite-plugin-babel": "^1.3.1",
|
|
126
126
|
"vite-plugin-node-polyfills": "^0.23.0"
|
|
127
127
|
},
|
|
128
|
-
"gitHead": "
|
|
128
|
+
"gitHead": "4e09a5936748fc15c1ab6ea90b741b3e44f5eab7"
|
|
129
129
|
}
|
package/src/checkout/form.tsx
CHANGED
|
@@ -4,6 +4,8 @@ import noop from 'lodash/noop';
|
|
|
4
4
|
import { useEffect } from 'react';
|
|
5
5
|
import { joinURL } from 'ufo';
|
|
6
6
|
|
|
7
|
+
import { ReactGA } from '@arcblock/ux/lib/withTracker';
|
|
8
|
+
import type { PayFailedEvent, PaySuccessEvent } from '@arcblock/ux/lib/withTracker/action/pay';
|
|
7
9
|
import api from '../libs/api';
|
|
8
10
|
import { getPrefix, mergeExtraParams } from '../libs/util';
|
|
9
11
|
import Payment from '../payment';
|
|
@@ -67,15 +69,32 @@ export default function CheckoutForm({
|
|
|
67
69
|
}
|
|
68
70
|
}, [type, mode, data, extraParams]);
|
|
69
71
|
|
|
70
|
-
const handlePaid = () => {
|
|
72
|
+
const handlePaid = (result: CheckoutContext) => {
|
|
71
73
|
setState({ completed: true });
|
|
72
|
-
onPaid?.(
|
|
74
|
+
onPaid?.(result as CheckoutContext);
|
|
75
|
+
|
|
76
|
+
const paySuccessEvent: PaySuccessEvent = {
|
|
77
|
+
action: 'paySuccess',
|
|
78
|
+
// @ts-ignore 后续升级的话就会报错了,移除这个 lint 即可
|
|
79
|
+
mode: data?.checkoutSession?.mode!,
|
|
80
|
+
success: true,
|
|
81
|
+
};
|
|
82
|
+
ReactGA.event(paySuccessEvent.action, paySuccessEvent);
|
|
73
83
|
};
|
|
74
84
|
|
|
75
85
|
const handleError = (err: any) => {
|
|
76
86
|
console.error(err);
|
|
77
87
|
setState({ appError: err });
|
|
78
88
|
onError?.(err);
|
|
89
|
+
|
|
90
|
+
const payFailedEvent: PayFailedEvent = {
|
|
91
|
+
action: 'payFailed',
|
|
92
|
+
// @ts-ignore后续升级的话就会报错了,移除这个 lint 即可
|
|
93
|
+
mode: data?.checkoutSession?.mode!,
|
|
94
|
+
errorMessage: err.message,
|
|
95
|
+
success: false,
|
|
96
|
+
};
|
|
97
|
+
ReactGA.event(payFailedEvent.action, payFailedEvent);
|
|
79
98
|
};
|
|
80
99
|
|
|
81
100
|
const Checkout =
|
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
import React, { useState, useCallback } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Box,
|
|
4
|
+
Typography,
|
|
5
|
+
Stack,
|
|
6
|
+
Button,
|
|
7
|
+
CircularProgress,
|
|
8
|
+
Card,
|
|
9
|
+
CardContent,
|
|
10
|
+
IconButton,
|
|
11
|
+
Tooltip,
|
|
12
|
+
SxProps,
|
|
13
|
+
Collapse,
|
|
14
|
+
} from '@mui/material';
|
|
15
|
+
import { AddOutlined, CreditCard, SettingsOutlined, AccountBalanceWalletOutlined } from '@mui/icons-material';
|
|
16
|
+
|
|
17
|
+
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
18
|
+
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
19
|
+
import { useNavigate } from 'react-router-dom';
|
|
20
|
+
import { joinURL } from 'ufo';
|
|
21
|
+
|
|
22
|
+
import type { AutoRechargeConfig } from '@blocklet/payment-types';
|
|
23
|
+
import { useRequest } from 'ahooks';
|
|
24
|
+
|
|
25
|
+
import { getPrefix, formatBNStr, formatNumber, formatPrice } from '../../libs/util';
|
|
26
|
+
import { createLink, handleNavigation } from '../../libs/navigation';
|
|
27
|
+
import { usePaymentContext } from '../../contexts/payment';
|
|
28
|
+
import api from '../../libs/api';
|
|
29
|
+
import AutoTopupModal from './modal';
|
|
30
|
+
|
|
31
|
+
export interface AutoTopupCardProps {
|
|
32
|
+
currencyId: string;
|
|
33
|
+
onConfigChange?: (config: AutoRechargeConfig) => void;
|
|
34
|
+
sx?: SxProps;
|
|
35
|
+
// 渲染模式: default=完整显示, simple=默认收起支持展开, custom=自定义渲染
|
|
36
|
+
mode?: 'default' | 'simple' | 'custom';
|
|
37
|
+
// 自定义渲染函数(custom模式下使用)
|
|
38
|
+
children?: (
|
|
39
|
+
openModal: () => void,
|
|
40
|
+
config: AutoRechargeConfig | null,
|
|
41
|
+
paymentData: { paymentInfo: any; balanceInfo: any } | null,
|
|
42
|
+
loading: boolean
|
|
43
|
+
) => React.ReactNode;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const fetchConfig = async (customerId: string, currencyId: string) => {
|
|
47
|
+
const { data } = await api.get(`/api/auto-recharge-configs/customer/${customerId}`, {
|
|
48
|
+
params: { currency_id: currencyId },
|
|
49
|
+
});
|
|
50
|
+
return data;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const fetchCurrencyBalance = async (currencyId: string, payerAddress: string) => {
|
|
54
|
+
const { data } = await api.get('/api/customers/payer-token', {
|
|
55
|
+
params: { currencyId, payerAddress },
|
|
56
|
+
});
|
|
57
|
+
return data;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const cardStyle = {
|
|
61
|
+
height: '100%',
|
|
62
|
+
width: '100%',
|
|
63
|
+
border: '1px solid',
|
|
64
|
+
borderColor: 'divider',
|
|
65
|
+
boxShadow: 1,
|
|
66
|
+
borderRadius: 1,
|
|
67
|
+
backgroundColor: 'background.default',
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export default function AutoTopupCard({
|
|
71
|
+
currencyId,
|
|
72
|
+
onConfigChange = () => {},
|
|
73
|
+
sx = {},
|
|
74
|
+
mode = 'default',
|
|
75
|
+
children = undefined,
|
|
76
|
+
}: AutoTopupCardProps) {
|
|
77
|
+
const { t } = useLocaleContext();
|
|
78
|
+
const navigate = useNavigate();
|
|
79
|
+
const { session } = usePaymentContext();
|
|
80
|
+
const [modalOpen, setModalOpen] = useState(false);
|
|
81
|
+
const [paymentData, setPaymentData] = useState<{ paymentInfo: any; balanceInfo: any } | null>(null);
|
|
82
|
+
const [quickSetupMode, setQuickSetupMode] = useState(false); // 是否是快速设置模式
|
|
83
|
+
// simple模式默认收起,default模式默认展开
|
|
84
|
+
const [expanded, setExpanded] = useState(mode === 'default');
|
|
85
|
+
|
|
86
|
+
const customerId = session?.user?.did || '';
|
|
87
|
+
|
|
88
|
+
const {
|
|
89
|
+
data: config,
|
|
90
|
+
loading,
|
|
91
|
+
refresh,
|
|
92
|
+
} = useRequest(() => fetchConfig(customerId, currencyId), {
|
|
93
|
+
refreshDeps: [customerId, currencyId],
|
|
94
|
+
ready: !!customerId && !!currencyId,
|
|
95
|
+
onSuccess: (data) => {
|
|
96
|
+
loadPaymentInfo(data);
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const loadPaymentInfo = useCallback(async (data: any) => {
|
|
101
|
+
if (!data?.recharge_currency_id) return;
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const paymentMethodType = data?.paymentMethod?.type;
|
|
105
|
+
const paymentInfo = data?.payment_settings?.payment_method_options?.[paymentMethodType];
|
|
106
|
+
const balanceInfo =
|
|
107
|
+
paymentInfo?.payer && paymentMethodType !== 'stripe'
|
|
108
|
+
? await fetchCurrencyBalance(data.recharge_currency_id, paymentInfo.payer as string)
|
|
109
|
+
: null;
|
|
110
|
+
|
|
111
|
+
setPaymentData({
|
|
112
|
+
paymentInfo,
|
|
113
|
+
balanceInfo,
|
|
114
|
+
});
|
|
115
|
+
} catch (error) {
|
|
116
|
+
console.error('Failed to load payment info:', error);
|
|
117
|
+
}
|
|
118
|
+
}, []);
|
|
119
|
+
|
|
120
|
+
const handleRecharge = (e: React.MouseEvent) => {
|
|
121
|
+
if (!paymentData?.paymentInfo?.payer) return;
|
|
122
|
+
const url = joinURL(
|
|
123
|
+
getPrefix(),
|
|
124
|
+
`/customer/recharge/${config?.recharge_currency_id}?rechargeAddress=${paymentData.paymentInfo.payer}`
|
|
125
|
+
);
|
|
126
|
+
const link = createLink(url, true);
|
|
127
|
+
handleNavigation(e, link, navigate);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const handleConfigSuccess = (newConfig: AutoRechargeConfig) => {
|
|
131
|
+
refresh();
|
|
132
|
+
onConfigChange?.(newConfig);
|
|
133
|
+
setModalOpen(false);
|
|
134
|
+
setQuickSetupMode(false); // 重置快速设置模式
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const handleToggleExpanded = () => {
|
|
138
|
+
setExpanded(!expanded);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
if (loading) {
|
|
142
|
+
return (
|
|
143
|
+
<Card sx={{ ...cardStyle, ...sx }}>
|
|
144
|
+
<CardContent>
|
|
145
|
+
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: 80 }}>
|
|
146
|
+
<CircularProgress size={24} />
|
|
147
|
+
</Box>
|
|
148
|
+
</CardContent>
|
|
149
|
+
</Card>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (!config) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const renderPurchaseDetails = () => {
|
|
158
|
+
const { paymentInfo, balanceInfo } = paymentData || {};
|
|
159
|
+
|
|
160
|
+
if (!paymentInfo) {
|
|
161
|
+
return (
|
|
162
|
+
<Typography
|
|
163
|
+
variant="body2"
|
|
164
|
+
sx={{
|
|
165
|
+
color: 'text.secondary',
|
|
166
|
+
}}>
|
|
167
|
+
{t('payment.autoTopup.notConfigured')}
|
|
168
|
+
</Typography>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const purchaseAmount = formatPrice(
|
|
173
|
+
config.price,
|
|
174
|
+
config.rechargeCurrency,
|
|
175
|
+
config.price.product?.unit_label,
|
|
176
|
+
config.quantity,
|
|
177
|
+
true
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
if (config?.paymentMethod?.type === 'stripe') {
|
|
181
|
+
const cardBrand =
|
|
182
|
+
(paymentInfo?.card_brand || 'Card').charAt(0).toUpperCase() +
|
|
183
|
+
(paymentInfo?.card_brand || 'Card').slice(1).toLowerCase();
|
|
184
|
+
const last4 = paymentInfo?.card_last4;
|
|
185
|
+
|
|
186
|
+
return (
|
|
187
|
+
<Stack spacing={1}>
|
|
188
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
|
189
|
+
<Typography
|
|
190
|
+
variant="body2"
|
|
191
|
+
sx={{
|
|
192
|
+
color: 'text.secondary',
|
|
193
|
+
}}>
|
|
194
|
+
{t('payment.autoTopup.purchaseAmount')}:
|
|
195
|
+
</Typography>
|
|
196
|
+
<Typography variant="body2" sx={{ fontWeight: 600, color: 'text.primary' }}>
|
|
197
|
+
{purchaseAmount}
|
|
198
|
+
</Typography>
|
|
199
|
+
</Box>
|
|
200
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
|
201
|
+
<Typography
|
|
202
|
+
variant="body2"
|
|
203
|
+
sx={{
|
|
204
|
+
color: 'text.secondary',
|
|
205
|
+
}}>
|
|
206
|
+
{t('payment.autoTopup.paymentMethod')}:
|
|
207
|
+
</Typography>
|
|
208
|
+
<Stack
|
|
209
|
+
direction="row"
|
|
210
|
+
spacing={1}
|
|
211
|
+
sx={{
|
|
212
|
+
alignItems: 'center',
|
|
213
|
+
}}>
|
|
214
|
+
<CreditCard fontSize="small" sx={{ color: 'text.secondary' }} />
|
|
215
|
+
<Typography variant="body2" sx={{ color: 'text.primary', fontWeight: 500 }}>
|
|
216
|
+
{cardBrand}({last4})
|
|
217
|
+
</Typography>
|
|
218
|
+
</Stack>
|
|
219
|
+
</Box>
|
|
220
|
+
</Stack>
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return (
|
|
225
|
+
<Stack spacing={1}>
|
|
226
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
|
227
|
+
<Typography
|
|
228
|
+
variant="body2"
|
|
229
|
+
sx={{
|
|
230
|
+
color: 'text.secondary',
|
|
231
|
+
}}>
|
|
232
|
+
{t('payment.autoTopup.purchaseAmount')}:
|
|
233
|
+
</Typography>
|
|
234
|
+
<Typography variant="body2" sx={{ fontWeight: 600, color: 'text.primary' }}>
|
|
235
|
+
{purchaseAmount}
|
|
236
|
+
</Typography>
|
|
237
|
+
</Box>
|
|
238
|
+
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
|
239
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
|
240
|
+
<Typography
|
|
241
|
+
variant="body2"
|
|
242
|
+
sx={{
|
|
243
|
+
color: 'text.secondary',
|
|
244
|
+
}}>
|
|
245
|
+
{t('payment.autoTopup.walletBalance')}:
|
|
246
|
+
</Typography>
|
|
247
|
+
<Tooltip
|
|
248
|
+
title={paymentInfo?.payer ? `${t('payment.autoTopup.paymentAddress')}: ${paymentInfo.payer}` : ''}
|
|
249
|
+
placement="top">
|
|
250
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
|
251
|
+
<AccountBalanceWalletOutlined sx={{ fontSize: 16, color: 'text.secondary' }} />
|
|
252
|
+
<Typography variant="body2" sx={{ fontWeight: 600, color: 'text.primary' }}>
|
|
253
|
+
{balanceInfo
|
|
254
|
+
? `${formatBNStr(balanceInfo?.token || '0', config?.rechargeCurrency?.decimal || 18)} ${config?.rechargeCurrency?.symbol || ''}`
|
|
255
|
+
: '--'}
|
|
256
|
+
</Typography>
|
|
257
|
+
</Box>
|
|
258
|
+
</Tooltip>
|
|
259
|
+
{balanceInfo && (
|
|
260
|
+
<Button
|
|
261
|
+
size="small"
|
|
262
|
+
variant="text"
|
|
263
|
+
onClick={handleRecharge}
|
|
264
|
+
sx={{
|
|
265
|
+
color: 'primary.main',
|
|
266
|
+
display: 'flex',
|
|
267
|
+
alignItems: 'center',
|
|
268
|
+
}}>
|
|
269
|
+
<AddOutlined fontSize="small" />
|
|
270
|
+
{t('payment.autoTopup.addFunds')}
|
|
271
|
+
</Button>
|
|
272
|
+
)}
|
|
273
|
+
</Box>
|
|
274
|
+
</Box>
|
|
275
|
+
</Stack>
|
|
276
|
+
);
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const openModal = () => setModalOpen(true);
|
|
280
|
+
|
|
281
|
+
const renderInnerView = () => {
|
|
282
|
+
if (mode === 'custom') {
|
|
283
|
+
return children && typeof children === 'function' ? (
|
|
284
|
+
<>{children(openModal, config, paymentData, loading)}</>
|
|
285
|
+
) : (
|
|
286
|
+
<Typography>
|
|
287
|
+
Please provide a valid render function
|
|
288
|
+
<pre>{'(openModal, config, paymentData, loading) => ReactNode'}</pre>
|
|
289
|
+
</Typography>
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return (
|
|
294
|
+
<Card sx={{ ...cardStyle, ...sx }}>
|
|
295
|
+
<CardContent>
|
|
296
|
+
{/* Header */}
|
|
297
|
+
<Stack
|
|
298
|
+
direction="row"
|
|
299
|
+
className="auto-topup-header"
|
|
300
|
+
sx={{
|
|
301
|
+
justifyContent: 'space-between',
|
|
302
|
+
alignItems: 'center',
|
|
303
|
+
borderBottom: '1px solid',
|
|
304
|
+
borderColor: 'divider',
|
|
305
|
+
pb: 1.5,
|
|
306
|
+
}}>
|
|
307
|
+
<Typography variant="subtitle2" sx={{ fontWeight: 600, color: 'text.primary' }}>
|
|
308
|
+
{t('payment.autoTopup.title')}
|
|
309
|
+
</Typography>
|
|
310
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
|
311
|
+
<IconButton
|
|
312
|
+
size="small"
|
|
313
|
+
onClick={openModal}
|
|
314
|
+
sx={{
|
|
315
|
+
p: 0.5,
|
|
316
|
+
color: 'text.secondary',
|
|
317
|
+
'&:hover': {
|
|
318
|
+
bgcolor: 'grey.50',
|
|
319
|
+
color: 'text.primary',
|
|
320
|
+
},
|
|
321
|
+
}}>
|
|
322
|
+
<SettingsOutlined fontSize="small" />
|
|
323
|
+
</IconButton>
|
|
324
|
+
</Box>
|
|
325
|
+
</Stack>
|
|
326
|
+
|
|
327
|
+
{config?.enabled ? (
|
|
328
|
+
<Stack spacing={1.5} className="auto-topup-content" sx={{ pt: 1.5 }}>
|
|
329
|
+
{/* Main Description */}
|
|
330
|
+
{(() => {
|
|
331
|
+
const threshold = `${formatNumber(config.threshold)} ${config.currency?.symbol || ''}`;
|
|
332
|
+
const credits = `${formatNumber(
|
|
333
|
+
Number(config.price.metadata?.credit_config?.credit_amount || 0) * Number(config.quantity)
|
|
334
|
+
)} ${config.currency?.name || ''}`;
|
|
335
|
+
|
|
336
|
+
return (
|
|
337
|
+
<Typography
|
|
338
|
+
variant="body2"
|
|
339
|
+
sx={{
|
|
340
|
+
color: 'text.secondary',
|
|
341
|
+
}}>
|
|
342
|
+
{t('payment.autoTopup.activeDescriptionWithCredits', { threshold, credits })}
|
|
343
|
+
{/* 展开收起按钮 - 仅在simple模式下显示 */}
|
|
344
|
+
{mode === 'simple' && (
|
|
345
|
+
<Button
|
|
346
|
+
component="span"
|
|
347
|
+
size="small"
|
|
348
|
+
variant="text"
|
|
349
|
+
onClick={handleToggleExpanded}
|
|
350
|
+
sx={{
|
|
351
|
+
color: 'primary.main',
|
|
352
|
+
minWidth: 'auto',
|
|
353
|
+
ml: 1,
|
|
354
|
+
p: 0,
|
|
355
|
+
fontSize: 'inherit',
|
|
356
|
+
textTransform: 'none',
|
|
357
|
+
'&:hover': {
|
|
358
|
+
backgroundColor: 'transparent',
|
|
359
|
+
textDecoration: 'underline',
|
|
360
|
+
},
|
|
361
|
+
}}>
|
|
362
|
+
{expanded ? t('payment.autoTopup.hideDetails') : t('payment.autoTopup.showDetails')}
|
|
363
|
+
</Button>
|
|
364
|
+
)}
|
|
365
|
+
</Typography>
|
|
366
|
+
);
|
|
367
|
+
})()}
|
|
368
|
+
|
|
369
|
+
<Collapse in={mode === 'default' || expanded}>
|
|
370
|
+
<Box
|
|
371
|
+
sx={{
|
|
372
|
+
bgcolor: 'grey.50',
|
|
373
|
+
borderRadius: 1,
|
|
374
|
+
p: 1.5,
|
|
375
|
+
}}>
|
|
376
|
+
{renderPurchaseDetails()}
|
|
377
|
+
</Box>
|
|
378
|
+
</Collapse>
|
|
379
|
+
</Stack>
|
|
380
|
+
) : (
|
|
381
|
+
<Stack
|
|
382
|
+
className="auto-topup-content"
|
|
383
|
+
sx={{
|
|
384
|
+
minHeight: 80,
|
|
385
|
+
display: 'flex',
|
|
386
|
+
flexDirection: 'column',
|
|
387
|
+
alignItems: 'center',
|
|
388
|
+
justifyContent: 'center',
|
|
389
|
+
pt: 1.5,
|
|
390
|
+
gap: 2,
|
|
391
|
+
}}>
|
|
392
|
+
<Typography
|
|
393
|
+
variant="body2"
|
|
394
|
+
sx={{
|
|
395
|
+
color: 'text.secondary',
|
|
396
|
+
textAlign: 'left',
|
|
397
|
+
}}>
|
|
398
|
+
{t('payment.autoTopup.inactiveDescription', {
|
|
399
|
+
name: config?.currency?.name,
|
|
400
|
+
})}
|
|
401
|
+
<Button
|
|
402
|
+
component="span"
|
|
403
|
+
variant="text"
|
|
404
|
+
size="small"
|
|
405
|
+
onClick={() => {
|
|
406
|
+
setQuickSetupMode(true);
|
|
407
|
+
setModalOpen(true);
|
|
408
|
+
}}
|
|
409
|
+
sx={{
|
|
410
|
+
color: 'primary.main',
|
|
411
|
+
minWidth: 'auto',
|
|
412
|
+
ml: 1,
|
|
413
|
+
p: 0,
|
|
414
|
+
fontSize: 'inherit',
|
|
415
|
+
textTransform: 'none',
|
|
416
|
+
'&:hover': {
|
|
417
|
+
backgroundColor: 'transparent',
|
|
418
|
+
textDecoration: 'underline',
|
|
419
|
+
},
|
|
420
|
+
}}>
|
|
421
|
+
{t('payment.autoTopup.setup')}
|
|
422
|
+
</Button>
|
|
423
|
+
</Typography>
|
|
424
|
+
</Stack>
|
|
425
|
+
)}
|
|
426
|
+
</CardContent>
|
|
427
|
+
</Card>
|
|
428
|
+
);
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
return (
|
|
432
|
+
<>
|
|
433
|
+
{renderInnerView()}
|
|
434
|
+
|
|
435
|
+
{modalOpen && (
|
|
436
|
+
<AutoTopupModal
|
|
437
|
+
open={modalOpen}
|
|
438
|
+
onClose={() => {
|
|
439
|
+
setModalOpen(false);
|
|
440
|
+
setQuickSetupMode(false); // 关闭时重置快速设置模式
|
|
441
|
+
}}
|
|
442
|
+
currencyId={currencyId}
|
|
443
|
+
onSuccess={handleConfigSuccess}
|
|
444
|
+
defaultEnabled={quickSetupMode} // 传递默认启用状态
|
|
445
|
+
/>
|
|
446
|
+
)}
|
|
447
|
+
</>
|
|
448
|
+
);
|
|
449
|
+
}
|