157 lines
5.7 KiB
Markdown
157 lines
5.7 KiB
Markdown
# Fase 1 — Step 2: Transaction Engine + Webhook Callback (Spesifikasi Implementasi)
|
|
|
|
Dokumen ini merinci implementasi callback QRIS, state machine transaksi, dan eventing dasar untuk alur static flow.
|
|
|
|
## 1) Tujuan Step 2
|
|
- Menerima callback pembayaran dari partner secara aman dan idempotent
|
|
- Menyimpan state transaksi dari `initiated` sampai `paid/failed/expired`
|
|
- Menyimpan jejak event untuk audit dan observability
|
|
- Menyiapkan output event agar notifikasi MQTT dan pelaporan ops bisa dipicu
|
|
|
|
## 2) Alur State Transaksi (Static Flow)
|
|
|
|
1. `initiated`
|
|
2. `awaiting_payment`
|
|
3. `paid`
|
|
4. `failed`
|
|
5. `expired`
|
|
6. `reversed` (opsional untuk fase awal)
|
|
|
|
### Transisi Utama
|
|
- `initiated` -> `awaiting_payment`
|
|
- terjadi saat transaksi static dibuat dari mapping merchant/outlet/terminal
|
|
- saat ini boleh dilakukan di Step 2 sebagai fallback/manual test
|
|
- `awaiting_payment` -> `paid`
|
|
- terjadi saat callback partner valid dengan status success
|
|
- `awaiting_payment` -> `failed`
|
|
- status partner gagal/declined/rejected
|
|
- `awaiting_payment` -> `expired`
|
|
- expiry waktu lewat atau event timeout dari partner
|
|
- `failed` / `paid` adalah terminal state untuk Step 1
|
|
|
|
## 3) API Endpoint: Webhook Callback
|
|
|
|
### Endpoint
|
|
- `POST /integrations/qris/callback`
|
|
|
|
### Request Headers
|
|
- `X-Partner-Signature`: signature HMAC (mandatory)
|
|
- `X-Partner-Event`: jenis event
|
|
- `Idempotency-Key` (jika tersedia dari partner)
|
|
- `X-Request-Id` (opsional, gunakan untuk trace)
|
|
|
|
### Behavior
|
|
- Validasi signature sebelum payload diproses
|
|
- Jika signature invalid: response `401` dan tidak membuat transaksi
|
|
- Parsing event_type:
|
|
- sukses -> candidate `paid`
|
|
- gagal -> candidate `failed`
|
|
- expired / timeout -> candidate `expired`
|
|
- Kunci idempotency:
|
|
- key = kombinasi partner_reference + signature_reference/transaction_reference + status event
|
|
- Jika sudah diproses:
|
|
- kembalikan response sukses idempotent (tanpa mengubah state lagi)
|
|
|
|
### Request/Response Format
|
|
- Response sukses:
|
|
- `status`: `accepted`
|
|
- `request_id`
|
|
- `event_id`
|
|
- `timestamp`
|
|
- Response error:
|
|
- envelope dari keputusan `D-003`
|
|
|
|
## 4) Validasi Callback
|
|
|
|
### Required Fields (minimum)
|
|
- `partner_reference`
|
|
- `amount`
|
|
- `currency` (default `IDR`)
|
|
- `status` / `payment_status`
|
|
- `paid_at`
|
|
- `merchant_id` / `terminal_id` / mapping key lain dari payload partner
|
|
- `signature`
|
|
|
|
### Validasi Data
|
|
- `amount > 0`
|
|
- transaksi ditemukan: mapping `partner_reference` ke `transactions.partner_reference` (atau lookup `merchant_reference`)
|
|
- status yang tidak dikenali -> log warning dan set ke `failed` dengan reason `UNKNOWN_STATUS`
|
|
- cek expiry jika ada (`expired_at`) saat status sukses masuk -> jika expired, set `failed` / `expired`
|
|
|
|
## 5) Idempotency Strategy
|
|
|
|
### Tabel Idempotency
|
|
- gunakan `idempotency_keys` yang sudah didefinisikan di Step 1
|
|
- scope: `callback_processing`
|
|
- key value: hash dari `(partner_reference + payment_status + partner_txn_id)`
|
|
- jika key sudah ada:
|
|
- return hasil callback yang sama (replay safe)
|
|
|
|
## 6) Transaction Store yang Diperlukan
|
|
|
|
### `transactions`
|
|
- fields yang harus dipopulasi di Step 2:
|
|
- `merchant_id`, `outlet_id`, `terminal_id`, `device_id` (jika static device direct binding)
|
|
- `qr_mode` = `static`
|
|
- `initiation_mode` = `static`
|
|
- `partner_reference`
|
|
- `amount`, `currency`, `status`
|
|
- `created_at`, `paid_at`, `expired_at`, `updated_at`
|
|
|
|
### `transaction_events`
|
|
- event wajib untuk setiap perubahan state:
|
|
- `event_type`: `INITIATED`, `STATE_CHANGED`, `CALLBACK_RECEIVED`, `CALLBACK_REJECTED`, `CALLBACK_DUPLICATE`, `PUSH_QUEUED`
|
|
- `source`: `webhook`, `system`, `admin`
|
|
- `payload_json`: raw payload callback + context
|
|
- urutan event dipakai untuk debugging urut transaksi
|
|
|
|
## 7) Integrasi Ke Step 3 (Notifikasi)
|
|
- ketika state transaksi berubah ke `paid`, emit event internal:
|
|
- `transaction.paid`
|
|
- payload ringan: `transaction_id`, `merchant_id`, `device_id`, `amount`, `currency`, `partner_reference`, `paid_at`
|
|
- event ini menjadi trigger awal untuk notification orchestrator Step 3
|
|
- jika transaction tidak punya binding aktif:
|
|
- event tetap tercatat dengan reason `NO_ACTIVE_BINDING`
|
|
|
|
## 8) Error Code (saran)
|
|
- `WEBHOOK_SIGNATURE_INVALID`
|
|
- `TRANSACTION_NOT_FOUND`
|
|
- `PAYMENT_STATUS_INVALID`
|
|
- `DUPLICATE_WEBHOOK`
|
|
- `CALLBACK_PARTNER_DATA_INVALID`
|
|
- `IDEMPOTENCY_MISSING_KEY`
|
|
|
|
## 9) Contoh Pseudocode Alur Handler
|
|
|
|
1. terima payload
|
|
2. validasi signature
|
|
3. normalisasi `request_id` dan `event_id`
|
|
4. ambil `partner_reference` -> cari transaksi
|
|
5. cek idempotency callback
|
|
6. jika duplikat: simpan `transaction_events` duplicate dan return success
|
|
7. validasi transaksi + status
|
|
8. update status + paid_at jika sukses
|
|
9. simpan event (`CALLBACK_RECEIVED`, `STATE_CHANGED`)
|
|
10. emit `transaction.paid` (hanya saat sukses)
|
|
11. commit
|
|
12. return response success
|
|
|
|
## 10) API/Query untuk Operasional (minimal)
|
|
- `GET /admin/transactions` tetap support filter `status`, `merchant_id`, `from`, `to`, `partner_reference`
|
|
- `GET /admin/transactions/{transactionId}` menampilkan event timeline
|
|
- `GET /admin/transactions/{transactionId}/events` opsional di Step 2 untuk debug
|
|
|
|
## 11) Acceptance Criteria Step 2
|
|
- webhook menerima callback valid dan mengubah state transaksi ke `paid`
|
|
- callback duplicate tidak menambah perubahan state tambahan
|
|
- signature invalid ditolak `401`
|
|
- jika partner reference tidak ditemukan, response tetap deterministik dan tidak crash
|
|
- setiap perubahan state menghasilkan `transaction_events` minimal satu baris
|
|
- setiap perubahan ke `paid` menghasilkan event internal `transaction.paid` untuk Step 3
|
|
|
|
## 12) Keberhasilan Handoff ke Step 3
|
|
- state machine stabil dan bisa dipakai unit/integration smoke
|
|
- transaksi berhasil dan gagal terekam lengkap
|
|
- callback replay aman
|
|
- notifikasi orchestrator dapat subscribe pada event `transaction.paid`
|