payment-kit 1.13.45 → 1.13.47
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/dev.ts +1 -1
- package/api/src/libs/session.ts +1 -1
- package/api/src/routes/prices.ts +21 -18
- package/api/src/routes/pricing-table.ts +3 -1
- package/blocklet.yml +1 -1
- package/package.json +20 -20
- package/src/components/checkout/form/index.tsx +17 -14
- package/src/components/checkout/form/user-buttons.tsx +24 -0
- package/src/components/checkout/header.tsx +22 -5
- package/src/components/checkout/pay.tsx +50 -11
- package/src/components/checkout/product-item.tsx +7 -7
- package/src/components/checkout/summary.tsx +17 -4
- package/src/components/livemode.tsx +1 -1
- package/src/components/portal/invoice/list.tsx +16 -6
- package/src/components/portal/subscription/list.tsx +9 -1
- package/src/components/product/cross-sell-select.tsx +2 -2
- package/src/components/product/cross-sell.tsx +2 -1
- package/src/components/section/header.tsx +2 -0
- package/src/components/status.tsx +1 -1
- package/src/libs/util.ts +52 -31
- package/src/locales/en.tsx +36 -11
- package/src/locales/index.tsx +25 -0
- package/src/locales/zh.tsx +33 -9
- package/src/pages/admin/payments/links/detail.tsx +2 -1
- package/src/pages/admin/products/prices/detail.tsx +11 -3
- package/src/pages/admin/products/prices/list.tsx +4 -2
- package/src/pages/admin/products/pricing-tables/detail.tsx +2 -1
- package/src/pages/admin/products/products/create.tsx +2 -2
- package/src/pages/admin/products/products/detail.tsx +2 -2
- package/src/pages/admin/products/products/index.tsx +2 -2
- package/src/pages/checkout/pricing-table.tsx +66 -16
package/api/dev.ts
CHANGED
package/api/src/libs/session.ts
CHANGED
package/api/src/routes/prices.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { fromTokenToUnit } from '@ocap/util';
|
|
1
|
+
import { fromTokenToUnit, fromUnitToToken } from '@ocap/util';
|
|
2
2
|
import { Router } from 'express';
|
|
3
3
|
import Joi from 'joi';
|
|
4
4
|
import pick from 'lodash/pick';
|
|
@@ -114,20 +114,20 @@ router.get('/:id/upsell', auth, async (req, res) => {
|
|
|
114
114
|
// update price
|
|
115
115
|
// FIXME: upsell validate https://stripe.com/docs/payments/checkout/upsells
|
|
116
116
|
router.put('/:id', auth, async (req, res) => {
|
|
117
|
-
const
|
|
117
|
+
const doc = await Price.findByPkOrLookupKey(req.params.id as string);
|
|
118
118
|
|
|
119
|
-
if (!
|
|
119
|
+
if (!doc) {
|
|
120
120
|
return res.status(404).json({ error: 'price not found' });
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
if (
|
|
123
|
+
if (doc.active === false) {
|
|
124
124
|
return res.status(403).json({ error: 'price archived' });
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
const updates: Partial<Price> = Price.formatBeforeSave(
|
|
128
128
|
pick(
|
|
129
129
|
req.body,
|
|
130
|
-
|
|
130
|
+
doc.locked
|
|
131
131
|
? ['nickname', 'description', 'metadata', 'currency_options', 'upsell']
|
|
132
132
|
: ['type', 'model', 'active', 'livemode', 'nickname', 'recurring', 'description', 'tiers', 'unit_amount', 'transform_quantity', 'metadata', 'lookup_key', 'currency_options', 'upsell'] // prettier-ignore
|
|
133
133
|
)
|
|
@@ -135,38 +135,41 @@ router.put('/:id', auth, async (req, res) => {
|
|
|
135
135
|
|
|
136
136
|
if (updates.lookup_key) {
|
|
137
137
|
const exist = await Price.findOne({ where: { lookup_key: updates.lookup_key } });
|
|
138
|
-
if (exist && exist.id !==
|
|
138
|
+
if (exist && exist.id !== doc.id) {
|
|
139
139
|
return res.status(400).json({ error: `lookup_key ${updates.lookup_key} already used by ${exist.id}` });
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
const currencies = await PaymentCurrency.findAll({ where: { active: true } });
|
|
144
|
-
const currency = currencies.find((x) => x.id ===
|
|
144
|
+
const currency = currencies.find((x) => x.id === doc.currency_id);
|
|
145
145
|
if (!currency) {
|
|
146
|
-
return res.status(400).json({ error: `currency used in price not found or not active: ${
|
|
146
|
+
return res.status(400).json({ error: `currency used in price not found or not active: ${doc.currency_id}` });
|
|
147
147
|
}
|
|
148
148
|
if (updates.unit_amount) {
|
|
149
149
|
updates.unit_amount = fromTokenToUnit(updates.unit_amount, currency.decimal).toString();
|
|
150
|
+
if (updates.currency_options) {
|
|
151
|
+
const exist = updates.currency_options.find((x) => x.currency_id === doc.currency_id);
|
|
152
|
+
if (exist) {
|
|
153
|
+
exist.unit_amount = fromUnitToToken(updates.unit_amount as string, currency.decimal);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
150
156
|
}
|
|
151
157
|
if (updates.currency_options) {
|
|
152
158
|
updates.currency_options = Price.formatCurrencies(updates.currency_options, currencies);
|
|
153
|
-
|
|
159
|
+
const index = updates.currency_options.findIndex((x) => x.currency_id === doc.currency_id);
|
|
160
|
+
if (index > -1) {
|
|
161
|
+
updates.unit_amount = updates.currency_options[index]?.unit_amount;
|
|
162
|
+
} else {
|
|
154
163
|
updates.currency_options.unshift({
|
|
155
|
-
currency_id:
|
|
156
|
-
unit_amount:
|
|
164
|
+
currency_id: doc.currency_id,
|
|
165
|
+
unit_amount: doc.unit_amount,
|
|
157
166
|
tiers: null,
|
|
158
167
|
custom_unit_amount: null,
|
|
159
168
|
});
|
|
160
169
|
}
|
|
161
|
-
if (updates.unit_amount) {
|
|
162
|
-
const base = price.currency_options.find((x) => x.currency_id === price.currency_id);
|
|
163
|
-
if (base) {
|
|
164
|
-
base.unit_amount = updates.unit_amount;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
170
|
}
|
|
168
171
|
|
|
169
|
-
await
|
|
172
|
+
await doc.update(Price.formatBeforeSave(updates));
|
|
170
173
|
|
|
171
174
|
return res.json(await getExpandedPrice(req.params.id as string));
|
|
172
175
|
});
|
|
@@ -344,9 +344,11 @@ router.post('/:id/checkout/:priceId', async (req, res) => {
|
|
|
344
344
|
},
|
|
345
345
|
});
|
|
346
346
|
|
|
347
|
+
const currency = await PaymentCurrency.findOne({ where: { livemode: doc.livemode, is_base_currency: true } });
|
|
348
|
+
|
|
347
349
|
raw.livemode = doc.livemode;
|
|
348
350
|
raw.created_via = 'portal';
|
|
349
|
-
raw.currency_id =
|
|
351
|
+
raw.currency_id = currency?.id;
|
|
350
352
|
|
|
351
353
|
if (req.query.redirect) {
|
|
352
354
|
raw.success_url = req.query.redirect as string;
|
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.47",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "blocklet dev",
|
|
6
6
|
"eject": "vite eject",
|
|
@@ -40,25 +40,25 @@
|
|
|
40
40
|
]
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@arcblock/did": "^1.18.
|
|
43
|
+
"@arcblock/did": "^1.18.95",
|
|
44
44
|
"@arcblock/did-auth-storage-nedb": "^1.7.1",
|
|
45
|
-
"@arcblock/did-connect": "^2.8.
|
|
46
|
-
"@arcblock/did-util": "^1.18.
|
|
47
|
-
"@arcblock/ux": "^2.8.
|
|
45
|
+
"@arcblock/did-connect": "^2.8.7",
|
|
46
|
+
"@arcblock/did-util": "^1.18.95",
|
|
47
|
+
"@arcblock/ux": "^2.8.7",
|
|
48
48
|
"@blocklet/logger": "1.16.17",
|
|
49
49
|
"@blocklet/sdk": "1.16.17",
|
|
50
|
-
"@blocklet/ui-react": "^2.8.
|
|
50
|
+
"@blocklet/ui-react": "^2.8.7",
|
|
51
51
|
"@blocklet/uploader": "^0.0.33",
|
|
52
|
-
"@mui/icons-material": "^5.14.
|
|
53
|
-
"@mui/lab": "^5.0.0-alpha.
|
|
54
|
-
"@mui/material": "^5.14.
|
|
55
|
-
"@mui/styles": "^5.14.
|
|
56
|
-
"@mui/system": "^5.14.
|
|
57
|
-
"@ocap/asset": "^1.18.
|
|
58
|
-
"@ocap/client": "^1.18.
|
|
59
|
-
"@ocap/mcrypto": "^1.18.
|
|
60
|
-
"@ocap/util": "^1.18.
|
|
61
|
-
"@ocap/wallet": "^1.18.
|
|
52
|
+
"@mui/icons-material": "^5.14.16",
|
|
53
|
+
"@mui/lab": "^5.0.0-alpha.151",
|
|
54
|
+
"@mui/material": "^5.14.16",
|
|
55
|
+
"@mui/styles": "^5.14.16",
|
|
56
|
+
"@mui/system": "^5.14.16",
|
|
57
|
+
"@ocap/asset": "^1.18.95",
|
|
58
|
+
"@ocap/client": "^1.18.95",
|
|
59
|
+
"@ocap/mcrypto": "^1.18.95",
|
|
60
|
+
"@ocap/util": "^1.18.95",
|
|
61
|
+
"@ocap/wallet": "^1.18.95",
|
|
62
62
|
"@stripe/react-stripe-js": "^2.3.1",
|
|
63
63
|
"@stripe/stripe-js": "^2.1.10",
|
|
64
64
|
"ahooks": "^3.7.8",
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
"react-error-boundary": "^4.0.11",
|
|
89
89
|
"react-hook-form": "^7.47.0",
|
|
90
90
|
"react-international-phone": "^3.1.2",
|
|
91
|
-
"react-router-dom": "^6.
|
|
91
|
+
"react-router-dom": "^6.18.0",
|
|
92
92
|
"rimraf": "^3.0.2",
|
|
93
93
|
"sequelize": "^6.33.0",
|
|
94
94
|
"sqlite3": "^5.1.6",
|
|
@@ -103,12 +103,12 @@
|
|
|
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.47",
|
|
107
107
|
"@types/cookie-parser": "^1.4.5",
|
|
108
108
|
"@types/cors": "^2.8.15",
|
|
109
109
|
"@types/dotenv-flow": "^3.3.2",
|
|
110
110
|
"@types/express": "^4.17.20",
|
|
111
|
-
"@types/node": "^18.18.
|
|
111
|
+
"@types/node": "^18.18.8",
|
|
112
112
|
"@types/react": "^18.2.33",
|
|
113
113
|
"@types/react-dom": "^18.2.14",
|
|
114
114
|
"@vitejs/plugin-react": "^2.2.0",
|
|
@@ -140,5 +140,5 @@
|
|
|
140
140
|
"parser": "typescript"
|
|
141
141
|
}
|
|
142
142
|
},
|
|
143
|
-
"gitHead": "
|
|
143
|
+
"gitHead": "e917c1f665d97114b37ddb566329a1f9c6a9ba6b"
|
|
144
144
|
}
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import 'react-international-phone/style.css';
|
|
2
2
|
|
|
3
|
-
import SessionManager from '@arcblock/did-connect/lib/SessionManager';
|
|
4
3
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
5
|
-
import
|
|
4
|
+
import { useTheme } from '@arcblock/ux/lib/Theme';
|
|
6
5
|
import Toast from '@arcblock/ux/lib/Toast';
|
|
7
6
|
import type { TCheckoutSessionExpanded, TCustomer, TPaymentIntent, TPaymentMethodExpanded } from '@did-pay/types';
|
|
8
7
|
import { LoadingButton } from '@mui/lab';
|
|
9
|
-
import { Avatar, Fade, InputAdornment, MenuItem, Select, Stack,
|
|
10
|
-
import { useSetState } from 'ahooks';
|
|
8
|
+
import { Avatar, Fade, InputAdornment, MenuItem, Select, Stack, Typography } from '@mui/material';
|
|
9
|
+
import { useCreation, useSetState, useSize } from 'ahooks';
|
|
11
10
|
import { PhoneNumberUtil } from 'google-libphonenumber';
|
|
12
11
|
import pWaitFor from 'p-wait-for';
|
|
13
12
|
import { useEffect } from 'react';
|
|
@@ -21,6 +20,7 @@ import FormInput from '../../input';
|
|
|
21
20
|
import AddressForm from './address';
|
|
22
21
|
import PhoneInput from './phone';
|
|
23
22
|
import StripeCheckout from './stripe';
|
|
23
|
+
import UserButtons from './user-buttons';
|
|
24
24
|
|
|
25
25
|
const phoneUtil = PhoneNumberUtil.getInstance();
|
|
26
26
|
|
|
@@ -72,6 +72,7 @@ export default function PaymentForm({
|
|
|
72
72
|
onPaid,
|
|
73
73
|
onError,
|
|
74
74
|
}: PageData) {
|
|
75
|
+
const theme = useTheme();
|
|
75
76
|
const { t } = useLocaleContext();
|
|
76
77
|
const { session, connectApi } = useSessionContext();
|
|
77
78
|
const { control, getValues, setValue, handleSubmit } = useFormContext();
|
|
@@ -112,6 +113,17 @@ export default function PaymentForm({
|
|
|
112
113
|
const paymentCurrency = useWatch({ control, name: 'payment_currency' });
|
|
113
114
|
const paymentCurrencies = paymentMethods.find((x) => x.id === paymentMethod)?.payment_currencies || [];
|
|
114
115
|
|
|
116
|
+
const domSize = useSize(document.body);
|
|
117
|
+
|
|
118
|
+
const isCloumnLayout = useCreation(() => {
|
|
119
|
+
if (domSize) {
|
|
120
|
+
if (domSize?.width <= theme.breakpoints.values.md) {
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return false;
|
|
125
|
+
}, [domSize, theme]);
|
|
126
|
+
|
|
115
127
|
const payee = getStatementDescriptor(checkoutSession.line_items);
|
|
116
128
|
const buttonText = session.user
|
|
117
129
|
? t(`checkout.${checkoutSession.mode}`)
|
|
@@ -249,16 +261,7 @@ export default function PaymentForm({
|
|
|
249
261
|
<Stack className="cko-payment-contact">
|
|
250
262
|
<Stack direction="row" sx={{ mb: 1 }} alignItems="center" justifyContent="space-between">
|
|
251
263
|
<Typography sx={{ color: 'text.primary', fontWeight: 600 }}>{t('checkout.contact')}</Typography>
|
|
252
|
-
|
|
253
|
-
<LocaleSelector showText={false} />
|
|
254
|
-
{session.user ? (
|
|
255
|
-
<SessionManager session={session} />
|
|
256
|
-
) : (
|
|
257
|
-
<Tooltip title={t('checkout.login')} arrow>
|
|
258
|
-
<SessionManager session={session} />
|
|
259
|
-
</Tooltip>
|
|
260
|
-
)}
|
|
261
|
-
</Stack>
|
|
264
|
+
{isCloumnLayout ? null : <UserButtons />}
|
|
262
265
|
</Stack>
|
|
263
266
|
<Stack direction="column" className="cko-payment-form" spacing={0}>
|
|
264
267
|
<FormInput
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import SessionManager from '@arcblock/did-connect/lib/SessionManager';
|
|
2
|
+
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
3
|
+
import LocaleSelector from '@arcblock/ux/lib/Locale/selector';
|
|
4
|
+
import { Stack, Tooltip } from '@mui/material';
|
|
5
|
+
|
|
6
|
+
import { useSessionContext } from '../../../contexts/session';
|
|
7
|
+
|
|
8
|
+
export default function UserButtons() {
|
|
9
|
+
const { t } = useLocaleContext();
|
|
10
|
+
const { session } = useSessionContext();
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<Stack direction="row" alignItems="center" justifyContent="space-between">
|
|
14
|
+
<LocaleSelector showText={false} />
|
|
15
|
+
{session.user ? (
|
|
16
|
+
<SessionManager session={session} />
|
|
17
|
+
) : (
|
|
18
|
+
<Tooltip title={t('checkout.login')} arrow>
|
|
19
|
+
<SessionManager session={session} />
|
|
20
|
+
</Tooltip>
|
|
21
|
+
)}
|
|
22
|
+
</Stack>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { useTheme } from '@arcblock/ux/lib/Theme';
|
|
1
2
|
import type { TCheckoutSessionExpanded } from '@did-pay/types';
|
|
2
3
|
import { Avatar, Stack, Typography } from '@mui/material';
|
|
3
|
-
import { useLocalStorageState } from 'ahooks';
|
|
4
|
+
import { useCreation, useLocalStorageState, useSize } from 'ahooks';
|
|
4
5
|
|
|
5
6
|
import { getStatementDescriptor } from '../../libs/util';
|
|
6
7
|
import Livemode from '../livemode';
|
|
8
|
+
import UserButtons from './form/user-buttons';
|
|
7
9
|
|
|
8
10
|
type Props = {
|
|
9
11
|
checkoutSession: TCheckoutSessionExpanded;
|
|
@@ -12,12 +14,27 @@ type Props = {
|
|
|
12
14
|
export default function PaymentHeader({ checkoutSession }: Props) {
|
|
13
15
|
const [livemode] = useLocalStorageState('livemode', { defaultValue: true });
|
|
14
16
|
const brand = getStatementDescriptor(checkoutSession.line_items);
|
|
17
|
+
const theme = useTheme();
|
|
18
|
+
|
|
19
|
+
const domSize = useSize(document.body);
|
|
20
|
+
|
|
21
|
+
const isCloumnLayout = useCreation(() => {
|
|
22
|
+
if (domSize) {
|
|
23
|
+
if (domSize?.width <= theme.breakpoints.values.md) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}, [domSize, theme]);
|
|
15
29
|
|
|
16
30
|
return (
|
|
17
|
-
<Stack className="cko-header" direction="row" spacing={1} alignItems="center">
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
31
|
+
<Stack className="cko-header" direction="row" spacing={1} alignItems="center" justifyContent="space-between">
|
|
32
|
+
<Stack direction="row" spacing={1} alignItems="center">
|
|
33
|
+
<Avatar variant="square" src={window.blocklet.appLogo} sx={{ width: 32, height: 32 }} />
|
|
34
|
+
<Typography sx={{ color: 'text.primary', fontWeight: 600 }}>{brand}</Typography>
|
|
35
|
+
{!livemode && <Livemode />}
|
|
36
|
+
</Stack>
|
|
37
|
+
{isCloumnLayout ? <UserButtons /> : null}
|
|
21
38
|
</Stack>
|
|
22
39
|
);
|
|
23
40
|
}
|
|
@@ -81,7 +81,7 @@ export default function CheckoutPay({
|
|
|
81
81
|
if (!checkoutSession) {
|
|
82
82
|
return (
|
|
83
83
|
<PaymentRoot>
|
|
84
|
-
<Stack
|
|
84
|
+
<Stack className="cko-container">
|
|
85
85
|
<Stack className="cko-overview">
|
|
86
86
|
<OverviewSkeleton />
|
|
87
87
|
</Stack>
|
|
@@ -223,7 +223,7 @@ export function CheckoutPayMain({
|
|
|
223
223
|
return (
|
|
224
224
|
<FormProvider {...methods}>
|
|
225
225
|
<PaymentRoot>
|
|
226
|
-
<Stack
|
|
226
|
+
<Stack className="cko-container">
|
|
227
227
|
<Fade in>
|
|
228
228
|
<Stack className="cko-overview" direction="column">
|
|
229
229
|
<PaymentHeader checkoutSession={state.checkoutSession} />
|
|
@@ -269,14 +269,16 @@ export function CheckoutPayMain({
|
|
|
269
269
|
export const PaymentRoot = styled(Box)`
|
|
270
270
|
box-sizing: border-box;
|
|
271
271
|
display: flex;
|
|
272
|
-
flex-
|
|
272
|
+
flex-direction: column;
|
|
273
273
|
justify-content: center;
|
|
274
|
-
|
|
274
|
+
align-items: center;
|
|
275
|
+
min-height: 100vh;
|
|
276
|
+
position: relative;
|
|
275
277
|
|
|
276
278
|
&:before {
|
|
277
279
|
animation-fill-mode: both;
|
|
278
280
|
background: #ffffff;
|
|
279
|
-
content: '
|
|
281
|
+
content: '';
|
|
280
282
|
height: 100%;
|
|
281
283
|
position: fixed;
|
|
282
284
|
right: 0;
|
|
@@ -288,21 +290,30 @@ export const PaymentRoot = styled(Box)`
|
|
|
288
290
|
|
|
289
291
|
.cko-container {
|
|
290
292
|
width: 100%;
|
|
291
|
-
max-width:
|
|
293
|
+
max-width: 1000px;
|
|
292
294
|
display: flex;
|
|
293
|
-
|
|
295
|
+
flex-direction: row;
|
|
294
296
|
justify-content: space-between;
|
|
295
297
|
position: relative;
|
|
296
|
-
|
|
298
|
+
padding: 0 16px;
|
|
297
299
|
}
|
|
298
300
|
|
|
299
301
|
.cko-overview {
|
|
300
|
-
width:
|
|
302
|
+
width: 400px;
|
|
301
303
|
min-height: 540px;
|
|
304
|
+
position: relative;
|
|
305
|
+
}
|
|
306
|
+
.cko-header {
|
|
307
|
+
left: 0;
|
|
308
|
+
margin-bottom: 0;
|
|
309
|
+
position: absolute;
|
|
310
|
+
right: 0;
|
|
311
|
+
top: 0;
|
|
312
|
+
transition: background-color 0.15s ease, box-shadow 0.15s ease-out;
|
|
302
313
|
}
|
|
303
314
|
|
|
304
315
|
.cko-payment {
|
|
305
|
-
width:
|
|
316
|
+
width: 400px;
|
|
306
317
|
.MuiInputBase-root {
|
|
307
318
|
border-radius: 0;
|
|
308
319
|
}
|
|
@@ -337,10 +348,38 @@ export const PaymentRoot = styled(Box)`
|
|
|
337
348
|
}
|
|
338
349
|
}
|
|
339
350
|
|
|
351
|
+
.cko-header {
|
|
352
|
+
}
|
|
353
|
+
|
|
340
354
|
.cko-footer {
|
|
341
355
|
position: absolute;
|
|
342
356
|
bottom: 0;
|
|
343
|
-
left:
|
|
357
|
+
left: 12px;
|
|
344
358
|
margin: 12px 0;
|
|
345
359
|
}
|
|
360
|
+
|
|
361
|
+
@media (max-width: ${({ theme }) => theme.breakpoints.values.md}px) {
|
|
362
|
+
&:before {
|
|
363
|
+
display: none;
|
|
364
|
+
}
|
|
365
|
+
.cko-container {
|
|
366
|
+
flex-direction: column;
|
|
367
|
+
align-items: center;
|
|
368
|
+
gap: 24px;
|
|
369
|
+
min-width: 350px;
|
|
370
|
+
max-width: 400px;
|
|
371
|
+
}
|
|
372
|
+
.cko-overview {
|
|
373
|
+
width: 100%;
|
|
374
|
+
min-height: auto;
|
|
375
|
+
}
|
|
376
|
+
.cko-payment {
|
|
377
|
+
width: 100%;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
.cko-footer {
|
|
381
|
+
position: static;
|
|
382
|
+
margin-top: 0;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
346
385
|
`;
|
|
@@ -23,10 +23,10 @@ ProductItem.defaultProps = {
|
|
|
23
23
|
};
|
|
24
24
|
|
|
25
25
|
export default function ProductItem({ item, session, currency, mode, children, onUpsell, onDownsell }: Props) {
|
|
26
|
-
const { t } = useLocaleContext();
|
|
27
|
-
const pricing = formatLineItemPricing(item, currency, session.subscription_data?.trial_period_days || 0);
|
|
26
|
+
const { t, locale } = useLocaleContext();
|
|
27
|
+
const pricing = formatLineItemPricing(item, currency, session.subscription_data?.trial_period_days || 0, locale);
|
|
28
28
|
const saving = formatUpsellSaving(session, currency);
|
|
29
|
-
const metered = item.price?.recurring?.usage_type === 'metered' ? '
|
|
29
|
+
const metered = item.price?.recurring?.usage_type === 'metered' ? t('common.metered') : '';
|
|
30
30
|
const canUpsell = mode === 'normal' && session.line_items.length === 1;
|
|
31
31
|
return (
|
|
32
32
|
<Stack direction="column" alignItems="flex-start" spacing={1} sx={{ width: '100%' }}>
|
|
@@ -38,7 +38,7 @@ export default function ProductItem({ item, session, currency, mode, children, o
|
|
|
38
38
|
description={item.price.product?.description}
|
|
39
39
|
extra={
|
|
40
40
|
item.price.type === 'recurring' && item.price.recurring
|
|
41
|
-
? [pricing.quantity,
|
|
41
|
+
? [pricing.quantity, t('common.billed', { rule: `${formatRecurring(item.upsell_price?.recurring || item.price.recurring, true, 'per', locale)} ${metered}` })].filter(Boolean).join(', ') // prettier-ignore
|
|
42
42
|
: pricing.quantity
|
|
43
43
|
}
|
|
44
44
|
/>
|
|
@@ -71,12 +71,12 @@ export default function ProductItem({ item, session, currency, mode, children, o
|
|
|
71
71
|
onChange={() => onUpsell(item.price_id, item.price.upsell?.upsells_to_id)}
|
|
72
72
|
/>
|
|
73
73
|
{t('checkout.upsell.save', {
|
|
74
|
-
recurring:
|
|
74
|
+
recurring: formatRecurring(item.price.upsell.upsells_to.recurring as PriceRecurring, true, 'per', locale),
|
|
75
75
|
})}
|
|
76
76
|
<Status label={t('checkout.upsell.off', { saving })} color="primary" variant="outlined" sx={{ ml: 1 }} />
|
|
77
77
|
</Typography>
|
|
78
78
|
<Typography component="span" sx={{ fontSize: 12 }}>
|
|
79
|
-
{formatPrice(item.price.upsell.upsells_to, currency, item.price.product?.unit_label)}
|
|
79
|
+
{formatPrice(item.price.upsell.upsells_to, currency, item.price.product?.unit_label, 1, true, locale)}
|
|
80
80
|
</Typography>
|
|
81
81
|
</Stack>
|
|
82
82
|
)}
|
|
@@ -102,7 +102,7 @@ export default function ProductItem({ item, session, currency, mode, children, o
|
|
|
102
102
|
})}
|
|
103
103
|
</Typography>
|
|
104
104
|
<Typography component="span" sx={{ fontSize: 12 }}>
|
|
105
|
-
{formatPrice(item.price, currency, item.price.product?.unit_label)}
|
|
105
|
+
{formatPrice(item.price, currency, item.price.product?.unit_label, 1, true, locale)}
|
|
106
106
|
</Typography>
|
|
107
107
|
</Stack>
|
|
108
108
|
)}
|
|
@@ -41,10 +41,10 @@ export default function PaymentSummary({
|
|
|
41
41
|
onApplyCrossSell,
|
|
42
42
|
onCancelCrossSell,
|
|
43
43
|
}: Props) {
|
|
44
|
-
const { t } = useLocaleContext();
|
|
44
|
+
const { t, locale } = useLocaleContext();
|
|
45
45
|
const [state, setState] = useSetState({ loading: false });
|
|
46
46
|
const { data, runAsync } = useRequest(() => fetchCrossSell(checkoutSession.id));
|
|
47
|
-
const headlines = formatCheckoutHeadlines(checkoutSession, currency);
|
|
47
|
+
const headlines = formatCheckoutHeadlines(checkoutSession, currency, locale);
|
|
48
48
|
|
|
49
49
|
const handleUpsell = async (from: string, to: string) => {
|
|
50
50
|
await onUpsell(from, to);
|
|
@@ -82,7 +82,12 @@ export default function PaymentSummary({
|
|
|
82
82
|
|
|
83
83
|
return (
|
|
84
84
|
<Fade in>
|
|
85
|
-
<Stack
|
|
85
|
+
<Stack
|
|
86
|
+
className="cko-product"
|
|
87
|
+
direction="column"
|
|
88
|
+
sx={{
|
|
89
|
+
mt: 8,
|
|
90
|
+
}}>
|
|
86
91
|
<Stack className="cko-product-summary" sx={{ mb: 4 }}>
|
|
87
92
|
<Typography sx={{ fontWeight: 500, fontSize: '1.15rem', color: 'text.secondary' }}>
|
|
88
93
|
{headlines.action}
|
|
@@ -120,7 +125,15 @@ export default function PaymentSummary({
|
|
|
120
125
|
direction="column"
|
|
121
126
|
alignItems="flex-end"
|
|
122
127
|
spacing={0.5}
|
|
123
|
-
sx={{
|
|
128
|
+
sx={{
|
|
129
|
+
border: '1px solid #eee',
|
|
130
|
+
borderRadius: 1,
|
|
131
|
+
padding: 1,
|
|
132
|
+
mt: {
|
|
133
|
+
xs: 4,
|
|
134
|
+
md: 8,
|
|
135
|
+
},
|
|
136
|
+
}}>
|
|
124
137
|
<ProductItem
|
|
125
138
|
item={{ quantity: 1, price: data, price_id: data.id, cross_sell: true } as TLineItemExpanded}
|
|
126
139
|
session={checkoutSession}
|
|
@@ -4,7 +4,6 @@ import type { Paginated, TInvoiceExpanded } from '@did-pay/types';
|
|
|
4
4
|
import { Box, Button, CircularProgress, Stack, Typography } from '@mui/material';
|
|
5
5
|
import { fromUnitToToken } from '@ocap/util';
|
|
6
6
|
import { useInfiniteScroll } from 'ahooks';
|
|
7
|
-
import React from 'react';
|
|
8
7
|
import { Link } from 'react-router-dom';
|
|
9
8
|
|
|
10
9
|
import api from '../../../libs/api';
|
|
@@ -63,12 +62,23 @@ export default function CustomerInvoiceList({ customer_id }: Props) {
|
|
|
63
62
|
const grouped = groupByDate(data.list);
|
|
64
63
|
|
|
65
64
|
return (
|
|
66
|
-
<Stack direction="column"
|
|
65
|
+
<Stack direction="column" gap={3} sx={{ mt: 1 }}>
|
|
67
66
|
{Object.entries(grouped).map(([date, invoices]) => (
|
|
68
|
-
<
|
|
69
|
-
<Typography sx={{ fontWeight: 'bold', color: 'text.secondary', mt: 2 }}>{date}</Typography>
|
|
67
|
+
<Box key={date}>
|
|
68
|
+
<Typography sx={{ fontWeight: 'bold', color: 'text.secondary', mt: 2, mb: 1 }}>{date}</Typography>
|
|
70
69
|
{invoices.map((invoice) => (
|
|
71
|
-
<Stack
|
|
70
|
+
<Stack
|
|
71
|
+
key={invoice.id}
|
|
72
|
+
direction={{
|
|
73
|
+
xs: 'column',
|
|
74
|
+
sm: 'row',
|
|
75
|
+
}}
|
|
76
|
+
gap={{
|
|
77
|
+
xs: 0.5,
|
|
78
|
+
sm: 1.5,
|
|
79
|
+
md: 4,
|
|
80
|
+
}}
|
|
81
|
+
flexWrap="nowrap">
|
|
72
82
|
<Box flex={2}>
|
|
73
83
|
<Link to={`/customer/invoice/${invoice.id}`}>
|
|
74
84
|
<Typography component="span">{invoice.number}</Typography>
|
|
@@ -91,7 +101,7 @@ export default function CustomerInvoiceList({ customer_id }: Props) {
|
|
|
91
101
|
</Box>
|
|
92
102
|
</Stack>
|
|
93
103
|
))}
|
|
94
|
-
</
|
|
104
|
+
</Box>
|
|
95
105
|
))}
|
|
96
106
|
<Box>
|
|
97
107
|
{hasMore && (
|
|
@@ -99,7 +99,15 @@ export function CurrentSubscriptionsInner({ id, onChange }: Props) {
|
|
|
99
99
|
return (
|
|
100
100
|
<Stack direction="column" spacing={4} sx={{ mt: 2 }}>
|
|
101
101
|
{data.list.map((subscription) => (
|
|
102
|
-
<Stack
|
|
102
|
+
<Stack
|
|
103
|
+
key={subscription.id}
|
|
104
|
+
direction="row"
|
|
105
|
+
justifyContent="space-between"
|
|
106
|
+
gap={{
|
|
107
|
+
xs: 1,
|
|
108
|
+
sm: 3,
|
|
109
|
+
}}
|
|
110
|
+
flexWrap="wrap">
|
|
103
111
|
<Stack direction="column" spacing={0.5}>
|
|
104
112
|
<Stack direction="row" spacing={1} alignItems="center">
|
|
105
113
|
<AvatarGroup max={3}>
|
|
@@ -13,7 +13,7 @@ type Props = {
|
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
export default function CrossSellSelect({ data, onSelect }: Props) {
|
|
16
|
-
const { t } = useLocaleContext();
|
|
16
|
+
const { t, locale } = useLocaleContext();
|
|
17
17
|
const { products } = useProductsContext();
|
|
18
18
|
const { settings } = useSettingsContext();
|
|
19
19
|
|
|
@@ -41,7 +41,7 @@ export default function CrossSellSelect({ data, onSelect }: Props) {
|
|
|
41
41
|
<MenuItem key={x.id} value={x.id}>
|
|
42
42
|
<InfoCard
|
|
43
43
|
name={x.name}
|
|
44
|
-
description={formatProductPrice(x as any, settings.baseCurrency)}
|
|
44
|
+
description={formatProductPrice(x as any, settings.baseCurrency, locale)}
|
|
45
45
|
logo={x.images[0]}
|
|
46
46
|
/>
|
|
47
47
|
</MenuItem>
|
|
@@ -14,6 +14,7 @@ import InfoRow from '../info-row';
|
|
|
14
14
|
import CrossSellSelect from './cross-sell-select';
|
|
15
15
|
|
|
16
16
|
export function CrossSellForm({ data, onChange }: { data: TProductExpanded; onChange: Function }) {
|
|
17
|
+
const { locale } = useLocaleContext();
|
|
17
18
|
const { settings } = useSettingsContext();
|
|
18
19
|
const [state, setState] = useSetState({
|
|
19
20
|
loading: false,
|
|
@@ -55,7 +56,7 @@ export function CrossSellForm({ data, onChange }: { data: TProductExpanded; onCh
|
|
|
55
56
|
<Stack spacing={1} direction="row" alignItems="center">
|
|
56
57
|
<InfoCard
|
|
57
58
|
name={to.name}
|
|
58
|
-
description={formatProductPrice(to as any, settings.baseCurrency)}
|
|
59
|
+
description={formatProductPrice(to as any, settings.baseCurrency, locale)}
|
|
59
60
|
logo={to.images[0]}
|
|
60
61
|
/>
|
|
61
62
|
<IconButton size="small" sx={{ ml: 1 }} onClick={onRemoveUpsell}>
|