42 add billing portal endpoint (#4315)
* Add create billing portal session endpoint * Rename checkout to checkoutSession * Code review returns
This commit is contained in:
@ -11,8 +11,9 @@ import { ProductPricesEntity } from 'src/core/billing/dto/product-prices.entity'
|
||||
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
|
||||
import { AuthUser } from 'src/decorators/auth/auth-user.decorator';
|
||||
import { User } from 'src/core/user/user.entity';
|
||||
import { CheckoutInput } from 'src/core/billing/dto/checkout.input';
|
||||
import { CheckoutEntity } from 'src/core/billing/dto/checkout.entity';
|
||||
import { CheckoutSessionInput } from 'src/core/billing/dto/checkout-session.input';
|
||||
import { SessionEntity } from 'src/core/billing/dto/session.entity';
|
||||
import { BillingSessionInput } from 'src/core/billing/dto/billing-session.input';
|
||||
|
||||
@Resolver()
|
||||
export class BillingResolver {
|
||||
@ -38,11 +39,25 @@ export class BillingResolver {
|
||||
};
|
||||
}
|
||||
|
||||
@Mutation(() => CheckoutEntity)
|
||||
@Query(() => SessionEntity)
|
||||
@UseGuards(JwtAuthGuard)
|
||||
async checkout(
|
||||
async billingPortalSession(
|
||||
@AuthUser() user: User,
|
||||
@Args() { recurringInterval, successUrlPath }: CheckoutInput,
|
||||
@Args() { returnUrlPath }: BillingSessionInput,
|
||||
) {
|
||||
return {
|
||||
url: await this.billingService.computeBillingPortalSessionURL(
|
||||
user.defaultWorkspaceId,
|
||||
returnUrlPath,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@Mutation(() => SessionEntity)
|
||||
@UseGuards(JwtAuthGuard)
|
||||
async checkoutSession(
|
||||
@AuthUser() user: User,
|
||||
@Args() { recurringInterval, successUrlPath }: CheckoutSessionInput,
|
||||
) {
|
||||
const stripeProductId = this.billingService.getProductStripeId(
|
||||
AvailableProduct.BasePlan,
|
||||
@ -66,7 +81,7 @@ export class BillingResolver {
|
||||
);
|
||||
|
||||
return {
|
||||
url: await this.billingService.checkout(
|
||||
url: await this.billingService.computeCheckoutSessionURL(
|
||||
user,
|
||||
stripePriceId,
|
||||
successUrlPath,
|
||||
|
||||
@ -11,6 +11,7 @@ import { BillingSubscriptionItem } from 'src/core/billing/entities/billing-subsc
|
||||
import { Workspace } from 'src/core/workspace/workspace.entity';
|
||||
import { ProductPriceEntity } from 'src/core/billing/dto/product-price.entity';
|
||||
import { User } from 'src/core/user/user.entity';
|
||||
import { assert } from 'src/utils/assert';
|
||||
|
||||
export enum AvailableProduct {
|
||||
BasePlan = 'base-plan',
|
||||
@ -101,18 +102,45 @@ export class BillingService {
|
||||
return billingSubscriptionItem;
|
||||
}
|
||||
|
||||
async checkout(user: User, priceId: string, successUrlPath?: string) {
|
||||
async computeBillingPortalSessionURL(
|
||||
workspaceId: string,
|
||||
returnUrlPath?: string,
|
||||
) {
|
||||
const billingSubscription =
|
||||
await this.billingSubscriptionRepository.findOneOrFail({
|
||||
where: { workspaceId },
|
||||
});
|
||||
|
||||
const session = await this.stripeService.createBillingPortalSession(
|
||||
billingSubscription.stripeCustomerId,
|
||||
returnUrlPath,
|
||||
);
|
||||
|
||||
assert(session.url, 'Error: missing billingPortal.session.url');
|
||||
|
||||
return session.url;
|
||||
}
|
||||
|
||||
async computeCheckoutSessionURL(
|
||||
user: User,
|
||||
priceId: string,
|
||||
successUrlPath?: string,
|
||||
): Promise<string> {
|
||||
const frontBaseUrl = this.environmentService.getFrontBaseUrl();
|
||||
const successUrl = successUrlPath
|
||||
? frontBaseUrl + successUrlPath
|
||||
: frontBaseUrl;
|
||||
|
||||
return await this.stripeService.createCheckoutSession(
|
||||
const session = await this.stripeService.createCheckoutSession(
|
||||
user,
|
||||
priceId,
|
||||
successUrl,
|
||||
frontBaseUrl,
|
||||
);
|
||||
|
||||
assert(session.url, 'Error: missing checkout.session.url');
|
||||
|
||||
return session.url;
|
||||
}
|
||||
|
||||
async deleteSubscription(workspaceId: string) {
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
import { ArgsType, Field } from '@nestjs/graphql';
|
||||
|
||||
import { IsOptional, IsString } from 'class-validator';
|
||||
|
||||
@ArgsType()
|
||||
export class BillingSessionInput {
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
returnUrlPath?: string;
|
||||
}
|
||||
@ -4,7 +4,7 @@ import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||
import Stripe from 'stripe';
|
||||
|
||||
@ArgsType()
|
||||
export class CheckoutInput {
|
||||
export class CheckoutSessionInput {
|
||||
@Field(() => String)
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@ -1,7 +1,7 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
@ObjectType()
|
||||
export class CheckoutEntity {
|
||||
export class SessionEntity {
|
||||
@Field(() => String)
|
||||
url: string;
|
||||
}
|
||||
@ -4,7 +4,6 @@ import Stripe from 'stripe';
|
||||
|
||||
import { EnvironmentService } from 'src/integrations/environment/environment.service';
|
||||
import { User } from 'src/core/user/user.entity';
|
||||
import { assert } from 'src/utils/assert';
|
||||
|
||||
@Injectable()
|
||||
export class StripeService {
|
||||
@ -43,13 +42,23 @@ export class StripeService {
|
||||
await this.stripe.subscriptions.cancel(stripeSubscriptionId);
|
||||
}
|
||||
|
||||
async createBillingPortalSession(
|
||||
stripeCustomerId: string,
|
||||
returnUrlPath?: string,
|
||||
): Promise<Stripe.BillingPortal.Session> {
|
||||
return await this.stripe.billingPortal.sessions.create({
|
||||
customer: stripeCustomerId,
|
||||
return_url: returnUrlPath ?? this.environmentService.getFrontBaseUrl(),
|
||||
});
|
||||
}
|
||||
|
||||
async createCheckoutSession(
|
||||
user: User,
|
||||
priceId: string,
|
||||
successUrl?: string,
|
||||
cancelUrl?: string,
|
||||
) {
|
||||
const session = await this.stripe.checkout.sessions.create({
|
||||
): Promise<Stripe.Checkout.Session> {
|
||||
return await this.stripe.checkout.sessions.create({
|
||||
line_items: [
|
||||
{
|
||||
price: priceId,
|
||||
@ -70,11 +79,5 @@ export class StripeService {
|
||||
success_url: successUrl,
|
||||
cancel_url: cancelUrl,
|
||||
});
|
||||
|
||||
assert(session.url, 'Error: missing checkout.session.url');
|
||||
|
||||
this.logger.log(`Stripe Checkout Session Url Redirection: ${session.url}`);
|
||||
|
||||
return session.url;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user