payment-kit 1.13.47 → 1.13.49

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.
@@ -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.status(400).json({ error: 'Please select cross sell product to continue' });
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.status(403).json({ error: 'Checkout session subscription status unexpected' });
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
 
@@ -353,7 +353,7 @@ export async function ensureInvoiceForCheckout({
353
353
 
354
354
  return InvoiceItem.create({
355
355
  livemode: checkoutSession.livemode,
356
- amount: setup.amount,
356
+ amount: quantity > 0 ? setup.amount : '0',
357
357
  quantity,
358
358
  description: setup.description,
359
359
  period: setup.period,
@@ -65,6 +65,9 @@ router.get('/', authMine, async (req, res) => {
65
65
  const customer = await Customer.findOne({ where: { did: query.customer_did } });
66
66
  if (customer) {
67
67
  where.customer_id = customer.id;
68
+ } else {
69
+ res.json({ count: 0, list: [] });
70
+ return;
68
71
  }
69
72
  }
70
73
  if (query.subscription_id) {
@@ -63,6 +63,9 @@ router.get('/', authMine, async (req, res) => {
63
63
  const customer = await Customer.findOne({ where: { did: query.customer_did } });
64
64
  if (customer) {
65
65
  where.customer_id = customer.id;
66
+ } else {
67
+ res.json({ count: 0, list: [] });
68
+ return;
66
69
  }
67
70
  }
68
71
  if (query.invoice_id) {
@@ -73,7 +73,7 @@ const formatBeforeSave = (payload: any) => {
73
73
  // @ts-ignore
74
74
  raw.after_completion.hosted_confirmation = null;
75
75
  }
76
- if (!payload.include_free_trial) {
76
+ if (typeof payload.include_free_trial === 'boolean' && !payload.include_free_trial) {
77
77
  // @ts-ignore
78
78
  raw.subscription_data = null;
79
79
  }
@@ -75,6 +75,9 @@ router.get('/', authMine, async (req, res) => {
75
75
  const customer = await Customer.findOne({ where: { did: query.customer_did } });
76
76
  if (customer) {
77
77
  where.customer_id = customer.id;
78
+ } else {
79
+ res.json({ count: 0, list: [] });
80
+ return;
78
81
  }
79
82
  }
80
83
  if (typeof livemode === 'boolean') {
package/blocklet.yml CHANGED
@@ -14,7 +14,7 @@ repository:
14
14
  type: git
15
15
  url: git+https://github.com/blocklet/payment-kit.git
16
16
  specVersion: 1.2.8
17
- version: 1.13.47
17
+ version: 1.13.49
18
18
  logo: logo.png
19
19
  files:
20
20
  - dist
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "payment-kit",
3
- "version": "1.13.47",
3
+ "version": "1.13.49",
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.47",
106
+ "@did-pay/types": "1.13.49",
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": "e917c1f665d97114b37ddb566329a1f9c6a9ba6b"
143
+ "gitHead": "8fd11641c7ee8f092afc35eb9615c27d321d398c"
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 isCloumnLayout = useCreation(() => {
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
- {isCloumnLayout ? null : <UserButtons />}
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="center" spacing={1} flex={2}>
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: '120%' }} color="text.secondary">
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
- <Stack
125
- direction="column"
126
- alignItems="flex-end"
127
- spacing={0.5}
128
- sx={{
129
- border: '1px solid #eee',
130
- borderRadius: 1,
131
- padding: 1,
132
- mt: {
133
- xs: 4,
134
- md: 8,
135
- },
136
- }}>
137
- <ProductItem
138
- item={{ quantity: 1, price: data, price_id: data.id, cross_sell: true } as TLineItemExpanded}
139
- session={checkoutSession}
140
- currency={currency}
141
- onUpsell={noop}
142
- onDownsell={noop}
143
- />
144
- <Stack direction="row" alignItems="center" justifyContent="space-between" sx={{ width: 1 }}>
145
- <Typography>
146
- {checkoutSession.cross_sell_behavior === 'required' && (
147
- <Status label={t('checkout.required')} color="info" variant="outlined" sx={{ mr: 1 }} />
148
- )}
149
- </Typography>
150
- <LoadingButton
151
- size="small"
152
- loadingPosition="end"
153
- color={checkoutSession.cross_sell_behavior === 'required' ? 'info' : 'info'}
154
- variant={checkoutSession.cross_sell_behavior === 'required' ? 'text' : 'text'}
155
- loading={state.loading}
156
- onClick={handleApplyCrossSell}>
157
- {t('checkout.cross_sell.add')}
158
- </LoadingButton>
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
- </Stack>
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
  },
@@ -513,6 +513,7 @@ export default flat({
513
513
  portal: 'Manage subscriptions',
514
514
  cardPay: '{action} with card',
515
515
  empty: 'No thing to pay',
516
+ per: 'per',
516
517
  pay: 'Pay {payee}',
517
518
  try1: 'Try {name}',
518
519
  try2: 'Try {name} and {count} more',