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
|
@@ -35,7 +35,12 @@ export default {
|
|
|
35
35
|
const items = subscription!.items as TLineItemExpanded[];
|
|
36
36
|
const trialing = true;
|
|
37
37
|
const billingThreshold = Number(subscription.billing_thresholds?.amount_gte || 0);
|
|
38
|
-
const fastCheckoutAmount = getFastCheckoutAmount(
|
|
38
|
+
const fastCheckoutAmount = await getFastCheckoutAmount({
|
|
39
|
+
items,
|
|
40
|
+
mode: 'subscription',
|
|
41
|
+
currencyId: paymentCurrency.id,
|
|
42
|
+
trialing: false,
|
|
43
|
+
});
|
|
39
44
|
|
|
40
45
|
if (paymentMethod.type === 'arcblock') {
|
|
41
46
|
const delegation = await isDelegationSufficientForPayment({
|
|
@@ -37,7 +37,12 @@ export default {
|
|
|
37
37
|
const items = subscription!.items as TLineItemExpanded[];
|
|
38
38
|
const trialing = false;
|
|
39
39
|
const billingThreshold = Number(subscription!.billing_thresholds?.amount_gte || 0);
|
|
40
|
-
const fastCheckoutAmount = getFastCheckoutAmount(
|
|
40
|
+
const fastCheckoutAmount = await getFastCheckoutAmount({
|
|
41
|
+
items,
|
|
42
|
+
mode: 'subscription',
|
|
43
|
+
currencyId: paymentCurrency.id,
|
|
44
|
+
trialing: false,
|
|
45
|
+
});
|
|
41
46
|
|
|
42
47
|
if (paymentMethod.type === 'arcblock') {
|
|
43
48
|
const delegation = await isDelegationSufficientForPayment({
|
|
@@ -52,7 +52,12 @@ export default {
|
|
|
52
52
|
const trialing = trialInDays > 0 || trialEnd > now;
|
|
53
53
|
const billingThreshold = Number(checkoutSession.subscription_data?.billing_threshold_amount || 0);
|
|
54
54
|
const minStakeAmount = Number(checkoutSession.subscription_data?.min_stake_amount || 0);
|
|
55
|
-
const fastCheckoutAmount = getFastCheckoutAmount(
|
|
55
|
+
const fastCheckoutAmount = await getFastCheckoutAmount({
|
|
56
|
+
items,
|
|
57
|
+
mode: checkoutSession.mode,
|
|
58
|
+
currencyId: paymentCurrency.id,
|
|
59
|
+
trialing,
|
|
60
|
+
});
|
|
56
61
|
|
|
57
62
|
if (paymentMethod.type === 'arcblock') {
|
|
58
63
|
const delegation = await isDelegationSufficientForPayment({
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
isDonationCheckoutSession,
|
|
23
23
|
getSubscriptionLineItems,
|
|
24
24
|
} from '../../libs/session';
|
|
25
|
+
import { getDiscountRecordsForCheckout } from '../../libs/discount/coupon';
|
|
25
26
|
import {
|
|
26
27
|
expandSubscriptionItems,
|
|
27
28
|
getSubscriptionPaymentAddress,
|
|
@@ -268,7 +269,26 @@ export async function ensureSetupIntent(checkoutSessionId: string, skipInvoice?:
|
|
|
268
269
|
if (checkoutSession.invoice_id) {
|
|
269
270
|
invoice = await Invoice.findByPk(checkoutSession.invoice_id);
|
|
270
271
|
} else {
|
|
271
|
-
//
|
|
272
|
+
// Get discount information for this checkout session
|
|
273
|
+
let discountInfo: { appliedDiscounts: string[]; discountBreakdown: Array<{ amount: string; discount: string }> } = {
|
|
274
|
+
appliedDiscounts: [],
|
|
275
|
+
discountBreakdown: []
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
discountInfo = await getDiscountRecordsForCheckout({
|
|
280
|
+
checkoutSessionId: checkoutSession.id,
|
|
281
|
+
customerId: customer.id,
|
|
282
|
+
});
|
|
283
|
+
} catch (error) {
|
|
284
|
+
logger.warn('Failed to get discount records for setup intent checkout session', {
|
|
285
|
+
checkoutSessionId: checkoutSession.id,
|
|
286
|
+
customerId: customer.id,
|
|
287
|
+
error: error.message,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// generate empty invoice here with discount information
|
|
272
292
|
const result = await ensureInvoiceAndItems({
|
|
273
293
|
customer,
|
|
274
294
|
currency: paymentCurrency,
|
|
@@ -294,14 +314,20 @@ export async function ensureSetupIntent(checkoutSessionId: string, skipInvoice?:
|
|
|
294
314
|
|
|
295
315
|
default_payment_method_id: subscription.default_payment_method_id as string,
|
|
296
316
|
|
|
317
|
+
discounts: discountInfo.appliedDiscounts,
|
|
318
|
+
total_discount_amounts: discountInfo.discountBreakdown,
|
|
319
|
+
|
|
297
320
|
custom_fields: checkoutSession.invoice_creation?.invoice_data?.custom_fields || [],
|
|
298
321
|
footer: checkoutSession.invoice_creation?.invoice_data?.footer || '',
|
|
299
322
|
metadata: checkoutSession.invoice_creation?.invoice_data?.metadata || {},
|
|
300
|
-
} as Invoice,
|
|
323
|
+
} as unknown as Invoice,
|
|
301
324
|
});
|
|
302
325
|
invoice = result.invoice;
|
|
303
326
|
|
|
304
|
-
logger.info(`Invoice created for checkoutSession ${checkoutSession.id} in setup mode: ${invoice.id}
|
|
327
|
+
logger.info(`Invoice created for checkoutSession ${checkoutSession.id} in setup mode: ${invoice.id}`, {
|
|
328
|
+
hasDiscounts: discountInfo.appliedDiscounts.length > 0,
|
|
329
|
+
discountCount: discountInfo.appliedDiscounts.length,
|
|
330
|
+
});
|
|
305
331
|
|
|
306
332
|
// persist invoice id
|
|
307
333
|
await checkoutSession.update({ invoice_id: invoice.id });
|
|
@@ -436,7 +462,41 @@ export async function ensureInvoiceForCheckout({
|
|
|
436
462
|
const trialEnd = Number(checkoutSession.subscription_data?.trial_end || 0);
|
|
437
463
|
const now = dayjs().unix();
|
|
438
464
|
const invoiceItems = lineItems || (await Price.expand(checkoutSession.line_items, { product: true }));
|
|
439
|
-
|
|
465
|
+
|
|
466
|
+
// Get discount records for this checkout session
|
|
467
|
+
let discountInfo: { appliedDiscounts: string[]; discountBreakdown: Array<{ amount: string; discount: string }> } = {
|
|
468
|
+
appliedDiscounts: [],
|
|
469
|
+
discountBreakdown: []
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
// Prepare discount configuration for getCheckoutAmount
|
|
473
|
+
let discountConfig;
|
|
474
|
+
try {
|
|
475
|
+
discountInfo = await getDiscountRecordsForCheckout({
|
|
476
|
+
checkoutSessionId: checkoutSession.id,
|
|
477
|
+
customerId: customer.id,
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
// Apply discount if we have discount records
|
|
481
|
+
if (discountInfo.appliedDiscounts.length > 0 && checkoutSession.discounts?.length) {
|
|
482
|
+
const firstDiscount = checkoutSession.discounts[0];
|
|
483
|
+
if (firstDiscount?.coupon && firstDiscount?.promotion_code) {
|
|
484
|
+
discountConfig = {
|
|
485
|
+
promotionCodeId: firstDiscount.promotion_code,
|
|
486
|
+
couponId: firstDiscount.coupon,
|
|
487
|
+
customerId: customer.id,
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
} catch (error) {
|
|
492
|
+
logger.warn('Failed to get discount records for checkout session', {
|
|
493
|
+
checkoutSessionId: checkoutSession.id,
|
|
494
|
+
customerId: customer.id,
|
|
495
|
+
error: error.message,
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const totalAmount = await getCheckoutAmount(invoiceItems, checkoutSession.currency_id, trialInDays > 0 || trialEnd > now, discountConfig);
|
|
440
500
|
const { invoice, items } = await ensureInvoiceAndItems({
|
|
441
501
|
customer,
|
|
442
502
|
currency: currency as PaymentCurrency,
|
|
@@ -461,7 +521,8 @@ export async function ensureInvoiceForCheckout({
|
|
|
461
521
|
payment_intent_id: paymentIntent?.id,
|
|
462
522
|
checkout_session_id: checkoutSession.id,
|
|
463
523
|
|
|
464
|
-
total: totalAmount.
|
|
524
|
+
total: totalAmount.total,
|
|
525
|
+
subtotal: totalAmount.subtotal,
|
|
465
526
|
|
|
466
527
|
default_payment_method_id: (subscription?.default_payment_method_id ||
|
|
467
528
|
paymentIntent?.payment_method_id) as string,
|
|
@@ -469,10 +530,20 @@ export async function ensureInvoiceForCheckout({
|
|
|
469
530
|
custom_fields: checkoutSession.invoice_creation?.invoice_data?.custom_fields || [],
|
|
470
531
|
footer: checkoutSession.invoice_creation?.invoice_data?.footer || '',
|
|
471
532
|
metadata,
|
|
533
|
+
|
|
534
|
+
discounts: discountInfo.appliedDiscounts,
|
|
535
|
+
total_discount_amounts: discountInfo.discountBreakdown,
|
|
536
|
+
|
|
472
537
|
...(props || {}),
|
|
473
|
-
} as Invoice,
|
|
538
|
+
} as unknown as Invoice,
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
logger.info('Invoice created for checkoutSession', {
|
|
542
|
+
checkoutSessionId: checkoutSession.id,
|
|
543
|
+
invoiceId: invoice.id,
|
|
544
|
+
hasDiscounts: discountInfo.appliedDiscounts.length > 0,
|
|
545
|
+
discountCount: discountInfo.appliedDiscounts.length,
|
|
474
546
|
});
|
|
475
|
-
logger.info('Invoice created for checkoutSession', { checkoutSessionId: checkoutSession.id, invoiceId: invoice.id });
|
|
476
547
|
|
|
477
548
|
// only update invoice_id for single invoice
|
|
478
549
|
if (!isGroupInvoice) {
|
|
@@ -691,7 +762,7 @@ export async function getDelegationTxClaim({
|
|
|
691
762
|
requiredStake?: boolean;
|
|
692
763
|
requiredAmount?: string;
|
|
693
764
|
}) {
|
|
694
|
-
const amount = getFastCheckoutAmount(items, mode, paymentCurrency.id);
|
|
765
|
+
const amount = await getFastCheckoutAmount({ items, mode, currencyId: paymentCurrency.id });
|
|
695
766
|
const address = toDelegateAddress(userDid, wallet.address);
|
|
696
767
|
const tokenLimits = await getTokenLimitsForDelegation(items, paymentMethod, paymentCurrency, address, amount);
|
|
697
768
|
let tokenRequirements = await getTokenRequirements({
|
|
@@ -1010,7 +1081,7 @@ export async function getTokenRequirements({
|
|
|
1010
1081
|
requiredStake,
|
|
1011
1082
|
}: TokenRequirementArgs) {
|
|
1012
1083
|
const tokenRequirements = [];
|
|
1013
|
-
let amount = getFastCheckoutAmount(items, mode, paymentCurrency.id, !!trialing);
|
|
1084
|
+
let amount = await getFastCheckoutAmount({ items, mode, currencyId: paymentCurrency.id, trialing: !!trialing });
|
|
1014
1085
|
|
|
1015
1086
|
// If the app has not staked, we need to add the gas fee to the amount
|
|
1016
1087
|
if ((await hasStakedForGas(paymentMethod)) === false) {
|
|
@@ -73,7 +73,12 @@ export default {
|
|
|
73
73
|
const trialing = trialInDays > 0 || trialEnd > now;
|
|
74
74
|
const billingThreshold = Number(checkoutSession.subscription_data?.billing_threshold_amount || 0);
|
|
75
75
|
const minStakeAmount = Number(checkoutSession.subscription_data?.min_stake_amount || 0);
|
|
76
|
-
const fastCheckoutAmount = getFastCheckoutAmount(
|
|
76
|
+
const fastCheckoutAmount = await getFastCheckoutAmount({
|
|
77
|
+
items,
|
|
78
|
+
mode: checkoutSession.mode,
|
|
79
|
+
currencyId: paymentCurrency.id,
|
|
80
|
+
trialing,
|
|
81
|
+
});
|
|
77
82
|
const claimsList: any[] = [];
|
|
78
83
|
|
|
79
84
|
const allSubscriptionIds = subscriptions.map((sub) => sub.id);
|
|
@@ -240,7 +245,12 @@ export default {
|
|
|
240
245
|
paymentCurrency.id
|
|
241
246
|
);
|
|
242
247
|
const trialing = trialInDays > 0 || trialEnd > now;
|
|
243
|
-
const fastCheckoutAmount = getFastCheckoutAmount(
|
|
248
|
+
const fastCheckoutAmount = await getFastCheckoutAmount({
|
|
249
|
+
items,
|
|
250
|
+
mode: checkoutSession.mode,
|
|
251
|
+
currencyId: paymentCurrency.id,
|
|
252
|
+
trialing,
|
|
253
|
+
});
|
|
244
254
|
// check token balance
|
|
245
255
|
const tokenBalanceCheck = await checkTokenBalance({
|
|
246
256
|
paymentMethod,
|