add payment and integration with midtrans
This commit is contained in:
@@ -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" });
|
||||
}
|
||||
Reference in New Issue
Block a user