Skip to content

Commit a74cd2b

Browse files
feat: add create checkout session usecase
1 parent a42601d commit a74cd2b

File tree

7 files changed

+113
-2
lines changed

7 files changed

+113
-2
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { IUseCase } from "../interfaces/usecase.interface";
2+
import { HttpStatus } from "@/infra/http/protocols.enum";
3+
import { DomainException } from "@/domain/error";
4+
import { IRepositoryFactory, IServiceFactory } from "@/application/factories";
5+
import { IUserRepository } from "@/application";
6+
import { IPaymentGatewayService } from "@/infra/services";
7+
8+
export namespace CreateCheckoutSessionNamespace {
9+
export interface Input {
10+
userId: string;
11+
priceId: string;
12+
}
13+
14+
export interface Output {
15+
sessionId: string;
16+
successUrl: string | null;
17+
cancelUrl: string | null;
18+
}
19+
}
20+
21+
export class CreateCheckoutSessionUseCase implements IUseCase {
22+
private userRepository: IUserRepository;
23+
private paymentGatewayService: IPaymentGatewayService;
24+
25+
constructor(
26+
private repositoryFactory: IRepositoryFactory,
27+
private serviceFactory: IServiceFactory,
28+
) {
29+
this.userRepository = this.repositoryFactory.createUserRepository();
30+
this.paymentGatewayService =
31+
this.serviceFactory.createPaymentGatewayService();
32+
}
33+
34+
async execute(
35+
input: CreateCheckoutSessionNamespace.Input,
36+
): Promise<CreateCheckoutSessionNamespace.Output> {
37+
const user = await this.userRepository.getById(input.userId);
38+
if (!user) {
39+
throw new DomainException("User not found", HttpStatus.NOT_FOUND);
40+
}
41+
const session = await this.paymentGatewayService.createCheckoutSession({
42+
priceId: input.priceId,
43+
userId: input.userId,
44+
});
45+
return {
46+
sessionId: session.sessionId,
47+
successUrl: session.successUrl,
48+
cancelUrl: session.cancelUrl,
49+
};
50+
}
51+
}

src/config/env.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ export const env = {
3232
},
3333
stripe: {
3434
stripe_secret: process.env.STRIPE_SECRET,
35+
success_url: process.env.STRIPE_SUCCESS_URL,
36+
cancel_url: process.env.STRIPE_CANCEL_URL,
3537
price_ids: {
3638
basic: process.env.STRIPE_PRICE_ID_BASIC,
3739
standard: process.env.STRIPE_PRICE_ID_STANDARD,

src/infra/adapters/payment-gateway/interfaces/payment-gateway.interface.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,18 @@ export namespace IPaymentGatewayOutput {
4141
export interface SaveCard {
4242
paymentMethod: string;
4343
}
44+
45+
export interface CheckoutSessionOutput {
46+
sessionId: string;
47+
url: string;
48+
}
4449
}
4550

4651
export interface IPaymentGateway {
52+
createCheckoutSession(
53+
priceId: string,
54+
userID: string,
55+
): Promise<Stripe.Checkout.Session>;
4756
createCustomer(user: User): Promise<string>;
4857
charge(
4958
input: IPaymentGatewayInput.Charge,
@@ -64,4 +73,5 @@ export interface IPaymentGateway {
6473
customerId: string,
6574
): Promise<Stripe.Customer | Stripe.DeletedCustomer>;
6675
retrieveSubscription(subscriptionId: string): Promise<Stripe.Subscription>;
76+
retrievePaymentMethod(paymentMethodId: string): Promise<Stripe.PaymentMethod>;
6777
}

src/infra/adapters/payment-gateway/stripe-adapter.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,21 @@ export class StripeAdapter implements IPaymentGateway {
1616
this.stripe = new Stripe(env.stripe.stripe_secret!);
1717
}
1818

19+
async createCheckoutSession(
20+
priceId: string,
21+
userId: string,
22+
): Promise<Stripe.Checkout.Session> {
23+
return await this.stripe.checkout.sessions.create({
24+
mode: "subscription",
25+
line_items: [{ price: priceId, quantity: 1 }],
26+
success_url: `${env.stripe.success_url}?session_id={CHECKOUT_SESSION_ID}`,
27+
cancel_url: env.stripe.cancel_url,
28+
metadata: {
29+
userId: userId,
30+
},
31+
});
32+
}
33+
1934
async createCustomer(user: User): Promise<string> {
2035
const customer = await this.stripe.customers.create({
2136
name: user.getName,
@@ -159,4 +174,10 @@ export class StripeAdapter implements IPaymentGateway {
159174
): Promise<Stripe.Subscription> {
160175
return await this.stripe.subscriptions.retrieve(subscriptionId);
161176
}
177+
178+
async retrievePaymentMethod(
179+
paymentMethodId: string,
180+
): Promise<Stripe.PaymentMethod> {
181+
return await this.stripe.paymentMethods.retrieve(paymentMethodId);
182+
}
162183
}

src/infra/services/payment-gateway/interfaces/payment-gateway-service.interface.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,19 @@ export namespace PaymentGatewayServiceInput {
1818
priceId: string;
1919
paymentMethodId: string;
2020
}
21+
22+
export interface CheckoutSessionOutput {
23+
sessionId: string;
24+
successUrl: string | null;
25+
cancelUrl: string | null;
26+
}
2127
}
2228

2329
export interface IPaymentGatewayService {
30+
createCheckoutSession(input: {
31+
priceId: string;
32+
userId: string;
33+
}): Promise<PaymentGatewayServiceInput.CheckoutSessionOutput>;
2434
createSubscription(
2535
input: PaymentGatewayServiceInput.CreateSubscription,
2636
subscriptionId: string,

src/infra/services/payment-gateway/payment-gateway-service.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,21 @@ export class PaymentGatewayService implements IPaymentGatewayService {
2121
this.repositoryFactory.createSubscriptionStripeDataModelRepository();
2222
}
2323

24+
async createCheckoutSession(input: {
25+
priceId: string;
26+
userId: string;
27+
}): Promise<PaymentGatewayServiceInput.CheckoutSessionOutput> {
28+
const session = await this.stripeAdapter.createCheckoutSession(
29+
input.priceId,
30+
input.userId,
31+
);
32+
return {
33+
sessionId: session.id,
34+
successUrl: session.success_url,
35+
cancelUrl: session.cancel_url,
36+
};
37+
}
38+
2439
async createSubscription(
2540
input: PaymentGatewayServiceInput.CreateSubscription,
2641
subscriptionId: string,
@@ -90,8 +105,8 @@ export class PaymentGatewayService implements IPaymentGatewayService {
90105
savedCard: IPaymentGatewayOutput.SaveCard,
91106
) {
92107
if (stripeData.stripePaymentMethodId !== savedCard.paymentMethod)
93-
return savedCard.paymentMethod;
94-
return stripeData.stripePaymentMethodId;
108+
return stripeData.stripePaymentMethodId;
109+
return savedCard.paymentMethod;
95110
}
96111

97112
private buildToSave(input: {

src/tests/config/env.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ describe("Environment Configuration", () => {
2222
expect(env.smtp.password).toBeDefined();
2323
expect(env.stripe).toBeDefined();
2424
expect(typeof env.stripe.stripe_secret).toBe("string");
25+
expect(typeof env.stripe.success_url).toBe("string");
26+
expect(typeof env.stripe.cancel_url).toBe("string");
2527
expect(typeof env.stripe.price_ids.basic).toBe("string");
2628
expect(typeof env.stripe.price_ids.standard).toBe("string");
2729
expect(typeof env.stripe.price_ids.premium).toBe("string");

0 commit comments

Comments
 (0)