feat(server): add lifetime plan flag (#7443)

This commit is contained in:
forehalo
2024-07-08 07:41:30 +00:00
parent de91027852
commit 9ef8829ef1
3 changed files with 72 additions and 3 deletions
@@ -1,6 +1,10 @@
import type { Stripe } from 'stripe';
import { defineStartupConfig, ModuleConfig } from '../../fundamentals/config';
import {
defineRuntimeConfig,
defineStartupConfig,
ModuleConfig,
} from '../../fundamentals/config';
export interface PaymentStartupConfig {
stripe?: {
@@ -11,10 +15,20 @@ export interface PaymentStartupConfig {
} & Stripe.StripeConfig;
}
export interface PaymentRuntimeConfig {
showLifetimePrice: boolean;
}
declare module '../config' {
interface PluginsConfig {
payment: ModuleConfig<PaymentStartupConfig>;
payment: ModuleConfig<PaymentStartupConfig, PaymentRuntimeConfig>;
}
}
defineStartupConfig('plugins.payment', {});
defineRuntimeConfig('plugins.payment', {
showLifetimePrice: {
desc: 'Whether enable lifetime price and allow user to pay for it.',
default: false,
},
});
@@ -121,8 +121,14 @@ export class SubscriptionService {
});
}
const lifetimePriceEnabled = await this.config.runtime.fetch(
'plugins.payment/showLifetimePrice'
);
const list = await this.stripe.prices.list({
active: true,
// only list recurring prices if lifetime price is not enabled
...(lifetimePriceEnabled ? {} : { type: 'recurring' }),
});
return list.data.filter(price => {
@@ -807,6 +813,16 @@ export class SubscriptionService {
recurring: SubscriptionRecurring,
variant?: SubscriptionPriceVariant
): Promise<string> {
if (recurring === SubscriptionRecurring.Lifetime) {
const lifetimePriceEnabled = await this.config.runtime.fetch(
'plugins.payment/showLifetimePrice'
);
if (!lifetimePriceEnabled) {
throw new ActionForbidden();
}
}
const prices = await this.stripe.prices.list({
lookup_keys: [encodeLookupKey(plan, recurring, variant)],
});
@@ -14,7 +14,7 @@ import {
FeatureManagementService,
} from '../../src/core/features';
import { EventEmitter } from '../../src/fundamentals';
import { ConfigModule } from '../../src/fundamentals/config';
import { Config, ConfigModule } from '../../src/fundamentals/config';
import {
CouponType,
encodeLookupKey,
@@ -969,6 +969,45 @@ const invoice: Stripe.Invoice = {
},
};
test('should not be able to checkout for lifetime recurring if not enabled', async t => {
const { service, stripe, u1 } = t.context;
Sinon.stub(stripe.subscriptions, 'list').resolves({ data: [] } as any);
await t.throwsAsync(
() =>
service.createCheckoutSession({
user: u1,
plan: SubscriptionPlan.Pro,
recurring: SubscriptionRecurring.Lifetime,
redirectUrl: '',
idempotencyKey: '',
}),
{ message: 'You are not allowed to perform this action.' }
);
});
test('should be able to checkout for lifetime recurring', async t => {
const { service, stripe, u1, app } = t.context;
const config = app.get(Config);
await config.runtime.set('plugins.payment/showLifetimePrice', true);
Sinon.stub(stripe.subscriptions, 'list').resolves({ data: [] } as any);
Sinon.stub(stripe.prices, 'list').resolves({
data: [PRICES[PRO_LIFETIME]],
} as any);
const sessionStub = Sinon.stub(stripe.checkout.sessions, 'create');
await service.createCheckoutSession({
user: u1,
plan: SubscriptionPlan.Pro,
recurring: SubscriptionRecurring.Lifetime,
redirectUrl: '',
idempotencyKey: '',
});
t.true(sessionStub.calledOnce);
});
test('should be able to subscribe to lifetime recurring', async t => {
// lifetime payment isn't a subscription, so we need to trigger the creation by invoice payment event
const { service, stripe, db, u1, event } = t.context;