Pusat Integrasi
Dokumentasi API
Panduan integrasi QRIS PAYED untuk membuat, memeriksa, membatalkan tagihan, dan memverifikasi webhook pembayaran.
Mulai dalam empat langkah
- 01
Buat akun merchant
Daftar, masuk, lalu lengkapi pengaturan merchant di dashboard.
- 02
Simpan kredensial
Gunakan API Key untuk request dan Webhook Secret untuk memverifikasi notifikasi.
- 03
Buat tagihan QRIS
Kirim order unik melalui endpoint pembuatan pembayaran. Integrator dapat menampilkan QRIS sendiri dari respons API.
- 04
Konfirmasi lifecycle
Terima webhook setiap perubahan PENDING, SUCCESS, EXPIRED, atau CANCELED; gunakan pemeriksaan status sebagai verifikasi cadangan.
Dasar integrasi
Base URL
https://payment.edsaed.eu.org/api/v1Format data
Kirim body sebagai JSON dan gunakan respons JSON. Semua nominal memakai bilangan bulat Rupiah, bukan desimal.
Autentikasi
Endpoint API menggunakan header Bearer. Untuk integrasi server-to-server, sertakan API Key merchant pada setiap request.
Authorization: Bearer API_KEY
Content-Type: application/json
Endpoint pembuatan pembayaran memiliki perlindungan lonjakan trafik sebesar maksimal 10 request per IP dalam jendela 60 detik. Tangani respons HTTP 429 dengan jeda lalu coba kembali secara terukur.
/payment/create
Membuat tagihan QRIS baru. Jika order ID yang sama masih berstatus PENDING, PAYED mengembalikan tagihan aktif tersebut, sehingga aplikasi Anda tidak menggandakan tagihan saat request diulang.
order_id
Wajib. Maksimal 64 karakter; gunakan huruf, angka, tanda hubung, atau garis bawah.
amount
Wajib. Bilangan bulat aman minimal Rp1 sebagai nominal dasar tagihan.
description
Opsional. Teks keterangan maksimal 255 karakter; default-nya Pembayaran.
Contoh request
curl -X POST https://payment.edsaed.eu.org/api/v1/payment/create \
-H "Authorization: Bearer API_KEY" \
-H "Content-Type: application/json" \
-d '{"order_id":"INV-001","amount":50000,"description":"Pesanan INV-001"}'
Contoh respons sukses
{
"success": true,
"message": "Tagihan berhasil dibuat",
"data": {
"order_id": "INV-001",
"amount": 50037,
"original_amount": 50000,
"fee": 37,
"mdr_fee": 0,
"net_amount": 50000,
"payment_type": "QRIS",
"status": "PENDING",
"description": "Pesanan INV-001",
"qr_string": "000201...",
"qr_url": "https://api.qrserver.com/...",
"checkout_url": "https://payment.edsaed.eu.org/pay/INV-001/co_a1b2c3d4e5f678901234567890abcdef",
"created_at": 1760000000000,
"expires_at": 1760000300000
}
}
Tampilkan qr_url sebagai gambar QRIS atau gunakan qr_string sebagai data QR. Nilai amount adalah total yang harus dibayar, termasuk kode unik Rp1–Rp99; original_amount adalah nominal dasar; mdr_fee adalah estimasi MDR QRIS untuk transaksi final di atas Rp500.000; dan net_amount adalah saldo bersih yang akan dicatat untuk merchant. Tagihan aktif berlaku lima menit.
/payment/create juga memuat checkout_url dengan format /pay/{order_id}/co_…. order_id dibuat terlihat agar URL mudah dikenali, sedangkan token acak wajib tetap ada sebagai bukti akses anti-enumerasi. Anda dapat memilih menampilkan qr_url/qr_string di aplikasi sendiri atau mengarahkan pelanggan ke URL tersebut. Kedua pilihan memakai satu QRIS, order_id, nominal akhir, kode unik, masa berlaku, status, dan webhook yang sama; membuka URL tidak membuat Payment Link atau order QRIS kedua.Hosted QRIS Checkout dan Payment Link
PAYED tetap QRIS-only, bukan checkout multi-channel. Untuk order dari API, pilih presentasi QRIS langsung atau halaman ter-host dari respons yang sama.
| Cara | Cara memperoleh | Apa yang ditampilkan pelanggan |
|---|---|---|
| QRIS API langsung | Gunakan qr_url atau qr_string dari respons /api/v1/payment/create. | Aplikasi merchant menampilkan QRIS dari order yang dibuat. |
| Hosted QRIS Checkout API | Arahkan pelanggan ke checkout_url dari respons /api/v1/payment/create. | Halaman /pay/{order_id}/co_… PAYED menampilkan QRIS dari order yang sama. |
| Payment Link dashboard | Buat Payment Link dari dashboard PAYED. | Pelanggan membuka link dashboard dan memakai lifecycle Payment Link satu kali pakai yang terpisah. |
order_id untuk keterbacaan, tetapi token co_… acak tetap wajib dan harus cocok dengan order tersebut. Mengakses /pay/{order_id} tanpa token akan ditolak. Halaman publik melakukan polling aman setiap tiga detik untuk order yang sama. Payment Link dashboard tetap terpisah: saat dibuka ia membuat satu QRIS PENDING sendiri, memakai QRIS aktif yang sama di perangkat lain, lalu terkunci setelah SUCCESS. Payment Link tidak menawarkan Virtual Account atau e-wallet lain./orders/check
Mengambil status terkini sebuah order milik merchant yang diautentikasi. Endpoint ini juga memperbarui order yang melewati masa berlaku menjadi EXPIRED.
curl -X POST https://payment.edsaed.eu.org/api/v1/orders/check \
-H "Authorization: Bearer API_KEY" \
-H "Content-Type: application/json" \
-d '{"order_id":"INV-001"}'
Respons sukses berbentuk {"success":true,"data":{...}} dan memuat kontrak transaksi terbaru tanpa API Key, delivery log webhook, atau diagnosis provider. Endpoint cek status juga memperbarui status menjadi EXPIRED bila masa berlaku sudah lewat.
/orders/check setiap tiga detik selama status masih PENDING dan tetap menangani webhook. Tanpa halaman/polling aktif, PAYED baru dapat menemukan pembayaran pada pemeriksaan terjadwal berikutnya; waktu tersebut mengikuti jadwal Cloudflare dan ketersediaan data dari penyedia, sehingga tidak dapat dijanjikan real-time.Endpoint webhook integrator harus segera memverifikasi signature, menyimpan payload secara idempoten berdasarkan order_id, lalu membalas HTTP 2xx. Pekerjaan berat seperti pengiriman pesan, update stok, atau query eksternal sebaiknya diteruskan ke antrean internal integrator setelah respons 2xx agar delivery tidak menunggu proses tersebut.
/payment/cancel
Membatalkan tagihan milik merchant yang masih berstatus PENDING. Order yang telah sukses, kedaluwarsa, atau sudah dibatalkan tidak dapat diubah lagi. Pada dashboard, tombol Batalkan tersedia pada baris PENDING dan status akhir akan terlihat sebagai CANCELED.
curl -X POST https://payment.edsaed.eu.org/api/v1/payment/cancel \
-H "Authorization: Bearer API_KEY" \
-H "Content-Type: application/json" \
-d '{"order_id":"INV-001"}'
Status pembayaran
PENDINGTagihan aktif dan menunggu pembayaran.
SUCCESSPembayaran terdeteksi dan transaksi selesai.
EXPIREDMasa berlaku lima menit telah berakhir tanpa pembayaran.
CANCELEDTagihan dibatalkan merchant saat masih menunggu pembayaran.
Webhook lifecycle pembayaran
PAYED mengirim satu HTTP POST untuk setiap transisi lifecycle yang berhasil: payment.pending, payment.success, payment.expired, dan payment.canceled. Callback memakai header signature HMAC-SHA256. Hanya payment.success yang menandakan settlement selesai dan boleh mengkreditkan saldo atau menuntaskan pesanan pada sistem integrator.
Callback tidak menunggu respons QRIS, tetapi baru dibuat setelah PAYED mendeteksi transaksi dari penyedia. Karena itu webhook dan polling bukan pengganti satu sama lain: gunakan webhook untuk notifikasi perubahan status, dan polling /orders/check tiga detik selama PENDING untuk UX cepat serta verifikasi cadangan. PAYED menganggap semua respons HTTP 2xx sebagai delivery berhasil.
Webhook Logs tetap menyimpan satu baris per order tanpa kolom Event. Baris itu selalu diperbarui ke status lifecycle terakhir beserta hasil delivery terakhir, sehingga PENDING lalu SUCCESS tidak tampil sebagai dua log terpisah.
Content-Type: application/json
User-Agent: PAYED-Webhook/1.0
x-payed-signature: HMAC_SHA256_HEX
{
"event": "payment.success",
"data": {
"order_id": "INV-001",
"amount": 50037,
"original_amount": 50000,
"fee": 37,
"mdr_fee": 0,
"net_amount": 50000,
"status": "SUCCESS",
"paid_at": 1760000123456
}
}
Verifikasi signature
Gunakan Webhook Secret sebagai kunci HMAC-SHA256 dan hitung signature dari raw body yang diterima, bukan JSON yang telah diparsing lalu dibentuk ulang. Bandingkan hasil heksadesimal dengan header x-payed-signature sebelum memproses order.
const rawBody = await request.text();
const key = await crypto.subtle.importKey(
'raw', new TextEncoder().encode(WEBHOOK_SECRET),
{ name: 'HMAC', hash: 'SHA-256' }, false, ['sign']
);
const digest = await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(rawBody));
const expected = Array.from(new Uint8Array(digest), b => b.toString(16).padStart(2, '0')).join('');
const valid = expected === request.headers.get('x-payed-signature');
Balas dengan HTTP 2xx setelah event diproses. Simpan pasangan order_id dan status terakhir agar proses bisnis idempoten bila callback diterima ulang. Jangan mengkreditkan saldo untuk event selain payment.success.
Test, Resend, dan hasil delivery
Tombol Test pada Settings mengirim payload dengan format yang sama, menggunakan event payment.test. Tombol Resend pada Webhook Logs hanya tersedia untuk pembayaran SUCCESS dan mengirim ulang payment.success. Webhook Logs menampilkan satu status order terbaru serta hasil delivery terakhir; hasil HTTP 2xx, termasuk 200, 201, 202, dan 204, dianggap diterima merchant.
Jika log menampilkan 4xx atau 5xx, server merchant menolak atau gagal memproses callback. Periksa log server merchant, validasi signature dari raw body, pastikan endpoint dapat diakses publik via HTTPS, lalu gunakan Resend setelah perbaikan.
Kesalahan yang perlu ditangani
VALIDATION_ERROR · 400Parameter tidak lengkap atau tidak sesuai format.
UNAUTHORIZED · 401Header Bearer tidak ada atau API Key tidak valid.
SUSPENDED · 403Akun merchant sedang ditangguhkan.
ORDER_NOT_FOUND · 404Order tidak ditemukan pada merchant yang diautentikasi.
RATE_LIMITED · 429Terlalu banyak request pembuatan pembayaran dari IP yang sama.
SYSTEM_ERROR · 500Konfigurasi QRIS sistem belum tersedia atau terjadi kegagalan internal.
{
"success": false,
"error": { "code": "VALIDATION_ERROR", "message": "Parameter tidak lengkap atau format amount salah" }
}