Files
Qris-Soundbox/README.md
2026-06-04 11:20:16 +07:00

340 lines
15 KiB
Markdown

# 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
- DEBIAN12_APP_SERVER_SETUP.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 <token>`)
- `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://broker.bizone.id: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 <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=<device-a-id> \
MQTT_TEST_DEVICE_A_PASSWORD=<secret-a> \
MQTT_TEST_DEVICE_B_USERNAME=<device-b-id> \
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/<file>.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 <merchant-id-atau-code> \
--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: <device-id>" \
-H "X-Device-Secret: <one-time-secret-yang-dipasang-di-device>" \
-d '{"device_id":"<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.