import { NextRequest, NextResponse } from "next/server"; import { midtransWebhookSchema } from "@/lib/midtrans"; import { paymentService } from "@/server/services/payment.service"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; /** * Webhook callback dari Midtrans. * * Aturan response: * - Body bukan JSON / shape tidak valid → 400 (Midtrans tetap retry, tapi mereka pasti * kirim shape valid; 400 di sini = bug bukan dari Midtrans). * - Signature mismatch → 401 (Midtrans tidak retry untuk auth error). * - Sudah final / unknown order / amount mismatch → 200 OK + log * (kita tidak mau Midtrans retry forever untuk kasus yang server-side perlu manual review). * - Sukses update → 200 OK. * * URL ini harus didaftarkan di dashboard Midtrans: * `/api/webhooks/midtrans`. */ export async function POST(req: NextRequest) { let raw: unknown; try { raw = await req.json(); } catch { return NextResponse.json({ error: "Body bukan JSON valid" }, { status: 400 }); } const parsed = midtransWebhookSchema.safeParse(raw); if (!parsed.success) { console.warn( "[midtrans-webhook] payload schema invalid", parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`) ); return NextResponse.json( { error: "Payload schema invalid" }, { status: 400 } ); } const body = parsed.data; let outcome; try { outcome = await paymentService.handleMidtransWebhook(body); } catch (err) { console.error("[midtrans-webhook] gagal proses callback", err, { order_id: body.order_id, }); return NextResponse.json( { error: "Gagal memproses callback" }, { status: 500 } ); } if (!outcome.ok) { if (outcome.reason === "signature_mismatch") { console.warn("[midtrans-webhook] signature mismatch", { order_id: body.order_id, }); return NextResponse.json({ error: "Invalid signature" }, { status: 401 }); } if (outcome.reason === "amount_mismatch") { console.warn("[midtrans-webhook] amount mismatch", { order_id: body.order_id, gross_amount: body.gross_amount, }); // Return 200 supaya Midtrans tidak retry; investigasi via log. return NextResponse.json({ status: "amount_mismatch_logged" }); } } if (outcome.ok && outcome.status === "booking_conflict") { console.warn( "[midtrans-webhook] PAID arrived for booking in conflict state — manual review required", { order_id: body.order_id, transaction_id: body.transaction_id } ); } return NextResponse.json({ status: outcome.ok ? outcome.status : "error" }); }