payment-kit 1.19.5 → 1.19.7
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/hooks/pre-start.ts +0 -4
- package/api/src/index.ts +4 -2
- package/api/src/libs/notification/template/customer-credit-grant-granted.ts +2 -2
- package/api/src/libs/notification/template/customer-credit-grant-low-balance.ts +3 -3
- package/api/src/libs/notification/template/customer-credit-insufficient.ts +16 -4
- package/api/src/libs/queue/index.ts +0 -1
- package/api/src/libs/util.ts +24 -0
- package/api/src/queues/credit-consume.ts +2 -0
- package/api/src/queues/notification.ts +3 -2
- package/api/src/routes/credit-grants.ts +15 -7
- package/api/src/routes/customers.ts +31 -30
- package/api/src/routes/payment-links.ts +6 -3
- package/api/src/store/migrations/20250725-add-lookup-key-to-link.ts +21 -0
- package/api/src/store/models/credit-grant.ts +4 -4
- package/api/src/store/models/customer.ts +6 -6
- package/api/src/store/models/payment-link.ts +29 -1
- package/blocklet.yml +1 -1
- package/package.json +18 -17
- package/src/pages/customer/credit-grant/detail.tsx +2 -2
- package/src/pages/customer/index.tsx +1 -1
|
@@ -6,8 +6,6 @@ import { ensurePaymentStats } from '../crons/payment-stat';
|
|
|
6
6
|
import { initPaywallResources } from '../libs/resource';
|
|
7
7
|
import { initialize } from '../store/models';
|
|
8
8
|
import { sequelize } from '../store/sequelize';
|
|
9
|
-
import { syncCurrencyLogo } from '../crons/currency';
|
|
10
|
-
import { ensureCreateOverdraftProtectionPrices } from '../libs/overdraft-protection';
|
|
11
9
|
|
|
12
10
|
dotenv.config({ silent: true });
|
|
13
11
|
|
|
@@ -16,8 +14,6 @@ dotenv.config({ silent: true });
|
|
|
16
14
|
initialize(sequelize);
|
|
17
15
|
await initPaywallResources();
|
|
18
16
|
await ensurePaymentStats();
|
|
19
|
-
await syncCurrencyLogo();
|
|
20
|
-
await ensureCreateOverdraftProtectionPrices();
|
|
21
17
|
process.exit(0);
|
|
22
18
|
} catch (err) {
|
|
23
19
|
console.error('pre-start error', err);
|
package/api/src/index.ts
CHANGED
|
@@ -7,7 +7,6 @@ import cookieParser from 'cookie-parser';
|
|
|
7
7
|
import cors from 'cors';
|
|
8
8
|
import dotenv from 'dotenv-flow';
|
|
9
9
|
import express, { ErrorRequestHandler } from 'express';
|
|
10
|
-
|
|
11
10
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
12
11
|
import { xss } from '@blocklet/xss';
|
|
13
12
|
import { csrf } from '@blocklet/sdk/lib/middlewares';
|
|
@@ -47,13 +46,14 @@ import { initialize } from './store/models';
|
|
|
47
46
|
import { sequelize } from './store/sequelize';
|
|
48
47
|
import { initUserHandler } from './integrations/blocklet/user';
|
|
49
48
|
import { startUploadBillingInfoListener } from './queues/space';
|
|
49
|
+
import { syncCurrencyLogo } from './crons/currency';
|
|
50
|
+
import { ensureCreateOverdraftProtectionPrices } from './libs/overdraft-protection';
|
|
50
51
|
|
|
51
52
|
dotenv.config();
|
|
52
53
|
|
|
53
54
|
initialize(sequelize);
|
|
54
55
|
|
|
55
56
|
export const app = express();
|
|
56
|
-
|
|
57
57
|
setupAccessLogger(app);
|
|
58
58
|
app.set('trust proxy', true);
|
|
59
59
|
app.use(cookieParser());
|
|
@@ -112,6 +112,8 @@ export const server = app.listen(port, (err?: any) => {
|
|
|
112
112
|
if (err) throw err;
|
|
113
113
|
logger.info(`> payment-kit ready on ${port}`);
|
|
114
114
|
|
|
115
|
+
syncCurrencyLogo();
|
|
116
|
+
ensureCreateOverdraftProtectionPrices();
|
|
115
117
|
startPaymentQueue().then(() => logger.info('payment queue started'));
|
|
116
118
|
startInvoiceQueue().then(() => logger.info('invoice queue started'));
|
|
117
119
|
startSubscriptionQueue().then(() => logger.info('subscription queue started'));
|
|
@@ -5,7 +5,7 @@ import { getUserLocale } from '../../../integrations/blocklet/notification';
|
|
|
5
5
|
import { translate } from '../../../locales';
|
|
6
6
|
import { CreditGrant, Customer, PaymentCurrency } from '../../../store/models';
|
|
7
7
|
import { formatTime } from '../../time';
|
|
8
|
-
import { getCustomerIndexUrl } from '../../util';
|
|
8
|
+
import { formatNumber, getCustomerIndexUrl } from '../../util';
|
|
9
9
|
import type { BaseEmailTemplate, BaseEmailTemplateType } from './base';
|
|
10
10
|
|
|
11
11
|
export interface CustomerCreditGrantGrantedEmailTemplateOptions {
|
|
@@ -59,7 +59,7 @@ export class CustomerCreditGrantGrantedEmailTemplate
|
|
|
59
59
|
locale,
|
|
60
60
|
userDid,
|
|
61
61
|
currencySymbol,
|
|
62
|
-
grantedAmount: `${fromUnitToToken(creditGrant.amount.toString(), paymentCurrency.decimal)} ${currencySymbol}`,
|
|
62
|
+
grantedAmount: `${formatNumber(fromUnitToToken(creditGrant.amount.toString(), paymentCurrency.decimal))} ${currencySymbol}`,
|
|
63
63
|
expiresAt,
|
|
64
64
|
neverExpires,
|
|
65
65
|
at,
|
|
@@ -5,7 +5,7 @@ import { getUserLocale } from '../../../integrations/blocklet/notification';
|
|
|
5
5
|
import { translate } from '../../../locales';
|
|
6
6
|
import { CreditGrant, Customer, PaymentCurrency } from '../../../store/models';
|
|
7
7
|
import type { BaseEmailTemplate, BaseEmailTemplateType } from './base';
|
|
8
|
-
import { getCustomerIndexUrl } from '../../util';
|
|
8
|
+
import { formatNumber, getCustomerIndexUrl } from '../../util';
|
|
9
9
|
|
|
10
10
|
export interface CustomerCreditGrantLowBalanceEmailTemplateOptions {
|
|
11
11
|
creditGrantId: string;
|
|
@@ -58,8 +58,8 @@ export class CustomerCreditGrantLowBalanceEmailTemplate
|
|
|
58
58
|
locale,
|
|
59
59
|
userDid,
|
|
60
60
|
currencySymbol,
|
|
61
|
-
availableAmount: `${fromUnitToToken(available.toString(), paymentCurrency.decimal)} ${currencySymbol}`,
|
|
62
|
-
totalGrantedAmount: `${fromUnitToToken(total.toString(), paymentCurrency.decimal)} ${currencySymbol}`,
|
|
61
|
+
availableAmount: `${formatNumber(fromUnitToToken(available.toString(), paymentCurrency.decimal))} ${currencySymbol}`,
|
|
62
|
+
totalGrantedAmount: `${formatNumber(fromUnitToToken(total.toString(), paymentCurrency.decimal))} ${currencySymbol}`,
|
|
63
63
|
lowBalancePercentage: `${percentage}%`,
|
|
64
64
|
};
|
|
65
65
|
}
|
|
@@ -8,10 +8,12 @@ import { getMainProductName } from '../../product';
|
|
|
8
8
|
import { getCustomerSubscriptionPageUrl } from '../../subscription';
|
|
9
9
|
import { formatTime } from '../../time';
|
|
10
10
|
import type { BaseEmailTemplate, BaseEmailTemplateType } from './base';
|
|
11
|
+
import { formatNumber, getCustomerIndexUrl } from '../../util';
|
|
11
12
|
|
|
12
13
|
export interface CustomerCreditInsufficientEmailTemplateOptions {
|
|
13
14
|
customerId: string;
|
|
14
15
|
currencyId: string;
|
|
16
|
+
meterName: string;
|
|
15
17
|
meterEventName: string;
|
|
16
18
|
requiredAmount: string;
|
|
17
19
|
availableAmount: string;
|
|
@@ -24,6 +26,7 @@ interface CustomerCreditInsufficientEmailTemplateContext {
|
|
|
24
26
|
userDid: string;
|
|
25
27
|
currencySymbol: string;
|
|
26
28
|
meterEventName: string;
|
|
29
|
+
meterName: string;
|
|
27
30
|
requiredAmount: string;
|
|
28
31
|
availableAmount: string;
|
|
29
32
|
isExhausted: boolean;
|
|
@@ -80,9 +83,10 @@ export class CustomerCreditInsufficientEmailTemplate
|
|
|
80
83
|
locale,
|
|
81
84
|
userDid,
|
|
82
85
|
currencySymbol,
|
|
86
|
+
meterName: this.options.meterName,
|
|
83
87
|
meterEventName: this.options.meterEventName,
|
|
84
|
-
requiredAmount: `${fromUnitToToken(this.options.requiredAmount, paymentCurrency.decimal)} ${currencySymbol}`,
|
|
85
|
-
availableAmount: `${fromUnitToToken(this.options.availableAmount, paymentCurrency.decimal)} ${currencySymbol}`,
|
|
88
|
+
requiredAmount: `${formatNumber(fromUnitToToken(this.options.requiredAmount, paymentCurrency.decimal))} ${currencySymbol}`,
|
|
89
|
+
availableAmount: `${formatNumber(fromUnitToToken(this.options.availableAmount, paymentCurrency.decimal))} ${currencySymbol}`,
|
|
86
90
|
isExhausted,
|
|
87
91
|
productName,
|
|
88
92
|
viewSubscriptionLink,
|
|
@@ -95,7 +99,7 @@ export class CustomerCreditInsufficientEmailTemplate
|
|
|
95
99
|
const {
|
|
96
100
|
locale,
|
|
97
101
|
userDid,
|
|
98
|
-
|
|
102
|
+
meterName,
|
|
99
103
|
requiredAmount,
|
|
100
104
|
availableAmount,
|
|
101
105
|
isExhausted,
|
|
@@ -132,7 +136,7 @@ export class CustomerCreditInsufficientEmailTemplate
|
|
|
132
136
|
type: 'text',
|
|
133
137
|
data: {
|
|
134
138
|
type: 'plain',
|
|
135
|
-
text:
|
|
139
|
+
text: meterName,
|
|
136
140
|
},
|
|
137
141
|
},
|
|
138
142
|
];
|
|
@@ -206,6 +210,14 @@ export class CustomerCreditInsufficientEmailTemplate
|
|
|
206
210
|
title: translate('notification.common.viewSubscription', locale),
|
|
207
211
|
link: viewSubscriptionLink,
|
|
208
212
|
},
|
|
213
|
+
{
|
|
214
|
+
name: translate('notification.common.viewCreditGrant', locale),
|
|
215
|
+
title: translate('notification.common.viewCreditGrant', locale),
|
|
216
|
+
link: getCustomerIndexUrl({
|
|
217
|
+
locale,
|
|
218
|
+
userDid,
|
|
219
|
+
}),
|
|
220
|
+
},
|
|
209
221
|
].filter(Boolean);
|
|
210
222
|
|
|
211
223
|
// 构建标题和正文
|
|
@@ -284,7 +284,6 @@ export default function createQueue<T = any>({ name, onJob, options = defaults }
|
|
|
284
284
|
Job.initialize(sequelize);
|
|
285
285
|
}
|
|
286
286
|
const jobs = await store.getJobs();
|
|
287
|
-
logger.info(`${name} jobs to populate`, { count: jobs.length });
|
|
288
287
|
jobs.forEach((x) => {
|
|
289
288
|
if (x.job && x.id) {
|
|
290
289
|
push({ job: x.job, id: x.id, persist: false });
|
package/api/src/libs/util.ts
CHANGED
|
@@ -13,6 +13,8 @@ import axios from 'axios';
|
|
|
13
13
|
import { ethers } from 'ethers';
|
|
14
14
|
import { fromUnitToToken } from '@ocap/util';
|
|
15
15
|
import get from 'lodash/get';
|
|
16
|
+
import numbro from 'numbro';
|
|
17
|
+
import { trimEnd } from 'lodash';
|
|
16
18
|
import dayjs from './dayjs';
|
|
17
19
|
import { blocklet, wallet } from './auth';
|
|
18
20
|
import type { PaymentCurrency, PaymentMethod, Subscription } from '../store/models';
|
|
@@ -579,3 +581,25 @@ export function getExplorerTxUrl({
|
|
|
579
581
|
}
|
|
580
582
|
return joinURL(explorerHost, '/tx', txHash);
|
|
581
583
|
}
|
|
584
|
+
|
|
585
|
+
export function formatNumber(
|
|
586
|
+
n: number | string,
|
|
587
|
+
precision: number = 6,
|
|
588
|
+
trim: boolean = true,
|
|
589
|
+
thousandSeparated: boolean = true
|
|
590
|
+
) {
|
|
591
|
+
if (!n || n === '0') {
|
|
592
|
+
return '0';
|
|
593
|
+
}
|
|
594
|
+
const num = numbro(n);
|
|
595
|
+
const options = {
|
|
596
|
+
thousandSeparated,
|
|
597
|
+
...((precision || precision === 0) && { mantissa: precision }),
|
|
598
|
+
};
|
|
599
|
+
const result = num.format(options);
|
|
600
|
+
if (!trim) {
|
|
601
|
+
return result;
|
|
602
|
+
}
|
|
603
|
+
const [left, right] = result.split('.');
|
|
604
|
+
return right ? [left, trimEnd(right, '0')].filter(Boolean).join('.') : left;
|
|
605
|
+
}
|
|
@@ -226,6 +226,7 @@ async function consumeAvailableCredits(
|
|
|
226
226
|
await createEvent('Customer', 'customer.credit.insufficient', context.customer, {
|
|
227
227
|
metadata: {
|
|
228
228
|
meter_event_id: context.meterEvent.id,
|
|
229
|
+
meter_name: context.meter.name,
|
|
229
230
|
meter_event_name: context.meterEvent.event_name,
|
|
230
231
|
required_amount: remainingToConsume.toString(),
|
|
231
232
|
available_amount: totalAvailable.toString(),
|
|
@@ -262,6 +263,7 @@ async function consumeAvailableCredits(
|
|
|
262
263
|
await createEvent('Customer', 'customer.credit.insufficient', context.customer, {
|
|
263
264
|
metadata: {
|
|
264
265
|
meter_event_id: context.meterEvent.id,
|
|
266
|
+
meter_name: context.meter.name,
|
|
265
267
|
meter_event_name: context.meterEvent.event_name,
|
|
266
268
|
required_amount: remainingToConsume.toString(),
|
|
267
269
|
available_amount: '0',
|
|
@@ -605,7 +605,8 @@ export async function startNotificationQueue() {
|
|
|
605
605
|
{
|
|
606
606
|
customerId: customer.id,
|
|
607
607
|
currencyId: metadata.currency_id,
|
|
608
|
-
|
|
608
|
+
meterName: metadata.meter_name || 'Service',
|
|
609
|
+
meterEventName: metadata.meter_event_name,
|
|
609
610
|
requiredAmount: metadata.required_amount,
|
|
610
611
|
availableAmount: metadata.available_amount,
|
|
611
612
|
pendingAmount: metadata.pending_amount || '0',
|
|
@@ -613,7 +614,7 @@ export async function startNotificationQueue() {
|
|
|
613
614
|
},
|
|
614
615
|
[customer.id, metadata.currency_id, metadata.subscription_id],
|
|
615
616
|
true,
|
|
616
|
-
|
|
617
|
+
24 * 3600 // 1天
|
|
617
618
|
);
|
|
618
619
|
});
|
|
619
620
|
}
|
|
@@ -73,7 +73,11 @@ router.get('/', authMine, async (req, res) => {
|
|
|
73
73
|
const { page, pageSize, ...query } = await listSchema.validateAsync(req.query, { stripUnknown: true });
|
|
74
74
|
const where = getWhereFromKvQuery(query.q);
|
|
75
75
|
if (query.customer_id) {
|
|
76
|
-
|
|
76
|
+
const customer = await Customer.findByPkOrDid(query.customer_id);
|
|
77
|
+
if (!customer) {
|
|
78
|
+
throw new Error(`Customer ${query.customer_id} not found`);
|
|
79
|
+
}
|
|
80
|
+
where.customer_id = customer.id;
|
|
77
81
|
}
|
|
78
82
|
if (query.currency_id) {
|
|
79
83
|
where.currency_id = query.currency_id;
|
|
@@ -182,16 +186,20 @@ router.post('/', auth, async (req, res) => {
|
|
|
182
186
|
|
|
183
187
|
let customer = await Customer.findByPkOrDid(req.body.customer_id);
|
|
184
188
|
if (!customer) {
|
|
185
|
-
|
|
189
|
+
if (req.body.customer_id.startsWith('cus_')) {
|
|
190
|
+
return res.status(404).json({ error: `Customer ${req.body.customer_id} not found` });
|
|
191
|
+
}
|
|
192
|
+
const did = req.body.customer_id;
|
|
193
|
+
const { user: userInfo } = await blocklet.getUser(did);
|
|
186
194
|
if (!userInfo) {
|
|
187
|
-
return res.status(404).json({ error: `User ${
|
|
195
|
+
return res.status(404).json({ error: `User ${did} not found` });
|
|
188
196
|
}
|
|
189
197
|
customer = await Customer.create({
|
|
190
198
|
livemode: true,
|
|
191
|
-
did: userInfo
|
|
192
|
-
name: userInfo
|
|
193
|
-
email: userInfo
|
|
194
|
-
phone: userInfo
|
|
199
|
+
did: userInfo?.did || did,
|
|
200
|
+
name: userInfo?.fullName || did,
|
|
201
|
+
email: userInfo?.email || '',
|
|
202
|
+
phone: userInfo?.phone || '',
|
|
195
203
|
address: Customer.formatAddressFromUser(userInfo),
|
|
196
204
|
description: userInfo.remark || '',
|
|
197
205
|
metadata: {},
|
|
@@ -384,37 +384,38 @@ router.get('/:id', auth, async (req, res) => {
|
|
|
384
384
|
try {
|
|
385
385
|
const doc = await Customer.findByPkOrDid(req.params.id as string);
|
|
386
386
|
if (doc) {
|
|
387
|
-
res.json(doc);
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
did: user.did,
|
|
400
|
-
name: user.fullName,
|
|
401
|
-
email: user.email,
|
|
402
|
-
phone: user.phone,
|
|
403
|
-
address: Customer.formatAddressFromUser(user),
|
|
404
|
-
description: user.remark,
|
|
405
|
-
metadata: {},
|
|
406
|
-
balance: '0',
|
|
407
|
-
next_invoice_sequence: 1,
|
|
408
|
-
delinquent: false,
|
|
409
|
-
invoice_prefix: Customer.getInvoicePrefix(),
|
|
410
|
-
});
|
|
411
|
-
logger.info('customer created', {
|
|
412
|
-
customerId: customer.id,
|
|
413
|
-
did: customer.did,
|
|
414
|
-
});
|
|
415
|
-
return res.json(customer);
|
|
387
|
+
return res.json(doc);
|
|
388
|
+
}
|
|
389
|
+
if (req.body.create) {
|
|
390
|
+
if (!req.user) {
|
|
391
|
+
return res.status(403).json({ error: 'Unauthorized' });
|
|
392
|
+
}
|
|
393
|
+
if (req.params.id.startsWith('cus_')) {
|
|
394
|
+
return res.status(404).json({ error: 'Customer not found' });
|
|
395
|
+
}
|
|
396
|
+
const { user } = await blocklet.getUser(req.params.id);
|
|
397
|
+
if (!user) {
|
|
398
|
+
return res.status(404).json({ error: 'User not found' });
|
|
416
399
|
}
|
|
417
|
-
|
|
400
|
+
const customer = await Customer.create({
|
|
401
|
+
livemode: true,
|
|
402
|
+
did: user?.did || req.params.id,
|
|
403
|
+
name: user?.fullName ?? req.params.id,
|
|
404
|
+
email: user?.email ?? '',
|
|
405
|
+
phone: user?.phone ?? '',
|
|
406
|
+
address: Customer.formatAddressFromUser(user),
|
|
407
|
+
description: user?.remark ?? '',
|
|
408
|
+
metadata: {},
|
|
409
|
+
balance: '0',
|
|
410
|
+
next_invoice_sequence: 1,
|
|
411
|
+
delinquent: false,
|
|
412
|
+
invoice_prefix: Customer.getInvoicePrefix(),
|
|
413
|
+
});
|
|
414
|
+
logger.info('customer created', {
|
|
415
|
+
customerId: customer.id,
|
|
416
|
+
did: customer.did,
|
|
417
|
+
});
|
|
418
|
+
return res.json(customer);
|
|
418
419
|
}
|
|
419
420
|
return res.status(404).json(null);
|
|
420
421
|
} catch (err) {
|
|
@@ -52,6 +52,7 @@ const formatBeforeSave = (payload: any) => {
|
|
|
52
52
|
payment_intent_data: null,
|
|
53
53
|
donation_settings: null,
|
|
54
54
|
enable_subscription_grouping: false,
|
|
55
|
+
lookup_key: null,
|
|
55
56
|
},
|
|
56
57
|
pick(payload, [
|
|
57
58
|
'name',
|
|
@@ -73,6 +74,7 @@ const formatBeforeSave = (payload: any) => {
|
|
|
73
74
|
'donation_settings',
|
|
74
75
|
'metadata',
|
|
75
76
|
'enable_subscription_grouping',
|
|
77
|
+
'lookup_key',
|
|
76
78
|
])
|
|
77
79
|
);
|
|
78
80
|
|
|
@@ -150,11 +152,12 @@ export async function createPaymentLink(payload: any) {
|
|
|
150
152
|
raw.invoice_creation = { enabled: true };
|
|
151
153
|
}
|
|
152
154
|
|
|
153
|
-
return PaymentLink.
|
|
155
|
+
return PaymentLink.insert(raw as PaymentLink);
|
|
154
156
|
}
|
|
155
157
|
|
|
156
158
|
const PaymentLinkCreateSchema = Joi.object({
|
|
157
|
-
name: Joi.string().optional(),
|
|
159
|
+
name: Joi.string().max(255).empty('').optional(),
|
|
160
|
+
lookup_key: Joi.string().max(128).empty('').optional(),
|
|
158
161
|
line_items: Joi.array()
|
|
159
162
|
.items(
|
|
160
163
|
Joi.object({
|
|
@@ -272,7 +275,7 @@ router.get('/', auth, async (req, res) => {
|
|
|
272
275
|
});
|
|
273
276
|
|
|
274
277
|
router.get('/:id', auth, async (req, res) => {
|
|
275
|
-
const doc = await PaymentLink.
|
|
278
|
+
const doc = await PaymentLink.findByPkOrLookupKey(req.params.id || '');
|
|
276
279
|
|
|
277
280
|
if (doc) {
|
|
278
281
|
// @ts-ignore
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { DataTypes } from 'sequelize';
|
|
2
|
+
|
|
3
|
+
import { Migration, safeApplyColumnChanges } from '../migrate';
|
|
4
|
+
|
|
5
|
+
export const up: Migration = async ({ context }) => {
|
|
6
|
+
await safeApplyColumnChanges(context, {
|
|
7
|
+
payment_links: [
|
|
8
|
+
{
|
|
9
|
+
name: 'lookup_key',
|
|
10
|
+
field: {
|
|
11
|
+
type: DataTypes.STRING(128),
|
|
12
|
+
allowNull: true,
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const down: Migration = async ({ context }) => {
|
|
20
|
+
await context.removeColumn('payment_links', 'lookup_key');
|
|
21
|
+
};
|
|
@@ -340,13 +340,13 @@ export class CreditGrant extends Model<InferAttributes<CreditGrant>, InferCreati
|
|
|
340
340
|
status: 'granted',
|
|
341
341
|
remaining_amount: { [Op.gt]: '0' },
|
|
342
342
|
[Op.and]: [
|
|
343
|
-
// effective_at check: null表示立即生效,或者已经生效
|
|
343
|
+
// effective_at check: null/0表示立即生效,或者已经生效
|
|
344
344
|
{
|
|
345
|
-
[Op.or]: [{ effective_at: null }, { effective_at: { [Op.lte]: now } }],
|
|
345
|
+
[Op.or]: [{ effective_at: null }, { effective_at: 0 }, { effective_at: { [Op.lte]: now } }],
|
|
346
346
|
},
|
|
347
|
-
// expires_at check: null表示永不过期,或者还未过期
|
|
347
|
+
// expires_at check: null/0表示永不过期,或者还未过期
|
|
348
348
|
{
|
|
349
|
-
[Op.or]: [{ expires_at: null }, { expires_at: { [Op.gt]: now } }],
|
|
349
|
+
[Op.or]: [{ expires_at: null }, { expires_at: 0 }, { expires_at: { [Op.gt]: now } }],
|
|
350
350
|
},
|
|
351
351
|
],
|
|
352
352
|
};
|
|
@@ -308,12 +308,12 @@ export class Customer extends Model<InferAttributes<Customer>, InferCreationAttr
|
|
|
308
308
|
|
|
309
309
|
public static formatAddressFromUser(user: any, customer?: Customer) {
|
|
310
310
|
return {
|
|
311
|
-
country: user
|
|
312
|
-
state: user
|
|
313
|
-
city: user
|
|
314
|
-
line1: user
|
|
315
|
-
line2: user
|
|
316
|
-
postal_code: user
|
|
311
|
+
country: user?.address?.country || customer?.address?.country || '',
|
|
312
|
+
state: user?.address?.province || customer?.address?.state || '',
|
|
313
|
+
city: user?.address?.city || customer?.address?.city || '',
|
|
314
|
+
line1: user?.address?.line1 || customer?.address?.line1 || '',
|
|
315
|
+
line2: user?.address?.line2 || customer?.address?.line2 || '',
|
|
316
|
+
postal_code: user?.address?.postalCode || customer?.address?.postal_code || '',
|
|
317
317
|
};
|
|
318
318
|
}
|
|
319
319
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/lines-between-class-members */
|
|
2
|
-
import { CreationOptional, DataTypes, InferAttributes, InferCreationAttributes, Model } from 'sequelize';
|
|
2
|
+
import { CreationOptional, DataTypes, InferAttributes, InferCreationAttributes, Model, Op } from 'sequelize';
|
|
3
3
|
import type { LiteralUnion } from 'type-fest';
|
|
4
4
|
|
|
5
5
|
import { createEvent } from '../../libs/audit';
|
|
@@ -89,6 +89,9 @@ export class PaymentLink extends Model<InferAttributes<PaymentLink>, InferCreati
|
|
|
89
89
|
|
|
90
90
|
declare enable_subscription_grouping?: boolean;
|
|
91
91
|
|
|
92
|
+
// A lookup key used to retrieve payment links dynamically from a static string
|
|
93
|
+
declare lookup_key: string | null;
|
|
94
|
+
|
|
92
95
|
// TODO: following fields not supported
|
|
93
96
|
// application_fee_amount
|
|
94
97
|
// application_fee_percent
|
|
@@ -224,6 +227,10 @@ export class PaymentLink extends Model<InferAttributes<PaymentLink>, InferCreati
|
|
|
224
227
|
type: DataTypes.BOOLEAN,
|
|
225
228
|
defaultValue: false,
|
|
226
229
|
},
|
|
230
|
+
lookup_key: {
|
|
231
|
+
type: DataTypes.STRING(128),
|
|
232
|
+
allowNull: true,
|
|
233
|
+
},
|
|
227
234
|
},
|
|
228
235
|
{
|
|
229
236
|
sequelize,
|
|
@@ -250,6 +257,27 @@ export class PaymentLink extends Model<InferAttributes<PaymentLink>, InferCreati
|
|
|
250
257
|
as: 'currency',
|
|
251
258
|
});
|
|
252
259
|
}
|
|
260
|
+
|
|
261
|
+
public static async insert(paymentLink: InferCreationAttributes<PaymentLink>) {
|
|
262
|
+
if (paymentLink.lookup_key) {
|
|
263
|
+
const exist = await this.count({ where: { lookup_key: paymentLink.lookup_key } });
|
|
264
|
+
if (exist) {
|
|
265
|
+
throw new Error('lookup_key already exists');
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return this.create(paymentLink);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
public static findByPkOrLookupKey(id: string, options: any = {}) {
|
|
273
|
+
if (!id) {
|
|
274
|
+
throw new Error('id is required');
|
|
275
|
+
}
|
|
276
|
+
return this.findOne({
|
|
277
|
+
where: { [Op.or]: [{ id }, { lookup_key: id }] },
|
|
278
|
+
...options,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
253
281
|
}
|
|
254
282
|
|
|
255
283
|
export type TPaymentLink = InferAttributes<PaymentLink>;
|
package/blocklet.yml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payment-kit",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.7",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "blocklet dev --open",
|
|
6
6
|
"lint": "tsc --noEmit && eslint src api/src --ext .mjs,.js,.jsx,.ts,.tsx",
|
|
@@ -44,30 +44,30 @@
|
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@abtnode/cron": "^1.16.46",
|
|
47
|
-
"@arcblock/did": "^1.
|
|
47
|
+
"@arcblock/did": "^1.21.0",
|
|
48
48
|
"@arcblock/did-auth-storage-nedb": "^1.7.1",
|
|
49
|
-
"@arcblock/did-connect": "^3.0.
|
|
50
|
-
"@arcblock/did-util": "^1.
|
|
51
|
-
"@arcblock/jwt": "^1.
|
|
52
|
-
"@arcblock/ux": "^3.0.
|
|
53
|
-
"@arcblock/validator": "^1.
|
|
54
|
-
"@blocklet/did-space-js": "^1.1.
|
|
49
|
+
"@arcblock/did-connect": "^3.0.32",
|
|
50
|
+
"@arcblock/did-util": "^1.21.0",
|
|
51
|
+
"@arcblock/jwt": "^1.21.0",
|
|
52
|
+
"@arcblock/ux": "^3.0.32",
|
|
53
|
+
"@arcblock/validator": "^1.21.0",
|
|
54
|
+
"@blocklet/did-space-js": "^1.1.8",
|
|
55
55
|
"@blocklet/js-sdk": "^1.16.46",
|
|
56
56
|
"@blocklet/logger": "^1.16.46",
|
|
57
|
-
"@blocklet/payment-react": "1.19.
|
|
57
|
+
"@blocklet/payment-react": "1.19.7",
|
|
58
58
|
"@blocklet/sdk": "^1.16.46",
|
|
59
|
-
"@blocklet/ui-react": "^3.0.
|
|
59
|
+
"@blocklet/ui-react": "^3.0.32",
|
|
60
60
|
"@blocklet/uploader": "^0.2.4",
|
|
61
61
|
"@blocklet/xss": "^0.2.2",
|
|
62
62
|
"@mui/icons-material": "^7.1.2",
|
|
63
63
|
"@mui/lab": "7.0.0-beta.14",
|
|
64
64
|
"@mui/material": "^7.1.2",
|
|
65
65
|
"@mui/system": "^7.1.1",
|
|
66
|
-
"@ocap/asset": "^1.
|
|
67
|
-
"@ocap/client": "^1.
|
|
68
|
-
"@ocap/mcrypto": "^1.
|
|
69
|
-
"@ocap/util": "^1.
|
|
70
|
-
"@ocap/wallet": "^1.
|
|
66
|
+
"@ocap/asset": "^1.21.0",
|
|
67
|
+
"@ocap/client": "^1.21.0",
|
|
68
|
+
"@ocap/mcrypto": "^1.21.0",
|
|
69
|
+
"@ocap/util": "^1.21.0",
|
|
70
|
+
"@ocap/wallet": "^1.21.0",
|
|
71
71
|
"@stripe/react-stripe-js": "^2.9.0",
|
|
72
72
|
"@stripe/stripe-js": "^2.4.0",
|
|
73
73
|
"ahooks": "^3.8.5",
|
|
@@ -97,6 +97,7 @@
|
|
|
97
97
|
"morgan": "^1.10.0",
|
|
98
98
|
"mui-daterange-picker": "^1.0.5",
|
|
99
99
|
"nanoid": "^3.3.11",
|
|
100
|
+
"numbro": "^2.5.0",
|
|
100
101
|
"p-all": "3.0.0",
|
|
101
102
|
"p-wait-for": "^3.2.0",
|
|
102
103
|
"pretty-ms-i18n": "^1.0.3",
|
|
@@ -122,7 +123,7 @@
|
|
|
122
123
|
"devDependencies": {
|
|
123
124
|
"@abtnode/types": "^1.16.46",
|
|
124
125
|
"@arcblock/eslint-config-ts": "^0.3.3",
|
|
125
|
-
"@blocklet/payment-types": "1.19.
|
|
126
|
+
"@blocklet/payment-types": "1.19.7",
|
|
126
127
|
"@types/cookie-parser": "^1.4.9",
|
|
127
128
|
"@types/cors": "^2.8.19",
|
|
128
129
|
"@types/debug": "^4.1.12",
|
|
@@ -168,5 +169,5 @@
|
|
|
168
169
|
"parser": "typescript"
|
|
169
170
|
}
|
|
170
171
|
},
|
|
171
|
-
"gitHead": "
|
|
172
|
+
"gitHead": "83b0e5ac23d05e12d07929789f06f85b30929169"
|
|
172
173
|
}
|
|
@@ -193,12 +193,12 @@ export default function CustomerCreditGrantDetail() {
|
|
|
193
193
|
label={t('common.effectiveDate')}
|
|
194
194
|
value={formatTime(data.effective_at ? data.effective_at * 1000 : data.created_at, 'YYYY-MM-DD HH:mm:ss')}
|
|
195
195
|
/>
|
|
196
|
-
{data.expires_at
|
|
196
|
+
{data.expires_at ? (
|
|
197
197
|
<InfoMetric
|
|
198
198
|
label={t('common.expirationDate')}
|
|
199
199
|
value={formatTime(data.expires_at * 1000, 'YYYY-MM-DD HH:mm:ss')}
|
|
200
200
|
/>
|
|
201
|
-
)}
|
|
201
|
+
) : null}
|
|
202
202
|
</Stack>
|
|
203
203
|
</Box>
|
|
204
204
|
</Box>
|
|
@@ -488,7 +488,6 @@ export default function CustomerHome() {
|
|
|
488
488
|
</Box>
|
|
489
489
|
<CreditOverview customerId={data?.id || ''} settings={settings} />
|
|
490
490
|
</Box>
|
|
491
|
-
<Divider />
|
|
492
491
|
</ConditionalSection>
|
|
493
492
|
);
|
|
494
493
|
|
|
@@ -586,6 +585,7 @@ export default function CustomerHome() {
|
|
|
586
585
|
{SummaryCard}
|
|
587
586
|
{SummaryCard && <Divider />}
|
|
588
587
|
{CreditCard}
|
|
588
|
+
{CreditCard && <Divider />}
|
|
589
589
|
{SubscriptionCard}
|
|
590
590
|
{SubscriptionCard && <Divider />}
|
|
591
591
|
{InvoiceCard}
|