payment-kit 1.13.18 → 1.13.20

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 (114) 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 +37 -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 +13 -6
  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 +84 -10
  62. package/src/components/checkout/form/index.tsx +169 -83
  63. package/src/components/checkout/form/phone.tsx +102 -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/customer/edit.tsx +73 -0
  69. package/src/components/customer/form.tsx +104 -0
  70. package/src/components/drawer-form.tsx +4 -4
  71. package/src/components/input.tsx +22 -4
  72. package/src/components/invoice/table.tsx +8 -3
  73. package/src/components/metadata/editor.tsx +2 -3
  74. package/src/components/payment-link/after-pay.tsx +1 -1
  75. package/src/components/payment-link/before-pay.tsx +11 -6
  76. package/src/components/payment-link/chrome.tsx +13 -0
  77. package/src/components/payment-link/preview.tsx +31 -0
  78. package/src/components/payment-link/product-select.tsx +8 -3
  79. package/src/components/payment-link/rename.tsx +2 -2
  80. package/src/components/payment-method/arcblock.tsx +53 -0
  81. package/src/components/payment-method/bitcoin.tsx +53 -0
  82. package/src/components/payment-method/ethereum.tsx +53 -0
  83. package/src/components/payment-method/form.tsx +54 -0
  84. package/src/components/payment-method/stripe.tsx +45 -0
  85. package/src/components/portal/invoice/list.tsx +1 -1
  86. package/src/components/portal/subscription/list.tsx +1 -1
  87. package/src/components/price/currency-select.tsx +53 -0
  88. package/src/components/price/form.tsx +118 -24
  89. package/src/components/product/add-price.tsx +1 -1
  90. package/src/components/product/edit-price.tsx +6 -2
  91. package/src/components/subscription/items/index.tsx +7 -6
  92. package/src/components/subscription/items/usage-records.tsx +98 -0
  93. package/src/components/subscription/list.tsx +3 -2
  94. package/src/components/subscription/status.tsx +68 -0
  95. package/src/contexts/settings.tsx +2 -2
  96. package/src/env.d.ts +2 -0
  97. package/src/libs/util.ts +116 -21
  98. package/src/locales/en.tsx +72 -3
  99. package/src/pages/admin/billing/invoices/detail.tsx +5 -2
  100. package/src/pages/admin/billing/subscriptions/detail.tsx +6 -6
  101. package/src/pages/admin/customers/customers/detail.tsx +43 -9
  102. package/src/pages/admin/payments/intents/detail.tsx +8 -3
  103. package/src/pages/admin/payments/links/create.tsx +23 -3
  104. package/src/pages/admin/payments/links/detail.tsx +13 -26
  105. package/src/pages/admin/products/prices/detail.tsx +55 -11
  106. package/src/pages/admin/products/prices/list.tsx +7 -1
  107. package/src/pages/admin/products/products/create.tsx +1 -1
  108. package/src/pages/admin/products/products/detail.tsx +14 -7
  109. package/src/pages/admin/settings/index.tsx +16 -6
  110. package/src/pages/admin/settings/payment-methods/create.tsx +81 -0
  111. package/src/pages/admin/settings/{payment-methods.tsx → payment-methods/index.tsx} +9 -6
  112. package/src/pages/checkout/pay.tsx +3 -1
  113. package/src/pages/customer/index.tsx +36 -1
  114. package/public/.gitkeep +0 -0
@@ -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
  ])}
@@ -26,8 +26,8 @@ export default function RenamePaymentLink({
26
26
  });
27
27
 
28
28
  const { handleSubmit, reset } = methods;
29
- const onSubmit = async (data: any) => {
30
- await handleSubmit(onSave)(data);
29
+ const onSubmit = async () => {
30
+ await handleSubmit(onSave)();
31
31
  reset();
32
32
  onCancel(null);
33
33
  };
@@ -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
+ }
@@ -0,0 +1,54 @@
1
+ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
2
+ import { Stack, ToggleButton, ToggleButtonGroup, Typography } from '@mui/material';
3
+ import { styled } from '@mui/system';
4
+ import { Controller, useFormContext, useWatch } from 'react-hook-form';
5
+
6
+ import ArcBlockMethodForm from './arcblock';
7
+ import BitcoinMethodForm from './bitcoin';
8
+ import EthereumMethodForm from './ethereum';
9
+ import StripeMethodForm from './stripe';
10
+
11
+ export default function PaymentMethodForm() {
12
+ const { t } = useLocaleContext();
13
+ const { control, setValue } = useFormContext();
14
+
15
+ const type = useWatch({ control, name: 'type' });
16
+
17
+ return (
18
+ <Root direction="column" alignItems="flex-start" spacing={2}>
19
+ <Controller
20
+ name="type"
21
+ control={control}
22
+ render={({ field }) => (
23
+ <ToggleButtonGroup {...field} onChange={(_, value: string) => setValue(field.name, value)} exclusive>
24
+ <ToggleButton value="arcblock">ArcBlock</ToggleButton>
25
+ <ToggleButton value="stripe">Stripe</ToggleButton>
26
+ <ToggleButton value="ethereum" disabled>
27
+ Ethereum
28
+ </ToggleButton>
29
+ <ToggleButton value="bitcoin" disabled>
30
+ Bitcoin
31
+ </ToggleButton>
32
+ </ToggleButtonGroup>
33
+ )}
34
+ />
35
+ <Typography variant="h6" sx={{ mb: 3, fontWeight: 600 }}>
36
+ {t('admin.paymentMethod.settings')}
37
+ </Typography>
38
+ {type === 'stripe' && <StripeMethodForm />}
39
+ {type === 'arcblock' && <ArcBlockMethodForm />}
40
+ {type === 'ethereum' && <EthereumMethodForm />}
41
+ {type === 'bitcoin' && <BitcoinMethodForm />}
42
+ </Root>
43
+ );
44
+ }
45
+
46
+ const Root = styled(Stack)`
47
+ select {
48
+ border: none;
49
+ &:active,
50
+ &:focus {
51
+ border: none;
52
+ }
53
+ }
54
+ `;
@@ -0,0 +1,45 @@
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 StripeMethodForm() {
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="publishable_key"
29
+ name="settings.stripe.publishable_key"
30
+ type="text"
31
+ rules={{ required: true }}
32
+ label={t('admin.paymentMethod.stripe.publishable_key.label')}
33
+ placeholder={t('admin.paymentMethod.stripe.publishable_key.tip')}
34
+ />
35
+ <FormInput
36
+ key="secret_key"
37
+ name="settings.stripe.secret_key"
38
+ type="password"
39
+ rules={{ required: true }}
40
+ label={t('admin.paymentMethod.stripe.secret_key.label')}
41
+ placeholder={t('admin.paymentMethod.stripe.secret_key.tip')}
42
+ />
43
+ </>
44
+ );
45
+ }
@@ -43,7 +43,7 @@ export default function CustomerInvoiceList({ customer_id }: Props) {
43
43
  const { data, loadMore, loadingMore, loading } = useInfiniteScroll<Paginated<TInvoiceExpanded>>(
44
44
  (d) => {
45
45
  const page = d ? Math.ceil(d.list.length / pageSize) + 1 : 1;
46
- return fetchData({ page, size: pageSize, status: 'open,paid', customer_id });
46
+ return fetchData({ page, pageSize, status: 'open,paid', customer_id });
47
47
  },
48
48
  {
49
49
  reloadDeps: [customer_id],
@@ -41,7 +41,7 @@ export function CurrentSubscriptionsInner({ id, onChange }: Props) {
41
41
  const { data, loadMore, loadingMore, loading } = useInfiniteScroll<Paginated<TSubscriptionExpanded>>(
42
42
  (d) => {
43
43
  const page = d ? Math.ceil(d.list.length / pageSize) + 1 : 1;
44
- return fetchData({ page, size: pageSize, status: 'active,trialing,paused', customer_id: id });
44
+ return fetchData({ page, pageSize, status: 'active,trialing,paused', customer_id: id });
45
45
  },
46
46
  {
47
47
  reloadDeps: [id],
@@ -0,0 +1,53 @@
1
+ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
2
+ import { AddOutlined } from '@mui/icons-material';
3
+ import { ListSubheader, MenuItem, Select, Stack, Typography } from '@mui/material';
4
+ import { useState } from 'react';
5
+ import type { LiteralUnion } from 'type-fest';
6
+
7
+ import { useSettingsContext } from '../../contexts/settings';
8
+ import { getSupportedPaymentMethods } from '../../libs/util';
9
+ import Currency from '../currency';
10
+
11
+ type Props = {
12
+ mode: LiteralUnion<'waiting' | 'selecting', string>;
13
+ hasSelected: (currency: any) => boolean;
14
+ onSelect: (currencyId: string) => void;
15
+ };
16
+
17
+ export default function CurrencySelect({ mode: initialMode, hasSelected, onSelect }: Props) {
18
+ const { t } = useLocaleContext();
19
+ const { settings } = useSettingsContext();
20
+ const [mode, setMode] = useState(initialMode);
21
+
22
+ const handleSelect = (e: any) => {
23
+ setMode('waiting');
24
+ onSelect(e.target.value);
25
+ };
26
+
27
+ if (mode === 'selecting') {
28
+ return (
29
+ <Select value="" sx={{ width: 260 }} size="small" onChange={handleSelect}>
30
+ {getSupportedPaymentMethods(settings.paymentMethods, hasSelected).map((method) => [
31
+ <ListSubheader key={method.id} sx={{ fontSize: '1rem', color: 'text.secondary', lineHeight: '2.5rem' }}>
32
+ {method.name}
33
+ </ListSubheader>,
34
+ ...method.payment_currencies.map((currency) => (
35
+ <MenuItem key={currency.id} sx={{ pl: 3 }} value={currency.id}>
36
+ <Stack direction="row" justifyContent="space-between" sx={{ width: '100%' }}>
37
+ <Currency logo={currency.logo} name={currency.name} />
38
+ <Typography fontWeight="bold">{currency.symbol}</Typography>
39
+ </Stack>
40
+ </MenuItem>
41
+ )),
42
+ ])}
43
+ </Select>
44
+ );
45
+ }
46
+
47
+ return (
48
+ <Stack sx={{ cursor: 'pointer' }} direction="row" alignItems="center" onClick={() => setMode('selecting')}>
49
+ <AddOutlined color="primary" />
50
+ <Typography color="primary">{t('admin.price.currency.add')}</Typography>
51
+ </Stack>
52
+ );
53
+ }