import 'dotenv/config'; import crypto from 'crypto'; import { PrismaClient } from '@prisma/client'; /** * Simuliert eine eingehende Bank-Transaktion und schickt sie signiert an den * lokal laufenden Webhook-Endpokt. Nutzung (nach `npm run seed` + `npm run dev`): * * npx ts-node scripts/send-test-webhook.ts "Zimmer 1" * * Ohne Argument wird die erste offene Zahlung aus der DB genommen. */ async function main() { const prisma = new PrismaClient(); const roomFilter = process.argv[2]; const payment = await prisma.payment.findFirst({ where: { status: 'PENDING', ...(roomFilter ? { contract: { room: { roomNumber: { equals: roomFilter } } } } : {}), }, include: { contract: { include: { room: true, user: true } } }, }); if (!payment) { console.error('Keine offene Zahlung gefunden. Vorher `npm run seed` ausführen.'); process.exit(1); } const body = { transactionId: `test-tx-${Date.now()}`, accountIban: process.env.WG_ACCOUNT_IBAN ?? 'DE89370400440532013000', amount: Number(payment.amount), currency: 'EUR', remittanceInfo: `Miete ${payment.contract.room.roomNumber} ${payment.contract.user.fullName}`, bookingDate: new Date().toISOString(), signature: '', // wird unten berechnet }; const rawBodyWithoutSignature = JSON.stringify({ ...body, signature: '' }); const secret = process.env.BANKING_WEBHOOK_SECRET ?? 'local_dev_secret_change_me'; const signature = crypto.createHmac('sha256', secret).update(rawBodyWithoutSignature, 'utf8').digest('hex'); // Der Server berechnet die Signatur über den kompletten rohen Body inkl. // Feld "signature" (leer) — daher senden wir exakt denselben String, den // wir signiert haben, nicht ein neu serialisiertes Objekt. const res = await fetch('http://localhost:3000/v1/webhooks/banking/transactions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Signature': signature, }, body: rawBodyWithoutSignature, }); console.log('Status:', res.status); console.log(await res.json()); await prisma.$disconnect(); } main().catch((err) => { console.error(err); process.exit(1); });