payment-kit 1.19.5 → 1.19.6

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.
@@ -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
- meterEventName,
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: meterEventName,
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 });
@@ -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
- meterEventName: metadata.meter_event_name || 'Service',
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
- 600
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
- where.customer_id = query.customer_id;
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
- const { user: userInfo } = await blocklet.getUser(req.body.customer_id);
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 ${req.body.customer_id} not found` });
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.did,
192
- name: userInfo.fullName,
193
- email: userInfo.email || '',
194
- phone: userInfo.phone || '',
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
- } else {
389
- if (req.body.create) {
390
- if (!req.user) {
391
- return res.status(403).json({ error: 'Unauthorized' });
392
- }
393
- const { user } = await blocklet.getUser(req.params.id);
394
- if (!user) {
395
- return res.status(404).json({ error: 'User not found' });
396
- }
397
- const customer = await Customer.create({
398
- livemode: true,
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
- return res.status(404).json(null);
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.create(raw as 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.findByPk(req.params.id);
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
+ };
@@ -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.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 || '',
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
@@ -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.19.5
17
+ version: 1.19.6
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.19.5",
3
+ "version": "1.19.6",
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.20.15",
47
+ "@arcblock/did": "^1.21.0",
48
48
  "@arcblock/did-auth-storage-nedb": "^1.7.1",
49
- "@arcblock/did-connect": "^3.0.26",
50
- "@arcblock/did-util": "^1.20.15",
51
- "@arcblock/jwt": "^1.20.15",
52
- "@arcblock/ux": "^3.0.26",
53
- "@arcblock/validator": "^1.20.15",
54
- "@blocklet/did-space-js": "^1.1.6",
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.5",
57
+ "@blocklet/payment-react": "1.19.6",
58
58
  "@blocklet/sdk": "^1.16.46",
59
- "@blocklet/ui-react": "^3.0.26",
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.20.15",
67
- "@ocap/client": "^1.20.15",
68
- "@ocap/mcrypto": "^1.20.15",
69
- "@ocap/util": "^1.20.15",
70
- "@ocap/wallet": "^1.20.15",
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.5",
126
+ "@blocklet/payment-types": "1.19.6",
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": "b2cb3888b7efa2cc71591f75240616c36e945b09"
172
+ "gitHead": "d062db562d215b8ef5485b3f291d7e4f994093be"
172
173
  }
@@ -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}