# Fase 1 — Step 3: Notifikasi MQTT Dasar (Static Payment) Dokumen ini memandu implementasi notifikasi success payment ke device berbasis event transaksi `paid`. ## 1) Tujuan Step 3 - Memastikan setiap transaksi `paid` memicu notifikasi sukses ke device yang valid - Menyimpan status delivery dan retry agar operasional bisa dipantau - Menyediakan endpoint admin untuk retry manual ## 2) Event Source - Trigger dari Step 2: event internal `transaction.paid` - Sumber wajib: event bus internal / service method call dari transaction service (untuk fase awal) ## 3) Mapping Notifikasi - Input: `transaction_id`, `merchant_id`, `device_id`, `amount`, `currency`, `paid_at`, `reference` - Output: payload MQTT `payment_success` ke topik: - `devices/{deviceId}/downlink/payment/success` - Hanya kirim jika device memiliki binding aktif (`device_bindings.active_flag = true`) ## 4) Tabel dan Status ### `notifications` Kolom inti: - `id` - `transaction_id` - `device_id` - `delivery_channel` = `mqtt` - `payload_type` = `payment_success` - `delivery_status`: `queued | sent | acknowledged | failed | retrying` - `retry_count` - `ack_status`: `pending | received | not_supported | not_needed` - `sent_at` - `ack_at` Status transisi: - dibuat -> `queued` - publish sukses -> `sent` - no ack handler tersedia pada fase awal -> `acknowledged` otomatis atau `failed` sesuai konfigurasi broker - publish gagal -> `retrying` - retry 3x gagal -> `failed` ## 5) Payload Kontrak MQTT (Step 3) ```json { "message_type": "payment_success", "event_id": "evt_123", "transaction_id": "tx_123", "merchant_name": "Toko Berkah", "amount": 50000, "currency": "IDR", "paid_at": "2026-05-23T10:02:10Z", "audio_text": "Pembayaran diterima lima puluh ribu rupiah", "display_text": "Pembayaran diterima Rp50.000" } ``` ## 6) Alur Proses Notifikasi 1. Step 2 publish event `transaction.paid` 2. Notification Orchestrator menerima event 3. Validasi `device_id` ada dan memiliki binding aktif 4. Buat record `notifications` dengan status `queued` 5. Publish ke `devices/{deviceId}/downlink/payment/success` 6. Jika sukses, update `sent` dan `sent_at` 7. Jika gagal: - hitung retry (exponential/linear simple backoff) - update `retrying` - lanjut retry maksimum 3x 8. Jika 3x gagal tetap `failed` 9. Return result ke endpoint retry bila dipanggil manual ## 7) Retry Policy Fase 1 (Sederhana) - max_attempt = 3 - retry_interval_seconds = 15, 30, 60 (tetap untuk fase awal) - retry_count disimpan di `notifications.retry_count` - tiap percobaan harus idempotent via `event_id` dan `transaction_id` ## 8) Endpoint Admin - `POST /admin/transactions/{transactionId}/retry-notification` - validasi transaksi status harus `paid` - akan membuat attempt publish baru jika `delivery_status` bukan `acknowledged` - response: - `transaction_id` - `notification_id` - `delivery_status` - `next_retry_at` ## 9) Endpoint Ops Monitoring Minimal - `GET /admin/devices/{deviceId}/notifications` - list notif by device + `delivery_status` - `GET /admin/transactions/{transactionId}` - tampilkan link notification + timeline status ## 10) Idempotency dan De-Dupe - kombinasi key notifikasi: - `transaction_id + event_id` - jika event duplicate: - tidak membuat record baru - return existing notif status - publish duplicate harus ditolak di orchestrator (guard). ## 11) Error Code (saran) - `NOTIFICATION_DEVICE_UNAVAILABLE` - `NOTIFICATION_NO_ACTIVE_BINDING` - `NOTIFICATION_PUBLISH_FAILED` - `NOTIFICATION_RETRY_EXHAUSTED` ## 12) Acceptance Criteria Step 3 - transaksi `paid` menghasilkan event `payment_success` ke topik device - notification record terbentuk untuk setiap transaksi `paid` - perangkat yang tidak terikat aktif tidak dipush (state `failed` dengan reason) - retry berjalan saat publish gagal - endpoint retry admin merubah status dari `failed/retrying` menjadi `sent` atau tetap `failed` setelah limit ## 13) Catatan Implementasi Step 3 (Tanpa Design) - gunakan QoS 1 untuk topic payment success - hindari retained message untuk event sukses pembayaran - logging harus menyertakan `transaction_id`, `device_id`, `event_id`, `request_id` - payload audit (audio/display) boleh berupa default ID locale