payment-kit 1.13.17 → 1.13.19

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 (109) hide show
  1. package/README.md +14 -0
  2. package/api/src/index.ts +17 -6
  3. package/api/src/integrations/stripe/handlers/index.ts +53 -0
  4. package/api/src/integrations/stripe/handlers/invoice.ts +252 -0
  5. package/api/src/integrations/stripe/handlers/payment-intent.ts +172 -0
  6. package/api/src/integrations/stripe/handlers/setup-intent.ts +42 -0
  7. package/api/src/integrations/stripe/handlers/subscription.ts +61 -0
  8. package/api/src/integrations/stripe/resource.ts +317 -0
  9. package/api/src/integrations/stripe/setup.ts +50 -0
  10. package/api/src/jobs/invoice.ts +11 -0
  11. package/api/src/jobs/payment.ts +15 -7
  12. package/api/src/jobs/subscription.ts +18 -2
  13. package/api/src/libs/session.ts +104 -8
  14. package/api/src/libs/util.ts +47 -1
  15. package/api/src/routes/checkout-sessions.ts +134 -27
  16. package/api/src/routes/connect/collect.ts +12 -4
  17. package/api/src/routes/connect/pay.ts +30 -20
  18. package/api/src/routes/connect/setup.ts +12 -4
  19. package/api/src/routes/connect/shared.ts +28 -4
  20. package/api/src/routes/connect/subscribe.ts +12 -5
  21. package/api/src/routes/customers.ts +5 -5
  22. package/api/src/routes/events.ts +9 -6
  23. package/api/src/routes/index.ts +2 -0
  24. package/api/src/routes/integrations/stripe.ts +64 -0
  25. package/api/src/routes/invoices.ts +19 -9
  26. package/api/src/routes/payment-intents.ts +19 -9
  27. package/api/src/routes/payment-links.ts +57 -15
  28. package/api/src/routes/payment-methods.ts +98 -1
  29. package/api/src/routes/prices.ts +71 -14
  30. package/api/src/routes/products.ts +79 -22
  31. package/api/src/routes/settings.ts +10 -11
  32. package/api/src/routes/subscription-items.ts +5 -5
  33. package/api/src/routes/subscriptions.ts +61 -10
  34. package/api/src/routes/usage-records.ts +52 -18
  35. package/api/src/routes/webhook-attempts.ts +5 -5
  36. package/api/src/routes/webhook-endpoints.ts +5 -5
  37. package/api/src/store/migrations/20230905-genesis.ts +2 -2
  38. package/api/src/store/migrations/20230911-seeding.ts +4 -3
  39. package/api/src/store/models/checkout-session.ts +15 -7
  40. package/api/src/store/models/index.ts +31 -7
  41. package/api/src/store/models/invoice.ts +1 -1
  42. package/api/src/store/models/payment-intent.ts +2 -5
  43. package/api/src/store/models/payment-link.ts +1 -1
  44. package/api/src/store/models/payment-method.ts +54 -33
  45. package/api/src/store/models/price.ts +52 -17
  46. package/api/src/store/models/product.ts +0 -3
  47. package/api/src/store/models/subscription.ts +3 -5
  48. package/api/src/store/models/types.ts +56 -2
  49. package/api/third.d.ts +2 -0
  50. package/blocklet.yml +1 -1
  51. package/package.json +36 -29
  52. package/public/currencies/dai.png +0 -0
  53. package/public/currencies/dollar.png +0 -0
  54. package/public/currencies/usdc.png +0 -0
  55. package/public/currencies/usdt.png +0 -0
  56. package/public/methods/arcblock.png +0 -0
  57. package/public/methods/binance.png +0 -0
  58. package/public/methods/coinbase.png +0 -0
  59. package/public/methods/ethereum.jpg +0 -0
  60. package/public/methods/stripe.png +0 -0
  61. package/src/components/checkout/form/address.tsx +86 -10
  62. package/src/components/checkout/form/index.tsx +169 -83
  63. package/src/components/checkout/form/phone.tsx +96 -0
  64. package/src/components/checkout/form/stripe.tsx +195 -0
  65. package/src/components/checkout/pay.tsx +115 -34
  66. package/src/components/checkout/product-item.tsx +4 -3
  67. package/src/components/checkout/summary.tsx +5 -4
  68. package/src/components/drawer-form.tsx +4 -4
  69. package/src/components/input.tsx +22 -4
  70. package/src/components/invoice/table.tsx +8 -3
  71. package/src/components/payment-link/before-pay.tsx +11 -6
  72. package/src/components/payment-link/chrome.tsx +13 -0
  73. package/src/components/payment-link/preview.tsx +31 -0
  74. package/src/components/payment-link/product-select.tsx +8 -3
  75. package/src/components/payment-method/arcblock.tsx +53 -0
  76. package/src/components/payment-method/bitcoin.tsx +53 -0
  77. package/src/components/payment-method/ethereum.tsx +53 -0
  78. package/src/components/payment-method/form.tsx +54 -0
  79. package/src/components/payment-method/stripe.tsx +45 -0
  80. package/src/components/portal/invoice/list.tsx +1 -1
  81. package/src/components/portal/subscription/list.tsx +1 -1
  82. package/src/components/price/currency-select.tsx +53 -0
  83. package/src/components/price/form.tsx +118 -24
  84. package/src/components/product/add-price.tsx +1 -1
  85. package/src/components/product/edit-price.tsx +6 -2
  86. package/src/components/subscription/items/index.tsx +7 -6
  87. package/src/components/subscription/items/usage-records.tsx +98 -0
  88. package/src/components/subscription/list.tsx +3 -2
  89. package/src/components/subscription/status.tsx +68 -0
  90. package/src/contexts/settings.tsx +2 -2
  91. package/src/env.d.ts +2 -0
  92. package/src/libs/util.ts +116 -21
  93. package/src/locales/en.tsx +71 -3
  94. package/src/pages/admin/billing/invoices/detail.tsx +5 -2
  95. package/src/pages/admin/billing/subscriptions/detail.tsx +6 -6
  96. package/src/pages/admin/customers/customers/detail.tsx +13 -1
  97. package/src/pages/admin/payments/intents/detail.tsx +8 -3
  98. package/src/pages/admin/payments/links/create.tsx +23 -3
  99. package/src/pages/admin/payments/links/detail.tsx +13 -26
  100. package/src/pages/admin/products/prices/detail.tsx +55 -11
  101. package/src/pages/admin/products/prices/list.tsx +7 -1
  102. package/src/pages/admin/products/products/create.tsx +1 -1
  103. package/src/pages/admin/products/products/detail.tsx +14 -7
  104. package/src/pages/admin/settings/index.tsx +16 -6
  105. package/src/pages/admin/settings/payment-methods/create.tsx +81 -0
  106. package/src/pages/admin/settings/{payment-methods.tsx → payment-methods/index.tsx} +9 -6
  107. package/src/pages/checkout/pay.tsx +3 -1
  108. package/src/pages/customer/index.tsx +12 -1
  109. package/public/.gitkeep +0 -0
@@ -2,6 +2,7 @@ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
2
2
  import type {
3
3
  TCheckoutSessionExpanded,
4
4
  TCustomer,
5
+ TPaymentCurrency,
5
6
  TPaymentIntent,
6
7
  TPaymentLink,
7
8
  TPaymentMethodExpanded,
@@ -9,9 +10,11 @@ import type {
9
10
  import { Box, Fade, Stack } from '@mui/material';
10
11
  import { styled } from '@mui/system';
11
12
  import { useEffect } from 'react';
13
+ import { FormProvider, useForm } from 'react-hook-form';
12
14
 
15
+ import { useSessionContext } from '../../contexts/session';
13
16
  import { useSettingsContext } from '../../contexts/settings';
14
- import { formatError, getStatementDescriptor } from '../../libs/util';
17
+ import { findCurrency, formatError, getStatementDescriptor } from '../../libs/util';
15
18
  import PaymentError from './error';
16
19
  import CheckoutFooter from './footer';
17
20
  import PaymentForm from './form';
@@ -54,7 +57,6 @@ export default function CheckoutPay({
54
57
  onPaid,
55
58
  onError,
56
59
  }: Props) {
57
- const { t } = useLocaleContext();
58
60
  const { refresh, livemode, setLivemode } = useSettingsContext();
59
61
 
60
62
  useEffect(() => {
@@ -108,42 +110,117 @@ export default function CheckoutPay({
108
110
  );
109
111
  }
110
112
 
111
- const brand = getStatementDescriptor(checkoutSession.line_items);
113
+ return (
114
+ <CheckoutPayMain
115
+ checkoutSession={checkoutSession}
116
+ paymentMethods={paymentMethods as TPaymentMethodExpanded[]}
117
+ paymentLink={paymentLink}
118
+ paymentIntent={paymentIntent}
119
+ completed={completed}
120
+ customer={customer as TCustomer}
121
+ onPaid={onPaid}
122
+ onError={onError}
123
+ />
124
+ );
125
+ }
126
+
127
+ type MainProps = {
128
+ onPaid: Function;
129
+ onError: Function;
130
+ checkoutSession: TCheckoutSessionExpanded;
131
+ paymentMethods: TPaymentMethodExpanded[];
132
+ paymentLink?: TPaymentLink;
133
+ paymentIntent?: TPaymentIntent;
134
+ customer: TCustomer;
135
+ completed?: boolean;
136
+ };
137
+
138
+ CheckoutPayMain.defaultProps = {
139
+ paymentLink: null,
140
+ paymentIntent: null,
141
+ completed: false,
142
+ };
143
+
144
+ export function CheckoutPayMain({
145
+ checkoutSession,
146
+ paymentMethods,
147
+ paymentLink,
148
+ paymentIntent,
149
+ customer,
150
+ completed,
151
+ onPaid,
152
+ onError,
153
+ }: MainProps) {
154
+ const { t } = useLocaleContext();
155
+ const { session } = useSessionContext();
156
+ const { settings } = useSettingsContext();
157
+
158
+ const defaultCurrencyId = checkoutSession?.currency_id || checkoutSession?.line_items[0]?.price.currency_id;
159
+ const defaultMethodId = paymentMethods.find((m) => m.payment_currencies.some((c) => c.id === defaultCurrencyId))?.id;
160
+
161
+ const methods = useForm({
162
+ defaultValues: {
163
+ customer_name: customer?.name || session.user?.fullName || '',
164
+ customer_email: customer?.email || session.user?.email || '',
165
+ customer_phone: customer?.phone || session.user?.phone || '',
166
+ payment_method: defaultMethodId,
167
+ payment_currency: defaultCurrencyId,
168
+ billing_address: Object.assign(
169
+ {
170
+ country: '',
171
+ state: '',
172
+ city: '',
173
+ line1: '',
174
+ line2: '',
175
+ postal_code: '',
176
+ },
177
+ customer?.address || {},
178
+ { country: customer?.address?.country || 'us' }
179
+ ),
180
+ },
181
+ });
182
+
183
+ const currencyId = methods.watch('payment_currency') as string;
184
+ const currency =
185
+ (findCurrency(paymentMethods as TPaymentMethodExpanded[], currencyId as string) as TPaymentCurrency) ||
186
+ settings.baseCurrency;
112
187
 
113
188
  return (
114
- <PaymentRoot>
115
- <Stack direction="row" className="cko-container">
116
- <Fade in>
117
- <Stack className="cko-overview" direction="column">
118
- <PaymentHeader checkoutSession={checkoutSession} />
119
- <PaymentSummary checkoutSession={checkoutSession} />
189
+ <FormProvider {...methods}>
190
+ <PaymentRoot>
191
+ <Stack direction="row" className="cko-container">
192
+ <Fade in>
193
+ <Stack className="cko-overview" direction="column">
194
+ <PaymentHeader checkoutSession={checkoutSession} />
195
+ <PaymentSummary checkoutSession={checkoutSession} currency={currency} />
196
+ </Stack>
197
+ </Fade>
198
+ <Stack className="cko-payment" direction="column" spacing={4}>
199
+ {completed && (
200
+ <PaymentSuccess
201
+ payee={getStatementDescriptor(checkoutSession.line_items)}
202
+ action={checkoutSession.mode}
203
+ message={
204
+ paymentLink?.after_completion?.hosted_confirmation?.custom_message ||
205
+ t(`checkout.completed.${checkoutSession.mode}`)
206
+ }
207
+ />
208
+ )}
209
+ {!completed && (
210
+ <PaymentForm
211
+ checkoutSession={checkoutSession}
212
+ paymentMethods={paymentMethods as TPaymentMethodExpanded[]}
213
+ paymentIntent={paymentIntent}
214
+ customer={customer}
215
+ onPaid={onPaid}
216
+ onError={onError}
217
+ />
218
+ )}
120
219
  </Stack>
121
- </Fade>
122
- <Stack className="cko-payment" direction="column" spacing={4}>
123
- {completed && (
124
- <PaymentSuccess
125
- payee={brand}
126
- action={checkoutSession.mode}
127
- message={
128
- paymentLink?.after_completion?.hosted_confirmation?.custom_message ||
129
- t(`checkout.completed.${checkoutSession.mode}`)
130
- }
131
- />
132
- )}
133
- {!completed && (
134
- <PaymentForm
135
- checkoutSession={checkoutSession}
136
- paymentMethods={paymentMethods as TPaymentMethodExpanded[]}
137
- paymentIntent={paymentIntent}
138
- customer={customer}
139
- onPaid={onPaid}
140
- onError={onError}
141
- />
142
- )}
220
+ <CheckoutFooter className="cko-footer" />
143
221
  </Stack>
144
- <CheckoutFooter className="cko-footer" />
145
- </Stack>
146
- </PaymentRoot>
222
+ </PaymentRoot>
223
+ </FormProvider>
147
224
  );
148
225
  }
149
226
 
@@ -198,6 +275,10 @@ export const PaymentRoot = styled(Box)`
198
275
  margin: -1px 0 0 -1px;
199
276
  }
200
277
 
278
+ .MuiFormHelperText-root {
279
+ margin-left: 14px;
280
+ }
281
+
201
282
  .MuiOutlinedInput-notchedOutline {
202
283
  border: none;
203
284
  }
@@ -1,4 +1,4 @@
1
- import type { TCheckoutSessionExpanded, TLineItemExpanded } from '@did-pay/types';
1
+ import type { TCheckoutSessionExpanded, TLineItemExpanded, TPaymentCurrency } from '@did-pay/types';
2
2
  import { Stack, Typography } from '@mui/material';
3
3
 
4
4
  import { formatLineItemPricing, formatRecurring } from '../../libs/util';
@@ -7,10 +7,11 @@ import ProductCard from './product-card';
7
7
  type Props = {
8
8
  item: TLineItemExpanded;
9
9
  session: TCheckoutSessionExpanded;
10
+ currency: TPaymentCurrency;
10
11
  };
11
12
 
12
- export default function ProductItem({ item, session }: Props) {
13
- const pricing = formatLineItemPricing(item, session.currency, session.subscription_data?.trial_period_days || 0);
13
+ export default function ProductItem({ item, session, currency }: Props) {
14
+ const pricing = formatLineItemPricing(item, currency, session.subscription_data?.trial_period_days || 0);
14
15
  const metered = item.price?.recurring?.usage_type === 'metered' ? ' based on usage' : '';
15
16
  return (
16
17
  <Stack direction="row" alignItems="center" justifyContent="space-between">
@@ -1,4 +1,4 @@
1
- import type { TCheckoutSessionExpanded, TLineItemExpanded } from '@did-pay/types';
1
+ import type { TCheckoutSessionExpanded, TLineItemExpanded, TPaymentCurrency } from '@did-pay/types';
2
2
  import { Fade, Stack, Typography } from '@mui/material';
3
3
 
4
4
  import { formatCheckoutHeadlines } from '../../libs/util';
@@ -7,10 +7,11 @@ import ProductItem from './product-item';
7
7
 
8
8
  type Props = {
9
9
  checkoutSession: TCheckoutSessionExpanded;
10
+ currency: TPaymentCurrency;
10
11
  };
11
12
 
12
- export default function PaymentSummary({ checkoutSession }: Props) {
13
- const headlines = formatCheckoutHeadlines(checkoutSession);
13
+ export default function PaymentSummary({ checkoutSession, currency }: Props) {
14
+ const headlines = formatCheckoutHeadlines(checkoutSession, currency);
14
15
  return (
15
16
  <Fade in>
16
17
  <Stack className="cko-product" direction="column" sx={{ mt: 4 }}>
@@ -25,7 +26,7 @@ export default function PaymentSummary({ checkoutSession }: Props) {
25
26
  </Stack>
26
27
  <Stack spacing={2}>
27
28
  {checkoutSession.line_items.map((x: TLineItemExpanded) => (
28
- <ProductItem key={x.price_id} item={x} session={checkoutSession} />
29
+ <ProductItem key={x.price_id} item={x} session={checkoutSession} currency={currency} />
29
30
  ))}
30
31
  </Stack>
31
32
  </Stack>
@@ -12,13 +12,13 @@ type Props = {
12
12
  text: string;
13
13
  addons: React.ReactNode;
14
14
  children: React.ReactNode;
15
- maxWidth?: number;
15
+ width?: number;
16
16
  style?: Record<string, any>;
17
17
  };
18
18
 
19
19
  DrawerForm.defaultProps = {
20
20
  style: {},
21
- maxWidth: 960,
21
+ width: 960,
22
22
  };
23
23
 
24
24
  export default function DrawerForm(props: Props) {
@@ -38,7 +38,7 @@ export default function DrawerForm(props: Props) {
38
38
  open={open}
39
39
  onClose={() => setOpen(false)}
40
40
  sx={props.style || {}}
41
- maxWidth={props.maxWidth}
41
+ width={props.width}
42
42
  disableEscapeKeyDown>
43
43
  <Stack
44
44
  direction="row"
@@ -72,6 +72,6 @@ const Container = styled<any>(Drawer)`
72
72
  .MuiPaper-root {
73
73
  width: 80%;
74
74
  min-width: 360px;
75
- max-width: ${(props) => props.maxWidth}px;
75
+ max-width: ${(props) => props.width}px;
76
76
  }
77
77
  `;
@@ -1,21 +1,35 @@
1
- import { Box, FormLabel, TextField, TextFieldProps } from '@mui/material';
1
+ import { Box, FormLabel, InputAdornment, TextField, TextFieldProps, Typography } from '@mui/material';
2
+ import get from 'lodash/get';
2
3
  import { Controller, RegisterOptions, useFormContext } from 'react-hook-form';
3
4
 
4
5
  type InputProps = TextFieldProps & {
5
6
  name: string;
6
7
  label?: string;
7
8
  placeholder?: string;
9
+ errorPosition?: 'right' | 'bottom';
8
10
  rules?: RegisterOptions;
9
11
  };
10
12
 
11
13
  FormInput.defaultProps = {
12
14
  label: '',
13
15
  placeholder: '',
16
+ errorPosition: 'bottom',
14
17
  rules: {},
15
18
  };
16
19
 
17
- export default function FormInput({ name, label, placeholder, rules, ...rest }: InputProps) {
20
+ export function FormInputError({ error }: { error: string }) {
21
+ return (
22
+ <InputAdornment position="end">
23
+ <Typography component="span" color="error">
24
+ {error}
25
+ </Typography>
26
+ </InputAdornment>
27
+ );
28
+ }
29
+
30
+ export default function FormInput({ name, label, placeholder, rules, errorPosition, ...rest }: InputProps) {
18
31
  const { control, formState } = useFormContext();
32
+ const error = get(formState.errors, name)?.message as string;
19
33
  return (
20
34
  <Controller
21
35
  name={name}
@@ -26,12 +40,16 @@ export default function FormInput({ name, label, placeholder, rules, ...rest }:
26
40
  {!!label && <FormLabel>{label}</FormLabel>}
27
41
  <TextField
28
42
  fullWidth
29
- error={!!formState.errors[name]}
30
- helperText={formState.errors[name]?.message as string}
43
+ error={!!get(formState.errors, name)}
44
+ helperText={errorPosition === 'bottom' && error ? error : ''}
31
45
  placeholder={placeholder}
32
46
  size="small"
33
47
  {...field}
34
48
  {...rest}
49
+ InputProps={Object.assign(
50
+ rest.InputProps || {},
51
+ errorPosition === 'right' && error ? { endAdornment: <FormInputError error={error} /> } : {}
52
+ )}
35
53
  />
36
54
  </Box>
37
55
  )}
@@ -2,7 +2,7 @@ import type { TInvoiceExpanded } from '@did-pay/types';
2
2
  import { Table, TableBody, TableCell, TableHead, TableRow, Typography } from '@mui/material';
3
3
  import { styled } from '@mui/system';
4
4
 
5
- import { formatAmount, formatToDate } from '../../libs/util';
5
+ import { formatAmount, formatToDate, getPriceUintAmountByCurrency } from '../../libs/util';
6
6
  import LineItemActions from '../subscription/items/actions';
7
7
 
8
8
  type Props = {
@@ -53,7 +53,12 @@ export default function InvoiceTable({ invoice, simple }: Props) {
53
53
  {line.price.product.unit_label ? ` (per ${line.price.product.unit_label})` : ''}
54
54
  </TableCell>
55
55
  <TableCell align="right">{line.quantity}</TableCell>
56
- <TableCell align="right">{formatAmount(line.price.unit_amount, invoice.paymentCurrency.decimal)}</TableCell>
56
+ <TableCell align="right">
57
+ {formatAmount(
58
+ getPriceUintAmountByCurrency(line.price, invoice.paymentCurrency),
59
+ invoice.paymentCurrency.decimal
60
+ )}
61
+ </TableCell>
57
62
  <TableCell align="right">{formatAmount(line.amount, invoice.paymentCurrency.decimal)}</TableCell>
58
63
  {!simple && (
59
64
  <TableCell align="right">
@@ -94,7 +99,7 @@ export default function InvoiceTable({ invoice, simple }: Props) {
94
99
  Amount Due
95
100
  </TableCell>
96
101
  <TableCell align="right" sx={{ fontWeight: 600 }}>
97
- {formatAmount(invoice.amount_due, invoice.paymentCurrency.decimal)}
102
+ {formatAmount(invoice.amount_remaining, invoice.paymentCurrency.decimal)}
98
103
  </TableCell>
99
104
  <TableCell>&nbsp;</TableCell>
100
105
  </TableRow>
@@ -5,7 +5,7 @@ import { useEffect, useState } from 'react';
5
5
  import { Controller, useFieldArray, useFormContext, useWatch } from 'react-hook-form';
6
6
 
7
7
  import { useProductsContext } from '../../contexts/products';
8
- import { getProductByPriceId, isPriceRecurringAligned } from '../../libs/util';
8
+ import { getProductByPriceId, isPriceAligned } from '../../libs/util';
9
9
  import CreateProduct from '../product/create';
10
10
  import LineItem from './item';
11
11
  import ProductSelect from './product-select';
@@ -53,11 +53,11 @@ export default function BeforePay() {
53
53
  };
54
54
 
55
55
  return (
56
- <Stack spacing={2}>
56
+ <Stack spacing={2} sx={{ width: '100%' }}>
57
57
  <Typography variant="h6" sx={{ fontWeight: 600 }}>
58
58
  {t('admin.paymentLink.products')} ({getValues().line_items.length})
59
59
  </Typography>
60
- <Stack spacing={2}>
60
+ <Stack spacing={2} sx={{ width: '100%' }}>
61
61
  {items.fields.map((item, index) => {
62
62
  // @ts-ignore
63
63
  const product = getProductByPriceId(products, item.price_id);
@@ -69,7 +69,7 @@ export default function BeforePay() {
69
69
  <LineItem
70
70
  key={item.id}
71
71
  // @ts-ignore
72
- valid={isPriceRecurringAligned(items.fields, products, index)}
72
+ valid={isPriceAligned(items.fields, products, index).aligned}
73
73
  prefix={`line_items.${index}`}
74
74
  product={product}
75
75
  onRemove={() => items.remove(index)}
@@ -77,9 +77,14 @@ export default function BeforePay() {
77
77
  />
78
78
  );
79
79
  })}
80
- {items.fields.some((_, index) => !isPriceRecurringAligned(items.fields as any[], products, index)) && (
80
+ {items.fields.some((_, index) => !isPriceAligned(items.fields as any[], products, index).recurring) && (
81
81
  <Typography color="error" fontSize="small">
82
- {t('admin.paymentLink.notAligned')}
82
+ {t('admin.paymentLink.recurringNotAligned')}
83
+ </Typography>
84
+ )}
85
+ {items.fields.some((_, index) => !isPriceAligned(items.fields as any[], products, index).currency) && (
86
+ <Typography color="error" fontSize="small">
87
+ {t('admin.paymentLink.currencyNotAligned')}
83
88
  </Typography>
84
89
  )}
85
90
  <ProductSelect
@@ -0,0 +1,13 @@
1
+ import { Box } from '@mui/material';
2
+ import { styled } from '@mui/system';
3
+
4
+ const Chrome = styled(Box)`
5
+ background-color: #fcfeff;
6
+ border-radius: 8px;
7
+ margin-top: 40px;
8
+ position: relative;
9
+ overflow: hidden;
10
+ box-shadow: 0 20px 44px #32325d1f, 0 -1px 32px #32325d0f, 0 3px 12px #00000014;
11
+ `;
12
+
13
+ export default Chrome;
@@ -0,0 +1,31 @@
1
+ import { Box } from '@mui/material';
2
+ import { useSize } from 'ahooks';
3
+ import IframeResizer from 'iframe-resizer-react';
4
+ import { useRef } from 'react';
5
+
6
+ import Chrome from './chrome';
7
+
8
+ PaymentLinkPreview.defaultProps = {
9
+ version: 1,
10
+ };
11
+
12
+ export default function PaymentLinkPreview({ id, version }: { id: string; version?: number }) {
13
+ const ref = useRef(null);
14
+ const size = useSize(ref);
15
+ return (
16
+ <Chrome ref={ref}>
17
+ <Box sx={{ width: '100%' }}>&nbsp;</Box>
18
+ <IframeResizer
19
+ style={{
20
+ width: '1px',
21
+ minWidth: size?.width,
22
+ minHeight: '64vh',
23
+ transform: 'scale(0.8)',
24
+ transformOrigin: 'center',
25
+ border: 'none',
26
+ }}
27
+ src={`${window.blocklet.prefix}checkout/pay/${id}?preview=1&version=${version}`}
28
+ />
29
+ </Chrome>
30
+ );
31
+ }
@@ -8,7 +8,7 @@ import type { LiteralUnion } from 'type-fest';
8
8
 
9
9
  import { useProductsContext } from '../../contexts/products';
10
10
  import { useSettingsContext } from '../../contexts/settings';
11
- import { formatPrice } from '../../libs/util';
11
+ import { formatPrice, getPriceCurrencyOptions } from '../../libs/util';
12
12
 
13
13
  type Props = {
14
14
  mode: LiteralUnion<'waiting' | 'selecting', string>;
@@ -39,7 +39,7 @@ export default function ProductSelect({ mode: initialMode, hasSelected, onSelect
39
39
 
40
40
  if (mode === 'selecting') {
41
41
  return (
42
- <Select value="" fullWidth size="small" onChange={handleSelect}>
42
+ <Select value="" fullWidth size="small" onChange={handleSelect} MenuProps={{ style: { maxHeight: 480 } }}>
43
43
  <MenuItem value="add">
44
44
  <AddOutlined />
45
45
  {t('admin.product.add')}
@@ -50,7 +50,12 @@ export default function ProductSelect({ mode: initialMode, hasSelected, onSelect
50
50
  </ListSubheader>,
51
51
  ...product.prices.map((price) => (
52
52
  <MenuItem key={price.id} sx={{ pl: 3 }} value={price.id}>
53
- {formatPrice(price, settings.baseCurrency)}
53
+ <Typography color="text.primary">{formatPrice(price, settings.baseCurrency)}</Typography>
54
+ <Typography color="text.secondary" sx={{ ml: 2 }}>
55
+ {getPriceCurrencyOptions(price).length > 1
56
+ ? ` +${getPriceCurrencyOptions(price).length - 1} more currencies`
57
+ : ''}
58
+ </Typography>
54
59
  </MenuItem>
55
60
  )),
56
61
  ])}
@@ -0,0 +1,53 @@
1
+ /* eslint-disable no-nested-ternary */
2
+ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
3
+
4
+ import FormInput from '../input';
5
+
6
+ export default function ArcBlockMethodForm() {
7
+ const { t } = useLocaleContext();
8
+
9
+ return (
10
+ <>
11
+ <FormInput
12
+ key="name"
13
+ name="name"
14
+ type="text"
15
+ rules={{ required: true }}
16
+ label={t('admin.paymentMethod.name.label')}
17
+ placeholder={t('admin.paymentMethod.name.tip')}
18
+ />
19
+ <FormInput
20
+ key="description"
21
+ name="description"
22
+ type="text"
23
+ rules={{ required: true }}
24
+ label={t('admin.paymentMethod.description.label')}
25
+ placeholder={t('admin.paymentMethod.description.tip')}
26
+ />
27
+ <FormInput
28
+ key="secret_key"
29
+ name="settings.arcblock.chain_id"
30
+ type="text"
31
+ rules={{ required: true }}
32
+ label={t('admin.paymentMethod.arcblock.chain_id.label')}
33
+ placeholder={t('admin.paymentMethod.arcblock.chain_id.tip')}
34
+ />
35
+ <FormInput
36
+ key="api_host"
37
+ name="settings.arcblock.api_host"
38
+ type="text"
39
+ rules={{ required: true }}
40
+ label={t('admin.paymentMethod.arcblock.api_host.label')}
41
+ placeholder={t('admin.paymentMethod.arcblock.api_host.tip')}
42
+ />
43
+ <FormInput
44
+ key="explorer_host"
45
+ name="settings.arcblock.explorer_host"
46
+ type="text"
47
+ rules={{ required: true }}
48
+ label={t('admin.paymentMethod.arcblock.explorer_host.label')}
49
+ placeholder={t('admin.paymentMethod.arcblock.explorer_host.tip')}
50
+ />
51
+ </>
52
+ );
53
+ }
@@ -0,0 +1,53 @@
1
+ /* eslint-disable no-nested-ternary */
2
+ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
3
+
4
+ import FormInput from '../input';
5
+
6
+ export default function BitcoinMethodForm() {
7
+ const { t } = useLocaleContext();
8
+
9
+ return (
10
+ <>
11
+ <FormInput
12
+ key="name"
13
+ name="name"
14
+ type="text"
15
+ rules={{ required: true }}
16
+ label={t('admin.paymentMethod.name.label')}
17
+ placeholder={t('admin.paymentMethod.name.tip')}
18
+ />
19
+ <FormInput
20
+ key="description"
21
+ name="description"
22
+ type="text"
23
+ rules={{ required: true }}
24
+ label={t('admin.paymentMethod.description.label')}
25
+ placeholder={t('admin.paymentMethod.description.tip')}
26
+ />
27
+ <FormInput
28
+ key="secret_key"
29
+ name="settings.bitcoin.chain_id"
30
+ type="text"
31
+ rules={{ required: true }}
32
+ label={t('admin.paymentMethod.bitcoin.chain_id.label')}
33
+ placeholder={t('admin.paymentMethod.bitcoin.chain_id.tip')}
34
+ />
35
+ <FormInput
36
+ key="api_host"
37
+ name="settings.bitcoin.api_host"
38
+ type="text"
39
+ rules={{ required: true }}
40
+ label={t('admin.paymentMethod.bitcoin.api_host.label')}
41
+ placeholder={t('admin.paymentMethod.bitcoin.api_host.tip')}
42
+ />
43
+ <FormInput
44
+ key="explorer_host"
45
+ name="settings.bitcoin.explorer_host"
46
+ type="text"
47
+ rules={{ required: true }}
48
+ label={t('admin.paymentMethod.bitcoin.explorer_host.label')}
49
+ placeholder={t('admin.paymentMethod.bitcoin.explorer_host.tip')}
50
+ />
51
+ </>
52
+ );
53
+ }
@@ -0,0 +1,53 @@
1
+ /* eslint-disable no-nested-ternary */
2
+ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
3
+
4
+ import FormInput from '../input';
5
+
6
+ export default function EthereumMethodForm() {
7
+ const { t } = useLocaleContext();
8
+
9
+ return (
10
+ <>
11
+ <FormInput
12
+ key="name"
13
+ name="name"
14
+ type="text"
15
+ rules={{ required: true }}
16
+ label={t('admin.paymentMethod.name.label')}
17
+ placeholder={t('admin.paymentMethod.name.tip')}
18
+ />
19
+ <FormInput
20
+ key="description"
21
+ name="description"
22
+ type="text"
23
+ rules={{ required: true }}
24
+ label={t('admin.paymentMethod.description.label')}
25
+ placeholder={t('admin.paymentMethod.description.tip')}
26
+ />
27
+ <FormInput
28
+ key="secret_key"
29
+ name="settings.ethereum.chain_id"
30
+ type="text"
31
+ rules={{ required: true }}
32
+ label={t('admin.paymentMethod.ethereum.chain_id.label')}
33
+ placeholder={t('admin.paymentMethod.ethereum.chain_id.tip')}
34
+ />
35
+ <FormInput
36
+ key="api_host"
37
+ name="settings.ethereum.api_host"
38
+ type="text"
39
+ rules={{ required: true }}
40
+ label={t('admin.paymentMethod.ethereum.api_host.label')}
41
+ placeholder={t('admin.paymentMethod.ethereum.api_host.tip')}
42
+ />
43
+ <FormInput
44
+ key="explorer_host"
45
+ name="settings.ethereum.explorer_host"
46
+ type="text"
47
+ rules={{ required: true }}
48
+ label={t('admin.paymentMethod.ethereum.explorer_host.label')}
49
+ placeholder={t('admin.paymentMethod.ethereum.explorer_host.tip')}
50
+ />
51
+ </>
52
+ );
53
+ }