@blocklet/payment-react 1.18.0 → 1.18.2

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 (54) hide show
  1. package/es/checkout/donate.js +55 -10
  2. package/es/checkout/form.d.ts +2 -1
  3. package/es/checkout/form.js +32 -45
  4. package/es/components/payment-beneficiaries.d.ts +24 -0
  5. package/es/components/payment-beneficiaries.js +70 -0
  6. package/es/index.d.ts +2 -1
  7. package/es/index.js +3 -1
  8. package/es/locales/en.js +13 -1
  9. package/es/locales/zh.js +13 -1
  10. package/es/payment/donation-form.d.ts +24 -0
  11. package/es/payment/donation-form.js +604 -0
  12. package/es/payment/error.d.ts +1 -1
  13. package/es/payment/error.js +11 -1
  14. package/es/payment/form/index.d.ts +9 -3
  15. package/es/payment/form/index.js +60 -12
  16. package/es/payment/product-donation.js +101 -56
  17. package/es/payment/skeleton/donation.d.ts +1 -0
  18. package/es/payment/skeleton/donation.js +30 -0
  19. package/es/theme/index.js +13 -0
  20. package/es/types/index.d.ts +2 -0
  21. package/lib/checkout/donate.js +85 -10
  22. package/lib/checkout/form.d.ts +2 -1
  23. package/lib/checkout/form.js +39 -49
  24. package/lib/components/payment-beneficiaries.d.ts +24 -0
  25. package/lib/components/payment-beneficiaries.js +113 -0
  26. package/lib/index.d.ts +2 -1
  27. package/lib/index.js +8 -0
  28. package/lib/locales/en.js +13 -1
  29. package/lib/locales/zh.js +13 -1
  30. package/lib/payment/donation-form.d.ts +24 -0
  31. package/lib/payment/donation-form.js +645 -0
  32. package/lib/payment/error.d.ts +1 -1
  33. package/lib/payment/error.js +2 -2
  34. package/lib/payment/form/index.d.ts +9 -3
  35. package/lib/payment/form/index.js +60 -8
  36. package/lib/payment/product-donation.js +143 -72
  37. package/lib/payment/skeleton/donation.d.ts +1 -0
  38. package/lib/payment/skeleton/donation.js +66 -0
  39. package/lib/theme/index.js +13 -0
  40. package/lib/types/index.d.ts +2 -0
  41. package/package.json +3 -3
  42. package/src/checkout/donate.tsx +64 -11
  43. package/src/checkout/form.tsx +17 -31
  44. package/src/components/payment-beneficiaries.tsx +97 -0
  45. package/src/index.ts +2 -0
  46. package/src/locales/en.tsx +12 -0
  47. package/src/locales/zh.tsx +12 -0
  48. package/src/payment/donation-form.tsx +647 -0
  49. package/src/payment/error.tsx +13 -4
  50. package/src/payment/form/index.tsx +66 -11
  51. package/src/payment/product-donation.tsx +94 -39
  52. package/src/payment/skeleton/donation.tsx +35 -0
  53. package/src/theme/index.tsx +13 -0
  54. package/src/types/index.ts +2 -0
@@ -1,3 +1,4 @@
1
+ /* eslint-disable @typescript-eslint/indent */
1
2
  import 'react-international-phone/style.css';
2
3
 
3
4
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
@@ -10,8 +11,7 @@ import type {
10
11
  TPaymentIntent,
11
12
  TPaymentMethodExpanded,
12
13
  } from '@blocklet/payment-types';
13
- import { LoadingButton } from '@mui/lab';
14
- import { Box, Divider, Fade, FormLabel, Stack, Typography } from '@mui/material';
14
+ import { Box, Button, CircularProgress, Divider, Fade, FormLabel, Stack, Typography } from '@mui/material';
15
15
  import { useMemoizedFn, useSetState } from 'ahooks';
16
16
  import pWaitFor from 'p-wait-for';
17
17
  import { useEffect, useMemo, useRef, useState } from 'react';
@@ -42,7 +42,7 @@ import StripeCheckout from './stripe';
42
42
  import { useMobile } from '../../hooks/mobile';
43
43
  import { validatePhoneNumber } from '../../libs/phone-validator';
44
44
 
45
- const waitForCheckoutComplete = async (sessionId: string) => {
45
+ export const waitForCheckoutComplete = async (sessionId: string) => {
46
46
  let result: CheckoutContext;
47
47
 
48
48
  await pWaitFor(
@@ -71,14 +71,19 @@ const waitForCheckoutComplete = async (sessionId: string) => {
71
71
  return result;
72
72
  };
73
73
 
74
- const hasDidWallet = (user: any) => {
74
+ export const hasDidWallet = (user: any) => {
75
75
  const connected = user?.connectedAccounts || user?.extraConfigs?.connectedAccounts || [];
76
76
  return connected.some((x: any) => x.provider === 'wallet');
77
77
  };
78
78
 
79
- type PageData = CheckoutContext & CheckoutCallbacks;
79
+ type PageData = CheckoutContext &
80
+ CheckoutCallbacks & {
81
+ onlyShowBtn?: boolean;
82
+ };
80
83
 
81
- PaymentForm.defaultProps = {};
84
+ PaymentForm.defaultProps = {
85
+ onlyShowBtn: false,
86
+ };
82
87
 
83
88
  // FIXME: https://stripe.com/docs/elements/address-element
84
89
  // TODO: https://country-regions.github.io/react-country-region-selector/
@@ -96,6 +101,7 @@ export default function PaymentForm({
96
101
  // mode,
97
102
  action,
98
103
  currencyId,
104
+ onlyShowBtn,
99
105
  }: PageData) {
100
106
  // const theme = useTheme();
101
107
  const { t } = useLocaleContext();
@@ -398,6 +404,47 @@ export default function PaymentForm({
398
404
  setState({ stripePaying: false });
399
405
  };
400
406
 
407
+ if (onlyShowBtn) {
408
+ return (
409
+ <>
410
+ <Box className="cko-payment-submit-btn">
411
+ <Button
412
+ variant="contained"
413
+ color="primary"
414
+ size="large"
415
+ className="cko-submit-button"
416
+ onClick={() => {
417
+ if (state.submitting || state.paying) {
418
+ return;
419
+ }
420
+ onAction();
421
+ }}
422
+ fullWidth
423
+ disabled={state.stripePaying || !quantityInventoryStatus || !payable}>
424
+ {(state.submitting || state.paying) && (
425
+ <CircularProgress size={16} sx={{ mr: 0.5, color: 'var(--foregrounds-fg-on-color, #fff)' }} />
426
+ )}
427
+ {state.submitting || state.paying ? t('payment.checkout.processing') : buttonText}
428
+ </Button>
429
+ </Box>
430
+ {state.customerLimited && (
431
+ <ConfirmDialog
432
+ onConfirm={() =>
433
+ window.open(
434
+ joinURL(getPrefix(), `/customer/invoice/past-due?referer=${encodeURIComponent(window.location.href)}`),
435
+ '_self'
436
+ )
437
+ }
438
+ onCancel={() => setState({ customerLimited: false })}
439
+ confirm={t('payment.customer.pastDue.alert.confirm')}
440
+ title={t('payment.customer.pastDue.alert.title')}
441
+ message={t('payment.customer.pastDue.alert.description')}
442
+ color="primary"
443
+ />
444
+ )}
445
+ </>
446
+ );
447
+ }
401
448
  return (
402
449
  <>
403
450
  <Fade in>
@@ -498,20 +545,28 @@ export default function PaymentForm({
498
545
  </Stack>
499
546
  </Fade>
500
547
  <Divider sx={{ mt: 2.5, mb: 2.5 }} />
548
+
501
549
  <Fade in>
502
550
  <Stack className="cko-payment-submit">
503
551
  <Box className="cko-payment-submit-btn">
504
- <LoadingButton
552
+ <Button
505
553
  variant="contained"
506
554
  color="primary"
507
555
  size="large"
508
556
  className="cko-submit-button"
509
- onClick={onAction}
557
+ onClick={() => {
558
+ if (state.submitting || state.paying) {
559
+ return;
560
+ }
561
+ onAction();
562
+ }}
510
563
  fullWidth
511
- disabled={state.submitting || state.paying || state.stripePaying || !quantityInventoryStatus || !payable}
512
- loading={state.submitting || state.paying}>
564
+ disabled={state.stripePaying || !quantityInventoryStatus || !payable}>
565
+ {(state.submitting || state.paying) && (
566
+ <CircularProgress size={16} sx={{ mr: 0.5, color: 'var(--foregrounds-fg-on-color, #fff)' }} />
567
+ )}
513
568
  {state.submitting || state.paying ? t('payment.checkout.processing') : buttonText}
514
- </LoadingButton>
569
+ </Button>
515
570
  </Box>
516
571
 
517
572
  {['subscription', 'setup'].includes(checkoutSession.mode) && (
@@ -1,10 +1,9 @@
1
1
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
2
2
  import type { DonationSettings, TLineItemExpanded, TPaymentCurrency } from '@blocklet/payment-types';
3
- import { Box, Card, CardActionArea, FormControlLabel, Stack, TextField, Typography } from '@mui/material';
3
+ import { Avatar, Box, Card, CardActionArea, Grid, Stack, TextField, Typography } from '@mui/material';
4
4
  import { useSetState } from 'ahooks';
5
- import { useEffect } from 'react';
5
+ import { useEffect, useRef } from 'react';
6
6
 
7
- import Switch from '../components/switch-button';
8
7
  import { formatAmountPrecisionLimit } from '../libs/util';
9
8
  import { usePaymentContext } from '../contexts/payment';
10
9
  import { usePreventWheel } from '../hooks/scroll';
@@ -23,13 +22,17 @@ export default function ProductDonation({
23
22
  const { t, locale } = useLocaleContext();
24
23
  const { setPayable } = usePaymentContext();
25
24
  usePreventWheel();
26
- const preset = settings.amount.preset || settings.amount.presets?.[0] || '0';
25
+ const presets = settings?.amount?.presets || [];
26
+ const preset = settings?.amount?.preset || presets?.[0] || '0';
27
+ const supportPreset = presets.length > 0;
28
+ const supportCustom = !!settings?.amount?.custom;
27
29
  const [state, setState] = useSetState({
28
30
  selected: preset,
29
31
  input: '',
30
- custom: settings.amount.presets?.length === 0,
32
+ custom: !supportPreset,
31
33
  error: '',
32
34
  });
35
+ const customInputRef = useRef<HTMLInputElement>(null);
33
36
 
34
37
  // Set default amount
35
38
  useEffect(() => {
@@ -42,7 +45,16 @@ export default function ProductDonation({
42
45
  }
43
46
  }, [settings.amount.preset, settings.amount.presets]); // eslint-disable-line
44
47
 
48
+ useEffect(() => {
49
+ if (state.custom) {
50
+ setTimeout(() => {
51
+ customInputRef.current?.focus();
52
+ }, 0);
53
+ }
54
+ }, [state.custom]);
55
+
45
56
  const handleSelect = (amount: string) => {
57
+ setPayable(true);
46
58
  setState({ selected: amount, custom: false, error: '' });
47
59
  onChange({ priceId: item.price_id, amount });
48
60
  };
@@ -69,71 +81,114 @@ export default function ProductDonation({
69
81
  onChange({ priceId: item.price_id, amount: value });
70
82
  };
71
83
 
72
- const handleToggle = (event: any) => {
73
- if (event.target.checked) {
74
- setState({ custom: true, input: state.selected, error: '' });
75
- } else {
76
- setPayable(true);
77
- handleSelect(preset);
84
+ const handleCustomSelect = () => {
85
+ setState({ custom: true, error: '' });
86
+ if (!state.input) {
87
+ setPayable(false);
78
88
  }
79
89
  };
80
90
 
81
91
  return (
82
92
  <Box display="flex" flexDirection="column" alignItems="flex-start" gap={1.5}>
83
- {settings.amount.custom && preset !== '0' && (
84
- <FormControlLabel
85
- control={<Switch checked={state.custom} sx={{ marginRight: 0.4 }} onChange={handleToggle} />}
86
- label={state.custom ? t('payment.checkout.donation.select') : t('payment.checkout.donation.custom')}
87
- sx={{ marginRight: 2, marginLeft: 0.5, color: 'text.lighter' }}
88
- />
89
- )}
90
- {!state.custom && (
91
- <Box display="flex" flexWrap="wrap" alignItems="center" gap={1.5}>
93
+ {supportPreset && (
94
+ <Grid container spacing={2}>
92
95
  {settings.amount.presets &&
93
96
  settings.amount.presets.length > 0 &&
94
97
  settings.amount.presets.map((amount) => (
98
+ <Grid item xs={6} sm={3} key={amount}>
99
+ <Card
100
+ key={amount}
101
+ variant="outlined"
102
+ sx={{
103
+ minWidth: 115,
104
+ textAlign: 'center',
105
+ transition: 'all 0.3s',
106
+ cursor: 'pointer',
107
+ '&:hover': {
108
+ transform: 'translateY(-4px)',
109
+ boxShadow: 3,
110
+ },
111
+ height: '42px',
112
+ ...(state.selected === amount && !state.custom
113
+ ? { borderColor: 'primary.main', borderWidth: 1 }
114
+ : {}),
115
+ }}>
116
+ <CardActionArea onClick={() => handleSelect(amount)}>
117
+ <Stack
118
+ direction="row"
119
+ sx={{ py: 1.5, px: 1.5 }}
120
+ spacing={0.5}
121
+ alignItems="center"
122
+ justifyContent="center">
123
+ <Avatar src={currency?.logo} sx={{ width: 16, height: 16, mr: 0.5 }} alt={currency?.symbol} />
124
+ <Typography
125
+ component="strong"
126
+ lineHeight={1}
127
+ variant="h3"
128
+ sx={{ fontVariantNumeric: 'tabular-nums', fontWeight: 400 }}>
129
+ {amount}
130
+ </Typography>
131
+ <Typography lineHeight={1} fontSize={14} color="text.secondary">
132
+ {currency?.symbol}
133
+ </Typography>
134
+ </Stack>
135
+ </CardActionArea>
136
+ </Card>
137
+ </Grid>
138
+ ))}
139
+ {supportCustom && (
140
+ <Grid item xs={6} sm={3} key="custom">
95
141
  <Card
96
- key={amount}
142
+ key="custom"
97
143
  variant="outlined"
98
144
  sx={{
99
- minWidth: 115,
100
145
  textAlign: 'center',
101
- ...(state.selected === amount && !state.custom ? { borderColor: 'primary.main' } : {}),
146
+ transition: 'all 0.3s',
147
+ cursor: 'pointer',
148
+ '&:hover': {
149
+ transform: 'translateY(-4px)',
150
+ boxShadow: 3,
151
+ },
152
+ height: '42px',
153
+ ...(state.custom ? { borderColor: 'primary.main', borderWidth: 1 } : {}),
102
154
  }}>
103
- <CardActionArea onClick={() => handleSelect(amount)}>
155
+ <CardActionArea onClick={() => handleCustomSelect()}>
104
156
  <Stack
105
157
  direction="row"
106
- sx={{ py: 1, px: 0.5 }}
158
+ sx={{ py: 1.5, px: 1.5 }}
107
159
  spacing={0.5}
108
- alignItems="flex-end"
160
+ alignItems="center"
109
161
  justifyContent="center">
110
- <Typography
111
- component="strong"
112
- lineHeight={1}
113
- variant="h3"
114
- sx={{ fontVariantNumeric: 'tabular-nums', fontWeight: 400 }}>
115
- {amount}
162
+ <Typography variant="h3" lineHeight={1} sx={{ fontWeight: 400 }}>
163
+ {t('common.custom')}
116
164
  </Typography>
117
- <Typography component="small" lineHeight={1} fontSize={12}>
118
- ABT
119
- </Typography>{' '}
120
165
  </Stack>
121
166
  </CardActionArea>
122
167
  </Card>
123
- ))}
124
- </Box>
168
+ </Grid>
169
+ )}
170
+ </Grid>
125
171
  )}
126
172
  {state.custom && (
127
173
  <TextField
128
- label={preset !== '0' ? null : t('payment.checkout.donation.custom')}
129
174
  type="number"
130
175
  value={state.input}
131
176
  onChange={handleInput}
132
- inputProps={{ min: settings.amount.minimum, max: settings.amount.maximum }}
133
177
  margin="none"
134
178
  fullWidth
135
179
  error={!!state.error}
136
180
  helperText={state.error}
181
+ inputRef={customInputRef}
182
+ // eslint-disable-next-line react/jsx-no-duplicate-props
183
+ InputProps={{
184
+ endAdornment: (
185
+ <Stack direction="row" spacing={0.5} alignItems="center" sx={{ ml: 1 }}>
186
+ <Avatar src={currency?.logo} sx={{ width: 16, height: 16 }} alt={currency?.symbol} />
187
+ <Typography>{currency?.symbol}</Typography>
188
+ </Stack>
189
+ ),
190
+ autoComplete: 'off',
191
+ }}
137
192
  sx={{
138
193
  mt: preset !== '0' ? 0 : 1,
139
194
  }}
@@ -0,0 +1,35 @@
1
+ import { Box, Divider, Fade, Skeleton, Stack } from '@mui/material';
2
+
3
+ export default function DonationSkeleton() {
4
+ return (
5
+ <Fade in>
6
+ <Stack direction="column">
7
+ <Skeleton variant="text" sx={{ fontSize: '2rem', width: '40%' }} />
8
+ <Skeleton sx={{ mt: 2 }} variant="rounded" height={80} />
9
+ <Divider
10
+ sx={{
11
+ mt: {
12
+ xs: '16px',
13
+ md: '16px',
14
+ },
15
+ mb: {
16
+ xs: '16px',
17
+ md: '16px',
18
+ },
19
+ }}
20
+ />
21
+ <Stack direction="row" justifyContent="space-between" spacing={2}>
22
+ <Skeleton variant="text" sx={{ fontSize: '1.5rem', width: '40%' }} />
23
+ <Box display="flex" alignItems="center" gap={2}>
24
+ <Box>
25
+ <Skeleton height={60} width={80} />
26
+ </Box>
27
+ <Box>
28
+ <Skeleton height={60} width={120} />
29
+ </Box>
30
+ </Box>
31
+ </Stack>
32
+ </Stack>
33
+ </Fade>
34
+ );
35
+ }
@@ -48,6 +48,9 @@ export function PaymentThemeProvider({
48
48
  minHeight: '1.65em',
49
49
  lineHeight: '1.65em',
50
50
  },
51
+ '&.Mui-focused .MuiOutlinedInput-notchedOutline': {
52
+ borderWidth: '1px',
53
+ },
51
54
  },
52
55
  },
53
56
  },
@@ -121,6 +124,9 @@ export function PaymentThemeProvider({
121
124
  },
122
125
  MuiPopover: {
123
126
  styleOverrides: {
127
+ root: {
128
+ zIndex: 1200,
129
+ },
124
130
  paper: ({ theme }) => ({
125
131
  border: `1px solid ${theme.palette.divider}`,
126
132
  boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)',
@@ -241,6 +247,13 @@ export function PaymentThemeProvider({
241
247
  },
242
248
  },
243
249
  },
250
+ MuiDialog: {
251
+ styleOverrides: {
252
+ root: {
253
+ zIndex: 1200,
254
+ },
255
+ },
256
+ },
244
257
  },
245
258
  };
246
259
 
@@ -46,6 +46,8 @@ export type CheckoutProps = Partial<CheckoutCallbacks> & {
46
46
  action?: string;
47
47
  mode?: LiteralUnion<'standalone' | 'inline' | 'popup' | 'inline-minimal' | 'popup-minimal', string>;
48
48
  theme?: 'default' | 'inherit' | PaymentThemeOptions;
49
+ formType?: 'donation' | 'payment';
50
+ formRender?: Record<string, any>;
49
51
  };
50
52
 
51
53
  export type CheckoutCallbacks = {