specs / 029-Changes-Tip-Booking-History

029 - Fitur Tip di Booking History

  • Tanggal: 2026-08-13
  • Chat/topik: Tambah fitur tip (chip/manual, 1x per order) di detail booking history untuk order yang sudah Completed, dibayar lewat flow "pay existing invoice" (seperti reschedule fee), porting 1:1 ke Android/iOS/Web.

Ringkasan perubahan

Order/transaksi court booking yang sudah Completed kini bisa diberi tip satu kali oleh member, dari halaman detail booking history. Member memilih nominal via chip (Rp 5.000/10.000/20.000/30.000/50.000) atau input manual (min Rp 5.000, tanpa batas atas), lalu dibayar lewat flow pembayaran invoice yang sudah ada (sama seperti reschedule fee) — QRIS/VA/dst. Setelah tip Completed, section tip berubah jadi kartu "Thank you" dengan nominal yang diberikan, dan CTA tidak muncul lagi (hanya bisa 1x per order).

Tidak ada tabel/kolom baru di database. Tip disimpan sebagai Transaction biasa (transaction_type='Tip') dengan satu TransactionItem (item_type='Fee', name='Tip', reference_id=id order asli). Status "sudah pernah tip" dihitung on-the-fly dari query tersebut, lalu diekspos sebagai field response-only tip_amount pada Transaction.

Sebagai bagian dari pekerjaan ini, ditemukan dan diperbaiki bug lama: reschedule fee ternyata ikut kena diskon TierBenefit otomatis + PPN 11% karena CustomerID diteruskan ke CreateTransaction tanpa guard. Ditambahkan flag SkipDiscountAndTax di CreateTransactionRequest, dipakai baik oleh reschedule fee maupun Tip, supaya nominal yang di-charge persis sesuai yang diminta tanpa pajak/diskon tak terduga.

Impact bisnis

  • Menambah kanal apresiasi member ke staff (tip), tanpa menyentuh alur pembayaran booking utama.
  • Fix bug reschedule fee mencegah member membayar lebih (atau lebih sedikit karena tier discount) dari nominal fee yang seharusnya — dampak finansial langsung ke revenue reschedule fee sebelumnya.

Epic / User stories

  • Epic: Tip untuk staff dari booking history.
  • US: Sebagai member yang sudah menyelesaikan booking, saya ingin memberi tip ke staff dengan nominal pilihan saya, agar saya bisa menunjukkan apresiasi tanpa proses rumit.
  • US: Sebagai member yang sudah memberi tip, saya ingin melihat konfirmasi nominal yang saya berikan, agar saya tahu tip saya berhasil dan tidak memberi tip dobel.

User terdampak

Member (semua platform: Android, iOS, Web). Tidak berdampak ke admin, kasir POS, atau staff court secara langsung (staff menerima tip melalui proses operasional venue, di luar scope aplikasi ini).

Aplikasi terdampak

  • mpc-be: PERLU — endpoint baru POST /orders/:id/tip, enrichment tip_amount di GET /transactions/customer/:id & GET /transactions/:id, fix SkipDiscountAndTax untuk reschedule fee & tip.
  • mpc-be-pos: TIDAK PERLU — tabel transactions/transaction_items sama, tapi kode POS hanya mengecek ItemType string tertentu secara eksplisit; nilai baru "Tip"/"Fee" lewat tanpa efek stock/booking.
  • mpc-admin: TIDAK PERLU — halaman Transactions render transactionType sebagai teks bebas; "Tip" otomatis muncul tanpa ubah kode.
  • mpc-android: PERLU — source of truth, dikerjakan pertama.
  • mpc-ios: PERLU — port 1:1 (member-app-parity).
  • mpc-web: PERLU — port 1:1 + membangun infra "pay existing invoice" yang belum ada sebelumnya (reschedule fee pun belum punya flow ini di web).
  • mpc-owner: TIDAK PERLU — tidak ada perubahan domain owner.
  • ksr-sync: TIDAK PERLU — tidak menyentuh sync mpcdb→ksrdb.
  • mpc-pos (kasir/Flutter): TIDAK PERLU — fitur ini murni member-facing.

Perubahan schema

Tidak ada kolom/tabel baru. Hanya index tambahan opsional untuk performa query (lihat mpc-dev/docs/schema/migration/022-tip-reference-index.md): CREATE INDEX IF NOT EXISTS idx_transaction_items_reference_id ON transaction_items(reference_id); — sudah diapply ke mpcdb production.

Perubahan kode

  • mpc-be:
    • internal/bookings/repository.go + service.go: FindByTransactionID / GetBookingsByTransactionID, DeriveOrderStatus (mirror historyUiStatus()+orderStatus() client). Fix RescheduleBooking: SkipDiscountAndTax: true di CreateTransactionRequest.
    • internal/transactions/entity.go: const TypeTip, field response-only TipAmount.
    • internal/transactions/repository.go + service.go: FindCompletedTipAmountsByReferenceIDs, HasCompletedTip; enrichment TipAmount di GetTransactionByID/GetTransactionsByCustomerID; SkipDiscountAndTax guard di CreateTransaction.
    • internal/orders/service.go + handler.go: CreateTip (validasi owner/Completed/belum-tip/amount≥5000) + route POST /orders/:id/tip.
    • mpc-dev/docs/api/be.md: dokumentasi endpoint baru + field tip_amount.
  • mpc-android (source of truth): feature/history/TipScreen.kt (baru), BookingDetailScreen.kt (TipSection), PaymentScreen.kt (title/label dinamis dari nama item invoice), OrderSummaryScreen.kt (copy Tip + tombol "Back to My Bookings"), data/Models.kt/Dtos.kt/MpcApi.kt/MpcRepository.kt (tipAmount, createOrderTip), nav/Routes.kt/AppNavHost.kt (route tip). APK demo mpc-android-demo-v1.17.apk di-rebuild (chat sama, tanpa bump versi).
  • mpc-ios (port 1:1): Features/History/TipScreen.swift (baru), BookingDetailScreen.swift (TipSection), Features/Booking/PaymentScreen.swift (infra baru: title & kartu invoice dinamis untuk existing-invoice pay), OrderSummaryScreen.swift (copy Tip + tombol "Back to My Bookings"), Data/Models.swift/Remote/Dtos.swift/Remote/MpcApi.swift/Repo/MpcRepository.swift (tipAmount, CreateTipRequestDto, createOrderTip), Navigation/Route.swift/RootView.swift (case .tip).
  • mpc-web (port 1:1 + infra baru): src/lib/store/checkout.tsx (existingTransactionId/existingInvoiceLabel/existingInvoiceAmount + setExistingInvoice), src/app/payment/page.tsx (cabang existing-invoice pay), src/app/tip/page.tsx (baru), src/app/history/page.tsx (deriveOrderStatus + TipSection), src/app/order-summary/page.tsx (copy Tip + tombol "Back to My Bookings"), src/lib/api/types.ts/repo.ts (transactionType/tipAmount, createOrderTip). Deploy production (SKIP_WEB=1 SKIP_POS_WEB=1 ./deploy-mpc.sh), smoke test /tip 200 & /history 307 (redirect login, expected).