import { NextRequest, NextResponse } from "next/server" import prisma from "@/lib/prisma" import { sendWhatsAppMessage } from "@/lib/whatsapp" import { setQrValue } from "@/lib/qr-store" /** * WAHA webhook — receives incoming WhatsApp messages + session status events * Handles: PAID, HELP, CANCEL commands from donors * Also captures QR code value for the pairing flow */ export async function POST(request: NextRequest) { try { const body = await request.json() const { event, payload } = body // Handle session.status events (for QR code capture) if (event === "session.status") { console.log("[WAHA] Session status:", JSON.stringify({ status: payload?.status, hasQr: !!payload?.qr, keys: Object.keys(payload || {}) })) // WAHA sends QR in different formats depending on version const qrVal = payload?.qr?.value || payload?.qr || payload?.qrCode if (payload?.status === "SCAN_QR_CODE" && qrVal && typeof qrVal === "string") { setQrValue(qrVal) console.log("[WAHA] QR value stored, length:", qrVal.length) } return NextResponse.json({ ok: true }) } // WAHA sends message events if (!payload?.body || !payload?.from) { return NextResponse.json({ ok: true }) } let text = payload.body.trim().toUpperCase() const fromPhone = payload.from.replace("@c.us", "") // Alias STOP / UNSUBSCRIBE / OPT OUT → CANCEL (PECR compliance) if (["STOP", "UNSUBSCRIBE", "OPT OUT", "OPTOUT"].includes(text)) { text = "CANCEL" } // Only handle known commands — all others go to AI NLU (if enabled) if (!["PAID", "HELP", "CANCEL", "STATUS"].includes(text)) { // AI-10: Natural Language Understanding for non-keyword messages try { const { classifyDonorMessage } = await import("@/lib/ai") const intent = await classifyDonorMessage(payload.body.trim(), fromPhone) if (intent && intent.confidence >= 0.8 && ["PAID", "HELP", "CANCEL", "STATUS"].includes(intent.action)) { text = intent.action } else { return NextResponse.json({ ok: true }) } } catch { return NextResponse.json({ ok: true }) } } if (!prisma) return NextResponse.json({ ok: true }) // Find pledges by this phone number // Normalize: try with and without country code const phoneVariants = [fromPhone] if (fromPhone.startsWith("44")) { phoneVariants.push("0" + fromPhone.slice(2)) phoneVariants.push("+" + fromPhone) } const pledges = await prisma.pledge.findMany({ where: { donorPhone: { in: phoneVariants }, status: { in: ["new", "initiated"] }, }, include: { event: { select: { name: true } }, paymentInstruction: true, }, orderBy: { createdAt: "desc" }, take: 5, }) if (pledges.length === 0) { await sendWhatsAppMessage(fromPhone, `We couldn't find any pending pledges for this number. If you need help, please contact the charity directly.`) return NextResponse.json({ ok: true }) } const pledge = pledges[0] // Most recent const amount = (pledge.amountPence / 100).toFixed(0) switch (text) { case "PAID": { await prisma.pledge.update({ where: { id: pledge.id }, data: { status: "initiated", iPaidClickedAt: new Date() }, }) await sendWhatsAppMessage(fromPhone, `✅ Thanks! We've noted that you've paid your *£${amount}* pledge to ${pledge.event.name}.\n\nWe'll confirm once the payment is matched. Ref: \`${pledge.reference}\`` ) break } case "HELP": { const bankDetails = pledge.paymentInstruction?.bankDetails as Record | null if (bankDetails) { await sendWhatsAppMessage(fromPhone, `🏦 *Bank Details for your £${amount} pledge:*\n\nSort Code: \`${bankDetails.sortCode}\`\nAccount: \`${bankDetails.accountNo}\`\nName: ${bankDetails.accountName}\nReference: \`${pledge.reference}\`\n\n⚠️ _Use the exact reference above_` ) } else { await sendWhatsAppMessage(fromPhone, `Your pledge ref is \`${pledge.reference}\` for £${amount} to ${pledge.event.name}.\n\nContact the charity for payment details.` ) } break } case "CANCEL": { await prisma.pledge.update({ where: { id: pledge.id }, data: { status: "cancelled", cancelledAt: new Date() }, }) // Revoke WhatsApp consent for ALL pledges from this number (PECR compliance) await prisma.pledge.updateMany({ where: { donorPhone: { in: phoneVariants }, whatsappOptIn: true }, data: { whatsappOptIn: false }, }) // Skip all pending reminders for cancelled pledge await prisma.reminder.updateMany({ where: { pledgeId: pledge.id, status: "pending" }, data: { status: "skipped" }, }) await sendWhatsAppMessage(fromPhone, `Your £${amount} pledge to ${pledge.event.name} has been cancelled. You won't receive any more messages from us. Thank you for considering! 🙏` ) break } case "STATUS": { const statusText = pledges.map(p => `• £${(p.amountPence / 100).toFixed(0)} → ${p.event.name} (${p.status}) [${p.reference}]` ).join("\n") await sendWhatsAppMessage(fromPhone, `📋 *Your pledges:*\n\n${statusText}`) break } } return NextResponse.json({ ok: true }) } catch (error) { console.error("WhatsApp webhook error:", error) return NextResponse.json({ ok: true }) } }