@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.
Files changed (104) hide show
  1. package/es/checkout/donate.js +7 -5
  2. package/es/checkout/form.js +2 -2
  3. package/es/checkout/table.js +2 -2
  4. package/es/components/blockchain/gas.js +1 -1
  5. package/es/components/blockchain/tx.js +1 -1
  6. package/es/components/confirm.d.ts +4 -2
  7. package/es/components/confirm.js +5 -2
  8. package/es/components/pricing-table.js +1 -1
  9. package/es/components/safe-guard.js +1 -1
  10. package/es/contexts/payment.js +2 -2
  11. package/es/history/invoice/list.js +62 -10
  12. package/es/history/mini-invoice/list.js +16 -8
  13. package/es/history/payment/list.js +2 -2
  14. package/es/hooks/subscription.d.ts +7 -0
  15. package/es/hooks/subscription.js +55 -0
  16. package/es/index.d.ts +4 -3
  17. package/es/index.js +4 -3
  18. package/es/{api.js → libs/api.js} +6 -2
  19. package/es/libs/did-connect.d.ts +10 -0
  20. package/es/libs/did-connect.js +11 -0
  21. package/es/{util.d.ts → libs/util.d.ts} +1 -1
  22. package/es/{util.js → libs/util.js} +1 -1
  23. package/es/locales/en.js +1 -0
  24. package/es/locales/zh.js +1 -0
  25. package/es/payment/form/index.js +28 -6
  26. package/es/payment/form/phone.js +1 -1
  27. package/es/payment/header.js +1 -1
  28. package/es/payment/index.js +9 -2
  29. package/es/payment/product-card.js +34 -14
  30. package/es/payment/product-item.js +26 -16
  31. package/es/payment/summary.js +12 -3
  32. package/es/types/shims.d.ts +1 -3
  33. package/lib/checkout/donate.js +6 -5
  34. package/lib/checkout/form.js +2 -2
  35. package/lib/checkout/table.js +2 -2
  36. package/lib/components/blockchain/gas.js +1 -1
  37. package/lib/components/blockchain/tx.js +1 -1
  38. package/lib/components/confirm.d.ts +4 -2
  39. package/lib/components/confirm.js +5 -2
  40. package/lib/components/pricing-table.js +1 -1
  41. package/lib/components/safe-guard.js +1 -1
  42. package/lib/contexts/payment.js +2 -2
  43. package/lib/history/invoice/list.js +77 -9
  44. package/lib/history/mini-invoice/list.js +17 -3
  45. package/lib/history/payment/list.js +2 -2
  46. package/lib/hooks/subscription.d.ts +7 -0
  47. package/lib/hooks/subscription.js +62 -0
  48. package/lib/index.d.ts +4 -3
  49. package/lib/index.js +17 -5
  50. package/lib/{api.js → libs/api.js} +5 -2
  51. package/lib/libs/did-connect.d.ts +10 -0
  52. package/lib/libs/did-connect.js +16 -0
  53. package/lib/{util.d.ts → libs/util.d.ts} +1 -1
  54. package/lib/{util.js → libs/util.js} +1 -1
  55. package/lib/locales/en.js +1 -0
  56. package/lib/locales/zh.js +1 -0
  57. package/lib/payment/form/index.js +35 -9
  58. package/lib/payment/form/phone.js +1 -1
  59. package/lib/payment/header.js +1 -1
  60. package/lib/payment/index.js +9 -2
  61. package/lib/payment/product-card.js +10 -0
  62. package/lib/payment/product-item.js +3 -2
  63. package/lib/payment/summary.js +4 -2
  64. package/lib/types/shims.d.ts +1 -3
  65. package/package.json +31 -30
  66. package/src/checkout/donate.tsx +7 -5
  67. package/src/checkout/form.tsx +2 -2
  68. package/src/checkout/table.tsx +2 -2
  69. package/src/components/blockchain/gas.tsx +1 -1
  70. package/src/components/blockchain/tx.tsx +1 -1
  71. package/src/components/confirm.tsx +7 -3
  72. package/src/components/pricing-table.tsx +1 -1
  73. package/src/components/safe-guard.tsx +1 -1
  74. package/src/contexts/payment.tsx +2 -2
  75. package/src/history/invoice/list.tsx +83 -19
  76. package/src/history/mini-invoice/list.tsx +19 -8
  77. package/src/history/payment/list.tsx +2 -2
  78. package/src/hooks/subscription.ts +68 -0
  79. package/src/index.ts +4 -3
  80. package/src/{api.ts → libs/api.ts} +7 -2
  81. package/src/libs/did-connect.ts +20 -0
  82. package/src/{util.ts → libs/util.ts} +4 -3
  83. package/src/locales/en.tsx +1 -0
  84. package/src/locales/zh.tsx +1 -0
  85. package/src/payment/form/index.tsx +36 -5
  86. package/src/payment/form/phone.tsx +1 -1
  87. package/src/payment/header.tsx +1 -1
  88. package/src/payment/index.tsx +9 -2
  89. package/src/payment/product-card.tsx +13 -3
  90. package/src/payment/product-item.tsx +7 -2
  91. package/src/payment/summary.tsx +7 -3
  92. package/src/types/shims.d.ts +1 -3
  93. /package/es/{api.d.ts → libs/api.d.ts} +0 -0
  94. /package/es/{dayjs.d.ts → libs/dayjs.d.ts} +0 -0
  95. /package/es/{dayjs.js → libs/dayjs.js} +0 -0
  96. /package/es/{theme.d.ts → libs/theme.d.ts} +0 -0
  97. /package/es/{theme.js → libs/theme.js} +0 -0
  98. /package/lib/{api.d.ts → libs/api.d.ts} +0 -0
  99. /package/lib/{dayjs.d.ts → libs/dayjs.d.ts} +0 -0
  100. /package/lib/{dayjs.js → libs/dayjs.js} +0 -0
  101. /package/lib/{theme.d.ts → libs/theme.d.ts} +0 -0
  102. /package/lib/{theme.js → libs/theme.js} +0 -0
  103. /package/src/{dayjs.ts → libs/dayjs.ts} +0 -0
  104. /package/src/{theme.ts → libs/theme.ts} +0 -0
@@ -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 { formatBNStr, formatToDate, formatToDatetime, getInvoiceStatusColor, getPrefix, getTxLink } from '../../util';
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 { data, loadMore, loadingMore, loading } = useInfiniteScroll<Result>(
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
- <Button
173
- component="a"
174
- variant="contained"
175
- color="primary"
176
- size="small"
177
- href={link.url}
178
- target={link.external ? '_blank' : target}
179
- rel="noreferrer">
180
- {t('payment.customer.invoice.pay')}
181
- </Button>
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 { joinURL } from 'ufo';
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={joinURL(
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 { formatBNStr, formatToDate, getPaymentIntentStatusColor } from '../../util';
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
- config.params = { ...(config.params || {}), livemode: isNull(livemode) ? true : JSON.parse(livemode), locale };
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: any) => x.did === PAYMENT_KIT_DID);
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
  }
@@ -274,6 +274,7 @@ export default flat({
274
274
  title: 'Manage subscriptions',
275
275
  view: 'View Subscription',
276
276
  current: 'Current subscription',
277
+ viewAll: 'View all',
277
278
  empty: 'There are no subscriptions here',
278
279
  changePayment: 'Change payment method',
279
280
  },
@@ -266,6 +266,7 @@ export default flat({
266
266
  title: '订阅管理',
267
267
  view: '管理订阅',
268
268
  current: '当前订阅',
269
+ viewAll: '查看所有',
269
270
  empty: '没有任何订阅',
270
271
  changePayment: '切换支付方式',
271
272
  },
@@ -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 { TCustomer, TPaymentIntent, TPaymentMethodExpanded } from '@blocklet/payment-types';
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
- setState({ paid: true, paying: false });
181
- onPaid(result);
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';
@@ -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 = {
@@ -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 direction="column" alignItems="flex-start" justifyContent="space-around">
28
- <Typography variant="body1" sx={{ fontWeight: 500, mb: 0.5, lineHeight: 1 }} color="text.primary">
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 direction="row" alignItems="center" justifyContent="space-between" sx={{ width: '100%' }}>
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}
@@ -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 { formatAmount, formatCheckoutHeadlines, getPriceUintAmountByCurrency } from '../util';
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 component="div" sx={{ fontWeight: 500, fontSize: '1.15rem', color: 'text.secondary' }}>
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} />
@@ -13,6 +13,4 @@ declare module '@blocklet/*';
13
13
 
14
14
  declare module 'pretty-ms-i18n';
15
15
 
16
- declare interface Window {
17
- blocklet: any;
18
- }
16
+ declare var blocklet: import('@blocklet/sdk').WindowBlocklet;