QRISX Developer Quickstart

Integrasikan QRISX tanpa menebak flow pembayaran.

Halaman ini merangkum kontrak resmi QRISX untuk create payment, Dynamic QRIS, status pembayaran, signed webhook, idempotency, polling fallback, dan fulfillment yang aman.

1. Quickstart

Minimum safe flow

QRISX memiliki payment state. Aplikasi Anda tetap memiliki order, produk, subscription, entitlement, booking, atau business rule lainnya.

Local Order/Invoice
  -> Create QRISX Payment
  -> Display QRIS
  -> Customer Pays
  -> Verify payment.paid webhook
  -> Mark local invoice paid exactly once
  -> Fulfil local business action exactly once
01

Buat invoice lokal dulu

Gunakan reference immutable milik aplikasi, misalnya ORDER-10021.

02

Create QRISX payment

Gunakan reference tersebut sebagai external_id dan Idempotency-Key untuk payment attempt pertama.

03

Customer bayar

Tampilkan QRIS dari backend Anda. Customer harus membayar payable_amount.

04

PAID → fulfillment

Verifikasi webhook, dedupe event, lalu jalankan fulfillment aplikasi tepat satu kali.

2. Credentials

API key dan webhook secret hanya untuk server.

Buat API key dan webhook endpoint dari QRISX admin. Jangan menaruh API key atau webhook secret di browser JavaScript, public HTML, logs, analytics, screenshot, chat, atau source control.

QRISX_BASE_URL=https://qrisx.cepat.digital
QRISX_API_KEY=<secret>
QRISX_WEBHOOK_SECRET=<secret>

3. Payment API

Base URL: https://qrisx.cepat.digital

Semua endpoint /api/v1/* membutuhkan Bearer API key server-side dan production harus menggunakan HTTPS.

Create payment

Idempotency-Key wajib. Retry jaringan harus memakai key dan body yang sama agar tidak membuat payment intent duplikat.

curl --request POST 'https://qrisx.cepat.digital/api/v1/payments' \
  --header 'Authorization: Bearer <QRISX_API_KEY>' \
  --header 'Idempotency-Key: ORDER-10021' \
  --header 'Content-Type: application/json' \
  --data '{
    "amount": 99000,
    "external_id": "ORDER-10021",
    "expires_in_seconds": 300
  }'

Payment object

Timestamp memakai Unix milliseconds. Customer membayar payable_amount, bukan base_amount.

{
  "id": "payment-id",
  "external_id": "ORDER-1001",
  "status": "PENDING",
  "base_amount": 100000,
  "unique_code": 123,
  "payable_amount": 100123,
  "qris_string": "...",
  "qris_png_url": "/api/v1/payments/payment-id/qris.png",
  "expires_at": 1700000300000,
  "paid_at": null,
  "created_at": 1700000000000,
  "updated_at": 1700000000000
}
MethodEndpointFungsi
POST/api/v1/paymentsCreate payment
GET/api/v1/payments/{payment_id}Get payment / polling fallback
GET/api/v1/paymentsList latest payments, bounded to 100 records
GET/api/v1/payments/{payment_id}/qris.pngAuthenticated QRIS PNG
POST/api/v1/payments/{payment_id}/cancelCancel payment yang masih PENDING
Status
PENDING
Fulfillment allowed
PAID
Status
EXPIRED
Status
CANCELLED
QR display contract

Jangan expose API key untuk mengambil PNG dari browser.

Endpoint qris.png diautentikasi. Ambil bytes PNG dari backend aplikasi Anda dan proxy melalui route milik aplikasi, atau render qris_string dengan trusted server-side QR renderer. Customer harus membayar payable_amount.

4. Signed Webhook

Verifikasi signature sebelum percaya payload.

QRISX mengirim final payment-state events ke HTTPS webhook endpoint yang aktif: payment.paid, payment.expired, dan payment.cancelled.

Delivery headers

Content-Type: application/json
User-Agent: QRISX/1
X-QRISX-Event: payment.paid
X-QRISX-Delivery-ID: <delivery-id>
X-QRISX-Timestamp: <unix-seconds>
X-QRISX-Signature: v1=<hex-hmac-sha256>

Payload

{
  "id": "event-id",
  "event": "payment.paid",
  "created_at": 1700000000000,
  "data": {
    "payment": {
      "id": "payment-id",
      "external_id": "ORDER-1001",
      "status": "PAID",
      "base_amount": 100000,
      "unique_code": 123,
      "payable_amount": 100123,
      "expires_at": 1700000300000,
      "paid_at": 1700000100000,
      "created_at": 1700000000000,
      "updated_at": 1700000100000
    }
  }
}

Signature algorithm

QRISX menandatangani byte sequence berikut menggunakan HMAC-SHA256 dengan webhook secret:

<X-QRISX-Timestamp>.<raw-request-body>

Header dikirim dalam format X-QRISX-Signature: v1=<lowercase-hex-digest>.

  1. 1. Baca raw HTTP body sebelum JSON di-serialize ulang.
  2. 2. Baca X-QRISX-Timestamp dan X-QRISX-Signature.
  3. 3. Tolak timestamp malformed atau stale untuk mengurangi replay risk.
  4. 4. Hitung expected HMAC-SHA256.
  5. 5. Bandingkan signature dengan constant-time comparison.
  6. 6. Baru parse dan percaya event.
PHP verification
$valid = Qrisx\WebhookVerifier::verify(
    rawBody: $request->getContent(),
    timestamp: (string) $request->header('X-QRISX-Timestamp'),
    signature: (string) $request->header('X-QRISX-Signature'),
    secret: config('services.qrisx.webhook_secret'),
);

At-least-once delivery

Webhook valid dapat datang lebih dari sekali. Dedupe menggunakan payload event id, X-QRISX-Delivery-ID, atau keduanya.

UNIQUE(provider, event_id)

Business fulfillment juga harus idempotent agar duplicate payment.paid tidak memberi akses, subscription, atau fulfillment dua kali.

Retry behavior

HTTP 2xx dianggap sukses. Non-2xx atau network failure diretry dengan bounded delays: 1s → 5s → 15s → 60s. Maksimal 5 attempts. Return 2xx hanya setelah event diterima secara durable.

5. Business state

Hanya PAID yang boleh mengaktifkan fulfillment.

Browser redirect, refresh, success page, atau callback client-side bukan bukti pembayaran. QRISX membuktikan payment state; aplikasi Anda memutuskan apa yang dibuka setelah pembayaran.

  1. 1. Deduplicate event.
  2. 2. Cari local invoice lewat external_id dan/atau stored QRISX payment id.
  3. 3. Verifikasi payment memang milik invoice tersebut.
  4. 4. Verifikasi expected amount dan business rule.
  5. 5. Mark local invoice paid secara idempotent.
  6. 6. Jalankan fulfillment secara idempotent.
  7. 7. Return 2xx.
Polling fallback

Jika webhook terlambat, backend consumer boleh mengecek status payment. Polling adalah fallback, bukan pengganti signed webhook.

GET /api/v1/payments/{payment_id}

6. PHP / Laravel

SDK itu convenience, bukan lock-in.

Kontrak QRISX adalah HTTPS + JSON + HMAC. Backend language lain tetap dapat integrasi langsung selama mengikuti authentication, idempotency, signature verification, dan fulfillment rules yang sama.

✓ Lightweight PHP SDK
✓ Plain PHP reference implementation
✓ Laravel reference implementation
✓ OpenAPI specification tersedia di repository QRISX
$payment = $qrisx->createPayment(
    amount: 99000,
    idempotencyKey: $invoice->reference,
    externalId: $invoice->reference,
    expiresInSeconds: 300,
);

// In your own authenticated backend route:
$png = $qrisx->getQrisPng($payment['id']);

7. Production checklist

Sebelum integrasi dianggap production-ready.

✓ HTTPS aktif untuk API dan webhook.
✓ API key hanya server-side.
✓ Webhook secret hanya server-side.
✓ Signature diverifikasi terhadap raw body.
✓ Timestamp replay tolerance diterapkan.
✓ Event / delivery deduplication tersedia.
✓ Local payment fulfillment idempotent.
✓ Network retry memakai idempotency key dan body yang sama.
✓ Browser return tidak dianggap payment confirmation.
✓ Customer melihat dan membayar payable_amount.

Integration boundary

QRISX membuktikan pembayaran. Aplikasi Anda menentukan apa yang dibuka.

Product, plan, subscription, course, download, booking, shipping, license, membership, dan business rules tetap berada di consumer application.

QRISX — Developer Documentation
Public docs diringkas dari kontrak QRISX v1 di repository.