"use server"; import { getServerSession } from "next-auth"; import { revalidatePath } from "next/cache"; import { authOptions } from "@/lib/auth"; import { isAdminEmail } from "@/lib/admin"; import { refundService } from "@/server/services/refund.service"; import { auditLog } from "@/server/services/audit-log.service"; import { emailService } from "@/lib/email/send"; import { prisma } from "@/lib/prisma"; import { createRefundSchema, refundDecisionSchema } from "./schemas"; async function requireAdmin() { const session = await getServerSession(authOptions); if (!session?.user || !isAdminEmail(session.user.email)) { return null; } return session.user; } export async function createRefundAction(formData: FormData) { const admin = await requireAdmin(); if (!admin) return { error: "Tidak memiliki akses admin" }; const parsed = createRefundSchema.safeParse({ bookingId: formData.get("bookingId") as string, reason: formData.get("reason") as string, reportedBy: formData.get("reportedBy") as string, reportNote: formData.get("reportNote") as string, amount: (formData.get("amount") as string) || undefined, }); if (!parsed.success) { return { error: parsed.error.issues[0].message }; } try { const refund = await refundService.requestRefund({ bookingId: parsed.data.bookingId, reason: parsed.data.reason, reportedBy: parsed.data.reportedBy, reportNote: parsed.data.reportNote, amount: parsed.data.amount, initiatedByAdminId: admin.id, }); await auditLog.record({ admin: { id: admin.id, email: admin.email }, action: "REFUND_CREATE", entityType: "Refund", entityId: refund.id, payload: { bookingId: parsed.data.bookingId, amount: parsed.data.amount, reason: parsed.data.reason, }, }); void notifyRefundCreated(refund.id); revalidatePath("/admin/refunds"); return { success: true }; } catch (err) { return { error: (err as Error).message }; } } async function notifyRefundCreated(refundId: string) { const refund = await prisma.refund.findUnique({ where: { id: refundId }, include: { booking: { include: { user: { select: { email: true, name: true } }, trip: { select: { title: true } }, }, }, }, }); if (!refund) return; await emailService.send({ to: refund.booking.user.email, idempotencyKey: `refund_created-${refund.id}`, template: { template: "refund_created", data: { userName: refund.booking.user.name, tripTitle: refund.booking.trip.title, amount: refund.amount, reason: refund.reason, }, }, }); } async function notifyRefundDecision( refundId: string, decision: "SUCCEEDED" | "FAILED", adminNote: string ) { const refund = await prisma.refund.findUnique({ where: { id: refundId }, include: { booking: { include: { user: { select: { email: true, name: true } }, trip: { select: { title: true } }, }, }, }, }); if (!refund) return; await emailService.send({ to: refund.booking.user.email, idempotencyKey: `refund_${decision.toLowerCase()}-${refund.id}`, template: decision === "SUCCEEDED" ? { template: "refund_succeeded", data: { userName: refund.booking.user.name, tripTitle: refund.booking.trip.title, amount: refund.amount, adminNote, }, } : { template: "refund_failed", data: { userName: refund.booking.user.name, tripTitle: refund.booking.trip.title, amount: refund.amount, adminNote, }, }, }); } export async function decideRefundAction(formData: FormData) { const admin = await requireAdmin(); if (!admin) return { error: "Tidak memiliki akses admin" }; const parsed = refundDecisionSchema.safeParse({ refundId: formData.get("refundId") as string, decision: formData.get("decision") as string, adminNote: (formData.get("adminNote") as string) || undefined, }); if (!parsed.success) { return { error: parsed.error.issues[0].message }; } const { refundId, decision, adminNote } = parsed.data; const needsNote = decision === "REJECT" || decision === "SUCCEEDED" || decision === "FAILED"; if (needsNote && (!adminNote || !adminNote.trim())) { return { error: "Catatan/alasan admin wajib diisi untuk tindakan ini" }; } try { if (decision === "APPROVE") { await refundService.approveRefund({ refundId, adminId: admin.id, adminNote, }); } else if (decision === "REJECT") { await refundService.rejectRefund({ refundId, adminId: admin.id, adminNote: adminNote!, }); } else if (decision === "SUCCEEDED") { await refundService.markSucceededManual({ refundId, adminId: admin.id, adminNote: adminNote!, }); } else { await refundService.markFailed({ refundId, adminId: admin.id, adminNote: adminNote!, }); } await auditLog.record({ admin: { id: admin.id, email: admin.email }, action: `REFUND_${decision}`, entityType: "Refund", entityId: refundId, payload: adminNote ? { adminNote } : undefined, }); // Notif email user kalau decision final (SUCCEEDED/FAILED) — APPROVE/REJECT // intermediate, refund_created sudah dikirim sebelumnya. if (decision === "SUCCEEDED" || decision === "FAILED") { void notifyRefundDecision(refundId, decision, adminNote ?? ""); } revalidatePath("/admin/refunds"); return { success: true }; } catch (err) { return { error: (err as Error).message }; } }