payment-kit 1.15.34 → 1.15.36
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/libs/notification/template/subscription-canceled.ts +4 -0
- package/api/src/libs/refund.ts +4 -0
- package/api/src/libs/subscription.ts +25 -0
- package/api/src/queues/subscription.ts +2 -2
- package/api/src/routes/checkout-sessions.ts +2 -2
- package/api/src/routes/connect/recharge.ts +28 -3
- package/api/src/routes/connect/shared.ts +88 -0
- package/api/src/routes/customers.ts +2 -2
- package/api/src/routes/invoices.ts +5 -1
- package/api/src/routes/payment-links.ts +3 -0
- package/api/src/routes/refunds.ts +22 -1
- package/api/src/routes/subscriptions.ts +47 -5
- package/api/src/routes/webhook-attempts.ts +14 -1
- package/api/src/store/models/invoice.ts +2 -1
- package/blocklet.yml +1 -1
- package/package.json +4 -4
- package/src/app.tsx +3 -1
- package/src/components/invoice/list.tsx +40 -11
- package/src/components/invoice/recharge.tsx +244 -0
- package/src/components/payment-intent/actions.tsx +2 -1
- package/src/components/payment-link/actions.tsx +6 -6
- package/src/components/payment-link/item.tsx +53 -18
- package/src/components/pricing-table/actions.tsx +14 -3
- package/src/components/refund/actions.tsx +43 -1
- package/src/components/refund/list.tsx +1 -1
- package/src/components/subscription/portal/actions.tsx +22 -1
- package/src/components/subscription/portal/list.tsx +1 -0
- package/src/components/webhook/attempts.tsx +19 -121
- package/src/components/webhook/request-info.tsx +139 -0
- package/src/locales/en.tsx +9 -0
- package/src/locales/zh.tsx +8 -0
- package/src/pages/admin/payments/refunds/detail.tsx +2 -2
- package/src/pages/admin/products/links/create.tsx +4 -1
- package/src/pages/customer/invoice/detail.tsx +6 -0
- package/src/pages/customer/recharge.tsx +45 -35
- package/src/pages/customer/subscription/detail.tsx +8 -18
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { fromUnitToToken } from '@ocap/util';
|
|
4
4
|
import prettyMsI18n from 'pretty-ms-i18n';
|
|
5
5
|
|
|
6
|
+
import { Op } from 'sequelize';
|
|
6
7
|
import { getUserLocale } from '../../../integrations/blocklet/notification';
|
|
7
8
|
import { translate } from '../../../locales';
|
|
8
9
|
import { Customer, PaymentMethod, Refund, Subscription } from '../../../store/models';
|
|
@@ -107,6 +108,9 @@ export class SubscriptionCanceledEmailTemplate implements BaseEmailTemplate<Subs
|
|
|
107
108
|
where: {
|
|
108
109
|
subscription_id: subscription.id,
|
|
109
110
|
type: 'refund',
|
|
111
|
+
status: {
|
|
112
|
+
[Op.not]: 'canceled',
|
|
113
|
+
},
|
|
110
114
|
},
|
|
111
115
|
});
|
|
112
116
|
const conditions = [
|
package/api/src/libs/refund.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BN } from '@ocap/util';
|
|
2
2
|
import { Op, type WhereOptions } from 'sequelize';
|
|
3
3
|
import { PaymentIntent, Refund } from '../store/models';
|
|
4
|
+
import logger from './logger';
|
|
4
5
|
|
|
5
6
|
export async function getRefundAmountSetup({
|
|
6
7
|
currencyId,
|
|
@@ -45,6 +46,9 @@ export async function getRefundAmountSetup({
|
|
|
45
46
|
include: [],
|
|
46
47
|
});
|
|
47
48
|
if (count === 0) {
|
|
49
|
+
logger.info('No refund found for payment intent', {
|
|
50
|
+
paymentIntentId,
|
|
51
|
+
});
|
|
48
52
|
return {
|
|
49
53
|
amount: paymentIntent.amount_received,
|
|
50
54
|
totalAmount: paymentIntent.amount_received,
|
|
@@ -313,6 +313,8 @@ export async function createProration(
|
|
|
313
313
|
prorations: [],
|
|
314
314
|
newCredit: '0',
|
|
315
315
|
appliedCredit: '0',
|
|
316
|
+
remaining: '0',
|
|
317
|
+
remainingUnused: '0',
|
|
316
318
|
};
|
|
317
319
|
}
|
|
318
320
|
|
|
@@ -363,6 +365,25 @@ export async function createProration(
|
|
|
363
365
|
|
|
364
366
|
// 5. adjust invoice total && update customer token balance
|
|
365
367
|
const total = setup.amount.setup;
|
|
368
|
+
|
|
369
|
+
// 6. calculate remaining amount
|
|
370
|
+
const refunds = await Refund.findAll({
|
|
371
|
+
where: {
|
|
372
|
+
status: { [Op.not]: 'canceled' },
|
|
373
|
+
subscription_id: subscription.id,
|
|
374
|
+
currency_id: lastInvoice?.currency_id,
|
|
375
|
+
invoice_id: lastInvoice?.id,
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
const refundAmount = refunds.reduce((acc, x) => acc.add(new BN(x.amount || '0')), new BN(0));
|
|
379
|
+
|
|
380
|
+
// 7. calculate remaining amount, default refund recurring amount
|
|
381
|
+
const calcRemaining = (amount: BN, subtract: BN) =>
|
|
382
|
+
amount.sub(subtract).lt(new BN(0)) ? '0' : amount.sub(subtract).toString();
|
|
383
|
+
|
|
384
|
+
const remaining = calcRemaining(new BN(total), refundAmount);
|
|
385
|
+
const remainingUnused = calcRemaining(unused, refundAmount);
|
|
386
|
+
|
|
366
387
|
let due = setup.amount.setup;
|
|
367
388
|
let newCredit = '0';
|
|
368
389
|
let appliedCredit = '0';
|
|
@@ -384,6 +405,8 @@ export async function createProration(
|
|
|
384
405
|
prorationEnd,
|
|
385
406
|
prorationRate,
|
|
386
407
|
unused: unused.toString(),
|
|
408
|
+
remaining,
|
|
409
|
+
remainingUnused,
|
|
387
410
|
total,
|
|
388
411
|
due,
|
|
389
412
|
newCredit,
|
|
@@ -394,6 +417,8 @@ export async function createProration(
|
|
|
394
417
|
lastInvoice,
|
|
395
418
|
total,
|
|
396
419
|
due,
|
|
420
|
+
remaining,
|
|
421
|
+
remainingUnused,
|
|
397
422
|
used: new BN(lastInvoice.amount_due).sub(unused).toString(),
|
|
398
423
|
unused: unused.toString(),
|
|
399
424
|
prorations,
|
|
@@ -702,7 +702,7 @@ const ensureRefundOnCancel = async (subscription: Subscription) => {
|
|
|
702
702
|
}
|
|
703
703
|
|
|
704
704
|
const result = await getSubscriptionRefundSetup(subscription, subscription.cancel_at, lastInvoice.currency_id);
|
|
705
|
-
if (result.
|
|
705
|
+
if (result.remainingUnused === '0') {
|
|
706
706
|
logger.warn('Refund skipped because unused amount is 0', {
|
|
707
707
|
subscription: subscription.id,
|
|
708
708
|
unused: result.unused,
|
|
@@ -713,7 +713,7 @@ const ensureRefundOnCancel = async (subscription: Subscription) => {
|
|
|
713
713
|
const item = await Refund.create({
|
|
714
714
|
type: 'refund',
|
|
715
715
|
livemode: subscription.livemode,
|
|
716
|
-
amount: refund === 'last' ? result.
|
|
716
|
+
amount: refund === 'last' ? result.remaining : result.remainingUnused,
|
|
717
717
|
description: 'refund_transfer_on_subscription_cancel',
|
|
718
718
|
status: 'pending',
|
|
719
719
|
reason: 'requested_by_admin',
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* eslint-disable consistent-return */
|
|
2
2
|
import { isValid } from '@arcblock/did';
|
|
3
3
|
import { getUrl } from '@blocklet/sdk/lib/component';
|
|
4
|
-
import
|
|
4
|
+
import sessionMiddleware from '@blocklet/sdk/lib/middlewares/session';
|
|
5
5
|
import { BN, fromUnitToToken } from '@ocap/util';
|
|
6
6
|
import { NextFunction, Request, Response, Router } from 'express';
|
|
7
7
|
import Joi from 'joi';
|
|
@@ -73,7 +73,7 @@ import { ensureInvoiceForCheckout } from './connect/shared';
|
|
|
73
73
|
|
|
74
74
|
const router = Router();
|
|
75
75
|
|
|
76
|
-
const user =
|
|
76
|
+
const user = sessionMiddleware();
|
|
77
77
|
const auth = authenticate<CheckoutSession>({ component: true, roles: ['owner', 'admin'] });
|
|
78
78
|
|
|
79
79
|
const getPaymentMethods = async (doc: CheckoutSession) => {
|
|
@@ -6,7 +6,7 @@ import { executeEvmTransaction, waitForEvmTxConfirm } from 'api/src/integrations
|
|
|
6
6
|
import type { CallbackArgs } from '../../libs/auth';
|
|
7
7
|
import { getGasPayerExtra } from '../../libs/payment';
|
|
8
8
|
import { getTxMetadata } from '../../libs/util';
|
|
9
|
-
import { ensureSubscriptionRecharge, getAuthPrincipalClaim } from './shared';
|
|
9
|
+
import { ensureRechargeInvoice, ensureSubscriptionRecharge, getAuthPrincipalClaim } from './shared';
|
|
10
10
|
import logger from '../../libs/logger';
|
|
11
11
|
|
|
12
12
|
export default {
|
|
@@ -74,10 +74,29 @@ export default {
|
|
|
74
74
|
},
|
|
75
75
|
onAuth: async ({ request, userDid, claims, extraParams }: CallbackArgs) => {
|
|
76
76
|
const { subscriptionId } = extraParams;
|
|
77
|
-
const { paymentMethod, paymentCurrency, receiverAddress } =
|
|
77
|
+
const { paymentMethod, paymentCurrency, receiverAddress, subscription, customer } =
|
|
78
|
+
await ensureSubscriptionRecharge(subscriptionId);
|
|
78
79
|
let { amount } = extraParams;
|
|
79
80
|
amount = fromTokenToUnit(amount, paymentCurrency.decimal).toString();
|
|
80
81
|
|
|
82
|
+
const afterTxExecution = async (paymentDetails: any) => {
|
|
83
|
+
await ensureRechargeInvoice(
|
|
84
|
+
{
|
|
85
|
+
total: amount,
|
|
86
|
+
description: 'Subscription recharge',
|
|
87
|
+
currency_id: paymentCurrency.id,
|
|
88
|
+
metadata: {
|
|
89
|
+
payment_details: {
|
|
90
|
+
[paymentMethod.type]: paymentDetails,
|
|
91
|
+
receiverAddress,
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
subscription!,
|
|
96
|
+
paymentMethod,
|
|
97
|
+
customer!
|
|
98
|
+
);
|
|
99
|
+
};
|
|
81
100
|
if (paymentMethod.type === 'arcblock') {
|
|
82
101
|
try {
|
|
83
102
|
const client = paymentMethod.getOcapClient();
|
|
@@ -104,6 +123,11 @@ export default {
|
|
|
104
123
|
paymentMethod: paymentMethod.type,
|
|
105
124
|
});
|
|
106
125
|
|
|
126
|
+
await afterTxExecution({
|
|
127
|
+
tx_hash: txHash,
|
|
128
|
+
payer: userDid,
|
|
129
|
+
type: 'transfer',
|
|
130
|
+
});
|
|
107
131
|
return { hash: txHash };
|
|
108
132
|
} catch (err) {
|
|
109
133
|
console.error(err);
|
|
@@ -120,13 +144,14 @@ export default {
|
|
|
120
144
|
Number(paymentDetails.block_height),
|
|
121
145
|
paymentMethod.confirmation.block
|
|
122
146
|
)
|
|
123
|
-
.then(() => {
|
|
147
|
+
.then(async () => {
|
|
124
148
|
logger.info('Recharge successful', {
|
|
125
149
|
receiverAddress,
|
|
126
150
|
amount,
|
|
127
151
|
subscriptionId,
|
|
128
152
|
paymentMethod: paymentMethod.type,
|
|
129
153
|
});
|
|
154
|
+
await afterTxExecution(paymentDetails);
|
|
130
155
|
})
|
|
131
156
|
.catch(console.error);
|
|
132
157
|
|
|
@@ -624,10 +624,17 @@ export async function ensureSubscriptionRecharge(subscriptionId: string) {
|
|
|
624
624
|
if (!receiverAddress) {
|
|
625
625
|
throw new Error(`Receiver address not found for subscription ${subscriptionId}`);
|
|
626
626
|
}
|
|
627
|
+
|
|
628
|
+
const customer = await Customer.findByPk(subscription.customer_id);
|
|
629
|
+
if (!customer) {
|
|
630
|
+
throw new Error(`Customer ${subscription.customer_id} not found`);
|
|
631
|
+
}
|
|
627
632
|
return {
|
|
628
633
|
paymentCurrency: paymentCurrency as PaymentCurrency,
|
|
629
634
|
paymentMethod: paymentMethod as PaymentMethod,
|
|
630
635
|
receiverAddress,
|
|
636
|
+
subscription,
|
|
637
|
+
customer
|
|
631
638
|
};
|
|
632
639
|
}
|
|
633
640
|
|
|
@@ -1200,4 +1207,85 @@ export async function updateStripeSubscriptionAfterChangePayment(setupIntent: Se
|
|
|
1200
1207
|
}
|
|
1201
1208
|
}
|
|
1202
1209
|
}
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
export async function ensureRechargeInvoice(
|
|
1213
|
+
invoiceProps: { total: string; description?: string; checkout_session_id?: string; currency_id: string; metadata?: any; payment_settings?: any },
|
|
1214
|
+
subscription: Subscription,
|
|
1215
|
+
paymentMethod: PaymentMethod,
|
|
1216
|
+
customer: Customer
|
|
1217
|
+
) {
|
|
1218
|
+
try {
|
|
1219
|
+
const rechargeInvoice = await Invoice.create({
|
|
1220
|
+
livemode: subscription.livemode,
|
|
1221
|
+
number: await customer.getInvoiceNumber(),
|
|
1222
|
+
description: invoiceProps?.description || 'Subscription recharge',
|
|
1223
|
+
statement_descriptor: '',
|
|
1224
|
+
period_start: dayjs().unix(),
|
|
1225
|
+
period_end: dayjs().unix(),
|
|
1226
|
+
|
|
1227
|
+
auto_advance: false,
|
|
1228
|
+
paid: true,
|
|
1229
|
+
paid_out_of_band: false,
|
|
1230
|
+
|
|
1231
|
+
status: 'paid',
|
|
1232
|
+
collection_method: 'charge_automatically',
|
|
1233
|
+
billing_reason: 'recharge',
|
|
1234
|
+
|
|
1235
|
+
currency_id: invoiceProps.currency_id,
|
|
1236
|
+
customer_id: customer.id,
|
|
1237
|
+
payment_intent_id: '',
|
|
1238
|
+
subscription_id: subscription?.id,
|
|
1239
|
+
checkout_session_id: invoiceProps?.checkout_session_id || '',
|
|
1240
|
+
|
|
1241
|
+
total: invoiceProps.total || '0',
|
|
1242
|
+
subtotal: invoiceProps.total || '0',
|
|
1243
|
+
tax: '0',
|
|
1244
|
+
subtotal_excluding_tax: invoiceProps.total || '0',
|
|
1245
|
+
|
|
1246
|
+
amount_due: '0',
|
|
1247
|
+
amount_paid: invoiceProps.total || '0',
|
|
1248
|
+
amount_remaining: '0',
|
|
1249
|
+
amount_shipping: '0',
|
|
1250
|
+
|
|
1251
|
+
starting_balance: '0',
|
|
1252
|
+
ending_balance: '0',
|
|
1253
|
+
starting_token_balance: {},
|
|
1254
|
+
ending_token_balance: {},
|
|
1255
|
+
|
|
1256
|
+
attempt_count: 0,
|
|
1257
|
+
attempted: false,
|
|
1258
|
+
// next_payment_attempt: undefined,
|
|
1259
|
+
|
|
1260
|
+
custom_fields: [],
|
|
1261
|
+
customer_address: customer.address,
|
|
1262
|
+
customer_email: customer.email,
|
|
1263
|
+
customer_name: customer.name,
|
|
1264
|
+
customer_phone: customer.phone,
|
|
1265
|
+
|
|
1266
|
+
discounts: [],
|
|
1267
|
+
total_discount_amounts: [],
|
|
1268
|
+
|
|
1269
|
+
due_date: undefined,
|
|
1270
|
+
effective_at: dayjs().unix(),
|
|
1271
|
+
status_transitions: {
|
|
1272
|
+
finalized_at: dayjs().unix(),
|
|
1273
|
+
},
|
|
1274
|
+
|
|
1275
|
+
payment_settings: invoiceProps?.payment_settings || subscription?.payment_settings,
|
|
1276
|
+
default_payment_method_id: paymentMethod.id,
|
|
1277
|
+
|
|
1278
|
+
account_country: '',
|
|
1279
|
+
account_name: '',
|
|
1280
|
+
metadata: invoiceProps.metadata || {},
|
|
1281
|
+
});
|
|
1282
|
+
logger.info('create recharge invoice success', {
|
|
1283
|
+
rechargeInvoice,
|
|
1284
|
+
subscriptionId: subscription?.id,
|
|
1285
|
+
paymentMethod: paymentMethod.id,
|
|
1286
|
+
customerId: customer.id,
|
|
1287
|
+
});
|
|
1288
|
+
} catch (error) {
|
|
1289
|
+
logger.error('ensureRechargeInvoice: create invoice failed', { error, subscriptionId: subscription?.id, paymentMethod: paymentMethod.id, customerId: customer.id });
|
|
1290
|
+
}
|
|
1203
1291
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import sessionMiddleware from '@blocklet/sdk/lib/middlewares/session';
|
|
2
2
|
import { Router } from 'express';
|
|
3
3
|
import Joi from 'joi';
|
|
4
4
|
import pick from 'lodash/pick';
|
|
@@ -79,7 +79,7 @@ router.get('/search', auth, async (req, res) => {
|
|
|
79
79
|
});
|
|
80
80
|
|
|
81
81
|
// eslint-disable-next-line consistent-return
|
|
82
|
-
router.get('/me',
|
|
82
|
+
router.get('/me', sessionMiddleware(), async (req, res) => {
|
|
83
83
|
if (!req.user) {
|
|
84
84
|
return res.status(403).json({ error: 'Unauthorized' });
|
|
85
85
|
}
|
|
@@ -107,8 +107,12 @@ router.get('/', authMine, async (req, res) => {
|
|
|
107
107
|
// @ts-ignore
|
|
108
108
|
where[key] = query[key];
|
|
109
109
|
});
|
|
110
|
+
const excludeBillingReasons = ['recharge'];
|
|
110
111
|
if (!!(include_staking && query.subscription_id) || !include_staking) {
|
|
111
|
-
|
|
112
|
+
excludeBillingReasons.push('stake');
|
|
113
|
+
}
|
|
114
|
+
if (excludeBillingReasons.length > 0) {
|
|
115
|
+
where.billing_reason = { [Op.notIn]: excludeBillingReasons };
|
|
112
116
|
}
|
|
113
117
|
try {
|
|
114
118
|
const { rows: list, count } = await Invoice.findAndCountAll({
|
|
@@ -93,6 +93,9 @@ const formatBeforeSave = (payload: any) => {
|
|
|
93
93
|
|
|
94
94
|
raw.line_items?.forEach((x) => {
|
|
95
95
|
if (x.adjustable_quantity?.enabled) {
|
|
96
|
+
if (Number(x.adjustable_quantity?.minimum) >= Number(x.adjustable_quantity?.maximum)) {
|
|
97
|
+
throw new Error('adjustable_quantity.minimum must be less than adjustable_quantity.maximum');
|
|
98
|
+
}
|
|
96
99
|
x.adjustable_quantity.minimum = Number(x.adjustable_quantity?.minimum);
|
|
97
100
|
x.adjustable_quantity.maximum = Number(x.adjustable_quantity?.maximum);
|
|
98
101
|
}
|
|
@@ -261,7 +261,28 @@ router.put('/:id', authAdmin, async (req, res) => {
|
|
|
261
261
|
requestBody: req.body,
|
|
262
262
|
requestedBy: req.user?.did,
|
|
263
263
|
});
|
|
264
|
-
res.status(
|
|
264
|
+
return res.status(400).json({ error: err.message });
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
router.post('/:id/cancel', authAdmin, async (req, res) => {
|
|
269
|
+
const doc = await Refund.findByPk(req.params.id as string);
|
|
270
|
+
if (!doc) {
|
|
271
|
+
return res.status(404).json({ error: 'Refund not found' });
|
|
272
|
+
}
|
|
273
|
+
if (doc.status === 'succeeded') {
|
|
274
|
+
return res.status(400).json({ error: 'Refund is already succeeded' });
|
|
275
|
+
}
|
|
276
|
+
try {
|
|
277
|
+
await doc.update({ status: 'canceled' });
|
|
278
|
+
return res.json(doc);
|
|
279
|
+
} catch (err) {
|
|
280
|
+
logger.error('Cancel refund failed', {
|
|
281
|
+
refundId: req.params.id,
|
|
282
|
+
error: err.message,
|
|
283
|
+
requestedBy: req.user?.did,
|
|
284
|
+
});
|
|
285
|
+
return res.status(400).json({ error: err.message });
|
|
265
286
|
}
|
|
266
287
|
});
|
|
267
288
|
|
|
@@ -389,7 +389,7 @@ router.put('/:id/cancel', authPortal, async (req, res) => {
|
|
|
389
389
|
return res.status(403).json({ error: 'Not authorized to refund' });
|
|
390
390
|
}
|
|
391
391
|
const result = await getSubscriptionRefundSetup(subscription, updates.cancel_at);
|
|
392
|
-
if (result.
|
|
392
|
+
if (result.remainingUnused !== '0') {
|
|
393
393
|
// @ts-ignore
|
|
394
394
|
updates.cancelation_details = {
|
|
395
395
|
...(updates.cancelation_details || {}),
|
|
@@ -1279,7 +1279,7 @@ router.get('/:id/proration', authPortal, async (req, res) => {
|
|
|
1279
1279
|
|
|
1280
1280
|
const anchor = req.query.time ? dayjs(req.query.time as any).unix() : dayjs().unix();
|
|
1281
1281
|
const result = await getSubscriptionRefundSetup(subscription, anchor, invoice?.currency_id);
|
|
1282
|
-
if (result.
|
|
1282
|
+
if (result.remaining === '0') {
|
|
1283
1283
|
return res.json(null);
|
|
1284
1284
|
}
|
|
1285
1285
|
const paymentCurrency = await PaymentCurrency.findByPk(result.lastInvoice?.currency_id);
|
|
@@ -1289,9 +1289,9 @@ router.get('/:id/proration', authPortal, async (req, res) => {
|
|
|
1289
1289
|
}
|
|
1290
1290
|
|
|
1291
1291
|
return res.json({
|
|
1292
|
-
total: result.
|
|
1292
|
+
total: result.remaining,
|
|
1293
1293
|
latest: invoice?.total,
|
|
1294
|
-
unused: result.
|
|
1294
|
+
unused: result.remainingUnused,
|
|
1295
1295
|
used: result.used,
|
|
1296
1296
|
prorations: result.prorations,
|
|
1297
1297
|
paymentCurrency,
|
|
@@ -1815,5 +1815,47 @@ router.get('/:id/payer-token', authMine, async (req, res) => {
|
|
|
1815
1815
|
return res.json({ token, paymentAddress });
|
|
1816
1816
|
});
|
|
1817
1817
|
|
|
1818
|
-
|
|
1818
|
+
const rechargeSchema = createListParamSchema<{
|
|
1819
|
+
customer_id?: string;
|
|
1820
|
+
customer_did?: string;
|
|
1821
|
+
currency_id?: string;
|
|
1822
|
+
}>({
|
|
1823
|
+
customer_id: Joi.string().empty(''),
|
|
1824
|
+
customer_did: Joi.string().empty(''),
|
|
1825
|
+
currency_id: Joi.string().empty(''),
|
|
1826
|
+
});
|
|
1827
|
+
router.get('/:id/recharge', authMine, async (req, res) => {
|
|
1828
|
+
const subscription = await Subscription.findByPk(req.params.id);
|
|
1829
|
+
if (!subscription) {
|
|
1830
|
+
return res.status(404).json({ error: `Subscription(${req.params.id}) not found` });
|
|
1831
|
+
}
|
|
1832
|
+
const { page, pageSize, ...query } = await rechargeSchema.validateAsync(req.query, {
|
|
1833
|
+
stripUnknown: false,
|
|
1834
|
+
allowUnknown: true,
|
|
1835
|
+
});
|
|
1836
|
+
const where = getWhereFromKvQuery(query.q);
|
|
1837
|
+
|
|
1838
|
+
try {
|
|
1839
|
+
const { rows: invoices, count } = await Invoice.findAndCountAll({
|
|
1840
|
+
where: {
|
|
1841
|
+
subscription_id: subscription.id,
|
|
1842
|
+
billing_reason: 'recharge',
|
|
1843
|
+
paid: true,
|
|
1844
|
+
...where,
|
|
1845
|
+
},
|
|
1846
|
+
offset: (page - 1) * pageSize,
|
|
1847
|
+
limit: pageSize,
|
|
1848
|
+
order: [['created_at', 'DESC']],
|
|
1849
|
+
include: [
|
|
1850
|
+
{ model: PaymentCurrency, as: 'paymentCurrency' },
|
|
1851
|
+
{ model: PaymentMethod, as: 'paymentMethod' },
|
|
1852
|
+
],
|
|
1853
|
+
});
|
|
1854
|
+
|
|
1855
|
+
return res.json({ count, list: invoices, subscription, paging: { page, pageSize } });
|
|
1856
|
+
} catch (err) {
|
|
1857
|
+
console.error(err);
|
|
1858
|
+
return res.status(400).json({ error: err.message });
|
|
1859
|
+
}
|
|
1860
|
+
});
|
|
1819
1861
|
export default router;
|
|
@@ -5,6 +5,7 @@ import type { WhereOptions } from 'sequelize';
|
|
|
5
5
|
import { createListParamSchema } from '../libs/api';
|
|
6
6
|
import { authenticate } from '../libs/security';
|
|
7
7
|
import { Event, WebhookAttempt, WebhookEndpoint } from '../store/models';
|
|
8
|
+
import { blocklet } from '../libs/auth';
|
|
8
9
|
|
|
9
10
|
const router = Router();
|
|
10
11
|
const auth = authenticate<WebhookAttempt>({ component: true, roles: ['owner', 'admin'] });
|
|
@@ -42,7 +43,19 @@ router.get('/', auth, async (req, res) => {
|
|
|
42
43
|
],
|
|
43
44
|
});
|
|
44
45
|
|
|
45
|
-
|
|
46
|
+
const updatedList = await Promise.all(
|
|
47
|
+
list.map(async (attempt: any) => {
|
|
48
|
+
const updated = attempt.toJSON();
|
|
49
|
+
if (updated.event?.request?.requested_by) {
|
|
50
|
+
const { user } = await blocklet.getUser(updated.event.request.requested_by);
|
|
51
|
+
if (user) {
|
|
52
|
+
updated.event.requestInfo = user;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return updated;
|
|
56
|
+
})
|
|
57
|
+
);
|
|
58
|
+
res.json({ count, list: updatedList, paging: { page, pageSize } });
|
|
46
59
|
} catch (err) {
|
|
47
60
|
console.error(err);
|
|
48
61
|
res.json({ count: 0, list: [], paging: { page, pageSize } });
|
package/blocklet.yml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payment-kit",
|
|
3
|
-
"version": "1.15.
|
|
3
|
+
"version": "1.15.36",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "blocklet dev --open",
|
|
6
6
|
"eject": "vite eject",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"@arcblock/validator": "^1.18.139",
|
|
54
54
|
"@blocklet/js-sdk": "1.16.33-beta-20241031-073543-49b1ff9b",
|
|
55
55
|
"@blocklet/logger": "1.16.33-beta-20241031-073543-49b1ff9b",
|
|
56
|
-
"@blocklet/payment-react": "1.15.
|
|
56
|
+
"@blocklet/payment-react": "1.15.36",
|
|
57
57
|
"@blocklet/sdk": "1.16.33-beta-20241031-073543-49b1ff9b",
|
|
58
58
|
"@blocklet/ui-react": "^2.10.65",
|
|
59
59
|
"@blocklet/uploader": "^0.1.51",
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
"devDependencies": {
|
|
121
121
|
"@abtnode/types": "1.16.33-beta-20241031-073543-49b1ff9b",
|
|
122
122
|
"@arcblock/eslint-config-ts": "^0.3.3",
|
|
123
|
-
"@blocklet/payment-types": "1.15.
|
|
123
|
+
"@blocklet/payment-types": "1.15.36",
|
|
124
124
|
"@types/cookie-parser": "^1.4.7",
|
|
125
125
|
"@types/cors": "^2.8.17",
|
|
126
126
|
"@types/debug": "^4.1.12",
|
|
@@ -165,5 +165,5 @@
|
|
|
165
165
|
"parser": "typescript"
|
|
166
166
|
}
|
|
167
167
|
},
|
|
168
|
-
"gitHead": "
|
|
168
|
+
"gitHead": "0139cb00a67b6ebd845d932fa6f240cf95b1cc16"
|
|
169
169
|
}
|
package/src/app.tsx
CHANGED
|
@@ -8,7 +8,7 @@ import { ToastProvider } from '@arcblock/ux/lib/Toast';
|
|
|
8
8
|
import { CircularProgress } from '@mui/material';
|
|
9
9
|
import React, { Suspense } from 'react';
|
|
10
10
|
import { ErrorBoundary } from 'react-error-boundary';
|
|
11
|
-
import { PaymentThemeProvider } from '@blocklet/payment-react';
|
|
11
|
+
import { PaymentThemeProvider, usePreventWheel } from '@blocklet/payment-react';
|
|
12
12
|
import { Navigate, Route, BrowserRouter as Router, Routes } from 'react-router-dom';
|
|
13
13
|
import { joinURL } from 'ufo';
|
|
14
14
|
|
|
@@ -150,6 +150,8 @@ export default function WrappedApp() {
|
|
|
150
150
|
// While the blocklet is deploy to a sub path, this will be work properly.
|
|
151
151
|
const prefix = window?.blocklet?.prefix || '/';
|
|
152
152
|
|
|
153
|
+
usePreventWheel();
|
|
154
|
+
|
|
153
155
|
return (
|
|
154
156
|
<ToastProvider>
|
|
155
157
|
<SessionProvider
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
Table,
|
|
10
10
|
useDefaultPageSize,
|
|
11
11
|
getInvoiceDescriptionAndReason,
|
|
12
|
+
getTxLink,
|
|
12
13
|
} from '@blocklet/payment-react';
|
|
13
14
|
import type { TInvoiceExpanded } from '@blocklet/payment-types';
|
|
14
15
|
import { CircularProgress, Typography } from '@mui/material';
|
|
@@ -87,6 +88,33 @@ InvoiceList.defaultProps = {
|
|
|
87
88
|
mode: 'admin',
|
|
88
89
|
};
|
|
89
90
|
|
|
91
|
+
const getInvoiceLink = (invoice: TInvoiceExpanded) => {
|
|
92
|
+
if (invoice.id.startsWith('in_')) {
|
|
93
|
+
return {
|
|
94
|
+
external: false,
|
|
95
|
+
url: `/admin/billing/${invoice.id}`,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
external: true,
|
|
101
|
+
connect: false,
|
|
102
|
+
url: getTxLink(invoice.paymentMethod, invoice.metadata?.payment_details).link,
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
function InvoiceLink({ invoice, children }: { invoice: TInvoiceExpanded; children: React.ReactNode }) {
|
|
107
|
+
const link = getInvoiceLink(invoice);
|
|
108
|
+
if (link.external) {
|
|
109
|
+
return (
|
|
110
|
+
<a href={link.url} target="_blank" rel="noreferrer">
|
|
111
|
+
{children}
|
|
112
|
+
</a>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
return <Link to={link.url}>{children}</Link>;
|
|
116
|
+
}
|
|
117
|
+
|
|
90
118
|
export default function InvoiceList({
|
|
91
119
|
customer_id,
|
|
92
120
|
subscription_id,
|
|
@@ -145,12 +173,12 @@ export default function InvoiceList({
|
|
|
145
173
|
customBodyRenderLite: (_: string, index: number) => {
|
|
146
174
|
const item = data.list[index] as TInvoiceExpanded;
|
|
147
175
|
return (
|
|
148
|
-
<
|
|
176
|
+
<InvoiceLink invoice={item}>
|
|
149
177
|
<Typography component="strong" fontWeight={600}>
|
|
150
178
|
{formatBNStr(item?.total, item?.paymentCurrency.decimal)}
|
|
151
179
|
{item?.paymentCurrency.symbol}
|
|
152
180
|
</Typography>
|
|
153
|
-
</
|
|
181
|
+
</InvoiceLink>
|
|
154
182
|
);
|
|
155
183
|
},
|
|
156
184
|
},
|
|
@@ -163,9 +191,9 @@ export default function InvoiceList({
|
|
|
163
191
|
customBodyRenderLite: (_: string, index: number) => {
|
|
164
192
|
const item = data.list[index] as TInvoiceExpanded;
|
|
165
193
|
return (
|
|
166
|
-
<
|
|
194
|
+
<InvoiceLink invoice={item}>
|
|
167
195
|
<Status label={item?.status} color={getInvoiceStatusColor(item?.status)} />
|
|
168
|
-
</
|
|
196
|
+
</InvoiceLink>
|
|
169
197
|
);
|
|
170
198
|
},
|
|
171
199
|
},
|
|
@@ -177,9 +205,9 @@ export default function InvoiceList({
|
|
|
177
205
|
customBodyRenderLite: (_: string, index: number) => {
|
|
178
206
|
const item = data.list[index] as TInvoiceExpanded;
|
|
179
207
|
return (
|
|
180
|
-
<
|
|
208
|
+
<InvoiceLink invoice={item}>
|
|
181
209
|
<Status label={getInvoiceDescriptionAndReason(item, locale)?.type} />
|
|
182
|
-
</
|
|
210
|
+
</InvoiceLink>
|
|
183
211
|
);
|
|
184
212
|
},
|
|
185
213
|
},
|
|
@@ -190,20 +218,21 @@ export default function InvoiceList({
|
|
|
190
218
|
options: {
|
|
191
219
|
customBodyRenderLite: (_: string, index: number) => {
|
|
192
220
|
const item = data.list[index] as TInvoiceExpanded;
|
|
193
|
-
return <
|
|
221
|
+
return <InvoiceLink invoice={item}>{item.number}</InvoiceLink>;
|
|
194
222
|
},
|
|
195
223
|
},
|
|
196
224
|
},
|
|
197
225
|
{
|
|
198
226
|
label: t('common.description'),
|
|
199
227
|
name: 'description',
|
|
228
|
+
minWidth: 120,
|
|
200
229
|
options: {
|
|
201
230
|
customBodyRenderLite: (_: string, index: number) => {
|
|
202
231
|
const item = data.list[index] as TInvoiceExpanded;
|
|
203
232
|
return (
|
|
204
|
-
<
|
|
233
|
+
<InvoiceLink invoice={item}>
|
|
205
234
|
{getInvoiceDescriptionAndReason(item, locale)?.description || item?.id}
|
|
206
|
-
</
|
|
235
|
+
</InvoiceLink>
|
|
207
236
|
);
|
|
208
237
|
},
|
|
209
238
|
},
|
|
@@ -215,7 +244,7 @@ export default function InvoiceList({
|
|
|
215
244
|
sort: true,
|
|
216
245
|
customBodyRenderLite: (_: string, index: number) => {
|
|
217
246
|
const item = data.list[index] as TInvoiceExpanded;
|
|
218
|
-
return <
|
|
247
|
+
return <InvoiceLink invoice={item}>{formatTime(item.created_at)}</InvoiceLink>;
|
|
219
248
|
},
|
|
220
249
|
},
|
|
221
250
|
},
|
|
@@ -226,7 +255,7 @@ export default function InvoiceList({
|
|
|
226
255
|
sort: true,
|
|
227
256
|
customBodyRenderLite: (_: string, index: number) => {
|
|
228
257
|
const item = data.list[index] as TInvoiceExpanded;
|
|
229
|
-
return <
|
|
258
|
+
return <InvoiceLink invoice={item}>{formatTime(item.updated_at)}</InvoiceLink>;
|
|
230
259
|
},
|
|
231
260
|
},
|
|
232
261
|
},
|