Files
Qris-Soundbox/13-fase1-step2-callback-transaction-spec.md
2026-05-25 08:22:12 +07:00

5.7 KiB

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