payment-kit 1.13.111 → 1.13.113

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 (126) hide show
  1. package/api/src/libs/notification/template/subscription-trial-will-end.ts +1 -1
  2. package/api/src/libs/notification/template/subscription-will-renew.ts +1 -1
  3. package/api/src/routes/products.ts +1 -0
  4. package/api/src/routes/subscriptions.ts +2 -0
  5. package/blocklet.yml +1 -1
  6. package/package.json +6 -6
  7. package/src/components/actions.tsx +1 -2
  8. package/src/components/blockchain/tx.tsx +1 -1
  9. package/src/components/click-boundary.tsx +1 -2
  10. package/src/components/customer/actions.tsx +2 -3
  11. package/src/components/customer/edit.tsx +3 -3
  12. package/src/components/customer/form.tsx +22 -24
  13. package/src/components/drawer-form.tsx +2 -4
  14. package/src/components/event/list.tsx +2 -3
  15. package/src/components/invoice/action.tsx +2 -3
  16. package/src/components/invoice/list.tsx +3 -4
  17. package/src/components/invoice/table.tsx +7 -7
  18. package/src/components/metadata/form.tsx +3 -4
  19. package/src/components/passport/actions.tsx +1 -2
  20. package/src/components/passport/assign.tsx +1 -1
  21. package/src/components/payment-intent/actions.tsx +2 -3
  22. package/src/components/payment-intent/list.tsx +3 -4
  23. package/src/components/payment-link/actions.tsx +2 -3
  24. package/src/components/payment-link/item.tsx +3 -5
  25. package/src/components/payment-link/product-select.tsx +3 -4
  26. package/src/components/payment-link/rename.tsx +3 -4
  27. package/src/components/payment-method/arcblock.tsx +1 -2
  28. package/src/components/payment-method/bitcoin.tsx +1 -2
  29. package/src/components/payment-method/ethereum.tsx +1 -2
  30. package/src/components/payment-method/stripe.tsx +1 -2
  31. package/src/components/portal/invoice/list.tsx +4 -5
  32. package/src/components/portal/subscription/actions.tsx +7 -8
  33. package/src/components/portal/subscription/cancel.tsx +13 -12
  34. package/src/components/portal/subscription/list.tsx +4 -10
  35. package/src/components/price/currency-select.tsx +2 -2
  36. package/src/components/price/form.tsx +3 -4
  37. package/src/components/price/upsell-select.tsx +2 -3
  38. package/src/components/price/upsell.tsx +3 -5
  39. package/src/components/pricing-table/actions.tsx +2 -3
  40. package/src/components/pricing-table/payment-settings.tsx +4 -4
  41. package/src/components/pricing-table/price-item.tsx +4 -7
  42. package/src/components/pricing-table/product-item.tsx +3 -3
  43. package/src/components/pricing-table/rename.tsx +3 -4
  44. package/src/components/product/actions.tsx +2 -3
  45. package/src/components/product/add-price.tsx +2 -2
  46. package/src/components/product/create.tsx +2 -4
  47. package/src/components/product/cross-sell-select.tsx +3 -3
  48. package/src/components/product/cross-sell.tsx +4 -5
  49. package/src/components/product/edit.tsx +1 -1
  50. package/src/components/product/form.tsx +6 -6
  51. package/src/components/relative-time.tsx +1 -2
  52. package/src/components/subscription/actions/cancel.tsx +2 -3
  53. package/src/components/subscription/actions/index.tsx +2 -3
  54. package/src/components/subscription/items/actions.tsx +1 -1
  55. package/src/components/subscription/items/index.tsx +2 -2
  56. package/src/components/subscription/items/usage-records.tsx +2 -3
  57. package/src/components/subscription/list.tsx +3 -4
  58. package/src/components/subscription/metrics.tsx +2 -2
  59. package/src/components/subscription/status.tsx +3 -3
  60. package/src/components/webhook/attempts.tsx +3 -3
  61. package/src/contexts/products.tsx +2 -3
  62. package/src/libs/api.ts +2 -19
  63. package/src/libs/dayjs.ts +1 -15
  64. package/src/libs/util.ts +24 -555
  65. package/src/locales/en.tsx +0 -139
  66. package/src/locales/index.tsx +5 -23
  67. package/src/locales/zh.tsx +0 -136
  68. package/src/pages/admin/billing/index.tsx +4 -2
  69. package/src/pages/admin/billing/invoices/detail.tsx +3 -4
  70. package/src/pages/admin/billing/subscriptions/detail.tsx +3 -3
  71. package/src/pages/admin/customers/customers/detail.tsx +3 -5
  72. package/src/pages/admin/customers/customers/index.tsx +2 -3
  73. package/src/pages/admin/customers/index.tsx +4 -2
  74. package/src/pages/admin/developers/events/detail.tsx +2 -3
  75. package/src/pages/admin/developers/index.tsx +4 -2
  76. package/src/pages/admin/developers/webhooks/detail.tsx +3 -4
  77. package/src/pages/admin/developers/webhooks/index.tsx +3 -4
  78. package/src/pages/admin/index.tsx +10 -7
  79. package/src/pages/admin/payments/index.tsx +4 -2
  80. package/src/pages/admin/payments/intents/detail.tsx +4 -6
  81. package/src/pages/admin/payments/links/create.tsx +3 -5
  82. package/src/pages/admin/payments/links/detail.tsx +4 -5
  83. package/src/pages/admin/payments/links/index.tsx +4 -6
  84. package/src/pages/admin/products/passports/index.tsx +1 -1
  85. package/src/pages/admin/products/prices/actions.tsx +2 -3
  86. package/src/pages/admin/products/prices/detail.tsx +2 -3
  87. package/src/pages/admin/products/prices/list.tsx +2 -4
  88. package/src/pages/admin/products/pricing-tables/create.tsx +2 -3
  89. package/src/pages/admin/products/pricing-tables/detail.tsx +4 -5
  90. package/src/pages/admin/products/pricing-tables/index.tsx +2 -4
  91. package/src/pages/admin/products/products/create.tsx +2 -4
  92. package/src/pages/admin/products/products/detail.tsx +4 -5
  93. package/src/pages/admin/products/products/index.tsx +4 -6
  94. package/src/pages/admin/settings/index.tsx +4 -2
  95. package/src/pages/admin/settings/payment-methods/create.tsx +3 -5
  96. package/src/pages/admin/settings/payment-methods/index.tsx +2 -3
  97. package/src/pages/checkout/index.tsx +6 -3
  98. package/src/pages/checkout/pay.tsx +5 -69
  99. package/src/pages/checkout/pricing-table.tsx +3 -7
  100. package/src/pages/customer/index.tsx +7 -7
  101. package/src/pages/customer/invoice.tsx +10 -11
  102. package/src/pages/customer/subscription/detail.tsx +4 -4
  103. package/src/pages/customer/subscription/update.tsx +19 -19
  104. package/src/components/checkout/amount.tsx +0 -24
  105. package/src/components/checkout/error.tsx +0 -30
  106. package/src/components/checkout/footer.tsx +0 -12
  107. package/src/components/checkout/form/address.tsx +0 -119
  108. package/src/components/checkout/form/index.tsx +0 -400
  109. package/src/components/checkout/form/phone.tsx +0 -103
  110. package/src/components/checkout/form/stripe.tsx +0 -195
  111. package/src/components/checkout/form/user-buttons.tsx +0 -24
  112. package/src/components/checkout/header.tsx +0 -40
  113. package/src/components/checkout/pay.tsx +0 -385
  114. package/src/components/checkout/pricing-table.tsx +0 -205
  115. package/src/components/checkout/product-card.tsx +0 -52
  116. package/src/components/checkout/product-item.tsx +0 -111
  117. package/src/components/checkout/skeleton/overview.tsx +0 -21
  118. package/src/components/checkout/skeleton/payment.tsx +0 -35
  119. package/src/components/checkout/success.tsx +0 -183
  120. package/src/components/checkout/summary.tsx +0 -198
  121. package/src/components/input.tsx +0 -58
  122. package/src/components/livemode.tsx +0 -23
  123. package/src/components/pricing-table/product-skeleton.tsx +0 -39
  124. package/src/components/status.tsx +0 -19
  125. package/src/components/switch.tsx +0 -48
  126. package/src/contexts/settings.tsx +0 -54
@@ -1,400 +0,0 @@
1
- import 'react-international-phone/style.css';
2
-
3
- import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
4
- import { useTheme } from '@arcblock/ux/lib/Theme';
5
- import Toast from '@arcblock/ux/lib/Toast';
6
- import type { TCheckoutSessionExpanded, TCustomer, TPaymentIntent, TPaymentMethodExpanded } from '@did-pay/types';
7
- import { LoadingButton } from '@mui/lab';
8
- import { Avatar, Fade, InputAdornment, MenuItem, Select, Stack, Typography } from '@mui/material';
9
- import { useCreation, useSetState, useSize } from 'ahooks';
10
- import { PhoneNumberUtil } from 'google-libphonenumber';
11
- import pWaitFor from 'p-wait-for';
12
- import { useEffect } from 'react';
13
- import { Controller, useFormContext, useWatch } from 'react-hook-form';
14
- import { dispatch } from 'use-bus';
15
- import isEmail from 'validator/es/lib/isEmail';
16
-
17
- import { useSessionContext } from '../../../contexts/session';
18
- import api from '../../../libs/api';
19
- import { formatError, getStatementDescriptor } from '../../../libs/util';
20
- import FormInput from '../../input';
21
- import AddressForm from './address';
22
- import PhoneInput from './phone';
23
- import StripeCheckout from './stripe';
24
- import UserButtons from './user-buttons';
25
-
26
- const phoneUtil = PhoneNumberUtil.getInstance();
27
-
28
- const waitForCheckoutComplete = (sessionId: string) => {
29
- return pWaitFor(
30
- async () => {
31
- const { data } = await api.get(`/api/checkout-sessions/retrieve/${sessionId}`);
32
- if (
33
- data.paymentIntent &&
34
- data.paymentIntent.status === 'requires_action' &&
35
- data.paymentIntent.last_payment_error
36
- ) {
37
- throw new Error(data.paymentIntent.last_payment_error.message);
38
- }
39
-
40
- return (
41
- data.checkoutSession?.status === 'complete' &&
42
- ['paid', 'no_payment_required'].includes(data.checkoutSession?.payment_status)
43
- );
44
- },
45
- { interval: 2000, timeout: 3 * 60 * 1000 }
46
- );
47
- };
48
-
49
- type PageData = {
50
- onPaid: Function;
51
- onError: Function;
52
- checkoutSession: TCheckoutSessionExpanded;
53
- paymentMethods: TPaymentMethodExpanded[];
54
- paymentIntent?: TPaymentIntent;
55
- customer?: TCustomer;
56
- };
57
-
58
- PaymentForm.defaultProps = {
59
- paymentIntent: null,
60
- customer: null,
61
- };
62
-
63
- // FIXME: https://stripe.com/docs/elements/address-element
64
- // TODO: https://country-regions.github.io/react-country-region-selector/
65
- // https://www.npmjs.com/package/postal-codes-js
66
- // https://www.npmjs.com/package/val-zip
67
- // https://npm.runkit.com/zips
68
- export default function PaymentForm({
69
- checkoutSession,
70
- paymentMethods,
71
- paymentIntent,
72
- customer,
73
- onPaid,
74
- onError,
75
- }: PageData) {
76
- const theme = useTheme();
77
- const { t } = useLocaleContext();
78
- const { session, connectApi } = useSessionContext();
79
- const { control, getValues, setValue, handleSubmit } = useFormContext();
80
- const [state, setState] = useSetState<{
81
- submitting: boolean;
82
- paying: boolean;
83
- paid: boolean;
84
- paymentIntent?: TPaymentIntent;
85
- stripeContext?: {
86
- client_secret: string;
87
- intent_type: string;
88
- status: string;
89
- };
90
- customer?: TCustomer;
91
- stripePaying: boolean;
92
- }>({
93
- submitting: false,
94
- paying: false,
95
- paid: false,
96
- paymentIntent,
97
- stripeContext: undefined,
98
- customer,
99
- stripePaying: false,
100
- });
101
-
102
- useEffect(() => {
103
- if (session.user) {
104
- const values = getValues();
105
- if (!values.customer_name) {
106
- setValue('customer_name', session.user.fullName);
107
- }
108
- if (!values.customer_email) {
109
- setValue('customer_email', session.user.email);
110
- }
111
- if (!values.customer_phone) {
112
- setValue('customer_phone', session.user.phone);
113
- }
114
- }
115
- }, [session.user, getValues, setValue]);
116
-
117
- const paymentMethod = useWatch({ control, name: 'payment_method' });
118
- const paymentCurrency = useWatch({ control, name: 'payment_currency' });
119
- const paymentCurrencies = paymentMethods.find((x) => x.id === paymentMethod)?.payment_currencies || [];
120
-
121
- const domSize = useSize(document.body);
122
-
123
- const isColumnLayout = useCreation(() => {
124
- if (domSize) {
125
- if (domSize?.width <= theme.breakpoints.values.md) {
126
- return true;
127
- }
128
- }
129
- return false;
130
- }, [domSize, theme]);
131
-
132
- const payee = getStatementDescriptor(checkoutSession.line_items);
133
- const buttonText = session.user
134
- ? t(`checkout.${checkoutSession.mode}`)
135
- : t('checkout.connect', { action: t(`checkout.${checkoutSession.mode}`) });
136
-
137
- const method = paymentMethods.find((x) => x.id === paymentMethod) as TPaymentMethodExpanded;
138
-
139
- const handleMethodChange = (e: any) => {
140
- setValue('payment_method', e.target.value);
141
- const currencies = paymentMethods.find((x) => x.id === e.target.value)?.payment_currencies || [];
142
- if (currencies.some((x) => x.id === paymentCurrency) === false) {
143
- setValue('payment_currency', currencies[0]?.id);
144
- }
145
- };
146
-
147
- const handleConnected = async () => {
148
- try {
149
- await waitForCheckoutComplete(checkoutSession.id);
150
- setState({ paid: true, paying: false });
151
- onPaid();
152
- } catch (err) {
153
- Toast.error(formatError(err));
154
- } finally {
155
- setState({ paying: false });
156
- }
157
- };
158
-
159
- const onUserLoggedIn = async () => {
160
- const { data: profile } = await api.get('/api/customers/me');
161
- if (profile) {
162
- const values = getValues();
163
- if (!values.customer_name) {
164
- setValue('customer_name', profile.name);
165
- }
166
- if (!values.customer_email) {
167
- setValue('customer_email', profile.email);
168
- }
169
- if (!values.customer_phone) {
170
- setValue('customer_phone', profile.phone);
171
- }
172
- if (profile.address?.country) {
173
- setValue('billing_address.country', profile.address.country);
174
- }
175
- if (profile.address?.state) {
176
- setValue('billing_address.state', profile.address.state);
177
- }
178
- if (profile.address?.line1) {
179
- setValue('billing_address.line1', profile.address.line1);
180
- }
181
- if (profile.address?.line2) {
182
- setValue('billing_address.line2', profile.address.line2);
183
- }
184
- if (profile.address?.city) {
185
- setValue('billing_address.city', profile.address.city);
186
- }
187
- if (profile.address?.postal_code) {
188
- setValue('billing_address.postal_code', profile.address.postal_code);
189
- }
190
- }
191
- };
192
-
193
- const onSubmit = async (data: any) => {
194
- setState({ submitting: true });
195
- try {
196
- const result = await api.put(`/api/checkout-sessions/${checkoutSession.id}/submit`, data);
197
-
198
- setState({
199
- paymentIntent: result.data.paymentIntent,
200
- stripeContext: result.data.stripeContext,
201
- customer: result.data.customer,
202
- submitting: false,
203
- });
204
-
205
- if (['arcblock', 'ethereum'].includes(method.type)) {
206
- setState({ paying: true });
207
- if (result.data.balance?.sufficient || result.data.delegation?.sufficient) {
208
- await handleConnected();
209
- } else {
210
- connectApi.open({
211
- action: checkoutSession.mode,
212
- timeout: 5 * 60 * 1000,
213
- extraParams: { checkoutSessionId: checkoutSession.id },
214
- onSuccess: async () => {
215
- connectApi.close();
216
- await handleConnected();
217
- },
218
- onClose: () => {
219
- connectApi.close();
220
- setState({ submitting: false, paying: false });
221
- },
222
- onError: (err: any) => {
223
- setState({ submitting: false, paying: false });
224
- onError(err);
225
- },
226
- });
227
- }
228
- }
229
- if (['stripe'].includes(method.type)) {
230
- if (result.data.stripeContext?.status === 'succeeded') {
231
- setState({ paying: true });
232
- } else {
233
- setState({ stripePaying: true });
234
- }
235
- }
236
- } catch (err) {
237
- if (err.response?.data?.code) {
238
- dispatch(`error.${err.response?.data?.code}`);
239
- }
240
- Toast.error(formatError(err));
241
- } finally {
242
- setState({ submitting: false });
243
- }
244
- };
245
-
246
- const onAction = () => {
247
- if (session.user) {
248
- handleSubmit(onSubmit)();
249
- } else {
250
- session.login({
251
- onSuccess: onUserLoggedIn,
252
- extraParams: {},
253
- });
254
- }
255
- };
256
-
257
- const onStripeConfirm = async () => {
258
- setState({ stripePaying: false, paying: true });
259
- await handleConnected();
260
- };
261
-
262
- const onStripeCancel = () => {
263
- setState({ stripePaying: false });
264
- };
265
-
266
- return (
267
- <>
268
- <Fade in>
269
- <Stack className="cko-payment-contact">
270
- <Stack direction="row" sx={{ mb: 1 }} alignItems="center" justifyContent="space-between">
271
- <Typography sx={{ color: 'text.primary', fontWeight: 600 }}>{t('checkout.contact')}</Typography>
272
- {isColumnLayout ? null : <UserButtons />}
273
- </Stack>
274
- <Stack direction="column" className="cko-payment-form" spacing={0}>
275
- <FormInput
276
- name="customer_name"
277
- variant="outlined"
278
- errorPosition="right"
279
- rules={{
280
- required: t('checkout.required'),
281
- }}
282
- InputProps={{
283
- startAdornment: <InputAdornment position="start">{t('checkout.customer.name')}</InputAdornment>,
284
- }}
285
- />
286
- <FormInput
287
- name="customer_email"
288
- variant="outlined"
289
- errorPosition="right"
290
- rules={{
291
- required: t('checkout.required'),
292
- validate: (x) => (isEmail(x) ? true : t('checkout.invalid')),
293
- }}
294
- InputProps={{
295
- startAdornment: <InputAdornment position="start">{t('checkout.customer.email')}</InputAdornment>,
296
- }}
297
- />
298
- {checkoutSession.phone_number_collection?.enabled && (
299
- <PhoneInput
300
- name="customer_phone"
301
- variant="outlined"
302
- errorPosition="right"
303
- placeholder="Phone number"
304
- rules={{
305
- required: t('checkout.required'),
306
- validate: (x: string) => {
307
- try {
308
- const parsed = phoneUtil.parseAndKeepRawInput(x);
309
- return phoneUtil.isValidNumber(parsed) ? true : t('checkout.invalid');
310
- } catch {
311
- return t('checkout.invalid');
312
- }
313
- },
314
- }}
315
- />
316
- )}
317
- </Stack>
318
- </Stack>
319
- </Fade>
320
- <AddressForm mode={checkoutSession.billing_address_collection as string} stripe={method?.type === 'stripe'} />
321
- <Fade in>
322
- <Stack direction="column" className="cko-payment-methods">
323
- <Typography sx={{ mb: 2, color: 'text.primary', fontWeight: 600 }}>{t('checkout.method')}</Typography>
324
- <Stack direction="row" spacing={1}>
325
- <Controller
326
- name="payment_method"
327
- control={control}
328
- render={({ field }) => (
329
- <Select {...field} onChange={handleMethodChange} sx={{ flex: 1 }} size="small">
330
- {paymentMethods.map((x) => {
331
- const selected = x.id === paymentMethod;
332
- return (
333
- <MenuItem key={x.id} value={x.id}>
334
- <Stack direction="row" spacing={1}>
335
- <Avatar src={x.logo} alt={x.name} sx={{ width: 20, height: 20 }} />
336
- <Typography color={selected ? 'text.primary' : 'text.secondary'}>{x.name}</Typography>
337
- </Stack>
338
- </MenuItem>
339
- );
340
- })}
341
- </Select>
342
- )}
343
- />
344
- <Controller
345
- name="payment_currency"
346
- control={control}
347
- render={({ field }) => (
348
- <Select {...field} sx={{ flex: 1 }} size="small">
349
- {paymentCurrencies.map((x) => {
350
- const selected = x.id === paymentCurrency;
351
- return (
352
- <MenuItem key={x.id} value={x.id}>
353
- <Stack direction="row" spacing={1}>
354
- <Avatar src={x.logo} alt={x.name} sx={{ width: 20, height: 20 }} />
355
- <Typography color={selected ? 'text.primary' : 'text.secondary'}>{x.symbol}</Typography>
356
- </Stack>
357
- </MenuItem>
358
- );
359
- })}
360
- </Select>
361
- )}
362
- />
363
- </Stack>
364
- {state.stripePaying && state.stripeContext && (
365
- <StripeCheckout
366
- clientSecret={state.stripeContext.client_secret}
367
- intentType={state.stripeContext.intent_type}
368
- publicKey={method.settings.stripe?.publishable_key as string}
369
- customer={state.customer as TCustomer}
370
- mode={checkoutSession.mode}
371
- onConfirm={onStripeConfirm}
372
- onCancel={onStripeCancel}
373
- />
374
- )}
375
- </Stack>
376
- </Fade>
377
- <Fade in>
378
- <Stack className="cko-payment-submit">
379
- <LoadingButton
380
- variant="contained"
381
- color="primary"
382
- size="large"
383
- onClick={onAction}
384
- fullWidth
385
- loadingPosition="end"
386
- disabled={state.submitting || state.paying || state.stripePaying}
387
- loading={state.submitting || state.paying}>
388
- {state.submitting || state.paying ? t('checkout.processing') : buttonText}
389
- </LoadingButton>
390
- {['subscription', 'setup'].includes(checkoutSession.mode) && (
391
- <Typography
392
- sx={{ mt: 1, color: 'text.secondary', fontSize: '0.9rem', lineHeight: '1.1rem', textAlign: 'center' }}>
393
- {t('checkout.confirm', { payee })}
394
- </Typography>
395
- )}
396
- </Stack>
397
- </Fade>
398
- </>
399
- );
400
- }
@@ -1,103 +0,0 @@
1
- /* eslint-disable react/prop-types */
2
- import { InputAdornment, MenuItem, Select, Typography } from '@mui/material';
3
- import omit from 'lodash/omit';
4
- import { useEffect } from 'react';
5
- import { useFormContext, useWatch } from 'react-hook-form';
6
- import { CountryIso2, FlagEmoji, defaultCountries, parseCountry, usePhoneInput } from 'react-international-phone';
7
-
8
- import { isValidCountry } from '../../../libs/util';
9
- import FormInput from '../../input';
10
-
11
- export default function PhoneInput({ ...props }) {
12
- const countryFieldName = props.countryFieldName || 'billing_address.country';
13
-
14
- const { control, getValues, setValue } = useFormContext();
15
- const values = getValues();
16
-
17
- const { phone, handlePhoneValueChange, inputRef, country, setCountry } = usePhoneInput({
18
- defaultCountry: isValidCountry(values[countryFieldName]) ? values[countryFieldName] : 'us',
19
- value: values[props.name] || '',
20
- countries: defaultCountries,
21
- onChange: (data) => {
22
- setValue(props.name, data.phone);
23
- },
24
- });
25
-
26
- const userCountry = useWatch({ control, name: countryFieldName });
27
-
28
- useEffect(() => {
29
- if (userCountry !== country) {
30
- setCountry(userCountry);
31
- }
32
- // eslint-disable-next-line react-hooks/exhaustive-deps
33
- }, [userCountry]);
34
-
35
- const onCountryChange = (e: any) => {
36
- setCountry(e.target.value as CountryIso2);
37
- setValue(countryFieldName, e.target.value);
38
- };
39
-
40
- return (
41
- // @ts-ignore
42
- <FormInput
43
- value={phone}
44
- onChange={handlePhoneValueChange}
45
- type="tel"
46
- inputRef={inputRef}
47
- InputProps={{
48
- startAdornment: (
49
- <InputAdornment position="start" style={{ marginRight: '2px', marginLeft: '-8px' }}>
50
- <Select
51
- MenuProps={{
52
- style: {
53
- height: '300px',
54
- width: '360px',
55
- top: '10px',
56
- left: '-34px',
57
- },
58
- transformOrigin: {
59
- vertical: 'top',
60
- horizontal: 'left',
61
- },
62
- }}
63
- sx={{
64
- width: 'max-content',
65
- // Remove default outline (display only on focus)
66
- fieldset: {
67
- display: 'none',
68
- },
69
- '&.Mui-focused:has(div[aria-expanded="false"])': {
70
- fieldset: {
71
- display: 'block',
72
- },
73
- },
74
- // Update default spacing
75
- '.MuiSelect-select': {
76
- padding: '8px',
77
- paddingRight: '24px !important',
78
- },
79
- svg: {
80
- right: 0,
81
- },
82
- }}
83
- value={country}
84
- onChange={onCountryChange}
85
- renderValue={(code) => <FlagEmoji iso2={code} style={{ display: 'flex' }} />}>
86
- {defaultCountries.map((c) => {
87
- const parsed = parseCountry(c);
88
- return (
89
- <MenuItem key={parsed.iso2} value={parsed.iso2}>
90
- <FlagEmoji iso2={parsed.iso2} style={{ marginRight: '8px' }} />
91
- <Typography marginRight="8px">{parsed.name}</Typography>
92
- <Typography color="gray">+{parsed.dialCode}</Typography>
93
- </MenuItem>
94
- );
95
- })}
96
- </Select>
97
- </InputAdornment>
98
- ),
99
- }}
100
- {...omit(props, ['countryFieldName'])}
101
- />
102
- );
103
- }
@@ -1,195 +0,0 @@
1
- import Center from '@arcblock/ux/lib/Center';
2
- import Dialog from '@arcblock/ux/lib/Dialog';
3
- import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
4
- import type { TCustomer } from '@did-pay/types';
5
- import { LoadingButton } from '@mui/lab';
6
- import { CircularProgress, Typography } from '@mui/material';
7
- import { styled } from '@mui/system';
8
- import { Elements, PaymentElement, useElements, useStripe } from '@stripe/react-stripe-js';
9
- import { loadStripe } from '@stripe/stripe-js';
10
- import { useSetState } from 'ahooks';
11
- import { useEffect } from 'react';
12
-
13
- type StripeCheckoutFormProps = {
14
- clientSecret: string;
15
- intentType: string;
16
- customer: TCustomer;
17
- mode: string;
18
- onConfirm: Function;
19
- };
20
-
21
- // @doc https://stripe.com/docs/js/elements_object/create_payment_element
22
- function StripeCheckoutForm({ clientSecret, intentType, customer, mode, onConfirm }: StripeCheckoutFormProps) {
23
- const stripe = useStripe();
24
- const elements = useElements();
25
- const { t } = useLocaleContext();
26
-
27
- const [state, setState] = useSetState({
28
- message: '',
29
- confirming: false,
30
- loaded: false,
31
- });
32
-
33
- useEffect(() => {
34
- if (!stripe) {
35
- return;
36
- }
37
-
38
- if (!clientSecret) {
39
- return;
40
- }
41
-
42
- const method = intentType === 'payment_intent' ? 'retrievePaymentIntent' : 'retrieveSetupIntent';
43
- stripe[method](clientSecret).then(({ paymentIntent, setupIntent }: any) => {
44
- const intent = paymentIntent || setupIntent;
45
- switch (intent?.status) {
46
- case 'succeeded':
47
- setState({ message: t('paymentCredit.preparePayMessage.succeeded') });
48
- break;
49
- case 'processing':
50
- setState({ message: t('paymentCredit.preparePayMessage.processing') });
51
- break;
52
- case 'requires_payment_method': // 忽略该状态
53
- default:
54
- break;
55
- }
56
- });
57
- // eslint-disable-next-line react-hooks/exhaustive-deps
58
- }, [stripe, clientSecret]);
59
-
60
- const handleSubmit = async (e: any) => {
61
- e.preventDefault();
62
-
63
- if (!stripe || !elements) {
64
- return;
65
- }
66
-
67
- try {
68
- setState({ confirming: true });
69
- const method = intentType === 'payment_intent' ? 'confirmPayment' : 'confirmSetup';
70
- const { error } = await stripe[method]({
71
- elements,
72
- redirect: 'if_required',
73
- confirmParams: {
74
- payment_method_data: {
75
- billing_details: {
76
- name: customer.name,
77
- phone: customer.phone,
78
- email: customer.email,
79
- address: customer.address,
80
- },
81
- },
82
- },
83
- });
84
-
85
- setState({ confirming: false });
86
- if (error) {
87
- if (error.type === 'validation_error') {
88
- return;
89
- }
90
-
91
- setState({ message: error.message as string });
92
- return;
93
- }
94
-
95
- onConfirm();
96
- } catch (err) {
97
- console.error(err);
98
- setState({ confirming: false, message: err.message as string });
99
- }
100
- };
101
-
102
- return (
103
- <Content onSubmit={handleSubmit}>
104
- <PaymentElement
105
- options={{ layout: 'auto', fields: { billingDetails: 'never' }, readOnly: state.confirming }}
106
- onReady={() => setState({ loaded: true })}
107
- />
108
- {(!stripe || !elements || !state.loaded) && (
109
- <Center relative="parent">
110
- <CircularProgress />
111
- </Center>
112
- )}
113
- {stripe && elements && state.loaded && (
114
- <LoadingButton
115
- fullWidth
116
- sx={{ mt: 2, mb: 1, borderRadius: 0, fontSize: '1.1rem' }}
117
- type="submit"
118
- disabled={state.confirming || !state.loaded}
119
- loading={state.confirming}
120
- loadingPosition="end"
121
- variant="contained"
122
- color="primary"
123
- size="large">
124
- {t('checkout.continue', { action: mode })}
125
- </LoadingButton>
126
- )}
127
- {state.message && <Typography sx={{ mt: 1, color: 'error.main' }}>{state.message}</Typography>}
128
- </Content>
129
- );
130
- }
131
-
132
- const Content = styled('form')`
133
- display: flex;
134
- flex-direction: column;
135
- justify-content: center;
136
- align-items: center;
137
- width: 100%;
138
- height: 100%;
139
- min-height: 320px;
140
- `;
141
-
142
- type StripeCheckoutProps = {
143
- clientSecret: string;
144
- intentType: string;
145
- publicKey: string;
146
- mode: string;
147
- customer: TCustomer;
148
- onConfirm: Function;
149
- onCancel: Function;
150
- };
151
-
152
- export default function StripeCheckout({
153
- clientSecret,
154
- intentType,
155
- publicKey,
156
- mode,
157
- customer,
158
- onConfirm,
159
- onCancel,
160
- }: StripeCheckoutProps) {
161
- const stripePromise = loadStripe(publicKey);
162
- const { t } = useLocaleContext();
163
- const [state, setState] = useSetState({
164
- open: true,
165
- closable: true,
166
- });
167
-
168
- const handleClose = (_: any, reason: string) => {
169
- if (reason === 'backdropClick') {
170
- return;
171
- }
172
-
173
- setState({ open: false });
174
- onCancel();
175
- };
176
-
177
- return (
178
- <Dialog
179
- title={t('checkout.cardPay', { action: t(`checkout.${mode}`) })}
180
- showCloseButton={state.closable}
181
- open={state.open}
182
- onClose={handleClose}
183
- disableEscapeKeyDown>
184
- <Elements options={{ clientSecret }} stripe={stripePromise}>
185
- <StripeCheckoutForm
186
- clientSecret={clientSecret}
187
- intentType={intentType}
188
- mode={mode}
189
- customer={customer}
190
- onConfirm={onConfirm}
191
- />
192
- </Elements>
193
- </Dialog>
194
- );
195
- }