# Decisions Log — QRIS Soundbox Platform Log keputusan arsitektur dan implementasi yang harus dijadikan acuan eksekusi. ## Format Entri - ID: [D-XXX] - Tanggal: - Keputusan: - Alasan: - Dampak / implikasi: - Status: ## D-001 — Basis Implementasi Fase 0 dan 1 - Tanggal: 2026-05-23 - Keputusan: - Menjalankan eksekusi langsung berdasarkan fase roadmap, tanpa pembuatan jadwal rinci. - Alasan: - Tim sudah punya pembagian fase yang jelas dan siap mulai implementasi langsung. - Dampak / implikasi: - Fokus pada deliverable per fase dan DoD, bukan timeline. - Status: Active ## D-002 — Scope Fase 1 Step 1 - Tanggal: 2026-05-23 - Keputusan: - Step 1 Fase 1 dibatasi pada core foundation: auth baseline, schema MVP, merchant/outlet/terminal/device/binding, observability dasar. - Alasan: - Memastikan jalur static payment bisa dipersiapkan stabil sebelum dynamic flow. - Dampak / implikasi: - Fitur lain (settlement, dynamic QR, merchant portal) ditunda sampai Step 1 stabil. - Status: Active ## D-003 — API Contract Error Standard - Tanggal: 2026-05-23 - Keputusan: - Semua response error mengikuti envelope seragam: - `code`, `message`, `details`, `request_id`, `timestamp`. - Alasan: - Memudahkan debug dan tracing lintas service/device. - Dampak / implikasi: - Semua handler API harus mematuhi middleware response formatter yang sama. - Status: Active ## D-004 — Traceability Requirement - Tanggal: 2026-05-23 - Keputusan: - Semua request harus membawa `request_id`; webhook/device callback harus dihubungkan dengan `trace_id` jika multi-step. - Alasan: - Root-cause analysis perlu konteks end-to-end dari callback sampai notifikasi. - Dampak / implikasi: - Framework logging dan parser event wajib men-generate field ini secara konsisten. - Status: Active ## D-005 — Idempotency Rule - Tanggal: 2026-05-23 - Keputusan: - Setiap path sensitif terhadap duplicate (create merchant/create device/binding/webhook request/dynamic QR nanti) wajib men-support idempotency key/table. - Alasan: - Menghindari double create, double binding, dan state drift akibat retry. - Dampak / implikasi: - Menambah kebutuhan service/DB untuk `idempotency_keys` sejak awal Fase 1. - Status: Active ## D-006 — Device Binding - Tanggal: 2026-05-23 - Keputusan: - Notifikasi pembayaran hanya boleh dipush ke device dari binding aktif yang valid (active_flag=true). - Alasan: - Mencegah notifikasi salah kirim jika device pernah dipindahkan antar outlet/terminal. - Dampak / implikasi: - Query notification selalu harus resolve binding aktif secara explicit. - Status: Active ## D-007 — Fallback Strategy untuk Fase 1 - Tanggal: 2026-05-23 - Keputusan: - Retry MQTT pada Fase 1 memakai retry sederhana (fixed/backoff pendek), tanpa DLQ kompleks. - Alasan: - Menghemat effort awal sambil menjaga fitur core berjalan. - Dampak / implikasi: - Fase 4 mengelola policy DLQ dan retry matang. - Status: Active ## D-008 — Minimal RBAC untuk Mulai Eksekusi - Tanggal: 2026-05-23 - Keputusan: - Implement RBAC baseline minimum di Fase 1 (admin-only) sambil menjaga token device terpisah. - Alasan: - Mengurangi risiko akses silang awal tanpa memperlambat pengembangan. - Dampak / implikasi: - Permission matrix akan disempurnakan di Fase 4. - Status: Active ## D-009 — Dokumentasi Eksekusi Step 1 - Tanggal: 2026-05-23 - Keputusan: - Spesifikasi detail Step 1 dituangkan di `12-fase1-step1-core-foundation-spec.md`. - Alasan: - Menghindari ketidakpastian saat tim mulai coding. - Dampak / implikasi: - Semua implementer wajib merujuk dokumen ini saat menyiapkan branch dan PR. - Status: Active ## D-010 — Webhook Signature dan Parsing - Tanggal: 2026-05-23 - Keputusan: - Semua callback harus divalidasi signature-nya terlebih dahulu; jika tidak valid, respons harus `401` dan tidak boleh melakukan perubahan state apapun. - Alasan: - Payment callback adalah sumber trust utama dan harus dijaga dari spoofing/replay. - Dampak / implikasi: - Service callback wajib mengimplementasikan validator HMAC (atau skema cryptographic sesuai partner) sebelum mapping transaksi. - Status: Active ## D-011 — Callback Idempotency di Level Transaksi - Tanggal: 2026-05-23 - Keputusan: - Callback duplikat harus diidentifikasi deterministik dari `partner_reference + payment_status + partner_event_id` dan diperlakukan idempotent. - Alasan: - Callback retry sangat umum dari partner dan dapat memicu double state update. - Dampak / implikasi: - state transition dilakukan hanya jika perubahan state valid sesuai mesin state. - Status: Active ## D-012 — Evented Transaction State Change - Tanggal: 2026-05-23 - Keputusan: - Setiap perubahan state transaksi harus menghasilkan `transaction_events` agar Step 3 dan observability bisa berjalan. - Alasan: - Auditability dan troubleshooting menuntut timeline per transaksi. - Dampak / implikasi: - update transaksi tanpa menulis event dianggap bug di Step 1. - Status: Active ## D-013 — Terminal Event `paid` sebagai Trigger Notification - Tanggal: 2026-05-23 - Keputusan: - Notifikasi pembayaran di Step 3 harus dipicu dari event internal status `paid`, bukan dari polling callback. - Alasan: - Menghindari race dan duplikasi notifikasi. - Dampak / implikasi: - Implementasi Step 3 harus subscribe dan consume event transaction paid. - Status: Active ## D-014 — Retry Notifikasi Fase 1 - Tanggal: 2026-05-23 - Keputusan: - Step 3 menggunakan retry dasar maksimum 3 kali dengan jadwal 15/30/60 detik. - Alasan: - Menyeimbangkan reliability dan kecepatan operasional tanpa kompleksitas DLQ penuh pada fase awal. - Dampak / implikasi: - `notifications.retry_count` wajib ditulis dan dibatasi maksimal 3. - Status: Active ## D-015 — Notifikasi Tanpa Ack Fase 1 - Tanggal: 2026-05-23 - Keputusan: - Pada fase 1, absence of MQTT ack tidak dianggap error akhir; status sukses ditentukan dari publish outcome, bukan ack dari device. - Alasan: - Device ecosystem belum konsisten untuk ack schema, dan goal fase 1 adalah stabilitas flow utama. - Dampak / implikasi: - `ack_status` bisa `not_supported`, dan operasi tetap lanjut dengan monitoring via retry/publish status. - Status: Active ## D-016 — Heartbeat Status Threshold Fase 1 - Tanggal: 2026-05-23 - Keputusan: - Definisi status device ditetapkan: online (<90s), degraded (signal/battery buruk), stale (<15 menit), offline (>15 menit) berdasarkan `last_seen_at`. - Alasan: - Konsistensi status dibutuhkan untuk ops triage cepat. - Dampak / implikasi: - Setiap list/detail device menampilkan status yang dihitung dari rule yang sama. - Status: Active ## D-017 — Dashboard KPI Fase 1 - Tanggal: 2026-05-23 - Keputusan: - Dashboard ops minimum menampilkan: transaksi hari ini, success rate hari ini, active devices, pending notifications, stale/offline counts. - Alasan: - Tim operasi perlu indikator cepat tanpa menunggu custom analytics. - Dampak / implikasi: - Endpoint summary dashboard wajib dihitung dari data transaksi/device/notification inti. - Status: Active ## D-018 — Pencairan Dana Non-Tersentral per Merchant - Tanggal: 2026-05-24 - Keputusan: - Pencairan dana tidak dilakukan di rekening perusahaan; setiap merchant memakai rekening tujuan sendiri (atau reference payout miliknya) untuk settlement. - Alasan: - Menghindari ketergantungan izin/operasional PJP di tahap awal dan mempercepat launch MVP. - Dampak / implikasi: - Schema dan model onboarding merchant memakai `payout_account_reference`/rekening merchant, bukan rekening vault pusat. - Modul settlement platform difokuskan ke rekonsiliasi, status payout, dan visibility, bukan pengelolaan rekening pusat. - Callback payout dan payout execution dianggap partner-oriented (tergantung integrasi penyedia), bukan core di fase awal. - Status: Active ## D-019 — Fase 1 Audit Log dan Ledger Placeholder - Tanggal: 2026-05-26 - Keputusan: - Fase 1 mencatat audit log untuk aksi admin/webhook penting dan membuat ledger placeholder `gross_income` saat transaksi berubah ke `paid`. - Alasan: - Acceptance Fase 1 membutuhkan audit aksi CRUD penting, callback state changes, dan placeholder ledger untuk transaksi sukses tanpa menunggu modul finance penuh. - Dampak / implikasi: - `audit_logs` menjadi sumber trace operasional awal untuk entity penting. - `ledger_entries` Fase 1 hanya placeholder gross income; fee/platform payable detail tetap masuk Fase 3. - Duplicate paid callback tetap idempotent dan tidak membuat ledger duplikat karena unique key per `transaction_id + entry_type`. - Status: Active ## D-020 — Awal Fase 2 Dynamic QR API-Direct - Tanggal: 2026-05-26 - Keputusan: - Fase 2 dimulai dari capability resolver dan endpoint `POST /device/transactions/dynamic-qr` untuk device `communication_mode=api`. - Dynamic QR API-direct memakai mock QRIS payload lokal sampai integrasi partner QRIS tersedia. - Alasan: - Capability/routing harus stabil sebelum MQTT dynamic dan config push dibangun. - Mock partner memungkinkan transaksi dynamic tersimpan dan callback Fase 1 tetap diuji sebagai source of truth. - Dampak / implikasi: - Device static/MQTT yang tidak memiliki capability `dynamic_qr.api_direct` ditolak dengan `DEVICE_CAPABILITY_NOT_SUPPORTED`. - Device wajib punya binding aktif ke terminal `qr_mode=dynamic_api`; jika tidak, API mengembalikan `DEVICE_NOT_BOUND`. - Response dynamic QR idempotent memakai `Idempotency-Key` atau `request_id`, dan transaksi dibuat `awaiting_payment`. - Callback paid tetap memakai endpoint webhook yang sama untuk mengubah transaksi menjadi `paid` dan memicu notifikasi. - Status: Active ## D-021 — MQTT Dynamic QR dan Device Config Fase 2 - Tanggal: 2026-05-26 - Keputusan: - MQTT dynamic QR Fase 2 diimplementasikan sebagai HTTP simulator endpoint `POST /device/mqtt/uplink/dynamic-qr/request` yang mencatat uplink/downlink ke `mqtt_messages`. - Config push memakai `PATCH /admin/devices/{deviceId}/config`, disimpan di `device_configs`, dipublish ke MQTT outbox, lalu device mengirim `POST /device/config/ack`. - Alasan: - Belum ada broker MQTT sungguhan di stack lokal, tapi kontrak topic/payload dan idempotency perlu bisa diuji end-to-end. - Outbox membuat downlink response/config push observable lewat admin sebelum integrasi broker asli. - Dampak / implikasi: - Saat broker MQTT dipasang, `mqtt_messages` bisa menjadi outbox/trace awal untuk adapter broker. - Dynamic MQTT request memakai `request_id` sebagai `correlation_id` dan idempotency key. - Device config selalu versioned; ACK dicatat terpisah di `device_config_acks`. - Status: Active ## D-022 — Config Drift dan Retry Push Fase 2 - Tanggal: 2026-05-26 - Keputusan: - Fase 2 menambahkan status drift config device melalui `GET /admin/devices/{deviceId}/config/status`. - Retry config dilakukan via `POST /admin/devices/{deviceId}/config/retry-push` tanpa menaikkan `config_version`. - ACK config dari device juga dicatat sebagai uplink trace di `mqtt_messages` dengan `message_type=config_ack`. - Alasan: - Operasi perlu membedakan config `applied`, `pending_ack`, `failed_ack`, `stale_ack`, dan `never_pushed` sebelum broker MQTT sungguhan dipasang. - Retry harus mengirim ulang desired config yang sama agar idempotent dan tidak membuat drift versi buatan. - Dampak / implikasi: - Config yang sudah `applied` tidak boleh di-retry kecuali admin mengirim `force=true`. - `mqtt_messages` menjadi timeline awal untuk config push dan ACK device. - Saat broker MQTT asli masuk, endpoint retry tetap menjadi trigger adapter/outbox, bukan tempat menyimpan logic konfigurasi baru. - Status: Active ## D-023 — Health Summary Device Fase 2 - Tanggal: 2026-05-26 - Keputusan: - Endpoint admin device menambahkan `health_summary` berisi `status`, `score`, `age_seconds`, dan `reasons`. - `derived_status` tetap dipertahankan untuk kompatibilitas UI, namun nilainya berasal dari rule health summary yang sama. - Alasan: - Operasi membutuhkan konteks kenapa device online/degraded/stale/offline, bukan hanya label status. - Skor 0-100 memudahkan sorting/filter dashboard tanpa mengubah threshold status Fase 1. - Dampak / implikasi: - Status masih mengikuti threshold Fase 1: online <90 detik, stale >90 detik, offline >15 menit, degraded untuk sinyal/baterai buruk. - UI dapat memakai `health_summary.reasons` untuk badge/tooltip ops. - Status: Active ## D-024 — UI Ops Device Fase 2 - Tanggal: 2026-05-26 - Keputusan: - UI device registry dan device technical detail menampilkan `health_summary` dan config delivery status. - Retry config push tersedia dari drawer device registry dan halaman detail device. - Alasan: - Operator perlu melihat status Fase 2 tanpa membuka raw payload/API response. - Retry config adalah tindakan operasional langsung, sehingga harus dekat dengan status drift config. - Dampak / implikasi: - UI memakai endpoint `GET /admin/devices/{deviceId}/config/status` dan `POST /admin/devices/{deviceId}/config/retry-push`. - `derived_status` tetap dipakai sebagai fallback/compatibility, sementara health score/reasons menjadi konteks tambahan. - Status: Active ## D-025 — Dynamic QR Expiry Sweep Fase 2 - Tanggal: 2026-05-26 - Keputusan: - Dynamic QR `awaiting_payment` yang melewati `expired_at` dapat ditutup oleh sweep internal `POST /admin/transactions/expire-due`. - Sweep hanya berlaku untuk transaksi `qr_mode=dynamic` dan status `awaiting_payment`. - Alasan: - Fase 2 tidak boleh bergantung penuh pada callback partner untuk menutup QR yang kadaluarsa. - Expiry internal menjaga daftar transaksi pending tetap akurat untuk ops dan device flow. - Dampak / implikasi: - Sweep menulis `STATE_CHANGED` event dengan reason `dynamic_qr_expired`. - Callback paid yang datang setelah transaksi sudah `expired` tetap ditolak oleh state transition guard. - Endpoint admin ini bisa menjadi dasar scheduler/background worker saat fase operasional berikutnya. - Status: Active