PSP Tester API
Sandbox endpoint to test PSP integration without touching the production PSP or bank.
Auth: Bearer JWT staff dengan role superAdmin (atau UUID di SUPER_ADMIN_USER_IDS), divalidasi requireSuperAdmin middleware. Bukan header X-Internal-API-Key, bukan cookie session.
Base URL: /api/integration/v1/psp di integration_api (port 8092)
integration_apiPSP Tester dan PSP External Tester dimiliki integration_api (port 8092) sejak cutover 2026-06-16. Tidak ada handler PSP tester di dashboard_api (services/dashboard_api/) — endpoint dipanggil langsung ke integration_api di /api/integration/v1/psp/{keys,execute}. Client Flutter (merchant_integration) memakai base /api/integration/v1.
- Tests untuk Events API (
/psp/v1/events,/psp/v1/settlements) → request dikirim dariintegration_apikepayment_serviceport 8085 secara internal - Tests untuk Lookup API (
/api/psp/v1/merchants/*) → loopback kedashboard_api:8082(lookup API tetap dimiliki dashboard_api) - Tester endpoint hanya tersedia via
integration_api:8092
Tidak ada auto-seeding merchant test. Gunakan merchant sandbox yang sudah ada (lihat setup di docs/sandbox-merchant-setup.md).
Use Cases
- QA end-to-end payment flow testing without charging a real card
- Load testing before releasing transaction features
- Simulating edge cases: duplicate event, late webhook, invalid signature
- Testing Flow A vs Flow B routing based on NMID
List API Keys Aktif
GET /api/integration/v1/psp/keys
Menampilkan PSP API keys yang aktif untuk sandbox.
curl -X GET https://api-merchant.kesles.com/api/integration/v1/psp/keys \
-H "Authorization: Bearer <superadmin_staff_jwt>"
Execute Test Event
POST /api/integration/v1/psp/execute
PSP Internal Tester panel menggunakan format langsung: pilih method + path + body di panel, lalu execute. Panel tidak menggunakan field simulate_type — request dikirim sesuai path yang dipilih operator.
integration_api melakukan HMAC signing di server side sebelum forward ke target service. Secret tidak pernah keluar ke browser.
Tiga Jalur Eksekusi
1. PSP Lookup API (/api/psp/v1/merchants/*)
Pilih API key dari dropdown, pilih endpoint lookup (mis. by-nmid, by-mid, by-code, by-serial, {id}/devices), isi path param, klik Execute. Backend melakukan HMAC sign dengan X-API-Key-ID / X-Timestamp / X-Signature lalu loopback ke dashboard_api:8082.
Contoh eksekusi langsung via curl (alternatif panel):
curl -X POST https://api-merchant.kesles.com/api/integration/v1/psp/execute \
-H "Authorization: Bearer <superadmin_staff_jwt>" \
-H "Content-Type: application/json" \
-d '{
"method": "GET",
"path": "/api/psp/v1/merchants/by-nmid/ID102326873XXXX",
"body": ""
}'
2. Payment Events (/psp/v1/events, /psp/v1/settlements)
Backend sign menggunakan PSP_TESTER_PAYMENT_KEY_ID dari env, lalu forward ke payment_service:8085. Menggunakan X-PSP-Key-ID / X-PSP-Timestamp / X-PSP-Signature.
Contoh kirim event transaksi sukses:
curl -X POST https://api-merchant.kesles.com/api/integration/v1/psp/execute \
-H "Authorization: Bearer <superadmin_staff_jwt>" \
-H "Content-Type: application/json" \
-d '{
"method": "POST",
"path": "/psp/v1/events",
"body": "{\"external_event_id\":\"test-evt-001\",\"event_type\":\"transaction.success\",\"nmid\":\"ID102326873XXXX\",\"gross_amount\":50000,\"status\":\"success\"}"
}'
3. Static Key (/internal/transactions/*, /internal/notifications/*)
Tidak menggunakan HMAC. Request diteruskan ke merchant_core_api dengan header X-Internal-API-Key dari env. Digunakan untuk test push notification, transaction status update, dan test-psp-event.
Merchant & Outlet Picker (Multi-Outlet)
Panel PSP Tester punya picker merchant untuk mengisi field NMID / MID / TID otomatis. Sejak platform multi-outlet, NMID/MID berbeda per outlet (merchant.merchant_outlets.nmid/mid), sedangkan TID per terminal/device (payment_terminal_inventory.tid, kini di inventory_service). Karena itu picker bertahap:
- Pilih merchant → set
merchant_code+ ambil identifier merchant-level (fallback). - Pilih outlet (Default / Cabang) → isi
NMID/MIDdari outlet terpilih.
Perilaku:
- 1 outlet → NMID/MID otomatis dari outlet itu (tanpa dialog).
- >1 outlet → muncul dialog Pilih Outlet (label + NMID per outlet; outlet
is_defaultdiberi badge Default) → pilih → field terisi. - Outlet tanpa NMID ditandai
NMID: — (belum di-assign). - Gagal fetch / outlet kosong → fallback ke NMID/MID merchant-level (perilaku lama).
- TID tetap dari merchant-level/manual (per-device — di luar scope picker outlet).
GET /api/integration/v1/merchants/{id}/outlets
Mengembalikan daftar outlet satu merchant dengan nmid/mid per-outlet untuk picker. requireSuperAdmin; proxy ke merchant_core_api GET /internal/merchants/{id}/outlets (header X-Internal-API-Key).
curl -X GET https://api-merchant.kesles.com/api/integration/v1/merchants/<merchant_uuid>/outlets \
-H "Authorization: Bearer <superadmin_staff_jwt>"
Response:
{
"outlets": [
{
"outlet_name": "KESLES SANDBOX",
"is_default": true,
"nmid": "ID0000000000001",
"mid": "000000000000001"
},
{
"outlet_name": "Cabang Pembantu",
"is_default": false,
"nmid": "",
"mid": ""
}
],
"count": 2
}
Resolver PSP nyata (/api/psp/v1/merchants/by-nmid) sudah punya jalur Tier-2 yang resolve via merchant_outlets.nmid, di balik feature flag MultiOutletPSPLookup (internal_psp_lookup_handlers.go). Picker di tester hanya mengisi field; aktifkan flag bila ingin resolver produksi membaca NMID per-outlet.
Push Warning Test (Merchant Inactive)
POST /internal/notifications/push/test-psp-event
Auth: X-Internal-API-Key (static key forward ke merchant_core_api)
Kirim test FCM push ke merchant, mensimulasikan notifikasi dari event PSP (mis. merchant inactive warning).
Request body:
{
"payload": {
"merchant_code": "MRC-...",
"mid": "",
"nmid": "",
"status": "inactive",
"title": "Peringatan Merchant",
"body": "Status merchant Anda sedang tidak aktif",
"amount": 0
}
}
Salah satu dari merchant_code, mid, atau nmid wajib diisi untuk resolve merchant.
Contoh via tester panel: pilih path /internal/notifications/push/test-psp-event, method POST, isi body JSON di atas dengan merchant_code merchant sandbox.
Auth Header Comparison
| Test target | Key ID header | Timestamp header | Signature header | signed_payload order |
|---|---|---|---|---|
Lookup API (/api/psp/v1/merchants/*) | X-API-Key-ID | X-Timestamp | X-Signature | timestamp\nmethod\npath\nbody |
Events API (/psp/v1/events) | X-PSP-Key-ID | X-PSP-Timestamp | X-PSP-Signature | method\npath\ntimestamp\nbody |
X-PSP-Timestamp menggunakan format RFC3339, contoh: 2026-06-08T10:30:00+07:00.
Access Gate
| Endpoint | Gate |
|---|---|
GET /api/integration/v1/psp/keys | requireSuperAdmin (Bearer JWT staff role superAdmin) |
POST /api/integration/v1/psp/execute | requireSuperAdmin (Bearer JWT staff role superAdmin) |
Akses dibatasi oleh requireSuperAdmin middleware: hanya JWT staff dengan role superAdmin (atau UUID di SUPER_ADMIN_USER_IDS) yang lolos; selain itu dikembalikan 403. Tidak ada APP_ENV guard terpisah — gating murni berbasis role.
Related
- Events API — full event schema reference
- Integration Contract — HMAC spec, payload format
- Dashboard Admin API
- Incident Response — when needing to simulate a production incident in staging