diff --git a/backend/src/routes/bankingWebhook.ts b/backend/src/routes/bankingWebhook.ts new file mode 100644 index 0000000..f3a19b3 --- /dev/null +++ b/backend/src/routes/bankingWebhook.ts @@ -0,0 +1,69 @@ +import { Router, Request, Response } from 'express'; +import express from 'express'; +import { PrismaClient } from '@prisma/client'; +import { verifyWebhookSignature } from '../modules/banking/signature'; +import { PaymentService } from '../modules/banking/paymentService'; +import { IncomingBankWebhookPayload } from '../modules/banking/types'; + +const prisma = new PrismaClient(); +const paymentService = new PaymentService(prisma); + +/** + * Router NUR für den Banking-Webhook. Trägt seinen eigenen + * `express.raw()`-Body-Parser direkt an der Route, damit er unabhängig + * davon funktioniert, wie der globale JSON-Parser in app.ts registriert ist + * (kein Risiko, dass der Stream doppelt gelesen wird). + */ +export const bankingWebhookRawRouter = Router(); + +const BANKING_WEBHOOK_SECRET = process.env.BANKING_WEBHOOK_SECRET!; +const WG_ACCOUNT_IBAN = process.env.WG_ACCOUNT_IBAN!; // Schutz vor Fremdkonto-Events + +bankingWebhookRawRouter.post( + '/webhooks/banking/transactions', + express.raw({ type: 'application/json' }), + async (req: Request, res: Response) => { + const rawBody: string = req.body instanceof Buffer ? req.body.toString('utf8') : ''; + const signature = req.header('X-Signature'); + + const isValid = verifyWebhookSignature(rawBody, signature, BANKING_WEBHOOK_SECRET); + if (!isValid) { + return res.status(401).json({ error: 'invalid_signature' }); + } + + let payload: IncomingBankWebhookPayload; + try { + payload = JSON.parse(rawBody); + } catch { + return res.status(400).json({ error: 'invalid_json' }); + } + + if (payload.accountIban !== WG_ACCOUNT_IBAN) { + // Kein Fehler, aber irrelevantes Konto -> ignorieren (200, damit Provider nicht retriggert) + return res.status(200).json({ status: 'ignored_account' }); + } + + if (payload.amount <= 0) { + // Nur Zahlungseingänge relevant, keine Abbuchungen + return res.status(200).json({ status: 'ignored_outgoing' }); + } + + try { + const result = await paymentService.processIncomingTransaction(payload); + return res.status(200).json(result); + } catch (err) { + console.error('[bankingWebhook] Verarbeitung fehlgeschlagen:', err); + // 500, damit der Provider (bei entsprechender Retry-Policy) erneut zustellt + return res.status(500).json({ error: 'processing_failed' }); + } + }, +); + +/** Router für die normalen (JSON-basierten) Banking-/Payment-Endpunkte. */ +export const paymentsRouter = Router(); + +/** Cockpit-Endpoint: liefert die Ampel-Übersicht für alle aktiven Verträge. */ +paymentsRouter.get('/payments/traffic-lights', async (_req: Request, res: Response) => { + const lights = await paymentService.getRentTrafficLights(); + res.status(200).json(lights); +});