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.
- package/README.md +14 -0
- package/api/src/index.ts +17 -6
- package/api/src/integrations/stripe/handlers/index.ts +53 -0
- package/api/src/integrations/stripe/handlers/invoice.ts +252 -0
- package/api/src/integrations/stripe/handlers/payment-intent.ts +172 -0
- package/api/src/integrations/stripe/handlers/setup-intent.ts +42 -0
- package/api/src/integrations/stripe/handlers/subscription.ts +61 -0
- package/api/src/integrations/stripe/resource.ts +317 -0
- package/api/src/integrations/stripe/setup.ts +50 -0
- package/api/src/jobs/invoice.ts +11 -0
- package/api/src/jobs/payment.ts +15 -7
- package/api/src/jobs/subscription.ts +18 -2
- package/api/src/libs/session.ts +104 -8
- package/api/src/libs/util.ts +47 -1
- package/api/src/routes/checkout-sessions.ts +134 -27
- package/api/src/routes/connect/collect.ts +12 -4
- package/api/src/routes/connect/pay.ts +30 -20
- package/api/src/routes/connect/setup.ts +12 -4
- package/api/src/routes/connect/shared.ts +28 -4
- package/api/src/routes/connect/subscribe.ts +12 -5
- package/api/src/routes/customers.ts +5 -5
- package/api/src/routes/events.ts +9 -6
- package/api/src/routes/index.ts +2 -0
- package/api/src/routes/integrations/stripe.ts +64 -0
- package/api/src/routes/invoices.ts +19 -9
- package/api/src/routes/payment-intents.ts +19 -9
- package/api/src/routes/payment-links.ts +57 -15
- package/api/src/routes/payment-methods.ts +98 -1
- package/api/src/routes/prices.ts +71 -14
- package/api/src/routes/products.ts +79 -22
- package/api/src/routes/settings.ts +10 -11
- package/api/src/routes/subscription-items.ts +5 -5
- package/api/src/routes/subscriptions.ts +61 -10
- package/api/src/routes/usage-records.ts +52 -18
- package/api/src/routes/webhook-attempts.ts +5 -5
- package/api/src/routes/webhook-endpoints.ts +5 -5
- package/api/src/store/migrations/20230905-genesis.ts +2 -2
- package/api/src/store/migrations/20230911-seeding.ts +4 -3
- package/api/src/store/models/checkout-session.ts +15 -7
- package/api/src/store/models/index.ts +31 -7
- package/api/src/store/models/invoice.ts +1 -1
- package/api/src/store/models/payment-intent.ts +2 -5
- package/api/src/store/models/payment-link.ts +1 -1
- package/api/src/store/models/payment-method.ts +54 -33
- package/api/src/store/models/price.ts +52 -17
- package/api/src/store/models/product.ts +0 -3
- package/api/src/store/models/subscription.ts +3 -5
- package/api/src/store/models/types.ts +56 -2
- package/api/third.d.ts +2 -0
- package/blocklet.yml +1 -1
- package/package.json +36 -29
- package/public/currencies/dai.png +0 -0
- package/public/currencies/dollar.png +0 -0
- package/public/currencies/usdc.png +0 -0
- package/public/currencies/usdt.png +0 -0
- package/public/methods/arcblock.png +0 -0
- package/public/methods/binance.png +0 -0
- package/public/methods/coinbase.png +0 -0
- package/public/methods/ethereum.jpg +0 -0
- package/public/methods/stripe.png +0 -0
- package/src/components/checkout/form/address.tsx +86 -10
- package/src/components/checkout/form/index.tsx +169 -83
- package/src/components/checkout/form/phone.tsx +96 -0
- package/src/components/checkout/form/stripe.tsx +195 -0
- package/src/components/checkout/pay.tsx +115 -34
- package/src/components/checkout/product-item.tsx +4 -3
- package/src/components/checkout/summary.tsx +5 -4
- package/src/components/drawer-form.tsx +4 -4
- package/src/components/input.tsx +22 -4
- package/src/components/invoice/table.tsx +8 -3
- package/src/components/payment-link/before-pay.tsx +11 -6
- package/src/components/payment-link/chrome.tsx +13 -0
- package/src/components/payment-link/preview.tsx +31 -0
- package/src/components/payment-link/product-select.tsx +8 -3
- package/src/components/payment-method/arcblock.tsx +53 -0
- package/src/components/payment-method/bitcoin.tsx +53 -0
- package/src/components/payment-method/ethereum.tsx +53 -0
- package/src/components/payment-method/form.tsx +54 -0
- package/src/components/payment-method/stripe.tsx +45 -0
- package/src/components/portal/invoice/list.tsx +1 -1
- package/src/components/portal/subscription/list.tsx +1 -1
- package/src/components/price/currency-select.tsx +53 -0
- package/src/components/price/form.tsx +118 -24
- package/src/components/product/add-price.tsx +1 -1
- package/src/components/product/edit-price.tsx +6 -2
- package/src/components/subscription/items/index.tsx +7 -6
- package/src/components/subscription/items/usage-records.tsx +98 -0
- package/src/components/subscription/list.tsx +3 -2
- package/src/components/subscription/status.tsx +68 -0
- package/src/contexts/settings.tsx +2 -2
- package/src/env.d.ts +2 -0
- package/src/libs/util.ts +116 -21
- package/src/locales/en.tsx +71 -3
- package/src/pages/admin/billing/invoices/detail.tsx +5 -2
- package/src/pages/admin/billing/subscriptions/detail.tsx +6 -6
- package/src/pages/admin/customers/customers/detail.tsx +13 -1
- package/src/pages/admin/payments/intents/detail.tsx +8 -3
- package/src/pages/admin/payments/links/create.tsx +23 -3
- package/src/pages/admin/payments/links/detail.tsx +13 -26
- package/src/pages/admin/products/prices/detail.tsx +55 -11
- package/src/pages/admin/products/prices/list.tsx +7 -1
- package/src/pages/admin/products/products/create.tsx +1 -1
- package/src/pages/admin/products/products/detail.tsx +14 -7
- package/src/pages/admin/settings/index.tsx +16 -6
- package/src/pages/admin/settings/payment-methods/create.tsx +81 -0
- package/src/pages/admin/settings/{payment-methods.tsx → payment-methods/index.tsx} +9 -6
- package/src/pages/checkout/pay.tsx +3 -1
- package/src/pages/customer/index.tsx +12 -1
- package/public/.gitkeep +0 -0
|
@@ -2,6 +2,7 @@ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
|
2
2
|
import type {
|
|
3
3
|
TCheckoutSessionExpanded,
|
|
4
4
|
TCustomer,
|
|
5
|
+
TPaymentCurrency,
|
|
5
6
|
TPaymentIntent,
|
|
6
7
|
TPaymentLink,
|
|
7
8
|
TPaymentMethodExpanded,
|
|
@@ -9,9 +10,11 @@ import type {
|
|
|
9
10
|
import { Box, Fade, Stack } from '@mui/material';
|
|
10
11
|
import { styled } from '@mui/system';
|
|
11
12
|
import { useEffect } from 'react';
|
|
13
|
+
import { FormProvider, useForm } from 'react-hook-form';
|
|
12
14
|
|
|
15
|
+
import { useSessionContext } from '../../contexts/session';
|
|
13
16
|
import { useSettingsContext } from '../../contexts/settings';
|
|
14
|
-
import { formatError, getStatementDescriptor } from '../../libs/util';
|
|
17
|
+
import { findCurrency, formatError, getStatementDescriptor } from '../../libs/util';
|
|
15
18
|
import PaymentError from './error';
|
|
16
19
|
import CheckoutFooter from './footer';
|
|
17
20
|
import PaymentForm from './form';
|
|
@@ -54,7 +57,6 @@ export default function CheckoutPay({
|
|
|
54
57
|
onPaid,
|
|
55
58
|
onError,
|
|
56
59
|
}: Props) {
|
|
57
|
-
const { t } = useLocaleContext();
|
|
58
60
|
const { refresh, livemode, setLivemode } = useSettingsContext();
|
|
59
61
|
|
|
60
62
|
useEffect(() => {
|
|
@@ -108,42 +110,117 @@ export default function CheckoutPay({
|
|
|
108
110
|
);
|
|
109
111
|
}
|
|
110
112
|
|
|
111
|
-
|
|
113
|
+
return (
|
|
114
|
+
<CheckoutPayMain
|
|
115
|
+
checkoutSession={checkoutSession}
|
|
116
|
+
paymentMethods={paymentMethods as TPaymentMethodExpanded[]}
|
|
117
|
+
paymentLink={paymentLink}
|
|
118
|
+
paymentIntent={paymentIntent}
|
|
119
|
+
completed={completed}
|
|
120
|
+
customer={customer as TCustomer}
|
|
121
|
+
onPaid={onPaid}
|
|
122
|
+
onError={onError}
|
|
123
|
+
/>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
type MainProps = {
|
|
128
|
+
onPaid: Function;
|
|
129
|
+
onError: Function;
|
|
130
|
+
checkoutSession: TCheckoutSessionExpanded;
|
|
131
|
+
paymentMethods: TPaymentMethodExpanded[];
|
|
132
|
+
paymentLink?: TPaymentLink;
|
|
133
|
+
paymentIntent?: TPaymentIntent;
|
|
134
|
+
customer: TCustomer;
|
|
135
|
+
completed?: boolean;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
CheckoutPayMain.defaultProps = {
|
|
139
|
+
paymentLink: null,
|
|
140
|
+
paymentIntent: null,
|
|
141
|
+
completed: false,
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export function CheckoutPayMain({
|
|
145
|
+
checkoutSession,
|
|
146
|
+
paymentMethods,
|
|
147
|
+
paymentLink,
|
|
148
|
+
paymentIntent,
|
|
149
|
+
customer,
|
|
150
|
+
completed,
|
|
151
|
+
onPaid,
|
|
152
|
+
onError,
|
|
153
|
+
}: MainProps) {
|
|
154
|
+
const { t } = useLocaleContext();
|
|
155
|
+
const { session } = useSessionContext();
|
|
156
|
+
const { settings } = useSettingsContext();
|
|
157
|
+
|
|
158
|
+
const defaultCurrencyId = checkoutSession?.currency_id || checkoutSession?.line_items[0]?.price.currency_id;
|
|
159
|
+
const defaultMethodId = paymentMethods.find((m) => m.payment_currencies.some((c) => c.id === defaultCurrencyId))?.id;
|
|
160
|
+
|
|
161
|
+
const methods = useForm({
|
|
162
|
+
defaultValues: {
|
|
163
|
+
customer_name: customer?.name || session.user?.fullName || '',
|
|
164
|
+
customer_email: customer?.email || session.user?.email || '',
|
|
165
|
+
customer_phone: customer?.phone || session.user?.phone || '',
|
|
166
|
+
payment_method: defaultMethodId,
|
|
167
|
+
payment_currency: defaultCurrencyId,
|
|
168
|
+
billing_address: Object.assign(
|
|
169
|
+
{
|
|
170
|
+
country: '',
|
|
171
|
+
state: '',
|
|
172
|
+
city: '',
|
|
173
|
+
line1: '',
|
|
174
|
+
line2: '',
|
|
175
|
+
postal_code: '',
|
|
176
|
+
},
|
|
177
|
+
customer?.address || {},
|
|
178
|
+
{ country: customer?.address?.country || 'us' }
|
|
179
|
+
),
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const currencyId = methods.watch('payment_currency') as string;
|
|
184
|
+
const currency =
|
|
185
|
+
(findCurrency(paymentMethods as TPaymentMethodExpanded[], currencyId as string) as TPaymentCurrency) ||
|
|
186
|
+
settings.baseCurrency;
|
|
112
187
|
|
|
113
188
|
return (
|
|
114
|
-
<
|
|
115
|
-
<
|
|
116
|
-
<
|
|
117
|
-
<
|
|
118
|
-
<
|
|
119
|
-
|
|
189
|
+
<FormProvider {...methods}>
|
|
190
|
+
<PaymentRoot>
|
|
191
|
+
<Stack direction="row" className="cko-container">
|
|
192
|
+
<Fade in>
|
|
193
|
+
<Stack className="cko-overview" direction="column">
|
|
194
|
+
<PaymentHeader checkoutSession={checkoutSession} />
|
|
195
|
+
<PaymentSummary checkoutSession={checkoutSession} currency={currency} />
|
|
196
|
+
</Stack>
|
|
197
|
+
</Fade>
|
|
198
|
+
<Stack className="cko-payment" direction="column" spacing={4}>
|
|
199
|
+
{completed && (
|
|
200
|
+
<PaymentSuccess
|
|
201
|
+
payee={getStatementDescriptor(checkoutSession.line_items)}
|
|
202
|
+
action={checkoutSession.mode}
|
|
203
|
+
message={
|
|
204
|
+
paymentLink?.after_completion?.hosted_confirmation?.custom_message ||
|
|
205
|
+
t(`checkout.completed.${checkoutSession.mode}`)
|
|
206
|
+
}
|
|
207
|
+
/>
|
|
208
|
+
)}
|
|
209
|
+
{!completed && (
|
|
210
|
+
<PaymentForm
|
|
211
|
+
checkoutSession={checkoutSession}
|
|
212
|
+
paymentMethods={paymentMethods as TPaymentMethodExpanded[]}
|
|
213
|
+
paymentIntent={paymentIntent}
|
|
214
|
+
customer={customer}
|
|
215
|
+
onPaid={onPaid}
|
|
216
|
+
onError={onError}
|
|
217
|
+
/>
|
|
218
|
+
)}
|
|
120
219
|
</Stack>
|
|
121
|
-
|
|
122
|
-
<Stack className="cko-payment" direction="column" spacing={4}>
|
|
123
|
-
{completed && (
|
|
124
|
-
<PaymentSuccess
|
|
125
|
-
payee={brand}
|
|
126
|
-
action={checkoutSession.mode}
|
|
127
|
-
message={
|
|
128
|
-
paymentLink?.after_completion?.hosted_confirmation?.custom_message ||
|
|
129
|
-
t(`checkout.completed.${checkoutSession.mode}`)
|
|
130
|
-
}
|
|
131
|
-
/>
|
|
132
|
-
)}
|
|
133
|
-
{!completed && (
|
|
134
|
-
<PaymentForm
|
|
135
|
-
checkoutSession={checkoutSession}
|
|
136
|
-
paymentMethods={paymentMethods as TPaymentMethodExpanded[]}
|
|
137
|
-
paymentIntent={paymentIntent}
|
|
138
|
-
customer={customer}
|
|
139
|
-
onPaid={onPaid}
|
|
140
|
-
onError={onError}
|
|
141
|
-
/>
|
|
142
|
-
)}
|
|
220
|
+
<CheckoutFooter className="cko-footer" />
|
|
143
221
|
</Stack>
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
</PaymentRoot>
|
|
222
|
+
</PaymentRoot>
|
|
223
|
+
</FormProvider>
|
|
147
224
|
);
|
|
148
225
|
}
|
|
149
226
|
|
|
@@ -198,6 +275,10 @@ export const PaymentRoot = styled(Box)`
|
|
|
198
275
|
margin: -1px 0 0 -1px;
|
|
199
276
|
}
|
|
200
277
|
|
|
278
|
+
.MuiFormHelperText-root {
|
|
279
|
+
margin-left: 14px;
|
|
280
|
+
}
|
|
281
|
+
|
|
201
282
|
.MuiOutlinedInput-notchedOutline {
|
|
202
283
|
border: none;
|
|
203
284
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { TCheckoutSessionExpanded, TLineItemExpanded } from '@did-pay/types';
|
|
1
|
+
import type { TCheckoutSessionExpanded, TLineItemExpanded, TPaymentCurrency } from '@did-pay/types';
|
|
2
2
|
import { Stack, Typography } from '@mui/material';
|
|
3
3
|
|
|
4
4
|
import { formatLineItemPricing, formatRecurring } from '../../libs/util';
|
|
@@ -7,10 +7,11 @@ import ProductCard from './product-card';
|
|
|
7
7
|
type Props = {
|
|
8
8
|
item: TLineItemExpanded;
|
|
9
9
|
session: TCheckoutSessionExpanded;
|
|
10
|
+
currency: TPaymentCurrency;
|
|
10
11
|
};
|
|
11
12
|
|
|
12
|
-
export default function ProductItem({ item, session }: Props) {
|
|
13
|
-
const pricing = formatLineItemPricing(item,
|
|
13
|
+
export default function ProductItem({ item, session, currency }: Props) {
|
|
14
|
+
const pricing = formatLineItemPricing(item, currency, session.subscription_data?.trial_period_days || 0);
|
|
14
15
|
const metered = item.price?.recurring?.usage_type === 'metered' ? ' based on usage' : '';
|
|
15
16
|
return (
|
|
16
17
|
<Stack direction="row" alignItems="center" justifyContent="space-between">
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { TCheckoutSessionExpanded, TLineItemExpanded } from '@did-pay/types';
|
|
1
|
+
import type { TCheckoutSessionExpanded, TLineItemExpanded, TPaymentCurrency } from '@did-pay/types';
|
|
2
2
|
import { Fade, Stack, Typography } from '@mui/material';
|
|
3
3
|
|
|
4
4
|
import { formatCheckoutHeadlines } from '../../libs/util';
|
|
@@ -7,10 +7,11 @@ import ProductItem from './product-item';
|
|
|
7
7
|
|
|
8
8
|
type Props = {
|
|
9
9
|
checkoutSession: TCheckoutSessionExpanded;
|
|
10
|
+
currency: TPaymentCurrency;
|
|
10
11
|
};
|
|
11
12
|
|
|
12
|
-
export default function PaymentSummary({ checkoutSession }: Props) {
|
|
13
|
-
const headlines = formatCheckoutHeadlines(checkoutSession);
|
|
13
|
+
export default function PaymentSummary({ checkoutSession, currency }: Props) {
|
|
14
|
+
const headlines = formatCheckoutHeadlines(checkoutSession, currency);
|
|
14
15
|
return (
|
|
15
16
|
<Fade in>
|
|
16
17
|
<Stack className="cko-product" direction="column" sx={{ mt: 4 }}>
|
|
@@ -25,7 +26,7 @@ export default function PaymentSummary({ checkoutSession }: Props) {
|
|
|
25
26
|
</Stack>
|
|
26
27
|
<Stack spacing={2}>
|
|
27
28
|
{checkoutSession.line_items.map((x: TLineItemExpanded) => (
|
|
28
|
-
<ProductItem key={x.price_id} item={x} session={checkoutSession} />
|
|
29
|
+
<ProductItem key={x.price_id} item={x} session={checkoutSession} currency={currency} />
|
|
29
30
|
))}
|
|
30
31
|
</Stack>
|
|
31
32
|
</Stack>
|
|
@@ -12,13 +12,13 @@ type Props = {
|
|
|
12
12
|
text: string;
|
|
13
13
|
addons: React.ReactNode;
|
|
14
14
|
children: React.ReactNode;
|
|
15
|
-
|
|
15
|
+
width?: number;
|
|
16
16
|
style?: Record<string, any>;
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
DrawerForm.defaultProps = {
|
|
20
20
|
style: {},
|
|
21
|
-
|
|
21
|
+
width: 960,
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
export default function DrawerForm(props: Props) {
|
|
@@ -38,7 +38,7 @@ export default function DrawerForm(props: Props) {
|
|
|
38
38
|
open={open}
|
|
39
39
|
onClose={() => setOpen(false)}
|
|
40
40
|
sx={props.style || {}}
|
|
41
|
-
|
|
41
|
+
width={props.width}
|
|
42
42
|
disableEscapeKeyDown>
|
|
43
43
|
<Stack
|
|
44
44
|
direction="row"
|
|
@@ -72,6 +72,6 @@ const Container = styled<any>(Drawer)`
|
|
|
72
72
|
.MuiPaper-root {
|
|
73
73
|
width: 80%;
|
|
74
74
|
min-width: 360px;
|
|
75
|
-
max-width: ${(props) => props.
|
|
75
|
+
max-width: ${(props) => props.width}px;
|
|
76
76
|
}
|
|
77
77
|
`;
|
package/src/components/input.tsx
CHANGED
|
@@ -1,21 +1,35 @@
|
|
|
1
|
-
import { Box, FormLabel, TextField, TextFieldProps } from '@mui/material';
|
|
1
|
+
import { Box, FormLabel, InputAdornment, TextField, TextFieldProps, Typography } from '@mui/material';
|
|
2
|
+
import get from 'lodash/get';
|
|
2
3
|
import { Controller, RegisterOptions, useFormContext } from 'react-hook-form';
|
|
3
4
|
|
|
4
5
|
type InputProps = TextFieldProps & {
|
|
5
6
|
name: string;
|
|
6
7
|
label?: string;
|
|
7
8
|
placeholder?: string;
|
|
9
|
+
errorPosition?: 'right' | 'bottom';
|
|
8
10
|
rules?: RegisterOptions;
|
|
9
11
|
};
|
|
10
12
|
|
|
11
13
|
FormInput.defaultProps = {
|
|
12
14
|
label: '',
|
|
13
15
|
placeholder: '',
|
|
16
|
+
errorPosition: 'bottom',
|
|
14
17
|
rules: {},
|
|
15
18
|
};
|
|
16
19
|
|
|
17
|
-
export
|
|
20
|
+
export function FormInputError({ error }: { error: string }) {
|
|
21
|
+
return (
|
|
22
|
+
<InputAdornment position="end">
|
|
23
|
+
<Typography component="span" color="error">
|
|
24
|
+
{error}
|
|
25
|
+
</Typography>
|
|
26
|
+
</InputAdornment>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default function FormInput({ name, label, placeholder, rules, errorPosition, ...rest }: InputProps) {
|
|
18
31
|
const { control, formState } = useFormContext();
|
|
32
|
+
const error = get(formState.errors, name)?.message as string;
|
|
19
33
|
return (
|
|
20
34
|
<Controller
|
|
21
35
|
name={name}
|
|
@@ -26,12 +40,16 @@ export default function FormInput({ name, label, placeholder, rules, ...rest }:
|
|
|
26
40
|
{!!label && <FormLabel>{label}</FormLabel>}
|
|
27
41
|
<TextField
|
|
28
42
|
fullWidth
|
|
29
|
-
error={!!formState.errors
|
|
30
|
-
helperText={
|
|
43
|
+
error={!!get(formState.errors, name)}
|
|
44
|
+
helperText={errorPosition === 'bottom' && error ? error : ''}
|
|
31
45
|
placeholder={placeholder}
|
|
32
46
|
size="small"
|
|
33
47
|
{...field}
|
|
34
48
|
{...rest}
|
|
49
|
+
InputProps={Object.assign(
|
|
50
|
+
rest.InputProps || {},
|
|
51
|
+
errorPosition === 'right' && error ? { endAdornment: <FormInputError error={error} /> } : {}
|
|
52
|
+
)}
|
|
35
53
|
/>
|
|
36
54
|
</Box>
|
|
37
55
|
)}
|
|
@@ -2,7 +2,7 @@ import type { TInvoiceExpanded } from '@did-pay/types';
|
|
|
2
2
|
import { Table, TableBody, TableCell, TableHead, TableRow, Typography } from '@mui/material';
|
|
3
3
|
import { styled } from '@mui/system';
|
|
4
4
|
|
|
5
|
-
import { formatAmount, formatToDate } from '../../libs/util';
|
|
5
|
+
import { formatAmount, formatToDate, getPriceUintAmountByCurrency } from '../../libs/util';
|
|
6
6
|
import LineItemActions from '../subscription/items/actions';
|
|
7
7
|
|
|
8
8
|
type Props = {
|
|
@@ -53,7 +53,12 @@ export default function InvoiceTable({ invoice, simple }: Props) {
|
|
|
53
53
|
{line.price.product.unit_label ? ` (per ${line.price.product.unit_label})` : ''}
|
|
54
54
|
</TableCell>
|
|
55
55
|
<TableCell align="right">{line.quantity}</TableCell>
|
|
56
|
-
<TableCell align="right">
|
|
56
|
+
<TableCell align="right">
|
|
57
|
+
{formatAmount(
|
|
58
|
+
getPriceUintAmountByCurrency(line.price, invoice.paymentCurrency),
|
|
59
|
+
invoice.paymentCurrency.decimal
|
|
60
|
+
)}
|
|
61
|
+
</TableCell>
|
|
57
62
|
<TableCell align="right">{formatAmount(line.amount, invoice.paymentCurrency.decimal)}</TableCell>
|
|
58
63
|
{!simple && (
|
|
59
64
|
<TableCell align="right">
|
|
@@ -94,7 +99,7 @@ export default function InvoiceTable({ invoice, simple }: Props) {
|
|
|
94
99
|
Amount Due
|
|
95
100
|
</TableCell>
|
|
96
101
|
<TableCell align="right" sx={{ fontWeight: 600 }}>
|
|
97
|
-
{formatAmount(invoice.
|
|
102
|
+
{formatAmount(invoice.amount_remaining, invoice.paymentCurrency.decimal)}
|
|
98
103
|
</TableCell>
|
|
99
104
|
<TableCell> </TableCell>
|
|
100
105
|
</TableRow>
|
|
@@ -5,7 +5,7 @@ import { useEffect, useState } from 'react';
|
|
|
5
5
|
import { Controller, useFieldArray, useFormContext, useWatch } from 'react-hook-form';
|
|
6
6
|
|
|
7
7
|
import { useProductsContext } from '../../contexts/products';
|
|
8
|
-
import { getProductByPriceId,
|
|
8
|
+
import { getProductByPriceId, isPriceAligned } from '../../libs/util';
|
|
9
9
|
import CreateProduct from '../product/create';
|
|
10
10
|
import LineItem from './item';
|
|
11
11
|
import ProductSelect from './product-select';
|
|
@@ -53,11 +53,11 @@ export default function BeforePay() {
|
|
|
53
53
|
};
|
|
54
54
|
|
|
55
55
|
return (
|
|
56
|
-
<Stack spacing={2}>
|
|
56
|
+
<Stack spacing={2} sx={{ width: '100%' }}>
|
|
57
57
|
<Typography variant="h6" sx={{ fontWeight: 600 }}>
|
|
58
58
|
{t('admin.paymentLink.products')} ({getValues().line_items.length})
|
|
59
59
|
</Typography>
|
|
60
|
-
<Stack spacing={2}>
|
|
60
|
+
<Stack spacing={2} sx={{ width: '100%' }}>
|
|
61
61
|
{items.fields.map((item, index) => {
|
|
62
62
|
// @ts-ignore
|
|
63
63
|
const product = getProductByPriceId(products, item.price_id);
|
|
@@ -69,7 +69,7 @@ export default function BeforePay() {
|
|
|
69
69
|
<LineItem
|
|
70
70
|
key={item.id}
|
|
71
71
|
// @ts-ignore
|
|
72
|
-
valid={
|
|
72
|
+
valid={isPriceAligned(items.fields, products, index).aligned}
|
|
73
73
|
prefix={`line_items.${index}`}
|
|
74
74
|
product={product}
|
|
75
75
|
onRemove={() => items.remove(index)}
|
|
@@ -77,9 +77,14 @@ export default function BeforePay() {
|
|
|
77
77
|
/>
|
|
78
78
|
);
|
|
79
79
|
})}
|
|
80
|
-
{items.fields.some((_, index) => !
|
|
80
|
+
{items.fields.some((_, index) => !isPriceAligned(items.fields as any[], products, index).recurring) && (
|
|
81
81
|
<Typography color="error" fontSize="small">
|
|
82
|
-
{t('admin.paymentLink.
|
|
82
|
+
{t('admin.paymentLink.recurringNotAligned')}
|
|
83
|
+
</Typography>
|
|
84
|
+
)}
|
|
85
|
+
{items.fields.some((_, index) => !isPriceAligned(items.fields as any[], products, index).currency) && (
|
|
86
|
+
<Typography color="error" fontSize="small">
|
|
87
|
+
{t('admin.paymentLink.currencyNotAligned')}
|
|
83
88
|
</Typography>
|
|
84
89
|
)}
|
|
85
90
|
<ProductSelect
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Box } from '@mui/material';
|
|
2
|
+
import { styled } from '@mui/system';
|
|
3
|
+
|
|
4
|
+
const Chrome = styled(Box)`
|
|
5
|
+
background-color: #fcfeff;
|
|
6
|
+
border-radius: 8px;
|
|
7
|
+
margin-top: 40px;
|
|
8
|
+
position: relative;
|
|
9
|
+
overflow: hidden;
|
|
10
|
+
box-shadow: 0 20px 44px #32325d1f, 0 -1px 32px #32325d0f, 0 3px 12px #00000014;
|
|
11
|
+
`;
|
|
12
|
+
|
|
13
|
+
export default Chrome;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Box } from '@mui/material';
|
|
2
|
+
import { useSize } from 'ahooks';
|
|
3
|
+
import IframeResizer from 'iframe-resizer-react';
|
|
4
|
+
import { useRef } from 'react';
|
|
5
|
+
|
|
6
|
+
import Chrome from './chrome';
|
|
7
|
+
|
|
8
|
+
PaymentLinkPreview.defaultProps = {
|
|
9
|
+
version: 1,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default function PaymentLinkPreview({ id, version }: { id: string; version?: number }) {
|
|
13
|
+
const ref = useRef(null);
|
|
14
|
+
const size = useSize(ref);
|
|
15
|
+
return (
|
|
16
|
+
<Chrome ref={ref}>
|
|
17
|
+
<Box sx={{ width: '100%' }}> </Box>
|
|
18
|
+
<IframeResizer
|
|
19
|
+
style={{
|
|
20
|
+
width: '1px',
|
|
21
|
+
minWidth: size?.width,
|
|
22
|
+
minHeight: '64vh',
|
|
23
|
+
transform: 'scale(0.8)',
|
|
24
|
+
transformOrigin: 'center',
|
|
25
|
+
border: 'none',
|
|
26
|
+
}}
|
|
27
|
+
src={`${window.blocklet.prefix}checkout/pay/${id}?preview=1&version=${version}`}
|
|
28
|
+
/>
|
|
29
|
+
</Chrome>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -8,7 +8,7 @@ import type { LiteralUnion } from 'type-fest';
|
|
|
8
8
|
|
|
9
9
|
import { useProductsContext } from '../../contexts/products';
|
|
10
10
|
import { useSettingsContext } from '../../contexts/settings';
|
|
11
|
-
import { formatPrice } from '../../libs/util';
|
|
11
|
+
import { formatPrice, getPriceCurrencyOptions } from '../../libs/util';
|
|
12
12
|
|
|
13
13
|
type Props = {
|
|
14
14
|
mode: LiteralUnion<'waiting' | 'selecting', string>;
|
|
@@ -39,7 +39,7 @@ export default function ProductSelect({ mode: initialMode, hasSelected, onSelect
|
|
|
39
39
|
|
|
40
40
|
if (mode === 'selecting') {
|
|
41
41
|
return (
|
|
42
|
-
<Select value="" fullWidth size="small" onChange={handleSelect}>
|
|
42
|
+
<Select value="" fullWidth size="small" onChange={handleSelect} MenuProps={{ style: { maxHeight: 480 } }}>
|
|
43
43
|
<MenuItem value="add">
|
|
44
44
|
<AddOutlined />
|
|
45
45
|
{t('admin.product.add')}
|
|
@@ -50,7 +50,12 @@ export default function ProductSelect({ mode: initialMode, hasSelected, onSelect
|
|
|
50
50
|
</ListSubheader>,
|
|
51
51
|
...product.prices.map((price) => (
|
|
52
52
|
<MenuItem key={price.id} sx={{ pl: 3 }} value={price.id}>
|
|
53
|
-
{formatPrice(price, settings.baseCurrency)}
|
|
53
|
+
<Typography color="text.primary">{formatPrice(price, settings.baseCurrency)}</Typography>
|
|
54
|
+
<Typography color="text.secondary" sx={{ ml: 2 }}>
|
|
55
|
+
{getPriceCurrencyOptions(price).length > 1
|
|
56
|
+
? ` +${getPriceCurrencyOptions(price).length - 1} more currencies`
|
|
57
|
+
: ''}
|
|
58
|
+
</Typography>
|
|
54
59
|
</MenuItem>
|
|
55
60
|
)),
|
|
56
61
|
])}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/* eslint-disable no-nested-ternary */
|
|
2
|
+
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
3
|
+
|
|
4
|
+
import FormInput from '../input';
|
|
5
|
+
|
|
6
|
+
export default function ArcBlockMethodForm() {
|
|
7
|
+
const { t } = useLocaleContext();
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<>
|
|
11
|
+
<FormInput
|
|
12
|
+
key="name"
|
|
13
|
+
name="name"
|
|
14
|
+
type="text"
|
|
15
|
+
rules={{ required: true }}
|
|
16
|
+
label={t('admin.paymentMethod.name.label')}
|
|
17
|
+
placeholder={t('admin.paymentMethod.name.tip')}
|
|
18
|
+
/>
|
|
19
|
+
<FormInput
|
|
20
|
+
key="description"
|
|
21
|
+
name="description"
|
|
22
|
+
type="text"
|
|
23
|
+
rules={{ required: true }}
|
|
24
|
+
label={t('admin.paymentMethod.description.label')}
|
|
25
|
+
placeholder={t('admin.paymentMethod.description.tip')}
|
|
26
|
+
/>
|
|
27
|
+
<FormInput
|
|
28
|
+
key="secret_key"
|
|
29
|
+
name="settings.arcblock.chain_id"
|
|
30
|
+
type="text"
|
|
31
|
+
rules={{ required: true }}
|
|
32
|
+
label={t('admin.paymentMethod.arcblock.chain_id.label')}
|
|
33
|
+
placeholder={t('admin.paymentMethod.arcblock.chain_id.tip')}
|
|
34
|
+
/>
|
|
35
|
+
<FormInput
|
|
36
|
+
key="api_host"
|
|
37
|
+
name="settings.arcblock.api_host"
|
|
38
|
+
type="text"
|
|
39
|
+
rules={{ required: true }}
|
|
40
|
+
label={t('admin.paymentMethod.arcblock.api_host.label')}
|
|
41
|
+
placeholder={t('admin.paymentMethod.arcblock.api_host.tip')}
|
|
42
|
+
/>
|
|
43
|
+
<FormInput
|
|
44
|
+
key="explorer_host"
|
|
45
|
+
name="settings.arcblock.explorer_host"
|
|
46
|
+
type="text"
|
|
47
|
+
rules={{ required: true }}
|
|
48
|
+
label={t('admin.paymentMethod.arcblock.explorer_host.label')}
|
|
49
|
+
placeholder={t('admin.paymentMethod.arcblock.explorer_host.tip')}
|
|
50
|
+
/>
|
|
51
|
+
</>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/* eslint-disable no-nested-ternary */
|
|
2
|
+
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
3
|
+
|
|
4
|
+
import FormInput from '../input';
|
|
5
|
+
|
|
6
|
+
export default function BitcoinMethodForm() {
|
|
7
|
+
const { t } = useLocaleContext();
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<>
|
|
11
|
+
<FormInput
|
|
12
|
+
key="name"
|
|
13
|
+
name="name"
|
|
14
|
+
type="text"
|
|
15
|
+
rules={{ required: true }}
|
|
16
|
+
label={t('admin.paymentMethod.name.label')}
|
|
17
|
+
placeholder={t('admin.paymentMethod.name.tip')}
|
|
18
|
+
/>
|
|
19
|
+
<FormInput
|
|
20
|
+
key="description"
|
|
21
|
+
name="description"
|
|
22
|
+
type="text"
|
|
23
|
+
rules={{ required: true }}
|
|
24
|
+
label={t('admin.paymentMethod.description.label')}
|
|
25
|
+
placeholder={t('admin.paymentMethod.description.tip')}
|
|
26
|
+
/>
|
|
27
|
+
<FormInput
|
|
28
|
+
key="secret_key"
|
|
29
|
+
name="settings.bitcoin.chain_id"
|
|
30
|
+
type="text"
|
|
31
|
+
rules={{ required: true }}
|
|
32
|
+
label={t('admin.paymentMethod.bitcoin.chain_id.label')}
|
|
33
|
+
placeholder={t('admin.paymentMethod.bitcoin.chain_id.tip')}
|
|
34
|
+
/>
|
|
35
|
+
<FormInput
|
|
36
|
+
key="api_host"
|
|
37
|
+
name="settings.bitcoin.api_host"
|
|
38
|
+
type="text"
|
|
39
|
+
rules={{ required: true }}
|
|
40
|
+
label={t('admin.paymentMethod.bitcoin.api_host.label')}
|
|
41
|
+
placeholder={t('admin.paymentMethod.bitcoin.api_host.tip')}
|
|
42
|
+
/>
|
|
43
|
+
<FormInput
|
|
44
|
+
key="explorer_host"
|
|
45
|
+
name="settings.bitcoin.explorer_host"
|
|
46
|
+
type="text"
|
|
47
|
+
rules={{ required: true }}
|
|
48
|
+
label={t('admin.paymentMethod.bitcoin.explorer_host.label')}
|
|
49
|
+
placeholder={t('admin.paymentMethod.bitcoin.explorer_host.tip')}
|
|
50
|
+
/>
|
|
51
|
+
</>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/* eslint-disable no-nested-ternary */
|
|
2
|
+
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
3
|
+
|
|
4
|
+
import FormInput from '../input';
|
|
5
|
+
|
|
6
|
+
export default function EthereumMethodForm() {
|
|
7
|
+
const { t } = useLocaleContext();
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<>
|
|
11
|
+
<FormInput
|
|
12
|
+
key="name"
|
|
13
|
+
name="name"
|
|
14
|
+
type="text"
|
|
15
|
+
rules={{ required: true }}
|
|
16
|
+
label={t('admin.paymentMethod.name.label')}
|
|
17
|
+
placeholder={t('admin.paymentMethod.name.tip')}
|
|
18
|
+
/>
|
|
19
|
+
<FormInput
|
|
20
|
+
key="description"
|
|
21
|
+
name="description"
|
|
22
|
+
type="text"
|
|
23
|
+
rules={{ required: true }}
|
|
24
|
+
label={t('admin.paymentMethod.description.label')}
|
|
25
|
+
placeholder={t('admin.paymentMethod.description.tip')}
|
|
26
|
+
/>
|
|
27
|
+
<FormInput
|
|
28
|
+
key="secret_key"
|
|
29
|
+
name="settings.ethereum.chain_id"
|
|
30
|
+
type="text"
|
|
31
|
+
rules={{ required: true }}
|
|
32
|
+
label={t('admin.paymentMethod.ethereum.chain_id.label')}
|
|
33
|
+
placeholder={t('admin.paymentMethod.ethereum.chain_id.tip')}
|
|
34
|
+
/>
|
|
35
|
+
<FormInput
|
|
36
|
+
key="api_host"
|
|
37
|
+
name="settings.ethereum.api_host"
|
|
38
|
+
type="text"
|
|
39
|
+
rules={{ required: true }}
|
|
40
|
+
label={t('admin.paymentMethod.ethereum.api_host.label')}
|
|
41
|
+
placeholder={t('admin.paymentMethod.ethereum.api_host.tip')}
|
|
42
|
+
/>
|
|
43
|
+
<FormInput
|
|
44
|
+
key="explorer_host"
|
|
45
|
+
name="settings.ethereum.explorer_host"
|
|
46
|
+
type="text"
|
|
47
|
+
rules={{ required: true }}
|
|
48
|
+
label={t('admin.paymentMethod.ethereum.explorer_host.label')}
|
|
49
|
+
placeholder={t('admin.paymentMethod.ethereum.explorer_host.tip')}
|
|
50
|
+
/>
|
|
51
|
+
</>
|
|
52
|
+
);
|
|
53
|
+
}
|