mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-02 18:09:58 +08:00
fix(server): should auto apply ea price for users (#9082)
This commit is contained in:
@@ -66,7 +66,7 @@ export abstract class SubscriptionManager {
|
|||||||
): KnownStripePrice[] | Promise<KnownStripePrice[]>;
|
): KnownStripePrice[] | Promise<KnownStripePrice[]>;
|
||||||
|
|
||||||
abstract checkout(
|
abstract checkout(
|
||||||
price: KnownStripePrice,
|
lookupKey: LookupKey,
|
||||||
params: z.infer<typeof CheckoutParams>,
|
params: z.infer<typeof CheckoutParams>,
|
||||||
args: any
|
args: any
|
||||||
): Promise<Stripe.Checkout.Session>;
|
): Promise<Stripe.Checkout.Session>;
|
||||||
@@ -206,9 +206,7 @@ export abstract class SubscriptionManager {
|
|||||||
return customer;
|
return customer;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async getPrice(
|
async getPrice(lookupKey: LookupKey): Promise<KnownStripePrice | null> {
|
||||||
lookupKey: LookupKey
|
|
||||||
): Promise<KnownStripePrice | null> {
|
|
||||||
const prices = await this.stripe.prices.list({
|
const prices = await this.stripe.prices.list({
|
||||||
lookup_keys: [encodeLookupKey(lookupKey)],
|
lookup_keys: [encodeLookupKey(lookupKey)],
|
||||||
limit: 1,
|
limit: 1,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
Config,
|
Config,
|
||||||
EventEmitter,
|
EventEmitter,
|
||||||
InternalServerError,
|
InternalServerError,
|
||||||
|
InvalidCheckoutParameters,
|
||||||
SubscriptionAlreadyExists,
|
SubscriptionAlreadyExists,
|
||||||
SubscriptionPlanNotFound,
|
SubscriptionPlanNotFound,
|
||||||
URLHelper,
|
URLHelper,
|
||||||
@@ -21,6 +22,7 @@ import {
|
|||||||
KnownStripeInvoice,
|
KnownStripeInvoice,
|
||||||
KnownStripePrice,
|
KnownStripePrice,
|
||||||
KnownStripeSubscription,
|
KnownStripeSubscription,
|
||||||
|
LookupKey,
|
||||||
retriveLookupKeyFromStripeSubscription,
|
retriveLookupKeyFromStripeSubscription,
|
||||||
SubscriptionPlan,
|
SubscriptionPlan,
|
||||||
SubscriptionRecurring,
|
SubscriptionRecurring,
|
||||||
@@ -88,15 +90,20 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async checkout(
|
async checkout(
|
||||||
price: KnownStripePrice,
|
lookupKey: LookupKey,
|
||||||
params: z.infer<typeof CheckoutParams>,
|
params: z.infer<typeof CheckoutParams>,
|
||||||
{ user }: z.infer<typeof UserSubscriptionCheckoutArgs>
|
{ user }: z.infer<typeof UserSubscriptionCheckoutArgs>
|
||||||
) {
|
) {
|
||||||
const lookupKey = price.lookupKey;
|
if (
|
||||||
|
lookupKey.plan !== SubscriptionPlan.Pro &&
|
||||||
|
lookupKey.plan !== SubscriptionPlan.AI
|
||||||
|
) {
|
||||||
|
throw new InvalidCheckoutParameters();
|
||||||
|
}
|
||||||
|
|
||||||
const subscription = await this.getSubscription({
|
const subscription = await this.getSubscription({
|
||||||
// @ts-expect-error filtered already
|
plan: lookupKey.plan,
|
||||||
plan: price.lookupKey.plan,
|
userId: user.id,
|
||||||
user,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -119,12 +126,12 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
|||||||
|
|
||||||
const customer = await this.getOrCreateCustomer(user.id);
|
const customer = await this.getOrCreateCustomer(user.id);
|
||||||
const strategy = await this.strategyStatus(customer);
|
const strategy = await this.strategyStatus(customer);
|
||||||
const available = await this.isPriceAvailable(price, {
|
const price = await this.autoPrice(lookupKey, strategy);
|
||||||
...strategy,
|
|
||||||
onetime: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!available) {
|
if (
|
||||||
|
!price ||
|
||||||
|
!(await this.isPriceAvailable(price, { ...strategy, onetime: true }))
|
||||||
|
) {
|
||||||
throw new SubscriptionPlanNotFound({
|
throw new SubscriptionPlanNotFound({
|
||||||
plan: lookupKey.plan,
|
plan: lookupKey.plan,
|
||||||
recurring: lookupKey.recurring,
|
recurring: lookupKey.recurring,
|
||||||
@@ -564,6 +571,40 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
|||||||
return subscription;
|
return subscription;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(
|
private async isPriceAvailable(
|
||||||
price: KnownStripePrice,
|
price: KnownStripePrice,
|
||||||
strategy: PriceStrategyStatus
|
strategy: PriceStrategyStatus
|
||||||
|
|||||||
@@ -9,12 +9,14 @@ import {
|
|||||||
type EventPayload,
|
type EventPayload,
|
||||||
OnEvent,
|
OnEvent,
|
||||||
SubscriptionAlreadyExists,
|
SubscriptionAlreadyExists,
|
||||||
|
SubscriptionPlanNotFound,
|
||||||
URLHelper,
|
URLHelper,
|
||||||
} from '../../../fundamentals';
|
} from '../../../fundamentals';
|
||||||
import {
|
import {
|
||||||
KnownStripeInvoice,
|
KnownStripeInvoice,
|
||||||
KnownStripePrice,
|
KnownStripePrice,
|
||||||
KnownStripeSubscription,
|
KnownStripeSubscription,
|
||||||
|
LookupKey,
|
||||||
retriveLookupKeyFromStripeSubscription,
|
retriveLookupKeyFromStripeSubscription,
|
||||||
SubscriptionPlan,
|
SubscriptionPlan,
|
||||||
SubscriptionRecurring,
|
SubscriptionRecurring,
|
||||||
@@ -62,7 +64,7 @@ export class WorkspaceSubscriptionManager extends SubscriptionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async checkout(
|
async checkout(
|
||||||
{ price }: KnownStripePrice,
|
lookupKey: LookupKey,
|
||||||
params: z.infer<typeof CheckoutParams>,
|
params: z.infer<typeof CheckoutParams>,
|
||||||
args: z.infer<typeof WorkspaceSubscriptionCheckoutArgs>
|
args: z.infer<typeof WorkspaceSubscriptionCheckoutArgs>
|
||||||
) {
|
) {
|
||||||
@@ -75,6 +77,15 @@ export class WorkspaceSubscriptionManager extends SubscriptionManager {
|
|||||||
throw new SubscriptionAlreadyExists({ plan: SubscriptionPlan.Team });
|
throw new SubscriptionAlreadyExists({ plan: SubscriptionPlan.Team });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const price = await this.getPrice(lookupKey);
|
||||||
|
|
||||||
|
if (!price) {
|
||||||
|
throw new SubscriptionPlanNotFound({
|
||||||
|
plan: lookupKey.plan,
|
||||||
|
recurring: lookupKey.recurring,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const customer = await this.getOrCreateCustomer(args.user.id);
|
const customer = await this.getOrCreateCustomer(args.user.id);
|
||||||
|
|
||||||
const discounts = await (async () => {
|
const discounts = await (async () => {
|
||||||
@@ -102,7 +113,7 @@ export class WorkspaceSubscriptionManager extends SubscriptionManager {
|
|||||||
return this.stripe.checkout.sessions.create({
|
return this.stripe.checkout.sessions.create({
|
||||||
line_items: [
|
line_items: [
|
||||||
{
|
{
|
||||||
price: price.id,
|
price: price.price.id,
|
||||||
quantity: count,
|
quantity: count,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -42,11 +42,9 @@ import { ScheduleManager } from './schedule';
|
|||||||
import {
|
import {
|
||||||
decodeLookupKey,
|
decodeLookupKey,
|
||||||
DEFAULT_PRICES,
|
DEFAULT_PRICES,
|
||||||
encodeLookupKey,
|
|
||||||
KnownStripeInvoice,
|
KnownStripeInvoice,
|
||||||
KnownStripePrice,
|
KnownStripePrice,
|
||||||
KnownStripeSubscription,
|
KnownStripeSubscription,
|
||||||
LookupKey,
|
|
||||||
retriveLookupKeyFromStripePrice,
|
retriveLookupKeyFromStripePrice,
|
||||||
retriveLookupKeyFromStripeSubscription,
|
retriveLookupKeyFromStripeSubscription,
|
||||||
SubscriptionPlan,
|
SubscriptionPlan,
|
||||||
@@ -129,19 +127,6 @@ export class SubscriptionService implements OnApplicationBootstrap {
|
|||||||
throw new ActionForbidden();
|
throw new ActionForbidden();
|
||||||
}
|
}
|
||||||
|
|
||||||
const price = await this.getPrice({
|
|
||||||
plan,
|
|
||||||
recurring,
|
|
||||||
variant: variant ?? null,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!price) {
|
|
||||||
throw new SubscriptionPlanNotFound({
|
|
||||||
plan,
|
|
||||||
recurring,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const manager = this.select(plan);
|
const manager = this.select(plan);
|
||||||
const result = CheckoutExtraArgs.safeParse(args);
|
const result = CheckoutExtraArgs.safeParse(args);
|
||||||
|
|
||||||
@@ -149,7 +134,15 @@ export class SubscriptionService implements OnApplicationBootstrap {
|
|||||||
throw new InvalidCheckoutParameters();
|
throw new InvalidCheckoutParameters();
|
||||||
}
|
}
|
||||||
|
|
||||||
return manager.checkout(price, params, args);
|
return manager.checkout(
|
||||||
|
{
|
||||||
|
plan,
|
||||||
|
recurring,
|
||||||
|
variant: variant ?? null,
|
||||||
|
},
|
||||||
|
params,
|
||||||
|
args
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async cancelSubscription(
|
async cancelSubscription(
|
||||||
@@ -270,7 +263,7 @@ export class SubscriptionService implements OnApplicationBootstrap {
|
|||||||
throw new SameSubscriptionRecurring({ recurring });
|
throw new SameSubscriptionRecurring({ recurring });
|
||||||
}
|
}
|
||||||
|
|
||||||
const price = await this.getPrice({
|
const price = await manager.getPrice({
|
||||||
plan: identity.plan,
|
plan: identity.plan,
|
||||||
recurring,
|
recurring,
|
||||||
variant: null,
|
variant: null,
|
||||||
@@ -469,24 +462,6 @@ export class SubscriptionService implements OnApplicationBootstrap {
|
|||||||
.filter(Boolean) as KnownStripePrice[];
|
.filter(Boolean) as KnownStripePrice[];
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getPrice(
|
|
||||||
lookupKey: LookupKey
|
|
||||||
): Promise<KnownStripePrice | null> {
|
|
||||||
const prices = await this.stripe.prices.list({
|
|
||||||
lookup_keys: [encodeLookupKey(lookupKey)],
|
|
||||||
limit: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
const price = prices.data[0];
|
|
||||||
|
|
||||||
return price
|
|
||||||
? {
|
|
||||||
lookupKey,
|
|
||||||
price,
|
|
||||||
}
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async parseStripeInvoice(
|
private async parseStripeInvoice(
|
||||||
invoice: Stripe.Invoice
|
invoice: Stripe.Invoice
|
||||||
): Promise<KnownStripeInvoice | null> {
|
): Promise<KnownStripeInvoice | null> {
|
||||||
|
|||||||
@@ -26,14 +26,14 @@ export class StripeWebhook {
|
|||||||
@OnStripeEvent('invoice.updated')
|
@OnStripeEvent('invoice.updated')
|
||||||
@OnStripeEvent('invoice.finalization_failed')
|
@OnStripeEvent('invoice.finalization_failed')
|
||||||
@OnStripeEvent('invoice.payment_failed')
|
@OnStripeEvent('invoice.payment_failed')
|
||||||
@OnStripeEvent('invoice.payment_succeeded')
|
@OnStripeEvent('invoice.paid')
|
||||||
async onInvoiceUpdated(
|
async onInvoiceUpdated(
|
||||||
event:
|
event:
|
||||||
| Stripe.InvoiceCreatedEvent
|
| Stripe.InvoiceCreatedEvent
|
||||||
| Stripe.InvoiceUpdatedEvent
|
| Stripe.InvoiceUpdatedEvent
|
||||||
| Stripe.InvoiceFinalizationFailedEvent
|
| Stripe.InvoiceFinalizationFailedEvent
|
||||||
| Stripe.InvoicePaymentFailedEvent
|
| Stripe.InvoicePaymentFailedEvent
|
||||||
| Stripe.InvoicePaymentSucceededEvent
|
| Stripe.InvoicePaidEvent
|
||||||
) {
|
) {
|
||||||
const invoice = await this.stripe.invoices.retrieve(event.data.object.id);
|
const invoice = await this.stripe.invoices.retrieve(event.data.object.id);
|
||||||
await this.service.saveStripeInvoice(invoice);
|
await this.service.saveStripeInvoice(invoice);
|
||||||
|
|||||||
@@ -476,7 +476,6 @@ test('should get correct pro plan price for checking out', async t => {
|
|||||||
{
|
{
|
||||||
plan: SubscriptionPlan.Pro,
|
plan: SubscriptionPlan.Pro,
|
||||||
recurring: SubscriptionRecurring.Yearly,
|
recurring: SubscriptionRecurring.Yearly,
|
||||||
variant: SubscriptionVariant.EA,
|
|
||||||
successCallbackLink: '',
|
successCallbackLink: '',
|
||||||
},
|
},
|
||||||
{ user: u1 }
|
{ user: u1 }
|
||||||
@@ -593,7 +592,6 @@ test('should get correct ai plan price for checking out', async t => {
|
|||||||
{
|
{
|
||||||
plan: SubscriptionPlan.AI,
|
plan: SubscriptionPlan.AI,
|
||||||
recurring: SubscriptionRecurring.Yearly,
|
recurring: SubscriptionRecurring.Yearly,
|
||||||
variant: SubscriptionVariant.EA,
|
|
||||||
successCallbackLink: '',
|
successCallbackLink: '',
|
||||||
},
|
},
|
||||||
{ user: u1 }
|
{ user: u1 }
|
||||||
|
|||||||
Reference in New Issue
Block a user