Pusat Integrasi

Dokumentasi API

Panduan integrasi QRIS PAYED untuk membuat, memeriksa, membatalkan tagihan, dan memverifikasi webhook pembayaran.

Mulai dalam empat langkah

  1. 01

    Buat akun merchant

    Daftar, masuk, lalu lengkapi pengaturan merchant di dashboard.

  2. 02

    Simpan kredensial

    Gunakan API Key untuk request dan Webhook Secret untuk memverifikasi notifikasi.

  3. 03

    Buat tagihan QRIS

    Kirim order unik melalui endpoint pembuatan pembayaran. Integrator dapat menampilkan QRIS sendiri dari respons API.

  4. 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/v1

Format data

Kirim body sebagai JSON dan gunakan respons JSON. Semua nominal memakai bilangan bulat Rupiah, bukan desimal.

Jaga kredensial. API Key dan Webhook Secret tersedia di dashboard merchant. Jangan menaruhnya pada JavaScript browser, aplikasi publik, atau repositori kode.

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.

POST

/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.

Checkout URL dari order yang sama: setiap respons /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.

CaraCara memperolehApa yang ditampilkan pelanggan
QRIS API langsungGunakan qr_url atau qr_string dari respons /api/v1/payment/create.Aplikasi merchant menampilkan QRIS dari order yang dibuat.
Hosted QRIS Checkout APIArahkan pelanggan ke checkout_url dari respons /api/v1/payment/create.Halaman /pay/{order_id}/co_… PAYED menampilkan QRIS dari order yang sama.
Payment Link dashboardBuat Payment Link dari dashboard PAYED.Pelanggan membuka link dashboard dan memakai lifecycle Payment Link satu kali pakai yang terpisah.
Perilaku checkout API. URL kanonis menampilkan 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.
POST

/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.

Kontrak latency penting. Webhook bukan janji deteksi pembayaran dalam tiga detik. PAYED mengirim callback segera setelah pembayaran sudah terdeteksi dan settlement atomik selesai; pengiriman HTTP dilakukan di background dan memiliki batas respons lima detik pada endpoint integrator. Jika QRIS dibuka pada Hosted Checkout/Payment Link, halaman melakukan pemeriksaan status aman setiap tiga detik. Jika integrator menampilkan QRIS sendiri, integrator harus memanggil /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.

POST

/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

PENDING

Tagihan aktif dan menunggu pembayaran.

SUCCESS

Pembayaran terdeteksi dan transaksi selesai.

EXPIRED

Masa berlaku lima menit telah berakhir tanpa pembayaran.

CANCELED

Tagihan 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 · 400

Parameter tidak lengkap atau tidak sesuai format.

UNAUTHORIZED · 401

Header Bearer tidak ada atau API Key tidak valid.

SUSPENDED · 403

Akun merchant sedang ditangguhkan.

ORDER_NOT_FOUND · 404

Order tidak ditemukan pada merchant yang diautentikasi.

RATE_LIMITED · 429

Terlalu banyak request pembuatan pembayaran dari IP yang sama.

SYSTEM_ERROR · 500

Konfigurasi QRIS sistem belum tersedia atau terjadi kegagalan internal.

{
  "success": false,
  "error": { "code": "VALIDATION_ERROR", "message": "Parameter tidak lengkap atau format amount salah" }
}