payment-kit 1.13.35 → 1.13.36
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.
- package/api/src/routes/checkout-sessions.ts +35 -35
- package/blocklet.yml +1 -1
- package/package.json +3 -3
- package/src/components/checkout/header.tsx +1 -1
- package/src/components/drawer-form.tsx +3 -1
- package/src/components/payment-link/before-pay.tsx +11 -0
- package/src/components/pricing-table/product-settings.tsx +11 -0
- package/src/libs/util.ts +2 -2
- package/src/locales/en.tsx +1 -0
- package/src/locales/zh.tsx +1 -0
- package/src/pages/admin/payments/links/create.tsx +3 -0
- package/src/pages/admin/products/prices/actions.tsx +43 -23
- package/src/pages/admin/products/pricing-tables/create.tsx +3 -0
- package/src/pages/customer/index.tsx +5 -1
|
@@ -205,44 +205,44 @@ router.post('/', auth, async (req, res) => {
|
|
|
205
205
|
});
|
|
206
206
|
|
|
207
207
|
export async function startCheckoutSessionFromPaymentLink(id: string, req: Request, res: Response) {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
208
|
+
try {
|
|
209
|
+
const link = await PaymentLink.findByPk(id);
|
|
210
|
+
if (!link) {
|
|
211
|
+
res.status(400).json({ error: 'Payment link not found, please contact the source of the payment link.' });
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (!link.active) {
|
|
215
|
+
res.status(400).json({ error: 'Payment link archived, we can not create new checkout session.' });
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
217
218
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const raw: Partial<CheckoutSession> = await formatCheckoutSession(link, false);
|
|
221
|
-
raw.livemode = link.livemode;
|
|
222
|
-
raw.created_via = 'portal';
|
|
223
|
-
raw.currency_id = link.currency_id || req.currency.id;
|
|
224
|
-
raw.payment_link_id = link.id;
|
|
225
|
-
|
|
226
|
-
if (link.after_completion?.hosted_confirmation?.custom_message) {
|
|
227
|
-
raw.payment_intent_data = {
|
|
228
|
-
description: link.after_completion?.hosted_confirmation?.custom_message,
|
|
229
|
-
};
|
|
230
|
-
} else {
|
|
231
|
-
raw.payment_intent_data = {
|
|
232
|
-
// TODO: bake default into this
|
|
233
|
-
description: 'Thanks for your purchase',
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
if (link.after_completion?.redirect?.url) {
|
|
237
|
-
raw.success_url = link.after_completion?.redirect?.url;
|
|
238
|
-
}
|
|
219
|
+
const items = await Price.expand(link.line_items, { upsell: true });
|
|
239
220
|
|
|
240
|
-
|
|
241
|
-
raw.
|
|
242
|
-
raw.
|
|
243
|
-
|
|
221
|
+
const raw: Partial<CheckoutSession> = await formatCheckoutSession(link, false);
|
|
222
|
+
raw.livemode = link.livemode;
|
|
223
|
+
raw.created_via = 'portal';
|
|
224
|
+
raw.currency_id = link.currency_id || req.currency.id;
|
|
225
|
+
raw.payment_link_id = link.id;
|
|
226
|
+
|
|
227
|
+
if (link.after_completion?.hosted_confirmation?.custom_message) {
|
|
228
|
+
raw.payment_intent_data = {
|
|
229
|
+
description: link.after_completion?.hosted_confirmation?.custom_message,
|
|
230
|
+
};
|
|
231
|
+
} else {
|
|
232
|
+
raw.payment_intent_data = {
|
|
233
|
+
// TODO: bake default into this
|
|
234
|
+
description: 'Thanks for your purchase',
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
if (link.after_completion?.redirect?.url) {
|
|
238
|
+
raw.success_url = link.after_completion?.redirect?.url;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (req.query.redirect) {
|
|
242
|
+
raw.success_url = req.query.redirect as string;
|
|
243
|
+
raw.cancel_url = req.query.redirect as string;
|
|
244
|
+
}
|
|
244
245
|
|
|
245
|
-
try {
|
|
246
246
|
let doc;
|
|
247
247
|
if (req.query.preview === '1') {
|
|
248
248
|
doc = await CheckoutSession.findOne({ where: { payment_link_id: link.id, metadata: { preview: '1' } } });
|
package/blocklet.yml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payment-kit",
|
|
3
|
-
"version": "1.13.
|
|
3
|
+
"version": "1.13.36",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "blocklet dev",
|
|
6
6
|
"eject": "vite eject",
|
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
"@abtnode/types": "1.16.17-beta-952ef53d",
|
|
104
104
|
"@arcblock/eslint-config": "^0.2.4",
|
|
105
105
|
"@arcblock/eslint-config-ts": "^0.2.4",
|
|
106
|
-
"@did-pay/types": "1.13.
|
|
106
|
+
"@did-pay/types": "1.13.36",
|
|
107
107
|
"@types/cookie-parser": "^1.4.4",
|
|
108
108
|
"@types/cors": "^2.8.14",
|
|
109
109
|
"@types/dotenv-flow": "^3.3.1",
|
|
@@ -140,5 +140,5 @@
|
|
|
140
140
|
"parser": "typescript"
|
|
141
141
|
}
|
|
142
142
|
},
|
|
143
|
-
"gitHead": "
|
|
143
|
+
"gitHead": "9f921f0988c77ca3d0e9c6f383631f6e61fcd32f"
|
|
144
144
|
}
|
|
@@ -15,7 +15,7 @@ export default function PaymentHeader({ checkoutSession }: Props) {
|
|
|
15
15
|
|
|
16
16
|
return (
|
|
17
17
|
<Stack className="cko-header" direction="row" spacing={1} alignItems="center">
|
|
18
|
-
<Avatar src={window.blocklet.appLogo} sx={{ width: 32, height: 32 }} />
|
|
18
|
+
<Avatar variant="square" src={window.blocklet.appLogo} sx={{ width: 32, height: 32 }} />
|
|
19
19
|
<Typography sx={{ color: 'text.primary', fontWeight: 600 }}>{brand}</Typography>
|
|
20
20
|
{!livemode && <Livemode />}
|
|
21
21
|
</Stack>
|
|
@@ -13,16 +13,18 @@ type Props = {
|
|
|
13
13
|
addons: React.ReactNode;
|
|
14
14
|
children: React.ReactNode;
|
|
15
15
|
width?: number;
|
|
16
|
+
open?: boolean;
|
|
16
17
|
style?: Record<string, any>;
|
|
17
18
|
};
|
|
18
19
|
|
|
19
20
|
DrawerForm.defaultProps = {
|
|
20
21
|
style: {},
|
|
22
|
+
open: false,
|
|
21
23
|
width: 960,
|
|
22
24
|
};
|
|
23
25
|
|
|
24
26
|
export default function DrawerForm(props: Props) {
|
|
25
|
-
const [open, setOpen] = useState(
|
|
27
|
+
const [open, setOpen] = useState(props.open);
|
|
26
28
|
const settings = useSettingsContext();
|
|
27
29
|
|
|
28
30
|
useBus('drawer.submitted', () => setOpen(false), []);
|
|
@@ -3,6 +3,7 @@ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
|
3
3
|
import { Checkbox, FormControlLabel, Stack, TextField, Typography } from '@mui/material';
|
|
4
4
|
import { useEffect, useState } from 'react';
|
|
5
5
|
import { Controller, useFieldArray, useFormContext, useWatch } from 'react-hook-form';
|
|
6
|
+
import { useSearchParams } from 'react-router-dom';
|
|
6
7
|
|
|
7
8
|
import { useProductsContext } from '../../contexts/products';
|
|
8
9
|
import { getProductByPriceId, isPriceAligned } from '../../libs/util';
|
|
@@ -12,6 +13,7 @@ import ProductSelect from './product-select';
|
|
|
12
13
|
|
|
13
14
|
export default function BeforePay() {
|
|
14
15
|
const { t } = useLocaleContext();
|
|
16
|
+
const [params, setParams] = useSearchParams();
|
|
15
17
|
const { products, refresh } = useProductsContext();
|
|
16
18
|
const { control, setValue, getValues } = useFormContext();
|
|
17
19
|
const items = useFieldArray({ control, name: 'line_items' });
|
|
@@ -47,6 +49,15 @@ export default function BeforePay() {
|
|
|
47
49
|
}
|
|
48
50
|
};
|
|
49
51
|
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (params.get('price_id') && getValues().line_items.length === 0) {
|
|
54
|
+
onProductSelected(params.get('price_id') as string);
|
|
55
|
+
params.set('price_id', '');
|
|
56
|
+
setParams(params);
|
|
57
|
+
}
|
|
58
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
59
|
+
}, []);
|
|
60
|
+
|
|
50
61
|
const onProductCreated = () => {
|
|
51
62
|
setState({ creating: false });
|
|
52
63
|
refresh();
|
|
@@ -3,6 +3,7 @@ import { Checkbox, FormControlLabel, MenuItem, Select, Stack, Typography } from
|
|
|
3
3
|
import { useSetState } from 'ahooks';
|
|
4
4
|
import { useEffect } from 'react';
|
|
5
5
|
import { Controller, useFieldArray, useFormContext, useWatch } from 'react-hook-form';
|
|
6
|
+
import { useSearchParams } from 'react-router-dom';
|
|
6
7
|
|
|
7
8
|
import { useProductsContext } from '../../contexts/products';
|
|
8
9
|
import { getProductByPriceId, groupPricingTableItems, isPriceCurrencyAligned } from '../../libs/util';
|
|
@@ -12,6 +13,7 @@ import ProductItem from './product-item';
|
|
|
12
13
|
|
|
13
14
|
export default function PricingTableProductSettings() {
|
|
14
15
|
const { t } = useLocaleContext();
|
|
16
|
+
const [params, setParams] = useSearchParams();
|
|
15
17
|
const { products, refresh } = useProductsContext();
|
|
16
18
|
const { control, setValue, getValues } = useFormContext();
|
|
17
19
|
const items = useFieldArray({ control, name: 'items' });
|
|
@@ -79,6 +81,15 @@ export default function PricingTableProductSettings() {
|
|
|
79
81
|
}
|
|
80
82
|
};
|
|
81
83
|
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
if (params.get('price_id') && products.length && getValues().items.length === 0) {
|
|
86
|
+
onProductSelected(params.get('price_id') as string);
|
|
87
|
+
params.set('price_id', '');
|
|
88
|
+
setParams(params);
|
|
89
|
+
}
|
|
90
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
91
|
+
}, [products.length]);
|
|
92
|
+
|
|
82
93
|
const onProductCreated = () => {
|
|
83
94
|
setState({ creating: false });
|
|
84
95
|
refresh();
|
package/src/libs/util.ts
CHANGED
|
@@ -626,8 +626,8 @@ export function formatSubscriptionProduct(items: TSubscriptionItemExpanded[], ma
|
|
|
626
626
|
);
|
|
627
627
|
}
|
|
628
628
|
|
|
629
|
-
export function formatAmount(amount: string, decimals: number
|
|
630
|
-
return
|
|
629
|
+
export function formatAmount(amount: string, decimals: number) {
|
|
630
|
+
return fromUnitToToken(amount, decimals);
|
|
631
631
|
}
|
|
632
632
|
|
|
633
633
|
export function findCurrency(methods: TPaymentMethodExpanded[], currencyId: string) {
|
package/src/locales/en.tsx
CHANGED
package/src/locales/zh.tsx
CHANGED
|
@@ -7,6 +7,7 @@ import { AddOutlined } from '@mui/icons-material';
|
|
|
7
7
|
import { Button, Stack, Typography } from '@mui/material';
|
|
8
8
|
import { useEffect, useState } from 'react';
|
|
9
9
|
import { FormProvider, useForm } from 'react-hook-form';
|
|
10
|
+
import { useSearchParams } from 'react-router-dom';
|
|
10
11
|
import { dispatch } from 'use-bus';
|
|
11
12
|
|
|
12
13
|
import DrawerForm from '../../../../components/drawer-form';
|
|
@@ -25,6 +26,7 @@ type PaymentLink = InferFormType<TPaymentLink> & {
|
|
|
25
26
|
|
|
26
27
|
export default function CreatePaymentLink() {
|
|
27
28
|
const { t } = useLocaleContext();
|
|
29
|
+
const [params] = useSearchParams();
|
|
28
30
|
const { session } = useSessionContext();
|
|
29
31
|
const [current, setCurrent] = useState('beforePay');
|
|
30
32
|
const [stashed, setStashed] = useState(0);
|
|
@@ -128,6 +130,7 @@ export default function CreatePaymentLink() {
|
|
|
128
130
|
<DrawerForm
|
|
129
131
|
icon={<AddOutlined />}
|
|
130
132
|
text={t('admin.paymentLink.add')}
|
|
133
|
+
open={!!params.get('price_id')}
|
|
131
134
|
width={1280}
|
|
132
135
|
addons={
|
|
133
136
|
// @ts-ignore
|
|
@@ -2,6 +2,8 @@ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
|
2
2
|
import Toast from '@arcblock/ux/lib/Toast';
|
|
3
3
|
import type { TPrice } from '@did-pay/types';
|
|
4
4
|
import { useSetState } from 'ahooks';
|
|
5
|
+
import noop from 'lodash/noop';
|
|
6
|
+
import { useNavigate } from 'react-router-dom';
|
|
5
7
|
|
|
6
8
|
import Actions from '../../../../components/actions';
|
|
7
9
|
import ConfirmDialog from '../../../../components/confirm';
|
|
@@ -21,12 +23,13 @@ PriceActions.defaultProps = {
|
|
|
21
23
|
setAsDefault: false,
|
|
22
24
|
};
|
|
23
25
|
|
|
24
|
-
export default function PriceActions(
|
|
26
|
+
export default function PriceActions({ data, onChange, variant, setAsDefault }: Props) {
|
|
25
27
|
const { t } = useLocaleContext();
|
|
28
|
+
const navigate = useNavigate();
|
|
26
29
|
|
|
27
|
-
const canEdit =
|
|
28
|
-
const canArchive =
|
|
29
|
-
const canRemove = !
|
|
30
|
+
const canEdit = data.active;
|
|
31
|
+
const canArchive = data.active;
|
|
32
|
+
const canRemove = !data.locked && !setAsDefault;
|
|
30
33
|
|
|
31
34
|
const [state, setState] = useSetState({
|
|
32
35
|
action: '',
|
|
@@ -36,9 +39,9 @@ export default function PriceActions(props: Props) {
|
|
|
36
39
|
const onEditPrice = async (updates: TPrice) => {
|
|
37
40
|
try {
|
|
38
41
|
setState({ loading: true });
|
|
39
|
-
await api.put(`/api/prices/${
|
|
42
|
+
await api.put(`/api/prices/${data.id}`, updates).then((res) => res.data);
|
|
40
43
|
Toast.success(t('common.saved'));
|
|
41
|
-
|
|
44
|
+
onChange(state.action);
|
|
42
45
|
} catch (err) {
|
|
43
46
|
console.error(err);
|
|
44
47
|
Toast.error(formatError(err));
|
|
@@ -49,9 +52,9 @@ export default function PriceActions(props: Props) {
|
|
|
49
52
|
const onArchivePrice = async () => {
|
|
50
53
|
try {
|
|
51
54
|
setState({ loading: true });
|
|
52
|
-
await api.put(`/api/prices/${
|
|
55
|
+
await api.put(`/api/prices/${data.id}/archive`).then((res) => res.data);
|
|
53
56
|
Toast.success(t('common.saved'));
|
|
54
|
-
|
|
57
|
+
onChange(state.action);
|
|
55
58
|
} catch (err) {
|
|
56
59
|
console.error(err);
|
|
57
60
|
Toast.error(formatError(err));
|
|
@@ -62,9 +65,9 @@ export default function PriceActions(props: Props) {
|
|
|
62
65
|
const onRemovePrice = async () => {
|
|
63
66
|
try {
|
|
64
67
|
setState({ loading: true });
|
|
65
|
-
await api.delete(`/api/prices/${
|
|
68
|
+
await api.delete(`/api/prices/${data.id}`).then((res) => res.data);
|
|
66
69
|
Toast.success(t('common.removed'));
|
|
67
|
-
|
|
70
|
+
onChange(state.action);
|
|
68
71
|
} catch (err) {
|
|
69
72
|
console.error(err);
|
|
70
73
|
Toast.error(formatError(err));
|
|
@@ -75,11 +78,9 @@ export default function PriceActions(props: Props) {
|
|
|
75
78
|
const onSetAsDefault = async () => {
|
|
76
79
|
try {
|
|
77
80
|
setState({ loading: true });
|
|
78
|
-
await api
|
|
79
|
-
.put(`/api/products/${props.data.product_id}`, { default_price_id: props.data.id })
|
|
80
|
-
.then((res) => res.data);
|
|
81
|
+
await api.put(`/api/products/${data.product_id}`, { default_price_id: data.id }).then((res) => res.data);
|
|
81
82
|
Toast.success(t('common.removed'));
|
|
82
|
-
|
|
83
|
+
onChange(state.action);
|
|
83
84
|
} catch (err) {
|
|
84
85
|
console.error(err);
|
|
85
86
|
Toast.error(formatError(err));
|
|
@@ -88,7 +89,21 @@ export default function PriceActions(props: Props) {
|
|
|
88
89
|
}
|
|
89
90
|
};
|
|
90
91
|
|
|
92
|
+
const onCreatePaymentLink = () => {
|
|
93
|
+
navigate(`/admin/payments/links?price_id=${data.id}`);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const onCreatePricingTable = () => {
|
|
97
|
+
navigate(`/admin/products/pricing-tables?price_id=${data.id}`);
|
|
98
|
+
};
|
|
99
|
+
|
|
91
100
|
const actions = [
|
|
101
|
+
{
|
|
102
|
+
label: t('admin.pricing'),
|
|
103
|
+
handler: noop,
|
|
104
|
+
color: 'text.primary',
|
|
105
|
+
disabled: true,
|
|
106
|
+
},
|
|
92
107
|
{
|
|
93
108
|
label: t('admin.price.edit'),
|
|
94
109
|
handler: () => setState({ action: 'edit' }),
|
|
@@ -100,35 +115,40 @@ export default function PriceActions(props: Props) {
|
|
|
100
115
|
handler: () => setState({ action: 'archive' }),
|
|
101
116
|
color: canArchive ? 'text.primary' : 'text.disabled',
|
|
102
117
|
disabled: !canArchive,
|
|
103
|
-
divider: true,
|
|
104
118
|
},
|
|
105
119
|
{
|
|
106
120
|
label: t('admin.price.remove'),
|
|
107
121
|
handler: () => setState({ action: 'remove' }),
|
|
108
122
|
color: canRemove ? 'error.main' : 'text.disabled',
|
|
109
123
|
disabled: !canRemove,
|
|
110
|
-
divider:
|
|
124
|
+
divider: !setAsDefault,
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
label: t('admin.payments'),
|
|
128
|
+
handler: noop,
|
|
129
|
+
color: 'text.primary',
|
|
130
|
+
disabled: true,
|
|
111
131
|
},
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
// { label: 'Create pricing table', handler: props.onRemove, color: 'error', divider: true },
|
|
132
|
+
{ label: 'Create payment link', handler: onCreatePaymentLink, color: 'primary' },
|
|
133
|
+
{ label: 'Create pricing table', handler: onCreatePricingTable, color: 'primary' },
|
|
115
134
|
];
|
|
116
135
|
|
|
117
|
-
if (
|
|
118
|
-
actions.
|
|
136
|
+
if (setAsDefault) {
|
|
137
|
+
actions.splice(4, 0, {
|
|
119
138
|
label: t('admin.price.setAsDefault'),
|
|
120
139
|
handler: onSetAsDefault,
|
|
121
140
|
color: 'text.primary',
|
|
122
141
|
disabled: false,
|
|
142
|
+
divider: true,
|
|
123
143
|
});
|
|
124
144
|
}
|
|
125
145
|
|
|
126
146
|
return (
|
|
127
147
|
<>
|
|
128
|
-
<Actions variant={
|
|
148
|
+
<Actions variant={variant} actions={actions} />
|
|
129
149
|
{state.action === 'edit' && (
|
|
130
150
|
// @ts-ignore
|
|
131
|
-
<EditPrice price={
|
|
151
|
+
<EditPrice price={data} onSave={onEditPrice} onCancel={() => setState({ action: '' })} />
|
|
132
152
|
)}
|
|
133
153
|
{state.action === 'archive' && (
|
|
134
154
|
<ConfirmDialog
|
|
@@ -6,6 +6,7 @@ import { AddOutlined } from '@mui/icons-material';
|
|
|
6
6
|
import { Box, Button, Stack, Typography } from '@mui/material';
|
|
7
7
|
import { useEffect, useState } from 'react';
|
|
8
8
|
import { FormProvider, useForm } from 'react-hook-form';
|
|
9
|
+
import { useSearchParams } from 'react-router-dom';
|
|
9
10
|
import { dispatch } from 'use-bus';
|
|
10
11
|
|
|
11
12
|
import DrawerForm from '../../../../components/drawer-form';
|
|
@@ -20,6 +21,7 @@ import { formatError } from '../../../../libs/util';
|
|
|
20
21
|
|
|
21
22
|
export default function CreatePricingTable() {
|
|
22
23
|
const { t } = useLocaleContext();
|
|
24
|
+
const [params] = useSearchParams();
|
|
23
25
|
const { session } = useSessionContext();
|
|
24
26
|
const [step, setStep] = useState(0); // ['products', 'payment', 'portal']
|
|
25
27
|
const [stashed, setStashed] = useState(0);
|
|
@@ -91,6 +93,7 @@ export default function CreatePricingTable() {
|
|
|
91
93
|
<DrawerForm
|
|
92
94
|
icon={<AddOutlined />}
|
|
93
95
|
text={t('admin.pricingTable.add')}
|
|
96
|
+
open={!!params.get('price_id')}
|
|
94
97
|
width={1280}
|
|
95
98
|
addons={
|
|
96
99
|
// @ts-ignore
|