"use server"; import { getServerSession } from "next-auth"; import { authOptions } from "@/lib/auth"; import { paymentService } from "@/server/services/payment.service"; import { bookingService } from "@/server/services/booking.service"; import { refundService } from "@/server/services/refund.service"; import { absoluteUrl } from "@/lib/site"; import { revalidatePath } from "next/cache"; export type StartMidtransResponse = | { error: string } | { success: true; snapToken: string; snapJsUrl: string; clientKey: string; orderId: string; }; /** * Mulai pembayaran online via Midtrans untuk trip tertentu. Resolve booking * dari (tripId, userId) supaya client tidak perlu tahu bookingId. */ export async function startMidtransPaymentAction( tripId: string ): Promise { const session = await getServerSession(authOptions); if (!session?.user) { return { error: "Kamu harus login terlebih dahulu" }; } try { const booking = await bookingService.getByTripAndUser( tripId, session.user.id ); if (!booking || booking.status === "CANCELLED") { return { error: "Kamu tidak terdaftar di trip ini" }; } const result = await paymentService.startMidtransPayment( booking.id, session.user.id, { finishUrl: absoluteUrl(`/trips/${tripId}/payment`) } ); return { success: true, snapToken: result.snapToken, snapJsUrl: result.snapJsUrl, clientKey: result.clientKey, orderId: result.orderId, }; } catch (err) { return { error: (err as Error).message }; } } /** * Admin variant reconcile — skip ownership check, dipakai dari panel admin * `/admin/bookings/[id]` saat investigasi. */ export async function adminReconcileMidtransAction(orderId: string) { const session = await getServerSession(authOptions); if (!session?.user) { return { error: "Kamu harus login terlebih dahulu" }; } const { isAdminEmail } = await import("@/lib/admin"); if (!isAdminEmail(session.user.email)) { return { error: "Hanya admin yang bisa melakukan aksi ini" }; } if (!orderId || typeof orderId !== "string") { return { error: "order_id tidak valid" }; } try { const result = await paymentService.adminReconcile(orderId); if (!result.ok) { if (result.reason === "not_found") { return { error: "Order tidak ditemukan di sistem" }; } return { error: "Status pembayaran tidak cocok dengan tagihan" }; } const { auditLog } = await import("@/server/services/audit-log.service"); await auditLog.record({ admin: { id: session.user.id, email: session.user.email }, action: "PAYMENT_RECONCILE", entityType: "Payment", entityId: orderId, payload: { outcome: result.status }, }); return { success: true as const, status: result.status }; } catch (err) { return { error: (err as Error).message }; } } /** * Tarik status terkini dari Midtrans untuk satu order, lalu sinkron ke DB. * Dipakai oleh payment page saat user kembali dari Snap (redirect bawa * `?order_id=...`), dan oleh `MidtransPayButton` di callback `onSuccess`/ * `onPending`/`onClose` agar UI ter-update tanpa menunggu webhook. */ export async function reconcileMidtransPaymentAction(orderId: string) { const session = await getServerSession(authOptions); if (!session?.user) { return { error: "Kamu harus login terlebih dahulu" }; } if (!orderId || typeof orderId !== "string") { return { error: "order_id tidak valid" }; } try { const result = await paymentService.reconcileFromGateway( orderId, session.user.id ); if (!result.ok) { if (result.reason === "forbidden") { return { error: "Order ini bukan milikmu" }; } if (result.reason === "not_found") { return { error: "Order tidak ditemukan" }; } return { error: "Status pembayaran tidak cocok dengan tagihan" }; } return { success: true as const, status: result.status }; } catch (err) { return { error: (err as Error).message }; } } /** * Peserta cancel booking PAID dengan refund request. Server menghitung * nominal refund pakai policy default (lib/refund-policy.ts) — client * cuma kirim bookingId untuk cegah tampering. */ export async function cancelBookingWithRefundAction(tripId: string) { const session = await getServerSession(authOptions); if (!session?.user) { return { error: "Kamu harus login terlebih dahulu" }; } try { const booking = await bookingService.getByTripAndUser( tripId, session.user.id ); if (!booking) { return { error: "Kamu tidak terdaftar di trip ini" }; } const result = await refundService.requestUserCancellation({ bookingId: booking.id, userId: session.user.id, }); revalidatePath(`/trips/${tripId}`); revalidatePath("/trips"); revalidatePath("/"); revalidatePath("/profile"); revalidatePath("/admin/refunds"); return { success: true as const, kind: result.kind, refundAmount: result.refundAmount, days: result.days, }; } catch (err) { return { error: (err as Error).message }; } }