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); });