@blocklet/payment-react 1.13.239 → 1.13.241
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/donate.js +7 -5
- package/es/checkout/form.js +2 -2
- package/es/checkout/table.js +2 -2
- package/es/components/blockchain/gas.js +1 -1
- package/es/components/blockchain/tx.js +1 -1
- package/es/components/confirm.d.ts +4 -2
- package/es/components/confirm.js +5 -2
- package/es/components/pricing-table.js +1 -1
- package/es/components/safe-guard.js +1 -1
- package/es/contexts/payment.js +2 -2
- package/es/history/invoice/list.js +62 -10
- package/es/history/mini-invoice/list.js +16 -8
- package/es/history/payment/list.js +2 -2
- package/es/hooks/subscription.d.ts +7 -0
- package/es/hooks/subscription.js +55 -0
- package/es/index.d.ts +4 -3
- package/es/index.js +4 -3
- package/es/{api.js → libs/api.js} +6 -2
- package/es/libs/did-connect.d.ts +10 -0
- package/es/libs/did-connect.js +11 -0
- package/es/{util.d.ts → libs/util.d.ts} +1 -1
- package/es/{util.js → libs/util.js} +1 -1
- package/es/locales/en.js +1 -0
- package/es/locales/zh.js +1 -0
- package/es/payment/form/index.js +28 -6
- package/es/payment/form/phone.js +1 -1
- package/es/payment/header.js +1 -1
- package/es/payment/index.js +9 -2
- package/es/payment/product-card.js +34 -14
- package/es/payment/product-item.js +26 -16
- package/es/payment/summary.js +12 -3
- package/es/types/shims.d.ts +1 -3
- package/lib/checkout/donate.js +6 -5
- package/lib/checkout/form.js +2 -2
- package/lib/checkout/table.js +2 -2
- package/lib/components/blockchain/gas.js +1 -1
- package/lib/components/blockchain/tx.js +1 -1
- package/lib/components/confirm.d.ts +4 -2
- package/lib/components/confirm.js +5 -2
- package/lib/components/pricing-table.js +1 -1
- package/lib/components/safe-guard.js +1 -1
- package/lib/contexts/payment.js +2 -2
- package/lib/history/invoice/list.js +77 -9
- package/lib/history/mini-invoice/list.js +17 -3
- package/lib/history/payment/list.js +2 -2
- package/lib/hooks/subscription.d.ts +7 -0
- package/lib/hooks/subscription.js +62 -0
- package/lib/index.d.ts +4 -3
- package/lib/index.js +17 -5
- package/lib/{api.js → libs/api.js} +5 -2
- package/lib/libs/did-connect.d.ts +10 -0
- package/lib/libs/did-connect.js +16 -0
- package/lib/{util.d.ts → libs/util.d.ts} +1 -1
- package/lib/{util.js → libs/util.js} +1 -1
- package/lib/locales/en.js +1 -0
- package/lib/locales/zh.js +1 -0
- package/lib/payment/form/index.js +35 -9
- package/lib/payment/form/phone.js +1 -1
- package/lib/payment/header.js +1 -1
- package/lib/payment/index.js +9 -2
- package/lib/payment/product-card.js +10 -0
- package/lib/payment/product-item.js +3 -2
- package/lib/payment/summary.js +4 -2
- package/lib/types/shims.d.ts +1 -3
- package/package.json +31 -30
- package/src/checkout/donate.tsx +7 -5
- package/src/checkout/form.tsx +2 -2
- package/src/checkout/table.tsx +2 -2
- package/src/components/blockchain/gas.tsx +1 -1
- package/src/components/blockchain/tx.tsx +1 -1
- package/src/components/confirm.tsx +7 -3
- package/src/components/pricing-table.tsx +1 -1
- package/src/components/safe-guard.tsx +1 -1
- package/src/contexts/payment.tsx +2 -2
- package/src/history/invoice/list.tsx +83 -19
- package/src/history/mini-invoice/list.tsx +19 -8
- package/src/history/payment/list.tsx +2 -2
- package/src/hooks/subscription.ts +68 -0
- package/src/index.ts +4 -3
- package/src/{api.ts → libs/api.ts} +7 -2
- package/src/libs/did-connect.ts +20 -0
- package/src/{util.ts → libs/util.ts} +4 -3
- package/src/locales/en.tsx +1 -0
- package/src/locales/zh.tsx +1 -0
- package/src/payment/form/index.tsx +36 -5
- package/src/payment/form/phone.tsx +1 -1
- package/src/payment/header.tsx +1 -1
- package/src/payment/index.tsx +9 -2
- package/src/payment/product-card.tsx +13 -3
- package/src/payment/product-item.tsx +7 -2
- package/src/payment/summary.tsx +7 -3
- package/src/types/shims.d.ts +1 -3
- /package/es/{api.d.ts → libs/api.d.ts} +0 -0
- /package/es/{dayjs.d.ts → libs/dayjs.d.ts} +0 -0
- /package/es/{dayjs.js → libs/dayjs.js} +0 -0
- /package/es/{theme.d.ts → libs/theme.d.ts} +0 -0
- /package/es/{theme.js → libs/theme.js} +0 -0
- /package/lib/{api.d.ts → libs/api.d.ts} +0 -0
- /package/lib/{dayjs.d.ts → libs/dayjs.d.ts} +0 -0
- /package/lib/{dayjs.js → libs/dayjs.js} +0 -0
- /package/lib/{theme.d.ts → libs/theme.d.ts} +0 -0
- /package/lib/{theme.js → libs/theme.js} +0 -0
- /package/src/{dayjs.ts → libs/dayjs.ts} +0 -0
- /package/src/{theme.ts → libs/theme.ts} +0 -0
package/src/contexts/payment.tsx
CHANGED
|
@@ -4,8 +4,8 @@ import { useLocalStorageState, useRequest } from 'ahooks';
|
|
|
4
4
|
import type { Axios } from 'axios';
|
|
5
5
|
import { createContext, useContext } from 'react';
|
|
6
6
|
|
|
7
|
-
import api from '../api';
|
|
8
|
-
import { getPrefix } from '../util';
|
|
7
|
+
import api from '../libs/api';
|
|
8
|
+
import { getPrefix } from '../libs/util';
|
|
9
9
|
|
|
10
10
|
const prefix = getPrefix();
|
|
11
11
|
|
|
@@ -1,15 +1,28 @@
|
|
|
1
|
+
/* eslint-disable no-nested-ternary */
|
|
1
2
|
/* eslint-disable react/no-unstable-nested-components */
|
|
2
3
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
4
|
+
import Toast from '@arcblock/ux/lib/Toast';
|
|
3
5
|
import type { Paginated, TInvoiceExpanded, TSubscription } from '@blocklet/payment-types';
|
|
4
6
|
import { OpenInNewOutlined } from '@mui/icons-material';
|
|
5
7
|
import { Box, Button, CircularProgress, Hidden, Stack, Typography } from '@mui/material';
|
|
6
8
|
import { styled } from '@mui/system';
|
|
7
|
-
import { useInfiniteScroll } from 'ahooks';
|
|
9
|
+
import { useInfiniteScroll, useSetState } from 'ahooks';
|
|
10
|
+
import { useEffect } from 'react';
|
|
8
11
|
import { joinURL } from 'ufo';
|
|
9
12
|
|
|
10
|
-
import api from '../../api';
|
|
11
13
|
import Status from '../../components/status';
|
|
12
|
-
import {
|
|
14
|
+
import { usePaymentContext } from '../../contexts/payment';
|
|
15
|
+
import { useSubscription } from '../../hooks/subscription';
|
|
16
|
+
import api from '../../libs/api';
|
|
17
|
+
import {
|
|
18
|
+
formatBNStr,
|
|
19
|
+
formatError,
|
|
20
|
+
formatToDate,
|
|
21
|
+
formatToDatetime,
|
|
22
|
+
getInvoiceStatusColor,
|
|
23
|
+
getPrefix,
|
|
24
|
+
getTxLink,
|
|
25
|
+
} from '../../libs/util';
|
|
13
26
|
|
|
14
27
|
type Result = Paginated<TInvoiceExpanded> & { subscription: TSubscription };
|
|
15
28
|
|
|
@@ -50,6 +63,7 @@ const getInvoiceLink = (invoice: TInvoiceExpanded, action?: string) => {
|
|
|
50
63
|
if (invoice.id.startsWith('in_')) {
|
|
51
64
|
return {
|
|
52
65
|
external: false,
|
|
66
|
+
connect: invoice.status === 'uncollectible',
|
|
53
67
|
url: joinURL(
|
|
54
68
|
window.location.origin,
|
|
55
69
|
getPrefix(),
|
|
@@ -60,6 +74,7 @@ const getInvoiceLink = (invoice: TInvoiceExpanded, action?: string) => {
|
|
|
60
74
|
|
|
61
75
|
return {
|
|
62
76
|
external: true,
|
|
77
|
+
connect: false,
|
|
63
78
|
url: getTxLink(invoice.paymentMethod, invoice.metadata?.payment_details).link,
|
|
64
79
|
};
|
|
65
80
|
};
|
|
@@ -74,10 +89,14 @@ export default function CustomerInvoiceList({
|
|
|
74
89
|
target,
|
|
75
90
|
action,
|
|
76
91
|
}: Props) {
|
|
77
|
-
const { t, locale } = useLocaleContext();
|
|
78
92
|
const size = pageSize || 10;
|
|
79
93
|
|
|
80
|
-
const
|
|
94
|
+
const subscription = useSubscription('events');
|
|
95
|
+
const { t, locale } = useLocaleContext();
|
|
96
|
+
const { connect } = usePaymentContext();
|
|
97
|
+
const [state, setState] = useSetState({ paying: '' });
|
|
98
|
+
|
|
99
|
+
const { data, loadMore, loadingMore, loading, reloadAsync } = useInfiniteScroll<Result>(
|
|
81
100
|
(d) => {
|
|
82
101
|
const page = d ? Math.ceil(d.list.length / size) + 1 : 1;
|
|
83
102
|
return fetchData({
|
|
@@ -92,10 +111,52 @@ export default function CustomerInvoiceList({
|
|
|
92
111
|
});
|
|
93
112
|
},
|
|
94
113
|
{
|
|
95
|
-
reloadDeps: [customer_id, subscription_id, status],
|
|
114
|
+
reloadDeps: [customer_id, subscription_id, status, include_staking],
|
|
96
115
|
}
|
|
97
116
|
);
|
|
98
117
|
|
|
118
|
+
// Listen to invoice.paid event and refresh data
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
if (subscription && customer_id) {
|
|
121
|
+
subscription.on('invoice.paid', async ({ response }: { response: TInvoiceExpanded }) => {
|
|
122
|
+
if (response.customer_id === customer_id) {
|
|
123
|
+
await reloadAsync();
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}, [subscription]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
128
|
+
|
|
129
|
+
const onPay = (invoiceId: string) => {
|
|
130
|
+
if (state.paying) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
setState({ paying: invoiceId });
|
|
135
|
+
connect.open({
|
|
136
|
+
action: 'collect',
|
|
137
|
+
messages: {
|
|
138
|
+
scan: '',
|
|
139
|
+
title: t(`payment.customer.invoice.${action || 'pay'}`),
|
|
140
|
+
success: t(`payment.customer.invoice.${action || 'pay'}Success`),
|
|
141
|
+
error: t(`payment.customer.invoice.${action || 'pay'}Error`),
|
|
142
|
+
confirm: '',
|
|
143
|
+
} as any,
|
|
144
|
+
extraParams: { invoiceId, action },
|
|
145
|
+
onSuccess: () => {
|
|
146
|
+
connect.close();
|
|
147
|
+
setState({ paying: '' });
|
|
148
|
+
},
|
|
149
|
+
onClose: () => {
|
|
150
|
+
connect.close();
|
|
151
|
+
setState({ paying: '' });
|
|
152
|
+
},
|
|
153
|
+
onError: (err: any) => {
|
|
154
|
+
setState({ paying: '' });
|
|
155
|
+
Toast.error(formatError(err));
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
};
|
|
159
|
+
|
|
99
160
|
if (loading || !data) {
|
|
100
161
|
return <CircularProgress />;
|
|
101
162
|
}
|
|
@@ -169,16 +230,22 @@ export default function CustomerInvoiceList({
|
|
|
169
230
|
)}
|
|
170
231
|
<Box flex={1} textAlign="right">
|
|
171
232
|
{action ? (
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
233
|
+
link.connect ? (
|
|
234
|
+
<Button variant="contained" color="primary" size="small" onClick={() => onPay(invoice.id)}>
|
|
235
|
+
{t('payment.customer.invoice.pay')}
|
|
236
|
+
</Button>
|
|
237
|
+
) : (
|
|
238
|
+
<Button
|
|
239
|
+
component="a"
|
|
240
|
+
variant="contained"
|
|
241
|
+
color="primary"
|
|
242
|
+
size="small"
|
|
243
|
+
href={link.url}
|
|
244
|
+
target={link.external ? '_blank' : target}
|
|
245
|
+
rel="noreferrer">
|
|
246
|
+
{t('payment.customer.invoice.pay')}
|
|
247
|
+
</Button>
|
|
248
|
+
)
|
|
182
249
|
) : (
|
|
183
250
|
<Status label={invoice.status} color={getInvoiceStatusColor(invoice.status)} />
|
|
184
251
|
)}
|
|
@@ -222,9 +289,6 @@ const Root = styled(Stack)`
|
|
|
222
289
|
.invoice-description {
|
|
223
290
|
display: none !important;
|
|
224
291
|
}
|
|
225
|
-
svg[data-testid='OpenInNewOutlinedIcon'] {
|
|
226
|
-
display: none !important;
|
|
227
|
-
}
|
|
228
292
|
}
|
|
229
293
|
|
|
230
294
|
a.MuiButton-root {
|
|
@@ -15,10 +15,12 @@ import {
|
|
|
15
15
|
Typography,
|
|
16
16
|
} from '@mui/material';
|
|
17
17
|
import { useRequest } from 'ahooks';
|
|
18
|
-
import {
|
|
18
|
+
import { useMemo } from 'react';
|
|
19
|
+
import { joinURL, withQuery } from 'ufo';
|
|
19
20
|
|
|
20
|
-
import api from '../../api';
|
|
21
21
|
import Status from '../../components/status';
|
|
22
|
+
import api from '../../libs/api';
|
|
23
|
+
import { getDidConnectQueryParams } from '../../libs/did-connect';
|
|
22
24
|
import {
|
|
23
25
|
formatBNStr,
|
|
24
26
|
formatError,
|
|
@@ -28,7 +30,7 @@ import {
|
|
|
28
30
|
getInvoiceStatusColor,
|
|
29
31
|
getPrefix,
|
|
30
32
|
getSubscriptionStatusColor,
|
|
31
|
-
} from '../../util';
|
|
33
|
+
} from '../../libs/util';
|
|
32
34
|
|
|
33
35
|
const fetchInvoiceData = (params: Record<string, any> = {}): Promise<Paginated<TInvoiceExpanded>> => {
|
|
34
36
|
const search = new URLSearchParams();
|
|
@@ -66,6 +68,19 @@ export default function MiniInvoiceList() {
|
|
|
66
68
|
})
|
|
67
69
|
);
|
|
68
70
|
|
|
71
|
+
const subscriptionPageUrl: string = useMemo(() => {
|
|
72
|
+
if (!subscription) {
|
|
73
|
+
return '#';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const pageUrl: string = joinURL(window.location.origin, getPrefix(), `/customer/subscription/${subscription.id}`);
|
|
77
|
+
|
|
78
|
+
return withQuery(pageUrl, {
|
|
79
|
+
source: 'embed',
|
|
80
|
+
...getDidConnectQueryParams({ forceConnected: subscription.customer.did }),
|
|
81
|
+
});
|
|
82
|
+
}, [subscription]);
|
|
83
|
+
|
|
69
84
|
if (error) {
|
|
70
85
|
return (
|
|
71
86
|
<Position>
|
|
@@ -192,11 +207,7 @@ export default function MiniInvoiceList() {
|
|
|
192
207
|
variant="contained"
|
|
193
208
|
sx={{ color: '#fff!important', width: subscription.service_actions?.length ? 'auto' : '100%' }}
|
|
194
209
|
target="_blank"
|
|
195
|
-
href={
|
|
196
|
-
window.location.origin,
|
|
197
|
-
getPrefix(),
|
|
198
|
-
`/customer/subscription/${subscription.id}?source=embed`
|
|
199
|
-
)}>
|
|
210
|
+
href={subscriptionPageUrl}>
|
|
200
211
|
{t('payment.customer.subscriptions.view')}
|
|
201
212
|
</Button>
|
|
202
213
|
</Stack>
|
|
@@ -4,10 +4,10 @@ import type { Paginated, TPaymentIntentExpanded } from '@blocklet/payment-types'
|
|
|
4
4
|
import { Box, Button, CircularProgress, Stack, Typography } from '@mui/material';
|
|
5
5
|
import { useInfiniteScroll } from 'ahooks';
|
|
6
6
|
|
|
7
|
-
import api from '../../api';
|
|
8
7
|
import TxLink from '../../components/blockchain/tx';
|
|
9
8
|
import Status from '../../components/status';
|
|
10
|
-
import
|
|
9
|
+
import api from '../../libs/api';
|
|
10
|
+
import { formatBNStr, formatToDate, getPaymentIntentStatusColor } from '../../libs/util';
|
|
11
11
|
|
|
12
12
|
const groupByDate = (items: TPaymentIntentExpanded[]) => {
|
|
13
13
|
const grouped: { [key: string]: TPaymentIntentExpanded[] } = {};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { WsClient } from '@arcblock/ws';
|
|
2
|
+
import get from 'lodash/get';
|
|
3
|
+
import { useEffect, useRef } from 'react';
|
|
4
|
+
|
|
5
|
+
const RELAY_SOCKET_PREFIX = '/.well-known/service/relay';
|
|
6
|
+
const getAppId = () => get(window, 'blocklet.appPid') || get(window, 'blocklet.appId') || '';
|
|
7
|
+
const getRelayChannel = (token: string) => `relay:${getAppId()}:${token}`;
|
|
8
|
+
const getRelayProtocol = () => (window.location.protocol === 'https:' ? 'wss:' : 'ws:');
|
|
9
|
+
const getSocketHost = () => new URL(window.location.href).host;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @description channel 的值不能包含分隔符 / . : 等,否则前端接受不到事件
|
|
13
|
+
* @export
|
|
14
|
+
* @param {string} channel
|
|
15
|
+
* @return {*}
|
|
16
|
+
*/
|
|
17
|
+
export function useSubscription(channel: string) {
|
|
18
|
+
const socket = useRef<typeof WsClient>(null);
|
|
19
|
+
const subscription = useRef<any>(null);
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (getAppId()) {
|
|
23
|
+
const needReconnect = !socket.current || socket.current.isConnected() === false;
|
|
24
|
+
if (needReconnect) {
|
|
25
|
+
socket.current = new WsClient(`${getRelayProtocol()}//${getSocketHost()}${RELAY_SOCKET_PREFIX}`, {
|
|
26
|
+
longpollerTimeout: 5000, // connection timeout
|
|
27
|
+
heartbeatIntervalMs: 30 * 1000,
|
|
28
|
+
});
|
|
29
|
+
socket.current.connect();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return () => {
|
|
34
|
+
if (socket.current) {
|
|
35
|
+
socket.current.disconnect();
|
|
36
|
+
socket.current = null;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (channel) {
|
|
43
|
+
let needSubscription = false;
|
|
44
|
+
if (subscription.current) {
|
|
45
|
+
if (subscription.current.channel !== channel) {
|
|
46
|
+
socket.current?.unsubscribe(getRelayChannel(subscription.current.channel));
|
|
47
|
+
needSubscription = true;
|
|
48
|
+
}
|
|
49
|
+
} else {
|
|
50
|
+
needSubscription = true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (needSubscription) {
|
|
54
|
+
subscription.current = socket.current.subscribe(getRelayChannel(channel));
|
|
55
|
+
subscription.current.channel = channel;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return () => {
|
|
60
|
+
if (subscription.current) {
|
|
61
|
+
socket.current?.unsubscribe(getRelayChannel(subscription.current.channel));
|
|
62
|
+
subscription.current = null;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}, [channel]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
66
|
+
|
|
67
|
+
return subscription.current;
|
|
68
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import api from './api';
|
|
2
1
|
import CheckoutDonate from './checkout/donate';
|
|
3
2
|
import CheckoutForm from './checkout/form';
|
|
4
3
|
import CheckoutTable from './checkout/table';
|
|
@@ -11,10 +10,11 @@ import PricingTable from './components/pricing-table';
|
|
|
11
10
|
import SafeGuard from './components/safe-guard';
|
|
12
11
|
import Status from './components/status';
|
|
13
12
|
import Switch from './components/switch-button';
|
|
14
|
-
import dayjs from './dayjs';
|
|
15
13
|
import CustomerInvoiceList from './history/invoice/list';
|
|
16
14
|
import MiniInvoiceList from './history/mini-invoice/list';
|
|
17
15
|
import CustomerPaymentList from './history/payment/list';
|
|
16
|
+
import api from './libs/api';
|
|
17
|
+
import dayjs from './libs/dayjs';
|
|
18
18
|
import Amount from './payment/amount';
|
|
19
19
|
import AddressForm from './payment/form/address';
|
|
20
20
|
import CurrencySelector from './payment/form/currency';
|
|
@@ -24,8 +24,9 @@ import Payment from './payment/index';
|
|
|
24
24
|
import ProductSkeleton from './payment/product-skeleton';
|
|
25
25
|
import PaymentSummary from './payment/summary';
|
|
26
26
|
|
|
27
|
-
export * from './util';
|
|
27
|
+
export * from './libs/util';
|
|
28
28
|
export * from './contexts/payment';
|
|
29
|
+
export * from './hooks/subscription';
|
|
29
30
|
|
|
30
31
|
export { translations, createTranslator } from './locales';
|
|
31
32
|
|
|
@@ -12,9 +12,14 @@ api.interceptors.request.use(
|
|
|
12
12
|
// 'https://storage.staging.abtnet.io/app/payment-kit/.well-known/service'
|
|
13
13
|
config.baseURL = prefix || '';
|
|
14
14
|
|
|
15
|
-
const livemode = localStorage.getItem('livemode');
|
|
16
15
|
const locale = getLocale(window.blocklet?.languages);
|
|
17
|
-
|
|
16
|
+
const query = new URLSearchParams(config.url?.split('?').pop());
|
|
17
|
+
config.params = { ...(config.params || {}), locale };
|
|
18
|
+
// If we do not have livemode neither in config nor in query params, we will use the value from localStorage
|
|
19
|
+
if (typeof config.params.livemode === 'undefined' && query.has('livemode') === false) {
|
|
20
|
+
const livemode = localStorage.getItem('livemode');
|
|
21
|
+
config.params.livemode = isNull(livemode) ? true : JSON.parse(livemode);
|
|
22
|
+
}
|
|
18
23
|
|
|
19
24
|
return config;
|
|
20
25
|
},
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface DIDConnectCustomQuery {
|
|
2
|
+
forceConnected: string; // 填写用户的 DID
|
|
3
|
+
sourceAppPid?: string; // 应用程序 PID
|
|
4
|
+
switchBehavior?: 'auto' | 'disabled' | 'required'; // auto 代表引导切换,disabled 代表不提示,required 代表强制要求切换
|
|
5
|
+
showClose?: boolean; // 是否显示关闭按钮
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function getDidConnectQueryParams(params: DIDConnectCustomQuery) {
|
|
9
|
+
const didConnectQueryParams: DIDConnectCustomQuery = {
|
|
10
|
+
switchBehavior: 'auto',
|
|
11
|
+
showClose: false,
|
|
12
|
+
...params,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
'__did-connect__': Buffer.from(JSON.stringify(didConnectQueryParams), 'utf8').toString('base64'),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { getDidConnectQueryParams };
|
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
TSubscriptionExpanded,
|
|
14
14
|
TSubscriptionItemExpanded,
|
|
15
15
|
} from '@blocklet/payment-types';
|
|
16
|
+
import type { TComponent } from '@blocklet/sdk/lib/config';
|
|
16
17
|
import { BN, fromUnitToToken } from '@ocap/util';
|
|
17
18
|
import omit from 'lodash/omit';
|
|
18
19
|
import trimEnd from 'lodash/trimEnd';
|
|
@@ -20,8 +21,8 @@ import numbro from 'numbro';
|
|
|
20
21
|
import { defaultCountries } from 'react-international-phone';
|
|
21
22
|
import { joinURL } from 'ufo';
|
|
22
23
|
|
|
24
|
+
import { t } from '../locales';
|
|
23
25
|
import dayjs from './dayjs';
|
|
24
|
-
import { t } from './locales';
|
|
25
26
|
|
|
26
27
|
export const PAYMENT_KIT_DID = 'z2qaCNvKMv5GjouKdcDWexv6WqtHbpNPQDnAk';
|
|
27
28
|
|
|
@@ -29,12 +30,12 @@ export const isPaymentKitMounted = () => {
|
|
|
29
30
|
return (window.blocklet?.componentMountPoints || []).some((x: any) => x.did === PAYMENT_KIT_DID);
|
|
30
31
|
};
|
|
31
32
|
|
|
32
|
-
export const getPrefix = () => {
|
|
33
|
+
export const getPrefix = (): string => {
|
|
33
34
|
const componentId = (window?.blocklet?.componentId || '').split('/').pop();
|
|
34
35
|
if (componentId === PAYMENT_KIT_DID) {
|
|
35
36
|
return window.blocklet?.prefix;
|
|
36
37
|
}
|
|
37
|
-
const component = (window.blocklet?.componentMountPoints || []).find((x:
|
|
38
|
+
const component = (window.blocklet?.componentMountPoints || []).find((x: TComponent) => x.did === PAYMENT_KIT_DID);
|
|
38
39
|
if (component) {
|
|
39
40
|
return component.mountPoint;
|
|
40
41
|
}
|
package/src/locales/en.tsx
CHANGED
package/src/locales/zh.tsx
CHANGED
|
@@ -3,7 +3,13 @@ import 'react-international-phone/style.css';
|
|
|
3
3
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
4
4
|
import { useTheme } from '@arcblock/ux/lib/Theme';
|
|
5
5
|
import Toast from '@arcblock/ux/lib/Toast';
|
|
6
|
-
import type {
|
|
6
|
+
import type {
|
|
7
|
+
TCheckoutSession,
|
|
8
|
+
TCustomer,
|
|
9
|
+
TInvoice,
|
|
10
|
+
TPaymentIntent,
|
|
11
|
+
TPaymentMethodExpanded,
|
|
12
|
+
} from '@blocklet/payment-types';
|
|
7
13
|
import { LoadingButton } from '@mui/lab';
|
|
8
14
|
import { Fade, InputAdornment, Stack, Typography } from '@mui/material';
|
|
9
15
|
import { useCreation, useMemoizedFn, useSetState, useSize } from 'ahooks';
|
|
@@ -15,12 +21,13 @@ import { joinURL } from 'ufo';
|
|
|
15
21
|
import { dispatch } from 'use-bus';
|
|
16
22
|
import isEmail from 'validator/es/lib/isEmail';
|
|
17
23
|
|
|
18
|
-
import api from '../../api';
|
|
19
24
|
import ConfirmDialog from '../../components/confirm';
|
|
20
25
|
import FormInput from '../../components/input';
|
|
21
26
|
import { usePaymentContext } from '../../contexts/payment';
|
|
27
|
+
import { useSubscription } from '../../hooks/subscription';
|
|
28
|
+
import api from '../../libs/api';
|
|
29
|
+
import { flattenPaymentMethods, formatError, getPrefix, getStatementDescriptor } from '../../libs/util';
|
|
22
30
|
import { CheckoutCallbacks, CheckoutContext } from '../../types';
|
|
23
|
-
import { flattenPaymentMethods, formatError, getPrefix, getStatementDescriptor } from '../../util';
|
|
24
31
|
import UserButtons from './addon';
|
|
25
32
|
import AddressForm from './address';
|
|
26
33
|
import CurrencySelector from './currency';
|
|
@@ -85,6 +92,7 @@ export default function PaymentForm({
|
|
|
85
92
|
const theme = useTheme();
|
|
86
93
|
const { t } = useLocaleContext();
|
|
87
94
|
const { session, connect } = usePaymentContext();
|
|
95
|
+
const subscription = useSubscription('events');
|
|
88
96
|
const { control, getValues, setValue, handleSubmit } = useFormContext();
|
|
89
97
|
const [state, setState] = useSetState<{
|
|
90
98
|
submitting: boolean;
|
|
@@ -113,6 +121,25 @@ export default function PaymentForm({
|
|
|
113
121
|
const currencies = flattenPaymentMethods(paymentMethods);
|
|
114
122
|
const [paymentCurrencyIndex, setPaymentCurrencyIndex] = useState(0);
|
|
115
123
|
|
|
124
|
+
const onCheckoutComplete = useMemoizedFn(async ({ response }: { response: TCheckoutSession }) => {
|
|
125
|
+
if (response.id === checkoutSession.id && state.paid === false) {
|
|
126
|
+
await handleConnected();
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const onInvoicePaid = useMemoizedFn(async ({ response }: { response: TInvoice }) => {
|
|
131
|
+
if (response.customer_id === customer?.id && state.customerLimited) {
|
|
132
|
+
await onAction();
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
useEffect(() => {
|
|
137
|
+
if (subscription) {
|
|
138
|
+
subscription.on('checkout.session.completed', onCheckoutComplete);
|
|
139
|
+
subscription.on('invoice.paid', onInvoicePaid);
|
|
140
|
+
}
|
|
141
|
+
}, [subscription]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
142
|
+
|
|
116
143
|
useEffect(() => {
|
|
117
144
|
if (session?.user) {
|
|
118
145
|
const values = getValues();
|
|
@@ -177,8 +204,10 @@ export default function PaymentForm({
|
|
|
177
204
|
const handleConnected = async () => {
|
|
178
205
|
try {
|
|
179
206
|
const result = await waitForCheckoutComplete(checkoutSession.id);
|
|
180
|
-
|
|
181
|
-
|
|
207
|
+
if (state.paid === false) {
|
|
208
|
+
setState({ paid: true, paying: false });
|
|
209
|
+
onPaid(result);
|
|
210
|
+
}
|
|
182
211
|
} catch (err) {
|
|
183
212
|
Toast.error(formatError(err));
|
|
184
213
|
} finally {
|
|
@@ -230,6 +259,7 @@ export default function PaymentForm({
|
|
|
230
259
|
stripeContext: result.data.stripeContext,
|
|
231
260
|
customer: result.data.customer,
|
|
232
261
|
submitting: false,
|
|
262
|
+
customerLimited: false,
|
|
233
263
|
});
|
|
234
264
|
|
|
235
265
|
if (['arcblock', 'ethereum'].includes(method.type)) {
|
|
@@ -440,6 +470,7 @@ export default function PaymentForm({
|
|
|
440
470
|
confirm={t('payment.customer.pastDue.alert.confirm')}
|
|
441
471
|
title={t('payment.customer.pastDue.alert.title')}
|
|
442
472
|
message={t('payment.customer.pastDue.alert.description')}
|
|
473
|
+
color="primary"
|
|
443
474
|
/>
|
|
444
475
|
)}
|
|
445
476
|
</>
|
|
@@ -6,7 +6,7 @@ import { useFormContext, useWatch } from 'react-hook-form';
|
|
|
6
6
|
import { CountryIso2, FlagEmoji, defaultCountries, parseCountry, usePhoneInput } from 'react-international-phone';
|
|
7
7
|
|
|
8
8
|
import FormInput from '../../components/input';
|
|
9
|
-
import { isValidCountry } from '../../util';
|
|
9
|
+
import { isValidCountry } from '../../libs/util';
|
|
10
10
|
|
|
11
11
|
export default function PhoneInput({ ...props }) {
|
|
12
12
|
const countryFieldName = props.countryFieldName || 'billing_address.country';
|
package/src/payment/header.tsx
CHANGED
|
@@ -4,7 +4,7 @@ import { Avatar, Stack, Typography } from '@mui/material';
|
|
|
4
4
|
import { useCreation, useLocalStorageState, useSize } from 'ahooks';
|
|
5
5
|
|
|
6
6
|
import Livemode from '../components/livemode';
|
|
7
|
-
import { getStatementDescriptor } from '../util';
|
|
7
|
+
import { getStatementDescriptor } from '../libs/util';
|
|
8
8
|
import UserButtons from './form/addon';
|
|
9
9
|
|
|
10
10
|
type Props = {
|
package/src/payment/index.tsx
CHANGED
|
@@ -17,10 +17,10 @@ import { useEffect, useState } from 'react';
|
|
|
17
17
|
import { FormProvider, useForm, useWatch } from 'react-hook-form';
|
|
18
18
|
import type { LiteralUnion } from 'type-fest';
|
|
19
19
|
|
|
20
|
-
import api from '../api';
|
|
21
20
|
import { usePaymentContext } from '../contexts/payment';
|
|
21
|
+
import api from '../libs/api';
|
|
22
|
+
import { findCurrency, formatError, getStatementDescriptor, isValidCountry } from '../libs/util';
|
|
22
23
|
import { CheckoutCallbacks, CheckoutContext, CheckoutFormData } from '../types';
|
|
23
|
-
import { findCurrency, formatError, getStatementDescriptor, isValidCountry } from '../util';
|
|
24
24
|
import PaymentError from './error';
|
|
25
25
|
import CheckoutFooter from './footer';
|
|
26
26
|
import PaymentForm from './form';
|
|
@@ -387,6 +387,13 @@ export const Root = styled(Box)<{ mode: LiteralUnion<'standalone' | 'inline' | '
|
|
|
387
387
|
margin-top: ${(props) => (props.mode === 'standalone' ? '64px' : '0')};
|
|
388
388
|
}
|
|
389
389
|
.cko-product-summary {
|
|
390
|
+
width: 100%;
|
|
391
|
+
}
|
|
392
|
+
.cko-ellipsis {
|
|
393
|
+
width: 100%;
|
|
394
|
+
white-space: nowrap;
|
|
395
|
+
overflow: hidden;
|
|
396
|
+
text-overflow: ellipsis;
|
|
390
397
|
}
|
|
391
398
|
|
|
392
399
|
.cko-payment {
|
|
@@ -14,7 +14,7 @@ type Props = {
|
|
|
14
14
|
export default function ProductCard({ size, variant, name, logo, description, extra }: Props) {
|
|
15
15
|
const s = { width: size, height: size };
|
|
16
16
|
return (
|
|
17
|
-
<Stack direction="row" alignItems="flex-start" spacing={1} flex={2}>
|
|
17
|
+
<Stack direction="row" alignItems="flex-start" spacing={1} flex={2} sx={{ width: '100%' }}>
|
|
18
18
|
{logo ? (
|
|
19
19
|
// @ts-ignore
|
|
20
20
|
<Avatar src={logo} alt={name} variant={variant} sx={s} />
|
|
@@ -24,13 +24,23 @@ export default function ProductCard({ size, variant, name, logo, description, ex
|
|
|
24
24
|
{name.slice(0, 1)}
|
|
25
25
|
</Avatar>
|
|
26
26
|
)}
|
|
27
|
-
<Stack
|
|
28
|
-
|
|
27
|
+
<Stack
|
|
28
|
+
direction="column"
|
|
29
|
+
alignItems="flex-start"
|
|
30
|
+
justifyContent="space-around"
|
|
31
|
+
sx={{ flexShrink: 1, overflow: 'hidden' }}>
|
|
32
|
+
<Typography
|
|
33
|
+
className="cko-ellipsis"
|
|
34
|
+
variant="body1"
|
|
35
|
+
title={name}
|
|
36
|
+
sx={{ fontWeight: 500, mb: 0.5, lineHeight: 1 }}
|
|
37
|
+
color="text.primary">
|
|
29
38
|
{name}
|
|
30
39
|
</Typography>
|
|
31
40
|
{description && (
|
|
32
41
|
<Typography
|
|
33
42
|
variant="body1"
|
|
43
|
+
title={description}
|
|
34
44
|
sx={{ fontSize: '0.85rem', mb: 0.5, lineHeight: 1, textAlign: 'left' }}
|
|
35
45
|
color="text.secondary">
|
|
36
46
|
{description}
|
|
@@ -4,7 +4,7 @@ import { Stack, Typography } from '@mui/material';
|
|
|
4
4
|
|
|
5
5
|
import Status from '../components/status';
|
|
6
6
|
import Switch from '../components/switch-button';
|
|
7
|
-
import { formatLineItemPricing, formatPrice, formatRecurring, formatUpsellSaving } from '../util';
|
|
7
|
+
import { formatLineItemPricing, formatPrice, formatRecurring, formatUpsellSaving } from '../libs/util';
|
|
8
8
|
import ProductCard from './product-card';
|
|
9
9
|
|
|
10
10
|
type Props = {
|
|
@@ -41,7 +41,12 @@ export default function ProductItem({
|
|
|
41
41
|
return (
|
|
42
42
|
<Stack direction="column" alignItems="flex-start" spacing={1} sx={{ width: '100%' }}>
|
|
43
43
|
<Stack direction="column" alignItems="flex-end" sx={{ width: '100%' }}>
|
|
44
|
-
<Stack
|
|
44
|
+
<Stack
|
|
45
|
+
direction="row"
|
|
46
|
+
alignItems="flex-start"
|
|
47
|
+
spacing={0.5}
|
|
48
|
+
justifyContent="space-between"
|
|
49
|
+
sx={{ width: '100%' }}>
|
|
45
50
|
<ProductCard
|
|
46
51
|
logo={item.price.product?.images[0]}
|
|
47
52
|
name={item.price.product?.name}
|
package/src/payment/summary.tsx
CHANGED
|
@@ -8,9 +8,9 @@ import { useRequest, useSetState } from 'ahooks';
|
|
|
8
8
|
import noop from 'lodash/noop';
|
|
9
9
|
import useBus from 'use-bus';
|
|
10
10
|
|
|
11
|
-
import api from '../api';
|
|
12
11
|
import Status from '../components/status';
|
|
13
|
-
import
|
|
12
|
+
import api from '../libs/api';
|
|
13
|
+
import { formatAmount, formatCheckoutHeadlines, getPriceUintAmountByCurrency } from '../libs/util';
|
|
14
14
|
import PaymentAmount from './amount';
|
|
15
15
|
import ProductDonation from './product-donation';
|
|
16
16
|
import ProductItem from './product-item';
|
|
@@ -177,7 +177,11 @@ export default function PaymentSummary({
|
|
|
177
177
|
<Fade in>
|
|
178
178
|
<Stack className="cko-product" direction="column" {...rest}>
|
|
179
179
|
<Stack className="cko-product-summary" direction="column" alignItems="flex-start" sx={{ mb: { xs: 1, sm: 3 } }}>
|
|
180
|
-
<Typography
|
|
180
|
+
<Typography
|
|
181
|
+
className="cko-ellipsis"
|
|
182
|
+
component="div"
|
|
183
|
+
title={action || headlines.action}
|
|
184
|
+
sx={{ fontWeight: 500, fontSize: '1.15rem', color: 'text.secondary' }}>
|
|
181
185
|
{action || headlines.action}
|
|
182
186
|
</Typography>
|
|
183
187
|
<PaymentAmount amount={headlines.amount} />
|