payment-kit 1.13.246 → 1.13.247

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.
@@ -1,3 +1,4 @@
1
+ /* eslint-disable no-await-in-loop */
1
2
  import component from '@blocklet/sdk/lib/component';
2
3
  import { BN } from '@ocap/util';
3
4
  import type { LiteralUnion } from 'type-fest';
@@ -7,11 +8,13 @@ import {
7
8
  Customer,
8
9
  Invoice,
9
10
  InvoiceItem,
11
+ PaymentCurrency,
10
12
  Price,
11
13
  PriceRecurring,
12
14
  Subscription,
13
15
  SubscriptionItem,
14
16
  TLineItemExpanded,
17
+ UsageRecord,
15
18
  } from '../store/models';
16
19
  import dayjs from './dayjs';
17
20
  import logger from './logger';
@@ -377,3 +380,50 @@ export async function expandSubscriptionItems(subscriptionId: string) {
377
380
  const items = await SubscriptionItem.findAll({ where: { subscription_id: subscriptionId } });
378
381
  return Price.expand(items.map((x) => x.toJSON()));
379
382
  }
383
+
384
+ export async function getUpcomingInvoiceAmount(subscriptionId: string) {
385
+ const subscription = await Subscription.findByPk(subscriptionId);
386
+ if (!subscription) {
387
+ throw new Error('Subscription not found');
388
+ }
389
+
390
+ if (subscription.isActive() === false) {
391
+ throw new Error('Subscription not active, so usage check is skipped');
392
+ }
393
+
394
+ const currency = await PaymentCurrency.findByPk(subscription.currency_id);
395
+
396
+ const items = await SubscriptionItem.findAll({ where: { subscription_id: subscriptionId } });
397
+ const expanded = await Price.expand(items.map((x) => x.toJSON()));
398
+
399
+ let amount = new BN(0);
400
+ for (const item of expanded) {
401
+ const price = getSubscriptionItemPrice(item);
402
+ if (price.type === 'recurring') {
403
+ const unit = getPriceUintAmountByCurrency(price, subscription.currency_id);
404
+ if (price.recurring?.usage_type === 'licensed') {
405
+ amount = amount.add(new BN(unit).mul(new BN(item.quantity)));
406
+ }
407
+ if (price.recurring?.usage_type === 'metered') {
408
+ const rawQuantity = await UsageRecord.getSummary({
409
+ // @ts-ignore
410
+ id: item?.id as string,
411
+ start: subscription.current_period_start,
412
+ end: subscription.current_period_end,
413
+ method: price.recurring?.aggregate_usage as any,
414
+ dryRun: true,
415
+ });
416
+ // @ts-ignore
417
+ const quantity = price.transformQuantity(rawQuantity);
418
+ amount = amount.add(new BN(unit).mul(new BN(quantity)));
419
+ }
420
+ }
421
+ }
422
+
423
+ return {
424
+ amount: amount.toString(),
425
+ start: subscription.current_period_start,
426
+ end: subscription.current_period_end,
427
+ currency,
428
+ };
429
+ }
@@ -12,7 +12,12 @@ import logger from '../libs/logger';
12
12
  import { isDelegationSufficientForPayment } from '../libs/payment';
13
13
  import { authenticate } from '../libs/security';
14
14
  import { expandLineItems, getFastCheckoutAmount, isLineItemAligned } from '../libs/session';
15
- import { createProration, getSubscriptionCreateSetup, getSubscriptionRefundSetup } from '../libs/subscription';
15
+ import {
16
+ createProration,
17
+ getSubscriptionCreateSetup,
18
+ getSubscriptionRefundSetup,
19
+ getUpcomingInvoiceAmount,
20
+ } from '../libs/subscription';
16
21
  import { MAX_SUBSCRIPTION_ITEM_COUNT, formatMetadata } from '../libs/util';
17
22
  import { invoiceQueue } from '../queues/invoice';
18
23
  import { addSubscriptionJob, subscriptionQueue } from '../queues/subscription';
@@ -1469,4 +1474,15 @@ router.get('/:id/summary', authPortal, async (req, res) => {
1469
1474
  }
1470
1475
  });
1471
1476
 
1477
+ // Get upcoming invoice amount
1478
+ router.get('/:id/upcoming', authPortal, async (req, res) => {
1479
+ try {
1480
+ const result = await getUpcomingInvoiceAmount(req.params.id as string);
1481
+ return res.json(result);
1482
+ } catch (err) {
1483
+ console.error(err);
1484
+ return res.json({ error: err.message });
1485
+ }
1486
+ });
1487
+
1472
1488
  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.13.246
17
+ version: 1.13.247
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.13.246",
3
+ "version": "1.13.247",
4
4
  "scripts": {
5
5
  "dev": "cross-env COMPONENT_STORE_URL=https://test.store.blocklet.dev blocklet dev --open",
6
6
  "eject": "vite eject",
@@ -51,7 +51,7 @@
51
51
  "@arcblock/ux": "^2.9.77",
52
52
  "@arcblock/validator": "^1.18.116",
53
53
  "@blocklet/logger": "1.16.26",
54
- "@blocklet/payment-react": "1.13.246",
54
+ "@blocklet/payment-react": "1.13.247",
55
55
  "@blocklet/sdk": "1.16.26",
56
56
  "@blocklet/ui-react": "^2.9.77",
57
57
  "@blocklet/uploader": "^0.1.6",
@@ -116,7 +116,7 @@
116
116
  "devDependencies": {
117
117
  "@abtnode/types": "1.16.26",
118
118
  "@arcblock/eslint-config-ts": "^0.3.0",
119
- "@blocklet/payment-types": "1.13.246",
119
+ "@blocklet/payment-types": "1.13.247",
120
120
  "@types/cookie-parser": "^1.4.7",
121
121
  "@types/cors": "^2.8.17",
122
122
  "@types/dotenv-flow": "^3.3.3",
@@ -155,5 +155,5 @@
155
155
  "parser": "typescript"
156
156
  }
157
157
  },
158
- "gitHead": "2d37d4058cf3b15568f5c49457c32e7bf29b8359"
158
+ "gitHead": "517ee5ea4b2af99ce581dc74904ef30b08cbd32e"
159
159
  }
@@ -1,6 +1,7 @@
1
1
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
2
- import { formatTime } from '@blocklet/payment-react';
2
+ import { api, formatBNStr, formatTime } from '@blocklet/payment-react';
3
3
  import type { TSubscriptionExpanded } from '@blocklet/payment-types';
4
+ import { useRequest } from 'ahooks';
4
5
 
5
6
  import InfoMetric from '../info-metric';
6
7
 
@@ -8,8 +9,14 @@ type Props = {
8
9
  subscription: TSubscriptionExpanded;
9
10
  };
10
11
 
12
+ const fetchUpcoming = (id: string): Promise<{ amount: string }> => {
13
+ return api.get(`/api/subscriptions/${id}/upcoming`).then((res) => res.data);
14
+ };
15
+
11
16
  export default function SubscriptionMetrics({ subscription }: Props) {
12
17
  const { t } = useLocaleContext();
18
+ const { data: upcoming } = useRequest(() => fetchUpcoming(subscription.id));
19
+
13
20
  let scheduleToCancelTime = 0;
14
21
  if (['active', 'trialing', 'past_due'].includes(subscription.status) && subscription.cancel_at) {
15
22
  scheduleToCancelTime = subscription.cancel_at * 1000;
@@ -31,6 +38,15 @@ export default function SubscriptionMetrics({ subscription }: Props) {
31
38
  divider
32
39
  />
33
40
  )}
41
+ {upcoming && upcoming.amount !== '0' && (
42
+ <InfoMetric
43
+ label={t('admin.subscription.nextInvoiceAmount')}
44
+ value={`${formatBNStr(upcoming.amount, subscription.paymentCurrency.decimal)} ${
45
+ subscription.paymentCurrency.symbol
46
+ } (${t('common.estimated')})`}
47
+ divider
48
+ />
49
+ )}
34
50
  {scheduleToCancelTime > 0 && (
35
51
  <InfoMetric label={t('admin.subscription.cancel.schedule')} value={formatTime(scheduleToCancelTime)} divider />
36
52
  )}
@@ -4,6 +4,7 @@ export default flat({
4
4
  common: {
5
5
  redirecting: 'Redirecting...',
6
6
  title: 'Name',
7
+ estimated: 'Estimated',
7
8
  metadata: {
8
9
  label: 'Metadata',
9
10
  add: 'Add more metadata',
@@ -384,6 +385,7 @@ export default flat({
384
385
  discount: 'Discount',
385
386
  startedAt: 'Started',
386
387
  nextInvoice: 'Next Invoice',
388
+ nextInvoiceAmount: 'Next Invoice Amount',
387
389
  itemId: 'Subscription Item ID',
388
390
  update: 'Update subscription',
389
391
  resume: 'Resume payment collection',
@@ -4,6 +4,7 @@ export default flat({
4
4
  common: {
5
5
  redirecting: '跳转中...',
6
6
  title: '名称',
7
+ estimated: '预估',
7
8
  metadata: {
8
9
  label: '元数据',
9
10
  add: '添加更多元数据',
@@ -375,7 +376,8 @@ export default flat({
375
376
  trialEnd: '试用期结束于{date}',
376
377
  discount: '折扣',
377
378
  startedAt: '开始于',
378
- nextInvoice: '下一张账单',
379
+ nextInvoice: '下次账单时间',
380
+ nextInvoiceAmount: '下次账单金额',
379
381
  itemId: '订阅项目ID',
380
382
  update: '更新订阅',
381
383
  resume: '恢复付款',