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
@@ -0,0 +1,268 @@
1
+ /* eslint-disable @typescript-eslint/lines-between-class-members */
2
+ import { col, CreationOptional, DataTypes, fn, InferAttributes, InferCreationAttributes, Model, Op } from 'sequelize';
3
+
4
+ import { BN } from '@ocap/util';
5
+ import { createEvent } from '../../libs/audit';
6
+ import { createIdGenerator } from '../../libs/util';
7
+ import { CreditGrant } from './credit-grant';
8
+
9
+ export const nextCreditTransactionId = createIdGenerator('cbtxn', 14);
10
+
11
+ export class CreditTransaction extends Model<
12
+ InferAttributes<CreditTransaction>,
13
+ InferCreationAttributes<CreditTransaction>
14
+ > {
15
+ declare id: CreationOptional<string>;
16
+ declare quantity: string;
17
+ declare credit_amount: string;
18
+ declare remaining_balance: string;
19
+
20
+ // foreign keys
21
+ declare customer_id: string;
22
+ declare credit_grant_id: string;
23
+ declare meter_id?: string;
24
+ declare subscription_id?: string;
25
+ declare source?: string;
26
+
27
+ declare meter_event_name: string;
28
+ declare meter_unit: string;
29
+ declare description?: string;
30
+ declare metadata?: Record<string, any>;
31
+
32
+ declare created_at: CreationOptional<Date>;
33
+ declare updated_at: CreationOptional<Date>;
34
+
35
+ public static readonly GENESIS_ATTRIBUTES = {
36
+ id: {
37
+ type: DataTypes.STRING(18),
38
+ primaryKey: true,
39
+ allowNull: false,
40
+ defaultValue: nextCreditTransactionId,
41
+ },
42
+ quantity: {
43
+ type: DataTypes.STRING(32),
44
+ allowNull: false,
45
+ },
46
+ credit_amount: {
47
+ type: DataTypes.STRING(32),
48
+ allowNull: false,
49
+ },
50
+ remaining_balance: {
51
+ type: DataTypes.STRING(32),
52
+ allowNull: false,
53
+ },
54
+ customer_id: {
55
+ type: DataTypes.STRING(18),
56
+ allowNull: false,
57
+ },
58
+ credit_grant_id: {
59
+ type: DataTypes.STRING(18),
60
+ allowNull: false,
61
+ },
62
+ meter_id: {
63
+ type: DataTypes.STRING(18),
64
+ allowNull: true,
65
+ },
66
+ subscription_id: {
67
+ type: DataTypes.STRING(18),
68
+ allowNull: true,
69
+ },
70
+ source: {
71
+ type: DataTypes.STRING(255),
72
+ allowNull: true,
73
+ },
74
+
75
+ meter_event_name: {
76
+ type: DataTypes.STRING(128),
77
+ allowNull: false,
78
+ },
79
+ meter_unit: {
80
+ type: DataTypes.STRING(32),
81
+ allowNull: false,
82
+ },
83
+ description: {
84
+ type: DataTypes.TEXT,
85
+ allowNull: true,
86
+ },
87
+ metadata: {
88
+ type: DataTypes.JSON,
89
+ allowNull: true,
90
+ },
91
+ created_at: {
92
+ type: DataTypes.DATE,
93
+ defaultValue: DataTypes.NOW,
94
+ allowNull: false,
95
+ },
96
+ updated_at: {
97
+ type: DataTypes.DATE,
98
+ defaultValue: DataTypes.NOW,
99
+ allowNull: false,
100
+ },
101
+ };
102
+
103
+ public static initialize(sequelize: any) {
104
+ this.init(this.GENESIS_ATTRIBUTES, {
105
+ sequelize,
106
+ modelName: 'CreditTransaction',
107
+ tableName: 'credit_transactions',
108
+ createdAt: 'created_at',
109
+ updatedAt: 'updated_at',
110
+ indexes: [{ fields: ['customer_id'] }, { fields: ['credit_grant_id'] }, { fields: ['source'] }],
111
+ hooks: {
112
+ afterCreate: (model: CreditTransaction, options) =>
113
+ createEvent('CreditTransaction', 'customer.credit_transaction.created', model, options).catch(console.error),
114
+ },
115
+ });
116
+ }
117
+
118
+ public static associate(models: any) {
119
+ this.belongsTo(models.Customer, {
120
+ foreignKey: 'customer_id',
121
+ as: 'customer',
122
+ });
123
+
124
+ this.belongsTo(models.CreditGrant, {
125
+ foreignKey: 'credit_grant_id',
126
+ as: 'creditGrant',
127
+ });
128
+
129
+ this.belongsTo(models.Meter, {
130
+ foreignKey: 'meter_id',
131
+ as: 'meter',
132
+ });
133
+
134
+ this.belongsTo(models.Subscription, {
135
+ foreignKey: 'subscription_id',
136
+ as: 'subscription',
137
+ });
138
+ }
139
+
140
+ public static async getUsageSummary({
141
+ customerId,
142
+ subscriptionId,
143
+ meterEventName,
144
+ currencyId,
145
+ startTime,
146
+ endTime,
147
+ }: {
148
+ customerId?: string;
149
+ subscriptionId?: string;
150
+ meterEventName?: string;
151
+ currencyId?: string;
152
+ startTime?: Date;
153
+ endTime?: Date;
154
+ }) {
155
+ const whereClause: any = {};
156
+
157
+ if (customerId) {
158
+ whereClause.customer_id = customerId;
159
+ }
160
+ if (subscriptionId) {
161
+ whereClause.subscription_id = subscriptionId;
162
+ }
163
+
164
+ if (meterEventName) {
165
+ whereClause.meter_event_name = meterEventName;
166
+ }
167
+
168
+ if (currencyId) {
169
+ whereClause['$creditGrant.currency_id$'] = currencyId;
170
+ }
171
+
172
+ if (startTime || endTime) {
173
+ whereClause.created_at = {};
174
+ if (startTime) {
175
+ whereClause.created_at[Op.gte] = startTime;
176
+ }
177
+ if (endTime) {
178
+ whereClause.created_at[Op.lte] = endTime;
179
+ }
180
+ }
181
+
182
+ const include = currencyId
183
+ ? [
184
+ {
185
+ model: CreditGrant,
186
+ as: 'creditGrant',
187
+ attributes: [],
188
+ required: true,
189
+ where: { currency_id: currencyId },
190
+ },
191
+ ]
192
+ : [];
193
+
194
+ const transactions = await this.findAll({
195
+ attributes: ['quantity', 'credit_amount'],
196
+ where: whereClause,
197
+ include: include.length > 0 ? include : undefined,
198
+ raw: true,
199
+ });
200
+ const totalQuantity = transactions.reduce((acc, curr) => new BN(acc).add(new BN(curr.quantity)).toString(), '0');
201
+ const totalCreditAmount = transactions.reduce(
202
+ (acc, curr) => new BN(acc).add(new BN(curr.credit_amount)).toString(),
203
+ '0'
204
+ );
205
+ const transactionCount = transactions.length;
206
+
207
+ return {
208
+ total_quantity: totalQuantity,
209
+ total_credit_amount: totalCreditAmount,
210
+ transaction_count: transactionCount,
211
+ filters: {
212
+ customer_id: customerId,
213
+ subscription_id: subscriptionId,
214
+ meter_event_name: meterEventName,
215
+ currency_id: currencyId,
216
+ start_time: startTime?.toISOString(),
217
+ end_time: endTime?.toISOString(),
218
+ },
219
+ };
220
+ }
221
+
222
+ public static getUsageSummaryForCustomer(
223
+ customerId: string,
224
+ meterEventName?: string,
225
+ startTime?: Date,
226
+ endTime?: Date
227
+ ) {
228
+ return this.getUsageSummary({
229
+ customerId,
230
+ meterEventName,
231
+ startTime,
232
+ endTime,
233
+ });
234
+ }
235
+
236
+ public static getUsageSummaryForSubscription(subscriptionId: string, startTime?: Date, endTime?: Date) {
237
+ return this.getUsageSummary({
238
+ subscriptionId,
239
+ startTime,
240
+ endTime,
241
+ });
242
+ }
243
+
244
+ public static async getUsageTrend(customerId: string, meterEventName: string, startTime: Date, endTime: Date) {
245
+ const transactions = await this.findAll({
246
+ attributes: [
247
+ [fn('DATE', col('created_at')), 'date'],
248
+ [fn('SUM', col('quantity')), 'total_quantity'],
249
+ [fn('SUM', col('credit_amount')), 'total_credit_amount'],
250
+ [fn('COUNT', col('id')), 'transaction_count'],
251
+ ],
252
+ where: {
253
+ customer_id: customerId,
254
+ meter_event_name: meterEventName,
255
+ created_at: {
256
+ [Op.between]: [startTime, endTime],
257
+ },
258
+ },
259
+ group: [fn('DATE', col('created_at'))],
260
+ order: [[fn('DATE', col('created_at')), 'ASC']],
261
+ raw: true,
262
+ });
263
+
264
+ return transactions;
265
+ }
266
+ }
267
+
268
+ export type TCreditTransaction = InferAttributes<CreditTransaction>;
@@ -285,6 +285,14 @@ export class Customer extends Model<InferAttributes<Customer>, InferCreationAttr
285
285
  foreignKey: 'customer_id',
286
286
  as: 'invoices',
287
287
  });
288
+ this.hasMany(models.CreditGrant, {
289
+ foreignKey: 'customer_id',
290
+ as: 'credit_grants',
291
+ });
292
+ this.hasMany(models.CreditTransaction, {
293
+ foreignKey: 'customer_id',
294
+ as: 'credit_transactions',
295
+ });
288
296
  }
289
297
 
290
298
  public static findByPkOrDid(id: string, options: FindOptions<Customer> = {}) {
@@ -22,11 +22,15 @@ import { SetupIntent, TSetupIntent } from './setup-intent';
22
22
  import { Subscription, TSubscription } from './subscription';
23
23
  import { SubscriptionItem, TSubscriptionItem } from './subscription-item';
24
24
  import { SubscriptionSchedule } from './subscription-schedule';
25
- import type { LineItem, PricingTableItem } from './types';
25
+ import type { LineItem, PriceCurrency, PricingTableItem } from './types';
26
26
  import { TUsageRecord, UsageRecord } from './usage-record';
27
27
  import { TWebhookAttempt, WebhookAttempt } from './webhook-attempt';
28
28
  import { TWebhookEndpoint, WebhookEndpoint } from './webhook-endpoint';
29
29
  import { Setting } from './setting';
30
+ import { CreditGrant, TCreditGrant } from './credit-grant';
31
+ import { CreditTransaction, TCreditTransaction } from './credit-transaction';
32
+ import { Meter, TMeter } from './meter';
33
+ import { MeterEvent, TMeterEvent } from './meter-event';
30
34
 
31
35
  const models = {
32
36
  CheckoutSession,
@@ -57,6 +61,10 @@ const models = {
57
61
  Job,
58
62
  Lock,
59
63
  Setting,
64
+ CreditGrant,
65
+ CreditTransaction,
66
+ Meter,
67
+ MeterEvent,
60
68
  };
61
69
 
62
70
  export function initialize(sequelize: any) {
@@ -101,6 +109,10 @@ export * from './webhook-attempt';
101
109
  export * from './webhook-endpoint';
102
110
  export * from './types';
103
111
  export * from './setting';
112
+ export * from './credit-grant';
113
+ export * from './credit-transaction';
114
+ export * from './meter';
115
+ export * from './meter-event';
104
116
 
105
117
  export type TPriceExpanded = TPrice & {
106
118
  object: 'price';
@@ -110,6 +122,8 @@ export type TPriceExpanded = TPrice & {
110
122
  upsells_to: TPriceExpanded;
111
123
  upsells_to_id: string;
112
124
  };
125
+ meter?: TMeter;
126
+ currency_options?: (PriceCurrency & { currency?: TPaymentCurrency })[];
113
127
  };
114
128
 
115
129
  export type TLineItemExpanded = LineItem & {
@@ -171,6 +185,7 @@ export type TSubscriptionExpanded = TSubscription & {
171
185
  paymentCurrency: TPaymentCurrency;
172
186
  paymentMethod: TPaymentMethod;
173
187
  items: TSubscriptionItemExpanded[];
188
+ serviceType: 'credit' | 'standard';
174
189
  };
175
190
 
176
191
  export type TSubscriptionItemExpanded = TSubscriptionItem & {
@@ -237,6 +252,7 @@ export type TSetupIntentExpanded = TSetupIntent & {
237
252
  };
238
253
 
239
254
  export type TPricingTableItem = PricingTableItem & {
255
+ object: 'pricing_table_item';
240
256
  price: TPrice;
241
257
  product: TProduct;
242
258
  is_selected?: boolean;
@@ -244,11 +260,13 @@ export type TPricingTableItem = PricingTableItem & {
244
260
  };
245
261
 
246
262
  export type TPricingTableExpanded = TPricingTable & {
263
+ object: 'pricing_table';
247
264
  items: TPricingTableItem[];
248
265
  currency: TPaymentCurrency;
249
266
  };
250
267
 
251
268
  export type TRefundExpanded = TRefund & {
269
+ object: 'refund';
252
270
  customer: TCustomer;
253
271
  paymentCurrency: TPaymentCurrency;
254
272
  paymentMethod: TPaymentMethod;
@@ -264,3 +282,36 @@ export type TPayoutExpanded = TPayout & {
264
282
  paymentIntent: TPaymentIntent;
265
283
  customer: TCustomer;
266
284
  };
285
+
286
+ export type TCreditGrantExpanded = TCreditGrant & {
287
+ object: 'credit_grant';
288
+ customer: TCustomer;
289
+ paymentCurrency: TPaymentCurrency;
290
+ paymentMethod: TPaymentMethod;
291
+ items?: TLineItemExpanded[];
292
+ };
293
+
294
+ export type TCreditTransactionExpanded = TCreditTransaction & {
295
+ object: 'credit_transaction';
296
+ customer: TCustomer;
297
+ paymentCurrency: TPaymentCurrency;
298
+ paymentMethod?: TPaymentMethod;
299
+ creditGrant: TCreditGrant;
300
+ meter: TMeter;
301
+ subscription: TSubscription;
302
+ };
303
+
304
+ export type TMeterEventExpanded = TMeterEvent & {
305
+ object: 'meter_event';
306
+ customer: Customer;
307
+ subscription?: TSubscription;
308
+ meter: TMeter;
309
+ paymentCurrency: TPaymentCurrency;
310
+ };
311
+
312
+ export type CreditGrantSummary = {
313
+ paymentCurrency: TPaymentCurrency;
314
+ totalAmount: string;
315
+ remainingAmount: string;
316
+ grantCount: number;
317
+ };