payment-kit 1.18.51 → 1.18.53
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/invoices.ts +47 -0
- package/blocklet.yml +1 -1
- package/package.json +22 -22
- package/src/components/invoice/action.tsx +20 -0
- package/src/components/invoice/list.tsx +4 -1
- package/src/components/payment-intent/list.tsx +2 -4
- package/src/components/payouts/portal/list.tsx +6 -1
- package/src/locales/en.tsx +5 -0
- package/src/locales/zh.tsx +5 -0
- package/src/pages/customer/index.tsx +10 -10
- package/src/pages/customer/subscription/embed.tsx +25 -15
|
@@ -12,6 +12,7 @@ import { createListParamSchema, getOrder, getWhereFromKvQuery, MetadataSchema }
|
|
|
12
12
|
import { authenticate } from '../libs/security';
|
|
13
13
|
import { expandLineItems } from '../libs/session';
|
|
14
14
|
import { formatMetadata, getBlockletJson, getUserOrAppInfo } from '../libs/util';
|
|
15
|
+
import dayjs from '../libs/dayjs';
|
|
15
16
|
import { Customer } from '../store/models/customer';
|
|
16
17
|
import { Invoice } from '../store/models/invoice';
|
|
17
18
|
import { InvoiceItem } from '../store/models/invoice-item';
|
|
@@ -671,4 +672,50 @@ router.put('/:id', authAdmin, async (req, res) => {
|
|
|
671
672
|
}
|
|
672
673
|
});
|
|
673
674
|
|
|
675
|
+
router.post('/:id/void', authAdmin, async (req, res) => {
|
|
676
|
+
const invoice = await Invoice.findByPk(req.params.id as string);
|
|
677
|
+
if (!invoice) {
|
|
678
|
+
return res.status(404).json({ error: 'Invoice not found' });
|
|
679
|
+
}
|
|
680
|
+
if (['paid', 'void', 'draft'].includes(invoice.status)) {
|
|
681
|
+
return res.status(400).json({ error: 'Can not void this invoice' });
|
|
682
|
+
}
|
|
683
|
+
const paymentMethod = await PaymentMethod.findByPk(invoice.default_payment_method_id);
|
|
684
|
+
if (!paymentMethod) {
|
|
685
|
+
return res.status(400).json({ error: 'Payment method not found' });
|
|
686
|
+
}
|
|
687
|
+
if (invoice.subscription_id) {
|
|
688
|
+
const subscription = await Subscription.findByPk(invoice.subscription_id);
|
|
689
|
+
if (subscription && !subscription.isImmutable()) {
|
|
690
|
+
return res.status(400).json({ error: 'Subscription is not immutable, can not void invoice' });
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
try {
|
|
694
|
+
if (invoice.payment_intent_id) {
|
|
695
|
+
const paymentIntent = await PaymentIntent.findByPk(invoice.payment_intent_id);
|
|
696
|
+
if (paymentIntent && paymentIntent.status !== 'canceled') {
|
|
697
|
+
await paymentIntent.update({
|
|
698
|
+
status: 'canceled',
|
|
699
|
+
canceled_at: dayjs().unix(),
|
|
700
|
+
cancellation_reason: 'void_invoice',
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
if (paymentMethod.type === 'stripe' && invoice.metadata?.stripe_id) {
|
|
705
|
+
const client = paymentMethod.getStripeClient();
|
|
706
|
+
await client.invoices.voidInvoice(invoice.metadata.stripe_id);
|
|
707
|
+
}
|
|
708
|
+
await invoice.update({
|
|
709
|
+
status: 'void',
|
|
710
|
+
status_transitions: {
|
|
711
|
+
...(invoice.status_transitions || {}),
|
|
712
|
+
voided_at: dayjs().unix(),
|
|
713
|
+
},
|
|
714
|
+
});
|
|
715
|
+
return res.json(invoice);
|
|
716
|
+
} catch (error) {
|
|
717
|
+
logger.error('Failed to void invoice', { error, invoiceId: invoice.id });
|
|
718
|
+
return res.status(400).json({ error: 'Failed to void invoice' });
|
|
719
|
+
}
|
|
720
|
+
});
|
|
674
721
|
export default router;
|
package/blocklet.yml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payment-kit",
|
|
3
|
-
"version": "1.18.
|
|
3
|
+
"version": "1.18.53",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "blocklet dev --open",
|
|
6
6
|
"eject": "vite eject",
|
|
@@ -44,31 +44,31 @@
|
|
|
44
44
|
]
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@abtnode/cron": "^1.16.
|
|
48
|
-
"@arcblock/did": "^1.20.
|
|
47
|
+
"@abtnode/cron": "^1.16.44",
|
|
48
|
+
"@arcblock/did": "^1.20.13",
|
|
49
49
|
"@arcblock/did-auth-storage-nedb": "^1.7.1",
|
|
50
|
-
"@arcblock/did-connect": "^2.13.
|
|
51
|
-
"@arcblock/did-util": "^1.20.
|
|
52
|
-
"@arcblock/jwt": "^1.20.
|
|
53
|
-
"@arcblock/ux": "^2.13.
|
|
54
|
-
"@arcblock/validator": "^1.20.
|
|
50
|
+
"@arcblock/did-connect": "^2.13.62",
|
|
51
|
+
"@arcblock/did-util": "^1.20.13",
|
|
52
|
+
"@arcblock/jwt": "^1.20.13",
|
|
53
|
+
"@arcblock/ux": "^2.13.62",
|
|
54
|
+
"@arcblock/validator": "^1.20.13",
|
|
55
55
|
"@blocklet/did-space-js": "^1.0.57",
|
|
56
|
-
"@blocklet/js-sdk": "^1.16.
|
|
57
|
-
"@blocklet/logger": "^1.16.
|
|
58
|
-
"@blocklet/payment-react": "1.18.
|
|
59
|
-
"@blocklet/sdk": "^1.16.
|
|
60
|
-
"@blocklet/ui-react": "^2.13.
|
|
56
|
+
"@blocklet/js-sdk": "^1.16.44",
|
|
57
|
+
"@blocklet/logger": "^1.16.44",
|
|
58
|
+
"@blocklet/payment-react": "1.18.53",
|
|
59
|
+
"@blocklet/sdk": "^1.16.44",
|
|
60
|
+
"@blocklet/ui-react": "^2.13.62",
|
|
61
61
|
"@blocklet/uploader": "^0.1.95",
|
|
62
62
|
"@blocklet/xss": "^0.1.36",
|
|
63
63
|
"@mui/icons-material": "^5.16.6",
|
|
64
64
|
"@mui/lab": "^5.0.0-alpha.173",
|
|
65
65
|
"@mui/material": "^5.16.6",
|
|
66
66
|
"@mui/system": "^5.16.6",
|
|
67
|
-
"@ocap/asset": "^1.20.
|
|
68
|
-
"@ocap/client": "^1.20.
|
|
69
|
-
"@ocap/mcrypto": "^1.20.
|
|
70
|
-
"@ocap/util": "^1.20.
|
|
71
|
-
"@ocap/wallet": "^1.20.
|
|
67
|
+
"@ocap/asset": "^1.20.13",
|
|
68
|
+
"@ocap/client": "^1.20.13",
|
|
69
|
+
"@ocap/mcrypto": "^1.20.13",
|
|
70
|
+
"@ocap/util": "^1.20.13",
|
|
71
|
+
"@ocap/wallet": "^1.20.13",
|
|
72
72
|
"@stripe/react-stripe-js": "^2.7.3",
|
|
73
73
|
"@stripe/stripe-js": "^2.4.0",
|
|
74
74
|
"ahooks": "^3.8.0",
|
|
@@ -121,9 +121,9 @@
|
|
|
121
121
|
"web3": "^4.16.0"
|
|
122
122
|
},
|
|
123
123
|
"devDependencies": {
|
|
124
|
-
"@abtnode/types": "^1.16.
|
|
124
|
+
"@abtnode/types": "^1.16.44",
|
|
125
125
|
"@arcblock/eslint-config-ts": "^0.3.3",
|
|
126
|
-
"@blocklet/payment-types": "1.18.
|
|
126
|
+
"@blocklet/payment-types": "1.18.53",
|
|
127
127
|
"@types/cookie-parser": "^1.4.7",
|
|
128
128
|
"@types/cors": "^2.8.17",
|
|
129
129
|
"@types/debug": "^4.1.12",
|
|
@@ -153,7 +153,7 @@
|
|
|
153
153
|
"vite": "^5.3.5",
|
|
154
154
|
"vite-node": "^2.0.4",
|
|
155
155
|
"vite-plugin-babel-import": "^2.0.5",
|
|
156
|
-
"vite-plugin-blocklet": "^0.9.
|
|
156
|
+
"vite-plugin-blocklet": "^0.9.33",
|
|
157
157
|
"vite-plugin-node-polyfills": "^0.21.0",
|
|
158
158
|
"vite-plugin-svgr": "^4.2.0",
|
|
159
159
|
"vite-tsconfig-paths": "^4.3.2",
|
|
@@ -169,5 +169,5 @@
|
|
|
169
169
|
"parser": "typescript"
|
|
170
170
|
}
|
|
171
171
|
},
|
|
172
|
-
"gitHead": "
|
|
172
|
+
"gitHead": "39ab3a392c2367ceff0441fa968c442074827d92"
|
|
173
173
|
}
|
|
@@ -68,6 +68,10 @@ export default function InvoiceActions({ data, variant, onChange, mode }: Props)
|
|
|
68
68
|
Toast.error(result.error);
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
|
+
if (state.action === 'void') {
|
|
72
|
+
await api.post(`/api/invoices/${data.id}/void`).then((res) => res.data);
|
|
73
|
+
Toast.success(t('admin.invoice.void.success'));
|
|
74
|
+
}
|
|
71
75
|
onChange(state.action);
|
|
72
76
|
} catch (err) {
|
|
73
77
|
console.error(err);
|
|
@@ -99,6 +103,13 @@ export default function InvoiceActions({ data, variant, onChange, mode }: Props)
|
|
|
99
103
|
color: 'primary',
|
|
100
104
|
divider: true,
|
|
101
105
|
},
|
|
106
|
+
isAdmin &&
|
|
107
|
+
!['paid', 'void', 'draft'].includes(data.status) && {
|
|
108
|
+
label: t('admin.invoice.void.title'),
|
|
109
|
+
handler: () => setState({ action: 'void' }),
|
|
110
|
+
color: 'primary',
|
|
111
|
+
divider: true,
|
|
112
|
+
},
|
|
102
113
|
{
|
|
103
114
|
label: t('admin.customer.view'),
|
|
104
115
|
handler: () => {
|
|
@@ -138,6 +149,15 @@ export default function InvoiceActions({ data, variant, onChange, mode }: Props)
|
|
|
138
149
|
loading={state.loading}
|
|
139
150
|
/>
|
|
140
151
|
)}
|
|
152
|
+
{state.action === 'void' && (
|
|
153
|
+
<ConfirmDialog
|
|
154
|
+
onConfirm={handleAction}
|
|
155
|
+
onCancel={() => setState({ action: '' })}
|
|
156
|
+
title={t('admin.invoice.void.title')}
|
|
157
|
+
message={t('admin.invoice.void.tip')}
|
|
158
|
+
loading={state.loading}
|
|
159
|
+
/>
|
|
160
|
+
)}
|
|
141
161
|
</ClickBoundary>
|
|
142
162
|
);
|
|
143
163
|
}
|
|
@@ -174,7 +174,10 @@ export default function InvoiceList({
|
|
|
174
174
|
const item = data.list[index] as TInvoiceExpanded;
|
|
175
175
|
return (
|
|
176
176
|
<InvoiceLink invoice={item}>
|
|
177
|
-
<Typography
|
|
177
|
+
<Typography
|
|
178
|
+
component="strong"
|
|
179
|
+
fontWeight={600}
|
|
180
|
+
sx={{ textDecoration: item.status === 'void' ? 'line-through' : 'none' }}>
|
|
178
181
|
{formatBNStr(item?.total, item?.paymentCurrency.decimal)}
|
|
179
182
|
{item?.paymentCurrency.symbol}
|
|
180
183
|
</Typography>
|
|
@@ -117,12 +117,10 @@ export default function PaymentList({ customer_id, invoice_id, features }: ListP
|
|
|
117
117
|
options: {
|
|
118
118
|
customBodyRenderLite: (_: string, index: number) => {
|
|
119
119
|
const item = data.list[index] as TPaymentIntentExpanded;
|
|
120
|
+
const highlight = item.amount_received === '0' && item.status !== 'canceled';
|
|
120
121
|
return (
|
|
121
122
|
<Link to={`/admin/payments/${item.id}`}>
|
|
122
|
-
<Typography
|
|
123
|
-
component="strong"
|
|
124
|
-
sx={{ color: item.amount_received === '0' ? 'warning.main' : 'inherit' }}
|
|
125
|
-
fontWeight={600}>
|
|
123
|
+
<Typography component="strong" sx={{ color: highlight ? 'warning.main' : 'inherit' }} fontWeight={600}>
|
|
126
124
|
{formatBNStr(
|
|
127
125
|
item.amount_received === '0' ? item.amount : item.amount_received,
|
|
128
126
|
item?.paymentCurrency.decimal
|
|
@@ -47,6 +47,7 @@ type ListProps = {
|
|
|
47
47
|
status?: string;
|
|
48
48
|
customer_id?: string;
|
|
49
49
|
currency_id?: string;
|
|
50
|
+
setHasRevenues?: (hasRevenues: boolean) => void;
|
|
50
51
|
};
|
|
51
52
|
|
|
52
53
|
const getListKey = (props: ListProps) => {
|
|
@@ -60,9 +61,10 @@ CustomerRevenueList.defaultProps = {
|
|
|
60
61
|
status: '',
|
|
61
62
|
currency_id: '',
|
|
62
63
|
customer_id: '',
|
|
64
|
+
setHasRevenues: () => {},
|
|
63
65
|
};
|
|
64
66
|
|
|
65
|
-
export default function CustomerRevenueList({ currency_id, status, customer_id }: ListProps) {
|
|
67
|
+
export default function CustomerRevenueList({ currency_id, status, customer_id, setHasRevenues }: ListProps) {
|
|
66
68
|
const { t } = useLocaleContext();
|
|
67
69
|
const { isMobile } = useMobile('sm');
|
|
68
70
|
|
|
@@ -87,6 +89,9 @@ export default function CustomerRevenueList({ currency_id, status, customer_id }
|
|
|
87
89
|
debounce(() => {
|
|
88
90
|
fetchData(search).then((res: any) => {
|
|
89
91
|
setData(res);
|
|
92
|
+
if (setHasRevenues) {
|
|
93
|
+
setHasRevenues(res.count > 0);
|
|
94
|
+
}
|
|
90
95
|
});
|
|
91
96
|
}, 300)();
|
|
92
97
|
}, [search]);
|
package/src/locales/en.tsx
CHANGED
|
@@ -524,6 +524,11 @@ export default flat({
|
|
|
524
524
|
tip: 'Are you sure you want to return the stake? This action will return the stake to the customer immediately.',
|
|
525
525
|
success: 'Stake return application has been successfully created',
|
|
526
526
|
},
|
|
527
|
+
void: {
|
|
528
|
+
title: 'Void Invoice',
|
|
529
|
+
tip: 'Are you sure you want to void this invoice? This action will immediately void the invoice.',
|
|
530
|
+
success: 'Invoice voided',
|
|
531
|
+
},
|
|
527
532
|
},
|
|
528
533
|
subscription: {
|
|
529
534
|
view: 'View subscription',
|
package/src/locales/zh.tsx
CHANGED
|
@@ -197,6 +197,7 @@ export default function CustomerHome() {
|
|
|
197
197
|
const navigate = useNavigate();
|
|
198
198
|
const [subscriptionStatus, setSubscriptionStatus] = useState(false);
|
|
199
199
|
const [hasSubscriptions, setHasSubscriptions] = useState(false);
|
|
200
|
+
const [hasRevenues, setHasRevenues] = useState(false);
|
|
200
201
|
const { startTransition } = useTransitionContext();
|
|
201
202
|
const {
|
|
202
203
|
data,
|
|
@@ -433,7 +434,7 @@ export default function CustomerHome() {
|
|
|
433
434
|
);
|
|
434
435
|
|
|
435
436
|
const InvoiceCard = loadingCard ? (
|
|
436
|
-
<CardSkeleton height={
|
|
437
|
+
<CardSkeleton height={200} />
|
|
437
438
|
) : (
|
|
438
439
|
<Box className="base-card section section-invoices">
|
|
439
440
|
<Box className="section-header">
|
|
@@ -460,16 +461,15 @@ export default function CustomerHome() {
|
|
|
460
461
|
</Box>
|
|
461
462
|
);
|
|
462
463
|
|
|
463
|
-
const RevenueCard =
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
464
|
+
const RevenueCard =
|
|
465
|
+
loadingCard || !hasRevenues ? null : (
|
|
466
|
+
<Box className="base-card section section-revenue">
|
|
467
|
+
<Box className="section-header">
|
|
468
|
+
<Typography variant="h3">{t('customer.payout.title')}</Typography>
|
|
469
|
+
</Box>
|
|
470
|
+
<CustomerRevenueList setHasRevenues={setHasRevenues} />
|
|
469
471
|
</Box>
|
|
470
|
-
|
|
471
|
-
</Box>
|
|
472
|
-
);
|
|
472
|
+
);
|
|
473
473
|
|
|
474
474
|
return (
|
|
475
475
|
<Content>
|
|
@@ -37,11 +37,11 @@ import {
|
|
|
37
37
|
Typography,
|
|
38
38
|
} from '@mui/material';
|
|
39
39
|
import { useRequest, useSetState } from 'ahooks';
|
|
40
|
+
import SplitButton from '@arcblock/ux/lib/SplitButton';
|
|
40
41
|
import { useMemo } from 'react';
|
|
41
42
|
import { joinURL, withQuery } from 'ufo';
|
|
42
43
|
import prettyMs from 'pretty-ms-i18n';
|
|
43
44
|
import { isEmpty } from 'lodash';
|
|
44
|
-
import { PaymentOutlined } from '@mui/icons-material';
|
|
45
45
|
import InfoRow from '../../../components/info-row';
|
|
46
46
|
import InfoCard from '../../../components/info-card';
|
|
47
47
|
import { useSessionContext } from '../../../contexts/session';
|
|
@@ -82,7 +82,6 @@ export default function SubscriptionEmbed() {
|
|
|
82
82
|
const [state, setState] = useSetState({
|
|
83
83
|
batchPay: false,
|
|
84
84
|
});
|
|
85
|
-
|
|
86
85
|
const { session, connectApi } = useSessionContext();
|
|
87
86
|
|
|
88
87
|
const subscriptionId = params.get('id') || '';
|
|
@@ -327,19 +326,30 @@ export default function SubscriptionEmbed() {
|
|
|
327
326
|
{x.text[locale] || x.text.en || x.name}
|
|
328
327
|
</Button>
|
|
329
328
|
))}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
329
|
+
{hasPastDue ? (
|
|
330
|
+
<SplitButton
|
|
331
|
+
size="small"
|
|
332
|
+
color="error"
|
|
333
|
+
variant="contained"
|
|
334
|
+
menu={[
|
|
335
|
+
<SplitButton.Item key="view-subscription" component={Link} target="_blank" href={subscriptionPageUrl}>
|
|
336
|
+
{t('payment.customer.subscriptions.view')}
|
|
337
|
+
</SplitButton.Item>,
|
|
338
|
+
]}>
|
|
339
|
+
{() => (
|
|
340
|
+
<Button variant="contained" color="error" onClick={() => setState({ batchPay: true })}>
|
|
341
|
+
{t('payment.subscription.overdue.payNow')}
|
|
342
|
+
</Button>
|
|
343
|
+
)}
|
|
344
|
+
</SplitButton>
|
|
345
|
+
) : (
|
|
346
|
+
<Button
|
|
347
|
+
variant="contained"
|
|
348
|
+
sx={{ width: subscription.service_actions?.length ? 'auto' : '100%' }}
|
|
349
|
+
target="_blank"
|
|
350
|
+
href={subscriptionPageUrl}>
|
|
351
|
+
{t('payment.customer.subscriptions.view')}
|
|
352
|
+
</Button>
|
|
343
353
|
)}
|
|
344
354
|
</Stack>
|
|
345
355
|
</Box>
|