70 lines
2.7 KiB
TypeScript
70 lines
2.7 KiB
TypeScript
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);
|
|
});
|