@blocklet/payment-react 1.20.5 → 1.20.7
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/.aigne/doc-smith/config.yaml +2 -2
- package/.aigne/doc-smith/upload-cache.yaml +381 -0
- package/docs/components-business-auto-topup.md +128 -179
- package/docs/components-business-overdue-invoice-payment.md +108 -143
- package/docs/components-business-resume-subscription.md +117 -104
- package/docs/components-business.md +12 -36
- package/docs/components-checkout-checkout-donate.md +209 -149
- package/docs/components-checkout-checkout-form.md +115 -136
- package/docs/components-checkout-checkout-table.md +92 -172
- package/docs/components-checkout.md +43 -109
- package/docs/components-history-credit-grants-list.md +45 -70
- package/docs/components-history-credit-transactions-list.md +57 -67
- package/docs/components-history-invoice-list.md +58 -52
- package/docs/components-history-payment-list.md +19 -40
- package/docs/components-history.md +42 -67
- package/docs/components-ui-form-elements-address-form.md +37 -65
- package/docs/components-ui-form-elements-country-select.md +80 -59
- package/docs/components-ui-form-elements-currency-selector.md +57 -73
- package/docs/components-ui-form-elements-phone-input.md +90 -112
- package/docs/components-ui-form-elements.md +46 -80
- package/docs/components-ui-payment-summary.md +71 -119
- package/docs/components-ui-pricing-table.md +117 -204
- package/docs/components-ui.md +59 -32
- package/docs/components.md +89 -62
- package/docs/getting-started.md +36 -63
- package/docs/guides-theming.md +122 -84
- package/docs/guides-utilities.md +107 -145
- package/docs/guides.md +7 -84
- package/docs/hooks-use-mobile.md +50 -36
- package/docs/hooks-use-subscription.md +72 -89
- package/docs/hooks.md +12 -82
- package/docs/overview.md +45 -52
- package/docs/providers-donate-provider.md +73 -95
- package/docs/providers-payment-provider.md +115 -169
- package/docs/providers.md +27 -86
- package/es/locales/en.js +7 -0
- package/es/locales/zh.js +8 -1
- package/es/payment/index.js +3 -0
- package/es/payment/progress-item.d.ts +12 -0
- package/es/payment/progress-item.js +78 -0
- package/es/payment/success.d.ts +4 -1
- package/es/payment/success.js +55 -3
- package/lib/locales/en.js +7 -0
- package/lib/locales/zh.js +8 -1
- package/lib/payment/index.js +3 -0
- package/lib/payment/progress-item.d.ts +12 -0
- package/lib/payment/progress-item.js +107 -0
- package/lib/payment/success.d.ts +4 -1
- package/lib/payment/success.js +59 -3
- package/package.json +3 -3
- package/src/locales/en.tsx +7 -0
- package/src/locales/zh.tsx +8 -1
- package/src/payment/index.tsx +6 -0
- package/src/payment/progress-item.tsx +107 -0
- package/src/payment/success.tsx +88 -3
package/src/locales/en.tsx
CHANGED
|
@@ -182,6 +182,13 @@ export default flat({
|
|
|
182
182
|
donate: 'Thanks for your tip',
|
|
183
183
|
tip: 'A payment to {payee} has been completed. You can view the details of this payment in your account.',
|
|
184
184
|
},
|
|
185
|
+
vendor: {
|
|
186
|
+
processing: 'Processing...',
|
|
187
|
+
progress: 'Progress {progress}%',
|
|
188
|
+
delivered: 'Installation completed',
|
|
189
|
+
failed: 'Processing failed',
|
|
190
|
+
pending: 'Waiting for processing',
|
|
191
|
+
},
|
|
185
192
|
confirm: {
|
|
186
193
|
withStake:
|
|
187
194
|
'By confirming, you allow {payee} to charge your account for future payments and, if necessary, slash your stake. You can cancel your subscription or withdraw your stake at any time.',
|
package/src/locales/zh.tsx
CHANGED
|
@@ -179,7 +179,14 @@ export default flat({
|
|
|
179
179
|
subscription: '感谢您的订阅',
|
|
180
180
|
setup: '感谢您的订阅',
|
|
181
181
|
donate: '感谢您的支持',
|
|
182
|
-
tip: '向{payee}的付款已完成。您可以在您的账户中查看此付款的详细信息。',
|
|
182
|
+
tip: '向 {payee} 的付款已完成。您可以在您的账户中查看此付款的详细信息。',
|
|
183
|
+
},
|
|
184
|
+
vendor: {
|
|
185
|
+
processing: '正在安装中...',
|
|
186
|
+
progress: '进度 {progress}%',
|
|
187
|
+
delivered: '安装成功',
|
|
188
|
+
failed: '安装失败',
|
|
189
|
+
pending: '准备安装',
|
|
183
190
|
},
|
|
184
191
|
confirm: {
|
|
185
192
|
withStake:
|
package/src/payment/index.tsx
CHANGED
|
@@ -307,6 +307,12 @@ function PaymentInner({
|
|
|
307
307
|
{completed && (
|
|
308
308
|
<PaymentSuccess
|
|
309
309
|
mode={mode}
|
|
310
|
+
pageInfo={state.checkoutSession.metadata?.page_info}
|
|
311
|
+
hasVendor={
|
|
312
|
+
state.checkoutSession.metadata?.page_info?.hasVendor ||
|
|
313
|
+
state.checkoutSession.line_items.some((item: any) => item.price?.product?.vendor_config?.length > 0)
|
|
314
|
+
}
|
|
315
|
+
sessionId={state.checkoutSession.id}
|
|
310
316
|
payee={getStatementDescriptor(state.checkoutSession.line_items)}
|
|
311
317
|
action={state.checkoutSession.mode}
|
|
312
318
|
invoiceId={state.checkoutSession.invoice_id}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
2
|
+
import { Box, LinearProgress, Typography } from '@mui/material';
|
|
3
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
4
|
+
|
|
5
|
+
interface VendorStatus {
|
|
6
|
+
success: boolean;
|
|
7
|
+
status: 'delivered' | 'pending' | 'failed';
|
|
8
|
+
progress: number;
|
|
9
|
+
message: string;
|
|
10
|
+
appUrl?: string;
|
|
11
|
+
title?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function VendorProgressItem({ vendor }: { vendor: VendorStatus }) {
|
|
15
|
+
const { t } = useLocaleContext();
|
|
16
|
+
const [displayProgress, setDisplayProgress] = useState(0);
|
|
17
|
+
const animationRef = useRef<number>();
|
|
18
|
+
|
|
19
|
+
const startAnimation = useCallback(() => {
|
|
20
|
+
const realProgress = vendor.progress || 0;
|
|
21
|
+
let startTime: number;
|
|
22
|
+
let startProgress: number;
|
|
23
|
+
|
|
24
|
+
const animate = (currentTime: number) => {
|
|
25
|
+
if (!startTime) {
|
|
26
|
+
startTime = currentTime;
|
|
27
|
+
startProgress = displayProgress;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const elapsed = currentTime - startTime;
|
|
31
|
+
let newProgress: number;
|
|
32
|
+
|
|
33
|
+
if (realProgress === 100) {
|
|
34
|
+
// If progress is 100%, set directly to 100% without animation
|
|
35
|
+
newProgress = 100;
|
|
36
|
+
} else if (realProgress === 0) {
|
|
37
|
+
// When no data available, increase 1% per second
|
|
38
|
+
newProgress = Math.min(startProgress + elapsed / 1000, 99);
|
|
39
|
+
} else if (realProgress > startProgress) {
|
|
40
|
+
const duration = 1000; // 1 second to reach target quickly
|
|
41
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
42
|
+
newProgress = startProgress + (realProgress - startProgress) * progress;
|
|
43
|
+
} else {
|
|
44
|
+
// After reaching target, increase 1% per second, max 99%
|
|
45
|
+
newProgress = Math.min(startProgress + elapsed / 1000, 99);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Ensure progress is an integer
|
|
49
|
+
newProgress = Math.round(newProgress);
|
|
50
|
+
setDisplayProgress((pre) => (pre > newProgress ? pre : newProgress));
|
|
51
|
+
|
|
52
|
+
// Stop animation immediately when 100%
|
|
53
|
+
if (realProgress === 100) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Continue animation in other cases
|
|
58
|
+
if (newProgress < 99 && realProgress < 100) {
|
|
59
|
+
animationRef.current = requestAnimationFrame(animate);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
if (animationRef.current) {
|
|
64
|
+
cancelAnimationFrame(animationRef.current);
|
|
65
|
+
}
|
|
66
|
+
animationRef.current = requestAnimationFrame(animate);
|
|
67
|
+
}, [vendor.progress, displayProgress]);
|
|
68
|
+
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
startAnimation();
|
|
71
|
+
return () => {
|
|
72
|
+
if (animationRef.current) {
|
|
73
|
+
cancelAnimationFrame(animationRef.current);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}, [startAnimation]);
|
|
77
|
+
|
|
78
|
+
const isCompleted = displayProgress >= 80;
|
|
79
|
+
const statusText = isCompleted ? t('payment.checkout.vendor.delivered') : t('payment.checkout.vendor.processing');
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<Box sx={{ mb: 2 }}>
|
|
83
|
+
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 1 }}>
|
|
84
|
+
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
|
85
|
+
{vendor.title ? `${vendor.title} - ${statusText}` : statusText}
|
|
86
|
+
</Typography>
|
|
87
|
+
<Typography variant="body2" sx={{ color: 'text.secondary', fontWeight: 500 }}>
|
|
88
|
+
{t('payment.checkout.vendor.progress', { progress: displayProgress })}
|
|
89
|
+
</Typography>
|
|
90
|
+
</Box>
|
|
91
|
+
<LinearProgress
|
|
92
|
+
variant="determinate"
|
|
93
|
+
value={Math.min(displayProgress, 100)}
|
|
94
|
+
sx={{
|
|
95
|
+
height: 8,
|
|
96
|
+
borderRadius: 4,
|
|
97
|
+
backgroundColor: 'grey.200',
|
|
98
|
+
'& .MuiLinearProgress-bar': {
|
|
99
|
+
borderRadius: 4,
|
|
100
|
+
backgroundColor: isCompleted ? 'success.main' : 'primary.main',
|
|
101
|
+
transition: 'background-color 0.3s linear',
|
|
102
|
+
},
|
|
103
|
+
}}
|
|
104
|
+
/>
|
|
105
|
+
</Box>
|
|
106
|
+
);
|
|
107
|
+
}
|
package/src/payment/success.tsx
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
2
|
-
import { Grow, Link,
|
|
2
|
+
import { Box, Grow, Link, Paper, Stack, styled, Typography } from '@mui/material';
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
3
4
|
import { joinURL } from 'ufo';
|
|
4
5
|
|
|
5
6
|
import { Button } from '@arcblock/ux';
|
|
6
7
|
import { usePaymentContext } from '../contexts/payment';
|
|
8
|
+
import { VendorProgressItem } from './progress-item';
|
|
7
9
|
|
|
8
10
|
type Props = {
|
|
9
11
|
mode: string;
|
|
12
|
+
pageInfo?: any;
|
|
13
|
+
hasVendor?: boolean;
|
|
14
|
+
sessionId?: string;
|
|
10
15
|
message: string;
|
|
11
16
|
action: string;
|
|
12
17
|
payee: string;
|
|
@@ -15,8 +20,27 @@ type Props = {
|
|
|
15
20
|
subscriptions?: any[];
|
|
16
21
|
};
|
|
17
22
|
|
|
23
|
+
interface VendorStatus {
|
|
24
|
+
success: boolean;
|
|
25
|
+
status: 'delivered' | 'pending' | 'failed';
|
|
26
|
+
progress: number;
|
|
27
|
+
message: string;
|
|
28
|
+
appUrl?: string;
|
|
29
|
+
title?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface VendorResponse {
|
|
33
|
+
payment_status: string;
|
|
34
|
+
session_status: string;
|
|
35
|
+
vendors: VendorStatus[];
|
|
36
|
+
error: string | null;
|
|
37
|
+
}
|
|
38
|
+
|
|
18
39
|
export default function PaymentSuccess({
|
|
19
40
|
mode,
|
|
41
|
+
pageInfo = {},
|
|
42
|
+
hasVendor = false,
|
|
43
|
+
sessionId = '',
|
|
20
44
|
message,
|
|
21
45
|
action,
|
|
22
46
|
payee,
|
|
@@ -24,9 +48,69 @@ export default function PaymentSuccess({
|
|
|
24
48
|
subscriptionId = '',
|
|
25
49
|
subscriptions = [],
|
|
26
50
|
}: Props) {
|
|
27
|
-
const { t } = useLocaleContext();
|
|
28
|
-
const { prefix } = usePaymentContext();
|
|
51
|
+
const { t, locale } = useLocaleContext();
|
|
52
|
+
const { prefix, api } = usePaymentContext();
|
|
53
|
+
const [vendorStatus, setVendorStatus] = useState<VendorResponse | null>(null);
|
|
54
|
+
const [isAllCompleted, setIsAllCompleted] = useState(false);
|
|
29
55
|
let next: any = null;
|
|
56
|
+
|
|
57
|
+
// Fetch vendor status when hasVendor is true
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
if (!hasVendor || !sessionId) return undefined;
|
|
60
|
+
|
|
61
|
+
const fetchVendorStatus = async (interval?: NodeJS.Timeout) => {
|
|
62
|
+
try {
|
|
63
|
+
const response = await api.get(joinURL(prefix, `/api/vendors/order/${sessionId}/status`), {});
|
|
64
|
+
const allCompleted = response.data?.vendors?.every((vendor: any) => vendor.progress >= 80);
|
|
65
|
+
if (allCompleted) {
|
|
66
|
+
clearInterval(interval);
|
|
67
|
+
}
|
|
68
|
+
setIsAllCompleted(allCompleted);
|
|
69
|
+
setVendorStatus(response.data);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error('Failed to fetch vendor status:', error);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
fetchVendorStatus();
|
|
76
|
+
|
|
77
|
+
// Poll every 5 seconds if progress is less than 80%
|
|
78
|
+
const interval = setInterval(() => {
|
|
79
|
+
fetchVendorStatus(interval);
|
|
80
|
+
}, 5000);
|
|
81
|
+
|
|
82
|
+
return () => clearInterval(interval);
|
|
83
|
+
}, [hasVendor, api, prefix, sessionId]);
|
|
84
|
+
|
|
85
|
+
// Render vendor progress component
|
|
86
|
+
const renderVendorProgress = () => {
|
|
87
|
+
if (!hasVendor || !vendorStatus) return null;
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<Paper
|
|
91
|
+
elevation={0}
|
|
92
|
+
sx={{
|
|
93
|
+
p: 3,
|
|
94
|
+
backgroundColor: 'grey.50',
|
|
95
|
+
borderRadius: 2,
|
|
96
|
+
width: '100%',
|
|
97
|
+
mt: 2,
|
|
98
|
+
display: 'flex',
|
|
99
|
+
flexDirection: 'column',
|
|
100
|
+
gap: 2,
|
|
101
|
+
}}>
|
|
102
|
+
{vendorStatus.vendors?.map((vendor, index) => (
|
|
103
|
+
<VendorProgressItem key={vendor.title || `vendor-${index}`} vendor={vendor} />
|
|
104
|
+
))}
|
|
105
|
+
{pageInfo?.success_message?.[locale] && isAllCompleted ? (
|
|
106
|
+
<Typography variant="h6" sx={{ color: 'text.primary', mb: 1 }}>
|
|
107
|
+
{pageInfo?.success_message?.[locale]}
|
|
108
|
+
</Typography>
|
|
109
|
+
) : null}
|
|
110
|
+
</Paper>
|
|
111
|
+
);
|
|
112
|
+
};
|
|
113
|
+
|
|
30
114
|
if (['subscription', 'setup'].includes(action)) {
|
|
31
115
|
if (subscriptions && subscriptions.length > 1) {
|
|
32
116
|
next = (
|
|
@@ -134,6 +218,7 @@ export default function PaymentSuccess({
|
|
|
134
218
|
}}>
|
|
135
219
|
{t('payment.checkout.completed.tip', { payee })}
|
|
136
220
|
</Typography>
|
|
221
|
+
{renderVendorProgress()}
|
|
137
222
|
{next}
|
|
138
223
|
</Stack>
|
|
139
224
|
</Grow>
|