API Reference
Pengenalan
YR Gateway adalah gateway payment QRIS GoPay. API merchant memungkinkan integrasi langsung untuk membuat payment, memantau status, dan menerima notifikasi webhook saat pembayaran diterima.
https://gate.yrstand.com/api/v1Semua endpoint merchant relatif terhadap base URL di atas. Endpoint publik checkout tidak memerlukan autentikasi.
Autentikasi
API merchant menggunakan API key dengan skema Bearer. Semua request ke /api/v1/bot/* wajib menyertakan header:
Authorization: Bearer gk_live_example123API key bersifat per-integration. Jangan simpan API key di client-side atau bagikan ke pihak ketiga. Gunakan variabel environment di server backend.
Alur Pembayaran
Satu payment melewati empat langkah. Anda hanya perlu memanggil langkah pertama — sisanya berjalan sendiri.
- Buat payment. POST
/bot/paymentsdenganamount,order_id, dan metadata opsional. - Tampilkan QRIS ke pelanggan. Pakai
checkout_urldari response — halaman pembayaran siap pakai yang bisa dikirim lewat WhatsApp, email, atau chat, dan status di halaman itu ter-update sendiri. Atau renderqrisjadi QR code di aplikasi Anda sendiri. - Pelanggan scan dan bayar. GoPay adalah satu-satunya rail pembayaran aktif. QR berasal dari charge GoPay per invoice. Pelanggan membayar
amountpersis; fieldproviderselalu bernilaigopaydanexpected_amountsama denganamount. - Terima webhook. Saat pembayaran diterima, event
payment.paiddikirim ke endpoint aktif integration pemilik API key. Atur endpoint dan secret pada integration sebelum membuat payment. Ini sumber kebenaran untuk memenuhi order — jangan andalkan polling.
Polling GET /bot/payments/:id tetap tersedia sebagai cadangan jika endpoint webhook Anda sedang bermasalah. Untuk checkout publik, poll setiap 15 detik; expired atau cancelled dengan may_settle_late=true masih provisional selama grace GoPay 15 menit dan berhenti saat flag false atau status paid. State paid irreversible; expired dan cancelled hanya final saat may_settle_late=false.
Buat Payment
/api/v1/bot/paymentsAPI keyMembuat payment GoPay baru dengan QRIS per invoice.
| Parameter | Tipe | Wajib | Deskripsi |
|---|---|---|---|
amount | integer | wajib | Nominal dalam rupiah. Rp100 sampai Rp10.000.000. |
order_id | string | wajib | ID order milik Anda. Berfungsi sebagai idempotency key per integration: request ulang dengan body sama mengembalikan payment yang sama. Pemetaan order_id ke payment ini permanen, tidak dibatasi waktu: order_id yang sudah pernah dipakai akan selalu mengembalikan payment yang sama, termasuk setelah payment itu mencapai state final (paid, expired, atau cancelled). Untuk percobaan pembayaran baru setelah percobaan sebelumnya berakhir di state final, pakai order_id baru. |
metadata | object | opsional | Pasangan key-value string. Maksimum 32 key; key maksimum 64 byte, value 512 byte, total JSON 8 KiB. Urutan key tidak memengaruhi idempotency. Dikembalikan apa adanya di detail payment. |
- Berhasil mengembalikan 201 Created. Untuk order_id baru, state selalu pending. Untuk order_id yang sudah pernah dipakai pada integration ini, 201 yang sama mengembalikan payment yang sudah ada apa adanya, dengan state apa pun: pending, paid, expired, atau cancelled. Jangan asumsikan pending hanya dari status code 201; selalu periksa field state.
- Rail aktif hanya GoPay. Response selalu menyertakan provider: gopay.
- Pelanggan membayar amount persis. suffix selalu 0 dan expected_amount = amount; offset kode unik tidak pernah dimunculkan ke merchant.
- Simpan id dari response — itu yang dikirim sebagai payment_id di webhook.
- Atur endpoint dan secret webhook pada integration pemilik API key bila memerlukan notifikasi payment.paid.
curl -X POST https://gate.yrstand.com/api/v1/bot/payments \
-H "Authorization: Bearer gk_live_example123" \
-H "Content-Type: application/json" \
-d '{
"amount": 10000,
"order_id": "ORDER-20260804-001",
"metadata": {"customer_id": "C-1"}
}'{
"id": "pay_example_001",
"order_id": "ORDER-20260804-001",
"amount": 10000,
"provider": "gopay",
"suffix": 0,
"expected_amount": 10000,
"state": "pending",
"qris": "000201010212...",
"charge_order_id": "chg_example_001",
"expires_at": "2026-08-04T12:15:00Z",
"created_at": "2026-08-04T12:00:00Z",
"checkout_url": "https://gate.yrstand.com/pay?token=checkout_example"
}Daftar Payment
/api/v1/bot/paymentsAPI keyMengambil daftar payment milik integration.
- Query parameter limit (default 50, maksimum 200).
- Query parameter offset (default 0).
- Response berisi array payments dan total count.
curl "https://gate.yrstand.com/api/v1/bot/payments?limit=50&offset=0" \
-H "Authorization: Bearer gk_live_example123"{
"payments": [
{
"id": "pay_example_001",
"order_id": "ORDER-20260804-001",
"amount": 10000,
"provider": "gopay",
"suffix": 0,
"expected_amount": 10000,
"state": "pending",
"qris": "000201010212...",
"charge_order_id": "chg_example_001",
"expires_at": "2026-08-04T12:15:00Z",
"created_at": "2026-08-04T12:00:00Z",
"checkout_url": "https://gate.yrstand.com/pay?token=checkout_example"
}
],
"total": 1
}Detail Payment
/api/v1/bot/payments/:idAPI keyMengambil detail satu payment berdasarkan ID.
- Mengembalikan seluruh field Payment.
- Hanya payment milik tenant yang dapat diakses.
- paid_at dan metadata hanya muncul jika ada (omitempty).
curl "https://gate.yrstand.com/api/v1/bot/payments/pay_example_001" \
-H "Authorization: Bearer gk_live_example123"{
"id": "pay_example_001",
"order_id": "ORDER-20260804-001",
"amount": 10000,
"provider": "gopay",
"suffix": 0,
"expected_amount": 10000,
"state": "paid",
"qris": "000201010212...",
"charge_order_id": "chg_example_001",
"expires_at": "2026-08-04T12:15:00Z",
"created_at": "2026-08-04T12:00:00Z",
"paid_at": "2026-08-04T12:05:00Z",
"checkout_url": "https://gate.yrstand.com/pay?token=checkout_example",
"metadata": {"customer_id": "C-1"}
}Batalkan Payment
/api/v1/bot/payments/:id/cancelAPI keyMembatalkan payment yang masih pending.
- Mengembalikan 204 No Content jika berhasil (tanpa body).
- Payment hanya dapat dibatalkan jika state masih pending.
- Jika sudah paid atau kedaluwarsa, mengembalikan invalid_state.
curl -X POST "https://gate.yrstand.com/api/v1/bot/payments/pay_example_001/cancel" \
-H "Authorization: Bearer gk_live_example123"Checkout Publik
/api/v1/checkout/:tokenPublicMengambil status checkout publik untuk buyer.
- Endpoint publik, tidak memerlukan API key.
- token didapat dari field checkout_url payment.
- Response selalu menyertakan provider: gopay. Halaman checkout menampilkan expected_amount yang selalu sama dengan amount.
- Gunakan untuk polling status checkout oleh halaman pembayaran pelanggan setiap 15 detik. State paid irreversible. State expired/cancelled hanya final bila may_settle_late=false; bila true, keduanya provisional sampai paid atau flag false. may_settle_late=true hanya berlaku untuk charge GoPay dengan charge_order_id non-kosong selama grace 15 menit setelah expires_at. Batas 15 menit inklusif pada Unix integer seconds.
curl "https://gate.yrstand.com/api/v1/checkout/checkout_example"{
"merchant_name": "Toko Contoh",
"amount": 10000,
"provider": "gopay",
"expected_amount": 10000,
"qris": "000201010212...",
"expires_at": "2026-08-04T12:15:00Z",
"state": "pending",
"may_settle_late": false
}Webhook
Webhook endpoint integrationPublicNotifikasi payment.paid dikirim ke endpoint aktif integration.
- Endpoint ini outbound: YR Gateway mengirim ke endpoint HTTPS aktif pada integration pemilik API key, bukan endpoint yang dipanggil merchant. Atur endpoint dan webhook_secret melalui dashboard/API integration. Penerima wajib memverifikasi signature HMAC (lihat catatan X-Signature) sebelum memproses event.
- Payment tetap settle tanpa endpoint integration, tetapi tidak membuat delivery webhook.
- Satu-satunya event yang dikirim: payment.paid.
- X-Event-ID: ID delivery dari outbox gateway — bukan identitas settlement. Ia dapat berbeda dari event_id di body, yang berisi identitas settlement GoPay. Nilai header sama untuk semua percobaan ulang event yang sama; de-duplikasi wajib memakai header ini.
- X-Timestamp: Unix timestamp (detik) saat pengiriman.
- X-Signature: HMAC-SHA256(secret, timestamp + "." + raw_body) dalam hex.
- X-Attempt: nomor percobaan pengiriman (mulai dari 1).
- Body memuat payment_id, amount (nominal invoice), dan event_id (identitas settlement GoPay).
- Tolak timestamp stale (mis. selisih >5 menit) untuk mencegah replay.
- Kembalikan 2xx hanya setelah pemrosesan tahan lama (durable).
- Retry: delivery gagal diulang setelah 1, 2, 4, 8, dan 16 menit. Setelah percobaan kelima, delivery menjadi dead letter.
curl -X POST https://merchant.example/webhooks/yr-gateway \
-H "Content-Type: application/json" \
-H "X-Event-ID: evt_delivery_001" \
-H "X-Timestamp: 1722771600" \
-H "X-Signature: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08" \
-H "X-Attempt: 1" \
-d '{"payment_id":"pay_example_001","amount":10000,"event_id":"chg_example_001"}'{
"payment_id": "pay_example_001",
"amount": 10000,
"event_id": "chg_example_001"
}import crypto from "node:crypto";
const secret = process.env.YR_WEBHOOK_SECRET; // whsec_...
const timestamp = request.headers["x-timestamp"];
const signature = request.headers["x-signature"];
const rawBody = request.rawBody; // raw bytes, bukan parsed JSON
// 1. Tolak timestamp stale untuk mencegah replay
const now = Math.floor(Date.now() / 1000);
const ts = Number(timestamp);
if (!Number.isFinite(ts) || Math.abs(now - ts) > 300) {
return response.status(401).end();
}
// 2. Verifikasi signature HMAC-SHA256
const expected = crypto.createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
// Validasi bentuk dulu: signature yang hilang atau bukan hex 64-karakter
// membuat Buffer.from(signature, "hex") diam-diam menghasilkan buffer
// pendek, dan timingSafeEqual melempar RangeError untuk panjang yang tidak
// cocok -- exception itu jadi 500 di webhook, bukan penolakan 401.
const validFormat = typeof signature === "string" && /^[0-9a-f]{64}$/.test(signature);
const valid = validFormat && crypto.timingSafeEqual(
Buffer.from(signature, "hex"),
Buffer.from(expected, "hex"),
);
if (!valid) {
return response.status(401).end();
}
// 3. De-duplikasi berdasarkan X-Event-ID
const eventId = request.headers["x-event-id"];
if (await alreadyProcessed(eventId)) {
return response.status(200).end();
}
const event = JSON.parse(rawBody);
// event: { payment_id, amount, event_id }
// 4. Proses secara durable, lalu kembalikan 2xx
await processPayment(event);
response.status(200).end();Status Payment
Field state pada setiap payment bisa berisi salah satu dari nilai berikut. State paid irreversible. State expired dan cancelled hanya final saat may_settle_late=false.
| State | Terminal | Arti |
|---|---|---|
charging | tidak | Payment sudah dipesan dan charge GoPay sedang dibuat. Sementara, biasanya di bawah satu detik. |
pending | tidak | QRIS sudah terbit dan menunggu dibayar. Ini state saat POST /bot/payments berhasil. |
paid | ya | Pembayaran diterima. State ini irreversible; webhook payment.paid dikirim saat transisi ke state ini. |
expired | saat may_settle_late=false | Lewat expires_at. Hanya final saat may_settle_late=false. Jika true, status provisional dan perlu dipoll setiap 15 detik sampai false atau paid; flag ini hanya untuk charge GoPay dengan charge_order_id non-kosong dalam grace 15 menit. |
cancelled | saat may_settle_late=false | Dibatalkan lewat POST /bot/payments/:id/cancel. Hanya final saat may_settle_late=false. Jika true, status provisional dan perlu dipoll setiap 15 detik sampai false atau paid; flag ini hanya untuk charge GoPay dengan charge_order_id non-kosong dalam grace 15 menit. |
failed | tidak | Charge GoPay gagal dibuat. Request ulang dengan order_id yang sama akan mencoba lagi. |
Perlakukan state selain paid sebagai belum dibayar. Jangan memenuhi order berdasarkan pending saja.
Error
Semua error menggunakan envelope yang konsisten. Field request_id dapat digunakan untuk tracing.
{"error":{"code":"...","message":"...","request_id":"..."}}| Code | HTTP Status | Deskripsi |
|---|---|---|
unauthorized | 401 | API key hilang atau tidak valid. |
forbidden | 403 | Integration tidak memiliki scope yang diperlukan. |
invalid_request | 400 | Body request tidak valid atau tidak dapat diparse. |
invalid_amount | 400 | Amount di bawah minimum Rp100 atau di atas maksimum Rp10.000.000. |
invalid_metadata | 400 | Metadata melebihi batas: maksimum 32 key, key maksimum 64 byte, value maksimum 512 byte, total JSON maksimum 8 KiB. |
idempotency_conflict | 409 | order_id sudah digunakan dengan request body berbeda. |
charge_in_progress | 409 | Request lain untuk order_id yang sama sedang membuat charge. Coba lagi sesaat lagi. |
charge_amount_conflict | 409 | Charge untuk amount ini sedang diproses oleh request lain. Coba lagi. |
unique_amount_exhausted | 409 | Semua slot unique amount untuk amount ini sedang dipakai. Coba lagi sesaat lagi atau pakai amount lain. |
tenant_not_active | 403 | Akun tenant tidak aktif. |
gobiz_not_connected | 400 | Koneksi GoBiz belum tersedia untuk tenant. |
insufficient_balance | 402 | Saldo tidak cukup untuk fee reservation. |
invalid_state | 409 | Payment tidak dapat dibatalkan (sudah paid atau kedaluwarsa). |
gobiz_reconnect_required | 503 | Sesi GoBiz tenant kedaluwarsa, perlu reconnect dari dashboard. |
rate_limited | 429 | Rate limit GoBiz tercapai, coba lagi sesaat lagi. |
too_many_requests | 429 | Rate limit gateway ini sendiri tercapai (bukan GoBiz) -- per tenant atau gabungan seluruh tenant. Coba lagi sesaat lagi; kalau sering terjadi, minta operator gateway menaikkan batasnya. |
charge_failed | 503 | Gagal membuat charge pembayaran di GoBiz, coba lagi sesaat lagi. |
not_found | 404 | Resource tidak ditemukan atau bukan milik tenant. |
internal_error | 500 | Kesalahan internal server. Coba lagi dengan idempotency key yang sama. |