Refactor: extract link between session and purchase to stripe session control

main
MiguelMLorente 2025-11-24 22:13:21 +01:00
parent d4e8883304
commit a92855396b
5 changed files with 46 additions and 42 deletions

View File

@ -1,4 +1,4 @@
import { Body, Controller, Get, Post, Redirect } from '@nestjs/common'; import { Body, Controller, Get, Logger, Param, Post, Redirect } from '@nestjs/common';
import { PurchaseService } from '../service/purchase.service'; import { PurchaseService } from '../service/purchase.service';
import { UserService } from '../service/user.service'; import { UserService } from '../service/user.service';
import { PurchaseItem } from '../dto/purchase-item'; import { PurchaseItem } from '../dto/purchase-item';
@ -7,6 +7,8 @@ import { PurchaseStatus } from 'src/dto/purchase-status';
@Controller('/buy') @Controller('/buy')
export class BuyController { export class BuyController {
private readonly logger = new Logger(BuyController.name);
constructor( constructor(
private readonly purchaseService: PurchaseService, private readonly purchaseService: PurchaseService,
private readonly userService: UserService, private readonly userService: UserService,
@ -24,22 +26,21 @@ export class BuyController {
PurchaseItem.PAELLA, PurchaseItem.PAELLA,
quantity, quantity,
); );
const session = await this.stripeService.createStripeSession( const { sessionUrl } = await this.stripeService.createStripeSession(
user, user,
purchase, purchase,
); );
this.purchaseService.attachStripeSessionToPurchase(session.id, purchase.id); this.logger.debug(`Redirecting to ${sessionUrl}`);
console.log(session); return { url: sessionUrl };
return { url: session.url };
} }
@Post('/complete') @Post('/complete')
public async complete(@Body('userId') userId: number) { public async complete(@Body('userId') userId: number) {
const user = this.userService.getUserById(userId); const user = this.userService.getUserById(userId);
const session = await this.stripeService.getStripeSessionForCustomer(user); const { isPaid, purchaseId } =
const isPaid = session.payment_status == 'paid'; await this.stripeService.getStripeSessionForCustomer(user);
this.purchaseService.findAndTransitionPurchaseStatus( this.purchaseService.transitionPurchaseStatus(
session.id, purchaseId,
isPaid ? PurchaseStatus.COMPLETED : PurchaseStatus.IN_PROGRESS, isPaid ? PurchaseStatus.COMPLETED : PurchaseStatus.IN_PROGRESS,
); );
return { url: '/signup' }; return { url: '/signup' };

View File

@ -8,5 +8,4 @@ export class Purchase {
purchasedProduct: PurchaseItem; purchasedProduct: PurchaseItem;
purchasedUnits: number; purchasedUnits: number;
status: PurchaseStatus; status: PurchaseStatus;
stripeSessionId?: string;
} }

View File

@ -0,0 +1,5 @@
export class StripeSession {
sessionId: string;
userId: number;
purchaseId: number;
}

View File

@ -28,26 +28,12 @@ export class PurchaseService {
return purchase; return purchase;
} }
public attachStripeSessionToPurchase( public transitionPurchaseStatus(
sessionId: string,
purchaseId: number, purchaseId: number,
): void {
const purchase = this.purchases.find(
(purchase) => purchase.id === purchaseId,
);
if (!purchase) {
throw new NotFoundException();
}
purchase.stripeSessionId = sessionId;
}
public findAndTransitionPurchaseStatus(
sessionId: string,
purchaseStatus: PurchaseStatus, purchaseStatus: PurchaseStatus,
) { ) {
const purchase = this.purchases.find( const purchase = this.purchases.find(
(purchase) => purchase.stripeSessionId === sessionId, (purchase) => purchase.id === purchaseId,
); );
if (!purchase) { if (!purchase) {
throw new NotFoundException(); throw new NotFoundException();

View File

@ -2,22 +2,20 @@ import { Injectable, NotFoundException } from '@nestjs/common';
import { Purchase } from '../dto/purchase'; import { Purchase } from '../dto/purchase';
import Stripe from 'stripe'; import Stripe from 'stripe';
import { User } from '../dto/user'; import { User } from '../dto/user';
import { NotFoundError } from 'rxjs'; import { StripeSession } from 'src/dto/stripe-session';
@Injectable() @Injectable()
export class StripeService { export class StripeService {
private readonly stripe: Stripe; private readonly stripe: Stripe;
private readonly stripeSessions: Map<number, string> = new Map(); private readonly stripeSessions: StripeSession[] = [];
constructor() { constructor() {
const stripePrivateKey = process.env.STRIPE_PRIVATE_KEY!; const stripePrivateKey = process.env.STRIPE_PRIVATE_KEY!;
this.stripe = new Stripe(stripePrivateKey); this.stripe = new Stripe(stripePrivateKey);
} }
public async createStripeSession( public async createStripeSession(user: User, purchase: Purchase) {
user: User, const redirectUrl = `http://localhost:5173/buy/return?purchaseId=${purchase.id}`
purchase: Purchase,
): Promise<Stripe.Response<Stripe.Checkout.Session>> {
const session = await this.stripe.checkout.sessions.create({ const session = await this.stripe.checkout.sessions.create({
line_items: [ line_items: [
{ {
@ -26,21 +24,36 @@ export class StripeService {
}, },
], ],
mode: 'payment', mode: 'payment',
success_url: 'http://localhost:5173/buy/return', success_url: redirectUrl,
cancel_url: 'http://localhost:5173/buy/return', cancel_url: redirectUrl,
payment_method_types: ['card', 'paypal'], payment_method_types: ['card', 'paypal'],
}); });
this.stripeSessions.set(user.id, session.id); const stripeSession: StripeSession = {
return session; sessionId: session.id,
userId: user.id,
purchaseId: purchase.id,
};
this.stripeSessions.push(stripeSession);
return {
sessionUrl: session.url,
};
} }
public getStripeSessionForCustomer( public async getStripeSessionForCustomer(user: User) {
user: User, const stripeSession = this.stripeSessions.find(
): Promise<Stripe.Response<Stripe.Checkout.Session>> { (session) => session.userId === user.id,
const sessionId = this.stripeSessions.get(user.id); );
if (!sessionId) { if (!stripeSession) {
throw new NotFoundException(); throw new NotFoundException();
} }
return this.stripe.checkout.sessions.retrieve(sessionId);
const session = await this.stripe.checkout.sessions.retrieve(
stripeSession.sessionId,
);
return {
isPaid: session.payment_status === 'paid',
purchaseId: stripeSession.purchaseId,
};
} }
} }