refund roadmap pr-1 and pr-2
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
-- AlterEnum
|
||||
ALTER TYPE "BookingStatus" ADD VALUE 'PARTIALLY_REFUNDED';
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "RefundReason" AS ENUM ('USER_CANCELLATION', 'ORGANIZER_CANCELLED', 'TRIP_ISSUE', 'ADMIN_ADJUSTMENT', 'DISPUTE_RESOLVED', 'OTHER');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "RefundStatus" AS ENUM ('PENDING', 'APPROVED', 'REJECTED', 'PROCESSING', 'SUCCEEDED', 'FAILED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "RefundInitiator" AS ENUM ('USER', 'ORGANIZER', 'SYSTEM', 'ADMIN');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "RefundReporter" AS ENUM ('PARTICIPANT', 'ORGANIZER');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Refund" (
|
||||
"id" TEXT NOT NULL,
|
||||
"bookingId" TEXT NOT NULL,
|
||||
"paymentId" TEXT,
|
||||
"amount" INTEGER NOT NULL,
|
||||
"currency" TEXT NOT NULL DEFAULT 'IDR',
|
||||
"reason" "RefundReason" NOT NULL,
|
||||
"reportedBy" "RefundReporter" NOT NULL,
|
||||
"reportNote" TEXT NOT NULL,
|
||||
"initiatedBy" "RefundInitiator" NOT NULL DEFAULT 'ADMIN',
|
||||
"status" "RefundStatus" NOT NULL DEFAULT 'PENDING',
|
||||
"idempotencyKey" TEXT NOT NULL,
|
||||
"adminNote" TEXT,
|
||||
"reviewedById" TEXT,
|
||||
"reviewedAt" TIMESTAMP(3),
|
||||
"succeededAt" TIMESTAMP(3),
|
||||
"failedAt" TIMESTAMP(3),
|
||||
"externalRefundId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Refund_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Refund_idempotencyKey_key" ON "Refund"("idempotencyKey");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Refund_bookingId_status_idx" ON "Refund"("bookingId", "status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Refund_status_createdAt_idx" ON "Refund"("status", "createdAt");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Refund" ADD CONSTRAINT "Refund_bookingId_fkey" FOREIGN KEY ("bookingId") REFERENCES "Booking"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Refund" ADD CONSTRAINT "Refund_paymentId_fkey" FOREIGN KEY ("paymentId") REFERENCES "Payment"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Refund" ADD CONSTRAINT "Refund_reviewedById_fkey" FOREIGN KEY ("reviewedById") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@@ -32,6 +32,8 @@ model User {
|
||||
organizerVerification OrganizerVerification? @relation("OrganizerVerificationOwner")
|
||||
reviewedVerifications OrganizerVerification[] @relation("OrganizerVerificationReviewer")
|
||||
|
||||
reviewedRefunds Refund[] @relation("RefundReviewer")
|
||||
|
||||
profile UserProfile?
|
||||
}
|
||||
|
||||
@@ -258,6 +260,7 @@ model Booking {
|
||||
status BookingStatus @default(PENDING)
|
||||
|
||||
payments Payment[]
|
||||
refunds Refund[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
@@ -275,6 +278,7 @@ enum BookingStatus {
|
||||
PAID
|
||||
CANCELLED
|
||||
REFUNDED
|
||||
PARTIALLY_REFUNDED
|
||||
EXPIRED
|
||||
}
|
||||
|
||||
@@ -307,6 +311,8 @@ model Payment {
|
||||
failedAt DateTime?
|
||||
rejectionReason String?
|
||||
|
||||
refunds Refund[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@ -328,3 +334,105 @@ enum PaymentStatus {
|
||||
CANCELLED
|
||||
REFUNDED
|
||||
}
|
||||
|
||||
/// Refund = financial event terpisah dari Booking. Satu Booking bisa punya
|
||||
/// banyak Refund (partial, multi-tahap). Setiap row auditable: kapan dibuat,
|
||||
/// siapa melaporkan, siapa approve, kapan SUCCEEDED. Never delete — kalau
|
||||
/// gagal, set status=FAILED + alasan.
|
||||
///
|
||||
/// Di MVP refund dimasukkan admin secara manual berdasarkan laporan dari
|
||||
/// peserta atau organizer (via WhatsApp/email). Phase berikutnya akan
|
||||
/// menambah self-service flow dari user dan organizer.
|
||||
model Refund {
|
||||
id String @id @default(cuid())
|
||||
bookingId String
|
||||
booking Booking @relation(fields: [bookingId], references: [id], onDelete: Restrict)
|
||||
|
||||
/// Payment yang di-refund. Opsional di MVP (manual transfer bisa tidak
|
||||
/// terikat ke Payment row tertentu); wajib saat integrasi Midtrans (R-4).
|
||||
paymentId String?
|
||||
payment Payment? @relation(fields: [paymentId], references: [id], onDelete: Restrict)
|
||||
|
||||
/// Nominal refund dalam satuan terkecil (IDR rupiah, integer). Boleh < total
|
||||
/// payment untuk partial. Service layer enforce SUM(SUCCEEDED) <= payment.amount.
|
||||
amount Int
|
||||
currency String @default("IDR")
|
||||
|
||||
reason RefundReason
|
||||
|
||||
/// Siapa yang melaporkan kebutuhan refund ini ke admin.
|
||||
reportedBy RefundReporter
|
||||
/// Isi laporan dari peserta/organizer yang admin terima (mis. WA, email).
|
||||
reportNote String
|
||||
|
||||
/// Pihak yang membuat record di sistem. Di MVP selalu ADMIN; saat self-service
|
||||
/// nanti USER/ORGANIZER, dan SYSTEM untuk auto-trigger dari trip dibatalkan.
|
||||
initiatedBy RefundInitiator @default(ADMIN)
|
||||
|
||||
status RefundStatus @default(PENDING)
|
||||
|
||||
/// Idempotency key, dipakai saat panggil Midtrans Refund API di R-4. Generate
|
||||
/// sekali saat create supaya retry gateway tidak double-refund.
|
||||
idempotencyKey String @unique
|
||||
|
||||
/// Catatan admin: alasan tolak, referensi transfer manual, dst. Bebas teks.
|
||||
adminNote String?
|
||||
|
||||
/// Admin yang terakhir mengubah status (approve/reject/mark-succeeded/failed).
|
||||
reviewedById String?
|
||||
reviewedBy User? @relation("RefundReviewer", fields: [reviewedById], references: [id], onDelete: SetNull)
|
||||
reviewedAt DateTime?
|
||||
|
||||
succeededAt DateTime?
|
||||
failedAt DateTime?
|
||||
|
||||
/// ID refund di gateway (mis. Midtrans refund_id). Kosong untuk manual transfer.
|
||||
externalRefundId String?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([bookingId, status])
|
||||
@@index([status, createdAt])
|
||||
}
|
||||
|
||||
enum RefundReason {
|
||||
/// Peserta cancel booking sendiri (mengikuti refund window policy).
|
||||
USER_CANCELLATION
|
||||
/// Organizer membatalkan trip — peserta dapat full refund.
|
||||
ORGANIZER_CANCELLED
|
||||
/// Masalah saat/setelah trip (mis. itinerary tidak sesuai).
|
||||
TRIP_ISSUE
|
||||
/// Penyesuaian dari admin (kompensasi, koreksi nominal, dll.).
|
||||
ADMIN_ADJUSTMENT
|
||||
/// Hasil resolusi dispute / chargeback bank.
|
||||
DISPUTE_RESOLVED
|
||||
OTHER
|
||||
}
|
||||
|
||||
enum RefundStatus {
|
||||
/// Baru dilaporkan, menunggu review admin.
|
||||
PENDING
|
||||
/// Admin sudah setujui, siap dieksekusi (manual transfer / gateway).
|
||||
APPROVED
|
||||
/// Admin tolak (alasan di `adminNote`).
|
||||
REJECTED
|
||||
/// (R-4) Request sudah dikirim ke gateway, menunggu callback.
|
||||
PROCESSING
|
||||
/// Uang sudah keluar dari kas Setrip / merchant gateway.
|
||||
SUCCEEDED
|
||||
/// Eksekusi gagal (alasan di `adminNote`). Record tidak dihapus.
|
||||
FAILED
|
||||
}
|
||||
|
||||
enum RefundInitiator {
|
||||
USER
|
||||
ORGANIZER
|
||||
SYSTEM
|
||||
ADMIN
|
||||
}
|
||||
|
||||
enum RefundReporter {
|
||||
PARTICIPANT
|
||||
ORGANIZER
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user