import { NextRequest, NextResponse } from "next/server"; import { prisma } from "@/lib/prisma"; import Stripe from "stripe"; import { TaskStatus } from "@agent-bounty/contracts"; import { broadcastFomoEvent } from "@/lib/x-broadcaster"; import { sanitizeAgentId } from "@/lib/a2a-growth"; const stripe = process.env.STRIPE_SECRET_KEY ? new Stripe(process.env.STRIPE_SECRET_KEY, { apiVersion: "2026-05-27.dahlia", }) : null; function getStripeObjectId(value: string | Stripe.PaymentIntent | Stripe.SetupIntent | null) { if (typeof value === "string") return value; return value?.id || null; } async function handleDemandProposalFee(session: Stripe.Checkout.Session) { const metadata = session.metadata || {}; const taskId = metadata.task_id; const referralAgent = sanitizeAgentId(metadata.referral_agent); const proposalFeeCents = Number(metadata.proposal_fee_cents || session.amount_total || 0); const paymentIntentId = getStripeObjectId(session.payment_intent); const task = await prisma.task.findFirst({ where: taskId ? { id: taskId } : { stripe_checkout_session_id: session.id }, }); if (!task) { console.error(`[Webhook] Proposal task not found for session: ${session.id}`); return; } await prisma.$transaction(async (tx) => { await tx.task.update({ where: { id: task.id }, data: { stripe_checkout_session_id: session.id, stripe_payment_intent_id: paymentIntentId, status: TaskStatus.DRAFT, }, }); if (referralAgent) { await tx.agentProfile.upsert({ where: { agent_id: referralAgent }, update: { discovery_source: "DEMAND_PROPOSAL_PAID_REFERRAL", }, create: { agent_id: referralAgent, type: "SCOUT", status: "PENDING", discovery_source: "DEMAND_PROPOSAL_PAID_REFERRAL", capabilities: { growth_referral: true, source: metadata.source || "stripe-webhook", }, }, }); await tx.scoutReputation.upsert({ where: { scout_id: referralAgent }, update: { successful_conversions: { increment: 1 }, }, create: { scout_id: referralAgent, successful_conversions: 1, }, }); const existingAffiliate = await tx.affiliateLedger.findFirst({ where: { scout_id: referralAgent, task_id: task.id, status: "PENDING", }, }); if (!existingAffiliate && proposalFeeCents > 0) { await tx.affiliateLedger.create({ data: { scout_id: referralAgent, task_id: task.id, amount: Math.floor(proposalFeeCents * 0.1), currency: "USD", status: "PENDING", }, }); } } await tx.auditEvent.create({ data: { actorType: "SYSTEM", actorId: "stripe-webhook", action: "DEMAND_PROPOSAL_FEE_CAPTURED", entityType: "TASK", entityId: task.id, metadata: { stripe_session_id: session.id, stripe_payment_intent_id: paymentIntentId, fee_cents: proposalFeeCents, package_id: metadata.package_id || null, referral_agent: referralAgent || null, affiliate_fee_cents: referralAgent ? Math.floor(proposalFeeCents * 0.1) : 0, source: metadata.source || null, campaign: metadata.campaign || null, }, }, }); }); console.log(`[Webhook] Proposal fee captured for task ${task.id}. Payment Intent: ${paymentIntentId}`); } export async function POST(request: NextRequest) { const payload = await request.text(); const signature = request.headers.get("stripe-signature"); if (!signature || !process.env.STRIPE_WEBHOOK_SECRET) { return NextResponse.json({ error: "Missing signature or webhook secret" }, { status: 400 }); } let event: Stripe.Event; try { if (!stripe) { throw new Error("Stripe SDK is not initialized"); } event = stripe.webhooks.constructEvent( payload, signature, process.env.STRIPE_WEBHOOK_SECRET ); } catch (error: unknown) { console.error("[Webhook Error]", error); return NextResponse.json({ error: "Webhook signature verification failed" }, { status: 400 }); } try { if (event.type === "checkout.session.completed") { const session = event.data.object as Stripe.Checkout.Session; if (session.metadata?.intent === "DEMAND_PROPOSAL_FEE") { await handleDemandProposalFee(session); return NextResponse.json({ received: true }); } const task = await prisma.task.findFirst({ where: { stripe_checkout_session_id: session.id } }); if (!task) { console.error(`[Webhook] Task not found for session: ${session.id}`); return NextResponse.json({ received: true }); } // Payment is authorized (Auth Hold) // Save the payment_intent_id and set status to OPEN await prisma.task.update({ where: { id: task.id }, data: { stripe_payment_intent_id: session.payment_intent as string, status: TaskStatus.OPEN } }); console.log(`[Webhook] Task ${task.id} is now OPEN. Payment Intent: ${session.payment_intent}`); // High-Value Bounty FOMO trigger ($50 USD = 5000 cents) if (task.reward_amount >= 5000) { const formatted = task.reward_currency === "USD" ? `$${(task.reward_amount / 100).toFixed(0)}` : `NT$${task.reward_amount}`; void broadcastFomoEvent({ type: "HIGH_VALUE_BOUNTY", taskId: task.id, amountFormatted: formatted }); } } return NextResponse.json({ received: true }); } catch (error: unknown) { console.error("[Webhook Processing Error]", error); return NextResponse.json({ error: "Internal Error" }, { status: 500 }); } }