Pass Billing Checkout var in url to bypass credit card (#9283)
This commit is contained in:
@ -8,9 +8,10 @@ import {
|
||||
} from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
import { Response } from 'express';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { AuthException } from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { AuthOAuthExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-oauth-exception.filter';
|
||||
import { AuthRestApiExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-rest-api-exception.filter';
|
||||
import { GoogleOauthGuard } from 'src/engine/core-modules/auth/guards/google-oauth.guard';
|
||||
@ -18,9 +19,8 @@ import { GoogleProviderEnabledGuard } from 'src/engine/core-modules/auth/guards/
|
||||
import { AuthService } from 'src/engine/core-modules/auth/services/auth.service';
|
||||
import { GoogleRequest } from 'src/engine/core-modules/auth/strategies/google.auth.strategy';
|
||||
import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/login-token.service';
|
||||
import { AuthException } from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
|
||||
@Controller('auth/google')
|
||||
@ -55,6 +55,7 @@ export class GoogleAuthController {
|
||||
workspaceInviteHash,
|
||||
workspacePersonalInviteToken,
|
||||
targetWorkspaceSubdomain,
|
||||
billingCheckoutSessionState,
|
||||
} = req.user;
|
||||
|
||||
const signInUpParams = {
|
||||
@ -106,6 +107,7 @@ export class GoogleAuthController {
|
||||
this.authService.computeRedirectURI(
|
||||
loginToken.token,
|
||||
workspace.subdomain,
|
||||
billingCheckoutSessionState,
|
||||
),
|
||||
);
|
||||
} catch (err) {
|
||||
|
||||
@ -11,15 +11,15 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Response } from 'express';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { AuthException } from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { AuthRestApiExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-rest-api-exception.filter';
|
||||
import { MicrosoftOAuthGuard } from 'src/engine/core-modules/auth/guards/microsoft-oauth.guard';
|
||||
import { MicrosoftProviderEnabledGuard } from 'src/engine/core-modules/auth/guards/microsoft-provider-enabled.guard';
|
||||
import { AuthService } from 'src/engine/core-modules/auth/services/auth.service';
|
||||
import { MicrosoftRequest } from 'src/engine/core-modules/auth/strategies/microsoft.auth.strategy';
|
||||
import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/login-token.service';
|
||||
import { AuthException } from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
|
||||
@Controller('auth/microsoft')
|
||||
@ -90,6 +90,7 @@ export class MicrosoftAuthController {
|
||||
this.authService.computeRedirectURI(
|
||||
loginToken.token,
|
||||
workspace.subdomain,
|
||||
signInUpParams.billingCheckoutSessionState,
|
||||
),
|
||||
);
|
||||
} catch (err) {
|
||||
|
||||
@ -45,6 +45,14 @@ export class GoogleOauthGuard extends AuthGuard('google') {
|
||||
request.params.workspaceSubdomain = request.query.workspaceSubdomain;
|
||||
}
|
||||
|
||||
if (
|
||||
request.query.billingCheckoutSessionState &&
|
||||
typeof request.query.billingCheckoutSessionState === 'string'
|
||||
) {
|
||||
request.params.billingCheckoutSessionState =
|
||||
request.query.billingCheckoutSessionState;
|
||||
}
|
||||
|
||||
return (await super.canActivate(context)) as boolean;
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,6 +33,14 @@ export class MicrosoftOAuthGuard extends AuthGuard('microsoft') {
|
||||
request.params.workspaceSubdomain = request.query.workspaceSubdomain;
|
||||
}
|
||||
|
||||
if (
|
||||
request.query.billingCheckoutSessionState &&
|
||||
typeof request.query.billingCheckoutSessionState === 'string'
|
||||
) {
|
||||
request.params.billingCheckoutSessionState =
|
||||
request.query.billingCheckoutSessionState;
|
||||
}
|
||||
|
||||
return (await super.canActivate(context)) as boolean;
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,6 +28,7 @@ import { AuthorizeApp } from 'src/engine/core-modules/auth/dto/authorize-app.ent
|
||||
import { AuthorizeAppInput } from 'src/engine/core-modules/auth/dto/authorize-app.input';
|
||||
import { AvailableWorkspaceOutput } from 'src/engine/core-modules/auth/dto/available-workspaces.output';
|
||||
import { ChallengeInput } from 'src/engine/core-modules/auth/dto/challenge.input';
|
||||
import { AuthTokens } from 'src/engine/core-modules/auth/dto/token.entity';
|
||||
import { UpdatePassword } from 'src/engine/core-modules/auth/dto/update-password.entity';
|
||||
import {
|
||||
UserExists,
|
||||
@ -46,7 +47,6 @@ import { userValidator } from 'src/engine/core-modules/user/user.validate';
|
||||
import { WorkspaceInvitationService } from 'src/engine/core-modules/workspace-invitation/services/workspace-invitation.service';
|
||||
import { WorkspaceAuthProvider } from 'src/engine/core-modules/workspace/types/workspace.type';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthTokens } from 'src/engine/core-modules/auth/dto/token.entity';
|
||||
|
||||
@Injectable()
|
||||
// eslint-disable-next-line @nx/workspace-inject-workspace-repository
|
||||
@ -171,6 +171,7 @@ export class AuthService {
|
||||
fromSSO: boolean;
|
||||
targetWorkspaceSubdomain?: string;
|
||||
authProvider?: WorkspaceAuthProvider;
|
||||
billingCheckoutSessionState?: string;
|
||||
}) {
|
||||
return await this.signInUpService.signInUp({
|
||||
email,
|
||||
@ -413,11 +414,18 @@ export class AuthService {
|
||||
return workspace;
|
||||
}
|
||||
|
||||
computeRedirectURI(loginToken: string, subdomain?: string) {
|
||||
computeRedirectURI(
|
||||
loginToken: string,
|
||||
subdomain?: string,
|
||||
billingCheckoutSessionState?: string,
|
||||
) {
|
||||
const url = this.domainManagerService.buildWorkspaceURL({
|
||||
subdomain,
|
||||
pathname: '/verify',
|
||||
searchParams: { loginToken },
|
||||
searchParams: {
|
||||
loginToken,
|
||||
...(billingCheckoutSessionState ? { billingCheckoutSessionState } : {}),
|
||||
},
|
||||
});
|
||||
|
||||
return url.toString();
|
||||
|
||||
@ -35,8 +35,8 @@ import {
|
||||
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
|
||||
import { getDomainNameByEmail } from 'src/utils/get-domain-name-by-email';
|
||||
import { getImageBufferFromUrl } from 'src/utils/image';
|
||||
import { isWorkEmail } from 'src/utils/is-work-email';
|
||||
import { isDefined } from 'src/utils/is-defined';
|
||||
import { isWorkEmail } from 'src/utils/is-work-email';
|
||||
|
||||
export type SignInUpServiceInput = {
|
||||
email: string;
|
||||
|
||||
@ -18,6 +18,7 @@ export type GoogleRequest = Omit<
|
||||
workspaceInviteHash?: string;
|
||||
workspacePersonalInviteToken?: string;
|
||||
targetWorkspaceSubdomain?: string;
|
||||
billingCheckoutSessionState?: string;
|
||||
};
|
||||
};
|
||||
|
||||
@ -39,6 +40,12 @@ export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
|
||||
state: JSON.stringify({
|
||||
workspaceInviteHash: req.params.workspaceInviteHash,
|
||||
workspaceSubdomain: req.params.workspaceSubdomain,
|
||||
...(req.params.billingCheckoutSessionState
|
||||
? {
|
||||
billingCheckoutSessionState:
|
||||
req.params.billingCheckoutSessionState,
|
||||
}
|
||||
: {}),
|
||||
...(req.params.workspacePersonalInviteToken
|
||||
? {
|
||||
workspacePersonalInviteToken:
|
||||
@ -72,6 +79,7 @@ export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
|
||||
workspaceInviteHash: state.workspaceInviteHash,
|
||||
workspacePersonalInviteToken: state.workspacePersonalInviteToken,
|
||||
targetWorkspaceSubdomain: state.workspaceSubdomain,
|
||||
billingCheckoutSessionState: state.billingCheckoutSessionState,
|
||||
};
|
||||
|
||||
done(null, user);
|
||||
|
||||
@ -22,6 +22,7 @@ export type MicrosoftRequest = Omit<
|
||||
workspaceInviteHash?: string;
|
||||
workspacePersonalInviteToken?: string;
|
||||
targetWorkspaceSubdomain?: string;
|
||||
billingCheckoutSessionState?: string;
|
||||
};
|
||||
};
|
||||
|
||||
@ -43,6 +44,12 @@ export class MicrosoftStrategy extends PassportStrategy(Strategy, 'microsoft') {
|
||||
state: JSON.stringify({
|
||||
workspaceInviteHash: req.params.workspaceInviteHash,
|
||||
workspaceSubdomain: req.params.workspaceSubdomain,
|
||||
...(req.params.billingCheckoutSessionState
|
||||
? {
|
||||
billingCheckoutSessionState:
|
||||
req.params.billingCheckoutSessionState,
|
||||
}
|
||||
: {}),
|
||||
...(req.params.workspacePersonalInviteToken
|
||||
? {
|
||||
workspacePersonalInviteToken:
|
||||
@ -86,6 +93,7 @@ export class MicrosoftStrategy extends PassportStrategy(Strategy, 'microsoft') {
|
||||
workspaceInviteHash: state.workspaceInviteHash,
|
||||
workspacePersonalInviteToken: state.workspacePersonalInviteToken,
|
||||
targetWorkspaceSubdomain: state.workspaceSubdomain,
|
||||
billingCheckoutSessionState: state.billingCheckoutSessionState,
|
||||
};
|
||||
|
||||
done(null, user);
|
||||
|
||||
@ -56,7 +56,13 @@ export class BillingResolver {
|
||||
async checkoutSession(
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
@AuthUser() user: User,
|
||||
@Args() { recurringInterval, successUrlPath }: CheckoutSessionInput,
|
||||
@Args()
|
||||
{
|
||||
recurringInterval,
|
||||
successUrlPath,
|
||||
plan,
|
||||
requirePaymentMethod,
|
||||
}: CheckoutSessionInput,
|
||||
) {
|
||||
const productPrice = await this.stripeService.getStripePrice(
|
||||
AvailableProduct.BasePlan,
|
||||
@ -75,6 +81,8 @@ export class BillingResolver {
|
||||
workspace,
|
||||
productPrice.stripePriceId,
|
||||
successUrlPath,
|
||||
plan,
|
||||
requirePaymentMethod,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@ -1,16 +1,32 @@
|
||||
import { ArgsType, Field } from '@nestjs/graphql';
|
||||
|
||||
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||
import Stripe from 'stripe';
|
||||
import {
|
||||
IsBoolean,
|
||||
IsEnum,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
} from 'class-validator';
|
||||
|
||||
import { BillingPlanKey } from 'src/engine/core-modules/billing/enums/billing-plan-key.enum';
|
||||
import { SubscriptionInterval } from 'src/engine/core-modules/billing/enums/billing-subscription-interval.enum';
|
||||
|
||||
@ArgsType()
|
||||
export class CheckoutSessionInput {
|
||||
@Field(() => SubscriptionInterval)
|
||||
@IsString()
|
||||
@IsEnum(SubscriptionInterval)
|
||||
@IsNotEmpty()
|
||||
recurringInterval: Stripe.Price.Recurring.Interval;
|
||||
recurringInterval: SubscriptionInterval;
|
||||
|
||||
@Field(() => BillingPlanKey, { defaultValue: BillingPlanKey.PRO })
|
||||
@IsEnum(BillingPlanKey)
|
||||
@IsOptional()
|
||||
plan?: BillingPlanKey;
|
||||
|
||||
@Field(() => Boolean, { defaultValue: true })
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
requirePaymentMethod?: boolean;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsString()
|
||||
|
||||
@ -1,4 +1,12 @@
|
||||
import { registerEnumType } from '@nestjs/graphql';
|
||||
|
||||
export enum BillingPlanKey {
|
||||
BASE_PLAN = 'BASE_PLAN',
|
||||
PRO_PLAN = 'PRO_PLAN',
|
||||
BASE = 'BASE',
|
||||
PRO = 'PRO',
|
||||
ENTERPRISE = 'ENTERPRISE',
|
||||
}
|
||||
|
||||
registerEnumType(BillingPlanKey, {
|
||||
name: 'BillingPlanKey',
|
||||
description: 'The different billing plans available',
|
||||
});
|
||||
|
||||
@ -4,6 +4,7 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity';
|
||||
import { BillingPlanKey } from 'src/engine/core-modules/billing/enums/billing-plan-key.enum';
|
||||
import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service';
|
||||
import { StripeService } from 'src/engine/core-modules/billing/stripe/stripe.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service';
|
||||
@ -30,6 +31,8 @@ export class BillingPortalWorkspaceService {
|
||||
workspace: Workspace,
|
||||
priceId: string,
|
||||
successUrlPath?: string,
|
||||
plan?: BillingPlanKey,
|
||||
requirePaymentMethod?: boolean,
|
||||
): Promise<string> {
|
||||
const frontBaseUrl = this.domainManagerService.getBaseUrl();
|
||||
const cancelUrl = frontBaseUrl.toString();
|
||||
@ -57,6 +60,8 @@ export class BillingPortalWorkspaceService {
|
||||
successUrl,
|
||||
cancelUrl,
|
||||
stripeCustomerId,
|
||||
plan,
|
||||
requirePaymentMethod,
|
||||
);
|
||||
|
||||
assert(session.url, 'Error: missing checkout.session.url');
|
||||
|
||||
@ -52,9 +52,9 @@ export class BillingWebhookProductService {
|
||||
|
||||
isValidBillingPlanKey(planKey?: string) {
|
||||
switch (planKey) {
|
||||
case BillingPlanKey.BASE_PLAN:
|
||||
case BillingPlanKey.BASE:
|
||||
return true;
|
||||
case BillingPlanKey.PRO_PLAN:
|
||||
case BillingPlanKey.PRO:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
||||
@ -5,6 +5,7 @@ import Stripe from 'stripe';
|
||||
import { ProductPriceEntity } from 'src/engine/core-modules/billing/dto/product-price.entity';
|
||||
import { BillingSubscriptionItem } from 'src/engine/core-modules/billing/entities/billing-subscription-item.entity';
|
||||
import { AvailableProduct } from 'src/engine/core-modules/billing/enums/billing-available-product.enum';
|
||||
import { BillingPlanKey } from 'src/engine/core-modules/billing/enums/billing-plan-key.enum';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service';
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
@ -90,6 +91,8 @@ export class StripeService {
|
||||
successUrl?: string,
|
||||
cancelUrl?: string,
|
||||
stripeCustomerId?: string,
|
||||
plan: BillingPlanKey = BillingPlanKey.PRO,
|
||||
requirePaymentMethod = true,
|
||||
): Promise<Stripe.Checkout.Session> {
|
||||
return await this.stripe.checkout.sessions.create({
|
||||
line_items: [
|
||||
@ -102,18 +105,22 @@ export class StripeService {
|
||||
subscription_data: {
|
||||
metadata: {
|
||||
workspaceId,
|
||||
plan,
|
||||
},
|
||||
trial_period_days: this.environmentService.get(
|
||||
'BILLING_FREE_TRIAL_DURATION_IN_DAYS',
|
||||
),
|
||||
},
|
||||
automatic_tax: { enabled: true },
|
||||
tax_id_collection: { enabled: true },
|
||||
automatic_tax: { enabled: !!requirePaymentMethod },
|
||||
tax_id_collection: { enabled: !!requirePaymentMethod },
|
||||
customer: stripeCustomerId,
|
||||
customer_update: stripeCustomerId ? { name: 'auto' } : undefined,
|
||||
customer_email: stripeCustomerId ? undefined : user.email,
|
||||
success_url: successUrl,
|
||||
cancel_url: cancelUrl,
|
||||
payment_method_collection: requirePaymentMethod
|
||||
? 'always'
|
||||
: 'if_required',
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user