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.
- package/api/src/crons/index.ts +8 -0
- package/api/src/index.ts +2 -0
- package/api/src/integrations/stripe/handlers/invoice.ts +63 -5
- package/api/src/integrations/stripe/handlers/payment-intent.ts +1 -0
- package/api/src/integrations/stripe/resource.ts +253 -2
- package/api/src/libs/currency.ts +31 -0
- package/api/src/libs/discount/coupon.ts +1061 -0
- package/api/src/libs/discount/discount.ts +349 -0
- package/api/src/libs/discount/nft.ts +239 -0
- package/api/src/libs/discount/redemption.ts +636 -0
- package/api/src/libs/discount/vc.ts +73 -0
- package/api/src/libs/env.ts +1 -0
- package/api/src/libs/invoice.ts +44 -10
- package/api/src/libs/math-utils.ts +6 -0
- package/api/src/libs/price.ts +43 -0
- package/api/src/libs/session.ts +242 -57
- package/api/src/libs/subscription.ts +2 -6
- package/api/src/libs/vendor-util/adapters/launcher-adapter.ts +1 -1
- package/api/src/libs/vendor-util/adapters/types.ts +1 -0
- package/api/src/libs/vendor-util/fulfillment.ts +1 -1
- package/api/src/queues/auto-recharge.ts +1 -1
- package/api/src/queues/discount-status.ts +200 -0
- package/api/src/queues/subscription.ts +98 -5
- package/api/src/queues/usage-record.ts +1 -1
- package/api/src/queues/vendors/fulfillment-coordinator.ts +1 -29
- package/api/src/queues/vendors/return-processor.ts +184 -0
- package/api/src/queues/vendors/return-scanner.ts +119 -0
- package/api/src/queues/vendors/status-check.ts +1 -1
- package/api/src/routes/auto-recharge-configs.ts +5 -3
- package/api/src/routes/checkout-sessions.ts +755 -64
- package/api/src/routes/connect/change-payment.ts +6 -1
- package/api/src/routes/connect/change-plan.ts +6 -1
- package/api/src/routes/connect/setup.ts +6 -1
- package/api/src/routes/connect/shared.ts +80 -9
- package/api/src/routes/connect/subscribe.ts +12 -2
- package/api/src/routes/coupons.ts +518 -0
- package/api/src/routes/index.ts +4 -0
- package/api/src/routes/invoices.ts +44 -3
- package/api/src/routes/meter-events.ts +2 -1
- package/api/src/routes/payment-currencies.ts +1 -0
- package/api/src/routes/promotion-codes.ts +482 -0
- package/api/src/routes/subscriptions.ts +23 -2
- package/api/src/routes/vendor.ts +89 -2
- package/api/src/store/migrations/20250904-discount.ts +136 -0
- package/api/src/store/migrations/20250910-timestamp-fields.ts +116 -0
- package/api/src/store/migrations/20250916-add-description-fields.ts +30 -0
- package/api/src/store/migrations/20250918-add-vendor-extends.ts +20 -0
- package/api/src/store/models/checkout-session.ts +17 -2
- package/api/src/store/models/coupon.ts +144 -4
- package/api/src/store/models/discount.ts +23 -10
- package/api/src/store/models/index.ts +13 -2
- package/api/src/store/models/product-vendor.ts +6 -0
- package/api/src/store/models/promotion-code.ts +295 -18
- package/api/src/store/models/types.ts +30 -1
- package/api/tests/libs/session.spec.ts +48 -27
- package/blocklet.yml +1 -1
- package/package.json +20 -20
- package/src/app.tsx +2 -0
- package/src/components/customer/link.tsx +1 -1
- package/src/components/discount/discount-info.tsx +178 -0
- package/src/components/invoice/table.tsx +140 -48
- package/src/components/invoice-pdf/styles.ts +6 -0
- package/src/components/invoice-pdf/template.tsx +59 -33
- package/src/components/metadata/form.tsx +14 -5
- package/src/components/payment-link/actions.tsx +42 -0
- package/src/components/price/form.tsx +91 -65
- package/src/components/product/vendor-config.tsx +5 -3
- package/src/components/promotion/active-redemptions.tsx +534 -0
- package/src/components/promotion/currency-multi-select.tsx +350 -0
- package/src/components/promotion/currency-restrictions.tsx +117 -0
- package/src/components/promotion/product-select.tsx +292 -0
- package/src/components/promotion/promotion-code-form.tsx +534 -0
- package/src/components/subscription/portal/list.tsx +6 -1
- package/src/components/subscription/vendor-service-list.tsx +13 -2
- package/src/locales/en.tsx +227 -0
- package/src/locales/zh.tsx +222 -1
- package/src/pages/admin/billing/subscriptions/detail.tsx +5 -0
- package/src/pages/admin/products/coupons/applicable-products.tsx +166 -0
- package/src/pages/admin/products/coupons/create.tsx +612 -0
- package/src/pages/admin/products/coupons/detail.tsx +538 -0
- package/src/pages/admin/products/coupons/edit.tsx +127 -0
- package/src/pages/admin/products/coupons/index.tsx +210 -3
- package/src/pages/admin/products/index.tsx +22 -3
- package/src/pages/admin/products/products/detail.tsx +12 -2
- package/src/pages/admin/products/promotion-codes/actions.tsx +103 -0
- package/src/pages/admin/products/promotion-codes/create.tsx +235 -0
- package/src/pages/admin/products/promotion-codes/detail.tsx +416 -0
- package/src/pages/admin/products/promotion-codes/list.tsx +247 -0
- package/src/pages/admin/products/promotion-codes/verification-config.tsx +327 -0
- package/src/pages/admin/products/vendors/index.tsx +17 -5
- package/src/pages/customer/subscription/detail.tsx +5 -0
- package/vite.config.ts +4 -3
|
@@ -1,7 +1,22 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/lines-between-class-members */
|
|
2
|
-
import {
|
|
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 {
|
|
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
|
|
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:
|
|
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.
|
|
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(
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
});
|