add payment and integration with midtrans

This commit is contained in:
2026-05-08 21:44:34 +07:00
parent ecd4dc2ef4
commit 68ffaf2f69
14 changed files with 886 additions and 36 deletions
+81
View File
@@ -0,0 +1,81 @@
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" });
}