payment-kit 1.13.47 → 1.13.48
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 +17 -14
- package/blocklet.yml +1 -1
- package/package.json +3 -3
- package/src/components/checkout/form/index.tsx +6 -2
- package/src/components/checkout/product-card.tsx +3 -3
- package/src/components/checkout/summary.tsx +71 -38
- package/src/libs/api.ts +2 -1
- package/src/locales/en.tsx +1 -0
|
@@ -198,15 +198,14 @@ export async function getCheckoutSessionAmounts(checkoutSession: CheckoutSession
|
|
|
198
198
|
|
|
199
199
|
export async function ensureCheckoutSessionOpen(req: Request, res: Response, next: NextFunction) {
|
|
200
200
|
const doc = await CheckoutSession.findByPk(req.params.id);
|
|
201
|
-
|
|
202
201
|
if (!doc) {
|
|
203
|
-
return res.status(404).json({ error: 'Checkout session not found' });
|
|
202
|
+
return res.status(404).json({ code: 'CHECKOUT_NOT_FOUND', error: 'Checkout session not found' });
|
|
204
203
|
}
|
|
205
204
|
if (doc.status === 'complete') {
|
|
206
|
-
return res.status(403).json({ error: 'Checkout session completed' });
|
|
205
|
+
return res.status(403).json({ code: 'CHECKOUT_COMPLETED', error: 'Checkout session completed' });
|
|
207
206
|
}
|
|
208
207
|
if (doc.status === 'expired') {
|
|
209
|
-
return res.status(403).json({ error: 'Checkout session already expired' });
|
|
208
|
+
return res.status(403).json({ code: 'CHECKOUT_EXPIRED', error: 'Checkout session already expired' });
|
|
210
209
|
}
|
|
211
210
|
|
|
212
211
|
req.doc = doc;
|
|
@@ -405,7 +404,7 @@ router.get('/retrieve/:id', user, async (req, res) => {
|
|
|
405
404
|
router.put('/:id/submit', user, ensureCheckoutSessionOpen, async (req, res) => {
|
|
406
405
|
try {
|
|
407
406
|
if (!req.user) {
|
|
408
|
-
return res.status(403).json({ error: 'Please login to continue' });
|
|
407
|
+
return res.status(403).json({ code: 'REQUIRE_LOGIN', error: 'Please login to continue' });
|
|
409
408
|
}
|
|
410
409
|
|
|
411
410
|
const checkoutSession = req.doc as CheckoutSession;
|
|
@@ -416,7 +415,9 @@ router.put('/:id/submit', user, ensureCheckoutSessionOpen, async (req, res) => {
|
|
|
416
415
|
const result = await getCrossSellItem(checkoutSession);
|
|
417
416
|
// @ts-ignore
|
|
418
417
|
if (result.id) {
|
|
419
|
-
return res
|
|
418
|
+
return res
|
|
419
|
+
.status(400)
|
|
420
|
+
.json({ code: 'REQUIRE_CROSS_SELL', error: 'Please select cross sell product to continue' });
|
|
420
421
|
}
|
|
421
422
|
}
|
|
422
423
|
}
|
|
@@ -496,13 +497,13 @@ router.put('/:id/submit', user, ensureCheckoutSessionOpen, async (req, res) => {
|
|
|
496
497
|
if (paymentIntent) {
|
|
497
498
|
// Check payment intent, if we have a payment intent, we should not create a new one
|
|
498
499
|
if (paymentIntent.status === 'succeeded') {
|
|
499
|
-
return res.status(403).json({ error: 'Checkout session payment completed' });
|
|
500
|
+
return res.status(403).json({ code: 'PAYMENT_SUCCEEDED', error: 'Checkout session payment completed' });
|
|
500
501
|
}
|
|
501
502
|
if (paymentIntent.status === 'canceled') {
|
|
502
|
-
return res.status(403).json({ error: 'Checkout session payment canceled' });
|
|
503
|
+
return res.status(403).json({ code: 'PAYMENT_CANCELLED', error: 'Checkout session payment canceled' });
|
|
503
504
|
}
|
|
504
505
|
if (paymentIntent.status === 'processing') {
|
|
505
|
-
return res.status(403).json({ error: 'Checkout session payment processing' });
|
|
506
|
+
return res.status(403).json({ code: 'PAYMENT_PROCESSING', error: 'Checkout session payment processing' });
|
|
506
507
|
}
|
|
507
508
|
paymentIntent = await paymentIntent.update({
|
|
508
509
|
amount: checkoutSession.amount_total,
|
|
@@ -553,13 +554,13 @@ router.put('/:id/submit', user, ensureCheckoutSessionOpen, async (req, res) => {
|
|
|
553
554
|
|
|
554
555
|
if (setupIntent) {
|
|
555
556
|
if (setupIntent.status === 'succeeded') {
|
|
556
|
-
return res.status(403).json({ error: 'Checkout session setup completed' });
|
|
557
|
+
return res.status(403).json({ code: 'SETUP_SUCCEEDED', error: 'Checkout session setup completed' });
|
|
557
558
|
}
|
|
558
559
|
if (setupIntent.status === 'canceled') {
|
|
559
|
-
return res.status(403).json({ error: 'Checkout session setup canceled' });
|
|
560
|
+
return res.status(403).json({ code: 'SETUP_CANCELED', error: 'Checkout session setup canceled' });
|
|
560
561
|
}
|
|
561
562
|
if (setupIntent.status === 'processing') {
|
|
562
|
-
return res.status(403).json({ error: 'Checkout session setup processing' });
|
|
563
|
+
return res.status(403).json({ code: 'SETUP_PROCESSING', error: 'Checkout session setup processing' });
|
|
563
564
|
}
|
|
564
565
|
await setupIntent.update({
|
|
565
566
|
customer_id: customer.id,
|
|
@@ -597,7 +598,9 @@ router.put('/:id/submit', user, ensureCheckoutSessionOpen, async (req, res) => {
|
|
|
597
598
|
}
|
|
598
599
|
if (subscription) {
|
|
599
600
|
if (subscription.status !== 'incomplete') {
|
|
600
|
-
return res
|
|
601
|
+
return res
|
|
602
|
+
.status(403)
|
|
603
|
+
.json({ code: 'SUBSCRIPTION_INVALID', error: 'Checkout session subscription status unexpected' });
|
|
601
604
|
}
|
|
602
605
|
subscription = await subscription.update({
|
|
603
606
|
currency_id: paymentCurrency.id,
|
|
@@ -768,7 +771,7 @@ router.put('/:id/submit', user, ensureCheckoutSessionOpen, async (req, res) => {
|
|
|
768
771
|
return res.json({ paymentIntent, setupIntent, stripeContext, subscription, checkoutSession, customer, delegation });
|
|
769
772
|
} catch (err) {
|
|
770
773
|
console.error(err);
|
|
771
|
-
res.status(500).json({ error: err.message });
|
|
774
|
+
res.status(500).json({ code: err.code, error: err.message });
|
|
772
775
|
}
|
|
773
776
|
});
|
|
774
777
|
|
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.48",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "blocklet dev",
|
|
6
6
|
"eject": "vite eject",
|
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
"@abtnode/types": "1.16.17",
|
|
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.48",
|
|
107
107
|
"@types/cookie-parser": "^1.4.5",
|
|
108
108
|
"@types/cors": "^2.8.15",
|
|
109
109
|
"@types/dotenv-flow": "^3.3.2",
|
|
@@ -140,5 +140,5 @@
|
|
|
140
140
|
"parser": "typescript"
|
|
141
141
|
}
|
|
142
142
|
},
|
|
143
|
-
"gitHead": "
|
|
143
|
+
"gitHead": "55d5b96dd4ab54fb3a4400cb7b78924db51de16d"
|
|
144
144
|
}
|
|
@@ -11,6 +11,7 @@ import { PhoneNumberUtil } from 'google-libphonenumber';
|
|
|
11
11
|
import pWaitFor from 'p-wait-for';
|
|
12
12
|
import { useEffect } from 'react';
|
|
13
13
|
import { Controller, useFormContext, useWatch } from 'react-hook-form';
|
|
14
|
+
import { dispatch } from 'use-bus';
|
|
14
15
|
import isEmail from 'validator/es/lib/isEmail';
|
|
15
16
|
|
|
16
17
|
import { useSessionContext } from '../../../contexts/session';
|
|
@@ -115,7 +116,7 @@ export default function PaymentForm({
|
|
|
115
116
|
|
|
116
117
|
const domSize = useSize(document.body);
|
|
117
118
|
|
|
118
|
-
const
|
|
119
|
+
const isColumnLayout = useCreation(() => {
|
|
119
120
|
if (domSize) {
|
|
120
121
|
if (domSize?.width <= theme.breakpoints.values.md) {
|
|
121
122
|
return true;
|
|
@@ -229,6 +230,9 @@ export default function PaymentForm({
|
|
|
229
230
|
}
|
|
230
231
|
}
|
|
231
232
|
} catch (err) {
|
|
233
|
+
if (err.response?.data?.code) {
|
|
234
|
+
dispatch(`error.${err.response?.data?.code}`);
|
|
235
|
+
}
|
|
232
236
|
Toast.error(formatError(err));
|
|
233
237
|
} finally {
|
|
234
238
|
setState({ submitting: false });
|
|
@@ -261,7 +265,7 @@ export default function PaymentForm({
|
|
|
261
265
|
<Stack className="cko-payment-contact">
|
|
262
266
|
<Stack direction="row" sx={{ mb: 1 }} alignItems="center" justifyContent="space-between">
|
|
263
267
|
<Typography sx={{ color: 'text.primary', fontWeight: 600 }}>{t('checkout.contact')}</Typography>
|
|
264
|
-
{
|
|
268
|
+
{isColumnLayout ? null : <UserButtons />}
|
|
265
269
|
</Stack>
|
|
266
270
|
<Stack direction="column" className="cko-payment-form" spacing={0}>
|
|
267
271
|
<FormInput
|
|
@@ -14,7 +14,7 @@ type Props = {
|
|
|
14
14
|
export default function ProductCard({ size, variant, name, logo, description, extra }: Props) {
|
|
15
15
|
const s = { width: size, height: size };
|
|
16
16
|
return (
|
|
17
|
-
<Stack direction="row" alignItems="
|
|
17
|
+
<Stack direction="row" alignItems="flex-start" spacing={1} flex={2}>
|
|
18
18
|
{logo ? (
|
|
19
19
|
// @ts-ignore
|
|
20
20
|
<Avatar src={logo} alt={name} variant={variant} sx={s} />
|
|
@@ -25,11 +25,11 @@ export default function ProductCard({ size, variant, name, logo, description, ex
|
|
|
25
25
|
</Avatar>
|
|
26
26
|
)}
|
|
27
27
|
<Stack direction="column" alignItems="flex-start" justifyContent="space-around">
|
|
28
|
-
<Typography variant="body1" sx={{ fontWeight: 500 }} color="text.primary">
|
|
28
|
+
<Typography variant="body1" sx={{ fontWeight: 500, mb: 0.5, lineHeight: 1 }} color="text.primary">
|
|
29
29
|
{name}
|
|
30
30
|
</Typography>
|
|
31
31
|
{description && (
|
|
32
|
-
<Typography variant="body1" sx={{ fontSize: '0.85rem', lineHeight:
|
|
32
|
+
<Typography variant="body1" sx={{ fontSize: '0.85rem', mb: 0.5, lineHeight: 1 }} color="text.secondary">
|
|
33
33
|
{description}
|
|
34
34
|
</Typography>
|
|
35
35
|
)}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
2
2
|
import type { TCheckoutSessionExpanded, TLineItemExpanded, TPaymentCurrency } from '@did-pay/types';
|
|
3
3
|
import { LoadingButton } from '@mui/lab';
|
|
4
|
-
import { Fade, Stack, Typography } from '@mui/material';
|
|
4
|
+
import { Fade, Grow, Stack, Typography, keyframes } from '@mui/material';
|
|
5
5
|
import { useRequest, useSetState } from 'ahooks';
|
|
6
6
|
import noop from 'lodash/noop';
|
|
7
|
+
import useBus from 'use-bus';
|
|
7
8
|
|
|
8
9
|
import api from '../../libs/api';
|
|
9
10
|
import { formatCheckoutHeadlines } from '../../libs/util';
|
|
@@ -11,6 +12,24 @@ import Status from '../status';
|
|
|
11
12
|
import PaymentAmount from './amount';
|
|
12
13
|
import ProductItem from './product-item';
|
|
13
14
|
|
|
15
|
+
const shake = keyframes`
|
|
16
|
+
0% {
|
|
17
|
+
transform: rotate(0deg);
|
|
18
|
+
}
|
|
19
|
+
25% {
|
|
20
|
+
transform: rotate(2deg);
|
|
21
|
+
}
|
|
22
|
+
50% {
|
|
23
|
+
transform: rotate(0eg);
|
|
24
|
+
}
|
|
25
|
+
75% {
|
|
26
|
+
transform: rotate(-2deg);
|
|
27
|
+
}
|
|
28
|
+
100% {
|
|
29
|
+
transform: rotate(0deg);
|
|
30
|
+
}
|
|
31
|
+
`;
|
|
32
|
+
|
|
14
33
|
type Props = {
|
|
15
34
|
checkoutSession: TCheckoutSessionExpanded;
|
|
16
35
|
currency: TPaymentCurrency;
|
|
@@ -42,10 +61,21 @@ export default function PaymentSummary({
|
|
|
42
61
|
onCancelCrossSell,
|
|
43
62
|
}: Props) {
|
|
44
63
|
const { t, locale } = useLocaleContext();
|
|
45
|
-
const [state, setState] = useSetState({ loading: false });
|
|
64
|
+
const [state, setState] = useSetState({ loading: false, shake: false });
|
|
46
65
|
const { data, runAsync } = useRequest(() => fetchCrossSell(checkoutSession.id));
|
|
47
66
|
const headlines = formatCheckoutHeadlines(checkoutSession, currency, locale);
|
|
48
67
|
|
|
68
|
+
useBus(
|
|
69
|
+
'error.REQUIRE_CROSS_SELL',
|
|
70
|
+
() => {
|
|
71
|
+
setState({ shake: true });
|
|
72
|
+
setTimeout(() => {
|
|
73
|
+
setState({ shake: false });
|
|
74
|
+
}, 1000);
|
|
75
|
+
},
|
|
76
|
+
[]
|
|
77
|
+
);
|
|
78
|
+
|
|
49
79
|
const handleUpsell = async (from: string, to: string) => {
|
|
50
80
|
await onUpsell(from, to);
|
|
51
81
|
runAsync();
|
|
@@ -121,43 +151,46 @@ export default function PaymentSummary({
|
|
|
121
151
|
))}
|
|
122
152
|
</Stack>
|
|
123
153
|
{data && checkoutSession.line_items.some((x) => x.price_id === data.id) === false && (
|
|
124
|
-
<
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
154
|
+
<Grow in>
|
|
155
|
+
<Stack
|
|
156
|
+
direction="column"
|
|
157
|
+
alignItems="flex-end"
|
|
158
|
+
spacing={0.5}
|
|
159
|
+
sx={{
|
|
160
|
+
border: '1px solid #eee',
|
|
161
|
+
borderRadius: 1,
|
|
162
|
+
padding: 1,
|
|
163
|
+
animation: state.shake ? `${shake} 0.2s 5 ease-in-out` : 'none',
|
|
164
|
+
mt: {
|
|
165
|
+
xs: 4,
|
|
166
|
+
md: 8,
|
|
167
|
+
},
|
|
168
|
+
}}>
|
|
169
|
+
<ProductItem
|
|
170
|
+
item={{ quantity: 1, price: data, price_id: data.id, cross_sell: true } as TLineItemExpanded}
|
|
171
|
+
session={checkoutSession}
|
|
172
|
+
currency={currency}
|
|
173
|
+
onUpsell={noop}
|
|
174
|
+
onDownsell={noop}
|
|
175
|
+
/>
|
|
176
|
+
<Stack direction="row" alignItems="center" justifyContent="space-between" sx={{ width: 1 }}>
|
|
177
|
+
<Typography>
|
|
178
|
+
{checkoutSession.cross_sell_behavior === 'required' && (
|
|
179
|
+
<Status label={t('checkout.required')} color="info" variant="outlined" sx={{ mr: 1 }} />
|
|
180
|
+
)}
|
|
181
|
+
</Typography>
|
|
182
|
+
<LoadingButton
|
|
183
|
+
size="small"
|
|
184
|
+
loadingPosition="end"
|
|
185
|
+
color={checkoutSession.cross_sell_behavior === 'required' ? 'info' : 'info'}
|
|
186
|
+
variant={checkoutSession.cross_sell_behavior === 'required' ? 'text' : 'text'}
|
|
187
|
+
loading={state.loading}
|
|
188
|
+
onClick={handleApplyCrossSell}>
|
|
189
|
+
{t('checkout.cross_sell.add')}
|
|
190
|
+
</LoadingButton>
|
|
191
|
+
</Stack>
|
|
159
192
|
</Stack>
|
|
160
|
-
</
|
|
193
|
+
</Grow>
|
|
161
194
|
)}
|
|
162
195
|
</Stack>
|
|
163
196
|
</Fade>
|
package/src/libs/api.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getLocale } from '@arcblock/ux/lib/Locale/context';
|
|
2
2
|
import axios from 'axios';
|
|
3
|
+
import isNull from 'lodash/isNull';
|
|
3
4
|
|
|
4
5
|
axios.interceptors.request.use(
|
|
5
6
|
(config) => {
|
|
@@ -9,7 +10,7 @@ axios.interceptors.request.use(
|
|
|
9
10
|
|
|
10
11
|
const livemode = localStorage.getItem('livemode');
|
|
11
12
|
const locale = getLocale(window.blocklet.languages);
|
|
12
|
-
config.params = { ...(config.params || {}), livemode, locale };
|
|
13
|
+
config.params = { ...(config.params || {}), livemode: isNull(livemode) ? true : JSON.parse(livemode), locale };
|
|
13
14
|
|
|
14
15
|
return config;
|
|
15
16
|
},
|
package/src/locales/en.tsx
CHANGED