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
@@ -5,14 +5,16 @@ import Toast from '@arcblock/ux/lib/Toast';
5
5
  import type { InferFormType, TPaymentLink } from '@did-pay/types';
6
6
  import { AddOutlined } from '@mui/icons-material';
7
7
  import { Button, Stack, Typography } from '@mui/material';
8
- import { useState } from 'react';
8
+ import { useEffect, useState } from 'react';
9
9
  import { FormProvider, useForm } from 'react-hook-form';
10
10
  import { dispatch } from 'use-bus';
11
11
 
12
12
  import DrawerForm from '../../../../components/drawer-form';
13
13
  import AfterPay from '../../../../components/payment-link/after-pay';
14
14
  import BeforePay from '../../../../components/payment-link/before-pay';
15
+ import PaymentLinkPreview from '../../../../components/payment-link/preview';
15
16
  import { ProductsProvider } from '../../../../contexts/products';
17
+ import { useSessionContext } from '../../../../contexts/session';
16
18
  import { useSettingsContext } from '../../../../contexts/settings';
17
19
  import api from '../../../../libs/api';
18
20
  import { formatError } from '../../../../libs/util';
@@ -23,7 +25,9 @@ type PaymentLink = InferFormType<TPaymentLink> & {
23
25
 
24
26
  export default function CreatePaymentLink() {
25
27
  const { t } = useLocaleContext();
28
+ const { session } = useSessionContext();
26
29
  const [current, setCurrent] = useState('beforePay');
30
+ const [stashed, setStashed] = useState(0);
27
31
  const { settings } = useSettingsContext();
28
32
 
29
33
  const methods = useForm<PaymentLink>({
@@ -65,6 +69,22 @@ export default function CreatePaymentLink() {
65
69
  },
66
70
  });
67
71
 
72
+ const changes = methods.watch([
73
+ 'line_items',
74
+ 'allow_promotion_codes',
75
+ 'include_free_trial',
76
+ 'subscription_data',
77
+ 'billing_address_collection',
78
+ 'phone_number_collection',
79
+ ]);
80
+
81
+ useEffect(() => {
82
+ api.post('/api/payment-links/stash', methods.getValues()).then(() => {
83
+ setStashed(stashed + 1);
84
+ });
85
+ // eslint-disable-next-line react-hooks/exhaustive-deps
86
+ }, [JSON.stringify(changes)]);
87
+
68
88
  const tabs = [
69
89
  { label: t('admin.paymentLink.beforePay'), value: 'beforePay', component: BeforePay },
70
90
  { label: t('admin.paymentLink.afterPay'), value: 'afterPay', component: AfterPay },
@@ -104,7 +124,7 @@ export default function CreatePaymentLink() {
104
124
  <DrawerForm
105
125
  icon={<AddOutlined />}
106
126
  text={t('admin.paymentLink.add')}
107
- maxWidth={1280}
127
+ width={1280}
108
128
  addons={
109
129
  // @ts-ignore
110
130
  <Button variant="contained" size="small" onClick={methods.handleSubmit(onSubmit)}>
@@ -132,7 +152,7 @@ export default function CreatePaymentLink() {
132
152
  <Typography variant="h6" sx={{ mb: 2, fontWeight: 600 }}>
133
153
  {t('common.preview')}
134
154
  </Typography>
135
- <pre>FIXME</pre>
155
+ {stashed && <PaymentLinkPreview id={`plink_${session.user.did}`} version={stashed} />}
136
156
  </Stack>
137
157
  </Stack>
138
158
  </FormProvider>
@@ -6,7 +6,6 @@ import { ArrowBackOutlined, Edit } from '@mui/icons-material';
6
6
  import { Alert, Box, Button, CircularProgress, Grid, Stack, Typography } from '@mui/material';
7
7
  import { styled } from '@mui/system';
8
8
  import { useRequest, useSetState } from 'ahooks';
9
- import IframeResizer from 'iframe-resizer-react';
10
9
  import { isEmpty } from 'lodash';
11
10
  import { Link, useNavigate } from 'react-router-dom';
12
11
  import { joinURL } from 'ufo';
@@ -17,6 +16,7 @@ import InfoCard from '../../../../components/info-card';
17
16
  import InfoRow from '../../../../components/info-row';
18
17
  import MetadataEditor from '../../../../components/metadata/editor';
19
18
  import PaymentLinkActions from '../../../../components/payment-link/actions';
19
+ import PaymentLinkPreview from '../../../../components/payment-link/preview';
20
20
  import AddPrice from '../../../../components/product/add-price';
21
21
  import SectionHeader from '../../../../components/section/header';
22
22
  import Table from '../../../../components/table';
@@ -216,24 +216,32 @@ export default function PaymentLinkDetail(props: { id: string }) {
216
216
  </SectionHeader>
217
217
  <Stack>
218
218
  <InfoRow
219
+ sizes={[1, 1]}
219
220
  label={t('admin.paymentLink.allowPromotionCodes')}
220
221
  value={data.allow_promotion_codes ? 'Yes' : 'No'}
221
222
  />
222
223
  <InfoRow
224
+ sizes={[1, 1]}
223
225
  label={t('admin.paymentLink.requireBillingAddress')}
224
226
  value={data.billing_address_collection ? 'Yes' : 'No'}
225
227
  />
226
228
  <InfoRow
229
+ sizes={[1, 1]}
227
230
  label={t('admin.paymentLink.requirePhoneNumber')}
228
231
  value={data.phone_number_collection?.enabled ? 'Yes' : 'No'}
229
232
  />
230
233
  <InfoRow
234
+ sizes={[1, 1]}
231
235
  label={t('admin.paymentLink.includeFreeTrail')}
232
236
  value={data.subscription_data?.trial_period_days ? 'Yes' : 'No'}
233
237
  />
234
- <InfoRow label={t('admin.paymentLink.showConfirmPage')} value={data.after_completion?.type} />
235
- <InfoRow label={t('common.createdAt')} value={formatTime(data.created_at)} />
236
- <InfoRow label={t('common.updatedAt')} value={formatTime(data.updated_at)} />
238
+ <InfoRow
239
+ sizes={[1, 1]}
240
+ label={t('admin.paymentLink.showConfirmPage')}
241
+ value={data.after_completion?.type}
242
+ />
243
+ <InfoRow sizes={[1, 1]} label={t('common.createdAt')} value={formatTime(data.created_at)} />
244
+ <InfoRow sizes={[1, 1]} label={t('common.updatedAt')} value={formatTime(data.updated_at)} />
237
245
  </Stack>
238
246
  </Box>
239
247
  <Box className="section">
@@ -285,19 +293,7 @@ export default function PaymentLinkDetail(props: { id: string }) {
285
293
  <Box className="section">
286
294
  <SectionHeader title={t('common.preview')} />
287
295
  <Box className="section-body">
288
- <Chrome>
289
- <IframeResizer
290
- style={{
291
- width: '1px',
292
- minWidth: '840px',
293
- minHeight: '840px',
294
- transform: 'scale(0.65)',
295
- transformOrigin: '20% 10%',
296
- border: 'none',
297
- }}
298
- src={`${window.blocklet.prefix}checkout/pay/${data.id}?preview=1`}
299
- />
300
- </Chrome>
296
+ <PaymentLinkPreview id={data.id} />
301
297
  </Box>
302
298
  </Box>
303
299
  </Div>
@@ -307,12 +303,3 @@ export default function PaymentLinkDetail(props: { id: string }) {
307
303
  }
308
304
 
309
305
  const Div = styled(Stack)``;
310
-
311
- const Chrome = styled(Box)`
312
- background-color: #fcfeff;
313
- border-radius: 8px;
314
- margin-top: 40px;
315
- position: relative;
316
- overflow: hidden;
317
- box-shadow: 0 20px 44px #32325d1f, 0 -1px 32px #32325d0f, 0 3px 12px #00000014;
318
- `;
@@ -16,6 +16,7 @@ import InfoRow from '../../../../components/info-row';
16
16
  import MetadataEditor from '../../../../components/metadata/editor';
17
17
  import EditPrice from '../../../../components/product/edit-price';
18
18
  import SectionHeader from '../../../../components/section/header';
19
+ import Table from '../../../../components/table';
19
20
  import api from '../../../../libs/api';
20
21
  import { formatError, formatPrice, formatTime } from '../../../../libs/util';
21
22
  import PriceActions from './actions';
@@ -77,15 +78,20 @@ export default function PriceDetail(props: { id: string }) {
77
78
 
78
79
  return (
79
80
  <Root direction="column" spacing={4} sx={{ mb: 4 }}>
80
- {data.active === false && (
81
- <Alert severity="warning">
82
- <AlertTitle>{t('admin.price.archived')}</AlertTitle>
83
- {t('admin.price.archivedTip')}
84
- </Alert>
85
- )}
86
81
  <Box>
82
+ {data.active === false && (
83
+ <Alert severity="warning">
84
+ <AlertTitle>{t('admin.price.archived')}</AlertTitle>
85
+ {t('admin.price.archivedTip')}
86
+ </Alert>
87
+ )}
88
+ {data.locked && (
89
+ <Alert severity="warning" sx={{ mb: 1 }}>
90
+ {t('admin.price.locked')}
91
+ </Alert>
92
+ )}
87
93
  <Stack className="page-header" direction="row" justifyContent="space-between" alignItems="center">
88
- <Link to="/admin/products">
94
+ <Link to={`/admin/products/${data.product_id}`}>
89
95
  <Stack direction="row" alignItems="center" sx={{ fontWeight: 'normal' }}>
90
96
  <ArrowBackOutlined fontSize="small" sx={{ mr: 0.5, color: 'text.secondary' }} />
91
97
  <Typography variant="h6" sx={{ color: 'text.secondary', fontWeight: 'normal' }}>
@@ -137,10 +143,6 @@ export default function PriceDetail(props: { id: string }) {
137
143
  <InfoRow label={t('admin.price.recurring.interval')} value={data.recurring?.interval} />
138
144
  <InfoRow label={t('admin.price.nickname.label')} value={data.nickname} />
139
145
  <InfoRow label={t('admin.price.lookupKey')} value={data.lookup_key} />
140
- <InfoRow
141
- label={t('admin.paymentCurrency.name')}
142
- value={<Currency logo={data.currency.logo} name={data.currency.symbol} />}
143
- />
144
146
  <InfoRow label={t('common.createdAt')} value={formatTime(data.created_at)} />
145
147
  <InfoRow label={t('common.updatedAt')} value={formatTime(data.updated_at)} />
146
148
  </Grid>
@@ -190,6 +192,48 @@ export default function PriceDetail(props: { id: string }) {
190
192
  )}
191
193
  </Box>
192
194
  </Box>
195
+ <Box className="section">
196
+ <SectionHeader title={t('admin.price.currency.list')} />
197
+ <Box className="section-body">
198
+ <Table
199
+ toolbar={false}
200
+ footer={false}
201
+ data={data.currency_options}
202
+ columns={[
203
+ {
204
+ label: t('admin.price.name'),
205
+ name: 'currency_id',
206
+ width: 200,
207
+ options: {
208
+ // eslint-disable-next-line react/no-unstable-nested-components
209
+ customBodyRenderLite: (_: any, index: number) => {
210
+ const item = data.currency_options[index] as any;
211
+ return <Currency logo={item.currency.logo} name={item.currency.symbol} />;
212
+ },
213
+ },
214
+ },
215
+ {
216
+ label: t('common.id'),
217
+ name: 'unit_amount',
218
+ options: {
219
+ customBodyRenderLite: (_: any, index: number) => {
220
+ const item = data.currency_options[index] as any;
221
+ return formatPrice(
222
+ { type: data.type, unit_amount: item.unit_amount, recurring: data.recurring } as TPrice,
223
+ item.currency
224
+ );
225
+ },
226
+ },
227
+ },
228
+ ]}
229
+ options={{
230
+ count: data.currency_options.length,
231
+ page: 0,
232
+ rowsPerPage: 20,
233
+ }}
234
+ />
235
+ </Box>
236
+ </Box>
193
237
  <Box className="section">
194
238
  <SectionHeader title={t('admin.events')} />
195
239
  <Box className="section-body">
@@ -1,6 +1,7 @@
1
1
  /* eslint-disable react/no-unstable-nested-components */
2
2
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
3
- import { Stack, Typography } from '@mui/material';
3
+ import { LockOutlined } from '@mui/icons-material';
4
+ import { Stack, Tooltip, Typography } from '@mui/material';
4
5
  import { Link } from 'react-router-dom';
5
6
 
6
7
  import Copyable from '../../../../components/copyable';
@@ -26,6 +27,11 @@ export default function PricesList({ product, onChange }: { product: Product; on
26
27
  return (
27
28
  <Link to={`/admin/products/${price.id}`} color="text.primary">
28
29
  <Stack direction="row" alignItems="center" spacing={1}>
30
+ {price.locked && (
31
+ <Tooltip title={t('admin.price.locked')}>
32
+ <LockOutlined sx={{ color: 'text.secondary' }} />
33
+ </Tooltip>
34
+ )}
29
35
  <Typography component="span">{formatPrice(price, settings.baseCurrency)}</Typography>
30
36
  <Typography component="span">
31
37
  {price.id === product.default_price_id && <Status label="default" color="info" sx={{ height: 18 }} />}
@@ -58,7 +58,7 @@ export default function ProductsCreate() {
58
58
  <DrawerForm
59
59
  icon={<AddOutlined />}
60
60
  text={t('admin.product.add')}
61
- maxWidth={640}
61
+ width={640}
62
62
  addons={
63
63
  <Button variant="contained" size="small" onClick={handleSubmit(onSubmit)}>
64
64
  {t('admin.product.save')}
@@ -94,15 +94,22 @@ export default function ProductDetail(props: { id: string }) {
94
94
  }
95
95
  };
96
96
 
97
+ const isLocked = data.locked || data.prices.some((x) => x.locked);
98
+
97
99
  return (
98
100
  <Root direction="column" spacing={4} sx={{ mb: 4 }}>
99
- {data.active === false && (
100
- <Alert severity="warning">
101
- <AlertTitle>{t('admin.product.archived')}</AlertTitle>
102
- {t('admin.product.archivedTip')}
103
- </Alert>
104
- )}
105
101
  <Box>
102
+ {data.active === false && (
103
+ <Alert severity="warning">
104
+ <AlertTitle>{t('admin.product.archived')}</AlertTitle>
105
+ {t('admin.product.archivedTip')}
106
+ </Alert>
107
+ )}
108
+ {isLocked && (
109
+ <Alert severity="warning" sx={{ mb: 1 }}>
110
+ {t('admin.product.locked')}
111
+ </Alert>
112
+ )}
106
113
  <Stack className="page-header" direction="row" justifyContent="space-between" alignItems="center">
107
114
  <Link to="/admin/products">
108
115
  <Stack direction="row" alignItems="center" sx={{ fontWeight: 'normal' }}>
@@ -236,7 +243,7 @@ export default function ProductDetail(props: { id: string }) {
236
243
  <Box className="section">
237
244
  <SectionHeader title={t('admin.events')} />
238
245
  <Box className="section-body">
239
- <EventList features={{ toolbar: false }} object_id={data.id} />
246
+ <EventList features={{ toolbar: false }} object_id={[data.id, ...data.prices.map((x) => x.id)].join(',')} />
240
247
  </Box>
241
248
  </Box>
242
249
  </Root>
@@ -1,9 +1,11 @@
1
1
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
2
2
  import Tabs from '@arcblock/ux/lib/Tabs';
3
- import { Typography } from '@mui/material';
3
+ import { Stack, Typography } from '@mui/material';
4
4
  import React, { isValidElement } from 'react';
5
5
  import { useNavigate, useParams } from 'react-router-dom';
6
6
 
7
+ const PaymentMethodCreate = React.lazy(() => import('./payment-methods/create'));
8
+
7
9
  const pages = {
8
10
  paymentMethods: React.lazy(() => import('./payment-methods')),
9
11
  branding: React.lazy(() => import('./branding')),
@@ -27,13 +29,21 @@ export default function SettingsIndex() {
27
29
  { label: t('admin.business'), value: 'business' },
28
30
  ];
29
31
 
32
+ let extra = null;
33
+ if (page === 'payment-methods') {
34
+ extra = <PaymentMethodCreate />;
35
+ }
36
+
30
37
  return (
31
- <div>
32
- <Typography variant="h5" sx={{ mb: 1, fontWeight: 600 }}>
33
- {t('admin.settings')}
34
- </Typography>
38
+ <>
39
+ <Stack direction="row" alignItems="center" justifyContent="space-between">
40
+ <Typography variant="h5" sx={{ mb: 1, fontWeight: 600 }}>
41
+ {t('admin.products')}
42
+ </Typography>
43
+ {extra}
44
+ </Stack>
35
45
  <Tabs tabs={tabs} current={page} onChange={onTabChange} scrollButtons="auto" />
36
46
  {isValidElement(TabComponent) ? TabComponent : <TabComponent />}
37
- </div>
47
+ </>
38
48
  );
39
49
  }
@@ -0,0 +1,81 @@
1
+ /* eslint-disable no-nested-ternary */
2
+ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
3
+ import Toast from '@arcblock/ux/lib/Toast';
4
+ import type { TPaymentMethod } from '@did-pay/types';
5
+ import { AddOutlined } from '@mui/icons-material';
6
+ import { Button, Typography } from '@mui/material';
7
+ import { FormProvider, useForm } from 'react-hook-form';
8
+ import { dispatch } from 'use-bus';
9
+
10
+ import DrawerForm from '../../../../components/drawer-form';
11
+ import PaymentMethodForm from '../../../../components/payment-method/form';
12
+ import api from '../../../../libs/api';
13
+ import { formatError } from '../../../../libs/util';
14
+
15
+ export default function PaymentMethodCreate() {
16
+ const { t } = useLocaleContext();
17
+
18
+ const methods = useForm<TPaymentMethod>({
19
+ defaultValues: {
20
+ type: 'stripe',
21
+ name: '',
22
+ description: '',
23
+ settings: {
24
+ arcblock: {
25
+ chain_id: '',
26
+ api_host: '',
27
+ explorer_host: '',
28
+ },
29
+ stripe: {
30
+ publishable_key: '',
31
+ secret_key: '',
32
+ },
33
+ ethereum: {
34
+ chain_id: 0,
35
+ api_host: '',
36
+ explorer_host: '',
37
+ },
38
+ bitcoin: {
39
+ chain_id: 0,
40
+ api_host: '',
41
+ explorer_host: '',
42
+ },
43
+ },
44
+ },
45
+ });
46
+ const { handleSubmit } = methods;
47
+
48
+ const onSubmit = (data: TPaymentMethod) => {
49
+ api
50
+ .post('/api/payment-methods', data)
51
+ .then(() => {
52
+ Toast.success(t('admin.paymentMethod.saved'));
53
+ methods.reset();
54
+ dispatch('drawer.submitted');
55
+ dispatch('paymentMethod.created');
56
+ })
57
+ .catch((err) => {
58
+ console.error(err);
59
+ Toast.error(formatError(err));
60
+ });
61
+ };
62
+
63
+ return (
64
+ <DrawerForm
65
+ icon={<AddOutlined />}
66
+ text={t('admin.paymentMethod.add')}
67
+ width={640}
68
+ addons={
69
+ <Button variant="contained" size="small" onClick={handleSubmit(onSubmit)}>
70
+ {t('admin.paymentMethod.save')}
71
+ </Button>
72
+ }>
73
+ <FormProvider {...methods}>
74
+ <Typography variant="h6" sx={{ mb: 3, fontWeight: 600 }}>
75
+ {t('admin.paymentMethod.type')}
76
+ </Typography>
77
+ <PaymentMethodForm />
78
+ </FormProvider>
79
+ </DrawerForm>
80
+ );
81
+ }
@@ -1,12 +1,13 @@
1
1
  import type { TPaymentMethodExpanded } from '@did-pay/types';
2
2
  import { Alert, Box, CircularProgress, Grid, Typography } from '@mui/material';
3
3
  import { useRequest } from 'ahooks';
4
+ import useBus from 'use-bus';
4
5
 
5
- import IconCollapse from '../../../components/collapse';
6
- import InfoCard from '../../../components/info-card';
7
- import InfoRow from '../../../components/info-row';
8
- import Switch from '../../../components/switch';
9
- import api from '../../../libs/api';
6
+ import IconCollapse from '../../../../components/collapse';
7
+ import InfoCard from '../../../../components/info-card';
8
+ import InfoRow from '../../../../components/info-row';
9
+ import Switch from '../../../../components/switch';
10
+ import api from '../../../../libs/api';
10
11
 
11
12
  const getMethods = (params: Record<string, any> = {}): Promise<TPaymentMethodExpanded[]> => {
12
13
  const search = new URLSearchParams();
@@ -27,7 +28,9 @@ const groupByType = (methods: TPaymentMethodExpanded[]) => {
27
28
  };
28
29
 
29
30
  export default function PaymentMethods() {
30
- const { loading, error, data } = useRequest(() => getMethods({}));
31
+ const { loading, error, data, runAsync } = useRequest(() => getMethods({}));
32
+
33
+ useBus('paymentMethod.created', runAsync, []);
31
34
 
32
35
  if (error) {
33
36
  return <Alert severity="error">{error.message}</Alert>;
@@ -62,8 +62,10 @@ export default function Payment({ id }: Props) {
62
62
  }
63
63
  } else if (data?.checkoutSession?.success_url) {
64
64
  setTimeout(() => {
65
+ const tmp = new URL(data.checkoutSession.success_url as string);
66
+ tmp.searchParams.set('checkout_session_id', data.checkoutSession.id);
65
67
  // @ts-ignore
66
- window.location.href = data?.checkoutSession?.success_url;
68
+ window.location.href = tmp.href;
67
69
  }, 1000);
68
70
  }
69
71
  };
@@ -1,10 +1,13 @@
1
1
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
2
+ import Toast from '@arcblock/ux/lib/Toast';
2
3
  import type { TCustomerExpanded } from '@did-pay/types';
3
4
  import { Edit } from '@mui/icons-material';
4
5
  import { Alert, Box, Button, CircularProgress, Grid, Stack } from '@mui/material';
5
6
  import { styled } from '@mui/system';
6
7
  import { useRequest, useSetState } from 'ahooks';
8
+ import { FlagEmoji } from 'react-international-phone';
7
9
 
10
+ import EditCustomer from '../../components/customer/edit';
8
11
  import InfoRow from '../../components/info-row';
9
12
  import Layout from '../../components/layout';
10
13
  import CustomerInvoiceList from '../../components/portal/invoice/list';
@@ -44,6 +47,20 @@ export default function CustomerHome() {
44
47
  );
45
48
  }
46
49
 
50
+ const onUpdateInfo = async (updates: TCustomerExpanded) => {
51
+ try {
52
+ setState({ loading: true });
53
+ await api.put(`/api/customers/${data.id}`, updates).then((res) => res.data);
54
+ Toast.success(t('common.saved'));
55
+ runAsync();
56
+ } catch (err) {
57
+ console.error(err);
58
+ Toast.error(formatError(err));
59
+ } finally {
60
+ setState({ loading: false });
61
+ }
62
+ };
63
+
47
64
  return (
48
65
  <Layout>
49
66
  <Grid container spacing={5}>
@@ -81,7 +98,17 @@ export default function CustomerHome() {
81
98
  <InfoRow sizes={[1, 2]} label={t('admin.customer.name')} value={data.name} />
82
99
  <InfoRow sizes={[1, 2]} label={t('admin.customer.phone')} value={data.phone} />
83
100
  <InfoRow sizes={[1, 2]} label={t('admin.customer.email')} value={data.email} />
84
- <InfoRow sizes={[1, 2]} label={t('admin.customer.address.country')} value={data.address?.country} />
101
+ <InfoRow
102
+ sizes={[1, 2]}
103
+ label={t('admin.customer.address.country')}
104
+ value={
105
+ data.address?.country ? (
106
+ <FlagEmoji iso2={data.address?.country} style={{ display: 'flex', width: 24 }} />
107
+ ) : (
108
+ ''
109
+ )
110
+ }
111
+ />
85
112
  <InfoRow sizes={[1, 2]} label={t('admin.customer.address.state')} value={data.address?.state} />
86
113
  <InfoRow sizes={[1, 2]} label={t('admin.customer.address.city')} value={data.address?.city} />
87
114
  <InfoRow sizes={[1, 2]} label={t('admin.customer.address.line1')} value={data.address?.line1} />
@@ -92,6 +119,14 @@ export default function CustomerHome() {
92
119
  value={data.address?.postal_code}
93
120
  />
94
121
  </Stack>
122
+ {state.editing && (
123
+ <EditCustomer
124
+ data={data}
125
+ loading={state.loading}
126
+ onSave={onUpdateInfo}
127
+ onCancel={() => setState({ editing: false })}
128
+ />
129
+ )}
95
130
  </Box>
96
131
  </Root>
97
132
  </Grid>
package/public/.gitkeep DELETED
File without changes