payment-kit 1.20.11 → 1.20.13

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 (92) hide show
  1. package/api/src/crons/index.ts +8 -0
  2. package/api/src/index.ts +2 -0
  3. package/api/src/integrations/stripe/handlers/invoice.ts +63 -5
  4. package/api/src/integrations/stripe/handlers/payment-intent.ts +1 -0
  5. package/api/src/integrations/stripe/resource.ts +253 -2
  6. package/api/src/libs/currency.ts +31 -0
  7. package/api/src/libs/discount/coupon.ts +1061 -0
  8. package/api/src/libs/discount/discount.ts +349 -0
  9. package/api/src/libs/discount/nft.ts +239 -0
  10. package/api/src/libs/discount/redemption.ts +636 -0
  11. package/api/src/libs/discount/vc.ts +73 -0
  12. package/api/src/libs/env.ts +1 -0
  13. package/api/src/libs/invoice.ts +44 -10
  14. package/api/src/libs/math-utils.ts +6 -0
  15. package/api/src/libs/price.ts +43 -0
  16. package/api/src/libs/session.ts +242 -57
  17. package/api/src/libs/subscription.ts +2 -6
  18. package/api/src/libs/vendor-util/adapters/launcher-adapter.ts +1 -1
  19. package/api/src/libs/vendor-util/adapters/types.ts +1 -0
  20. package/api/src/libs/vendor-util/fulfillment.ts +1 -1
  21. package/api/src/queues/auto-recharge.ts +1 -1
  22. package/api/src/queues/discount-status.ts +200 -0
  23. package/api/src/queues/subscription.ts +98 -5
  24. package/api/src/queues/usage-record.ts +1 -1
  25. package/api/src/queues/vendors/fulfillment-coordinator.ts +1 -29
  26. package/api/src/queues/vendors/return-processor.ts +184 -0
  27. package/api/src/queues/vendors/return-scanner.ts +119 -0
  28. package/api/src/queues/vendors/status-check.ts +1 -1
  29. package/api/src/routes/auto-recharge-configs.ts +5 -3
  30. package/api/src/routes/checkout-sessions.ts +755 -64
  31. package/api/src/routes/connect/change-payment.ts +6 -1
  32. package/api/src/routes/connect/change-plan.ts +6 -1
  33. package/api/src/routes/connect/setup.ts +6 -1
  34. package/api/src/routes/connect/shared.ts +80 -9
  35. package/api/src/routes/connect/subscribe.ts +12 -2
  36. package/api/src/routes/coupons.ts +518 -0
  37. package/api/src/routes/index.ts +4 -0
  38. package/api/src/routes/invoices.ts +44 -3
  39. package/api/src/routes/meter-events.ts +2 -1
  40. package/api/src/routes/payment-currencies.ts +1 -0
  41. package/api/src/routes/promotion-codes.ts +482 -0
  42. package/api/src/routes/subscriptions.ts +23 -2
  43. package/api/src/routes/vendor.ts +89 -2
  44. package/api/src/store/migrations/20250904-discount.ts +136 -0
  45. package/api/src/store/migrations/20250910-timestamp-fields.ts +116 -0
  46. package/api/src/store/migrations/20250916-add-description-fields.ts +30 -0
  47. package/api/src/store/migrations/20250918-add-vendor-extends.ts +20 -0
  48. package/api/src/store/models/checkout-session.ts +17 -2
  49. package/api/src/store/models/coupon.ts +144 -4
  50. package/api/src/store/models/discount.ts +23 -10
  51. package/api/src/store/models/index.ts +13 -2
  52. package/api/src/store/models/product-vendor.ts +6 -0
  53. package/api/src/store/models/promotion-code.ts +295 -18
  54. package/api/src/store/models/types.ts +30 -1
  55. package/api/tests/libs/session.spec.ts +48 -27
  56. package/blocklet.yml +1 -1
  57. package/package.json +20 -20
  58. package/src/app.tsx +2 -0
  59. package/src/components/customer/link.tsx +1 -1
  60. package/src/components/discount/discount-info.tsx +178 -0
  61. package/src/components/invoice/table.tsx +140 -48
  62. package/src/components/invoice-pdf/styles.ts +6 -0
  63. package/src/components/invoice-pdf/template.tsx +59 -33
  64. package/src/components/metadata/form.tsx +14 -5
  65. package/src/components/payment-link/actions.tsx +42 -0
  66. package/src/components/price/form.tsx +91 -65
  67. package/src/components/product/vendor-config.tsx +5 -3
  68. package/src/components/promotion/active-redemptions.tsx +534 -0
  69. package/src/components/promotion/currency-multi-select.tsx +350 -0
  70. package/src/components/promotion/currency-restrictions.tsx +117 -0
  71. package/src/components/promotion/product-select.tsx +292 -0
  72. package/src/components/promotion/promotion-code-form.tsx +534 -0
  73. package/src/components/subscription/portal/list.tsx +6 -1
  74. package/src/components/subscription/vendor-service-list.tsx +13 -2
  75. package/src/locales/en.tsx +227 -0
  76. package/src/locales/zh.tsx +222 -1
  77. package/src/pages/admin/billing/subscriptions/detail.tsx +5 -0
  78. package/src/pages/admin/products/coupons/applicable-products.tsx +166 -0
  79. package/src/pages/admin/products/coupons/create.tsx +612 -0
  80. package/src/pages/admin/products/coupons/detail.tsx +538 -0
  81. package/src/pages/admin/products/coupons/edit.tsx +127 -0
  82. package/src/pages/admin/products/coupons/index.tsx +210 -3
  83. package/src/pages/admin/products/index.tsx +22 -3
  84. package/src/pages/admin/products/products/detail.tsx +12 -2
  85. package/src/pages/admin/products/promotion-codes/actions.tsx +103 -0
  86. package/src/pages/admin/products/promotion-codes/create.tsx +235 -0
  87. package/src/pages/admin/products/promotion-codes/detail.tsx +416 -0
  88. package/src/pages/admin/products/promotion-codes/list.tsx +247 -0
  89. package/src/pages/admin/products/promotion-codes/verification-config.tsx +327 -0
  90. package/src/pages/admin/products/vendors/index.tsx +17 -5
  91. package/src/pages/customer/subscription/detail.tsx +5 -0
  92. package/vite.config.ts +4 -3
@@ -1,7 +1,22 @@
1
1
  /* eslint-disable @typescript-eslint/lines-between-class-members */
2
- import { CreationOptional, DataTypes, InferAttributes, InferCreationAttributes, Model } from 'sequelize';
2
+ import {
3
+ CreationOptional,
4
+ DataTypes,
5
+ InferAttributes,
6
+ InferCreationAttributes,
7
+ Model,
8
+ Op,
9
+ FindOptions,
10
+ WhereOptions,
11
+ } from 'sequelize';
12
+ import { fromTokenToUnit } from '@ocap/util';
3
13
 
4
- import { createIdGenerator } from '../../libs/util';
14
+ import type { LiteralUnion } from 'type-fest';
15
+ import { createIdGenerator, formatMetadata } from '../../libs/util';
16
+ import { trimDecimals } from '../../libs/math-utils';
17
+ import type { NFTConfig, Restrictions, VCConfig, VerificationType } from './types';
18
+ import type { TPaymentCurrency } from './payment-currency';
19
+ import { createEvent } from '../../libs/audit';
5
20
 
6
21
  const nextId = createIdGenerator('promo', 24);
7
22
 
@@ -11,27 +26,33 @@ export class PromotionCode extends Model<InferAttributes<PromotionCode>, InferCr
11
26
  declare id: CreationOptional<string>;
12
27
  declare livemode: boolean;
13
28
  declare active: boolean;
29
+ declare locked: CreationOptional<boolean>;
14
30
 
15
31
  declare code: string;
16
32
 
33
+ declare description?: string;
34
+
17
35
  declare coupon_id: string;
18
36
 
19
37
  // redeem conditions
20
38
  declare max_redemptions?: number;
21
- declare restrictions?: {
22
- currency_options?: Record<string, number>;
23
- first_time_transaction?: boolean;
24
- minimum_amount?: number;
25
- minimum_amount_currency?: string;
26
- };
39
+ declare restrictions?: Restrictions;
27
40
  declare customer_id?: string;
28
41
 
29
42
  declare times_redeemed?: number;
30
43
 
31
- declare expires_at: CreationOptional<Date>;
44
+ declare expires_at?: number;
32
45
  declare created_at: CreationOptional<Date>;
33
46
  declare updated_at: CreationOptional<Date>;
34
47
 
48
+ // Separated verification configurations
49
+ declare verification_type?: VerificationType;
50
+ declare nft_config?: NFTConfig;
51
+ declare vc_config?: VCConfig;
52
+ declare customer_dids?: string[];
53
+ declare metadata?: Record<string, any>;
54
+ declare created_via: LiteralUnion<'api' | 'dashboard' | 'portal', string>;
55
+
35
56
  public static readonly GENESIS_ATTRIBUTES = {
36
57
  id: {
37
58
  type: DataTypes.STRING(30),
@@ -43,6 +64,10 @@ export class PromotionCode extends Model<InferAttributes<PromotionCode>, InferCr
43
64
  type: DataTypes.BOOLEAN,
44
65
  allowNull: false,
45
66
  },
67
+ locked: {
68
+ type: DataTypes.BOOLEAN,
69
+ defaultValue: false,
70
+ },
46
71
  active: {
47
72
  type: DataTypes.BOOLEAN,
48
73
  defaultValue: true,
@@ -51,9 +76,13 @@ export class PromotionCode extends Model<InferAttributes<PromotionCode>, InferCr
51
76
  type: DataTypes.STRING(16),
52
77
  allowNull: false,
53
78
  },
79
+ description: {
80
+ type: DataTypes.TEXT,
81
+ allowNull: true,
82
+ },
54
83
  coupon_id: {
55
84
  type: DataTypes.STRING(30),
56
- allowNull: true,
85
+ allowNull: false,
57
86
  },
58
87
  max_redemptions: {
59
88
  type: DataTypes.INTEGER,
@@ -75,8 +104,11 @@ export class PromotionCode extends Model<InferAttributes<PromotionCode>, InferCr
75
104
  type: DataTypes.JSON,
76
105
  allowNull: true,
77
106
  },
107
+ created_via: {
108
+ type: DataTypes.ENUM('api', 'dashboard', 'portal'),
109
+ },
78
110
  expires_at: {
79
- type: DataTypes.DATE,
111
+ type: DataTypes.INTEGER,
80
112
  allowNull: true,
81
113
  },
82
114
  created_at: {
@@ -92,13 +124,47 @@ export class PromotionCode extends Model<InferAttributes<PromotionCode>, InferCr
92
124
  };
93
125
 
94
126
  public static initialize(sequelize: any) {
95
- this.init(PromotionCode.GENESIS_ATTRIBUTES, {
96
- sequelize,
97
- modelName: 'PromotionCode',
98
- tableName: 'promotion_codes',
99
- createdAt: 'created_at',
100
- updatedAt: 'updated_at',
101
- });
127
+ this.init(
128
+ {
129
+ ...PromotionCode.GENESIS_ATTRIBUTES,
130
+ verification_type: {
131
+ type: DataTypes.ENUM('code', 'nft', 'vc', 'user_restricted'),
132
+ defaultValue: 'code',
133
+ allowNull: false,
134
+ },
135
+ nft_config: {
136
+ type: DataTypes.JSON,
137
+ allowNull: true,
138
+ },
139
+ vc_config: {
140
+ type: DataTypes.JSON,
141
+ allowNull: true,
142
+ },
143
+ customer_dids: {
144
+ type: DataTypes.JSON,
145
+ allowNull: true,
146
+ },
147
+ metadata: {
148
+ type: DataTypes.JSON,
149
+ allowNull: true,
150
+ },
151
+ },
152
+ {
153
+ sequelize,
154
+ modelName: 'PromotionCode',
155
+ tableName: 'promotion_codes',
156
+ createdAt: 'created_at',
157
+ updatedAt: 'updated_at',
158
+ hooks: {
159
+ afterCreate: (model: PromotionCode, options) =>
160
+ createEvent('PromotionCode', 'promotion_code.created', model, options).catch(console.error),
161
+ afterUpdate: (model: PromotionCode, options) =>
162
+ createEvent('PromotionCode', 'promotion_code.updated', model, options).catch(console.error),
163
+ afterDestroy: (model: PromotionCode, options) =>
164
+ createEvent('PromotionCode', 'promotion_code.deleted', model, options).catch(console.error),
165
+ },
166
+ }
167
+ );
102
168
  }
103
169
 
104
170
  public static associate(models: any) {
@@ -107,6 +173,217 @@ export class PromotionCode extends Model<InferAttributes<PromotionCode>, InferCr
107
173
  as: 'coupon',
108
174
  });
109
175
  }
176
+
177
+ public static formatBeforeSave(promotionCode: Partial<TPromotionCode>) {
178
+ if (promotionCode.metadata) {
179
+ promotionCode.metadata = formatMetadata(promotionCode.metadata);
180
+ }
181
+
182
+ if (promotionCode.max_redemptions) {
183
+ promotionCode.max_redemptions = Number(promotionCode.max_redemptions);
184
+ }
185
+
186
+ if (promotionCode.times_redeemed) {
187
+ promotionCode.times_redeemed = Number(promotionCode.times_redeemed);
188
+ }
189
+
190
+ return promotionCode;
191
+ }
192
+
193
+ /**
194
+ * Find promotion code by multiple criteria:
195
+ * - id: promotion code ID
196
+ * - code: promotion code string
197
+ * - nft_address: NFT contract address (when verification_type is 'nft')
198
+ * - vc_role: VC role (when verification_type is 'vc')
199
+ * - userDid: user DID (when verification_type is 'user_restricted')
200
+ */
201
+ public static findByMultipleCriteria(
202
+ searchValue: string,
203
+ options: FindOptions<PromotionCode> = {},
204
+ verificationType: VerificationType | null = null
205
+ ) {
206
+ const whereConditions = [{ id: searchValue }, { code: searchValue }] as WhereOptions[];
207
+
208
+ // Add NFT address search condition
209
+ if (verificationType === 'nft' || !verificationType) {
210
+ whereConditions.push({
211
+ verification_type: 'nft',
212
+ 'nft_config.addresses': {
213
+ [Op.contains]: [searchValue],
214
+ },
215
+ });
216
+ }
217
+
218
+ // Add VC role search condition
219
+ if (verificationType === 'vc' || !verificationType) {
220
+ whereConditions.push({
221
+ verification_type: 'vc',
222
+ 'vc_config.roles': {
223
+ [Op.contains]: [searchValue],
224
+ },
225
+ });
226
+ }
227
+
228
+ // Add user DID search condition
229
+ if (verificationType === 'user_restricted' || !verificationType) {
230
+ whereConditions.push({
231
+ verification_type: 'user_restricted',
232
+ customer_dids: {
233
+ [Op.contains]: [searchValue],
234
+ },
235
+ });
236
+ }
237
+
238
+ return this.findOne({
239
+ where: { [Op.or]: whereConditions },
240
+ ...options,
241
+ });
242
+ }
243
+
244
+ /**
245
+ * Find promotion code by specific criteria type
246
+ */
247
+ public static findByCode(code: string, options: FindOptions<PromotionCode> = {}) {
248
+ return this.findOne({
249
+ where: { code },
250
+ ...options,
251
+ });
252
+ }
253
+
254
+ public static findByNftAddress(nftAddress: string, options: FindOptions<PromotionCode> = {}) {
255
+ return this.findOne({
256
+ where: {
257
+ verification_type: 'nft',
258
+ 'nft_config.addresses': {
259
+ [Op.contains]: [nftAddress],
260
+ },
261
+ } as any,
262
+ ...options,
263
+ });
264
+ }
265
+
266
+ public static findByVcRole(vcRole: string, options: FindOptions<PromotionCode> = {}) {
267
+ return this.findOne({
268
+ where: {
269
+ verification_type: 'vc',
270
+ 'vc_config.roles': {
271
+ [Op.contains]: [vcRole],
272
+ },
273
+ } as any,
274
+ ...options,
275
+ });
276
+ }
277
+
278
+ public static findByUserDid(userDid: string, options: FindOptions<PromotionCode> = {}) {
279
+ return this.findOne({
280
+ where: {
281
+ verification_type: 'user_restricted',
282
+ customer_dids: {
283
+ [Op.contains]: [userDid],
284
+ },
285
+ } as any,
286
+ ...options,
287
+ });
288
+ }
289
+
290
+ /**
291
+ * Check if promotion code is being used
292
+ */
293
+ public async isUsed() {
294
+ const { Discount } = this.sequelize.models;
295
+ const discountCount = await Discount!.count({
296
+ where: { promotion_code_id: this.id },
297
+ });
298
+
299
+ if (discountCount > 0) {
300
+ return true;
301
+ }
302
+ return false;
303
+ }
304
+
305
+ /**
306
+ * Check if promotion code is valid for use
307
+ */
308
+ public isValid() {
309
+ if (!this.active) {
310
+ return { valid: false, reason: 'Promotion code is inactive' };
311
+ }
312
+
313
+ if (this.expires_at && Math.floor(Date.now() / 1000) > this.expires_at) {
314
+ return { valid: false, reason: 'Promotion code has expired' };
315
+ }
316
+
317
+ if (this.max_redemptions && (this.times_redeemed ?? 0) >= this.max_redemptions) {
318
+ return { valid: false, reason: 'Promotion code has reached maximum redemptions' };
319
+ }
320
+
321
+ return { valid: true };
322
+ }
323
+
324
+ /**
325
+ * Insert promotion code with formatting
326
+ */
327
+ public static insert(promotionCode: Partial<TPromotionCode>) {
328
+ const formattedData = this.formatBeforeSave({
329
+ active: true,
330
+ times_redeemed: 0,
331
+ metadata: {},
332
+ ...promotionCode,
333
+ });
334
+ return this.create(formattedData as any);
335
+ }
336
+
337
+ /**
338
+ * Format restrictions currency options for storage - convert unit amounts to token amounts
339
+ */
340
+ public static formatRestrictionsCurrencyOptions(
341
+ restrictions: Restrictions | undefined,
342
+ currencies: TPaymentCurrency[]
343
+ ): Restrictions | undefined {
344
+ if (!restrictions || !restrictions.currency_options) {
345
+ return restrictions;
346
+ }
347
+
348
+ const formatted: Record<string, { minimum_amount: string }> = {};
349
+
350
+ if (restrictions.minimum_amount && restrictions.minimum_amount_currency) {
351
+ const currency = currencies.find((c) => c.id === restrictions.minimum_amount_currency);
352
+ if (!currency) {
353
+ throw new Error(`currency ${restrictions.minimum_amount_currency} not found or inactive`);
354
+ }
355
+ restrictions.minimum_amount = fromTokenToUnit(
356
+ trimDecimals(restrictions.minimum_amount, currency.decimal),
357
+ currency.decimal
358
+ ).toString();
359
+ }
360
+
361
+ for (const [currencyId, optionData] of Object.entries(restrictions.currency_options)) {
362
+ const currency = currencies.find((c) => c.id === currencyId);
363
+ if (!currency) {
364
+ throw new Error(`currency ${currencyId} used in promotion code not found or inactive`);
365
+ }
366
+
367
+ formatted[currencyId] = {
368
+ minimum_amount: fromTokenToUnit(
369
+ trimDecimals(optionData.minimum_amount || '0', currency.decimal),
370
+ currency.decimal
371
+ ).toString(),
372
+ };
373
+
374
+ if (restrictions.minimum_amount && !restrictions.currency_options[currencyId]) {
375
+ restrictions.currency_options[currencyId] = {
376
+ minimum_amount: restrictions.minimum_amount,
377
+ };
378
+ }
379
+ }
380
+
381
+ // Return with the correct storage format (string values)
382
+ return {
383
+ ...restrictions,
384
+ currency_options: formatted,
385
+ } as any;
386
+ }
110
387
  }
111
388
 
112
389
  export type TPromotionCode = InferAttributes<PromotionCode>;
@@ -138,7 +138,9 @@ export type SimpleCustomField = {
138
138
 
139
139
  export type DiscountAmount = {
140
140
  amount: number;
141
- discount: string;
141
+ discount?: string;
142
+ promotion_code?: string;
143
+ coupon?: string;
142
144
  };
143
145
 
144
146
  export type CustomerAddress = {
@@ -788,3 +790,30 @@ export type RechargeConfig = {
788
790
  max_recharge_amount?: number;
789
791
  };
790
792
  };
793
+
794
+ export type NFTConfig = {
795
+ addresses?: string[];
796
+ tags?: string[];
797
+ trusted_issuers?: string[];
798
+ trusted_parents?: string[];
799
+ min_balance?: number;
800
+ };
801
+
802
+ export type VCConfig = {
803
+ roles?: string[];
804
+ trusted_issuers?: string[];
805
+ };
806
+
807
+ export type VerificationType = 'code' | 'nft' | 'vc' | 'user_restricted';
808
+
809
+ export type Restrictions = {
810
+ currency_options?: Record<
811
+ string,
812
+ {
813
+ minimum_amount: string;
814
+ }
815
+ >;
816
+ first_time_transaction?: boolean;
817
+ minimum_amount?: string;
818
+ minimum_amount_currency?: string;
819
+ };
@@ -3,8 +3,6 @@ import {
3
3
  getBillingThreshold,
4
4
  getCheckoutAmount,
5
5
  getCheckoutMode,
6
- getPriceCurrencyOptions,
7
- getPriceUintAmountByCurrency,
8
6
  getRecurringPeriod,
9
7
  getSubscriptionCreateSetup,
10
8
  getCheckoutSessionSubscriptionIds,
@@ -15,6 +13,7 @@ import {
15
13
  createPaymentBeneficiaries,
16
14
  getSubscriptionLineItems,
17
15
  } from '../../src/libs/session';
16
+ import { getPriceCurrencyOptions, getPriceUintAmountByCurrency } from '../../src/libs/price';
18
17
  import type { TLineItemExpanded } from '../../src/store/models';
19
18
  import type { PaymentBeneficiary } from '../../src/store/models/types';
20
19
  import { SubscriptionItem } from '../../src/store/models';
@@ -177,8 +176,8 @@ describe('getBillingThreshold', () => {
177
176
  });
178
177
  });
179
178
 
180
- describe('getCheckoutAmount', () => {
181
- it('should calculate the total amount correctly for recurring items', () => {
179
+ describe('await getCheckoutAmount', () => {
180
+ it('should calculate the total amount correctly for recurring items', async () => {
182
181
  const items = [
183
182
  {
184
183
  price: { type: 'recurring', recurring: { usage_type: 'licensed' }, currency_id: 'usd', unit_amount: '10' },
@@ -190,21 +189,21 @@ describe('getCheckoutAmount', () => {
190
189
  },
191
190
  ];
192
191
  const currencyId = 'usd';
193
- const result = getCheckoutAmount(items as TLineItemExpanded[], currencyId);
192
+ const result = await getCheckoutAmount(items as TLineItemExpanded[], currencyId);
194
193
  expect(result.total).toBe('20'); // Update the expected value based on your calculation
195
194
  });
196
195
 
197
- it('should calculate the total amount correctly for non-recurring items', () => {
196
+ it('should calculate the total amount correctly for non-recurring items', async () => {
198
197
  const items = [
199
198
  { price: { type: 'one_time', currency_id: 'usd', unit_amount: '10' }, quantity: 2 },
200
199
  { price: { type: 'one_time', currency_id: 'usd', unit_amount: '20' }, quantity: 3 },
201
200
  ];
202
201
  const currencyId = 'usd';
203
- const result = getCheckoutAmount(items as TLineItemExpanded[], currencyId);
202
+ const result = await getCheckoutAmount(items as TLineItemExpanded[], currencyId);
204
203
  expect(result.total).toBe('80');
205
204
  });
206
205
 
207
- it('should calculate the total amount correctly when trialing is true', () => {
206
+ it('should calculate the total amount correctly when trialing is true', async () => {
208
207
  const items = [
209
208
  {
210
209
  price: { type: 'recurring', recurring: { usage_type: 'licensed' }, currency_id: 'usd', unit_amount: '10' },
@@ -216,11 +215,11 @@ describe('getCheckoutAmount', () => {
216
215
  },
217
216
  ];
218
217
  const currencyId = 'usd';
219
- const result = getCheckoutAmount(items as TLineItemExpanded[], currencyId, true);
218
+ const result = await getCheckoutAmount(items as TLineItemExpanded[], currencyId, true);
220
219
  expect(result.total).toBe('0');
221
220
  });
222
221
 
223
- it('should throw when when custom_unit_amount is provided with other item', () => {
222
+ it('should throw when when custom_unit_amount is provided with other item', async () => {
224
223
  const items = [
225
224
  {
226
225
  price: { type: 'recurring', recurring: { usage_type: 'licensed' }, currency_id: 'usd', unit_amount: '10' },
@@ -237,12 +236,12 @@ describe('getCheckoutAmount', () => {
237
236
  },
238
237
  ];
239
238
  const currencyId = 'usd';
240
- expect(() => getCheckoutAmount(items as any[], currencyId)).toThrow(
239
+ await expect(getCheckoutAmount(items as any[], currencyId)).rejects.toThrow(
241
240
  'Multiple items with custom unit amount are not supported'
242
241
  );
243
242
  });
244
243
 
245
- it('should calculate the total amount correctly when custom_unit_amount.preset is provided', () => {
244
+ it('should calculate the total amount correctly when custom_unit_amount.preset is provided', async () => {
246
245
  const items = [
247
246
  {
248
247
  price: {
@@ -255,11 +254,11 @@ describe('getCheckoutAmount', () => {
255
254
  },
256
255
  ];
257
256
  const currencyId = 'usd';
258
- const result = getCheckoutAmount(items as TLineItemExpanded[], currencyId);
257
+ const result = await getCheckoutAmount(items as TLineItemExpanded[], currencyId);
259
258
  expect(result.total).toBe('10');
260
259
  });
261
260
 
262
- it('should calculate the total amount correctly when custom_unit_amount.presets is provided', () => {
261
+ it('should calculate the total amount correctly when custom_unit_amount.presets is provided', async () => {
263
262
  const items = [
264
263
  {
265
264
  price: {
@@ -272,7 +271,7 @@ describe('getCheckoutAmount', () => {
272
271
  },
273
272
  ];
274
273
  const currencyId = 'usd';
275
- const result = getCheckoutAmount(items as TLineItemExpanded[], currencyId);
274
+ const result = await getCheckoutAmount(items as TLineItemExpanded[], currencyId);
276
275
  expect(result.total).toBe('20');
277
276
  });
278
277
  });
@@ -531,35 +530,45 @@ describe('mergeSubscriptionDataFromLineItems', () => {
531
530
  });
532
531
 
533
532
  describe('getFastCheckoutAmount', () => {
534
- it('should return total amount for payment mode', () => {
533
+ it('should return total amount for payment mode', async () => {
535
534
  const items = [{ price: { type: 'one_time', currency_id: 'usd', unit_amount: '100' }, quantity: 1 }];
536
- const result = getFastCheckoutAmount(items as TLineItemExpanded[], 'payment', 'usd');
535
+ const result = await getFastCheckoutAmount({
536
+ items: items as TLineItemExpanded[],
537
+ mode: 'payment',
538
+ currencyId: 'usd',
539
+ });
537
540
  expect(result).toBe('100');
538
541
  });
539
542
 
540
- it('should return renew amount for setup mode', () => {
543
+ it('should return renew amount for setup mode', async () => {
541
544
  const items = [
542
545
  {
543
546
  price: { type: 'recurring', recurring: { usage_type: 'licensed' }, currency_id: 'usd', unit_amount: '100' },
544
547
  quantity: 1,
545
548
  },
546
549
  ];
547
- const result = getFastCheckoutAmount(items as any, 'setup', 'usd');
550
+ const result = await getFastCheckoutAmount({ items: items as any, mode: 'setup', currencyId: 'usd' });
548
551
  expect(result).toBe('100');
549
552
  });
550
553
 
551
- it('should multiply renew amount by minimumCycle for setup mode', () => {
554
+ it('should multiply renew amount by minimumCycle for setup mode', async () => {
552
555
  const items = [
553
556
  {
554
557
  price: { type: 'recurring', recurring: { usage_type: 'licensed' }, currency_id: 'usd', unit_amount: '100' },
555
558
  quantity: 1,
556
559
  },
557
560
  ];
558
- const result = getFastCheckoutAmount(items as any, 'setup', 'usd', false, 3);
561
+ const result = await getFastCheckoutAmount({
562
+ items: items as any,
563
+ mode: 'setup',
564
+ currencyId: 'usd',
565
+ trialing: false,
566
+ minimumCycle: 3,
567
+ });
559
568
  expect(result).toBe('300');
560
569
  });
561
570
 
562
- it('should return total + renew*(minimumCycle-1) for subscription mode', () => {
571
+ it('should return total + renew*(minimumCycle-1) for subscription mode', async () => {
563
572
  const items = [
564
573
  {
565
574
  price: { type: 'recurring', recurring: { usage_type: 'licensed' }, currency_id: 'usd', unit_amount: '100' },
@@ -567,11 +576,17 @@ describe('getFastCheckoutAmount', () => {
567
576
  },
568
577
  { price: { type: 'one_time', currency_id: 'usd', unit_amount: '50' }, quantity: 1 },
569
578
  ];
570
- const result = getFastCheckoutAmount(items as any, 'subscription', 'usd', false, 3);
579
+ const result = await getFastCheckoutAmount({
580
+ items: items as any,
581
+ mode: 'subscription',
582
+ currencyId: 'usd',
583
+ trialing: false,
584
+ minimumCycle: 3,
585
+ });
571
586
  expect(result).toBe('350');
572
587
  });
573
588
 
574
- it('should return total + renew*minimumCycle for subscription mode when trialing', () => {
589
+ it('should return total + renew*minimumCycle for subscription mode when trialing', async () => {
575
590
  const items = [
576
591
  {
577
592
  price: { type: 'recurring', recurring: { usage_type: 'licensed' }, currency_id: 'usd', unit_amount: '100' },
@@ -579,13 +594,19 @@ describe('getFastCheckoutAmount', () => {
579
594
  },
580
595
  { price: { type: 'one_time', currency_id: 'usd', unit_amount: '50' }, quantity: 1 },
581
596
  ];
582
- const result = getFastCheckoutAmount(items as any, 'subscription', 'usd', true, 3);
597
+ const result = await getFastCheckoutAmount({
598
+ items: items as any,
599
+ mode: 'subscription',
600
+ currencyId: 'usd',
601
+ trialing: true,
602
+ minimumCycle: 3,
603
+ });
583
604
  expect(result).toBe('350'); // 50 (total, since recurring is 0 during trial) + 100 (renew) * 3
584
605
  });
585
606
 
586
- it('should return 0 for unknown mode', () => {
607
+ it('should return 0 for unknown mode', async () => {
587
608
  const items = [{ price: { type: 'one_time', currency_id: 'usd', unit_amount: '100' }, quantity: 1 }];
588
- const result = getFastCheckoutAmount(items as any, 'unknown', 'usd');
609
+ const result = await getFastCheckoutAmount({ items: items as any, mode: 'unknown', currencyId: 'usd' });
589
610
  expect(result).toBe('0');
590
611
  });
591
612
  });
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.20.11
17
+ version: 1.20.13
18
18
  logo: logo.png
19
19
  files:
20
20
  - dist