feat: support sign-in with subscription coupon (#5768)

This commit is contained in:
Joooye_34
2024-02-01 08:43:47 +00:00
parent fec2090de5
commit 338c3001b0
7 changed files with 56 additions and 31 deletions

View File

@@ -23,6 +23,7 @@ import type { AuthPanelProps } from './index';
import * as style from './style.css'; import * as style from './style.css';
import { INTERNAL_BETA_URL, useAuth } from './use-auth'; import { INTERNAL_BETA_URL, useAuth } from './use-auth';
import { Captcha, useCaptcha } from './use-captcha'; import { Captcha, useCaptcha } from './use-captcha';
import { useSubscriptionSearch } from './use-subscription';
function validateEmail(email: string) { function validateEmail(email: string) {
return emailRegex.test(email); return emailRegex.test(email);
@@ -37,6 +38,7 @@ export const SignIn: FC<AuthPanelProps> = ({
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
const loginStatus = useCurrentLoginStatus(); const loginStatus = useCurrentLoginStatus();
const [verifyToken, challenge] = useCaptcha(); const [verifyToken, challenge] = useCaptcha();
const subscriptionData = useSubscriptionSearch();
const { const {
isMutating: isSigningIn, isMutating: isSigningIn,
@@ -83,7 +85,8 @@ export const SignIn: FC<AuthPanelProps> = ({
if (verifyToken) { if (verifyToken) {
if (user) { if (user) {
// provider password sign-in if user has by default // 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'); setAuthState('signInWithPassword');
} else { } else {
const res = await signIn(email, verifyToken, challenge); const res = await signIn(email, verifyToken, challenge);
@@ -103,6 +106,7 @@ export const SignIn: FC<AuthPanelProps> = ({
} }
} }
}, [ }, [
subscriptionData,
challenge, challenge,
email, email,
setAuthEmail, setAuthEmail,

View File

@@ -3,10 +3,10 @@ import { Button } from '@affine/component/ui/button';
import { Loading } from '@affine/component/ui/loading'; import { Loading } from '@affine/component/ui/loading';
import { AffineShapeIcon } from '@affine/core/components/page-list'; import { AffineShapeIcon } from '@affine/core/components/page-list';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import type { SubscriptionRecurring } from '@affine/graphql'; import type { SubscriptionPlan, SubscriptionRecurring } from '@affine/graphql';
import { import {
changePasswordMutation, changePasswordMutation,
checkoutMutation, createCheckoutSessionMutation,
subscriptionQuery, subscriptionQuery,
} from '@affine/graphql'; } from '@affine/graphql';
import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { useAFFiNEI18N } from '@affine/i18n/hooks';
@@ -30,18 +30,25 @@ const usePaymentRedirect = () => {
} }
const recurring = searchData.recurring as SubscriptionRecurring; const recurring = searchData.recurring as SubscriptionRecurring;
const plan = searchData.plan as SubscriptionPlan;
const coupon = searchData.coupon;
const idempotencyKey = useMemo(() => nanoid(), []); const idempotencyKey = useMemo(() => nanoid(), []);
const { trigger: checkoutSubscription } = useMutation({ const { trigger: checkoutSubscription } = useMutation({
mutation: checkoutMutation, mutation: createCheckoutSessionMutation,
}); });
return useAsyncCallback(async () => { return useAsyncCallback(async () => {
const { checkout } = await checkoutSubscription({ const { createCheckoutSession: checkoutUrl } = await checkoutSubscription({
input: {
recurring, recurring,
plan,
coupon,
idempotencyKey, idempotencyKey,
successCallbackLink: null,
},
}); });
window.open(checkout, '_self', 'norefferer'); window.open(checkoutUrl, '_self', 'norefferer');
}, [recurring, idempotencyKey, checkoutSubscription]); }, [recurring, plan, coupon, idempotencyKey, checkoutSubscription]);
}; };
const CenterLoading = () => { const CenterLoading = () => {

View File

@@ -4,6 +4,7 @@ import { useSearchParams } from 'react-router-dom';
enum SubscriptionKey { enum SubscriptionKey {
Recurring = 'subscription_recurring', Recurring = 'subscription_recurring',
Plan = 'subscription_plan', Plan = 'subscription_plan',
Coupon = 'coupon',
SignUp = 'sign_up', // A new user with subscription journey: signup > set password > pay in stripe > go to app 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 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 recurring = searchParams.get(SubscriptionKey.Recurring);
const plan = searchParams.get(SubscriptionKey.Plan); const plan = searchParams.get(SubscriptionKey.Plan);
const coupon = searchParams.get(SubscriptionKey.Coupon);
const withSignUp = searchParams.get(SubscriptionKey.SignUp) === '1'; const withSignUp = searchParams.get(SubscriptionKey.SignUp) === '1';
const passwordToken = searchParams.get(SubscriptionKey.Token); const passwordToken = searchParams.get(SubscriptionKey.Token);
return { return {
recurring, recurring,
plan, plan,
coupon,
withSignUp, withSignUp,
passwordToken, passwordToken,
getRedirectUrl(signUp?: boolean) { getRedirectUrl(signUp?: boolean) {
@@ -35,6 +38,10 @@ export function useSubscriptionSearch() {
[SubscriptionKey.Plan, plan ?? ''], [SubscriptionKey.Plan, plan ?? ''],
]); ]);
if (coupon) {
paymentParams.set(SubscriptionKey.Coupon, coupon);
}
if (signUp) { if (signUp) {
paymentParams.set(SubscriptionKey.SignUp, '1'); paymentParams.set(SubscriptionKey.SignUp, '1');
} }

View File

@@ -6,7 +6,7 @@ import type {
SubscriptionMutator, SubscriptionMutator,
} from '@affine/core/hooks/use-subscription'; } from '@affine/core/hooks/use-subscription';
import { import {
checkoutMutation, createCheckoutSessionMutation,
SubscriptionPlan, SubscriptionPlan,
SubscriptionRecurring, SubscriptionRecurring,
SubscriptionStatus, SubscriptionStatus,
@@ -359,7 +359,7 @@ const Upgrade = ({
}) => { }) => {
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
const { isMutating, trigger } = useMutation({ const { isMutating, trigger } = useMutation({
mutation: checkoutMutation, mutation: createCheckoutSessionMutation,
}); });
const newTabRef = useRef<Window | null>(null); const newTabRef = useRef<Window | null>(null);
@@ -383,13 +383,21 @@ const Upgrade = ({
newTabRef.current.focus(); newTabRef.current.focus();
} else { } else {
await trigger( await trigger(
{ recurring, idempotencyKey }, {
input: {
recurring,
idempotencyKey,
plan: SubscriptionPlan.Pro, // Only support prod plan now.
coupon: null,
successCallbackLink: null,
},
},
{ {
onSuccess: data => { onSuccess: data => {
// FIXME: safari prevents from opening new tab by window api // FIXME: safari prevents from opening new tab by window api
// TODO(@xp): what if electron? // TODO(@xp): what if electron?
const newTab = window.open( const newTab = window.open(
data.checkout, data.createCheckoutSession,
'_blank', '_blank',
'noopener noreferrer' 'noopener noreferrer'
); );

View File

@@ -1,6 +1,3 @@
mutation checkout( mutation createCheckoutSession($input: CreateCheckoutSessionInput!) {
$recurring: SubscriptionRecurring! createCheckoutSession(input: $input)
$idempotencyKey: String!
) {
checkout(recurring: $recurring, idempotencyKey: $idempotencyKey)
} }

View File

@@ -127,14 +127,14 @@ mutation changePassword($token: String!, $newPassword: String!) {
}`, }`,
}; };
export const checkoutMutation = { export const createCheckoutSessionMutation = {
id: 'checkoutMutation' as const, id: 'createCheckoutSessionMutation' as const,
operationName: 'checkout', operationName: 'createCheckoutSession',
definitionName: 'checkout', definitionName: 'createCheckoutSession',
containsFile: false, containsFile: false,
query: ` query: `
mutation checkout($recurring: SubscriptionRecurring!, $idempotencyKey: String!) { mutation createCheckoutSession($input: CreateCheckoutSessionInput!) {
checkout(recurring: $recurring, idempotencyKey: $idempotencyKey) createCheckoutSession(input: $input)
}`, }`,
}; };

View File

@@ -200,12 +200,14 @@ export type ChangePasswordMutation = {
}; };
}; };
export type CheckoutMutationVariables = Exact<{ export type CreateCheckoutSessionMutationVariables = Exact<{
recurring: SubscriptionRecurring; input: CreateCheckoutSessionInput;
idempotencyKey: Scalars['String']['input'];
}>; }>;
export type CheckoutMutation = { __typename?: 'Mutation'; checkout: string }; export type CreateCheckoutSessionMutation = {
__typename?: 'Mutation';
createCheckoutSession: string;
};
export type CreateCustomerPortalMutationVariables = Exact<{ export type CreateCustomerPortalMutationVariables = Exact<{
[key: string]: never; [key: string]: never;
@@ -1049,9 +1051,9 @@ export type Mutations =
response: ChangePasswordMutation; response: ChangePasswordMutation;
} }
| { | {
name: 'checkoutMutation'; name: 'createCheckoutSessionMutation';
variables: CheckoutMutationVariables; variables: CreateCheckoutSessionMutationVariables;
response: CheckoutMutation; response: CreateCheckoutSessionMutation;
} }
| { | {
name: 'createCustomerPortalMutation'; name: 'createCustomerPortalMutation';