diff --git a/packages/frontend/core/src/components/affine/auth/sign-in.tsx b/packages/frontend/core/src/components/affine/auth/sign-in.tsx index 9635853be1..b53bcecc81 100644 --- a/packages/frontend/core/src/components/affine/auth/sign-in.tsx +++ b/packages/frontend/core/src/components/affine/auth/sign-in.tsx @@ -23,6 +23,7 @@ import type { AuthPanelProps } from './index'; import * as style from './style.css'; import { INTERNAL_BETA_URL, useAuth } from './use-auth'; import { Captcha, useCaptcha } from './use-captcha'; +import { useSubscriptionSearch } from './use-subscription'; function validateEmail(email: string) { return emailRegex.test(email); @@ -37,6 +38,7 @@ export const SignIn: FC = ({ const t = useAFFiNEI18N(); const loginStatus = useCurrentLoginStatus(); const [verifyToken, challenge] = useCaptcha(); + const subscriptionData = useSubscriptionSearch(); const { isMutating: isSigningIn, @@ -83,7 +85,8 @@ export const SignIn: FC = ({ if (verifyToken) { if (user) { // provider password sign-in if user has by default - if (user.hasPassword) { + // If with payment, onl support email sign in to avoid redirect to affine app + if (user.hasPassword && !subscriptionData) { setAuthState('signInWithPassword'); } else { const res = await signIn(email, verifyToken, challenge); @@ -103,6 +106,7 @@ export const SignIn: FC = ({ } } }, [ + subscriptionData, challenge, email, setAuthEmail, diff --git a/packages/frontend/core/src/components/affine/auth/subscription-redirect.tsx b/packages/frontend/core/src/components/affine/auth/subscription-redirect.tsx index 908f8eb0b0..56271fe189 100644 --- a/packages/frontend/core/src/components/affine/auth/subscription-redirect.tsx +++ b/packages/frontend/core/src/components/affine/auth/subscription-redirect.tsx @@ -3,10 +3,10 @@ import { Button } from '@affine/component/ui/button'; import { Loading } from '@affine/component/ui/loading'; import { AffineShapeIcon } from '@affine/core/components/page-list'; import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; -import type { SubscriptionRecurring } from '@affine/graphql'; +import type { SubscriptionPlan, SubscriptionRecurring } from '@affine/graphql'; import { changePasswordMutation, - checkoutMutation, + createCheckoutSessionMutation, subscriptionQuery, } from '@affine/graphql'; import { useAFFiNEI18N } from '@affine/i18n/hooks'; @@ -30,18 +30,25 @@ const usePaymentRedirect = () => { } const recurring = searchData.recurring as SubscriptionRecurring; + const plan = searchData.plan as SubscriptionPlan; + const coupon = searchData.coupon; const idempotencyKey = useMemo(() => nanoid(), []); const { trigger: checkoutSubscription } = useMutation({ - mutation: checkoutMutation, + mutation: createCheckoutSessionMutation, }); return useAsyncCallback(async () => { - const { checkout } = await checkoutSubscription({ - recurring, - idempotencyKey, + const { createCheckoutSession: checkoutUrl } = await checkoutSubscription({ + input: { + recurring, + plan, + coupon, + idempotencyKey, + successCallbackLink: null, + }, }); - window.open(checkout, '_self', 'norefferer'); - }, [recurring, idempotencyKey, checkoutSubscription]); + window.open(checkoutUrl, '_self', 'norefferer'); + }, [recurring, plan, coupon, idempotencyKey, checkoutSubscription]); }; const CenterLoading = () => { diff --git a/packages/frontend/core/src/components/affine/auth/use-subscription.ts b/packages/frontend/core/src/components/affine/auth/use-subscription.ts index b13902c56c..70fc5daf8c 100644 --- a/packages/frontend/core/src/components/affine/auth/use-subscription.ts +++ b/packages/frontend/core/src/components/affine/auth/use-subscription.ts @@ -4,6 +4,7 @@ import { useSearchParams } from 'react-router-dom'; enum SubscriptionKey { Recurring = 'subscription_recurring', Plan = 'subscription_plan', + Coupon = 'coupon', SignUp = 'sign_up', // A new user with subscription journey: signup > set password > pay in stripe > go to app Token = 'token', // When signup, there should have a token to set password } @@ -22,11 +23,13 @@ export function useSubscriptionSearch() { const recurring = searchParams.get(SubscriptionKey.Recurring); const plan = searchParams.get(SubscriptionKey.Plan); + const coupon = searchParams.get(SubscriptionKey.Coupon); const withSignUp = searchParams.get(SubscriptionKey.SignUp) === '1'; const passwordToken = searchParams.get(SubscriptionKey.Token); return { recurring, plan, + coupon, withSignUp, passwordToken, getRedirectUrl(signUp?: boolean) { @@ -35,6 +38,10 @@ export function useSubscriptionSearch() { [SubscriptionKey.Plan, plan ?? ''], ]); + if (coupon) { + paymentParams.set(SubscriptionKey.Coupon, coupon); + } + if (signUp) { paymentParams.set(SubscriptionKey.SignUp, '1'); } diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/plan-card.tsx b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/plan-card.tsx index 319dbaa688..5b4636ede5 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/plan-card.tsx +++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/plan-card.tsx @@ -6,7 +6,7 @@ import type { SubscriptionMutator, } from '@affine/core/hooks/use-subscription'; import { - checkoutMutation, + createCheckoutSessionMutation, SubscriptionPlan, SubscriptionRecurring, SubscriptionStatus, @@ -359,7 +359,7 @@ const Upgrade = ({ }) => { const t = useAFFiNEI18N(); const { isMutating, trigger } = useMutation({ - mutation: checkoutMutation, + mutation: createCheckoutSessionMutation, }); const newTabRef = useRef(null); @@ -383,13 +383,21 @@ const Upgrade = ({ newTabRef.current.focus(); } else { await trigger( - { recurring, idempotencyKey }, + { + input: { + recurring, + idempotencyKey, + plan: SubscriptionPlan.Pro, // Only support prod plan now. + coupon: null, + successCallbackLink: null, + }, + }, { onSuccess: data => { // FIXME: safari prevents from opening new tab by window api // TODO(@xp): what if electron? const newTab = window.open( - data.checkout, + data.createCheckoutSession, '_blank', 'noopener noreferrer' ); diff --git a/packages/frontend/graphql/src/graphql/create-checkout-link.gql b/packages/frontend/graphql/src/graphql/create-checkout-link.gql index 5ab82000bb..c4ccfb4eef 100644 --- a/packages/frontend/graphql/src/graphql/create-checkout-link.gql +++ b/packages/frontend/graphql/src/graphql/create-checkout-link.gql @@ -1,6 +1,3 @@ -mutation checkout( - $recurring: SubscriptionRecurring! - $idempotencyKey: String! -) { - checkout(recurring: $recurring, idempotencyKey: $idempotencyKey) +mutation createCheckoutSession($input: CreateCheckoutSessionInput!) { + createCheckoutSession(input: $input) } diff --git a/packages/frontend/graphql/src/graphql/index.ts b/packages/frontend/graphql/src/graphql/index.ts index f8b7a049cd..867b10d24e 100644 --- a/packages/frontend/graphql/src/graphql/index.ts +++ b/packages/frontend/graphql/src/graphql/index.ts @@ -127,14 +127,14 @@ mutation changePassword($token: String!, $newPassword: String!) { }`, }; -export const checkoutMutation = { - id: 'checkoutMutation' as const, - operationName: 'checkout', - definitionName: 'checkout', +export const createCheckoutSessionMutation = { + id: 'createCheckoutSessionMutation' as const, + operationName: 'createCheckoutSession', + definitionName: 'createCheckoutSession', containsFile: false, query: ` -mutation checkout($recurring: SubscriptionRecurring!, $idempotencyKey: String!) { - checkout(recurring: $recurring, idempotencyKey: $idempotencyKey) +mutation createCheckoutSession($input: CreateCheckoutSessionInput!) { + createCheckoutSession(input: $input) }`, }; diff --git a/packages/frontend/graphql/src/schema.ts b/packages/frontend/graphql/src/schema.ts index 7da2156a79..34cdb794a4 100644 --- a/packages/frontend/graphql/src/schema.ts +++ b/packages/frontend/graphql/src/schema.ts @@ -200,12 +200,14 @@ export type ChangePasswordMutation = { }; }; -export type CheckoutMutationVariables = Exact<{ - recurring: SubscriptionRecurring; - idempotencyKey: Scalars['String']['input']; +export type CreateCheckoutSessionMutationVariables = Exact<{ + input: CreateCheckoutSessionInput; }>; -export type CheckoutMutation = { __typename?: 'Mutation'; checkout: string }; +export type CreateCheckoutSessionMutation = { + __typename?: 'Mutation'; + createCheckoutSession: string; +}; export type CreateCustomerPortalMutationVariables = Exact<{ [key: string]: never; @@ -1049,9 +1051,9 @@ export type Mutations = response: ChangePasswordMutation; } | { - name: 'checkoutMutation'; - variables: CheckoutMutationVariables; - response: CheckoutMutation; + name: 'createCheckoutSessionMutation'; + variables: CreateCheckoutSessionMutationVariables; + response: CreateCheckoutSessionMutation; } | { name: 'createCustomerPortalMutation';