Files
setrip/ADMIN_PAYMENT_OPS_ROADMAP.md
T
arifal c4efe4453b -
- 
- 
- 
2026-05-18 18:31:16 +07:00

5.9 KiB

Setrip — Admin Payment Operations Roadmap

Admin perlu visibilitas + kontrol penuh atas alur uang: payment Midtrans, refund, payout. Saat webhook gagal atau status mismatch, admin harus bisa reconcile tanpa edit DB.

Skenario nyata: webhook Midtrans drop di production. Booking.status = AWAITING_PAY padahal user sudah bayar (confirm email dari Midtrans). User komplain via WhatsApp. Saat ini admin harus query DB manual lalu update via Prisma Studio.


Baseline

  • paymentService.reconcileFromGateway(orderId, userId) di server/services/payment.service.ts sudah call Midtrans Core API + apply state-machine. Tapi userId check membatasi penggunaan ke user pemilik booking — admin perlu variant.
  • paymentService.handleMidtransWebhook ada + idempotent via applyGatewayStatus helper.
  • Payment.rawCallback simpan snapshot mentah untuk audit.
  • Refund + Payout model lengkap dengan reviewedBy/processedBy/adminNote.
  • Tidak ada page /admin/bookings/[id] untuk drill-down per-booking timeline.
  • Tidak ada admin variant reconcileFromGateway (yang tidak butuh userId check).
  • Tidak ada UI yang tampilkan Payment.rawCallback JSON.
  • Tidak ada filter refund per reason (mis. cari semua DISPUTE_RESOLVED).
  • Tidak ada bulk reconcile untuk stale PENDING/AWAITING payments.

Phase 1 — Booking + Payment Detail View

Admin perlu satu halaman yang tampilkan seluruh event uang untuk satu booking: payment attempts (Midtrans + manual legacy), refund history, payout status, raw callback.

Keputusan asumsi:

  • Drill-down dari /admin/trips/[id], /admin/refunds, /admin/payouts, dan global search nanti.
  • Tampilkan timeline chronological semua event Payment + Refund + Payout untuk booking — bukan tabel terpisah.
  • Payment.rawCallback ditampilkan sebagai collapsible JSON viewer (tidak default expanded — verbose).
  • Show juga Booking.status history kalau ada (tidak ada saat ini — updatedAt jadi proxy).
# Item Status File
1.1 bookingRepo.findByIdForAdmin(id) — include payments (with raw), refunds, payout, trip, user, participant server/repositories/booking.repo.ts
1.2 Page /admin/bookings/[id] — header (trip, user, amount, status), timeline events app/admin/bookings/[id]/page.tsx
1.3 Component PaymentTimelineAdmin — render Payment + Refund + Payout sorted by createdAt features/booking/components/payment-timeline-admin.tsx
1.4 Component RawCallbackViewer — collapsible <details> block dengan JSON pretty-printed features/booking/components/raw-callback-viewer.tsx
1.5 Link "Lihat detail" dari /admin/refunds ke /admin/bookings/[id] app/admin/refunds/page.tsx
1.6 Link "Lihat detail" dari /admin/payouts ke /admin/bookings/[id] app/admin/payouts/page.tsx

Tindakan manual: tidak ada.


Phase 2 — Admin Midtrans Reconciliation UI

Tombol di booking detail page yang panggil Midtrans Core API + apply update. Admin variant tidak butuh userId check.

Keputusan asumsi:

  • Reuse internal helper applyGatewayStatus di server/services/payment.service.ts — sudah ekstrak.
  • Buat paymentService.adminReconcile(orderId, adminId) — sama dengan reconcileFromGateway tapi:
    • Skip ownership check (admin bypass).
    • Log adminId di Payment.rawCallback snapshot (tambah field _reconciledByAdminId).
  • Server action adminReconcileMidtransAction(orderId) guard isAdmin.
  • UI: tombol per Payment row di timeline. Disable kalau Payment sudah final (PAID/FAILED/EXPIRED/CANCELLED/REFUNDED) tapi tetap show last-reconciled-at.
  • Tampilkan toast hasil: "Updated to PAID" / "Already PAID, no change" / "Amount mismatch (audit)".
# Item Status File
2.1 paymentService.adminReconcile(orderId, adminId) — variant tanpa ownership check server/services/payment.service.ts
2.2 Server action adminReconcileMidtransAction(orderId) features/booking/actions.ts (atau features/admin/actions.ts baru)
2.3 Tombol "Reconcile dari Midtrans" di tiap Payment Midtrans di timeline features/booking/components/payment-timeline-admin.tsx
2.4 Tampilkan Payment.rejectionReason (untuk amount mismatch log) di card payment features/booking/components/payment-timeline-admin.tsx
2.5 (Optional) Bulk reconcile: /admin/payments/stale — list Payment status PENDING/AWAITING > 6 jam app/admin/payments/stale/page.tsx

Tindakan manual:

  1. Brief admin: kapan pakai reconcile (peserta lapor "sudah bayar tapi status belum update"). Jangan dipakai untuk PAID booking (idempotent tapi noise).

Phase 3 — Dispute & Chargeback Tracking

RefundReason.DISPUTE_RESOLVED sudah ada di enum tapi tidak ada flow khusus.

Keputusan asumsi:

  • Tidak buat tabel baru. Filter di refund list page cukup.
  • Tambah "Chargeback note" field di Refund kalau perlu (skip untuk MVP — pakai adminNote saja).
  • Highlight visual: badge merah untuk DISPUTE_RESOLVED supaya admin treat khusus.
# Item Status File
3.1 Tab/filter reason di /admin/refunds — dropdown semua nilai RefundReason app/admin/refunds/page.tsx
3.2 Badge khusus untuk DISPUTE_RESOLVED di refund card features/refund/components/refund-review-card.tsx
3.3 Dokumentasi SOP: kapan pakai DISPUTE_RESOLVED vs reason lain docs/admin/refund-reasons.md (baru)

Tindakan manual:

  1. Tulis SOP dispute handling (alur bank → admin → refund creation).
  2. Brief admin: DISPUTE_RESOLVED hanya untuk chargeback yang sudah resolve via bank.