payment-kit 1.19.0 → 1.19.2

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 (139) hide show
  1. package/api/src/crons/index.ts +8 -0
  2. package/api/src/index.ts +4 -0
  3. package/api/src/libs/credit-grant.ts +146 -0
  4. package/api/src/libs/env.ts +1 -0
  5. package/api/src/libs/invoice.ts +4 -3
  6. package/api/src/libs/notification/template/base.ts +388 -2
  7. package/api/src/libs/notification/template/customer-credit-grant-granted.ts +149 -0
  8. package/api/src/libs/notification/template/customer-credit-grant-low-balance.ts +151 -0
  9. package/api/src/libs/notification/template/customer-credit-insufficient.ts +254 -0
  10. package/api/src/libs/notification/template/subscription-canceled.ts +193 -202
  11. package/api/src/libs/notification/template/subscription-refund-succeeded.ts +215 -237
  12. package/api/src/libs/notification/template/subscription-renewed.ts +130 -200
  13. package/api/src/libs/notification/template/subscription-succeeded.ts +100 -202
  14. package/api/src/libs/notification/template/subscription-trial-start.ts +142 -188
  15. package/api/src/libs/notification/template/subscription-trial-will-end.ts +146 -174
  16. package/api/src/libs/notification/template/subscription-upgraded.ts +96 -192
  17. package/api/src/libs/notification/template/subscription-will-canceled.ts +94 -135
  18. package/api/src/libs/notification/template/subscription-will-renew.ts +220 -245
  19. package/api/src/libs/payment.ts +69 -0
  20. package/api/src/libs/queue/index.ts +3 -2
  21. package/api/src/libs/session.ts +8 -0
  22. package/api/src/libs/subscription.ts +74 -3
  23. package/api/src/libs/util.ts +3 -1
  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 +728 -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/customers.ts +34 -5
  37. package/api/src/routes/index.ts +8 -0
  38. package/api/src/routes/meter-events.ts +347 -0
  39. package/api/src/routes/meters.ts +219 -0
  40. package/api/src/routes/payment-currencies.ts +20 -2
  41. package/api/src/routes/payment-links.ts +1 -1
  42. package/api/src/routes/payment-methods.ts +14 -2
  43. package/api/src/routes/prices.ts +43 -0
  44. package/api/src/routes/pricing-table.ts +13 -7
  45. package/api/src/routes/products.ts +63 -4
  46. package/api/src/routes/settings.ts +1 -1
  47. package/api/src/routes/subscriptions.ts +4 -0
  48. package/api/src/routes/webhook-endpoints.ts +0 -3
  49. package/api/src/store/migrations/20250610-billing-credit.ts +43 -0
  50. package/api/src/store/models/credit-grant.ts +486 -0
  51. package/api/src/store/models/credit-transaction.ts +268 -0
  52. package/api/src/store/models/customer.ts +8 -0
  53. package/api/src/store/models/index.ts +52 -1
  54. package/api/src/store/models/meter-event.ts +423 -0
  55. package/api/src/store/models/meter.ts +176 -0
  56. package/api/src/store/models/payment-currency.ts +66 -14
  57. package/api/src/store/models/price.ts +6 -0
  58. package/api/src/store/models/product.ts +2 -2
  59. package/api/src/store/models/subscription.ts +24 -0
  60. package/api/src/store/models/types.ts +28 -2
  61. package/api/tests/libs/subscription.spec.ts +53 -0
  62. package/blocklet.yml +9 -1
  63. package/package.json +4 -4
  64. package/scripts/sdk.js +233 -1
  65. package/src/app.tsx +10 -0
  66. package/src/components/collapse.tsx +11 -1
  67. package/src/components/conditional-section.tsx +87 -0
  68. package/src/components/customer/credit-grant-item-list.tsx +99 -0
  69. package/src/components/customer/credit-overview.tsx +246 -0
  70. package/src/components/customer/form.tsx +7 -3
  71. package/src/components/invoice/list.tsx +19 -1
  72. package/src/components/metadata/form.tsx +287 -91
  73. package/src/components/meter/actions.tsx +101 -0
  74. package/src/components/meter/add-usage-dialog.tsx +239 -0
  75. package/src/components/meter/events-list.tsx +657 -0
  76. package/src/components/meter/form.tsx +245 -0
  77. package/src/components/meter/products.tsx +264 -0
  78. package/src/components/meter/usage-guide.tsx +174 -0
  79. package/src/components/payment-currency/form.tsx +2 -0
  80. package/src/components/payment-intent/list.tsx +19 -1
  81. package/src/components/payment-link/item.tsx +2 -2
  82. package/src/components/payment-link/preview.tsx +1 -1
  83. package/src/components/payment-link/product-select.tsx +52 -12
  84. package/src/components/payment-method/arcblock.tsx +2 -0
  85. package/src/components/payment-method/base.tsx +2 -0
  86. package/src/components/payment-method/bitcoin.tsx +2 -0
  87. package/src/components/payment-method/ethereum.tsx +2 -0
  88. package/src/components/payment-method/stripe.tsx +2 -0
  89. package/src/components/payouts/list.tsx +19 -1
  90. package/src/components/payouts/portal/list.tsx +6 -11
  91. package/src/components/price/currency-select.tsx +56 -32
  92. package/src/components/price/form.tsx +912 -407
  93. package/src/components/pricing-table/preview.tsx +1 -1
  94. package/src/components/product/add-price.tsx +9 -7
  95. package/src/components/product/create.tsx +7 -4
  96. package/src/components/product/edit-price.tsx +21 -12
  97. package/src/components/product/features.tsx +17 -7
  98. package/src/components/product/form.tsx +100 -90
  99. package/src/components/refund/list.tsx +19 -1
  100. package/src/components/section/header.tsx +5 -18
  101. package/src/components/subscription/items/index.tsx +1 -1
  102. package/src/components/subscription/metrics.tsx +37 -5
  103. package/src/components/subscription/portal/actions.tsx +2 -1
  104. package/src/contexts/products.tsx +26 -9
  105. package/src/hooks/subscription.ts +34 -0
  106. package/src/libs/meter-utils.ts +196 -0
  107. package/src/libs/util.ts +4 -0
  108. package/src/locales/en.tsx +389 -5
  109. package/src/locales/zh.tsx +368 -1
  110. package/src/pages/admin/billing/index.tsx +61 -33
  111. package/src/pages/admin/billing/invoices/detail.tsx +1 -1
  112. package/src/pages/admin/billing/meters/create.tsx +60 -0
  113. package/src/pages/admin/billing/meters/detail.tsx +435 -0
  114. package/src/pages/admin/billing/meters/index.tsx +210 -0
  115. package/src/pages/admin/billing/meters/meter-event.tsx +346 -0
  116. package/src/pages/admin/billing/subscriptions/detail.tsx +47 -14
  117. package/src/pages/admin/customers/customers/credit-grant/detail.tsx +391 -0
  118. package/src/pages/admin/customers/customers/detail.tsx +14 -10
  119. package/src/pages/admin/customers/index.tsx +5 -0
  120. package/src/pages/admin/developers/events/detail.tsx +1 -1
  121. package/src/pages/admin/developers/index.tsx +1 -1
  122. package/src/pages/admin/payments/intents/detail.tsx +1 -1
  123. package/src/pages/admin/payments/payouts/detail.tsx +1 -1
  124. package/src/pages/admin/payments/refunds/detail.tsx +1 -1
  125. package/src/pages/admin/products/index.tsx +3 -2
  126. package/src/pages/admin/products/links/detail.tsx +1 -1
  127. package/src/pages/admin/products/prices/actions.tsx +16 -4
  128. package/src/pages/admin/products/prices/detail.tsx +30 -3
  129. package/src/pages/admin/products/prices/list.tsx +8 -1
  130. package/src/pages/admin/products/pricing-tables/detail.tsx +1 -1
  131. package/src/pages/admin/products/products/create.tsx +233 -57
  132. package/src/pages/admin/products/products/detail.tsx +2 -1
  133. package/src/pages/admin/settings/payment-methods/index.tsx +3 -0
  134. package/src/pages/customer/credit-grant/detail.tsx +308 -0
  135. package/src/pages/customer/index.tsx +44 -9
  136. package/src/pages/customer/recharge/account.tsx +5 -5
  137. package/src/pages/customer/subscription/change-payment.tsx +4 -2
  138. package/src/pages/customer/subscription/detail.tsx +48 -14
  139. package/src/pages/customer/subscription/embed.tsx +1 -1
@@ -3,6 +3,7 @@ import Toast from '@arcblock/ux/lib/Toast';
3
3
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
4
4
  import { useRequest } from 'ahooks';
5
5
  import { api } from '@blocklet/payment-react';
6
+ import type { TPaymentCurrency } from '@blocklet/payment-types';
6
7
 
7
8
  export function useUnpaidInvoicesCheckForSubscription(subscriptionId: string, manualCheck: boolean = false) {
8
9
  const { t } = useLocaleContext();
@@ -28,3 +29,36 @@ export function useUnpaidInvoicesCheckForSubscription(subscriptionId: string, ma
28
29
  hasUnpaid: data?.count > 0,
29
30
  };
30
31
  }
32
+
33
+ export function usePendingAmountForSubscription(subscriptionId: string, paymentCurrency?: TPaymentCurrency) {
34
+ const { data: pendingAmountSummary, run: checkPendingAmount } = useRequest(
35
+ async () => {
36
+ if (!subscriptionId || !paymentCurrency?.id) return null;
37
+
38
+ const response = await api.get('/api/meter-events/pending-amount', {
39
+ params: {
40
+ subscription_id: subscriptionId,
41
+ currency_id: paymentCurrency.id,
42
+ },
43
+ });
44
+
45
+ return response.data;
46
+ },
47
+ {
48
+ ready: !!subscriptionId && !!paymentCurrency?.id,
49
+ refreshDeps: [subscriptionId, paymentCurrency?.id],
50
+ pollingInterval: 30000, // 每30秒检查一次
51
+ }
52
+ );
53
+
54
+ // pendingAmountSummary 是 GroupedBN 格式: { [currencyId: string]: string }
55
+ const pendingAmount = pendingAmountSummary && paymentCurrency?.id ? pendingAmountSummary[paymentCurrency.id] : null;
56
+
57
+ const hasPendingAmount = pendingAmount && pendingAmount !== '0';
58
+
59
+ return {
60
+ pendingAmount,
61
+ hasPendingAmount,
62
+ checkPendingAmount,
63
+ };
64
+ }
@@ -0,0 +1,196 @@
1
+ import { api } from '@blocklet/payment-react';
2
+ import type { TMeter, TCustomer, TPaymentCurrency, TSubscription } from '@blocklet/payment-types';
3
+
4
+ export interface MeterInfo {
5
+ meter?: TMeter & { paymentCurrency?: TPaymentCurrency };
6
+ customer?: TCustomer;
7
+ paymentCurrency?: TPaymentCurrency;
8
+ subscription?: TSubscription;
9
+ }
10
+
11
+ export interface GetMeterInfoOptions {
12
+ meterId?: string;
13
+ customerId?: string;
14
+ eventName?: string;
15
+ includeCustomer?: boolean;
16
+ includePaymentCurrency?: boolean;
17
+ includeSubscription?: boolean;
18
+ subscriptionId?: string;
19
+ }
20
+
21
+ /**
22
+ * 获取meter相关信息的通用方法
23
+ * @param options 配置选项
24
+ * @returns Promise<MeterInfo>
25
+ */
26
+ export async function getMeterInfo(options: GetMeterInfoOptions = {}): Promise<MeterInfo> {
27
+ const {
28
+ meterId,
29
+ customerId,
30
+ eventName,
31
+ includeCustomer = false,
32
+ includePaymentCurrency = false,
33
+ includeSubscription = false,
34
+ subscriptionId,
35
+ } = options;
36
+
37
+ const result: MeterInfo = {};
38
+
39
+ try {
40
+ // 获取meter信息
41
+ if (meterId) {
42
+ const { data: meter } = await api.get(`/api/meters/${meterId}`);
43
+ result.meter = meter;
44
+
45
+ // 如果meter包含paymentCurrency且需要包含
46
+ if (includePaymentCurrency && meter.paymentCurrency) {
47
+ result.paymentCurrency = meter.paymentCurrency;
48
+ }
49
+ } else if (eventName) {
50
+ // 通过event_name查找meter
51
+ const { data: meters } = await api.get(`/api/meters?event_name=${encodeURIComponent(eventName)}`);
52
+ if (meters.list && meters.list.length > 0) {
53
+ const [firstMeter] = meters.list;
54
+ result.meter = firstMeter;
55
+
56
+ if (includePaymentCurrency && firstMeter.paymentCurrency) {
57
+ result.paymentCurrency = firstMeter.paymentCurrency;
58
+ }
59
+ }
60
+ }
61
+
62
+ // 获取customer信息
63
+ if (includeCustomer && customerId) {
64
+ const { data: customer } = await api.get(`/api/customers/${customerId}`);
65
+ result.customer = customer;
66
+ }
67
+
68
+ // 获取subscription信息
69
+ if (includeSubscription && subscriptionId) {
70
+ const { data: subscription } = await api.get(`/api/subscriptions/${subscriptionId}`);
71
+ result.subscription = subscription;
72
+ }
73
+
74
+ // 如果需要paymentCurrency但还没有获取到,尝试从meter的currency_id获取
75
+ if (includePaymentCurrency && !result.paymentCurrency && result.meter?.currency_id) {
76
+ try {
77
+ const { data: paymentCurrency } = await api.get(`/api/payment-currencies/${result.meter.currency_id}`);
78
+ result.paymentCurrency = paymentCurrency;
79
+ } catch (err) {
80
+ console.warn('Failed to fetch payment currency:', err);
81
+ }
82
+ }
83
+
84
+ return result;
85
+ } catch (error) {
86
+ console.error('Error fetching meter info:', error);
87
+ throw error;
88
+ }
89
+ }
90
+
91
+ /**
92
+ * 获取meter的完整信息(包含所有关联数据)
93
+ * @param meterId meter ID
94
+ * @returns Promise<MeterInfo>
95
+ */
96
+ export function getMeterFullInfo(meterId: string): Promise<MeterInfo> {
97
+ return getMeterInfo({
98
+ meterId,
99
+ includePaymentCurrency: true,
100
+ });
101
+ }
102
+
103
+ /**
104
+ * 根据事件名称获取meter信息
105
+ * @param eventName 事件名称
106
+ * @param includePaymentCurrency 是否包含支付货币信息
107
+ * @returns Promise<MeterInfo>
108
+ */
109
+ export function getMeterByEventName(eventName: string, includePaymentCurrency = true): Promise<MeterInfo> {
110
+ return getMeterInfo({
111
+ eventName,
112
+ includePaymentCurrency,
113
+ });
114
+ }
115
+
116
+ /**
117
+ * 获取meter事件的完整上下文信息
118
+ * @param options 配置选项
119
+ * @returns Promise<MeterInfo>
120
+ */
121
+ export function getMeterEventContext(options: {
122
+ meterId?: string;
123
+ eventName?: string;
124
+ customerId?: string;
125
+ subscriptionId?: string;
126
+ }): Promise<MeterInfo> {
127
+ const { meterId, eventName, customerId, subscriptionId } = options;
128
+
129
+ return getMeterInfo({
130
+ meterId,
131
+ eventName,
132
+ customerId,
133
+ subscriptionId,
134
+ includeCustomer: !!customerId,
135
+ includePaymentCurrency: true,
136
+ includeSubscription: !!subscriptionId,
137
+ });
138
+ }
139
+
140
+ /**
141
+ * 批量获取多个meter的信息
142
+ * @param meterIds meter ID数组
143
+ * @param includePaymentCurrency 是否包含支付货币信息
144
+ * @returns Promise<MeterInfo[]>
145
+ */
146
+ export function getBatchMeterInfo(meterIds: string[], includePaymentCurrency = true): Promise<MeterInfo[]> {
147
+ const promises = meterIds.map((meterId) =>
148
+ getMeterInfo({
149
+ meterId,
150
+ includePaymentCurrency,
151
+ }).catch((error) => {
152
+ console.warn(`Failed to fetch meter ${meterId}:`, error);
153
+ return { meter: undefined };
154
+ })
155
+ );
156
+
157
+ return Promise.all(promises);
158
+ }
159
+
160
+ /**
161
+ * 获取客户的meter使用情况
162
+ * @param customerId 客户ID
163
+ * @param meterId 可选的meter ID,如果不提供则获取所有meter
164
+ * @returns Promise<MeterInfo[]>
165
+ */
166
+ export async function getCustomerMeterUsage(customerId: string, meterId?: string): Promise<MeterInfo[]> {
167
+ try {
168
+ // 获取客户的credit transactions来找到相关的meters
169
+ const params = new URLSearchParams();
170
+ params.append('customer_id', customerId);
171
+ if (meterId) {
172
+ params.append('meter_id', meterId);
173
+ }
174
+
175
+ const { data: transactions } = await api.get(`/api/credit-transactions?${params.toString()}`);
176
+
177
+ // 提取唯一的meter IDs
178
+ const uniqueMeterIds = [
179
+ ...new Set(
180
+ transactions.list
181
+ ?.map((t: any) => t.meter?.id)
182
+ .filter((id: any): id is string => Boolean(id) && typeof id === 'string')
183
+ ),
184
+ ] as string[];
185
+
186
+ if (uniqueMeterIds.length === 0) {
187
+ return [];
188
+ }
189
+
190
+ // 批量获取meter信息
191
+ return await getBatchMeterInfo(uniqueMeterIds);
192
+ } catch (error) {
193
+ console.error('Error fetching customer meter usage:', error);
194
+ return [];
195
+ }
196
+ }
package/src/libs/util.ts CHANGED
@@ -50,6 +50,10 @@ export function getPricingModel(price: TPrice) {
50
50
  return 'package';
51
51
  }
52
52
 
53
+ if (price.type === 'recurring' && price.recurring?.usage_type === 'metered' && price.recurring?.meter_id) {
54
+ return 'credit_metered';
55
+ }
56
+
53
57
  return 'standard';
54
58
  }
55
59