mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-20 11:36:25 +08:00
chore: cleanup legacy logic (#15072)
This commit is contained in:
@@ -27,14 +27,6 @@ declare global {
|
||||
payment: {
|
||||
enabled: boolean;
|
||||
showLifetimePrice: boolean;
|
||||
/**
|
||||
* @deprecated use payment.stripe.apiKey
|
||||
*/
|
||||
apiKey: string;
|
||||
/**
|
||||
* @deprecated use payment.stripe.webhookKey
|
||||
*/
|
||||
webhookKey: string;
|
||||
stripe: ConfigItem<
|
||||
{
|
||||
/** Preferred place for Stripe API key */
|
||||
@@ -70,16 +62,6 @@ defineModuleConfig('payment', {
|
||||
desc: 'Whether enable lifetime price and allow user to pay for it.',
|
||||
default: true,
|
||||
},
|
||||
apiKey: {
|
||||
desc: '[Deprecated] Stripe API key. Use payment.stripe.apiKey instead.',
|
||||
default: '',
|
||||
env: 'STRIPE_API_KEY',
|
||||
},
|
||||
webhookKey: {
|
||||
desc: '[Deprecated] Stripe webhook key. Use payment.stripe.webhookKey instead.',
|
||||
default: '',
|
||||
env: 'STRIPE_WEBHOOK_KEY',
|
||||
},
|
||||
stripe: {
|
||||
desc: 'Stripe sdk options and credentials',
|
||||
default: {
|
||||
|
||||
@@ -19,9 +19,7 @@ export class StripeWebhookController {
|
||||
@Public()
|
||||
@Post('/webhook')
|
||||
async handleWebhook(@Req() req: RawBodyRequest<Request>) {
|
||||
const nestedWebhookKey = this.config.payment.stripe?.webhookKey;
|
||||
const legacyWebhookKey = this.config.payment.webhookKey;
|
||||
const webhookKey = nestedWebhookKey || legacyWebhookKey || '';
|
||||
const webhookKey = this.config.payment.stripe?.webhookKey || '';
|
||||
// Retrieve the event by verifying the signature using the raw body and secret.
|
||||
const signature = req.headers['stripe-signature'];
|
||||
try {
|
||||
|
||||
@@ -256,19 +256,21 @@ export abstract class SubscriptionManager {
|
||||
}
|
||||
|
||||
async getPrice(lookupKey: LookupKey): Promise<KnownStripePrice | null> {
|
||||
let key: string;
|
||||
try {
|
||||
key = encodeLookupKey(lookupKey);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
const prices = await this.stripe.prices.list({
|
||||
lookup_keys: [encodeLookupKey(lookupKey)],
|
||||
lookup_keys: [key],
|
||||
limit: 1,
|
||||
});
|
||||
|
||||
const price = prices.data[0];
|
||||
|
||||
return price
|
||||
? {
|
||||
lookupKey,
|
||||
price,
|
||||
}
|
||||
: null;
|
||||
return price ? { lookupKey, price } : null;
|
||||
}
|
||||
|
||||
protected async getCouponFromPromotionCode(
|
||||
|
||||
@@ -7,23 +7,18 @@ import { z } from 'zod';
|
||||
import {
|
||||
Config,
|
||||
EventBus,
|
||||
InternalServerError,
|
||||
InvalidCheckoutParameters,
|
||||
ManagedByAppStoreOrPlay,
|
||||
Mutex,
|
||||
OneMonth,
|
||||
OnEvent,
|
||||
OneYear,
|
||||
SubscriptionAlreadyExists,
|
||||
SubscriptionPlanNotFound,
|
||||
TooManyRequest,
|
||||
URLHelper,
|
||||
} from '../../../base';
|
||||
import { EntitlementService } from '../../../core/entitlement';
|
||||
import { EarlyAccessType, FeatureService } from '../../../core/features';
|
||||
import { StripeFactory } from '../stripe';
|
||||
import {
|
||||
CouponType,
|
||||
KnownStripeInvoice,
|
||||
KnownStripePrice,
|
||||
KnownStripeSubscription,
|
||||
@@ -32,7 +27,6 @@ import {
|
||||
SubscriptionPlan,
|
||||
SubscriptionRecurring,
|
||||
SubscriptionStatus,
|
||||
SubscriptionVariant,
|
||||
} from '../types';
|
||||
import {
|
||||
activeSubscriptionWhere,
|
||||
@@ -42,11 +36,8 @@ import {
|
||||
} from './common';
|
||||
|
||||
interface PriceStrategyStatus {
|
||||
proEarlyAccess: boolean;
|
||||
aiEarlyAccess: boolean;
|
||||
proSubscribed: boolean;
|
||||
aiSubscribed: boolean;
|
||||
onetime: boolean;
|
||||
}
|
||||
|
||||
export const UserSubscriptionIdentity = z.object({
|
||||
@@ -67,7 +58,6 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
||||
stripeProvider: StripeFactory,
|
||||
db: PrismaClient,
|
||||
private readonly config: Config,
|
||||
private readonly feature: FeatureService,
|
||||
private readonly event: EventBus,
|
||||
private readonly url: URLHelper,
|
||||
private readonly mutex: Mutex,
|
||||
@@ -78,22 +68,12 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
||||
|
||||
async filterPrices(
|
||||
prices: KnownStripePrice[],
|
||||
customer?: UserStripeCustomer
|
||||
_customer?: UserStripeCustomer
|
||||
) {
|
||||
const strategyStatus = customer
|
||||
? await this.strategyStatus(customer)
|
||||
: {
|
||||
proEarlyAccess: false,
|
||||
aiEarlyAccess: false,
|
||||
proSubscribed: false,
|
||||
aiSubscribed: false,
|
||||
onetime: false,
|
||||
};
|
||||
|
||||
const availablePrices: KnownStripePrice[] = [];
|
||||
|
||||
for (const price of prices) {
|
||||
if (await this.isPriceAvailable(price, strategyStatus)) {
|
||||
if (await this.isPriceAvailable(price)) {
|
||||
availablePrices.push(price);
|
||||
}
|
||||
}
|
||||
@@ -107,8 +87,9 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
||||
{ user }: z.infer<typeof UserSubscriptionCheckoutArgs>
|
||||
) {
|
||||
if (
|
||||
lookupKey.plan !== SubscriptionPlan.Pro &&
|
||||
lookupKey.plan !== SubscriptionPlan.AI
|
||||
(lookupKey.plan !== SubscriptionPlan.Pro &&
|
||||
lookupKey.plan !== SubscriptionPlan.AI) ||
|
||||
lookupKey.variant !== null
|
||||
) {
|
||||
throw new InvalidCheckoutParameters();
|
||||
}
|
||||
@@ -125,15 +106,8 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
||||
active &&
|
||||
// do not allow to re-subscribe unless
|
||||
!(
|
||||
/* current subscription is a onetime subscription and so as the one that's checking out */
|
||||
(
|
||||
(active.variant === SubscriptionVariant.Onetime &&
|
||||
lookupKey.variant === SubscriptionVariant.Onetime) ||
|
||||
/* current subscription is normal subscription and is checking-out a lifetime subscription */
|
||||
(active.recurring !== SubscriptionRecurring.Lifetime &&
|
||||
active.variant !== SubscriptionVariant.Onetime &&
|
||||
lookupKey.recurring === SubscriptionRecurring.Lifetime)
|
||||
)
|
||||
active.recurring !== SubscriptionRecurring.Lifetime &&
|
||||
lookupKey.recurring === SubscriptionRecurring.Lifetime
|
||||
)
|
||||
) {
|
||||
throw new SubscriptionAlreadyExists({ plan: lookupKey.plan });
|
||||
@@ -141,12 +115,9 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
||||
|
||||
const customer = await this.getOrCreateCustomer(user.id);
|
||||
const strategy = await this.strategyStatus(customer);
|
||||
const price = await this.autoPrice(lookupKey, strategy);
|
||||
const price = await this.getPrice(lookupKey);
|
||||
|
||||
if (
|
||||
!price ||
|
||||
!(await this.isPriceAvailable(price, { ...strategy, onetime: true }))
|
||||
) {
|
||||
if (!price || !(await this.isPriceAvailable(price))) {
|
||||
throw new SubscriptionPlanNotFound({
|
||||
plan: lookupKey.plan,
|
||||
recurring: lookupKey.recurring,
|
||||
@@ -154,10 +125,7 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
||||
}
|
||||
|
||||
const discounts = await (async () => {
|
||||
const coupon = await this.getBuildInCoupon(customer, price);
|
||||
if (coupon) {
|
||||
return { discounts: [{ coupon }] };
|
||||
} else if (params.coupon) {
|
||||
if (params.coupon) {
|
||||
const couponId = await this.getCouponFromPromotionCode(
|
||||
params.coupon,
|
||||
customer
|
||||
@@ -179,10 +147,9 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
||||
return undefined;
|
||||
})();
|
||||
|
||||
// mode: 'subscription' or 'payment' for lifetime and onetime payment
|
||||
// mode: 'subscription' or 'payment' for lifetime payment
|
||||
const mode =
|
||||
lookupKey.recurring === SubscriptionRecurring.Lifetime ||
|
||||
lookupKey.variant === SubscriptionVariant.Onetime
|
||||
lookupKey.recurring === SubscriptionRecurring.Lifetime
|
||||
? {
|
||||
mode: 'payment' as const,
|
||||
invoice_creation: {
|
||||
@@ -339,37 +306,6 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
||||
});
|
||||
}
|
||||
|
||||
private async getBuildInCoupon(
|
||||
customer: UserStripeCustomer,
|
||||
price: KnownStripePrice
|
||||
) {
|
||||
const strategyStatus = await this.strategyStatus(customer);
|
||||
|
||||
// onetime price is allowed for checkout
|
||||
strategyStatus.onetime = true;
|
||||
|
||||
if (!(await this.isPriceAvailable(price, strategyStatus))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let coupon: CouponType | undefined;
|
||||
|
||||
if (price.lookupKey.variant === SubscriptionVariant.EA) {
|
||||
if (price.lookupKey.plan === SubscriptionPlan.Pro) {
|
||||
coupon = CouponType.ProEarlyAccessOneYearFree;
|
||||
} else if (price.lookupKey.plan === SubscriptionPlan.AI) {
|
||||
coupon = CouponType.AIEarlyAccessOneYearFree;
|
||||
}
|
||||
} else if (price.lookupKey.plan === SubscriptionPlan.AI) {
|
||||
const { proEarlyAccess, aiSubscribed } = strategyStatus;
|
||||
if (proEarlyAccess && !aiSubscribed) {
|
||||
coupon = CouponType.ProEarlyAccessAIOneYearFree;
|
||||
}
|
||||
}
|
||||
|
||||
return coupon;
|
||||
}
|
||||
|
||||
async saveInvoice(knownInvoice: KnownStripeInvoice) {
|
||||
const { userId, lookupKey, stripeInvoice } = knownInvoice;
|
||||
this.assertUserIdExists(userId);
|
||||
@@ -387,11 +323,11 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
||||
},
|
||||
});
|
||||
|
||||
// onetime and lifetime subscription is a special "subscription" that doesn't get involved with stripe subscription system
|
||||
// we track the deals by invoice only.
|
||||
// Lifetime subscription does not get involved with the Stripe subscription system.
|
||||
// We track the deal by invoice only.
|
||||
if (stripeInvoice.status === 'paid') {
|
||||
await using lock = await this.mutex.acquire(
|
||||
`redeem-onetime-subscription:${stripeInvoice.id}`
|
||||
`redeem-lifetime-subscription:${stripeInvoice.id}`
|
||||
);
|
||||
|
||||
if (!lock) {
|
||||
@@ -400,8 +336,6 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
||||
|
||||
if (lookupKey.recurring === SubscriptionRecurring.Lifetime) {
|
||||
await this.saveLifetimeSubscription(knownInvoice);
|
||||
} else if (lookupKey.variant === SubscriptionVariant.Onetime) {
|
||||
await this.saveOnetimePaymentSubscription(knownInvoice);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -470,106 +404,7 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
||||
});
|
||||
}
|
||||
|
||||
async saveOnetimePaymentSubscription(knownInvoice: KnownStripeInvoice) {
|
||||
this.assertUserIdExists(knownInvoice.userId);
|
||||
const { userId, lookupKey, stripeInvoice } = knownInvoice;
|
||||
|
||||
const invoice = await this.db.invoice.findUnique({
|
||||
where: {
|
||||
stripeInvoiceId: stripeInvoice.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (!invoice) {
|
||||
// never happens
|
||||
throw new InternalServerError('Invoice not found');
|
||||
}
|
||||
|
||||
if (invoice.onetimeSubscriptionRedeemed) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.db.invoice.update({
|
||||
select: {
|
||||
onetimeSubscriptionRedeemed: true,
|
||||
},
|
||||
where: {
|
||||
stripeInvoiceId: stripeInvoice.id,
|
||||
},
|
||||
data: { onetimeSubscriptionRedeemed: true },
|
||||
});
|
||||
|
||||
const existingSubscription = await this.db.subscription.findUnique({
|
||||
where: {
|
||||
targetId_plan: {
|
||||
targetId: userId,
|
||||
plan: lookupKey.plan,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const subscriptionTime =
|
||||
lookupKey.recurring === SubscriptionRecurring.Monthly
|
||||
? OneMonth
|
||||
: OneYear;
|
||||
|
||||
let subscription: Subscription;
|
||||
|
||||
// extends the subscription time if exists
|
||||
if (existingSubscription) {
|
||||
if (!existingSubscription.end) {
|
||||
throw new InternalServerError(
|
||||
'Unexpected onetime subscription with no end date'
|
||||
);
|
||||
}
|
||||
|
||||
const period =
|
||||
// expired, reset the period
|
||||
existingSubscription.end <= new Date()
|
||||
? {
|
||||
start: new Date(),
|
||||
end: new Date(Date.now() + subscriptionTime),
|
||||
}
|
||||
: {
|
||||
end: new Date(
|
||||
existingSubscription.end.getTime() + subscriptionTime
|
||||
),
|
||||
};
|
||||
|
||||
subscription = await this.db.subscription.update({
|
||||
where: {
|
||||
id: existingSubscription.id,
|
||||
},
|
||||
data: period,
|
||||
});
|
||||
} else {
|
||||
subscription = await this.db.subscription.create({
|
||||
data: {
|
||||
targetId: userId,
|
||||
stripeSubscriptionId: null,
|
||||
...lookupKey,
|
||||
start: new Date(),
|
||||
end: new Date(Date.now() + subscriptionTime),
|
||||
status: SubscriptionStatus.Active,
|
||||
nextBillAt: null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
this.event.emit('user.subscription.activated', {
|
||||
userId,
|
||||
plan: lookupKey.plan,
|
||||
recurring: lookupKey.recurring,
|
||||
});
|
||||
await this.entitlement.upsertFromCloudSubscription({
|
||||
...subscription,
|
||||
targetId: userId,
|
||||
});
|
||||
|
||||
return subscription;
|
||||
}
|
||||
|
||||
async revokeOnetimeOrLifetime(knownInvoice: KnownStripeInvoice) {
|
||||
async revokeLifetime(knownInvoice: KnownStripeInvoice) {
|
||||
this.assertUserIdExists(knownInvoice.userId);
|
||||
const { userId, lookupKey } = knownInvoice;
|
||||
|
||||
@@ -609,7 +444,7 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
||||
});
|
||||
}
|
||||
|
||||
async restoreOnetimeOrLifetime(knownInvoice: KnownStripeInvoice) {
|
||||
async restoreLifetime(knownInvoice: KnownStripeInvoice) {
|
||||
this.assertUserIdExists(knownInvoice.userId);
|
||||
const { userId, lookupKey, stripeInvoice } = knownInvoice;
|
||||
|
||||
@@ -627,18 +462,6 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
||||
? stripeInvoice.created
|
||||
: Date.now() / 1000);
|
||||
|
||||
let end: Date | null = null;
|
||||
|
||||
if (lookupKey.recurring === SubscriptionRecurring.Lifetime) {
|
||||
end = null;
|
||||
} else if (lookupKey.variant === SubscriptionVariant.Onetime) {
|
||||
const isMonthly = lookupKey.recurring === SubscriptionRecurring.Monthly;
|
||||
const duration = isMonthly ? OneMonth : OneYear;
|
||||
end = subscription?.end ?? new Date(start * 1000 + duration);
|
||||
} else {
|
||||
end = subscription?.end ?? null;
|
||||
}
|
||||
|
||||
if (subscription) {
|
||||
const saved = await this.db.subscription.update({
|
||||
where: { id: subscription.id },
|
||||
@@ -647,7 +470,7 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
||||
canceledAt: null,
|
||||
nextBillAt: null,
|
||||
start: subscription.start ?? new Date(start * 1000),
|
||||
end,
|
||||
end: null,
|
||||
},
|
||||
});
|
||||
await this.entitlement.upsertFromCloudSubscription(saved);
|
||||
@@ -658,7 +481,7 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
||||
stripeSubscriptionId: null,
|
||||
...lookupKey,
|
||||
start: new Date(start * 1000),
|
||||
end,
|
||||
end: null,
|
||||
status: SubscriptionStatus.Active,
|
||||
nextBillAt: null,
|
||||
},
|
||||
@@ -673,111 +496,43 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
||||
});
|
||||
}
|
||||
|
||||
private async autoPrice(lookupKey: LookupKey, strategy: PriceStrategyStatus) {
|
||||
// auto select ea variant when available if not specified
|
||||
let variant: SubscriptionVariant | null = lookupKey.variant;
|
||||
|
||||
if (!variant) {
|
||||
// make the if conditions separated, more readable
|
||||
// pro early access
|
||||
if (
|
||||
lookupKey.plan === SubscriptionPlan.Pro &&
|
||||
lookupKey.recurring === SubscriptionRecurring.Yearly &&
|
||||
strategy.proEarlyAccess &&
|
||||
!strategy.proSubscribed
|
||||
) {
|
||||
variant = SubscriptionVariant.EA;
|
||||
}
|
||||
|
||||
// ai early access
|
||||
if (
|
||||
lookupKey.plan === SubscriptionPlan.AI &&
|
||||
lookupKey.recurring === SubscriptionRecurring.Yearly &&
|
||||
strategy.aiEarlyAccess &&
|
||||
!strategy.aiSubscribed
|
||||
) {
|
||||
variant = SubscriptionVariant.EA;
|
||||
}
|
||||
}
|
||||
|
||||
return this.getPrice({
|
||||
plan: lookupKey.plan,
|
||||
recurring: lookupKey.recurring,
|
||||
variant,
|
||||
});
|
||||
}
|
||||
|
||||
private async isPriceAvailable(
|
||||
price: KnownStripePrice,
|
||||
strategy: PriceStrategyStatus
|
||||
) {
|
||||
private async isPriceAvailable(price: KnownStripePrice) {
|
||||
if (price.lookupKey.plan === SubscriptionPlan.Pro) {
|
||||
return this.isProPriceAvailable(price, strategy);
|
||||
return this.isProPriceAvailable(price);
|
||||
}
|
||||
|
||||
if (price.lookupKey.plan === SubscriptionPlan.AI) {
|
||||
return this.isAIPriceAvailable(price, strategy);
|
||||
return this.isAIPriceAvailable(price);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private async isProPriceAvailable(
|
||||
{ lookupKey }: KnownStripePrice,
|
||||
{ proEarlyAccess, proSubscribed, onetime }: PriceStrategyStatus
|
||||
) {
|
||||
private async isProPriceAvailable({ lookupKey }: KnownStripePrice) {
|
||||
if (lookupKey.recurring === SubscriptionRecurring.Lifetime) {
|
||||
return this.config.payment.showLifetimePrice;
|
||||
}
|
||||
|
||||
if (lookupKey.variant === SubscriptionVariant.Onetime) {
|
||||
return onetime;
|
||||
}
|
||||
|
||||
// no special price for monthly plan
|
||||
if (lookupKey.recurring === SubscriptionRecurring.Monthly) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// show EA price instead of normal price if early access is available
|
||||
return proEarlyAccess && !proSubscribed
|
||||
? lookupKey.variant === SubscriptionVariant.EA
|
||||
: lookupKey.variant !== SubscriptionVariant.EA;
|
||||
return lookupKey.variant === null;
|
||||
}
|
||||
|
||||
private async isAIPriceAvailable(
|
||||
{ lookupKey }: KnownStripePrice,
|
||||
{ aiEarlyAccess, aiSubscribed, onetime }: PriceStrategyStatus
|
||||
) {
|
||||
private async isAIPriceAvailable({ lookupKey }: KnownStripePrice) {
|
||||
// no lifetime price for AI
|
||||
if (lookupKey.recurring === SubscriptionRecurring.Lifetime) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// never show onetime prices
|
||||
if (lookupKey.variant === SubscriptionVariant.Onetime) {
|
||||
return onetime;
|
||||
}
|
||||
|
||||
// show EA price instead of normal price if early access is available
|
||||
return aiEarlyAccess && !aiSubscribed
|
||||
? lookupKey.variant === SubscriptionVariant.EA
|
||||
: lookupKey.variant !== SubscriptionVariant.EA;
|
||||
return lookupKey.variant === null;
|
||||
}
|
||||
|
||||
private async strategyStatus(
|
||||
customer: UserStripeCustomer
|
||||
): Promise<PriceStrategyStatus> {
|
||||
const proEarlyAccess = await this.feature.isEarlyAccessUser(
|
||||
customer.userId,
|
||||
EarlyAccessType.App
|
||||
);
|
||||
|
||||
const aiEarlyAccess = await this.feature.isEarlyAccessUser(
|
||||
customer.userId,
|
||||
EarlyAccessType.AI
|
||||
);
|
||||
|
||||
let proSubscribed = false;
|
||||
let aiSubscribed = false;
|
||||
|
||||
@@ -786,8 +541,6 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
||||
status: 'all',
|
||||
});
|
||||
|
||||
// if the early access user had early access subscription in the past, but it got canceled or past due,
|
||||
// the user will lose the early access privilege
|
||||
for (const sub of subscriptions.data) {
|
||||
const lookupKey = retriveLookupKeyFromStripeSubscription(sub);
|
||||
if (!lookupKey) {
|
||||
@@ -803,13 +556,7 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
proEarlyAccess,
|
||||
aiEarlyAccess,
|
||||
proSubscribed,
|
||||
aiSubscribed,
|
||||
onetime: false,
|
||||
};
|
||||
return { proSubscribed, aiSubscribed };
|
||||
}
|
||||
|
||||
private assertUserIdExists(
|
||||
|
||||
@@ -166,14 +166,6 @@ export class InvoiceType implements Partial<Invoice> {
|
||||
@Field(() => Date)
|
||||
updatedAt!: Date;
|
||||
|
||||
// deprecated fields
|
||||
@Field(() => String, {
|
||||
name: 'id',
|
||||
nullable: true,
|
||||
deprecationReason: 'removed',
|
||||
})
|
||||
stripeInvoiceId?: string;
|
||||
|
||||
@Field(() => SubscriptionPlan, {
|
||||
nullable: true,
|
||||
deprecationReason: 'removed',
|
||||
@@ -475,12 +467,7 @@ export class UserSubscriptionResolver {
|
||||
) {}
|
||||
|
||||
private normalizeSubscription(s: Subscription) {
|
||||
if (
|
||||
s.variant &&
|
||||
![SubscriptionVariant.EA, SubscriptionVariant.Onetime].includes(
|
||||
s.variant as SubscriptionVariant
|
||||
)
|
||||
) {
|
||||
if (s.variant && s.variant !== SubscriptionVariant.Onetime) {
|
||||
s.variant = null;
|
||||
}
|
||||
return s;
|
||||
|
||||
@@ -55,7 +55,6 @@ import {
|
||||
SubscriptionPlan,
|
||||
SubscriptionRecurring,
|
||||
SubscriptionStatus,
|
||||
SubscriptionVariant,
|
||||
} from './types';
|
||||
|
||||
export const CheckoutExtraArgs = z.union([
|
||||
@@ -536,9 +535,8 @@ export class SubscriptionService {
|
||||
reason === 'dispute_open' ||
|
||||
reason === 'dispute_lost';
|
||||
const restore = reason === 'dispute_won';
|
||||
const isOneTimeOrLifetime =
|
||||
knownInvoice.lookupKey.recurring === SubscriptionRecurring.Lifetime ||
|
||||
knownInvoice.lookupKey.variant === SubscriptionVariant.Onetime;
|
||||
const isLifetime =
|
||||
knownInvoice.lookupKey.recurring === SubscriptionRecurring.Lifetime;
|
||||
|
||||
if (restore) {
|
||||
if (invoice.subscription) {
|
||||
@@ -564,11 +562,11 @@ export class SubscriptionService {
|
||||
}
|
||||
|
||||
if (
|
||||
isOneTimeOrLifetime &&
|
||||
isLifetime &&
|
||||
(knownInvoice.lookupKey.plan === SubscriptionPlan.Pro ||
|
||||
knownInvoice.lookupKey.plan === SubscriptionPlan.AI)
|
||||
) {
|
||||
await this.userManager.restoreOnetimeOrLifetime(knownInvoice);
|
||||
await this.userManager.restoreLifetime(knownInvoice);
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -615,11 +613,11 @@ export class SubscriptionService {
|
||||
}
|
||||
|
||||
if (
|
||||
isOneTimeOrLifetime &&
|
||||
isLifetime &&
|
||||
(knownInvoice.lookupKey.plan === SubscriptionPlan.Pro ||
|
||||
knownInvoice.lookupKey.plan === SubscriptionPlan.AI)
|
||||
) {
|
||||
await this.userManager.revokeOnetimeOrLifetime(knownInvoice);
|
||||
await this.userManager.revokeLifetime(knownInvoice);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
decodeLookupKey,
|
||||
DEFAULT_PRICES,
|
||||
SubscriptionRecurring,
|
||||
SubscriptionVariant,
|
||||
} from './types';
|
||||
|
||||
@Injectable()
|
||||
@@ -39,20 +38,18 @@ export class StripeFactory {
|
||||
}
|
||||
|
||||
setup() {
|
||||
// Prefer new keys under payment.stripe.*, fallback to legacy root keys for backward compatibility
|
||||
const {
|
||||
apiKey: nestedApiKey,
|
||||
apiKey,
|
||||
webhookKey: _,
|
||||
...config
|
||||
} = this.config.payment.stripe || {};
|
||||
// NOTE:
|
||||
// we always fake a key if not set because `new Stripe` will complain if it's empty string
|
||||
// this will make code cleaner than providing `Stripe` instance as optional one.
|
||||
const apiKey =
|
||||
nestedApiKey || this.config.payment.apiKey || 'stripe-api-key';
|
||||
const stripeApiKey = apiKey || 'stripe-api-key';
|
||||
|
||||
// TODO@(@darkskygit): use per-requests api key injection
|
||||
this.#stripe = new Stripe(apiKey, config);
|
||||
this.#stripe = new Stripe(stripeApiKey, config);
|
||||
if (this.config.payment.enabled) {
|
||||
this.server.enableFeature(ServerFeature.Payment);
|
||||
} else {
|
||||
@@ -107,8 +104,7 @@ export class StripeFactory {
|
||||
lookup_key: key,
|
||||
tax_behavior: 'inclusive',
|
||||
recurring:
|
||||
lookupKey.recurring === SubscriptionRecurring.Lifetime ||
|
||||
lookupKey.variant === SubscriptionVariant.Onetime
|
||||
lookupKey.recurring === SubscriptionRecurring.Lifetime
|
||||
? undefined
|
||||
: {
|
||||
interval:
|
||||
|
||||
@@ -20,7 +20,6 @@ export enum SubscriptionPlan {
|
||||
}
|
||||
|
||||
export enum SubscriptionVariant {
|
||||
EA = 'earlyaccess',
|
||||
Onetime = 'onetime',
|
||||
}
|
||||
|
||||
@@ -44,12 +43,6 @@ export enum InvoiceStatus {
|
||||
Uncollectible = 'uncollectible',
|
||||
}
|
||||
|
||||
export enum CouponType {
|
||||
ProEarlyAccessOneYearFree = 'pro_ea_one_year_free',
|
||||
AIEarlyAccessOneYearFree = 'ai_ea_one_year_free',
|
||||
ProEarlyAccessAIOneYearFree = 'ai_pro_ea_one_year_free',
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Events {
|
||||
'user.subscription.activated': {
|
||||
@@ -199,11 +192,6 @@ export const DEFAULT_PRICES = new Map([
|
||||
price: 8100,
|
||||
},
|
||||
],
|
||||
// only EA for yearly pro
|
||||
[
|
||||
`${SubscriptionPlan.Pro}_${SubscriptionRecurring.Yearly}_${SubscriptionVariant.EA}`,
|
||||
{ product: 'AFFiNE Pro', price: 5000 },
|
||||
],
|
||||
[
|
||||
`${SubscriptionPlan.Pro}_${SubscriptionRecurring.Lifetime}`,
|
||||
{
|
||||
@@ -211,29 +199,11 @@ export const DEFAULT_PRICES = new Map([
|
||||
price: 49900,
|
||||
},
|
||||
],
|
||||
[
|
||||
`${SubscriptionPlan.Pro}_${SubscriptionRecurring.Monthly}_${SubscriptionVariant.Onetime}`,
|
||||
{ product: 'AFFiNE Pro - One Month', price: 799 },
|
||||
],
|
||||
[
|
||||
`${SubscriptionPlan.Pro}_${SubscriptionRecurring.Yearly}_${SubscriptionVariant.Onetime}`,
|
||||
{ product: 'AFFiNE Pro - One Year', price: 8100 },
|
||||
],
|
||||
|
||||
// ai
|
||||
[
|
||||
`${SubscriptionPlan.AI}_${SubscriptionRecurring.Yearly}`,
|
||||
{ product: 'AFFiNE AI', price: 10680 },
|
||||
],
|
||||
// only EA for yearly AI
|
||||
[
|
||||
`${SubscriptionPlan.AI}_${SubscriptionRecurring.Yearly}_${SubscriptionVariant.EA}`,
|
||||
{ product: 'AFFiNE AI', price: 9900 },
|
||||
],
|
||||
[
|
||||
`${SubscriptionPlan.AI}_${SubscriptionRecurring.Yearly}_${SubscriptionVariant.Onetime}`,
|
||||
{ product: 'AFFiNE AI - One Year', price: 10680 },
|
||||
],
|
||||
|
||||
// team
|
||||
[
|
||||
@@ -284,7 +254,7 @@ export function decodeLookupKey(key: string): LookupKey {
|
||||
return {
|
||||
plan: plan as SubscriptionPlan,
|
||||
recurring: recurring as SubscriptionRecurring,
|
||||
variant: variant as SubscriptionVariant,
|
||||
variant: (variant as SubscriptionVariant) ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user