From 7c8bb78f56ac48a10f2bbac12fd101ac35ceb330 Mon Sep 17 00:00:00 2001 From: MiguelMLorente Date: Sat, 22 Nov 2025 16:07:46 +0100 Subject: [PATCH] Create stripe integration skeleton --- package-lock.json | 21 ++++++++++++++++++ package.json | 1 + src/buy.controller.ts | 44 ++++++++++++++++++++++++++++++++++++++ src/dto/purchase-item.ts | 4 ++++ src/dto/purchase-status.ts | 6 ++++++ src/dto/purchase.ts | 11 ++++++++++ src/purchase.service.ts | 31 +++++++++++++++++++++++++++ src/user.service.ts | 8 +++++++ 8 files changed, 126 insertions(+) create mode 100644 src/buy.controller.ts create mode 100644 src/dto/purchase-item.ts create mode 100644 src/dto/purchase-status.ts create mode 100644 src/dto/purchase.ts create mode 100644 src/purchase.service.ts diff --git a/package-lock.json b/package-lock.json index e4aa045..75148b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@nestjs/platform-express": "^11.0.1", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1", + "stripe": "^20.0.0", "typeorm": "^0.3.27" }, "devDependencies": { @@ -9247,6 +9248,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/stripe": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-20.0.0.tgz", + "integrity": "sha512-EaZeWpbJOCcDytdjKSwdrL5BxzbDGNueiCfHjHXlPdBQvLqoxl6AAivC35SPzTmVXJb5duXQlXFGS45H0+e6Gg==", + "license": "MIT", + "dependencies": { + "qs": "^6.11.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "@types/node": ">=16" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, "node_modules/strtok3": { "version": "10.3.4", "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.4.tgz", diff --git a/package.json b/package.json index c9374b7..f1f424a 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "@nestjs/platform-express": "^11.0.1", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1", + "stripe": "^20.0.0", "typeorm": "^0.3.27" }, "devDependencies": { diff --git a/src/buy.controller.ts b/src/buy.controller.ts new file mode 100644 index 0000000..2e75bb6 --- /dev/null +++ b/src/buy.controller.ts @@ -0,0 +1,44 @@ +import { Controller, Get, Post, Redirect } from "@nestjs/common"; +import Stripe from 'stripe' +import { PurchaseService } from "./purchase.service"; +import { UserService } from "./user.service"; +import { PurchaseItem } from "./dto/purchase-item"; + +@Controller('/buy') +export class BuyController { + private static TEST_KEY = "sk_test_51SVqfTBQui1OXGpt6kgnIuDpbwAqQBZqNoVXkn77UeoxfOMgjC3xFuXCet1h51REAq9XeP72qBDWdXlbaTvxb4yN00YWF6TWFm" + private readonly stripe = new Stripe(BuyController.TEST_KEY) + + constructor(private readonly purchaseService: PurchaseService, private readonly userService: UserService) {} + + @Post("start") + @Redirect() + public async getStripeSessionUrl(userId: number) { + const user = this.userService.getUserById(userId) + const purchase = this.purchaseService.recordPurchaseStart(user, PurchaseItem.PAELLA, 1) + const session = await this.stripe.checkout.sessions.create({ + line_items: [ + { + price: purchase.purchasedProduct, + quantity: purchase.purchasedUnits + } + ], + mode: "payment", + success_url: "https://localhost:3000/buy/complete", + cancel_url: "https://localhost:3000/buy/cancel", + + }) + + return {url: session.url} + } + + @Get("complete") + public complete() { + + } + + @Get("cancel") + public cancel() { + + } +} \ No newline at end of file diff --git a/src/dto/purchase-item.ts b/src/dto/purchase-item.ts new file mode 100644 index 0000000..479e2ec --- /dev/null +++ b/src/dto/purchase-item.ts @@ -0,0 +1,4 @@ +export enum PurchaseItem { + PAELLA = "PAELLA", +} + diff --git a/src/dto/purchase-status.ts b/src/dto/purchase-status.ts new file mode 100644 index 0000000..5cb3a02 --- /dev/null +++ b/src/dto/purchase-status.ts @@ -0,0 +1,6 @@ +export enum PurchaseStatus { + CREATED = "CREATED", + COMPLETED = "COMPLETED", + FAILED = "FAILED", +} + diff --git a/src/dto/purchase.ts b/src/dto/purchase.ts new file mode 100644 index 0000000..ef8eedb --- /dev/null +++ b/src/dto/purchase.ts @@ -0,0 +1,11 @@ +import { PurchaseItem } from "./purchase-item"; +import { PurchaseStatus } from "./purchase-status"; +import { User } from "./user"; + +export class Purchase { + id: number; + purchasedBy: User; + purchasedProduct: PurchaseItem; + purchasedUnits: number; + status: PurchaseStatus +} \ No newline at end of file diff --git a/src/purchase.service.ts b/src/purchase.service.ts new file mode 100644 index 0000000..18af01c --- /dev/null +++ b/src/purchase.service.ts @@ -0,0 +1,31 @@ +import { Injectable, Logger } from "@nestjs/common"; +import { Purchase } from "./dto/purchase"; +import { User } from "./dto/user"; +import { PurchaseItem } from "./dto/purchase-item"; +import { PurchaseStatus } from "./dto/purchase-status"; + +@Injectable() +export class PurchaseService { + private readonly purchases: Purchase[] = []; + private readonly logger = new Logger(PurchaseService.name); + + constructor() {} + + public recordPurchaseStart(user: User, purchasedItem: PurchaseItem, units: number): Purchase { + this.logger.debug(`Recording purchase for user ID: ${user.id}`) + const purchase: Purchase = { + id: this.purchases.length, + purchasedBy: user, + purchasedProduct: purchasedItem, + purchasedUnits: units, + status: PurchaseStatus.CREATED + } + this.purchases.push(purchase) + return purchase; + } + + public findPurchasesByUser(userId: number): Purchase[] { + this.logger.debug(`Searching purchases for user ID: ${userId}`) + return this.purchases.filter(purchase => purchase.purchasedBy.id = userId); + } +} \ No newline at end of file diff --git a/src/user.service.ts b/src/user.service.ts index 71e79a9..0b4cf86 100644 --- a/src/user.service.ts +++ b/src/user.service.ts @@ -15,6 +15,14 @@ export class UserService { return user; } + public getUserById(userId: number): User { + const user = this.users.find((u) => u.id === userId); + if (!user) { + throw new NotFoundException(); + } + return user; + } + public createUser(name: string, password: string): void { try { this.getUserByName(name);