# QRIS Soundbox Platform Package Paket ini berisi blueprint final v1 untuk platform merchant aggregator QRIS + soundbox universal. ## Isi paket - 01-executive-blueprint.md - 02-system-architecture.md - 03-domain-modules.md - 04-device-flows.md - 05-api-contract-draft.md - 06-mqtt-contract-draft.md - 07-database-schema-draft.md - 08-implementation-roadmap.md - 09-screen-inventory.md - 10-design-blueprint.md - 11-low-fi-wireframes.md - 18-mqtt-broker-mosquitto-debian13.md ## Tujuan Dokumen ini dibuat supaya tim bisa langsung mulai: - desain UI/UX - breakdown engineering - desain backend - integrasi device - cicil implementasi per fase ## Quick Start Implementasi (Lanjutan dari CODEx Handoff) - Backend bootstrap Fase 1 sudah dibuat di `src/`. - Fitur awal yang sudah aktif: - request context + `request_id` di middleware - error envelope (`code`, `message`, `details`, `request_id`, `timestamp`) - auth token minimal untuk endpoint admin - middleware idempotency untuk endpoint sensitif - endpoint awal: - `GET /health` - `GET /health/deep` - `GET /admin/health` (dengan `Authorization: Bearer `) - `GET /admin/health/deep` - `POST /admin/login` - `POST /admin/sample-idempotent` - `POST /admin/merchants` - `GET /admin/merchants` - `GET /admin/merchants/{id}` - `PATCH /admin/merchants/{id}` - `POST /admin/merchants/{merchantId}/outlets` - `POST /admin/merchants/{merchantId}/approve` - `POST /admin/merchants/{merchantId}/reject` - `GET /admin/outlets` - `GET /admin/outlets/{id}` - `POST /admin/outlets/{outletId}/terminals` - `GET /admin/terminals` - `GET /admin/terminals/{id}` - `POST /admin/devices` - `GET /admin/devices` - `GET /admin/devices/{id}` - `POST /admin/devices/{id}/credentials/rotate` - `POST /admin/devices/{id}/bind` - `POST /admin/devices/{id}/unbind` - `POST /admin/devices/{id}/commands` - `GET /admin/devices/{id}/commands` - `GET /admin/devices/{id}/commands/{commandId}` - `GET /admin/devices/{id}/notifications` - `GET /admin/devices/{id}/config` - `PATCH /admin/devices/{id}/config` - `GET /admin/devices/{id}/config/status` - `POST /admin/devices/{id}/config/retry-push` - `GET /admin/devices/{id}/mqtt-messages` - `GET /admin/audit-logs` - `GET /admin/ledger-entries` - `GET /admin/dashboard/summary` - Memuat settlement finance summary termasuk `settlement_adjustment_amount` dan `settlement_adjusted_paid_amount`. - `POST /admin/settlement-batches` - `GET /admin/settlement-batches` - `GET /admin/settlement-batches/{batchId}` - `GET /admin/settlement-batches/{batchId}/export.csv` - Query `format=bank_generic` tersedia untuk CSV upload bank generik; default tetap settlement report standard. - `POST /admin/settlement-batches/{batchId}/mark-paid` - `PATCH /admin/settlement-batches/{batchId}/reference` - `POST /admin/settlement-batches/{batchId}/adjustments` - Adjustment disimpan di tabel formal `settlement_batch_adjustments` dan ikut tampil di detail batch admin/merchant. - Metadata adjustment lama otomatis dibackfill ke tabel formal saat schema initialization berjalan. - `GET /admin/settlement-adjustments` - Report adjustment lintas settlement batch dengan filter `merchant_id`, `adjustment_type`, `from`, `to`, dan `limit`. - `GET /admin/settlement-adjustments/export.csv` - Export CSV untuk report adjustment settlement dengan filter yang sama. - `POST /admin/exports/settlement-adjustments` - Membuat job export CSV adjustment settlement secara async, dengan permission `settlement:export`. - `GET /admin/exports/{jobId}` - Melihat status job export. - `GET /admin/exports/{jobId}/download` - Download hasil CSV saat job sudah `completed`. - `POST /admin/settlement-batches/{batchId}/mark-failed` - `POST /admin/settlement-batches/{batchId}/cancel` - `POST /admin/settlement-batches/{batchId}/reprocess` - `GET /admin/reconciliation/settlement-batches` - `GET /admin/observability/summary` - `POST /merchant/login` - `GET /merchant/profile` - `GET /merchant/settlement-summary` - `GET /merchant/settlement-batches` - `GET /merchant/settlement-batches/{batchId}` - `GET /merchant/settlement-batches/{batchId}/export.csv` - Query `format=bank_generic` tersedia untuk CSV upload bank generik; default tetap settlement report standard. - `GET /admin/transactions` - `GET /admin/transactions/{transactionId}` - `POST /admin/transactions` - `POST /admin/transactions/expire-due` - `GET /admin/transactions/{transactionId}/events` - `POST /admin/transactions/{transactionId}/retry-notification` - `POST /admin/seed` - `POST /device/transactions/dynamic-qr` - `POST /device/mqtt/uplink/dynamic-qr/request` - `GET /device/config` - `POST /device/config/ack` ### Menjalankan lokal ```bash npm install cp .env.example .env npm run db:migrate npm run dev npm run build && npm start ``` Schema migration memakai tabel `schema_migrations`. Migration awal `001_current_schema_bootstrap` membaseline schema saat ini; migration berikutnya bisa ditambahkan ke folder `migrations/` dengan format `NNN_description.sql` atau `NNN_description.mts`. Untuk mode broker MQTT sungguhan, set `MQTT_PUBLISH_MODE=broker` di `.env` bersama `MQTT_BROKER_URL`, `MQTT_USERNAME`, `MQTT_PASSWORD`, dan `MQTT_CLIENT_ID`. Smoke test otomatis tetap override ke `MQTT_PUBLISH_MODE=simulator` agar tidak bergantung pada broker eksternal. Dynamic QR expiry sweep berjalan otomatis bila `DYNAMIC_QR_EXPIRY_SCHEDULER_ENABLED=true`; interval dan batch size diatur lewat `DYNAMIC_QR_EXPIRY_SWEEP_INTERVAL_MS` dan `DYNAMIC_QR_EXPIRY_SWEEP_LIMIT`. Finance light membuat ledger `gross_income`, `platform_fee`, dan `merchant_payable` saat transaksi menjadi `paid`. Rate fee default diatur lewat `FINANCE_PLATFORM_FEE_BPS`. Settlement light membuat batch dari ledger `merchant_payable` yang belum dibatch, dikelompokkan per merchant, lalu admin dapat menandai batch sebagai `paid`. UI `settlement-batch-management` sudah membaca batch real, menampilkan KPI settlement, membuka drawer detail batch, event history payout, generate batch, confirm paid dengan reference payout/note, mark failed/cancel dengan reason, reprocess failed/cancelled batch, dan download CSV payout report. Admin dashboard overview juga menampilkan summary settlement/finance dari agregasi DB: pending payout, paid payout, total platform fee, jumlah batch, batch terbaru, dan download CSV payout report per batch. UI `merchant-settlement-history` sudah membaca settlement batch milik merchant yang login, menampilkan pending/paid payout termasuk adjustment dan adjusted paid amount, membuka detail batch dengan payout event history, dan download CSV payout report merchant-scoped. ### Cleanup data smoke test ```bash PGHOST=127.0.0.1 PGPORT=5432 PGUSER=postgres PGPASSWORD=postgres PGDATABASE=qris_soundbox_platform npm run smoke:cleanup ``` Cleanup hanya menarget entitas smoke (`Smoke Merchant`, `PR-`, `DEV-`) agar data seed demo tidak ikut terhapus. ```bash PORT=3100 ADMIN_TOKEN=admin-dev-token DEVICE_TOKEN=device-dev-token INTEGRATION_WEBHOOK_SECRET=dev-callback-secret PGHOST=127.0.0.1 PGPORT=5432 PGUSER=postgres PGPASSWORD=postgres PGDATABASE=qris_soundbox_platform npm run smoke:flow ``` Smoke flow akan melakukan create merchant/device/transaction + heartbeat + callback paid + verifikasi event/heartbeat/notification, duplicate callback, invalid signature, audit log, ledger finance light, settlement batch light, skenario terminal tanpa binding, dynamic QR API-direct, expiry sweep dynamic QR, dynamic QR MQTT, device config push/retry/status/ack, dan trace MQTT config ack. ### Smoke test end-to-end (bootstrap + flow + cleanup) ```bash PGHOST=127.0.0.1 PGPORT=5432 PGUSER=postgres PGPASSWORD=postgres PGDATABASE=qris_soundbox_platform npm run smoke:e2e ``` Perintah ini menjalankan: - cleanup data smoke - start server lokal di port 3100 - wait sampai `/health` aktif - jalankan flow smoke lengkap - hentikan server setelah selesai ### Load test awal Jalankan backend lokal lalu eksekusi: ```bash PORT=3120 LOAD_CALLBACKS=30 LOAD_HEARTBEATS=60 LOAD_DYNAMIC_QR=30 LOAD_READS=30 LOAD_CONCURRENCY=10 npm run load:test ``` Script ini membuat data load test sementara, menjalankan burst transaction create, callback QRIS paid, heartbeat device, dynamic QR API, dan observability read, lalu membersihkan merchant/transaksi load test. Baseline lokal level 2 terakhir: 1610 request, 0 error, durasi 4955.45 ms, throughput perkiraan 324.9 req/s. p95 lokal: transaction create 183.56 ms, callback paid 129.03 ms, heartbeat 90.4 ms, dynamic QR 71.57 ms, observability summary 230.96 ms. ### Smoke test broker MQTT sungguhan ```bash npm run smoke:mqtt-real ``` Perintah ini membaca `.env`, menjalankan app lokal dengan `MQTT_PUBLISH_MODE=broker`, subscribe ke broker `devices/+/downlink/#`, lalu memverifikasi publish real untuk payment success, config push, dan dynamic QR response. Test akan membersihkan data merchant/transaksi/MQTT trace yang dibuat khusus smoke. Smoke broker real terakhir lulus terhadap `mqtts://mqtt.iptek.co:8883`: broker connect ok, subscribe `devices/+/downlink/#`, menerima payment success, config push, dynamic QR response, dan cleanup data smoke. ### Async export worker Export adjustment settlement memakai worker internal yang aktif default lewat `EXPORT_WORKER_ENABLED=true`. Endpoint create job mengembalikan status `pending`, worker mengambil job secara berkala, lalu client dapat polling `GET /admin/exports/{jobId}` sampai `completed` dan download dari `download_url`. UI `admin-reconciliation-management` sudah memakai flow async ini untuk tombol download adjustment report. Konfigurasi utama: - `EXPORT_WORKER_INTERVAL_MS` - `EXPORT_WORKER_BATCH_SIZE` - `EXPORT_JOB_STALE_RUNNING_MS` - `EXPORT_SETTLEMENT_ADJUSTMENT_MAX_ROWS` - `EXPORT_STORAGE_DIR` - `EXPORT_RETENTION_DAYS` Status worker dan count job tampil di `/admin/observability/summary`. Hasil export baru disimpan sebagai file di `EXPORT_STORAGE_DIR`; metadata job tetap di database. Worker juga membersihkan hasil export yang melewati `EXPORT_RETENTION_DAYS`. ### Provisioning credential MQTT device ```bash npm run mqtt:provision-device -- --base-url http://127.0.0.1:3000 --device-id ``` Perintah ini rotate credential device lewat API admin dan menampilkan command `mosquitto_passwd` untuk broker. Username MQTT device sama dengan `device_id` agar cocok dengan ACL Mosquitto `devices/%u/...`. Validasi/template ACL Mosquitto: ```bash npm run mqtt:check-acl -- --print-template npm run mqtt:check-acl -- --file /etc/mosquitto/acl ``` Smoke ACL memakai dua username device test: ```bash MQTT_TEST_DEVICE_A_USERNAME= \ MQTT_TEST_DEVICE_A_PASSWORD= \ MQTT_TEST_DEVICE_B_USERNAME= \ npm run smoke:mqtt-acl ``` UI device technical detail juga menyediakan tombol `Rotate Credential` dan modal one-time secret untuk operator. ### Backup dan restore readiness ```bash npm run backup:production -- --out ./backups --include-mosquitto npm run restore:plan -- --backup ./backups/.dump npm run restore:validate ``` `backup:production` membuat dump Postgres format custom dan opsional menyalin file Mosquitto passwd/ACL. `restore:plan` default hanya menampilkan command restore; tambahkan `--execute` hanya pada database restore yang memang sudah disiapkan. `restore:validate` menjalankan migration idempotent, health check, admin deep health, dan cek tabel kunci setelah restore. ### Load test staging profile ```bash npm run load:test:staging ``` Profile staging menjalankan transaction create, QRIS callback, heartbeat, dynamic QR, observability read, dan async export job. Set `BASE_URL`, token, dan env database staging sebelum menjalankan terhadap environment target. Hasil staging disimpan otomatis ke folder `reports/` melalui `LOAD_REPORT_FILE`. ### UI QA ringan ```bash npm run ui:qa ``` Checker ini memvalidasi inline script halaman operasional, memastikan export reconciliation memakai async job, dan memastikan navigasi placeholder di halaman operasional utama sudah dibersihkan. ### Rate limiting dan security polish Rate limit in-memory aktif default untuk login admin/merchant, admin write routes, device routes, dan integration callback routes. Login admin dan merchant juga dicatat ke audit log dengan action `admin.login.success`, `admin.login.failed`, `merchant.login.success`, dan `merchant.login.failed`. Halaman `/ui/admin-system-audit-logs` sudah membaca audit log real, menyediakan preset login events, filter action/entity/date, pencarian client-side, KPI login success/failed, dan drawer JSON payload. Konfigurasi utama: - `RATE_LIMIT_ENABLED` - `RATE_LIMIT_LOGIN_WINDOW_MS` - `RATE_LIMIT_LOGIN_MAX` - `RATE_LIMIT_DEVICE_WINDOW_MS` - `RATE_LIMIT_DEVICE_MAX` - `RATE_LIMIT_ADMIN_WRITE_WINDOW_MS` - `RATE_LIMIT_ADMIN_WRITE_MAX` - `TRUST_PROXY` - `JSON_BODY_LIMIT` Runbook operasional utama: `OPERATIONAL_RUNBOOK.md`. Checklist pilot: `PILOT_EXECUTION_CHECKLIST.md`. Catatan export storage: `EXPORT_STORAGE_READINESS.md`. ### Membuat admin user production ```bash npm run admin:create-user -- \ --email finance@example.com \ --name "Finance Ops" \ --role finance \ --password "ganti-dengan-password-kuat" ``` Role yang tersedia: `admin`, `finance`, `ops`, `support`, dan `viewer`. Untuk update password user yang sudah ada, tambahkan `--rotate-password`. ### Membuat merchant user production ```bash npm run merchant:create-user -- \ --merchant \ --email owner@merchant.com \ --name "Merchant Owner" \ --role owner \ --password "ganti-dengan-password-kuat" ``` Role merchant yang tersedia: `owner`, `finance`, `ops`, dan `viewer`. Untuk update password user yang sudah ada, tambahkan `--rotate-password`. ### Device API auth Endpoint `/device/*` mendukung credential per-device: ```bash curl -X POST http://127.0.0.1:3000/device/heartbeat \ -H "Content-Type: application/json" \ -H "X-Device-Id: " \ -H "X-Device-Secret: " \ -d '{"device_id":"","timestamp":"2026-05-28T00:00:00.000Z","network_strength":88,"battery_level":77}' ``` Credential hanya boleh dipakai untuk `device_id` yang sama. `Authorization: Bearer DEVICE_TOKEN` masih tersedia sebagai fallback/dev compatibility. Untuk production, fallback token global bisa dimatikan: ```env DEVICE_AUTH_ALLOW_LEGACY_TOKEN=false ``` ### Endpoint device lain - `POST /device/commands/ack` ### Quick screen preview - `GET /ui` => katalog halaman UI dari seluruh `design/*`. - `GET /ui/:page` => buka halaman berdasarkan slug (contoh: `/ui/admin-login`, `/ui/admin-dashboard-overview`, `/ui/merchant-login`). Status lanjutan: Fase 1 core flow sudah tercakup smoke e2e. Fase 2 sudah aktif untuk capability resolver, dynamic QR API-direct, dynamic QR MQTT via outbox, dan device config push/status/retry/ack. Catatan Fase 2 ops: endpoint daftar/detail device admin juga mengirim `health_summary` (`status`, `score`, `age_seconds`, `reasons`) untuk membantu triage device. UI device registry dan device technical detail sudah menampilkan health summary, config drift, dan retry config push. Catatan credential device: `POST /admin/devices/{id}/credentials/rotate` menerbitkan MQTT username/password satu kali, menyimpan fingerprint secret di database, dan payload device biasa hanya menampilkan ringkasan credential tanpa secret.