Lewati ke konten utama

Alur Integrasi PSP — Overview (Internal)

AUDIENCE: INTERNAL ONLY. Dipindahkan dari Public site ke Internal pada 2026-04-30 karena berisi info tentang tier PSP, identitas acquirer (Mandiri/BRI/BNI), dan arsitektur multi-tenant yang bukan untuk konsumsi publik. Public-facing doc untuk Tier 3 partner ada di api_docs/public/docs/api-docs/partner-api.md (tanpa mention PSP).

High-level flow bi-directional antara sistem PSP (Payment Service Provider) dan Kesles Merchant. Versi terstruktur lengkap (endpoint, payload, encryption spec) ada di PSP Integration Contract. Dokumen ini sebagai overview alur untuk onboarding engineer internal / partner team yang sudah NDA-locked.

Status integrasi (per 2026-05-31)

Sistem production-ready untuk dua tier:

  • PSP Internal Kesles (payment.kesles.com) — single-tenant, akses penuh termasuk endpoint inbound /psp/v1/events, /psp/v1/settlements, dan nmid-assignment.
  • PSP Bank / Acquirer eksternal (mis. Mandiri, BRI, BNI) — multi-tenant. Tiap bank dapat HMAC credential terpisah; sistem otomatis memfilter agar bank hanya melihat merchant yang NMID-nya diterbitkan oleh acquirer tersebut. Endpoint write-side (NMID assignment, payment events) dibatasi hanya untuk PSP Internal.

Untuk onboarding PSP Bank, hubungi tim integrasi Kesles dengan info: SWIFT 4-letter bank code, daftar IP allowlist untuk akses production, dan PIC teknis untuk koordinasi rotation kunci HMAC.

Service & Port Map

ServicePortPrefix EndpointKeterangan
payment-service8085/psp/v1/events · /psp/v1/settlementsEvent receiver (transaksi, NMID status, settlement)
dashboard_api8082/api/psp/v1/merchants/*Merchant lookup (read-only)
dashboard_api8082/api/psp/v1/merchants/{id}/nmid-assignmentNMID assignment callback
Status integrasi endpoint (per 2026-06-07)

POST /api/psp/v1/payment-events/* di dashboard_api sudah dihapus (Phase 4 selesai 2026-06-05). Sole receiver untuk semua event PSP adalah /psp/v1/events dan /psp/v1/settlements di payment-service (port 8085).

Base URL

EnvironmentURLStatus
Productionhttps://api-merchant.kesles.comDNS ✅ · cert SAN ✅ · nginx vhost ✅ · backend live
Staginghttps://api-merchant-staging.kesles.combelum di-provision
Local devhttp://127.0.0.1:8085 (payment-service) · http://127.0.0.1:8082 (dashboard_api)Loopback 401 missing_auth_headers jika tanpa credential ✅

Peran & Tanggung Jawab

SistemOwner dataExposure
Kesles MerchantMerchant profile, status, transaction recordMobile app, dashboard, partner API
PSPQRIS master, settlement, bank credentials, PSP-side merchant/terminal identifierH2H ke bank, customer payment page

Alur 1 — PSP Lookup Merchant

PSP menerima QRIS payment → butuh merchant info dari Kesles berdasarkan NMID.

PSP Kesles Merchant (dashboard_api:8082)
│ │
├─ GET /api/psp/v1/merchants/ ────>│ verify HMAC (X-API-Key-ID)
│ by-nmid/{nmid} │ verify IP allowlist
│ X-API-Key-ID: key-id │ filter bank_code
│ X-Timestamp: unix_secs │ return merchant data
│ X-Signature: hmac-sha256=.. │
│<──── 200 { merchant: {...} } ────┤

Auth: HMAC-SHA256 — lihat Integration Contract.


Alur 2A — PSP Forward Transaction Event (Flow A: Customer → Merchant)

Transaksi sukses di bank dengan NMID milik merchant Kesles → PSP push event ke payment-service.

PSP payment-service (8085) core_api (8080)
│ │ │
├─ POST /psp/v1/events ───────────>│ verify HMAC (X-PSP-Key-ID) │
│ X-PSP-Key-ID: key-id │ cek external_event_id │
│ X-PSP-Timestamp: rfc3339 │ nmid → merchant lookup │
│ X-PSP-Signature: <hex,noprefix>│ INSERT payment.transactions │
│ { │ │
│ "external_event_id": "...", │── POST /internal/ │
│ "event_type": "transaction │ transaction-status ───────>│
│ .created", │ (X-Internal-API-Key) │
│ "nmid": "ID102...", │ │──► FCM push
│ "gross_amount": 75000, │<── 202 Accepted ─────────────┤ ke merchant
│ ...flat fields... │ │
│ } │
│<──── 200 { ack: true, flow:"A" }─┤

Idempotency: external_event_id unique. Request yang sama dikirim ulang → HTTP 200 {"status":"duplicate_skipped"}, tidak diproses ulang.


Alur 2B — PSP Forward Transaction Event (Flow B: Merchant Beli Produk Kesles)

Transaksi sukses di bank dengan NMID korporat Kesles (ID9999999999999) → PSP push event → payment-service → notify order-service.

PSP payment-service (8085) order-service
│ │ │
├─ POST /psp/v1/events ───────────>│ verify HMAC │
│ { "nmid": "ID9999999999999", │ cek nmid = korporat │
│ "event_type": "transaction │ JANGAN insert ke │
│ .created", │ payment.transactions merchant│
│ "gross_amount": 250000, ...} │── notify order-service ─────>│
│ │ │
│<──── 200 { ack: true, flow:"B" }─┤

NMID korporat ID9999999999999 dikonfigurasi di db_kesles_merchant_payment.payment.qris_config.


Alur 3 — PSP Forward Settlement

Settlement harian dari bank → payment-service.

PSP payment-service (8085)
│ │
├─ POST /psp/v1/settlements ──────>│ verify HMAC
│ { "external_event_id": "...", │ INSERT payment.settlements
│ "nmid": "ID102...", │
│ "gross_amount": 3240000, ... }│
│<──── 200 { ack: true } ──────────┤

Alur 4 — NMID Status Changed

Bank suspend/aktifkan NMID → payment-service update status merchant.

PSP payment-service (8085)
│ │
├─ POST /psp/v1/events ───────────>│ verify HMAC
│ { "event_type": │ lookup merchant by nmid
│ "nmid.status_changed", │ UPDATE merchant.status
│ "nmid": "ID102...", │ push notification ke owner
│ "nmid_new_status": "suspended│
│ "nmid_old_status": "active", │
│ "nmid_reason": "fraud_detected"
│ } │
│<──── 200 { ack: true } ──────────┤

Alur 5 — Lifecycle Merchant (Kesles → PSP)

Status: TERIMPLEMENTASI (2026-05-20), dispatch OFF default. Outbound client (psp_outbound_client.go) live dengan mode switch PSP_OUTBOUND_MODE (default disabled). Lifecycle dispatcher (psp_outbound_triggers.go) di-wire ke flow approve / PATCH / DELETE merchant. Saat tim PSP siap, ops set 4 env vars + restart — tidak perlu deploy code baru. Lihat contract §5.0 untuk detail mode & aktivasi.

Merchant di-approve / suspend / activate / terminate di Kesles dashboard → otomatis sync ke PSP saat mode=dispatch.

Kesles Merchant (dashboard_api) PSP (payment.kesles.com)
│ │
│ (a) Operator approve / PATCH / DELETE merchant │
│ di dashboard │
│ (b) firePSPMerchantLifecycleAsync │
│ decide: register|suspend|reactivate| │
│ terminate|update │
│ (c) INSERT psp.outbound_requests (audit row) │
│ │
├─ POST/PATCH payment.kesles.com/api/... ────────>│ verify HMAC
│ (signed via psp_integration_go.Signer) │ update merchant status
│<──── 200/2xx ──────────────────────────────────┤
│ │
│ (d) UPDATE psp.outbound_requests │
│ (response_status + duration_ms + body) │
│ │
│ Audit visible: /api/dashboard/integrations/ │
│ psp-outbound/audit │

Mode behavior:

ModeBehavior
disabled (default)Skip semua step b–d, no-op
audit_onlyEksekusi b–c, skip HTTP call
dispatchEksekusi semua step b–d (production)

Penanganan Error

StatusKeteranganRetry?
200/201SuccessNo
401HMAC / timestamp / IP invalidNo — fix config
404Merchant tidak ditemukan / tidak aktifNo
429Rate limit exceededYes, exponential backoff
5xxServer errorYes, max 4 kali (1s, 2s, 4s, 8s)

Praktik Terbaik

  1. Subscribe event via HTTP POST, bukan polling — menghemat rate limit
  2. Store X-Request-ID dari response untuk trace kalau perlu eskalasi ke support
  3. Implement circuit breaker di sisi PSP — kalau Kesles down, jangan retry terus sampai thundering herd
  4. Pakai dual-secret mode saat Kesles notify rotasi secret, supaya zero-downtime
  5. String-to-sign format BERBEDA: Lookup = {unix_seconds}\n{METHOD}\n{path}\n{body} (timestamp first, integer); Event Receiver = {METHOD}\n{path}\n{RFC3339_timestamp}\n{body} (method first, timestamp string). Signature Scheme A pakai prefix hmac-sha256=, Scheme B hex biasa tanpa prefix.

Langkah Berikutnya