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, enrichmenttip_amountdiGET /transactions/customer/:id&GET /transactions/:id, fixSkipDiscountAndTaxuntuk reschedule fee & tip. - mpc-be-pos: TIDAK PERLU — tabel
transactions/transaction_itemssama, tapi kode POS hanya mengecekItemTypestring tertentu secara eksplisit; nilai baru "Tip"/"Fee" lewat tanpa efek stock/booking. - mpc-admin: TIDAK PERLU — halaman Transactions render
transactionTypesebagai 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(mirrorhistoryUiStatus()+orderStatus()client). FixRescheduleBooking:SkipDiscountAndTax: truediCreateTransactionRequest.internal/transactions/entity.go: constTypeTip, field response-onlyTipAmount.internal/transactions/repository.go+service.go:FindCompletedTipAmountsByReferenceIDs,HasCompletedTip; enrichmentTipAmountdiGetTransactionByID/GetTransactionsByCustomerID;SkipDiscountAndTaxguard diCreateTransaction.internal/orders/service.go+handler.go:CreateTip(validasi owner/Completed/belum-tip/amount≥5000) + routePOST /orders/:id/tip.mpc-dev/docs/api/be.md: dokumentasi endpoint baru + fieldtip_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(routetip). APK demompc-android-demo-v1.17.apkdi-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/tip200 &/history307 (redirect login, expected).