@blocklet/payment-react 1.13.210 → 1.13.212

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 (79) hide show
  1. package/es/checkout/donate.d.ts +20 -0
  2. package/es/checkout/donate.js +199 -0
  3. package/es/checkout/form.d.ts +2 -1
  4. package/es/checkout/form.js +13 -2
  5. package/es/components/blockchain/tx.d.ts +2 -0
  6. package/es/components/blockchain/tx.js +16 -5
  7. package/es/components/safe-guard.d.ts +3 -0
  8. package/es/components/safe-guard.js +4 -0
  9. package/es/index.d.ts +3 -1
  10. package/es/index.js +5 -1
  11. package/es/locales/en.js +8 -0
  12. package/es/locales/zh.js +8 -0
  13. package/es/payment/error.d.ts +3 -1
  14. package/es/payment/error.js +4 -3
  15. package/es/payment/form/currency.js +10 -12
  16. package/es/payment/form/index.d.ts +1 -1
  17. package/es/payment/form/index.js +15 -3
  18. package/es/payment/index.d.ts +3 -3
  19. package/es/payment/index.js +38 -13
  20. package/es/payment/product-donation.d.ts +7 -0
  21. package/es/payment/product-donation.js +99 -0
  22. package/es/payment/skeleton/overview.js +2 -2
  23. package/es/payment/skeleton/payment.js +2 -5
  24. package/es/payment/success.d.ts +2 -1
  25. package/es/payment/success.js +21 -12
  26. package/es/payment/summary.d.ts +8 -2
  27. package/es/payment/summary.js +46 -29
  28. package/es/types/index.d.ts +2 -0
  29. package/es/util.d.ts +2 -0
  30. package/es/util.js +47 -3
  31. package/lib/checkout/donate.d.ts +20 -0
  32. package/lib/checkout/donate.js +284 -0
  33. package/lib/checkout/form.d.ts +2 -1
  34. package/lib/checkout/form.js +5 -2
  35. package/lib/components/blockchain/tx.d.ts +2 -0
  36. package/lib/components/blockchain/tx.js +3 -1
  37. package/lib/components/safe-guard.d.ts +3 -0
  38. package/lib/components/safe-guard.js +12 -0
  39. package/lib/index.d.ts +3 -1
  40. package/lib/index.js +16 -0
  41. package/lib/locales/en.js +8 -0
  42. package/lib/locales/zh.js +8 -0
  43. package/lib/payment/error.d.ts +3 -1
  44. package/lib/payment/error.js +5 -3
  45. package/lib/payment/form/currency.js +10 -12
  46. package/lib/payment/form/index.d.ts +1 -1
  47. package/lib/payment/form/index.js +16 -4
  48. package/lib/payment/index.d.ts +3 -3
  49. package/lib/payment/index.js +56 -24
  50. package/lib/payment/product-donation.d.ts +7 -0
  51. package/lib/payment/product-donation.js +169 -0
  52. package/lib/payment/skeleton/overview.js +2 -2
  53. package/lib/payment/skeleton/payment.js +4 -8
  54. package/lib/payment/success.d.ts +2 -1
  55. package/lib/payment/success.js +3 -2
  56. package/lib/payment/summary.d.ts +8 -2
  57. package/lib/payment/summary.js +30 -7
  58. package/lib/types/index.d.ts +2 -0
  59. package/lib/util.d.ts +2 -0
  60. package/lib/util.js +44 -4
  61. package/package.json +6 -6
  62. package/src/checkout/donate.tsx +256 -0
  63. package/src/checkout/form.tsx +13 -4
  64. package/src/components/blockchain/tx.tsx +8 -1
  65. package/src/components/safe-guard.tsx +5 -0
  66. package/src/index.ts +4 -0
  67. package/src/locales/en.tsx +8 -0
  68. package/src/locales/zh.tsx +8 -0
  69. package/src/payment/error.tsx +4 -2
  70. package/src/payment/form/currency.tsx +11 -13
  71. package/src/payment/form/index.tsx +14 -4
  72. package/src/payment/index.tsx +40 -14
  73. package/src/payment/product-donation.tsx +118 -0
  74. package/src/payment/skeleton/overview.tsx +2 -2
  75. package/src/payment/skeleton/payment.tsx +1 -4
  76. package/src/payment/success.tsx +7 -2
  77. package/src/payment/summary.tsx +47 -28
  78. package/src/types/index.ts +2 -0
  79. package/src/util.ts +54 -3
@@ -70,10 +70,12 @@ export default function PaymentForm({
70
70
  checkoutSession,
71
71
  paymentMethods,
72
72
  paymentIntent,
73
+ paymentLink,
73
74
  customer,
74
75
  onPaid,
75
76
  onError,
76
77
  mode,
78
+ action,
77
79
  }: PageData) {
78
80
  const theme = useTheme();
79
81
  const { t } = useLocaleContext();
@@ -140,9 +142,17 @@ export default function PaymentForm({
140
142
  }, [domSize, theme]);
141
143
 
142
144
  const payee = getStatementDescriptor(checkoutSession.line_items);
143
- const buttonText = session?.user
144
- ? t(`payment.checkout.${checkoutSession.mode}`)
145
- : t('payment.checkout.connect', { action: t(`payment.checkout.${checkoutSession.mode}`) });
145
+ let buttonText = '';
146
+ if (paymentLink?.donation_settings) {
147
+ if (action) {
148
+ buttonText = action;
149
+ } else {
150
+ buttonText = t('payment.checkout.donate');
151
+ }
152
+ } else {
153
+ buttonText = t(`payment.checkout.${checkoutSession.mode}`);
154
+ }
155
+ buttonText = session?.user ? buttonText : t('payment.checkout.connect', { action: buttonText });
146
156
 
147
157
  const method = paymentMethods.find((x) => x.id === paymentMethod) as TPaymentMethodExpanded;
148
158
 
@@ -348,7 +358,7 @@ export default function PaymentForm({
348
358
  <AddressForm mode={checkoutSession.billing_address_collection as string} stripe={method?.type === 'stripe'} />
349
359
  <Fade in>
350
360
  <Stack direction="column" alignItems="flex-start" className="cko-payment-methods">
351
- <Typography sx={{ mb: 2, color: 'text.primary', fontWeight: 600 }}>{t('payment.checkout.method')}</Typography>
361
+ <Typography sx={{ mb: 1, color: 'text.primary', fontWeight: 600 }}>{t('payment.checkout.method')}</Typography>
352
362
  <Stack direction="row" sx={{ width: '100%' }}>
353
363
  <Controller
354
364
  name="payment_currency"
@@ -1,5 +1,4 @@
1
- import Center from '@arcblock/ux/lib/Center';
2
- /* eslint-disable import/no-extraneous-dependencies */
1
+ /* eslint-disable no-nested-ternary */
3
2
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
4
3
  import Toast from '@arcblock/ux/lib/Toast';
5
4
  import type {
@@ -12,10 +11,11 @@ import type {
12
11
  import { ArrowBackOutlined } from '@mui/icons-material';
13
12
  import { Box, Fade, Stack } from '@mui/material';
14
13
  import { styled } from '@mui/system';
14
+ import { fromTokenToUnit } from '@ocap/util';
15
15
  import { useSetState } from 'ahooks';
16
16
  import { useEffect, useState } from 'react';
17
17
  import { FormProvider, useForm, useWatch } from 'react-hook-form';
18
- import { LiteralUnion } from 'type-fest';
18
+ import type { LiteralUnion } from 'type-fest';
19
19
 
20
20
  import api from '../api';
21
21
  import { usePaymentContext } from '../contexts/payment';
@@ -50,6 +50,7 @@ export default function Payment({
50
50
  onError,
51
51
  onChange,
52
52
  goBack,
53
+ action,
53
54
  }: Props) {
54
55
  const { t } = useLocaleContext();
55
56
  const { refresh, livemode, setLivemode } = usePaymentContext();
@@ -74,7 +75,7 @@ export default function Payment({
74
75
  }, [checkoutSession, livemode, setLivemode, refresh]);
75
76
 
76
77
  if (error) {
77
- return <PaymentError title="Oops" description={formatError(error)} />;
78
+ return <PaymentError mode={mode} title="Oops" description={formatError(error)} />;
78
79
  }
79
80
 
80
81
  if (!checkoutSession || !delay) {
@@ -87,7 +88,7 @@ export default function Payment({
87
88
  <Stack className="cko-payment">
88
89
  <PaymentSkeleton />
89
90
  </Stack>
90
- <CheckoutFooter className="cko-footer" />
91
+ {mode === 'standalone' && <CheckoutFooter className="cko-footer" />}
91
92
  </Stack>
92
93
  </Root>
93
94
  );
@@ -96,12 +97,11 @@ export default function Payment({
96
97
  // expired session
97
98
  if (checkoutSession.expires_at <= Math.round(Date.now() / 1000)) {
98
99
  return (
99
- <Center>
100
- <PaymentError
101
- title={t('payment.checkout.expired.title')}
102
- description={t('payment.checkout.expired.description')}
103
- />
104
- </Center>
100
+ <PaymentError
101
+ mode={mode}
102
+ title={t('payment.checkout.expired.title')}
103
+ description={t('payment.checkout.expired.description')}
104
+ />
105
105
  );
106
106
  }
107
107
 
@@ -109,6 +109,7 @@ export default function Payment({
109
109
  if (checkoutSession.status === 'complete') {
110
110
  return (
111
111
  <PaymentError
112
+ mode={mode}
112
113
  title={t('payment.checkout.complete.title')}
113
114
  description={t('payment.checkout.complete.description')}
114
115
  />
@@ -128,6 +129,7 @@ export default function Payment({
128
129
  onChange={onChange}
129
130
  goBack={goBack}
130
131
  mode={mode}
132
+ action={action}
131
133
  />
132
134
  );
133
135
  }
@@ -150,6 +152,7 @@ export function PaymentInner({
150
152
  onError,
151
153
  onChange,
152
154
  goBack,
155
+ action,
153
156
  }: MainProps) {
154
157
  const { t } = useLocaleContext();
155
158
  const { settings, session } = usePaymentContext();
@@ -232,6 +235,19 @@ export function PaymentInner({
232
235
  }
233
236
  };
234
237
 
238
+ const onChangeAmount = async ({ priceId, amount }: { priceId: string; amount: string }) => {
239
+ try {
240
+ const { data } = await api.put(`/api/checkout-sessions/${state.checkoutSession.id}/amount`, {
241
+ priceId,
242
+ amount: fromTokenToUnit(amount, currency.decimal).toString(),
243
+ });
244
+ setState({ checkoutSession: data });
245
+ } catch (err) {
246
+ console.error(err);
247
+ Toast.error(formatError(err));
248
+ }
249
+ };
250
+
235
251
  const handlePaid = (result: any) => {
236
252
  setState({ checkoutSession: result.checkoutSession });
237
253
  onPaid(result);
@@ -247,7 +263,7 @@ export function PaymentInner({
247
263
  fontSize="medium"
248
264
  />
249
265
  )}
250
- <Stack className="cko-container" sx={{ gap: { sm: mode === 'standalone' ? 0 : 8 } }}>
266
+ <Stack className="cko-container" sx={{ gap: { sm: mode === 'standalone' ? 0 : mode === 'inline' ? 4 : 8 } }}>
251
267
  <Fade in>
252
268
  <Stack className="cko-overview" direction="column">
253
269
  {mode === 'standalone' ? <PaymentHeader checkoutSession={state.checkoutSession} /> : null}
@@ -261,21 +277,29 @@ export function PaymentInner({
261
277
  onDownsell={onDownsell}
262
278
  onApplyCrossSell={onApplyCrossSell}
263
279
  onCancelCrossSell={onCancelCrossSell}
280
+ onChangeAmount={onChangeAmount}
264
281
  checkoutSessionId={state.checkoutSession.id}
265
282
  crossSellBehavior={state.checkoutSession.cross_sell_behavior}
283
+ donationSettings={paymentLink?.donation_settings}
284
+ action={action}
266
285
  />
267
286
  </Stack>
268
287
  </Fade>
269
- <Stack className="cko-payment" direction="column" spacing={4}>
288
+ <Stack className="cko-payment" direction="column" spacing={{ xs: 2, sm: 4 }}>
270
289
  {completed && (
271
290
  <PaymentSuccess
291
+ mode={mode}
272
292
  payee={getStatementDescriptor(state.checkoutSession.line_items)}
273
293
  action={state.checkoutSession.mode}
274
294
  invoiceId={state.checkoutSession.invoice_id}
275
295
  subscriptionId={state.checkoutSession.subscription_id}
276
296
  message={
277
297
  paymentLink?.after_completion?.hosted_confirmation?.custom_message ||
278
- t(`payment.checkout.completed.${state.checkoutSession.mode}`)
298
+ t(
299
+ `payment.checkout.completed.${
300
+ paymentLink?.donation_settings ? 'donate' : state.checkoutSession.mode
301
+ }`
302
+ )
279
303
  }
280
304
  />
281
305
  )}
@@ -284,10 +308,12 @@ export function PaymentInner({
284
308
  checkoutSession={state.checkoutSession}
285
309
  paymentMethods={paymentMethods as TPaymentMethodExpanded[]}
286
310
  paymentIntent={paymentIntent}
311
+ paymentLink={paymentLink}
287
312
  customer={customer}
288
313
  onPaid={handlePaid}
289
314
  onError={onError}
290
315
  mode={mode}
316
+ action={action}
291
317
  />
292
318
  )}
293
319
  </Stack>
@@ -0,0 +1,118 @@
1
+ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
2
+ import type { DonationSettings, TLineItemExpanded } from '@blocklet/payment-types';
3
+ import { Box, Card, CardActionArea, FormControlLabel, Stack, TextField, Typography } from '@mui/material';
4
+ import { useSetState } from 'ahooks';
5
+ import { useEffect } from 'react';
6
+
7
+ import Switch from '../components/switch-button';
8
+
9
+ export default function ProductDonation({
10
+ item,
11
+ settings,
12
+ onChange,
13
+ }: {
14
+ item: TLineItemExpanded;
15
+ settings: DonationSettings;
16
+ onChange: Function;
17
+ }) {
18
+ const { t } = useLocaleContext();
19
+ const preset = settings.amount.preset || settings.amount.presets?.[0] || '0';
20
+ const [state, setState] = useSetState({
21
+ selected: preset,
22
+ input: '',
23
+ custom: settings.amount.presets?.length === 0,
24
+ error: '',
25
+ });
26
+
27
+ // Set default amount
28
+ useEffect(() => {
29
+ if (settings.amount.preset) {
30
+ setState({ selected: settings.amount.preset, custom: false });
31
+ } else if (settings.amount.presets && settings.amount.presets.length > 0) {
32
+ setState({ selected: settings.amount.presets[0], custom: false });
33
+ }
34
+ }, [settings.amount.preset, settings.amount.presets, setState]);
35
+
36
+ const handleSelect = (amount: string) => {
37
+ setState({ selected: amount, custom: false, error: '' });
38
+ onChange({ priceId: item.price_id, amount });
39
+ };
40
+
41
+ const handleInput = (event: any) => {
42
+ const { value } = event.target;
43
+ const min = parseFloat(settings.amount.minimum || '0');
44
+ const max = settings.amount.maximum ? parseFloat(settings.amount.maximum) : Infinity;
45
+
46
+ if (value < min || value > max) {
47
+ setState({ input: value, error: t('payment.checkout.donation.between', { min, max }) });
48
+ return;
49
+ }
50
+
51
+ setState({ error: '', input: value });
52
+ onChange({ priceId: item.price_id, amount: value });
53
+ };
54
+
55
+ const handleToggle = (event: any) => {
56
+ if (event.target.checked) {
57
+ setState({ custom: true, input: state.selected, error: '' });
58
+ } else {
59
+ setState({ custom: false, input: '', error: '' });
60
+ }
61
+ };
62
+
63
+ return (
64
+ <Box display="flex" flexDirection="column" alignItems="flex-start" gap={1.5}>
65
+ {settings.amount.custom && preset !== '0' && (
66
+ <FormControlLabel
67
+ control={<Switch checked={state.custom} sx={{ marginRight: 0.4 }} onChange={handleToggle} />}
68
+ label={state.custom ? t('payment.checkout.donation.select') : t('payment.checkout.donation.custom')}
69
+ sx={{ marginRight: 2, marginLeft: 0.5, color: 'text.secondary' }}
70
+ />
71
+ )}
72
+ {!state.custom && (
73
+ <Box display="flex" flexWrap="wrap" alignItems="center" gap={1.5}>
74
+ {settings.amount.presets &&
75
+ settings.amount.presets.length > 0 &&
76
+ settings.amount.presets.map((amount) => (
77
+ <Card
78
+ key={amount}
79
+ variant="outlined"
80
+ sx={{
81
+ minWidth: 120,
82
+ textAlign: 'center',
83
+ ...(state.selected === amount && !state.custom ? { borderColor: 'primary.main' } : {}),
84
+ }}>
85
+ <CardActionArea onClick={() => handleSelect(amount)}>
86
+ <Stack direction="row" sx={{ py: 1 }} spacing={0.5} alignItems="flex-end" justifyContent="center">
87
+ <Typography
88
+ component="strong"
89
+ lineHeight={1}
90
+ variant="h5"
91
+ sx={{ fontVariantNumeric: 'tabular-nums', fontWeight: 400 }}>
92
+ {amount}
93
+ </Typography>
94
+ <Typography component="small" lineHeight={1} fontSize={12}>
95
+ ABT
96
+ </Typography>{' '}
97
+ </Stack>
98
+ </CardActionArea>
99
+ </Card>
100
+ ))}
101
+ </Box>
102
+ )}
103
+ {state.custom && (
104
+ <TextField
105
+ label={preset !== '0' ? null : t('payment.checkout.donation.custom')}
106
+ type="number"
107
+ value={state.input}
108
+ onChange={handleInput}
109
+ inputProps={{ min: settings.amount.minimum, max: settings.amount.maximum }}
110
+ margin="none"
111
+ fullWidth
112
+ error={!!state.error}
113
+ helperText={state.error}
114
+ />
115
+ )}
116
+ </Box>
117
+ );
118
+ }
@@ -8,13 +8,13 @@ export default function OverviewSkeleton() {
8
8
  <Skeleton variant="circular" width={32} height={32} />
9
9
  <Skeleton variant="text" sx={{ fontSize: '2rem', width: '40%' }} />
10
10
  </Stack>
11
- <Typography mt={3} component="div" variant="h4">
11
+ <Typography mt={2} component="div" variant="h4">
12
12
  <Skeleton />
13
13
  </Typography>
14
14
  <Typography component="div" variant="h2">
15
15
  <Skeleton />
16
16
  </Typography>
17
- <Skeleton sx={{ mt: 3 }} variant="rounded" width={200} height={200} />
17
+ <Skeleton sx={{ mt: 2 }} variant="rounded" width={200} height={200} />
18
18
  </Stack>
19
19
  </Fade>
20
20
  );
@@ -3,7 +3,7 @@ import { Box, Fade, Skeleton, Stack, Typography } from '@mui/material';
3
3
  export default function PaymentSkeleton() {
4
4
  return (
5
5
  <Fade in>
6
- <Stack direction="column" spacing={3}>
6
+ <Stack direction="column" spacing={2}>
7
7
  <Skeleton variant="text" sx={{ fontSize: '2rem', width: '40%' }} />
8
8
  <Box>
9
9
  <Typography component="div" variant="h4">
@@ -25,9 +25,6 @@ export default function PaymentSkeleton() {
25
25
  <Typography component="div" variant="h4">
26
26
  <Skeleton />
27
27
  </Typography>
28
- <Typography component="div" variant="h1">
29
- <Skeleton />
30
- </Typography>
31
28
  </Box>
32
29
  </Stack>
33
30
  </Fade>
@@ -6,6 +6,7 @@ import { joinURL } from 'ufo';
6
6
  import { usePaymentContext } from '../contexts/payment';
7
7
 
8
8
  type Props = {
9
+ mode: string;
9
10
  message: string;
10
11
  action: string;
11
12
  payee: string;
@@ -13,7 +14,7 @@ type Props = {
13
14
  subscriptionId?: string;
14
15
  };
15
16
 
16
- export default function PaymentSuccess({ message, action, payee, invoiceId, subscriptionId }: Props) {
17
+ export default function PaymentSuccess({ mode, message, action, payee, invoiceId, subscriptionId }: Props) {
17
18
  const { t } = useLocaleContext();
18
19
  const { prefix } = usePaymentContext();
19
20
  let next: any = null;
@@ -37,7 +38,11 @@ export default function PaymentSuccess({ message, action, payee, invoiceId, subs
37
38
 
38
39
  return (
39
40
  <Grow in>
40
- <Stack direction="column" alignItems="center" justifyContent="center" sx={{ height: 360 }}>
41
+ <Stack
42
+ direction="column"
43
+ alignItems="center"
44
+ justifyContent={mode === 'standalone' ? 'center' : 'flex-start'}
45
+ sx={{ height: mode === 'standalone' ? 360 : 300 }}>
41
46
  <Div>
42
47
  <div className="check-icon">
43
48
  <span className="icon-line line-tip" />
@@ -1,5 +1,5 @@
1
1
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
2
- import type { TLineItemExpanded, TPaymentCurrency } from '@blocklet/payment-types';
2
+ import type { DonationSettings, TLineItemExpanded, TPaymentCurrency } from '@blocklet/payment-types';
3
3
  import { InfoOutlined } from '@mui/icons-material';
4
4
  import { LoadingButton } from '@mui/lab';
5
5
  import { Fade, Grow, Stack, Tooltip, Typography, keyframes } from '@mui/material';
@@ -12,6 +12,7 @@ import api from '../api';
12
12
  import Status from '../components/status';
13
13
  import { formatAmount, formatCheckoutHeadlines, getPriceUintAmountByCurrency } from '../util';
14
14
  import PaymentAmount from './amount';
15
+ import ProductDonation from './product-donation';
15
16
  import ProductItem from './product-item';
16
17
 
17
18
  const shake = keyframes`
@@ -40,10 +41,13 @@ type Props = {
40
41
  showStaking?: boolean;
41
42
  onUpsell?: Function;
42
43
  onDownsell?: Function;
44
+ onChangeAmount?: Function;
43
45
  onApplyCrossSell?: Function;
44
46
  onCancelCrossSell?: Function;
45
47
  checkoutSessionId?: string;
46
48
  crossSellBehavior?: string;
49
+ donationSettings?: DonationSettings; // only include backend part
50
+ action?: string;
47
51
  };
48
52
 
49
53
  async function fetchCrossSell(id: string) {
@@ -91,9 +95,12 @@ PaymentSummary.defaultProps = {
91
95
  onDownsell: noop,
92
96
  onApplyCrossSell: noop,
93
97
  onCancelCrossSell: noop,
98
+ onChangeAmount: noop,
94
99
  checkoutSessionId: '',
95
100
  crossSellBehavior: '',
96
101
  showStaking: false,
102
+ donationSettings: null,
103
+ action: '',
97
104
  };
98
105
 
99
106
  export default function PaymentSummary({
@@ -105,9 +112,12 @@ export default function PaymentSummary({
105
112
  onDownsell,
106
113
  onApplyCrossSell,
107
114
  onCancelCrossSell,
115
+ onChangeAmount,
108
116
  checkoutSessionId,
109
117
  crossSellBehavior,
110
118
  showStaking,
119
+ donationSettings,
120
+ action,
111
121
  ...rest
112
122
  }: Props) {
113
123
  const { t, locale } = useLocaleContext();
@@ -166,38 +176,47 @@ export default function PaymentSummary({
166
176
  return (
167
177
  <Fade in>
168
178
  <Stack className="cko-product" direction="column" {...rest}>
169
- <Stack className="cko-product-summary" direction="column" alignItems="flex-start" sx={{ mb: 4 }}>
179
+ <Stack className="cko-product-summary" direction="column" alignItems="flex-start" sx={{ mb: { xs: 0, sm: 3 } }}>
170
180
  <Typography sx={{ fontWeight: 500, fontSize: '1.15rem', color: 'text.secondary' }}>
171
- {headlines.action}
181
+ {action || headlines.action}
172
182
  </Typography>
173
183
  <PaymentAmount amount={headlines.amount} />
174
184
  {headlines.then && (
175
185
  <Typography sx={{ fontSize: '0.9rem', color: 'text.secondary' }}>{headlines.then}</Typography>
176
186
  )}
177
187
  </Stack>
178
- <Stack spacing={2}>
179
- {items.map((x: TLineItemExpanded) => (
180
- <ProductItem
181
- key={`${x.price_id}-${currency.id}`}
182
- item={x}
183
- items={items}
184
- trialInDays={trialInDays}
185
- currency={currency}
186
- onUpsell={handleUpsell}
187
- onDownsell={handleDownsell}>
188
- {x.cross_sell && (
189
- <LoadingButton
190
- size="small"
191
- loadingPosition="end"
192
- color="error"
193
- variant="text"
194
- loading={state.loading}
195
- onClick={handleCancelCrossSell}>
196
- {t('payment.checkout.cross_sell.remove')}
197
- </LoadingButton>
198
- )}
199
- </ProductItem>
200
- ))}
188
+ <Stack spacing={{ xs: 1, sm: 2 }}>
189
+ {items.map((x: TLineItemExpanded) =>
190
+ x.price.custom_unit_amount && onChangeAmount && donationSettings ? (
191
+ <ProductDonation
192
+ key={`${x.price_id}-${currency.id}`}
193
+ item={x}
194
+ settings={donationSettings}
195
+ onChange={onChangeAmount}
196
+ />
197
+ ) : (
198
+ <ProductItem
199
+ key={`${x.price_id}-${currency.id}`}
200
+ item={x}
201
+ items={items}
202
+ trialInDays={trialInDays}
203
+ currency={currency}
204
+ onUpsell={handleUpsell}
205
+ onDownsell={handleDownsell}>
206
+ {x.cross_sell && (
207
+ <LoadingButton
208
+ size="small"
209
+ loadingPosition="end"
210
+ color="error"
211
+ variant="text"
212
+ loading={state.loading}
213
+ onClick={handleCancelCrossSell}>
214
+ {t('payment.checkout.cross_sell.remove')}
215
+ </LoadingButton>
216
+ )}
217
+ </ProductItem>
218
+ )
219
+ )}
201
220
  </Stack>
202
221
  {data && items.some((x) => x.price_id === data.id) === false && (
203
222
  <Grow in>
@@ -210,7 +229,7 @@ export default function PaymentSummary({
210
229
  borderRadius: 1,
211
230
  padding: 1,
212
231
  animation: state.shake ? `${shake} 0.2s 5 ease-in-out` : 'none',
213
- mt: 3,
232
+ mt: { xs: 1, sm: 2 },
214
233
  }}>
215
234
  <ProductItem
216
235
  item={{ quantity: 1, price: data, price_id: data.id, cross_sell: true } as TLineItemExpanded}
@@ -249,7 +268,7 @@ export default function PaymentSummary({
249
268
  border: '1px solid #eee',
250
269
  borderRadius: 1,
251
270
  padding: 1,
252
- mt: 3,
271
+ mt: { xs: 1, sm: 2 },
253
272
  }}>
254
273
  <Stack direction="row" alignItems="center" spacing={0.5}>
255
274
  <Typography>{t('payment.checkout.staking.title')}</Typography>
@@ -15,6 +15,7 @@ export type CheckoutContext = {
15
15
  paymentIntent?: TPaymentIntent;
16
16
  customer?: TCustomer;
17
17
  mode: LiteralUnion<'standalone' | 'inline' | 'popup', string>;
18
+ action?: string;
18
19
  };
19
20
 
20
21
  export type CheckoutFormData = {
@@ -36,6 +37,7 @@ export type CheckoutFormData = {
36
37
  export type CheckoutProps = Partial<CheckoutCallbacks> & {
37
38
  id: string;
38
39
  extraParams?: Record<string, any>;
40
+ action?: string;
39
41
  mode?: LiteralUnion<'standalone' | 'inline' | 'popup' | 'inline-minimal' | 'popup-minimal', string>;
40
42
  };
41
43
 
package/src/util.ts CHANGED
@@ -23,6 +23,10 @@ import { t } from './locales';
23
23
 
24
24
  export const PAYMENT_KIT_DID = 'z2qaCNvKMv5GjouKdcDWexv6WqtHbpNPQDnAk';
25
25
 
26
+ export const isPaymentKitMounted = () => {
27
+ return (window.blocklet?.componentMountPoints || []).some((x: any) => x.did === PAYMENT_KIT_DID);
28
+ };
29
+
26
30
  export const getPrefix = () => {
27
31
  const componentId = (window?.blocklet?.componentId || '').split('/').pop();
28
32
  if (componentId === PAYMENT_KIT_DID) {
@@ -125,6 +129,10 @@ export const formatPrice = (
125
129
  bn: boolean = true,
126
130
  locale: string = 'en'
127
131
  ) => {
132
+ if (price.custom_unit_amount) {
133
+ return `Custom (${currency.symbol})`;
134
+ }
135
+
128
136
  const unit = getPriceUintAmountByCurrency(price, currency);
129
137
  const amount = bn
130
138
  ? fromUnitToToken(new BN(unit).mul(new BN(quantity)), currency.decimal).toString()
@@ -219,10 +227,16 @@ export function getPriceUintAmountByCurrency(price: TPrice, currency: TPaymentCu
219
227
  const options = getPriceCurrencyOptions(price);
220
228
  const option = options.find((x) => x.currency_id === currency.id);
221
229
  if (option) {
230
+ if (option.custom_unit_amount) {
231
+ return option.custom_unit_amount.preset || option.custom_unit_amount.presets[0];
232
+ }
222
233
  return option.unit_amount;
223
234
  }
224
235
 
225
236
  if (price.currency_id === currency.id) {
237
+ if (price.custom_unit_amount) {
238
+ return price.custom_unit_amount.preset || price.custom_unit_amount.presets[0];
239
+ }
226
240
  return price.unit_amount;
227
241
  }
228
242
 
@@ -235,7 +249,14 @@ export function getPriceCurrencyOptions(price: TPrice): PriceCurrency[] {
235
249
  return price.currency_options;
236
250
  }
237
251
 
238
- return [{ currency_id: price.currency_id, unit_amount: price.unit_amount, tiers: null, custom_unit_amount: null }];
252
+ return [
253
+ {
254
+ currency_id: price.currency_id,
255
+ unit_amount: price.unit_amount,
256
+ custom_unit_amount: price.custom_unit_amount || null,
257
+ tiers: null,
258
+ },
259
+ ];
239
260
  }
240
261
 
241
262
  export function formatLineItemPricing(
@@ -339,6 +360,20 @@ export function getRefundStatusColor(status: string) {
339
360
  }
340
361
  }
341
362
 
363
+ export function getPayoutStatusColor(status: string) {
364
+ switch (status) {
365
+ case 'paid':
366
+ return 'success';
367
+ case 'failed':
368
+ return 'warning';
369
+ case 'canceled':
370
+ case 'pending':
371
+ case 'in_transit':
372
+ default:
373
+ return 'default';
374
+ }
375
+ }
376
+
342
377
  export function getInvoiceStatusColor(status: string) {
343
378
  switch (status) {
344
379
  case 'paid':
@@ -370,6 +405,10 @@ export function getCheckoutAmount(
370
405
  trialing = false,
371
406
  upsell = true
372
407
  ) {
408
+ if (items.find((x) => (x.upsell_price || x.price).custom_unit_amount) && items.length > 1) {
409
+ throw new Error('Multiple items with custom unit amount are not supported');
410
+ }
411
+
373
412
  let renew = new BN(0);
374
413
  const total = items
375
414
  .filter((x) => {
@@ -377,9 +416,20 @@ export function getCheckoutAmount(
377
416
  return price != null;
378
417
  })
379
418
  .reduce((acc, x) => {
419
+ if (x.custom_amount) {
420
+ return acc.add(new BN(x.custom_amount));
421
+ }
422
+
380
423
  const price = upsell ? x.upsell_price || x.price : x.price;
424
+ const unitPrice = getPriceUintAmountByCurrency(price, currency);
425
+ if (price.custom_unit_amount) {
426
+ if (unitPrice) {
427
+ return acc.add(new BN(unitPrice).mul(new BN(x.quantity)));
428
+ }
429
+ }
430
+
381
431
  if (price?.type === 'recurring') {
382
- renew = renew.add(new BN(getPriceUintAmountByCurrency(price, currency)).mul(new BN(x.quantity)));
432
+ renew = renew.add(new BN(unitPrice).mul(new BN(x.quantity)));
383
433
 
384
434
  if (trialing) {
385
435
  return acc;
@@ -388,7 +438,8 @@ export function getCheckoutAmount(
388
438
  return acc;
389
439
  }
390
440
  }
391
- return acc.add(new BN(getPriceUintAmountByCurrency(price, currency)).mul(new BN(x.quantity)));
441
+
442
+ return acc.add(new BN(unitPrice).mul(new BN(x.quantity)));
392
443
  }, new BN(0))
393
444
  .toString();
394
445