diff --git a/packages/backend/server/src/plugins/payment/resolver.ts b/packages/backend/server/src/plugins/payment/resolver.ts index e3f5462d9..7fcb95c2f 100644 --- a/packages/backend/server/src/plugins/payment/resolver.ts +++ b/packages/backend/server/src/plugins/payment/resolver.ts @@ -1,8 +1,4 @@ -import { - BadGatewayException, - ForbiddenException, - InternalServerErrorException, -} from '@nestjs/common'; +import { BadGatewayException, ForbiddenException } from '@nestjs/common'; import { Args, Context, @@ -48,11 +44,11 @@ class SubscriptionPrice { @Field() currency!: string; - @Field() - amount!: number; + @Field(() => Int, { nullable: true }) + amount?: number | null; - @Field() - yearlyAmount!: number; + @Field(() => Int, { nullable: true }) + yearlyAmount?: number | null; } @ObjectType('UserSubscription') @@ -176,64 +172,39 @@ export class SubscriptionResolver { } ); - return Object.entries(group).map(([plan, prices]) => { - const yearly = prices.find( - price => - decodeLookupKey( - // @ts-expect-error empty lookup key is filtered out - price.lookup_key - )[1] === SubscriptionRecurring.Yearly - ); - const monthly = prices.find( - price => - decodeLookupKey( - // @ts-expect-error empty lookup key is filtered out - price.lookup_key - )[1] === SubscriptionRecurring.Monthly - ); + function findPrice(plan: SubscriptionPlan) { + const prices = group[plan]; - if (!yearly || !monthly) { - throw new InternalServerErrorException( - 'The prices are not configured correctly.' - ); + if (!prices) { + return null; } + const monthlyPrice = prices.find(p => p.recurring?.interval === 'month'); + const yearlyPrice = prices.find(p => p.recurring?.interval === 'year'); + const currency = monthlyPrice?.currency ?? yearlyPrice?.currency ?? 'usd'; return { - type: 'fixed', - plan: plan as SubscriptionPlan, - currency: monthly.currency, - amount: monthly.unit_amount ?? 0, - yearlyAmount: yearly.unit_amount ?? 0, + currency, + amount: monthlyPrice?.unit_amount, + yearlyAmount: yearlyPrice?.unit_amount, }; - }); - } - - /** - * @deprecated - */ - @Mutation(() => String, { - deprecationReason: 'use `createCheckoutSession` instead', - description: 'Create a subscription checkout link of stripe', - }) - async checkout( - @CurrentUser() user: CurrentUser, - @Args({ name: 'recurring', type: () => SubscriptionRecurring }) - recurring: SubscriptionRecurring, - @Args('idempotencyKey') idempotencyKey: string - ) { - const session = await this.service.createCheckoutSession({ - user, - plan: SubscriptionPlan.Pro, - recurring, - redirectUrl: `${this.config.baseUrl}/upgrade-success`, - idempotencyKey, - }); - - if (!session.url) { - throw new BadGatewayException('Failed to create checkout session.'); } - return session.url; + // extend it when new plans are added + const fixedPlans = [SubscriptionPlan.Pro]; + + return fixedPlans.reduce((prices, plan) => { + const price = findPrice(plan); + + if (price && (price.amount || price.yearlyAmount)) { + prices.push({ + type: 'fixed', + plan, + ...price, + }); + } + + return prices; + }, [] as SubscriptionPrice[]); } @Mutation(() => String, { diff --git a/packages/backend/server/src/plugins/payment/service.ts b/packages/backend/server/src/plugins/payment/service.ts index a23fe51c8..57728032a 100644 --- a/packages/backend/server/src/plugins/payment/service.ts +++ b/packages/backend/server/src/plugins/payment/service.ts @@ -65,7 +65,9 @@ export class SubscriptionService { ) {} async listPrices() { - return this.stripe.prices.list(); + return this.stripe.prices.list({ + active: true, + }); } async createCheckoutSession({ diff --git a/packages/backend/server/src/schema.gql b/packages/backend/server/src/schema.gql index 2d18df9e2..fd605ad8b 100644 --- a/packages/backend/server/src/schema.gql +++ b/packages/backend/server/src/schema.gql @@ -114,9 +114,6 @@ type Mutation { changeEmail(email: String!, token: String!): UserType! changePassword(newPassword: String!, token: String!): UserType! - """Create a subscription checkout link of stripe""" - checkout(idempotencyKey: String!, recurring: SubscriptionRecurring!): String! @deprecated(reason: "use `createCheckoutSession` instead") - """Create a subscription checkout link of stripe""" createCheckoutSession(input: CreateCheckoutSessionInput!): String! @@ -275,11 +272,11 @@ enum SubscriptionPlan { } type SubscriptionPrice { - amount: Int! + amount: Int currency: String! plan: SubscriptionPlan! type: String! - yearlyAmount: Int! + yearlyAmount: Int } enum SubscriptionRecurring { diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/billing/index.tsx b/packages/frontend/core/src/components/affine/setting-modal/general-setting/billing/index.tsx index 4fb381ee2..1ec3e0d40 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/billing/index.tsx +++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/billing/index.tsx @@ -108,8 +108,8 @@ const SubscriptionSettings = () => { ? '0' : price ? recurring === SubscriptionRecurring.Monthly - ? String(price.amount / 100) - : String(price.yearlyAmount / 100) + ? String((price.amount ?? 0) / 100) + : String((price.yearlyAmount ?? 0) / 100) : '?'; const t = useAFFiNEI18N(); diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/index.tsx b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/index.tsx index a4c495837..efd6b25f2 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/index.tsx +++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/index.tsx @@ -51,11 +51,14 @@ const Settings = () => { const detail = planDetail.get(price.plan); if (detail?.type === 'fixed') { - detail.price = (price.amount / 100).toFixed(2); - detail.yearlyPrice = (price.yearlyAmount / 100 / 12).toFixed(2); - detail.discount = Math.floor( - (1 - price.yearlyAmount / 12 / price.amount) * 100 - ).toString(); + detail.price = ((price.amount ?? 0) / 100).toFixed(2); + detail.yearlyPrice = ((price.yearlyAmount ?? 0) / 100 / 12).toFixed(2); + detail.discount = + price.yearlyAmount && price.amount + ? Math.floor( + (1 - price.yearlyAmount / 12 / price.amount) * 100 + ).toString() + : undefined; } }); diff --git a/packages/frontend/graphql/src/schema.ts b/packages/frontend/graphql/src/schema.ts index 2675c9ed6..288f58d08 100644 --- a/packages/frontend/graphql/src/schema.ts +++ b/packages/frontend/graphql/src/schema.ts @@ -538,8 +538,8 @@ export type PricesQuery = { type: string; plan: SubscriptionPlan; currency: string; - amount: number; - yearlyAmount: number; + amount: number | null; + yearlyAmount: number | null; }>; };