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
@@ -1,4 +1,5 @@
1
1
  /* eslint-disable react/no-unstable-nested-components */
2
+ import DidAddress from '@arcblock/ux/lib/DID';
2
3
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
3
4
  import Toast from '@arcblock/ux/lib/Toast';
4
5
  import type { TCustomerExpanded } from '@did-pay/types';
@@ -7,6 +8,7 @@ import { Alert, Box, Button, CircularProgress, Stack, Typography } from '@mui/ma
7
8
  import { styled } from '@mui/system';
8
9
  import { useRequest, useSetState } from 'ahooks';
9
10
  import { isEmpty } from 'lodash';
11
+ import { FlagEmoji } from 'react-international-phone';
10
12
  import { Link, useNavigate } from 'react-router-dom';
11
13
 
12
14
  import Copyable from '../../../../components/copyable';
@@ -126,6 +128,7 @@ export default function CustomerDetail(props: { id: string }) {
126
128
  </Button>
127
129
  </SectionHeader>
128
130
  <Stack>
131
+ <InfoRow label={t('common.did')} value={<DidAddress did={data.did} />} />
129
132
  <InfoRow label={t('admin.customer.name')} value={data.name} />
130
133
  <InfoRow label={t('admin.customer.phone')} value={data.phone} />
131
134
  <InfoRow label={t('admin.customer.email')} value={data.email} />
@@ -136,7 +139,16 @@ export default function CustomerDetail(props: { id: string }) {
136
139
  label={t('admin.customer.address.label')}
137
140
  value={
138
141
  <Stack direction="column">
139
- <InfoRow label={t('admin.customer.address.country')} value={data.address?.country} />
142
+ <InfoRow
143
+ label={t('admin.customer.address.country')}
144
+ value={
145
+ data.address?.country ? (
146
+ <FlagEmoji iso2={data.address?.country} style={{ display: 'flex', width: 24 }} />
147
+ ) : (
148
+ ''
149
+ )
150
+ }
151
+ />
140
152
  <InfoRow label={t('admin.customer.address.state')} value={data.address?.state} />
141
153
  <InfoRow label={t('admin.customer.address.city')} value={data.address?.city} />
142
154
  <InfoRow label={t('admin.customer.address.line1')} value={data.address?.line1} />
@@ -127,12 +127,12 @@ export default function PaymentIntentDetail(props: { id: string }) {
127
127
  </Stack>
128
128
  </Box>
129
129
  <Box className="section">
130
- <SectionHeader title={t('admin.paymentMethod.name')} />
130
+ <SectionHeader title={t('admin.paymentMethod._name')} />
131
131
  <Stack>
132
132
  <InfoRow label={t('common.id')} value={data.paymentMethod.id} />
133
133
  <InfoRow label={t('admin.paymentMethod.type')} value={data.paymentMethod.type} />
134
134
  <InfoRow
135
- label={t('admin.paymentMethod.name')}
135
+ label={t('admin.paymentMethod._name')}
136
136
  value={<Currency logo={data.paymentMethod.logo} name={data.paymentMethod.name} />}
137
137
  />
138
138
  <InfoRow
@@ -141,7 +141,12 @@ export default function PaymentIntentDetail(props: { id: string }) {
141
141
  />
142
142
  <InfoRow
143
143
  label={t('common.txHash')}
144
- value={<TxLink hash={data.payment_details?.tx_hash || data.metadata?.txHash} method={data.paymentMethod} />}
144
+ value={
145
+ <TxLink
146
+ hash={data.payment_details?.arcblock?.tx_hash || data.metadata?.txHash}
147
+ method={data.paymentMethod}
148
+ />
149
+ }
145
150
  />
146
151
  </Stack>
147
152
  </Box>
@@ -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
  };
@@ -4,6 +4,7 @@ import { Edit } from '@mui/icons-material';
4
4
  import { Alert, Box, Button, CircularProgress, Grid, Stack } from '@mui/material';
5
5
  import { styled } from '@mui/system';
6
6
  import { useRequest, useSetState } from 'ahooks';
7
+ import { FlagEmoji } from 'react-international-phone';
7
8
 
8
9
  import InfoRow from '../../components/info-row';
9
10
  import Layout from '../../components/layout';
@@ -81,7 +82,17 @@ export default function CustomerHome() {
81
82
  <InfoRow sizes={[1, 2]} label={t('admin.customer.name')} value={data.name} />
82
83
  <InfoRow sizes={[1, 2]} label={t('admin.customer.phone')} value={data.phone} />
83
84
  <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} />
85
+ <InfoRow
86
+ sizes={[1, 2]}
87
+ label={t('admin.customer.address.country')}
88
+ value={
89
+ data.address?.country ? (
90
+ <FlagEmoji iso2={data.address?.country} style={{ display: 'flex', width: 24 }} />
91
+ ) : (
92
+ ''
93
+ )
94
+ }
95
+ />
85
96
  <InfoRow sizes={[1, 2]} label={t('admin.customer.address.state')} value={data.address?.state} />
86
97
  <InfoRow sizes={[1, 2]} label={t('admin.customer.address.city')} value={data.address?.city} />
87
98
  <InfoRow sizes={[1, 2]} label={t('admin.customer.address.line1')} value={data.address?.line1} />
package/public/.gitkeep DELETED
File without changes