317 lines
11 KiB
Markdown
317 lines
11 KiB
Markdown
# Codex Handoff - QRIS Soundbox Platform
|
|
|
|
Tanggal update: 2026-06-03, Asia/Jakarta.
|
|
|
|
Dokumen ini adalah snapshot kerja terakhir untuk melanjutkan project tanpa perlu membaca ulang seluruh chat.
|
|
|
|
## Status Terakhir
|
|
|
|
- Fokus hari ini bergeser dari production readiness umum ke integrasi device soundbox QF100.
|
|
- Folder SDK lokal `QF100-60s-l511-SecondApp-260107/` ditemukan dan dianalisis. Folder ini sengaja tidak dimasukkan git.
|
|
- Backend sekarang sudah punya adapter awal untuk firmware QF100 sample:
|
|
- config server vendor-compatible;
|
|
- payload MQTT payment success format QF100;
|
|
- smoke script backend untuk static + dynamic QF100.
|
|
- Target milestone berikutnya: dua soundbox real, satu static dan satu dynamic, bisa ambil config dari backend, connect MQTT, lalu static payment test bunyi dan tercatat di dashboard.
|
|
- Worktree aktif/dirty karena perubahan backend QF100 dan update handoff. Jangan revert perubahan yang tidak eksplisit diminta.
|
|
|
|
## Verifikasi Terakhir
|
|
|
|
- `npm run typecheck`: pass setelah perubahan QF100.
|
|
- `npm install`: sudah dijalankan untuk memasang dependency lokal; perubahan lockfile accidental sudah dibalik.
|
|
- `npm run smoke:qf100`: script sudah dibuat, belum dijalankan end-to-end di DB/server lokal pada turn ini.
|
|
- Verifikasi lama yang masih relevan:
|
|
- `npm run db:migrate`: sebelumnya pass dan idempotent sampai migration `003_export_job_storage.sql`.
|
|
- `npm audit --json`: sebelumnya pass, 0 vulnerability.
|
|
- `npm run ui:qa`: sebelumnya pass.
|
|
- `npm run smoke:e2e`: sebelumnya pass.
|
|
- Real MQTT smoke sebelumnya pernah pass terhadap `mqtts://broker.bizone.id:8883`.
|
|
|
|
## SDK QF100 Yang Ditemukan
|
|
|
|
- Folder lokal: `QF100-60s-l511-SecondApp-260107/`.
|
|
- Sudah ditambahkan ke `.gitignore`:
|
|
- `QF100-60s-l511-SecondApp-260107/`
|
|
- Struktur penting:
|
|
- `docs/Config Server.docx`: kontrak config server vendor.
|
|
- `docs/Cloud Speaker API Spec V2.8.7.pdf`: API spec cloud speaker.
|
|
- `app/source/MainApp/globalDefine.h`: device model, demo mode, `CONFIG_ADDR`.
|
|
- `app/source/MainApp/demo.c`: alur config server, MQTT connect/subscribe, parse payment payload.
|
|
- `app/source/MainApp/demo.h`: MQTT TLS/QoS/clean session/cert config.
|
|
- `app/source/MainApp/main.c`: boot flow dan monitor network/MQTT.
|
|
- `app/inc/MercuryMqtt.h`: header MQTT SDK.
|
|
- `app/release/user_app.bin`: firmware build existing.
|
|
|
|
## Kesimpulan SDK QF100
|
|
|
|
- SDK ini memungkinkan develop/patch aplikasi firmware sendiri, tetapi bentuknya embedded C firmware app, bukan SDK backend.
|
|
- Strategi dipilih: firmware hanya hardcode URL config server backend kita, bukan hardcode broker MQTT.
|
|
- Firmware sample boot lalu call `CONFIG_ADDR` ke endpoint vendor `/speaker/dev-config`.
|
|
- Request config berisi field seperti:
|
|
- `dev-model`
|
|
- `item-number`
|
|
- `dev-sn`
|
|
- `fw-version`
|
|
- `fw-build`
|
|
- `app-config-version`
|
|
- `imei`
|
|
- `imsi`
|
|
- `iccid`
|
|
- Response yang firmware cari memiliki blok:
|
|
- `mqtt.broker-ip`
|
|
- `mqtt.broker-port`
|
|
- `mqtt.client-id`
|
|
- `mqtt.user-name`
|
|
- `mqtt.password`
|
|
- `mqtt.subscribe-topic`
|
|
- `mqtt.keep-alive`
|
|
- Payment success payload yang firmware sample bisa bunyikan:
|
|
```json
|
|
{
|
|
"header": {
|
|
"category": 1
|
|
},
|
|
"data": {
|
|
"pay-amount": 15000
|
|
}
|
|
}
|
|
```
|
|
- `MQTT_TLS_ENABLE` di SDK sample masih `0`. Jika broker production memakai `mqtts://...:8883`, firmware perlu patch TLS atau pilot perlu listener non-TLS terbatas.
|
|
|
|
## Implementasi QF100 Backend Hari Ini
|
|
|
|
### 1. Config Server Vendor-Compatible
|
|
|
|
- Route baru:
|
|
- `GET /speaker/dev-config`
|
|
- File:
|
|
- `src/routes/speaker.ts`
|
|
- mounted di `src/app.ts`
|
|
- Perilaku:
|
|
- menerima query/body vendor;
|
|
- lookup device memakai `dev-sn` -> `devices.serial_number`;
|
|
- jika device aktif, update:
|
|
- `vendor`
|
|
- `model`
|
|
- `communication_mode`
|
|
- `last_seen_at`
|
|
- `firmware_version`
|
|
- mencatat heartbeat `state: config_pull`;
|
|
- balas JSON vendor top-level, bukan wrapper internal `successResponse`.
|
|
- Error vendor style:
|
|
- `1001`: `dev-sn` kosong;
|
|
- `1002`: device tidak terdaftar atau inactive;
|
|
- `1003`: broker MQTT belum dikonfigurasi.
|
|
|
|
### 2. Env QF100
|
|
|
|
- Env baru di `src/config/env.ts` dan `.env.example`:
|
|
- `QF100_MQTT_BROKER_HOST`
|
|
- `QF100_MQTT_BROKER_PORT`
|
|
- `QF100_MQTT_USERNAME`
|
|
- `QF100_MQTT_PASSWORD`
|
|
- `QF100_MQTT_KEEP_ALIVE_SECONDS`
|
|
- Jika host/port QF100 kosong, endpoint mencoba parse dari `MQTT_BROKER_URL`.
|
|
- Catatan penting: password MQTT yang dikirim ke QF100 diambil dari `QF100_MQTT_PASSWORD` atau fallback `MQTT_PASSWORD`. Sistem saat ini menyimpan credential device sebagai fingerprint, jadi plaintext per-device tidak bisa dibaca ulang dari DB.
|
|
|
|
### 3. Payload MQTT QF100
|
|
|
|
- File:
|
|
- `src/shared/services/mqttPublisher.ts`
|
|
- `src/shared/orchestrators/notificationOrchestrator.ts`
|
|
- Topic QF100:
|
|
- `devices/{deviceId}/downlink/qf100`
|
|
- Adapter memilih format QF100 jika:
|
|
- `device.model` mengandung `QF100`; atau
|
|
- `capability_profile_json.mqtt_payload_profile`, `protocol_profile`, atau `vendor_protocol` bernilai `qf100`.
|
|
- Downlink payment sekarang juga dicatat ke `mqtt_messages`, sehingga dashboard device detail bisa melihat topic/payload yang dikirim.
|
|
|
|
### 4. Device Store dan Schema
|
|
|
|
- Lookup baru:
|
|
- `getDeviceBySerialNumber(serialNumber)` di `src/shared/store/deviceStore.ts`.
|
|
- Schema bootstrap menambahkan index:
|
|
- `idx_devices_serial_number`.
|
|
|
|
### 5. Smoke QF100
|
|
|
|
- Script baru:
|
|
- `scripts/smoke-qf100-adapter.mjs`
|
|
- Package script baru:
|
|
- `npm run smoke:qf100`
|
|
- Script ini:
|
|
- create merchant/outlet/terminal/device static;
|
|
- create merchant/outlet/terminal/device dynamic MQTT;
|
|
- set `model: QF100` dan `capability_profile_json.mqtt_payload_profile: qf100`;
|
|
- hit `/speaker/dev-config` untuk dua SN;
|
|
- validasi MQTT config vendor;
|
|
- trigger static QRIS paid callback;
|
|
- validasi `mqtt_messages` downlink QF100 payload `category: 1`;
|
|
- trigger backend dynamic QR MQTT flow untuk device dynamic.
|
|
- Untuk memakai SN real:
|
|
```bash
|
|
QF100_STATIC_SN=<sn-static> \
|
|
QF100_DYNAMIC_SN=<sn-dynamic> \
|
|
BASE_URL=http://127.0.0.1:3000 \
|
|
npm run smoke:qf100
|
|
```
|
|
|
|
## Device Flow Yang Disepakati
|
|
|
|
### Static Soundbox
|
|
|
|
1. Device boot.
|
|
2. Firmware call backend `/speaker/dev-config`.
|
|
3. Backend balas MQTT config.
|
|
4. Device connect MQTT dan subscribe `devices/{deviceId}/downlink/qf100`.
|
|
5. QRIS callback paid masuk backend.
|
|
6. Backend publish payload QF100 `category: 1`.
|
|
7. Device bunyi nominal.
|
|
8. Dashboard melihat transaction, notification, mqtt message, last seen/config pull.
|
|
|
|
### Dynamic Soundbox
|
|
|
|
- Prinsip: trigger tetap via MQTT, bukan polling terus-menerus.
|
|
- HTTP polling dynamic QR dianggap tidak cocok untuk skala 20 ribu device karena `gap=30s` berarti sekitar 40 ribu request/menit.
|
|
- Desain lanjutan:
|
|
1. Backend/admin/merchant membuat dynamic QR request.
|
|
2. Backend publish MQTT command ke device.
|
|
3. Device menampilkan QR langsung atau HTTP fetch QR detail dari URL.
|
|
4. Payment callback tetap jadi source of truth.
|
|
5. Payment success tetap via MQTT `category: 1`.
|
|
- Firmware dynamic handler belum dipatch. Perlu tambah handler misalnya `category == 10` di `demo.c`.
|
|
|
|
## Dashboard Yang Perlu Disiapkan Berikutnya
|
|
|
|
Prioritas UI/dashboard untuk test dua soundbox real:
|
|
|
|
1. Device detail QF100 panel:
|
|
- SN;
|
|
- model/profile;
|
|
- config pull terakhir;
|
|
- broker host/port yang dikirim;
|
|
- client-id;
|
|
- subscribe-topic;
|
|
- keepalive.
|
|
2. Test payment button:
|
|
- nominal quick actions, misalnya Rp1.000 dan Rp15.000;
|
|
- create dummy tx/callback internal;
|
|
- tampilkan notification dan MQTT payload result.
|
|
3. MQTT timeline:
|
|
- direction;
|
|
- topic;
|
|
- message_type;
|
|
- payload JSON;
|
|
- publish_status/reason;
|
|
- timestamp.
|
|
4. Dynamic QR panel:
|
|
- input nominal;
|
|
- create dynamic QR;
|
|
- tampilkan QR payload/status;
|
|
- nanti `Send to Soundbox` via MQTT command setelah firmware handler siap.
|
|
5. Ops summary:
|
|
- total soundbox;
|
|
- online/stale/offline;
|
|
- config pull terbaru;
|
|
- payment notification sent/failed;
|
|
- dynamic QR active/paid/expired.
|
|
|
|
## Endpoint Penting Saat Ini
|
|
|
|
- Health:
|
|
- `GET /health`
|
|
- `GET /health/deep`
|
|
- QF100 vendor config:
|
|
- `GET /speaker/dev-config`
|
|
- Admin auth/session:
|
|
- `POST /admin/login`
|
|
- `POST /admin/logout`
|
|
- `GET /admin/me`
|
|
- Admin device:
|
|
- `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`
|
|
- `GET /admin/devices/{id}/mqtt-messages`
|
|
- `GET /admin/devices/{id}/notifications`
|
|
- Device API:
|
|
- `POST /device/heartbeat`
|
|
- `POST /device/transactions/dynamic-qr`
|
|
- `POST /device/mqtt/uplink/dynamic-qr/request`
|
|
- `GET /device/config`
|
|
- `POST /device/config/ack`
|
|
- Integrations:
|
|
- `POST /integrations/qris/callback`
|
|
- Observability:
|
|
- `GET /admin/observability/summary`
|
|
- `GET /admin/observability/mqtt-status`
|
|
|
|
## Package Scripts Penting
|
|
|
|
- `npm run typecheck`
|
|
- `npm run db:migrate`
|
|
- `npm run smoke:qf100`
|
|
- `npm run smoke:e2e`
|
|
- `npm run smoke:mqtt-real`
|
|
- `npm run smoke:mqtt-acl`
|
|
- `npm run ui:qa`
|
|
- `npm run deploy:check-env`
|
|
- `npm run load:test`
|
|
- `npm run load:test:staging`
|
|
- `npm run mqtt:provision-device`
|
|
- `npm run mqtt:check-acl`
|
|
- `npm run admin:create-user`
|
|
- `npm run merchant:create-user`
|
|
|
|
## File Kunci Yang Sering Disentuh
|
|
|
|
- App bootstrap: `src/app.ts`
|
|
- Env config: `src/config/env.ts`
|
|
- QF100 config route: `src/routes/speaker.ts`
|
|
- Device route: `src/routes/device.ts`
|
|
- Admin route: `src/routes/admin.ts`
|
|
- MQTT publisher: `src/shared/services/mqttPublisher.ts`
|
|
- MQTT subscriber: `src/shared/services/mqttSubscriber.ts`
|
|
- Notification orchestrator: `src/shared/orchestrators/notificationOrchestrator.ts`
|
|
- Device store: `src/shared/store/deviceStore.ts`
|
|
- MQTT message store: `src/shared/store/mqttMessageStore.ts`
|
|
- Heartbeat store: `src/shared/store/heartbeatStore.ts`
|
|
- Schema bootstrap: `src/shared/db/pool.ts`
|
|
- QF100 smoke: `scripts/smoke-qf100-adapter.mjs`
|
|
- Env sample: `.env.example`
|
|
|
|
## Sisa Gap Utama
|
|
|
|
1. Jalankan `npm run smoke:qf100` terhadap backend + DB lokal/staging.
|
|
2. Register dua device real:
|
|
- static SN -> device static;
|
|
- dynamic SN -> device dynamic.
|
|
3. Patch firmware QF100:
|
|
- `CONFIG_ADDR` ke backend kita;
|
|
- TLS setting sesuai broker.
|
|
4. Test static device real:
|
|
- config pull;
|
|
- MQTT connect;
|
|
- payment success bunyi;
|
|
- dashboard melihat downlink.
|
|
5. Patch firmware dynamic:
|
|
- tambah handler command dynamic QR, kemungkinan `category == 10`;
|
|
- pilih direct QR payload vs HTTP fetch detail.
|
|
6. Siapkan dashboard QF100 detail/test panel.
|
|
7. Putuskan credential strategy production:
|
|
- shared pilot password saat ini cukup untuk lab;
|
|
- production sebaiknya credential per-device yang bisa diprovisioning aman.
|
|
8. Tetap perlu staging rehearsal, restore drill, export storage strategy, dan manual visual QA dari handoff lama.
|
|
|
|
## Catatan Penting
|
|
|
|
- Folder SDK QF100 adalah artefak lokal dan jangan dimasukkan git.
|
|
- Jangan hardcode broker credential production di firmware.
|
|
- Firmware cukup hardcode config server URL; broker/topic/credential dikirim dari backend.
|
|
- Jangan gunakan wildcard MQTT subscribe di production kecuali maintenance terkontrol.
|
|
- Jika memakai MQTT TLS (`mqtts://...:8883`), firmware QF100 perlu `MQTT_TLS_ENABLE=1` dan sertifikat yang sesuai.
|
|
- Rate limiting aktif default; `/speaker` memakai limiter device.
|
|
- `CODEX_HANDOFF.md` ini adalah snapshot operasional terbaru; untuk detail historis keputusan, baca `DECISIONS_LOG.md`.
|