82 lines
2.6 KiB
TypeScript
82 lines
2.6 KiB
TypeScript
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:
|
|
* `<NEXT_PUBLIC_SITE_URL>/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" });
|
|
}
|