payment-kit 1.18.56 → 1.19.1
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/.eslintrc.js +6 -0
- package/api/src/crons/index.ts +8 -0
- package/api/src/index.ts +4 -0
- package/api/src/libs/credit-grant.ts +146 -0
- package/api/src/libs/env.ts +1 -0
- package/api/src/libs/invoice.ts +4 -3
- package/api/src/libs/notification/template/base.ts +388 -2
- package/api/src/libs/notification/template/customer-credit-grant-granted.ts +149 -0
- package/api/src/libs/notification/template/customer-credit-grant-low-balance.ts +151 -0
- package/api/src/libs/notification/template/customer-credit-insufficient.ts +254 -0
- package/api/src/libs/notification/template/subscription-canceled.ts +193 -202
- package/api/src/libs/notification/template/subscription-refund-succeeded.ts +215 -237
- package/api/src/libs/notification/template/subscription-renewed.ts +130 -200
- package/api/src/libs/notification/template/subscription-succeeded.ts +100 -202
- package/api/src/libs/notification/template/subscription-trial-start.ts +142 -188
- package/api/src/libs/notification/template/subscription-trial-will-end.ts +146 -174
- package/api/src/libs/notification/template/subscription-upgraded.ts +96 -192
- package/api/src/libs/notification/template/subscription-will-canceled.ts +94 -135
- package/api/src/libs/notification/template/subscription-will-renew.ts +220 -245
- package/api/src/libs/payment.ts +69 -0
- package/api/src/libs/queue/index.ts +3 -2
- package/api/src/libs/session.ts +8 -0
- package/api/src/libs/subscription.ts +74 -3
- package/api/src/libs/ws.ts +23 -1
- package/api/src/locales/en.ts +33 -0
- package/api/src/locales/zh.ts +31 -0
- package/api/src/queues/credit-consume.ts +715 -0
- package/api/src/queues/credit-grant.ts +572 -0
- package/api/src/queues/notification.ts +173 -128
- package/api/src/queues/payment.ts +210 -122
- package/api/src/queues/subscription.ts +179 -0
- package/api/src/routes/checkout-sessions.ts +157 -9
- package/api/src/routes/connect/shared.ts +3 -2
- package/api/src/routes/credit-grants.ts +241 -0
- package/api/src/routes/credit-transactions.ts +208 -0
- package/api/src/routes/index.ts +8 -0
- package/api/src/routes/meter-events.ts +347 -0
- package/api/src/routes/meters.ts +219 -0
- package/api/src/routes/payment-currencies.ts +14 -2
- package/api/src/routes/payment-links.ts +1 -1
- package/api/src/routes/payment-methods.ts +14 -2
- package/api/src/routes/prices.ts +43 -0
- package/api/src/routes/pricing-table.ts +13 -7
- package/api/src/routes/products.ts +63 -4
- package/api/src/routes/settings.ts +1 -1
- package/api/src/routes/subscriptions.ts +4 -0
- package/api/src/store/migrations/20250610-billing-credit.ts +43 -0
- package/api/src/store/models/credit-grant.ts +486 -0
- package/api/src/store/models/credit-transaction.ts +268 -0
- package/api/src/store/models/customer.ts +8 -0
- package/api/src/store/models/index.ts +52 -1
- package/api/src/store/models/meter-event.ts +423 -0
- package/api/src/store/models/meter.ts +176 -0
- package/api/src/store/models/payment-currency.ts +66 -14
- package/api/src/store/models/price.ts +6 -0
- package/api/src/store/models/product.ts +2 -2
- package/api/src/store/models/subscription.ts +24 -0
- package/api/src/store/models/types.ts +28 -2
- package/api/tests/libs/subscription.spec.ts +53 -0
- package/blocklet.yml +9 -1
- package/package.json +57 -58
- package/scripts/sdk.js +233 -1
- package/src/app.tsx +10 -0
- package/src/components/actions.tsx +22 -9
- package/src/components/balance-list.tsx +40 -12
- package/src/components/collapse.tsx +33 -15
- package/src/components/copyable.tsx +8 -7
- package/src/components/currency.tsx +15 -7
- package/src/components/customer/actions.tsx +1 -5
- package/src/components/customer/credit-grant-item-list.tsx +99 -0
- package/src/components/customer/credit-overview.tsx +233 -0
- package/src/components/customer/form.tsx +7 -2
- package/src/components/customer/link.tsx +4 -12
- package/src/components/customer/notification-preference.tsx +18 -9
- package/src/components/customer/overdraft-protection.tsx +112 -41
- package/src/components/drawer-form.tsx +42 -18
- package/src/components/error.tsx +1 -5
- package/src/components/event/list.tsx +9 -10
- package/src/components/filter-toolbar.tsx +20 -19
- package/src/components/info-card.tsx +32 -18
- package/src/components/info-metric.tsx +16 -6
- package/src/components/info-row-group.tsx +1 -7
- package/src/components/info-row.tsx +30 -24
- package/src/components/invoice/action.tsx +1 -7
- package/src/components/invoice/list.tsx +34 -26
- package/src/components/invoice/recharge.tsx +5 -7
- package/src/components/invoice/table.tsx +17 -12
- package/src/components/layout/user.tsx +1 -1
- package/src/components/metadata/form.tsx +290 -94
- package/src/components/metadata/list.tsx +11 -3
- package/src/components/meter/actions.tsx +101 -0
- package/src/components/meter/add-usage-dialog.tsx +239 -0
- package/src/components/meter/events-list.tsx +657 -0
- package/src/components/meter/form.tsx +245 -0
- package/src/components/meter/products.tsx +264 -0
- package/src/components/meter/usage-guide.tsx +174 -0
- package/src/components/passport/actions.tsx +9 -4
- package/src/components/payment-currency/add.tsx +16 -3
- package/src/components/payment-currency/form.tsx +14 -6
- package/src/components/payment-intent/actions.tsx +24 -16
- package/src/components/payment-intent/list.tsx +30 -9
- package/src/components/payment-link/actions.tsx +1 -5
- package/src/components/payment-link/after-pay.tsx +4 -2
- package/src/components/payment-link/before-pay.tsx +14 -4
- package/src/components/payment-link/item.tsx +27 -6
- package/src/components/payment-link/preview.tsx +9 -9
- package/src/components/payment-link/product-select.tsx +69 -15
- package/src/components/payment-method/arcblock.tsx +8 -1
- package/src/components/payment-method/base.tsx +8 -1
- package/src/components/payment-method/bitcoin.tsx +8 -1
- package/src/components/payment-method/ethereum.tsx +8 -1
- package/src/components/payment-method/evm-rpc-input.tsx +11 -7
- package/src/components/payment-method/form.tsx +2 -7
- package/src/components/payment-method/stripe.tsx +2 -0
- package/src/components/payouts/actions.tsx +1 -5
- package/src/components/payouts/list.tsx +30 -10
- package/src/components/payouts/portal/list.tsx +11 -9
- package/src/components/price/currency-select.tsx +63 -32
- package/src/components/price/form.tsx +895 -370
- package/src/components/price/upsell-select.tsx +10 -2
- package/src/components/price/upsell.tsx +7 -2
- package/src/components/pricing-table/actions.tsx +1 -5
- package/src/components/pricing-table/customer-settings.tsx +5 -1
- package/src/components/pricing-table/payment-settings.tsx +14 -4
- package/src/components/pricing-table/preview.tsx +9 -9
- package/src/components/pricing-table/price-item.tsx +6 -1
- package/src/components/pricing-table/product-item.tsx +6 -1
- package/src/components/pricing-table/product-settings.tsx +17 -4
- package/src/components/product/actions.tsx +1 -5
- package/src/components/product/add-price.tsx +9 -7
- package/src/components/product/create.tsx +8 -9
- package/src/components/product/cross-sell-select.tsx +5 -1
- package/src/components/product/cross-sell.tsx +7 -2
- package/src/components/product/edit-price.tsx +21 -12
- package/src/components/product/features.tsx +26 -6
- package/src/components/product/form.tsx +115 -72
- package/src/components/progress-bar.tsx +1 -1
- package/src/components/refund/actions.tsx +1 -7
- package/src/components/refund/list.tsx +31 -18
- package/src/components/section/header.tsx +12 -14
- package/src/components/subscription/actions/cancel.tsx +22 -5
- package/src/components/subscription/actions/index.tsx +9 -10
- package/src/components/subscription/actions/pause.tsx +32 -6
- package/src/components/subscription/actions/slash-stake.tsx +5 -3
- package/src/components/subscription/description.tsx +12 -8
- package/src/components/subscription/items/index.tsx +31 -16
- package/src/components/subscription/items/usage-records.tsx +19 -5
- package/src/components/subscription/list.tsx +5 -7
- package/src/components/subscription/metrics.tsx +62 -15
- package/src/components/subscription/portal/actions.tsx +78 -71
- package/src/components/subscription/portal/cancel.tsx +10 -3
- package/src/components/subscription/portal/list.tsx +48 -26
- package/src/components/uploader.tsx +5 -13
- package/src/components/webhook/attempts.tsx +51 -16
- package/src/components/webhook/request-info.tsx +8 -6
- package/src/contexts/products.tsx +27 -10
- package/src/hooks/subscription.ts +34 -0
- package/src/libs/meter-utils.ts +196 -0
- package/src/libs/util.ts +4 -0
- package/src/locales/en.tsx +385 -4
- package/src/locales/zh.tsx +364 -0
- package/src/pages/admin/billing/index.tsx +61 -33
- package/src/pages/admin/billing/invoices/detail.tsx +49 -13
- package/src/pages/admin/billing/meters/create.tsx +60 -0
- package/src/pages/admin/billing/meters/detail.tsx +435 -0
- package/src/pages/admin/billing/meters/index.tsx +210 -0
- package/src/pages/admin/billing/meters/meter-event.tsx +346 -0
- package/src/pages/admin/billing/subscriptions/detail.tsx +90 -25
- package/src/pages/admin/customers/customers/credit-grant/detail.tsx +391 -0
- package/src/pages/admin/customers/customers/detail.tsx +67 -14
- package/src/pages/admin/customers/customers/index.tsx +6 -1
- package/src/pages/admin/customers/index.tsx +5 -0
- package/src/pages/admin/developers/events/detail.tsx +37 -11
- package/src/pages/admin/developers/index.tsx +1 -1
- package/src/pages/admin/developers/webhooks/detail.tsx +41 -11
- package/src/pages/admin/index.tsx +15 -2
- package/src/pages/admin/overview.tsx +107 -19
- package/src/pages/admin/payments/intents/detail.tsx +58 -14
- package/src/pages/admin/payments/payouts/detail.tsx +63 -15
- package/src/pages/admin/payments/refunds/detail.tsx +58 -14
- package/src/pages/admin/products/index.tsx +11 -4
- package/src/pages/admin/products/links/create.tsx +22 -4
- package/src/pages/admin/products/links/detail.tsx +43 -14
- package/src/pages/admin/products/passports/index.tsx +23 -4
- package/src/pages/admin/products/prices/actions.tsx +16 -9
- package/src/pages/admin/products/prices/detail.tsx +73 -14
- package/src/pages/admin/products/prices/list.tsx +15 -3
- package/src/pages/admin/products/pricing-tables/create.tsx +45 -12
- package/src/pages/admin/products/pricing-tables/detail.tsx +45 -14
- package/src/pages/admin/products/products/create.tsx +233 -54
- package/src/pages/admin/products/products/detail.tsx +74 -18
- package/src/pages/admin/settings/index.tsx +8 -1
- package/src/pages/admin/settings/payment-methods/index.tsx +87 -19
- package/src/pages/admin/settings/vault-config/edit-form.tsx +42 -28
- package/src/pages/admin/settings/vault-config/index.tsx +57 -10
- package/src/pages/customer/credit-grant/detail.tsx +308 -0
- package/src/pages/customer/index.tsx +76 -17
- package/src/pages/customer/invoice/detail.tsx +63 -14
- package/src/pages/customer/invoice/past-due.tsx +11 -3
- package/src/pages/customer/payout/detail.tsx +56 -13
- package/src/pages/customer/recharge/account.tsx +78 -18
- package/src/pages/customer/recharge/subscription.tsx +86 -25
- package/src/pages/customer/refund/list.tsx +60 -24
- package/src/pages/customer/subscription/change-payment.tsx +17 -6
- package/src/pages/customer/subscription/change-plan.tsx +34 -7
- package/src/pages/customer/subscription/detail.tsx +134 -34
- package/src/pages/customer/subscription/embed.tsx +25 -5
- package/src/pages/home.tsx +26 -4
- package/src/pages/integrations/donations/edit-form.tsx +25 -9
- package/src/pages/integrations/donations/index.tsx +26 -9
- package/src/pages/integrations/donations/preview.tsx +59 -15
- package/src/pages/integrations/index.tsx +10 -1
- package/src/pages/integrations/overview.tsx +78 -17
- package/vite.config.ts +60 -30
|
@@ -20,6 +20,7 @@ import { createIdGenerator, formatMetadata } from '../../libs/util';
|
|
|
20
20
|
import { sequelize } from '../sequelize';
|
|
21
21
|
import type { TPaymentCurrency } from './payment-currency';
|
|
22
22
|
import type { CustomUnitAmount, LineItem, PriceCurrency, PriceRecurring, PriceTier, TransformQuantity } from './types';
|
|
23
|
+
import { Meter, type TMeter } from './meter';
|
|
23
24
|
|
|
24
25
|
export const nextPriceId = createIdGenerator('price', 24);
|
|
25
26
|
|
|
@@ -32,6 +33,7 @@ type TPriceExpanded = TPrice & {
|
|
|
32
33
|
upsells_to: TPriceExpanded;
|
|
33
34
|
upsells_to_id: string;
|
|
34
35
|
};
|
|
36
|
+
meter?: TMeter;
|
|
35
37
|
};
|
|
36
38
|
type TLineItemExpanded = LineItem & { price: TPriceExpanded; upsell_price: TPriceExpanded };
|
|
37
39
|
|
|
@@ -395,6 +397,10 @@ export class Price extends Model<InferAttributes<Price>, InferCreationAttributes
|
|
|
395
397
|
x.upsell.upsells_to = to;
|
|
396
398
|
}
|
|
397
399
|
}
|
|
400
|
+
if (x.recurring?.meter_id) {
|
|
401
|
+
// @ts-ignore
|
|
402
|
+
x.meter = await Meter.findByPk(x.recurring?.meter_id);
|
|
403
|
+
}
|
|
398
404
|
})
|
|
399
405
|
);
|
|
400
406
|
}
|
|
@@ -20,7 +20,7 @@ export class Product extends Model<InferAttributes<Product>, InferCreationAttrib
|
|
|
20
20
|
declare livemode: boolean;
|
|
21
21
|
declare locked: CreationOptional<boolean>;
|
|
22
22
|
|
|
23
|
-
declare type: LiteralUnion<'service' | 'good', string>;
|
|
23
|
+
declare type: LiteralUnion<'service' | 'good' | 'credit', string>;
|
|
24
24
|
|
|
25
25
|
// The product’s name, meant to be displayable to the customer.
|
|
26
26
|
declare name: string;
|
|
@@ -79,7 +79,7 @@ export class Product extends Model<InferAttributes<Product>, InferCreationAttrib
|
|
|
79
79
|
defaultValue: false,
|
|
80
80
|
},
|
|
81
81
|
type: {
|
|
82
|
-
type: DataTypes.ENUM('service', 'good'),
|
|
82
|
+
type: DataTypes.ENUM('service', 'good', 'credit'),
|
|
83
83
|
},
|
|
84
84
|
name: {
|
|
85
85
|
type: DataTypes.STRING(512),
|
|
@@ -410,6 +410,30 @@ export class Subscription extends Model<InferAttributes<Subscription>, InferCrea
|
|
|
410
410
|
return this.isActive() && (!!this.cancel_at_period_end || !!this.cancel_at);
|
|
411
411
|
}
|
|
412
412
|
|
|
413
|
+
public async isConsumesCredit(): Promise<boolean> {
|
|
414
|
+
// @ts-ignore
|
|
415
|
+
const { SubscriptionItem, Price } = this.sequelize.models;
|
|
416
|
+
if (!SubscriptionItem || !Price) {
|
|
417
|
+
return false;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// 查询订阅的所有价格项
|
|
421
|
+
const items = await SubscriptionItem.findAll({
|
|
422
|
+
where: { subscription_id: this.id },
|
|
423
|
+
include: [
|
|
424
|
+
{
|
|
425
|
+
model: Price,
|
|
426
|
+
as: 'price',
|
|
427
|
+
},
|
|
428
|
+
],
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
return items.some((item: any) => {
|
|
432
|
+
const recurring = item.price?.recurring;
|
|
433
|
+
return recurring && recurring.usage_type === 'metered' && recurring.meter_id;
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
|
|
413
437
|
public async start() {
|
|
414
438
|
if (this.status === 'active') {
|
|
415
439
|
logger.warn(`subscription already active: ${this.id}`);
|
|
@@ -46,6 +46,7 @@ export type PriceRecurring = {
|
|
|
46
46
|
interval_count: number;
|
|
47
47
|
aggregate_usage?: LiteralUnion<'sum' | 'last_during_period' | 'max' | 'last_ever', string>;
|
|
48
48
|
usage_type?: LiteralUnion<'licensed' | 'metered', string>;
|
|
49
|
+
meter_id?: string;
|
|
49
50
|
};
|
|
50
51
|
|
|
51
52
|
export type PriceCurrency = {
|
|
@@ -325,7 +326,7 @@ export type PaymentDetails = {
|
|
|
325
326
|
arcblock?: {
|
|
326
327
|
tx_hash: string;
|
|
327
328
|
payer: string;
|
|
328
|
-
type?: LiteralUnion<'slash' | 'transfer' | 'delegate' | 'stake_return', string>;
|
|
329
|
+
type?: LiteralUnion<'slash' | 'transfer' | 'delegate' | 'stake_return' | 'credit', string>;
|
|
329
330
|
receiver?: string;
|
|
330
331
|
staking?: {
|
|
331
332
|
tx_hash: string;
|
|
@@ -722,7 +723,11 @@ export type EventType = LiteralUnion<
|
|
|
722
723
|
| 'transfer.reversed'
|
|
723
724
|
| 'transfer.updated'
|
|
724
725
|
| 'billing.discrepancy'
|
|
725
|
-
| 'usage.report.empty'
|
|
726
|
+
| 'usage.report.empty'
|
|
727
|
+
| 'customer.credit.insufficient'
|
|
728
|
+
| 'customer.credit_grant.granted'
|
|
729
|
+
| 'customer.credit_grant.low_balance'
|
|
730
|
+
| 'customer.credit_grant.depleted',
|
|
726
731
|
string
|
|
727
732
|
>;
|
|
728
733
|
|
|
@@ -751,3 +756,24 @@ export type NotificationSetting = {
|
|
|
751
756
|
exclude_events?: EventType[];
|
|
752
757
|
include_events?: EventType[];
|
|
753
758
|
};
|
|
759
|
+
|
|
760
|
+
export type CreditGrantApplicabilityConfig = {
|
|
761
|
+
scope: {
|
|
762
|
+
prices?: string[]; // 可选,指定适用的价格ID列表
|
|
763
|
+
price_type?: 'metered'; // 可选,按价格类型适用, 目前只支持metered
|
|
764
|
+
};
|
|
765
|
+
};
|
|
766
|
+
|
|
767
|
+
export type MeterEventStatus =
|
|
768
|
+
| 'pending'
|
|
769
|
+
| 'processing'
|
|
770
|
+
| 'requires_action'
|
|
771
|
+
| 'requires_capture'
|
|
772
|
+
| 'completed'
|
|
773
|
+
| 'canceled';
|
|
774
|
+
|
|
775
|
+
export type MeterEventPayload = {
|
|
776
|
+
customer_id: string;
|
|
777
|
+
value: string;
|
|
778
|
+
subscription_id?: string;
|
|
779
|
+
};
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
getSubscriptionStakeAmountSetup,
|
|
12
12
|
checkUsageReportEmpty,
|
|
13
13
|
calculateRecommendedRechargeAmount,
|
|
14
|
+
getSubscriptionCycleSetup,
|
|
14
15
|
} from '../../src/libs/subscription';
|
|
15
16
|
import { PaymentMethod, Subscription, SubscriptionItem, UsageRecord, Price } from '../../src/store/models';
|
|
16
17
|
|
|
@@ -766,3 +767,55 @@ describe('calculateRecommendedRechargeAmount', () => {
|
|
|
766
767
|
expect(result.amount).toBe('300');
|
|
767
768
|
});
|
|
768
769
|
});
|
|
770
|
+
|
|
771
|
+
describe('getSubscriptionCycleSetup', () => {
|
|
772
|
+
const mockRecurring = {
|
|
773
|
+
interval: 'day',
|
|
774
|
+
interval_count: 1,
|
|
775
|
+
usage_type: 'licensed',
|
|
776
|
+
} as any;
|
|
777
|
+
|
|
778
|
+
it('should return normal setup without catchUp option', () => {
|
|
779
|
+
const previousPeriodEnd = dayjs().subtract(1, 'day').unix();
|
|
780
|
+
const result = getSubscriptionCycleSetup(mockRecurring, previousPeriodEnd);
|
|
781
|
+
|
|
782
|
+
expect(result.missedPeriods).toBe(0);
|
|
783
|
+
expect(result.period.start).toBe(previousPeriodEnd);
|
|
784
|
+
expect(result.recovery).toBeUndefined();
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
it('should detect and calculate missed periods with catchUp enabled', () => {
|
|
788
|
+
const previousPeriodEnd = dayjs().subtract(5, 'days').unix(); // 5 days ago
|
|
789
|
+
const result = getSubscriptionCycleSetup(mockRecurring, previousPeriodEnd, {
|
|
790
|
+
catchUp: true,
|
|
791
|
+
maxMissedPeriods: 10,
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
expect(result.missedPeriods).toBeGreaterThan(0);
|
|
795
|
+
expect(result.recovery).toBeDefined();
|
|
796
|
+
expect(result.recovery?.originalPeriodEnd).toBe(previousPeriodEnd);
|
|
797
|
+
expect(result.recovery?.periodsSkipped).toBeGreaterThan(0);
|
|
798
|
+
expect(result.period.start).toBeGreaterThan(previousPeriodEnd);
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
it('should limit missed periods to maxMissedPeriods', () => {
|
|
802
|
+
const previousPeriodEnd = dayjs().subtract(100, 'days').unix(); // Very old
|
|
803
|
+
const result = getSubscriptionCycleSetup(mockRecurring, previousPeriodEnd, {
|
|
804
|
+
catchUp: true,
|
|
805
|
+
maxMissedPeriods: 10,
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
expect(result.missedPeriods).toBe(10);
|
|
809
|
+
expect(result.recovery?.wasLimited).toBe(true);
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
it('should handle future period end without catchUp', () => {
|
|
813
|
+
const futurePeriodEnd = dayjs().add(1, 'day').unix();
|
|
814
|
+
const result = getSubscriptionCycleSetup(mockRecurring, futurePeriodEnd, {
|
|
815
|
+
catchUp: true,
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
expect(result.missedPeriods).toBe(0);
|
|
819
|
+
expect(result.period.start).toBe(futurePeriodEnd);
|
|
820
|
+
});
|
|
821
|
+
});
|
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.
|
|
17
|
+
version: 1.19.1
|
|
18
18
|
logo: logo.png
|
|
19
19
|
files:
|
|
20
20
|
- dist
|
|
@@ -169,3 +169,11 @@ events:
|
|
|
169
169
|
description: Refund has been successfully processed and completed
|
|
170
170
|
- type: manual.notification
|
|
171
171
|
description: Application will send notification to user manually
|
|
172
|
+
- type: customer.credit_grant.granted
|
|
173
|
+
description: Credit grant has been successfully granted
|
|
174
|
+
- type: customer.credit_grant.low_balance
|
|
175
|
+
description: Credit grant has low balance
|
|
176
|
+
- type: customer.credit_grant.depleted
|
|
177
|
+
description: Credit grant has been depleted
|
|
178
|
+
- type: customer.credit.insufficient
|
|
179
|
+
description: Customer has insufficient credit
|
package/package.json
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payment-kit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.19.1",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "blocklet dev --open",
|
|
6
|
-
"eject": "vite eject",
|
|
7
6
|
"lint": "tsc --noEmit && eslint src api/src --ext .mjs,.js,.jsx,.ts,.tsx",
|
|
8
7
|
"lint:fix": "pnpm run lint --fix",
|
|
9
8
|
"format": "prettier -w src",
|
|
@@ -47,116 +46,116 @@
|
|
|
47
46
|
"@abtnode/cron": "^1.16.44",
|
|
48
47
|
"@arcblock/did": "^1.20.14",
|
|
49
48
|
"@arcblock/did-auth-storage-nedb": "^1.7.1",
|
|
50
|
-
"@arcblock/did-connect": "^
|
|
49
|
+
"@arcblock/did-connect": "^3.0.1",
|
|
51
50
|
"@arcblock/did-util": "^1.20.14",
|
|
52
51
|
"@arcblock/jwt": "^1.20.14",
|
|
53
|
-
"@arcblock/ux": "^
|
|
52
|
+
"@arcblock/ux": "^3.0.1",
|
|
54
53
|
"@arcblock/validator": "^1.20.14",
|
|
55
|
-
"@blocklet/did-space-js": "^1.0.
|
|
54
|
+
"@blocklet/did-space-js": "^1.0.62",
|
|
56
55
|
"@blocklet/js-sdk": "^1.16.44",
|
|
57
56
|
"@blocklet/logger": "^1.16.44",
|
|
58
|
-
"@blocklet/payment-react": "1.
|
|
57
|
+
"@blocklet/payment-react": "1.19.1",
|
|
59
58
|
"@blocklet/sdk": "^1.16.44",
|
|
60
|
-
"@blocklet/ui-react": "^
|
|
61
|
-
"@blocklet/uploader": "^0.1.
|
|
59
|
+
"@blocklet/ui-react": "^3.0.1",
|
|
60
|
+
"@blocklet/uploader": "^0.1.97",
|
|
62
61
|
"@blocklet/xss": "^0.1.36",
|
|
63
|
-
"@mui/icons-material": "^
|
|
64
|
-
"@mui/lab": "
|
|
65
|
-
"@mui/material": "^
|
|
66
|
-
"@mui/system": "^
|
|
62
|
+
"@mui/icons-material": "^7.1.2",
|
|
63
|
+
"@mui/lab": "7.0.0-beta.14",
|
|
64
|
+
"@mui/material": "^7.1.2",
|
|
65
|
+
"@mui/system": "^7.1.1",
|
|
67
66
|
"@ocap/asset": "^1.20.14",
|
|
68
67
|
"@ocap/client": "^1.20.14",
|
|
69
68
|
"@ocap/mcrypto": "^1.20.14",
|
|
70
69
|
"@ocap/util": "^1.20.14",
|
|
71
70
|
"@ocap/wallet": "^1.20.14",
|
|
72
|
-
"@stripe/react-stripe-js": "^2.
|
|
71
|
+
"@stripe/react-stripe-js": "^2.9.0",
|
|
73
72
|
"@stripe/stripe-js": "^2.4.0",
|
|
74
|
-
"ahooks": "^3.8.
|
|
75
|
-
"axios": "^1.
|
|
76
|
-
"body-parser": "^1.20.
|
|
73
|
+
"ahooks": "^3.8.5",
|
|
74
|
+
"axios": "^1.10.0",
|
|
75
|
+
"body-parser": "^1.20.3",
|
|
77
76
|
"cls-hooked": "^4.2.2",
|
|
78
|
-
"cookie-parser": "^1.4.
|
|
77
|
+
"cookie-parser": "^1.4.7",
|
|
79
78
|
"copy-to-clipboard": "^3.3.3",
|
|
80
79
|
"cors": "^2.8.5",
|
|
81
80
|
"date-fns": "^3.6.0",
|
|
82
|
-
"dayjs": "^1.11.
|
|
83
|
-
"debug": "^4.
|
|
81
|
+
"dayjs": "^1.11.13",
|
|
82
|
+
"debug": "^4.4.1",
|
|
84
83
|
"dotenv-flow": "^3.3.0",
|
|
85
|
-
"ethers": "^6.
|
|
86
|
-
"express": "^4.
|
|
84
|
+
"ethers": "^6.14.4",
|
|
85
|
+
"express": "^4.21.2",
|
|
87
86
|
"express-async-errors": "^3.1.1",
|
|
88
87
|
"express-history-api-fallback": "^2.2.1",
|
|
89
|
-
"fastq": "^1.
|
|
88
|
+
"fastq": "^1.19.1",
|
|
90
89
|
"flat": "^5.0.2",
|
|
91
|
-
"google-libphonenumber": "^3.2.
|
|
90
|
+
"google-libphonenumber": "^3.2.42",
|
|
92
91
|
"html2canvas": "^1.4.1",
|
|
93
92
|
"iframe-resizer-react": "^1.1.1",
|
|
94
|
-
"joi": "
|
|
95
|
-
"json-stable-stringify": "^1.
|
|
93
|
+
"joi": "17.12.2",
|
|
94
|
+
"json-stable-stringify": "^1.3.0",
|
|
96
95
|
"jspdf": "^2.5.2",
|
|
97
96
|
"lodash": "^4.17.21",
|
|
98
97
|
"morgan": "^1.10.0",
|
|
99
98
|
"mui-daterange-picker": "^1.0.5",
|
|
100
|
-
"nanoid": "3",
|
|
99
|
+
"nanoid": "^3.3.11",
|
|
101
100
|
"p-all": "3.0.0",
|
|
102
|
-
"p-wait-for": "3",
|
|
101
|
+
"p-wait-for": "^3.2.0",
|
|
103
102
|
"pretty-ms-i18n": "^1.0.3",
|
|
104
|
-
"react": "
|
|
105
|
-
"react-dom": "
|
|
106
|
-
"react-error-boundary": "^4.
|
|
107
|
-
"react-hook-form": "^7.
|
|
103
|
+
"react": "^19.1.0",
|
|
104
|
+
"react-dom": "^19.1.0",
|
|
105
|
+
"react-error-boundary": "^4.1.2",
|
|
106
|
+
"react-hook-form": "^7.58.1",
|
|
108
107
|
"react-international-phone": "^3.1.2",
|
|
109
|
-
"react-router-dom": "^6.
|
|
110
|
-
"recharts": "^2.
|
|
108
|
+
"react-router-dom": "^6.30.1",
|
|
109
|
+
"recharts": "^2.15.4",
|
|
111
110
|
"rimraf": "^3.0.2",
|
|
112
|
-
"sequelize": "^6.37.
|
|
111
|
+
"sequelize": "^6.37.7",
|
|
113
112
|
"sql-where-parser": "^2.2.1",
|
|
114
113
|
"sqlite3": "^5.1.7",
|
|
115
114
|
"stripe": "^13.11.0",
|
|
116
|
-
"typewriter-effect": "^2.
|
|
115
|
+
"typewriter-effect": "^2.22.0",
|
|
117
116
|
"ufo": "^1.6.1",
|
|
118
|
-
"umzug": "^3.8.
|
|
117
|
+
"umzug": "^3.8.2",
|
|
119
118
|
"use-bus": "^2.5.2",
|
|
120
|
-
"validator": "^13.
|
|
119
|
+
"validator": "^13.15.15",
|
|
121
120
|
"web3": "^4.16.0"
|
|
122
121
|
},
|
|
123
122
|
"devDependencies": {
|
|
124
123
|
"@abtnode/types": "^1.16.44",
|
|
125
124
|
"@arcblock/eslint-config-ts": "^0.3.3",
|
|
126
|
-
"@blocklet/payment-types": "1.
|
|
127
|
-
"@types/cookie-parser": "^1.4.
|
|
128
|
-
"@types/cors": "^2.8.
|
|
125
|
+
"@blocklet/payment-types": "1.19.1",
|
|
126
|
+
"@types/cookie-parser": "^1.4.9",
|
|
127
|
+
"@types/cors": "^2.8.19",
|
|
129
128
|
"@types/debug": "^4.1.12",
|
|
130
129
|
"@types/dotenv-flow": "^3.3.3",
|
|
131
|
-
"@types/express": "^4.17.
|
|
132
|
-
"@types/node": "^18.19.
|
|
133
|
-
"@types/react": "^18.3.
|
|
134
|
-
"@types/react-dom": "^18.3.
|
|
135
|
-
"@vitejs/plugin-react": "^4.
|
|
130
|
+
"@types/express": "^4.17.23",
|
|
131
|
+
"@types/node": "^18.19.112",
|
|
132
|
+
"@types/react": "^18.3.23",
|
|
133
|
+
"@types/react-dom": "^18.3.7",
|
|
134
|
+
"@vitejs/plugin-react": "^4.6.0",
|
|
136
135
|
"babel-plugin-lodash": "^3.3.4",
|
|
137
136
|
"bumpp": "^8.2.1",
|
|
138
137
|
"cross-env": "^7.0.3",
|
|
139
|
-
"eslint": "^8.57.
|
|
138
|
+
"eslint": "^8.57.1",
|
|
140
139
|
"import-sort-style-module": "^6.0.0",
|
|
141
140
|
"jest": "^29.7.0",
|
|
142
141
|
"lint-staged": "^12.5.0",
|
|
143
142
|
"nodemon": "^2.0.22",
|
|
144
143
|
"npm-run-all": "^4.1.5",
|
|
145
|
-
"prettier": "^3.
|
|
144
|
+
"prettier": "^3.6.0",
|
|
146
145
|
"prettier-plugin-import-sort": "^0.0.7",
|
|
147
|
-
"rollup-plugin-visualizer": "^
|
|
148
|
-
"ts-jest": "^29.
|
|
146
|
+
"rollup-plugin-visualizer": "^6.0.3",
|
|
147
|
+
"ts-jest": "^29.4.0",
|
|
149
148
|
"ts-node": "^10.9.2",
|
|
150
|
-
"tsx": "^4.
|
|
151
|
-
"type-fest": "^4.
|
|
152
|
-
"typescript": "
|
|
153
|
-
"vite": "^
|
|
154
|
-
"vite-node": "^2.
|
|
149
|
+
"tsx": "^4.20.3",
|
|
150
|
+
"type-fest": "^4.41.0",
|
|
151
|
+
"typescript": "5.5.4",
|
|
152
|
+
"vite": "^7.0.0",
|
|
153
|
+
"vite-node": "^3.2.4",
|
|
155
154
|
"vite-plugin-babel-import": "^2.0.5",
|
|
156
155
|
"vite-plugin-blocklet": "^0.9.33",
|
|
157
|
-
"vite-plugin-node-polyfills": "^0.
|
|
158
|
-
"vite-plugin-svgr": "^4.
|
|
159
|
-
"vite-tsconfig-paths": "^
|
|
156
|
+
"vite-plugin-node-polyfills": "^0.23.0",
|
|
157
|
+
"vite-plugin-svgr": "^4.3.0",
|
|
158
|
+
"vite-tsconfig-paths": "^5.1.4",
|
|
160
159
|
"zx": "^7.2.3"
|
|
161
160
|
},
|
|
162
161
|
"importSort": {
|
|
@@ -169,5 +168,5 @@
|
|
|
169
168
|
"parser": "typescript"
|
|
170
169
|
}
|
|
171
170
|
},
|
|
172
|
-
"gitHead": "
|
|
171
|
+
"gitHead": "48d5719c8ce4e89a16f8dd576ff8f72072e3909e"
|
|
173
172
|
}
|
package/scripts/sdk.js
CHANGED
|
@@ -548,17 +548,249 @@ const subscriptionModule = {
|
|
|
548
548
|
},
|
|
549
549
|
};
|
|
550
550
|
|
|
551
|
+
const meterModule = {
|
|
552
|
+
async createMeter() {
|
|
553
|
+
const meter = await payment.meters.create({
|
|
554
|
+
name: 'API Calls',
|
|
555
|
+
event_name: 'api_calls',
|
|
556
|
+
aggregation_method: 'sum',
|
|
557
|
+
unit: 'Token',
|
|
558
|
+
description: 'Track API usage',
|
|
559
|
+
component_did: 'zNKtcX5QyTxfM51osn5W8GMWvzabBREW2abA',
|
|
560
|
+
created_via: 'api',
|
|
561
|
+
});
|
|
562
|
+
console.log('Created meter:', meter);
|
|
563
|
+
return meter;
|
|
564
|
+
},
|
|
565
|
+
|
|
566
|
+
// 创建基于 Meter 的订阅产品和价格
|
|
567
|
+
async createMeteredSubscription() {
|
|
568
|
+
let meter;
|
|
569
|
+
try {
|
|
570
|
+
meter = await payment.meters.retrieve('api_calls');
|
|
571
|
+
if (!meter) {
|
|
572
|
+
meter = await this.createMeter();
|
|
573
|
+
}
|
|
574
|
+
} catch (error) {
|
|
575
|
+
meter = await this.createMeter();
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// 创建订阅产品
|
|
579
|
+
const subscriptionProduct = await payment.products.create({
|
|
580
|
+
name: 'API Call Service',
|
|
581
|
+
description: 'API Call Service',
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
// 创建基于 meter 的订阅价格
|
|
585
|
+
const subscriptionPrice = await payment.prices.create({
|
|
586
|
+
product_id: subscriptionProduct.id,
|
|
587
|
+
currency_id: meter.currency_id,
|
|
588
|
+
unit_amount: '1',
|
|
589
|
+
type: 'recurring',
|
|
590
|
+
recurring: {
|
|
591
|
+
interval: 'month',
|
|
592
|
+
interval_count: 1,
|
|
593
|
+
usage_type: 'metered',
|
|
594
|
+
meter_id: meter.id,
|
|
595
|
+
aggregate_usage: 'sum',
|
|
596
|
+
},
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
console.log('subscriptionPrice', subscriptionPrice);
|
|
600
|
+
// 创建 checkout session
|
|
601
|
+
const subscriptionCheckout = await payment.checkout.sessions.create({
|
|
602
|
+
success_url: 'https://audiobook.com/success?session_id={CHECKOUT_SESSION_ID}',
|
|
603
|
+
cancel_url: 'https://audiobook.com/cancel',
|
|
604
|
+
mode: 'subscription',
|
|
605
|
+
line_items: [
|
|
606
|
+
{
|
|
607
|
+
price_id: subscriptionPrice.id,
|
|
608
|
+
quantity: 1,
|
|
609
|
+
},
|
|
610
|
+
],
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
console.log('Metered subscription checkout:', subscriptionCheckout);
|
|
614
|
+
return { meter, subscriptionProduct, subscriptionPrice, subscriptionCheckout };
|
|
615
|
+
},
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
// 新增 Credit 相关模块
|
|
619
|
+
const creditModule = {
|
|
620
|
+
// 创建 Credit 产品
|
|
621
|
+
async createCreditProduct(meter) {
|
|
622
|
+
const creditProduct = await payment.products.create({
|
|
623
|
+
name: 'API Calls Credit',
|
|
624
|
+
description: 'API Call Credit, we can ...',
|
|
625
|
+
type: 'credit',
|
|
626
|
+
prices: [
|
|
627
|
+
{
|
|
628
|
+
type: 'one_time',
|
|
629
|
+
unit_amount: '0.01',
|
|
630
|
+
currency_id: 'pc_ByBkyhRQmedm',
|
|
631
|
+
currency_options: [
|
|
632
|
+
{ currency_id: 'pc_ByBkyhRQmedm', unit_amount: '0.01' },
|
|
633
|
+
{ currency_id: 'pc_Dp4lY5ejkALH', unit_amount: '0.01' },
|
|
634
|
+
],
|
|
635
|
+
lookup_key: 'api_call_credit_per_unit',
|
|
636
|
+
nickname: 'Per Unit Credit For API Call',
|
|
637
|
+
metadata: {
|
|
638
|
+
credit_config: {
|
|
639
|
+
priority: 50,
|
|
640
|
+
valid_duration_value: 0,
|
|
641
|
+
valid_duration_unit: 'days',
|
|
642
|
+
currency_id: meter.currency_id,
|
|
643
|
+
credit_amount: '1',
|
|
644
|
+
},
|
|
645
|
+
meter_id: meter.id,
|
|
646
|
+
},
|
|
647
|
+
},
|
|
648
|
+
{
|
|
649
|
+
type: 'one_time',
|
|
650
|
+
unit_amount: '8',
|
|
651
|
+
currency_id: 'pc_ByBkyhRQmedm',
|
|
652
|
+
lookup_key: 'api_calls_1000_pack',
|
|
653
|
+
nickname: '1000 Credits Pack For API Call',
|
|
654
|
+
currency_options: [
|
|
655
|
+
{ currency_id: 'pc_ByBkyhRQmedm', unit_amount: '8' },
|
|
656
|
+
{ currency_id: 'pc_Dp4lY5ejkALH', unit_amount: '8' },
|
|
657
|
+
],
|
|
658
|
+
metadata: {
|
|
659
|
+
credit_config: {
|
|
660
|
+
priority: 50,
|
|
661
|
+
valid_duration_value: 0,
|
|
662
|
+
valid_duration_unit: 'days',
|
|
663
|
+
currency_id: meter.currency_id,
|
|
664
|
+
credit_amount: '1000',
|
|
665
|
+
},
|
|
666
|
+
meter_id: meter.id,
|
|
667
|
+
},
|
|
668
|
+
},
|
|
669
|
+
],
|
|
670
|
+
});
|
|
671
|
+
console.log('Created credit product:', creditProduct.id);
|
|
672
|
+
console.log(
|
|
673
|
+
'Available prices:',
|
|
674
|
+
creditProduct.prices.map((p) => p.lookup_key)
|
|
675
|
+
);
|
|
676
|
+
return creditProduct;
|
|
677
|
+
},
|
|
678
|
+
|
|
679
|
+
// 创建支付链接
|
|
680
|
+
async createPaymentLinks() {
|
|
681
|
+
const perUnitPrice = await payment.prices.retrieve('api_call_credit_per_unit');
|
|
682
|
+
console.log('perUnitPrice', perUnitPrice);
|
|
683
|
+
// 1. 按分钟购买
|
|
684
|
+
const flexiblePaymentLink = await payment.paymentLinks.create({
|
|
685
|
+
name: 'Per Unit Credit Link',
|
|
686
|
+
currency_id: perUnitPrice.currency_id,
|
|
687
|
+
line_items: [
|
|
688
|
+
{
|
|
689
|
+
price_id: perUnitPrice.id,
|
|
690
|
+
quantity: 100,
|
|
691
|
+
adjustable_quantity: {
|
|
692
|
+
enabled: true,
|
|
693
|
+
minimum: 10,
|
|
694
|
+
maximum: 10000,
|
|
695
|
+
},
|
|
696
|
+
},
|
|
697
|
+
],
|
|
698
|
+
metadata: {
|
|
699
|
+
credit_purchase: 'true',
|
|
700
|
+
},
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
const packPrice = await payment.prices.retrieve('api_calls_1000_pack');
|
|
704
|
+
console.log('packPrice', packPrice);
|
|
705
|
+
// 2. 固定套餐包
|
|
706
|
+
const packagePaymentLink = await payment.paymentLinks.create({
|
|
707
|
+
name: 'Package Credit Link',
|
|
708
|
+
currency_id: packPrice.currency_id,
|
|
709
|
+
line_items: [
|
|
710
|
+
{
|
|
711
|
+
price_id: packPrice.id,
|
|
712
|
+
quantity: 1,
|
|
713
|
+
},
|
|
714
|
+
],
|
|
715
|
+
metadata: {
|
|
716
|
+
credit_purchase: 'true',
|
|
717
|
+
},
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
console.log('Flexible payment link:', flexiblePaymentLink);
|
|
721
|
+
console.log('Package payment link:', packagePaymentLink);
|
|
722
|
+
return { flexiblePaymentLink, packagePaymentLink };
|
|
723
|
+
},
|
|
724
|
+
|
|
725
|
+
// 创建/赠送 Credit Grant
|
|
726
|
+
async createCreditGrant(meter) {
|
|
727
|
+
// 方式2: 通用 Credit Grant
|
|
728
|
+
const universalCredit = await payment.creditGrants.create({
|
|
729
|
+
customer_id: 'cus_xga1PZSmiDZfz0',
|
|
730
|
+
amount: 50,
|
|
731
|
+
currency_id: meter.currency_id,
|
|
732
|
+
applicability_config: {
|
|
733
|
+
scope: {
|
|
734
|
+
price_type: 'metered',
|
|
735
|
+
},
|
|
736
|
+
},
|
|
737
|
+
category: 'promotional',
|
|
738
|
+
name: '听书时长充值',
|
|
739
|
+
metadata: {
|
|
740
|
+
purchase_source: 'mobile_app',
|
|
741
|
+
},
|
|
742
|
+
});
|
|
743
|
+
console.log('Universal credit grant:', universalCredit);
|
|
744
|
+
return { universalCredit };
|
|
745
|
+
},
|
|
746
|
+
|
|
747
|
+
// 上报 Credit 消耗用量
|
|
748
|
+
async reportUsage(customerId, minutes, sessionContext) {
|
|
749
|
+
const creditBalance = await payment.creditGrants.summary({
|
|
750
|
+
customer_id: customerId,
|
|
751
|
+
});
|
|
752
|
+
console.log('creditBalance', creditBalance);
|
|
753
|
+
const meterEvent = await payment.meterEvents.create({
|
|
754
|
+
event_name: 'api_calls',
|
|
755
|
+
timestamp: Math.floor(Date.now() / 1000),
|
|
756
|
+
payload: {
|
|
757
|
+
customer_id: customerId,
|
|
758
|
+
value: String(minutes),
|
|
759
|
+
subscription_id: sessionContext.subscriptionId || undefined,
|
|
760
|
+
},
|
|
761
|
+
identifier: `${customerId}_${sessionContext.sessionId}_${Date.now()}`,
|
|
762
|
+
metadata: {
|
|
763
|
+
session_id: sessionContext.sessionId,
|
|
764
|
+
},
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
return {
|
|
768
|
+
event_id: meterEvent.id,
|
|
769
|
+
identifier: meterEvent.identifier,
|
|
770
|
+
timestamp: meterEvent.timestamp,
|
|
771
|
+
};
|
|
772
|
+
},
|
|
773
|
+
};
|
|
774
|
+
|
|
551
775
|
const testModules = {
|
|
552
776
|
checkout: checkoutModule,
|
|
553
777
|
payment: paymentModule,
|
|
554
778
|
product: productModule,
|
|
555
779
|
subscription: subscriptionModule,
|
|
780
|
+
meter: meterModule,
|
|
781
|
+
credit: creditModule,
|
|
556
782
|
};
|
|
557
783
|
|
|
558
784
|
// 测试入口
|
|
559
785
|
async function runTest() {
|
|
560
786
|
payment.environments.setTestMode(true);
|
|
561
|
-
await testModules.
|
|
787
|
+
const { meter } = await testModules.meter.createMeteredSubscription();
|
|
788
|
+
// await testModules.credit.createCreditProduct(meter);
|
|
789
|
+
// await testModules.credit.createPaymentLinks();
|
|
790
|
+
// await testModules.credit.createCreditGrant(meter);
|
|
791
|
+
await testModules.credit.reportUsage('cus_xga1PZSmiDZfz0', 2, {
|
|
792
|
+
sessionId: 'session_123',
|
|
793
|
+
});
|
|
562
794
|
}
|
|
563
795
|
|
|
564
796
|
async function main() {
|