From d4d7ae902182926dcdb81ead92bb4d357f739afc Mon Sep 17 00:00:00 2001 From: oomokaro1 Date: Wed, 8 Jul 2026 20:33:38 +0100 Subject: [PATCH 1/3] feat(checkout): add session payment endpoint and remove TypeORM - Add GET /v1/checkout/sessions/:id/payment endpoint (API key auth) - Add webhook_endpoints and payment_links tables to Drizzle schema - Remove @nestjs/typeorm and typeorm dependencies - Replace TypeOrmHealthIndicator with Drizzle raw SQL health check - Update smoke test and webhook controller test for new architecture --- package-lock.json | 286 +++-------------------- package.json | 2 - src/__tests__/app.smoke.spec.ts | 30 +-- src/__tests__/webhook-controller.spec.ts | 10 +- src/app.module.ts | 9 +- src/checkout/checkout.controller.ts | 6 + src/checkout/checkout.service.ts | 24 +- src/db/schema.ts | 30 +++ src/monitoring/health.controller.ts | 16 +- 9 files changed, 118 insertions(+), 295 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0a7ccd6..bd06d07 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,6 @@ "@nestjs/platform-express": "^10.0.0", "@nestjs/schedule": "^6.1.3", "@nestjs/terminus": "^10.1.0", - "@nestjs/typeorm": "^10.0.0", "@stellar/stellar-sdk": "^12.0.0", "axios": "^1.6.0", "class-transformer": "^0.5.1", @@ -32,7 +31,6 @@ "prom-client": "^15.0.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.0", - "typeorm": "^0.3.17", "uuid": "^9.0.0", "zod": "^4.4.3" }, @@ -1872,6 +1870,7 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "devOptional": true, "license": "ISC", "dependencies": { "string-width": "^5.1.2", @@ -1889,6 +1888,7 @@ "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=12" @@ -1901,6 +1901,7 @@ "version": "6.2.3", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=12" @@ -1913,12 +1914,14 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "devOptional": true, "license": "MIT" }, "node_modules/@isaacs/cliui/node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "devOptional": true, "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", @@ -1936,6 +1939,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "devOptional": true, "license": "MIT", "dependencies": { "ansi-regex": "^6.2.2" @@ -1951,6 +1955,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "devOptional": true, "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", @@ -2818,22 +2823,6 @@ } } }, - "node_modules/@nestjs/typeorm": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-10.0.2.tgz", - "integrity": "sha512-H738bJyydK4SQkRCTeh1aFBxoO1E9xdL/HaLGThwrqN95os5mEyAtK7BLADOS+vldP4jDZ2VQPLj4epWwRqCeQ==", - "license": "MIT", - "dependencies": { - "uuid": "9.0.1" - }, - "peerDependencies": { - "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", - "@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0", - "reflect-metadata": "^0.1.13 || ^0.2.0", - "rxjs": "^7.2.0", - "typeorm": "^0.3.0" - } - }, "node_modules/@noble/hashes": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", @@ -2979,12 +2968,6 @@ "@sinonjs/commons": "^3.0.0" } }, - "node_modules/@sqltools/formatter": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz", - "integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==", - "license": "MIT" - }, "node_modules/@stellar/js-xdr": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@stellar/js-xdr/-/js-xdr-3.1.2.tgz", @@ -4151,15 +4134,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/ansis": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.3.0.tgz", - "integrity": "sha512-44mvgtPvohuU/70DdY5Oz2AIrLJ9k6/5x4KmoSvPwO+5Moijo0+N9D0fKbbYZQWP1hNm5CpOf+E01jhxG/r8xg==", - "license": "ISC", - "engines": { - "node": ">=14" - } - }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -4187,15 +4161,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/app-root-path": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz", - "integrity": "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==", - "license": "MIT", - "engines": { - "node": ">= 6.0.0" - } - }, "node_modules/append-field": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", @@ -4409,6 +4374,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "devOptional": true, "license": "MIT" }, "node_modules/bare-addon-resolve": { @@ -5020,6 +4986,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "devOptional": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -5300,6 +5267,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "devOptional": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -5314,12 +5282,14 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "devOptional": true, "license": "ISC" }, "node_modules/cross-spawn/node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "devOptional": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -5331,12 +5301,6 @@ "node": ">= 8" } }, - "node_modules/dayjs": { - "version": "1.11.20", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz", - "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==", - "license": "MIT" - }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -5350,6 +5314,7 @@ "version": "1.7.2", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", + "devOptional": true, "license": "MIT", "peerDependencies": { "babel-plugin-macros": "^3.1.0" @@ -5698,6 +5663,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "devOptional": true, "license": "MIT" }, "node_modules/ecdsa-sig-formatter": { @@ -5920,6 +5886,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=6" @@ -6778,6 +6745,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "devOptional": true, "license": "ISC", "dependencies": { "cross-spawn": "^7.0.6", @@ -6982,6 +6950,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "devOptional": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -7911,6 +7880,7 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "devOptional": true, "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" @@ -9087,6 +9057,7 @@ "version": "7.1.3", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "devOptional": true, "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" @@ -9402,6 +9373,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "devOptional": true, "license": "BlueOak-1.0.0" }, "node_modules/parent-module": { @@ -9505,6 +9477,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -9521,6 +9494,7 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "devOptional": true, "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", @@ -9537,6 +9511,7 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "devOptional": true, "license": "ISC" }, "node_modules/path-to-regexp": { @@ -10086,6 +10061,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -10473,6 +10449,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "devOptional": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -10485,6 +10462,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -10579,6 +10557,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "devOptional": true, "license": "ISC", "engines": { "node": ">=14" @@ -10661,22 +10640,6 @@ "dev": true, "license": "BSD-3-Clause" }, - "node_modules/sql-highlight": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/sql-highlight/-/sql-highlight-6.1.0.tgz", - "integrity": "sha512-ed7OK4e9ywpE7pgRMkMQmZDPKSVdm0oX5IEtZiKnFucSF0zu6c80GZBe38UqHuVhTWJ9xsKgSMjCG2bml86KvA==", - "funding": [ - "https://github.com/scriptcoded/sql-highlight?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/scriptcoded" - } - ], - "license": "MIT", - "engines": { - "node": ">=14" - } - }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -10765,6 +10728,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "devOptional": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -10792,6 +10756,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "devOptional": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -11528,195 +11493,6 @@ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", "license": "MIT" }, - "node_modules/typeorm": { - "version": "0.3.29", - "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.29.tgz", - "integrity": "sha512-wwPEX/df4l72gCmOsrs0otJZYLGA9lLQkUZCkukbsymEycV4zXv2KM7wU7v2r8L01TaCgY9ApSSqHQWBOUhEoQ==", - "license": "MIT", - "dependencies": { - "@sqltools/formatter": "^1.2.5", - "ansis": "^4.2.0", - "app-root-path": "^3.1.0", - "buffer": "^6.0.3", - "dayjs": "^1.11.20", - "debug": "^4.4.3", - "dedent": "^1.7.2", - "dotenv": "^16.6.1", - "glob": "^10.5.0", - "reflect-metadata": "^0.2.2", - "sha.js": "^2.4.12", - "sql-highlight": "^6.1.0", - "tslib": "^2.8.1", - "uuid": "^11.1.1", - "yargs": "^17.7.2" - }, - "bin": { - "typeorm": "cli.js", - "typeorm-ts-node-commonjs": "cli-ts-node-commonjs.js", - "typeorm-ts-node-esm": "cli-ts-node-esm.js" - }, - "engines": { - "node": ">=16.13.0" - }, - "funding": { - "url": "https://opencollective.com/typeorm" - }, - "peerDependencies": { - "@google-cloud/spanner": "^5.18.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", - "@sap/hana-client": "^2.14.22", - "better-sqlite3": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0", - "ioredis": "^5.0.4", - "mongodb": "^5.8.0 || ^6.0.0", - "mssql": "^9.1.1 || ^10.0.0 || ^11.0.0 || ^12.0.0", - "mysql2": "^2.2.5 || ^3.0.1", - "oracledb": "^6.3.0", - "pg": "^8.5.1", - "pg-native": "^3.0.0", - "pg-query-stream": "^4.0.0", - "redis": "^3.1.1 || ^4.0.0 || ^5.0.14", - "sql.js": "^1.4.0", - "sqlite3": "^5.0.3", - "ts-node": "^10.7.0", - "typeorm-aurora-data-api-driver": "^2.0.0 || ^3.0.0" - }, - "peerDependenciesMeta": { - "@google-cloud/spanner": { - "optional": true - }, - "@sap/hana-client": { - "optional": true - }, - "better-sqlite3": { - "optional": true - }, - "ioredis": { - "optional": true - }, - "mongodb": { - "optional": true - }, - "mssql": { - "optional": true - }, - "mysql2": { - "optional": true - }, - "oracledb": { - "optional": true - }, - "pg": { - "optional": true - }, - "pg-native": { - "optional": true - }, - "pg-query-stream": { - "optional": true - }, - "redis": { - "optional": true - }, - "sql.js": { - "optional": true - }, - "sqlite3": { - "optional": true - }, - "ts-node": { - "optional": true - }, - "typeorm-aurora-data-api-driver": { - "optional": true - } - } - }, - "node_modules/typeorm/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/typeorm/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/typeorm/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/typeorm/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/typeorm/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/typeorm/node_modules/reflect-metadata": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", - "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", - "license": "Apache-2.0" - }, - "node_modules/typeorm/node_modules/uuid": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", - "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/esm/bin/uuid" - } - }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -12115,6 +11891,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "devOptional": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -12169,6 +11946,7 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "devOptional": true, "license": "ISC", "engines": { "node": ">=10" @@ -12185,6 +11963,7 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "devOptional": true, "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -12203,6 +11982,7 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "devOptional": true, "license": "ISC", "engines": { "node": ">=12" diff --git a/package.json b/package.json index 4499e01..437b495 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,6 @@ "@nestjs/platform-express": "^10.0.0", "@nestjs/schedule": "^6.1.3", "@nestjs/terminus": "^10.1.0", - "@nestjs/typeorm": "^10.0.0", "@stellar/stellar-sdk": "^12.0.0", "axios": "^1.6.0", "class-transformer": "^0.5.1", @@ -47,7 +46,6 @@ "prom-client": "^15.0.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.0", - "typeorm": "^0.3.17", "uuid": "^9.0.0", "zod": "^4.4.3" }, diff --git a/src/__tests__/app.smoke.spec.ts b/src/__tests__/app.smoke.spec.ts index d8ceec1..ef36558 100644 --- a/src/__tests__/app.smoke.spec.ts +++ b/src/__tests__/app.smoke.spec.ts @@ -1,25 +1,19 @@ import { INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; -import { TypeOrmHealthIndicator } from '@nestjs/terminus'; import request from 'supertest'; import { AppModule } from '../app.module'; import { CorsOriginsCacheService } from '../middleware/cors-origins-cache.service'; import { PaymentDetectorService } from '../payments/payment-detector.service'; import { RedisService } from '../redis/redis.service'; -jest.mock('@nestjs/typeorm', () => { - const actual = jest.requireActual('@nestjs/typeorm'); - - return { - ...actual, - TypeOrmModule: { - ...actual.TypeOrmModule, - forRoot: jest.fn(() => ({ - module: class MockTypeOrmModule {}, - })), - }, - }; -}); +jest.mock('../db/index', () => ({ + db: { + execute: jest.fn().mockResolvedValue([]), + query: {}, + }, + client: {}, + schema: {}, +})); describe('App smoke test', () => { let app: INestApplication; @@ -37,14 +31,6 @@ describe('App smoke test', () => { const module: TestingModule = await Test.createTestingModule({ imports: [AppModule], }) - .overrideProvider(TypeOrmHealthIndicator) - .useValue({ - pingCheck: jest.fn().mockResolvedValue({ - database: { - status: 'up', - }, - }), - }) .overrideProvider(RedisService) .useValue({ getClient: jest.fn(() => redisClient), diff --git a/src/__tests__/webhook-controller.spec.ts b/src/__tests__/webhook-controller.spec.ts index 63bbd2b..9efbca8 100644 --- a/src/__tests__/webhook-controller.spec.ts +++ b/src/__tests__/webhook-controller.spec.ts @@ -17,10 +17,12 @@ function build() { } describe('WebhookController', () => { - it('is JWT-guarded at the class level', () => { - const guards = Reflect.getMetadata('__guards__', WebhookController); - expect(guards).toBeDefined(); - expect(guards).toHaveLength(1); + it('dashboard endpoints are JWT-guarded individually', () => { + const deliveryGuards = Reflect.getMetadata( + '__guards__', + WebhookController.prototype.listDeliveries, + ); + expect(deliveryGuards).toBeDefined(); }); describe('merchant scoping', () => { diff --git a/src/app.module.ts b/src/app.module.ts index b6863ce..3c746b8 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,5 +1,4 @@ import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; import { ScheduleModule } from '@nestjs/schedule'; import { AuthModule } from './auth/auth.module'; import { MerchantsModule } from './merchants/merchants.module'; @@ -12,6 +11,7 @@ import { RedisModule } from './redis/redis.module'; import { RateLimitModule } from './api/middleware/rate-limit.module'; import { AuditModule } from './audit/audit.module'; import { ConfigModule } from './config/config.module'; +import { PaymentLinksModule } from './payment-links/payment-links.module'; import { DynamicCorsMiddleware } from './middleware/dynamic-cors.middleware'; import { SecurityHeadersMiddleware } from './middleware/security-headers.middleware'; @@ -19,12 +19,6 @@ import { SecurityHeadersMiddleware } from './middleware/security-headers.middlew imports: [ ConfigModule, ScheduleModule.forRoot(), - TypeOrmModule.forRoot({ - type: 'postgres', - url: process.env.DATABASE_URL, - autoLoadEntities: true, - synchronize: process.env.NODE_ENV !== 'production', - }), RedisModule, RateLimitModule, AuditModule, @@ -32,6 +26,7 @@ import { SecurityHeadersMiddleware } from './middleware/security-headers.middlew MerchantsModule, CheckoutModule, PaymentsModule, + PaymentLinksModule, StellarModule, WebhookModule, MonitoringModule, diff --git a/src/checkout/checkout.controller.ts b/src/checkout/checkout.controller.ts index 46fe173..ddd9d21 100644 --- a/src/checkout/checkout.controller.ts +++ b/src/checkout/checkout.controller.ts @@ -21,6 +21,12 @@ export class CheckoutController { return this.checkout.getSession(id); } + @UseGuards(ApiKeyGuard) + @Get('sessions/:id/payment') + getSessionPayment(@Param('id') id: string) { + return this.checkout.getSessionPayment(id); + } + @UseGuards(ApiKeyGuard, RolesGuard, ResourceOwnershipGuard) @Roles('admin', 'merchant') @ResourceOwner('checkout_session') diff --git a/src/checkout/checkout.service.ts b/src/checkout/checkout.service.ts index d6a2c72..7785a1c 100644 --- a/src/checkout/checkout.service.ts +++ b/src/checkout/checkout.service.ts @@ -1,7 +1,7 @@ import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { db } from '../db/index'; -import { checkoutSessions } from '../db/schema'; +import { checkoutSessions, payments } from '../db/schema'; import { eq, and } from 'drizzle-orm'; import * as crypto from 'crypto'; import { AuditService } from '../audit/audit.service'; @@ -129,4 +129,26 @@ export class CheckoutService { .returning(); return updated; } + + async getSessionPayment(sessionId: string) { + const session = await db.query.checkoutSessions.findFirst({ + where: eq(checkoutSessions.id, sessionId), + }); + if (!session) throw new NotFoundException('Session not found'); + + const payment = await db.query.payments.findFirst({ + where: eq(payments.sessionId, sessionId), + }); + if (!payment) throw new NotFoundException('Payment not found for session'); + + return { + id: payment.id, + sessionId: payment.sessionId, + txHash: payment.txHash, + amount: payment.amount, + asset: payment.assetCode, + sender: payment.senderAddress, + confirmedAt: payment.confirmedAt, + }; + } } diff --git a/src/db/schema.ts b/src/db/schema.ts index 720728b..95fc58e 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -152,6 +152,34 @@ export const webhookDeadLetters = pgTable('webhook_dead_letters', { createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), }); +export const webhookEndpoints = pgTable('webhook_endpoints', { + id: uuid('id').defaultRandom().primaryKey(), + merchantId: uuid('merchant_id') + .notNull() + .references(() => merchants.id, { onDelete: 'cascade' }), + url: text('url').notNull(), + events: jsonb('events').notNull().default([]), + secret: text('secret').notNull(), + isActive: boolean('is_active').notNull().default(true), + createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), +}); + +export const paymentLinks = pgTable('payment_links', { + id: uuid('id').defaultRandom().primaryKey(), + merchantId: uuid('merchant_id') + .notNull() + .references(() => merchants.id, { onDelete: 'cascade' }), + amount: numeric('amount', { precision: 36, scale: 7 }).notNull(), + assetCode: text('asset_code').notNull(), + assetIssuer: text('asset_issuer'), + description: text('description'), + displayCurrency: text('display_currency'), + metadata: jsonb('metadata'), + active: boolean('active').notNull().default(true), + expiresAt: timestamp('expires_at', { withTimezone: true }), + createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), +}); + export const auditLogs = pgTable('audit_logs', { id: uuid('id').defaultRandom().primaryKey(), merchantId: uuid('merchant_id').references(() => merchants.id), @@ -171,5 +199,7 @@ export const schema = { payments, webhookDeliveries, webhookDeadLetters, + webhookEndpoints, + paymentLinks, auditLogs, }; diff --git a/src/monitoring/health.controller.ts b/src/monitoring/health.controller.ts index 8019b9a..b408e0f 100644 --- a/src/monitoring/health.controller.ts +++ b/src/monitoring/health.controller.ts @@ -1,16 +1,20 @@ import { Controller, Get } from '@nestjs/common'; -import { HealthCheck, HealthCheckService, TypeOrmHealthIndicator } from '@nestjs/terminus'; +import { HealthCheck, HealthCheckService } from '@nestjs/terminus'; +import { db } from '../db/index'; +import { sql } from 'drizzle-orm'; @Controller('health') export class HealthController { - constructor( - private health: HealthCheckService, - private db: TypeOrmHealthIndicator, - ) {} + constructor(private health: HealthCheckService) {} @Get() @HealthCheck() check() { - return this.health.check([() => this.db.pingCheck('database')]); + return this.health.check([ + async () => { + await db.execute(sql`SELECT 1`); + return { database: { status: 'up' } }; + }, + ]); } } From 072510b4f448f6b71eb2d3b42595820045bc1e4e Mon Sep 17 00:00:00 2001 From: oomokaro1 Date: Wed, 8 Jul 2026 20:34:34 +0100 Subject: [PATCH 2/3] feat(webhooks): add webhook endpoints CRUD for SDK integration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add POST /v1/webhooks — create webhook endpoint (API key auth) - Add GET /v1/webhooks — list webhook endpoints (API key auth) - Add DELETE /v1/webhooks/:id — delete webhook endpoint (API key auth) - Add createEndpoint, listEndpoints, deleteEndpoint to WebhookService - Existing JWT-guarded dashboard routes (deliveries, dead-letter) unchanged --- src/webhook/webhook.controller.ts | 55 ++++++++++++++++++++++++------- src/webhook/webhook.service.ts | 41 ++++++++++++++++++++++- 2 files changed, 84 insertions(+), 12 deletions(-) diff --git a/src/webhook/webhook.controller.ts b/src/webhook/webhook.controller.ts index 25a777b..c588b2b 100644 --- a/src/webhook/webhook.controller.ts +++ b/src/webhook/webhook.controller.ts @@ -6,6 +6,7 @@ import { Param, ParseUUIDPipe, Post, + Body, Query, Request, UseGuards, @@ -13,57 +14,89 @@ import { import { AuthGuard } from '@nestjs/passport'; import { WebhookService } from './webhook.service'; import { MerchantsService } from '../merchants/merchants.service'; +import { ApiKeyGuard } from '../auth/api-key.guard'; +import { IsArray, IsString, IsUrl } from 'class-validator'; + +class CreateWebhookEndpointDto { + @IsUrl({}, { message: 'Invalid URL format' }) + url: string; + + @IsArray() + @IsString({ each: true }) + events: string[]; +} @Controller('v1/webhooks') -@UseGuards(AuthGuard('jwt')) export class WebhookController { constructor( private readonly webhooks: WebhookService, private readonly merchants: MerchantsService, ) {} - private async merchantId(req: any): Promise { + private async merchantIdFromJwt(req: any): Promise { const merchant = await this.merchants.findByWallet(req.user.walletAddress); if (!merchant) throw new NotFoundException('Merchant not found'); return merchant.id; } - /** List recent webhook deliveries for the authenticated merchant. */ + // ── SDK-facing endpoints (API key auth) ── + + @UseGuards(ApiKeyGuard) + @Post() + async createEndpoint(@Request() req: any, @Body() dto: CreateWebhookEndpointDto) { + return this.webhooks.createEndpoint(req.merchantId, dto.url, dto.events); + } + + @UseGuards(ApiKeyGuard) + @Get() + async listEndpoints(@Request() req: any) { + return this.webhooks.listEndpoints(req.merchantId); + } + + // ── Dashboard-facing endpoints (JWT auth) ── + + @UseGuards(AuthGuard('jwt')) @Get('deliveries') async listDeliveries(@Request() req: any, @Query('limit') limit?: string) { - const merchantId = await this.merchantId(req); + const merchantId = await this.merchantIdFromJwt(req); return this.webhooks.listDeliveries(merchantId, this.clampLimit(limit)); } - /** Bound a client-supplied limit to a sane [1, 100] range (default 50). */ private clampLimit(limit?: string): number { const n = Number(limit); if (!Number.isFinite(n) || n <= 0) return 50; return Math.min(Math.floor(n), 100); } - /** List dead-letter entries for the authenticated merchant. */ + @UseGuards(AuthGuard('jwt')) @Get('dead-letter') async listDeadLetter(@Request() req: any) { - const merchantId = await this.merchantId(req); + const merchantId = await this.merchantIdFromJwt(req); return this.webhooks.listDeadLetters(merchantId); } - /** Manually retry a dead-letter entry. */ + @UseGuards(AuthGuard('jwt')) @Post('dead-letter/:id/retry') async retryDeadLetter(@Request() req: any, @Param('id', ParseUUIDPipe) id: string) { - const merchantId = await this.merchantId(req); + const merchantId = await this.merchantIdFromJwt(req); const result = await this.webhooks.retryDeadLetter(merchantId, id); if (!result) throw new NotFoundException('Dead-letter entry not found'); return { status: 'requeued', ...result }; } - /** Dismiss (delete) a dead-letter entry. */ + @UseGuards(AuthGuard('jwt')) @Delete('dead-letter/:id') async dismissDeadLetter(@Request() req: any, @Param('id', ParseUUIDPipe) id: string) { - const merchantId = await this.merchantId(req); + const merchantId = await this.merchantIdFromJwt(req); const ok = await this.webhooks.dismissDeadLetter(merchantId, id); if (!ok) throw new NotFoundException('Dead-letter entry not found'); return { status: 'dismissed' }; } + + @UseGuards(ApiKeyGuard) + @Delete(':id') + async deleteEndpoint(@Request() req: any, @Param('id', ParseUUIDPipe) id: string) { + const ok = await this.webhooks.deleteEndpoint(req.merchantId, id); + if (!ok) throw new NotFoundException('Webhook endpoint not found'); + } } diff --git a/src/webhook/webhook.service.ts b/src/webhook/webhook.service.ts index 147cfd6..0dd32ad 100644 --- a/src/webhook/webhook.service.ts +++ b/src/webhook/webhook.service.ts @@ -1,7 +1,8 @@ import { Injectable, Logger } from '@nestjs/common'; import { db } from '../db/index'; -import { webhookDeadLetters, webhookDeliveries, merchants } from '../db/schema'; +import { webhookDeadLetters, webhookDeliveries, webhookEndpoints, merchants } from '../db/schema'; import { and, desc, eq } from 'drizzle-orm'; +import * as crypto from 'crypto'; import { WebhookQueueService } from './webhook-queue.service'; @Injectable() @@ -62,4 +63,42 @@ export class WebhookService { .returning(); return !!deleted; } + + // ── Webhook Endpoints CRUD ── + + async createEndpoint(merchantId: string, url: string, events: string[]) { + const secret = 'whsec_' + crypto.randomBytes(24).toString('hex'); + const [endpoint] = await db + .insert(webhookEndpoints) + .values({ merchantId, url, events, secret } as any) + .returning(); + return this.toEndpointDto(endpoint); + } + + async listEndpoints(merchantId: string) { + const endpoints = await db.query.webhookEndpoints.findMany({ + where: eq(webhookEndpoints.merchantId, merchantId), + orderBy: [desc(webhookEndpoints.createdAt)], + }); + return endpoints.map((e) => this.toEndpointDto(e)); + } + + async deleteEndpoint(merchantId: string, endpointId: string): Promise { + const [deleted] = await db + .delete(webhookEndpoints) + .where(and(eq(webhookEndpoints.id, endpointId), eq(webhookEndpoints.merchantId, merchantId))) + .returning(); + return !!deleted; + } + + private toEndpointDto(endpoint: any) { + return { + id: endpoint.id, + url: endpoint.url, + events: endpoint.events, + secret: endpoint.secret, + active: endpoint.isActive, + createdAt: endpoint.createdAt, + }; + } } From de71fa07fa836bf31201ed674409ab0a6ffc938e Mon Sep 17 00:00:00 2001 From: oomokaro1 Date: Wed, 8 Jul 2026 20:44:48 +0100 Subject: [PATCH 3/3] fix: remove PaymentLinksModule import (belongs in PR #39) --- src/app.module.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/app.module.ts b/src/app.module.ts index 3c746b8..852fe96 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -11,7 +11,6 @@ import { RedisModule } from './redis/redis.module'; import { RateLimitModule } from './api/middleware/rate-limit.module'; import { AuditModule } from './audit/audit.module'; import { ConfigModule } from './config/config.module'; -import { PaymentLinksModule } from './payment-links/payment-links.module'; import { DynamicCorsMiddleware } from './middleware/dynamic-cors.middleware'; import { SecurityHeadersMiddleware } from './middleware/security-headers.middleware'; @@ -26,7 +25,6 @@ import { SecurityHeadersMiddleware } from './middleware/security-headers.middlew MerchantsModule, CheckoutModule, PaymentsModule, - PaymentLinksModule, StellarModule, WebhookModule, MonitoringModule,