payment-kit 1.18.51 → 1.18.52

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.
@@ -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
@@ -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.18.51
17
+ version: 1.18.52
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.18.51",
3
+ "version": "1.18.52",
4
4
  "scripts": {
5
5
  "dev": "blocklet dev --open",
6
6
  "eject": "vite eject",
@@ -55,7 +55,7 @@
55
55
  "@blocklet/did-space-js": "^1.0.57",
56
56
  "@blocklet/js-sdk": "^1.16.43",
57
57
  "@blocklet/logger": "^1.16.43",
58
- "@blocklet/payment-react": "1.18.51",
58
+ "@blocklet/payment-react": "1.18.52",
59
59
  "@blocklet/sdk": "^1.16.43",
60
60
  "@blocklet/ui-react": "^2.13.61",
61
61
  "@blocklet/uploader": "^0.1.95",
@@ -123,7 +123,7 @@
123
123
  "devDependencies": {
124
124
  "@abtnode/types": "^1.16.43",
125
125
  "@arcblock/eslint-config-ts": "^0.3.3",
126
- "@blocklet/payment-types": "1.18.51",
126
+ "@blocklet/payment-types": "1.18.52",
127
127
  "@types/cookie-parser": "^1.4.7",
128
128
  "@types/cors": "^2.8.17",
129
129
  "@types/debug": "^4.1.12",
@@ -169,5 +169,5 @@
169
169
  "parser": "typescript"
170
170
  }
171
171
  },
172
- "gitHead": "7aa2db4015aad46e2eb8529ff013791d5321c7bf"
172
+ "gitHead": "dfa2927758a635868ca2ce251d098ab15178a409"
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 component="strong" fontWeight={600}>
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)}&nbsp;
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
@@ -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',
@@ -513,6 +513,11 @@ export default flat({
513
513
  tip: '您确定要退还质押吗?此操作将立即退还质押给客户。',
514
514
  success: '质押退还申请已提交',
515
515
  },
516
+ void: {
517
+ title: '作废账单',
518
+ tip: '您确定要作废此账单吗?此操作将立即作废账单。',
519
+ success: '账单作废成功',
520
+ },
516
521
  },
517
522
  subscription: {
518
523
  view: '查看订阅',