payment-kit 1.18.56 → 1.19.1

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.
Files changed (214) hide show
  1. package/.eslintrc.js +6 -0
  2. package/api/src/crons/index.ts +8 -0
  3. package/api/src/index.ts +4 -0
  4. package/api/src/libs/credit-grant.ts +146 -0
  5. package/api/src/libs/env.ts +1 -0
  6. package/api/src/libs/invoice.ts +4 -3
  7. package/api/src/libs/notification/template/base.ts +388 -2
  8. package/api/src/libs/notification/template/customer-credit-grant-granted.ts +149 -0
  9. package/api/src/libs/notification/template/customer-credit-grant-low-balance.ts +151 -0
  10. package/api/src/libs/notification/template/customer-credit-insufficient.ts +254 -0
  11. package/api/src/libs/notification/template/subscription-canceled.ts +193 -202
  12. package/api/src/libs/notification/template/subscription-refund-succeeded.ts +215 -237
  13. package/api/src/libs/notification/template/subscription-renewed.ts +130 -200
  14. package/api/src/libs/notification/template/subscription-succeeded.ts +100 -202
  15. package/api/src/libs/notification/template/subscription-trial-start.ts +142 -188
  16. package/api/src/libs/notification/template/subscription-trial-will-end.ts +146 -174
  17. package/api/src/libs/notification/template/subscription-upgraded.ts +96 -192
  18. package/api/src/libs/notification/template/subscription-will-canceled.ts +94 -135
  19. package/api/src/libs/notification/template/subscription-will-renew.ts +220 -245
  20. package/api/src/libs/payment.ts +69 -0
  21. package/api/src/libs/queue/index.ts +3 -2
  22. package/api/src/libs/session.ts +8 -0
  23. package/api/src/libs/subscription.ts +74 -3
  24. package/api/src/libs/ws.ts +23 -1
  25. package/api/src/locales/en.ts +33 -0
  26. package/api/src/locales/zh.ts +31 -0
  27. package/api/src/queues/credit-consume.ts +715 -0
  28. package/api/src/queues/credit-grant.ts +572 -0
  29. package/api/src/queues/notification.ts +173 -128
  30. package/api/src/queues/payment.ts +210 -122
  31. package/api/src/queues/subscription.ts +179 -0
  32. package/api/src/routes/checkout-sessions.ts +157 -9
  33. package/api/src/routes/connect/shared.ts +3 -2
  34. package/api/src/routes/credit-grants.ts +241 -0
  35. package/api/src/routes/credit-transactions.ts +208 -0
  36. package/api/src/routes/index.ts +8 -0
  37. package/api/src/routes/meter-events.ts +347 -0
  38. package/api/src/routes/meters.ts +219 -0
  39. package/api/src/routes/payment-currencies.ts +14 -2
  40. package/api/src/routes/payment-links.ts +1 -1
  41. package/api/src/routes/payment-methods.ts +14 -2
  42. package/api/src/routes/prices.ts +43 -0
  43. package/api/src/routes/pricing-table.ts +13 -7
  44. package/api/src/routes/products.ts +63 -4
  45. package/api/src/routes/settings.ts +1 -1
  46. package/api/src/routes/subscriptions.ts +4 -0
  47. package/api/src/store/migrations/20250610-billing-credit.ts +43 -0
  48. package/api/src/store/models/credit-grant.ts +486 -0
  49. package/api/src/store/models/credit-transaction.ts +268 -0
  50. package/api/src/store/models/customer.ts +8 -0
  51. package/api/src/store/models/index.ts +52 -1
  52. package/api/src/store/models/meter-event.ts +423 -0
  53. package/api/src/store/models/meter.ts +176 -0
  54. package/api/src/store/models/payment-currency.ts +66 -14
  55. package/api/src/store/models/price.ts +6 -0
  56. package/api/src/store/models/product.ts +2 -2
  57. package/api/src/store/models/subscription.ts +24 -0
  58. package/api/src/store/models/types.ts +28 -2
  59. package/api/tests/libs/subscription.spec.ts +53 -0
  60. package/blocklet.yml +9 -1
  61. package/package.json +57 -58
  62. package/scripts/sdk.js +233 -1
  63. package/src/app.tsx +10 -0
  64. package/src/components/actions.tsx +22 -9
  65. package/src/components/balance-list.tsx +40 -12
  66. package/src/components/collapse.tsx +33 -15
  67. package/src/components/copyable.tsx +8 -7
  68. package/src/components/currency.tsx +15 -7
  69. package/src/components/customer/actions.tsx +1 -5
  70. package/src/components/customer/credit-grant-item-list.tsx +99 -0
  71. package/src/components/customer/credit-overview.tsx +233 -0
  72. package/src/components/customer/form.tsx +7 -2
  73. package/src/components/customer/link.tsx +4 -12
  74. package/src/components/customer/notification-preference.tsx +18 -9
  75. package/src/components/customer/overdraft-protection.tsx +112 -41
  76. package/src/components/drawer-form.tsx +42 -18
  77. package/src/components/error.tsx +1 -5
  78. package/src/components/event/list.tsx +9 -10
  79. package/src/components/filter-toolbar.tsx +20 -19
  80. package/src/components/info-card.tsx +32 -18
  81. package/src/components/info-metric.tsx +16 -6
  82. package/src/components/info-row-group.tsx +1 -7
  83. package/src/components/info-row.tsx +30 -24
  84. package/src/components/invoice/action.tsx +1 -7
  85. package/src/components/invoice/list.tsx +34 -26
  86. package/src/components/invoice/recharge.tsx +5 -7
  87. package/src/components/invoice/table.tsx +17 -12
  88. package/src/components/layout/user.tsx +1 -1
  89. package/src/components/metadata/form.tsx +290 -94
  90. package/src/components/metadata/list.tsx +11 -3
  91. package/src/components/meter/actions.tsx +101 -0
  92. package/src/components/meter/add-usage-dialog.tsx +239 -0
  93. package/src/components/meter/events-list.tsx +657 -0
  94. package/src/components/meter/form.tsx +245 -0
  95. package/src/components/meter/products.tsx +264 -0
  96. package/src/components/meter/usage-guide.tsx +174 -0
  97. package/src/components/passport/actions.tsx +9 -4
  98. package/src/components/payment-currency/add.tsx +16 -3
  99. package/src/components/payment-currency/form.tsx +14 -6
  100. package/src/components/payment-intent/actions.tsx +24 -16
  101. package/src/components/payment-intent/list.tsx +30 -9
  102. package/src/components/payment-link/actions.tsx +1 -5
  103. package/src/components/payment-link/after-pay.tsx +4 -2
  104. package/src/components/payment-link/before-pay.tsx +14 -4
  105. package/src/components/payment-link/item.tsx +27 -6
  106. package/src/components/payment-link/preview.tsx +9 -9
  107. package/src/components/payment-link/product-select.tsx +69 -15
  108. package/src/components/payment-method/arcblock.tsx +8 -1
  109. package/src/components/payment-method/base.tsx +8 -1
  110. package/src/components/payment-method/bitcoin.tsx +8 -1
  111. package/src/components/payment-method/ethereum.tsx +8 -1
  112. package/src/components/payment-method/evm-rpc-input.tsx +11 -7
  113. package/src/components/payment-method/form.tsx +2 -7
  114. package/src/components/payment-method/stripe.tsx +2 -0
  115. package/src/components/payouts/actions.tsx +1 -5
  116. package/src/components/payouts/list.tsx +30 -10
  117. package/src/components/payouts/portal/list.tsx +11 -9
  118. package/src/components/price/currency-select.tsx +63 -32
  119. package/src/components/price/form.tsx +895 -370
  120. package/src/components/price/upsell-select.tsx +10 -2
  121. package/src/components/price/upsell.tsx +7 -2
  122. package/src/components/pricing-table/actions.tsx +1 -5
  123. package/src/components/pricing-table/customer-settings.tsx +5 -1
  124. package/src/components/pricing-table/payment-settings.tsx +14 -4
  125. package/src/components/pricing-table/preview.tsx +9 -9
  126. package/src/components/pricing-table/price-item.tsx +6 -1
  127. package/src/components/pricing-table/product-item.tsx +6 -1
  128. package/src/components/pricing-table/product-settings.tsx +17 -4
  129. package/src/components/product/actions.tsx +1 -5
  130. package/src/components/product/add-price.tsx +9 -7
  131. package/src/components/product/create.tsx +8 -9
  132. package/src/components/product/cross-sell-select.tsx +5 -1
  133. package/src/components/product/cross-sell.tsx +7 -2
  134. package/src/components/product/edit-price.tsx +21 -12
  135. package/src/components/product/features.tsx +26 -6
  136. package/src/components/product/form.tsx +115 -72
  137. package/src/components/progress-bar.tsx +1 -1
  138. package/src/components/refund/actions.tsx +1 -7
  139. package/src/components/refund/list.tsx +31 -18
  140. package/src/components/section/header.tsx +12 -14
  141. package/src/components/subscription/actions/cancel.tsx +22 -5
  142. package/src/components/subscription/actions/index.tsx +9 -10
  143. package/src/components/subscription/actions/pause.tsx +32 -6
  144. package/src/components/subscription/actions/slash-stake.tsx +5 -3
  145. package/src/components/subscription/description.tsx +12 -8
  146. package/src/components/subscription/items/index.tsx +31 -16
  147. package/src/components/subscription/items/usage-records.tsx +19 -5
  148. package/src/components/subscription/list.tsx +5 -7
  149. package/src/components/subscription/metrics.tsx +62 -15
  150. package/src/components/subscription/portal/actions.tsx +78 -71
  151. package/src/components/subscription/portal/cancel.tsx +10 -3
  152. package/src/components/subscription/portal/list.tsx +48 -26
  153. package/src/components/uploader.tsx +5 -13
  154. package/src/components/webhook/attempts.tsx +51 -16
  155. package/src/components/webhook/request-info.tsx +8 -6
  156. package/src/contexts/products.tsx +27 -10
  157. package/src/hooks/subscription.ts +34 -0
  158. package/src/libs/meter-utils.ts +196 -0
  159. package/src/libs/util.ts +4 -0
  160. package/src/locales/en.tsx +385 -4
  161. package/src/locales/zh.tsx +364 -0
  162. package/src/pages/admin/billing/index.tsx +61 -33
  163. package/src/pages/admin/billing/invoices/detail.tsx +49 -13
  164. package/src/pages/admin/billing/meters/create.tsx +60 -0
  165. package/src/pages/admin/billing/meters/detail.tsx +435 -0
  166. package/src/pages/admin/billing/meters/index.tsx +210 -0
  167. package/src/pages/admin/billing/meters/meter-event.tsx +346 -0
  168. package/src/pages/admin/billing/subscriptions/detail.tsx +90 -25
  169. package/src/pages/admin/customers/customers/credit-grant/detail.tsx +391 -0
  170. package/src/pages/admin/customers/customers/detail.tsx +67 -14
  171. package/src/pages/admin/customers/customers/index.tsx +6 -1
  172. package/src/pages/admin/customers/index.tsx +5 -0
  173. package/src/pages/admin/developers/events/detail.tsx +37 -11
  174. package/src/pages/admin/developers/index.tsx +1 -1
  175. package/src/pages/admin/developers/webhooks/detail.tsx +41 -11
  176. package/src/pages/admin/index.tsx +15 -2
  177. package/src/pages/admin/overview.tsx +107 -19
  178. package/src/pages/admin/payments/intents/detail.tsx +58 -14
  179. package/src/pages/admin/payments/payouts/detail.tsx +63 -15
  180. package/src/pages/admin/payments/refunds/detail.tsx +58 -14
  181. package/src/pages/admin/products/index.tsx +11 -4
  182. package/src/pages/admin/products/links/create.tsx +22 -4
  183. package/src/pages/admin/products/links/detail.tsx +43 -14
  184. package/src/pages/admin/products/passports/index.tsx +23 -4
  185. package/src/pages/admin/products/prices/actions.tsx +16 -9
  186. package/src/pages/admin/products/prices/detail.tsx +73 -14
  187. package/src/pages/admin/products/prices/list.tsx +15 -3
  188. package/src/pages/admin/products/pricing-tables/create.tsx +45 -12
  189. package/src/pages/admin/products/pricing-tables/detail.tsx +45 -14
  190. package/src/pages/admin/products/products/create.tsx +233 -54
  191. package/src/pages/admin/products/products/detail.tsx +74 -18
  192. package/src/pages/admin/settings/index.tsx +8 -1
  193. package/src/pages/admin/settings/payment-methods/index.tsx +87 -19
  194. package/src/pages/admin/settings/vault-config/edit-form.tsx +42 -28
  195. package/src/pages/admin/settings/vault-config/index.tsx +57 -10
  196. package/src/pages/customer/credit-grant/detail.tsx +308 -0
  197. package/src/pages/customer/index.tsx +76 -17
  198. package/src/pages/customer/invoice/detail.tsx +63 -14
  199. package/src/pages/customer/invoice/past-due.tsx +11 -3
  200. package/src/pages/customer/payout/detail.tsx +56 -13
  201. package/src/pages/customer/recharge/account.tsx +78 -18
  202. package/src/pages/customer/recharge/subscription.tsx +86 -25
  203. package/src/pages/customer/refund/list.tsx +60 -24
  204. package/src/pages/customer/subscription/change-payment.tsx +17 -6
  205. package/src/pages/customer/subscription/change-plan.tsx +34 -7
  206. package/src/pages/customer/subscription/detail.tsx +134 -34
  207. package/src/pages/customer/subscription/embed.tsx +25 -5
  208. package/src/pages/home.tsx +26 -4
  209. package/src/pages/integrations/donations/edit-form.tsx +25 -9
  210. package/src/pages/integrations/donations/index.tsx +26 -9
  211. package/src/pages/integrations/donations/preview.tsx +59 -15
  212. package/src/pages/integrations/index.tsx +10 -1
  213. package/src/pages/integrations/overview.tsx +78 -17
  214. package/vite.config.ts +60 -30
@@ -20,6 +20,7 @@ import { createIdGenerator, formatMetadata } from '../../libs/util';
20
20
  import { sequelize } from '../sequelize';
21
21
  import type { TPaymentCurrency } from './payment-currency';
22
22
  import type { CustomUnitAmount, LineItem, PriceCurrency, PriceRecurring, PriceTier, TransformQuantity } from './types';
23
+ import { Meter, type TMeter } from './meter';
23
24
 
24
25
  export const nextPriceId = createIdGenerator('price', 24);
25
26
 
@@ -32,6 +33,7 @@ type TPriceExpanded = TPrice & {
32
33
  upsells_to: TPriceExpanded;
33
34
  upsells_to_id: string;
34
35
  };
36
+ meter?: TMeter;
35
37
  };
36
38
  type TLineItemExpanded = LineItem & { price: TPriceExpanded; upsell_price: TPriceExpanded };
37
39
 
@@ -395,6 +397,10 @@ export class Price extends Model<InferAttributes<Price>, InferCreationAttributes
395
397
  x.upsell.upsells_to = to;
396
398
  }
397
399
  }
400
+ if (x.recurring?.meter_id) {
401
+ // @ts-ignore
402
+ x.meter = await Meter.findByPk(x.recurring?.meter_id);
403
+ }
398
404
  })
399
405
  );
400
406
  }
@@ -20,7 +20,7 @@ export class Product extends Model<InferAttributes<Product>, InferCreationAttrib
20
20
  declare livemode: boolean;
21
21
  declare locked: CreationOptional<boolean>;
22
22
 
23
- declare type: LiteralUnion<'service' | 'good', string>;
23
+ declare type: LiteralUnion<'service' | 'good' | 'credit', string>;
24
24
 
25
25
  // The product’s name, meant to be displayable to the customer.
26
26
  declare name: string;
@@ -79,7 +79,7 @@ export class Product extends Model<InferAttributes<Product>, InferCreationAttrib
79
79
  defaultValue: false,
80
80
  },
81
81
  type: {
82
- type: DataTypes.ENUM('service', 'good'),
82
+ type: DataTypes.ENUM('service', 'good', 'credit'),
83
83
  },
84
84
  name: {
85
85
  type: DataTypes.STRING(512),
@@ -410,6 +410,30 @@ export class Subscription extends Model<InferAttributes<Subscription>, InferCrea
410
410
  return this.isActive() && (!!this.cancel_at_period_end || !!this.cancel_at);
411
411
  }
412
412
 
413
+ public async isConsumesCredit(): Promise<boolean> {
414
+ // @ts-ignore
415
+ const { SubscriptionItem, Price } = this.sequelize.models;
416
+ if (!SubscriptionItem || !Price) {
417
+ return false;
418
+ }
419
+
420
+ // 查询订阅的所有价格项
421
+ const items = await SubscriptionItem.findAll({
422
+ where: { subscription_id: this.id },
423
+ include: [
424
+ {
425
+ model: Price,
426
+ as: 'price',
427
+ },
428
+ ],
429
+ });
430
+
431
+ return items.some((item: any) => {
432
+ const recurring = item.price?.recurring;
433
+ return recurring && recurring.usage_type === 'metered' && recurring.meter_id;
434
+ });
435
+ }
436
+
413
437
  public async start() {
414
438
  if (this.status === 'active') {
415
439
  logger.warn(`subscription already active: ${this.id}`);
@@ -46,6 +46,7 @@ export type PriceRecurring = {
46
46
  interval_count: number;
47
47
  aggregate_usage?: LiteralUnion<'sum' | 'last_during_period' | 'max' | 'last_ever', string>;
48
48
  usage_type?: LiteralUnion<'licensed' | 'metered', string>;
49
+ meter_id?: string;
49
50
  };
50
51
 
51
52
  export type PriceCurrency = {
@@ -325,7 +326,7 @@ export type PaymentDetails = {
325
326
  arcblock?: {
326
327
  tx_hash: string;
327
328
  payer: string;
328
- type?: LiteralUnion<'slash' | 'transfer' | 'delegate' | 'stake_return', string>;
329
+ type?: LiteralUnion<'slash' | 'transfer' | 'delegate' | 'stake_return' | 'credit', string>;
329
330
  receiver?: string;
330
331
  staking?: {
331
332
  tx_hash: string;
@@ -722,7 +723,11 @@ export type EventType = LiteralUnion<
722
723
  | 'transfer.reversed'
723
724
  | 'transfer.updated'
724
725
  | 'billing.discrepancy'
725
- | 'usage.report.empty',
726
+ | 'usage.report.empty'
727
+ | 'customer.credit.insufficient'
728
+ | 'customer.credit_grant.granted'
729
+ | 'customer.credit_grant.low_balance'
730
+ | 'customer.credit_grant.depleted',
726
731
  string
727
732
  >;
728
733
 
@@ -751,3 +756,24 @@ export type NotificationSetting = {
751
756
  exclude_events?: EventType[];
752
757
  include_events?: EventType[];
753
758
  };
759
+
760
+ export type CreditGrantApplicabilityConfig = {
761
+ scope: {
762
+ prices?: string[]; // 可选,指定适用的价格ID列表
763
+ price_type?: 'metered'; // 可选,按价格类型适用, 目前只支持metered
764
+ };
765
+ };
766
+
767
+ export type MeterEventStatus =
768
+ | 'pending'
769
+ | 'processing'
770
+ | 'requires_action'
771
+ | 'requires_capture'
772
+ | 'completed'
773
+ | 'canceled';
774
+
775
+ export type MeterEventPayload = {
776
+ customer_id: string;
777
+ value: string;
778
+ subscription_id?: string;
779
+ };
@@ -11,6 +11,7 @@ import {
11
11
  getSubscriptionStakeAmountSetup,
12
12
  checkUsageReportEmpty,
13
13
  calculateRecommendedRechargeAmount,
14
+ getSubscriptionCycleSetup,
14
15
  } from '../../src/libs/subscription';
15
16
  import { PaymentMethod, Subscription, SubscriptionItem, UsageRecord, Price } from '../../src/store/models';
16
17
 
@@ -766,3 +767,55 @@ describe('calculateRecommendedRechargeAmount', () => {
766
767
  expect(result.amount).toBe('300');
767
768
  });
768
769
  });
770
+
771
+ describe('getSubscriptionCycleSetup', () => {
772
+ const mockRecurring = {
773
+ interval: 'day',
774
+ interval_count: 1,
775
+ usage_type: 'licensed',
776
+ } as any;
777
+
778
+ it('should return normal setup without catchUp option', () => {
779
+ const previousPeriodEnd = dayjs().subtract(1, 'day').unix();
780
+ const result = getSubscriptionCycleSetup(mockRecurring, previousPeriodEnd);
781
+
782
+ expect(result.missedPeriods).toBe(0);
783
+ expect(result.period.start).toBe(previousPeriodEnd);
784
+ expect(result.recovery).toBeUndefined();
785
+ });
786
+
787
+ it('should detect and calculate missed periods with catchUp enabled', () => {
788
+ const previousPeriodEnd = dayjs().subtract(5, 'days').unix(); // 5 days ago
789
+ const result = getSubscriptionCycleSetup(mockRecurring, previousPeriodEnd, {
790
+ catchUp: true,
791
+ maxMissedPeriods: 10,
792
+ });
793
+
794
+ expect(result.missedPeriods).toBeGreaterThan(0);
795
+ expect(result.recovery).toBeDefined();
796
+ expect(result.recovery?.originalPeriodEnd).toBe(previousPeriodEnd);
797
+ expect(result.recovery?.periodsSkipped).toBeGreaterThan(0);
798
+ expect(result.period.start).toBeGreaterThan(previousPeriodEnd);
799
+ });
800
+
801
+ it('should limit missed periods to maxMissedPeriods', () => {
802
+ const previousPeriodEnd = dayjs().subtract(100, 'days').unix(); // Very old
803
+ const result = getSubscriptionCycleSetup(mockRecurring, previousPeriodEnd, {
804
+ catchUp: true,
805
+ maxMissedPeriods: 10,
806
+ });
807
+
808
+ expect(result.missedPeriods).toBe(10);
809
+ expect(result.recovery?.wasLimited).toBe(true);
810
+ });
811
+
812
+ it('should handle future period end without catchUp', () => {
813
+ const futurePeriodEnd = dayjs().add(1, 'day').unix();
814
+ const result = getSubscriptionCycleSetup(mockRecurring, futurePeriodEnd, {
815
+ catchUp: true,
816
+ });
817
+
818
+ expect(result.missedPeriods).toBe(0);
819
+ expect(result.period.start).toBe(futurePeriodEnd);
820
+ });
821
+ });
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.56
17
+ version: 1.19.1
18
18
  logo: logo.png
19
19
  files:
20
20
  - dist
@@ -169,3 +169,11 @@ events:
169
169
  description: Refund has been successfully processed and completed
170
170
  - type: manual.notification
171
171
  description: Application will send notification to user manually
172
+ - type: customer.credit_grant.granted
173
+ description: Credit grant has been successfully granted
174
+ - type: customer.credit_grant.low_balance
175
+ description: Credit grant has low balance
176
+ - type: customer.credit_grant.depleted
177
+ description: Credit grant has been depleted
178
+ - type: customer.credit.insufficient
179
+ description: Customer has insufficient credit
package/package.json CHANGED
@@ -1,9 +1,8 @@
1
1
  {
2
2
  "name": "payment-kit",
3
- "version": "1.18.56",
3
+ "version": "1.19.1",
4
4
  "scripts": {
5
5
  "dev": "blocklet dev --open",
6
- "eject": "vite eject",
7
6
  "lint": "tsc --noEmit && eslint src api/src --ext .mjs,.js,.jsx,.ts,.tsx",
8
7
  "lint:fix": "pnpm run lint --fix",
9
8
  "format": "prettier -w src",
@@ -47,116 +46,116 @@
47
46
  "@abtnode/cron": "^1.16.44",
48
47
  "@arcblock/did": "^1.20.14",
49
48
  "@arcblock/did-auth-storage-nedb": "^1.7.1",
50
- "@arcblock/did-connect": "^2.13.66",
49
+ "@arcblock/did-connect": "^3.0.1",
51
50
  "@arcblock/did-util": "^1.20.14",
52
51
  "@arcblock/jwt": "^1.20.14",
53
- "@arcblock/ux": "^2.13.66",
52
+ "@arcblock/ux": "^3.0.1",
54
53
  "@arcblock/validator": "^1.20.14",
55
- "@blocklet/did-space-js": "^1.0.60",
54
+ "@blocklet/did-space-js": "^1.0.62",
56
55
  "@blocklet/js-sdk": "^1.16.44",
57
56
  "@blocklet/logger": "^1.16.44",
58
- "@blocklet/payment-react": "1.18.56",
57
+ "@blocklet/payment-react": "1.19.1",
59
58
  "@blocklet/sdk": "^1.16.44",
60
- "@blocklet/ui-react": "^2.13.66",
61
- "@blocklet/uploader": "^0.1.95",
59
+ "@blocklet/ui-react": "^3.0.1",
60
+ "@blocklet/uploader": "^0.1.97",
62
61
  "@blocklet/xss": "^0.1.36",
63
- "@mui/icons-material": "^5.16.6",
64
- "@mui/lab": "^5.0.0-alpha.173",
65
- "@mui/material": "^5.16.6",
66
- "@mui/system": "^5.16.6",
62
+ "@mui/icons-material": "^7.1.2",
63
+ "@mui/lab": "7.0.0-beta.14",
64
+ "@mui/material": "^7.1.2",
65
+ "@mui/system": "^7.1.1",
67
66
  "@ocap/asset": "^1.20.14",
68
67
  "@ocap/client": "^1.20.14",
69
68
  "@ocap/mcrypto": "^1.20.14",
70
69
  "@ocap/util": "^1.20.14",
71
70
  "@ocap/wallet": "^1.20.14",
72
- "@stripe/react-stripe-js": "^2.7.3",
71
+ "@stripe/react-stripe-js": "^2.9.0",
73
72
  "@stripe/stripe-js": "^2.4.0",
74
- "ahooks": "^3.8.0",
75
- "axios": "^1.7.5",
76
- "body-parser": "^1.20.2",
73
+ "ahooks": "^3.8.5",
74
+ "axios": "^1.10.0",
75
+ "body-parser": "^1.20.3",
77
76
  "cls-hooked": "^4.2.2",
78
- "cookie-parser": "^1.4.6",
77
+ "cookie-parser": "^1.4.7",
79
78
  "copy-to-clipboard": "^3.3.3",
80
79
  "cors": "^2.8.5",
81
80
  "date-fns": "^3.6.0",
82
- "dayjs": "^1.11.12",
83
- "debug": "^4.3.6",
81
+ "dayjs": "^1.11.13",
82
+ "debug": "^4.4.1",
84
83
  "dotenv-flow": "^3.3.0",
85
- "ethers": "^6.13.5",
86
- "express": "^4.19.2",
84
+ "ethers": "^6.14.4",
85
+ "express": "^4.21.2",
87
86
  "express-async-errors": "^3.1.1",
88
87
  "express-history-api-fallback": "^2.2.1",
89
- "fastq": "^1.17.1",
88
+ "fastq": "^1.19.1",
90
89
  "flat": "^5.0.2",
91
- "google-libphonenumber": "^3.2.38",
90
+ "google-libphonenumber": "^3.2.42",
92
91
  "html2canvas": "^1.4.1",
93
92
  "iframe-resizer-react": "^1.1.1",
94
- "joi": "^17.12.2",
95
- "json-stable-stringify": "^1.1.1",
93
+ "joi": "17.12.2",
94
+ "json-stable-stringify": "^1.3.0",
96
95
  "jspdf": "^2.5.2",
97
96
  "lodash": "^4.17.21",
98
97
  "morgan": "^1.10.0",
99
98
  "mui-daterange-picker": "^1.0.5",
100
- "nanoid": "3",
99
+ "nanoid": "^3.3.11",
101
100
  "p-all": "3.0.0",
102
- "p-wait-for": "3",
101
+ "p-wait-for": "^3.2.0",
103
102
  "pretty-ms-i18n": "^1.0.3",
104
- "react": "18.2.0",
105
- "react-dom": "18.2.0",
106
- "react-error-boundary": "^4.0.13",
107
- "react-hook-form": "^7.52.1",
103
+ "react": "^19.1.0",
104
+ "react-dom": "^19.1.0",
105
+ "react-error-boundary": "^4.1.2",
106
+ "react-hook-form": "^7.58.1",
108
107
  "react-international-phone": "^3.1.2",
109
- "react-router-dom": "^6.25.1",
110
- "recharts": "^2.12.7",
108
+ "react-router-dom": "^6.30.1",
109
+ "recharts": "^2.15.4",
111
110
  "rimraf": "^3.0.2",
112
- "sequelize": "^6.37.3",
111
+ "sequelize": "^6.37.7",
113
112
  "sql-where-parser": "^2.2.1",
114
113
  "sqlite3": "^5.1.7",
115
114
  "stripe": "^13.11.0",
116
- "typewriter-effect": "^2.21.0",
115
+ "typewriter-effect": "^2.22.0",
117
116
  "ufo": "^1.6.1",
118
- "umzug": "^3.8.1",
117
+ "umzug": "^3.8.2",
119
118
  "use-bus": "^2.5.2",
120
- "validator": "^13.12.0",
119
+ "validator": "^13.15.15",
121
120
  "web3": "^4.16.0"
122
121
  },
123
122
  "devDependencies": {
124
123
  "@abtnode/types": "^1.16.44",
125
124
  "@arcblock/eslint-config-ts": "^0.3.3",
126
- "@blocklet/payment-types": "1.18.56",
127
- "@types/cookie-parser": "^1.4.7",
128
- "@types/cors": "^2.8.17",
125
+ "@blocklet/payment-types": "1.19.1",
126
+ "@types/cookie-parser": "^1.4.9",
127
+ "@types/cors": "^2.8.19",
129
128
  "@types/debug": "^4.1.12",
130
129
  "@types/dotenv-flow": "^3.3.3",
131
- "@types/express": "^4.17.21",
132
- "@types/node": "^18.19.42",
133
- "@types/react": "^18.3.3",
134
- "@types/react-dom": "^18.3.0",
135
- "@vitejs/plugin-react": "^4.3.1",
130
+ "@types/express": "^4.17.23",
131
+ "@types/node": "^18.19.112",
132
+ "@types/react": "^18.3.23",
133
+ "@types/react-dom": "^18.3.7",
134
+ "@vitejs/plugin-react": "^4.6.0",
136
135
  "babel-plugin-lodash": "^3.3.4",
137
136
  "bumpp": "^8.2.1",
138
137
  "cross-env": "^7.0.3",
139
- "eslint": "^8.57.0",
138
+ "eslint": "^8.57.1",
140
139
  "import-sort-style-module": "^6.0.0",
141
140
  "jest": "^29.7.0",
142
141
  "lint-staged": "^12.5.0",
143
142
  "nodemon": "^2.0.22",
144
143
  "npm-run-all": "^4.1.5",
145
- "prettier": "^3.3.3",
144
+ "prettier": "^3.6.0",
146
145
  "prettier-plugin-import-sort": "^0.0.7",
147
- "rollup-plugin-visualizer": "^5.12.0",
148
- "ts-jest": "^29.2.5",
146
+ "rollup-plugin-visualizer": "^6.0.3",
147
+ "ts-jest": "^29.4.0",
149
148
  "ts-node": "^10.9.2",
150
- "tsx": "^4.19.2",
151
- "type-fest": "^4.23.0",
152
- "typescript": "^5.5.4",
153
- "vite": "^5.3.5",
154
- "vite-node": "^2.0.4",
149
+ "tsx": "^4.20.3",
150
+ "type-fest": "^4.41.0",
151
+ "typescript": "5.5.4",
152
+ "vite": "^7.0.0",
153
+ "vite-node": "^3.2.4",
155
154
  "vite-plugin-babel-import": "^2.0.5",
156
155
  "vite-plugin-blocklet": "^0.9.33",
157
- "vite-plugin-node-polyfills": "^0.21.0",
158
- "vite-plugin-svgr": "^4.2.0",
159
- "vite-tsconfig-paths": "^4.3.2",
156
+ "vite-plugin-node-polyfills": "^0.23.0",
157
+ "vite-plugin-svgr": "^4.3.0",
158
+ "vite-tsconfig-paths": "^5.1.4",
160
159
  "zx": "^7.2.3"
161
160
  },
162
161
  "importSort": {
@@ -169,5 +168,5 @@
169
168
  "parser": "typescript"
170
169
  }
171
170
  },
172
- "gitHead": "9fd0235186e07388337cf1a57b257f97de8dd4c3"
171
+ "gitHead": "48d5719c8ce4e89a16f8dd576ff8f72072e3909e"
173
172
  }
package/scripts/sdk.js CHANGED
@@ -548,17 +548,249 @@ const subscriptionModule = {
548
548
  },
549
549
  };
550
550
 
551
+ const meterModule = {
552
+ async createMeter() {
553
+ const meter = await payment.meters.create({
554
+ name: 'API Calls',
555
+ event_name: 'api_calls',
556
+ aggregation_method: 'sum',
557
+ unit: 'Token',
558
+ description: 'Track API usage',
559
+ component_did: 'zNKtcX5QyTxfM51osn5W8GMWvzabBREW2abA',
560
+ created_via: 'api',
561
+ });
562
+ console.log('Created meter:', meter);
563
+ return meter;
564
+ },
565
+
566
+ // 创建基于 Meter 的订阅产品和价格
567
+ async createMeteredSubscription() {
568
+ let meter;
569
+ try {
570
+ meter = await payment.meters.retrieve('api_calls');
571
+ if (!meter) {
572
+ meter = await this.createMeter();
573
+ }
574
+ } catch (error) {
575
+ meter = await this.createMeter();
576
+ }
577
+
578
+ // 创建订阅产品
579
+ const subscriptionProduct = await payment.products.create({
580
+ name: 'API Call Service',
581
+ description: 'API Call Service',
582
+ });
583
+
584
+ // 创建基于 meter 的订阅价格
585
+ const subscriptionPrice = await payment.prices.create({
586
+ product_id: subscriptionProduct.id,
587
+ currency_id: meter.currency_id,
588
+ unit_amount: '1',
589
+ type: 'recurring',
590
+ recurring: {
591
+ interval: 'month',
592
+ interval_count: 1,
593
+ usage_type: 'metered',
594
+ meter_id: meter.id,
595
+ aggregate_usage: 'sum',
596
+ },
597
+ });
598
+
599
+ console.log('subscriptionPrice', subscriptionPrice);
600
+ // 创建 checkout session
601
+ const subscriptionCheckout = await payment.checkout.sessions.create({
602
+ success_url: 'https://audiobook.com/success?session_id={CHECKOUT_SESSION_ID}',
603
+ cancel_url: 'https://audiobook.com/cancel',
604
+ mode: 'subscription',
605
+ line_items: [
606
+ {
607
+ price_id: subscriptionPrice.id,
608
+ quantity: 1,
609
+ },
610
+ ],
611
+ });
612
+
613
+ console.log('Metered subscription checkout:', subscriptionCheckout);
614
+ return { meter, subscriptionProduct, subscriptionPrice, subscriptionCheckout };
615
+ },
616
+ };
617
+
618
+ // 新增 Credit 相关模块
619
+ const creditModule = {
620
+ // 创建 Credit 产品
621
+ async createCreditProduct(meter) {
622
+ const creditProduct = await payment.products.create({
623
+ name: 'API Calls Credit',
624
+ description: 'API Call Credit, we can ...',
625
+ type: 'credit',
626
+ prices: [
627
+ {
628
+ type: 'one_time',
629
+ unit_amount: '0.01',
630
+ currency_id: 'pc_ByBkyhRQmedm',
631
+ currency_options: [
632
+ { currency_id: 'pc_ByBkyhRQmedm', unit_amount: '0.01' },
633
+ { currency_id: 'pc_Dp4lY5ejkALH', unit_amount: '0.01' },
634
+ ],
635
+ lookup_key: 'api_call_credit_per_unit',
636
+ nickname: 'Per Unit Credit For API Call',
637
+ metadata: {
638
+ credit_config: {
639
+ priority: 50,
640
+ valid_duration_value: 0,
641
+ valid_duration_unit: 'days',
642
+ currency_id: meter.currency_id,
643
+ credit_amount: '1',
644
+ },
645
+ meter_id: meter.id,
646
+ },
647
+ },
648
+ {
649
+ type: 'one_time',
650
+ unit_amount: '8',
651
+ currency_id: 'pc_ByBkyhRQmedm',
652
+ lookup_key: 'api_calls_1000_pack',
653
+ nickname: '1000 Credits Pack For API Call',
654
+ currency_options: [
655
+ { currency_id: 'pc_ByBkyhRQmedm', unit_amount: '8' },
656
+ { currency_id: 'pc_Dp4lY5ejkALH', unit_amount: '8' },
657
+ ],
658
+ metadata: {
659
+ credit_config: {
660
+ priority: 50,
661
+ valid_duration_value: 0,
662
+ valid_duration_unit: 'days',
663
+ currency_id: meter.currency_id,
664
+ credit_amount: '1000',
665
+ },
666
+ meter_id: meter.id,
667
+ },
668
+ },
669
+ ],
670
+ });
671
+ console.log('Created credit product:', creditProduct.id);
672
+ console.log(
673
+ 'Available prices:',
674
+ creditProduct.prices.map((p) => p.lookup_key)
675
+ );
676
+ return creditProduct;
677
+ },
678
+
679
+ // 创建支付链接
680
+ async createPaymentLinks() {
681
+ const perUnitPrice = await payment.prices.retrieve('api_call_credit_per_unit');
682
+ console.log('perUnitPrice', perUnitPrice);
683
+ // 1. 按分钟购买
684
+ const flexiblePaymentLink = await payment.paymentLinks.create({
685
+ name: 'Per Unit Credit Link',
686
+ currency_id: perUnitPrice.currency_id,
687
+ line_items: [
688
+ {
689
+ price_id: perUnitPrice.id,
690
+ quantity: 100,
691
+ adjustable_quantity: {
692
+ enabled: true,
693
+ minimum: 10,
694
+ maximum: 10000,
695
+ },
696
+ },
697
+ ],
698
+ metadata: {
699
+ credit_purchase: 'true',
700
+ },
701
+ });
702
+
703
+ const packPrice = await payment.prices.retrieve('api_calls_1000_pack');
704
+ console.log('packPrice', packPrice);
705
+ // 2. 固定套餐包
706
+ const packagePaymentLink = await payment.paymentLinks.create({
707
+ name: 'Package Credit Link',
708
+ currency_id: packPrice.currency_id,
709
+ line_items: [
710
+ {
711
+ price_id: packPrice.id,
712
+ quantity: 1,
713
+ },
714
+ ],
715
+ metadata: {
716
+ credit_purchase: 'true',
717
+ },
718
+ });
719
+
720
+ console.log('Flexible payment link:', flexiblePaymentLink);
721
+ console.log('Package payment link:', packagePaymentLink);
722
+ return { flexiblePaymentLink, packagePaymentLink };
723
+ },
724
+
725
+ // 创建/赠送 Credit Grant
726
+ async createCreditGrant(meter) {
727
+ // 方式2: 通用 Credit Grant
728
+ const universalCredit = await payment.creditGrants.create({
729
+ customer_id: 'cus_xga1PZSmiDZfz0',
730
+ amount: 50,
731
+ currency_id: meter.currency_id,
732
+ applicability_config: {
733
+ scope: {
734
+ price_type: 'metered',
735
+ },
736
+ },
737
+ category: 'promotional',
738
+ name: '听书时长充值',
739
+ metadata: {
740
+ purchase_source: 'mobile_app',
741
+ },
742
+ });
743
+ console.log('Universal credit grant:', universalCredit);
744
+ return { universalCredit };
745
+ },
746
+
747
+ // 上报 Credit 消耗用量
748
+ async reportUsage(customerId, minutes, sessionContext) {
749
+ const creditBalance = await payment.creditGrants.summary({
750
+ customer_id: customerId,
751
+ });
752
+ console.log('creditBalance', creditBalance);
753
+ const meterEvent = await payment.meterEvents.create({
754
+ event_name: 'api_calls',
755
+ timestamp: Math.floor(Date.now() / 1000),
756
+ payload: {
757
+ customer_id: customerId,
758
+ value: String(minutes),
759
+ subscription_id: sessionContext.subscriptionId || undefined,
760
+ },
761
+ identifier: `${customerId}_${sessionContext.sessionId}_${Date.now()}`,
762
+ metadata: {
763
+ session_id: sessionContext.sessionId,
764
+ },
765
+ });
766
+
767
+ return {
768
+ event_id: meterEvent.id,
769
+ identifier: meterEvent.identifier,
770
+ timestamp: meterEvent.timestamp,
771
+ };
772
+ },
773
+ };
774
+
551
775
  const testModules = {
552
776
  checkout: checkoutModule,
553
777
  payment: paymentModule,
554
778
  product: productModule,
555
779
  subscription: subscriptionModule,
780
+ meter: meterModule,
781
+ credit: creditModule,
556
782
  };
557
783
 
558
784
  // 测试入口
559
785
  async function runTest() {
560
786
  payment.environments.setTestMode(true);
561
- await testModules.checkout.createBatchSubscriptionWithCustomField();
787
+ const { meter } = await testModules.meter.createMeteredSubscription();
788
+ // await testModules.credit.createCreditProduct(meter);
789
+ // await testModules.credit.createPaymentLinks();
790
+ // await testModules.credit.createCreditGrant(meter);
791
+ await testModules.credit.reportUsage('cus_xga1PZSmiDZfz0', 2, {
792
+ sessionId: 'session_123',
793
+ });
562
794
  }
563
795
 
564
796
  async function main() {