Memulai
- Daftar dan masuk ke dasbor.
- Buat proyek — slug proyek dipakai di URL halaman bayar.
- Isi rekening/akun tujuan di tab Pengaturan proyek.
- Buat API key di tab API Keys. Simpan, karena hanya tampil sekali.
Base URL: https://payment.jhonaleystore.id/api/public/v1. Semua nominal dalam Rupiah (integer, tanpa desimal).
Autentikasi
Kirim API key di header Authorization. Jangan pernah menaruh key di kode frontend.
Authorization: Bearer lns_0123456789abcdef0123456789abcdef01234567Buat pembayaran
| Field | Tipe | Keterangan |
|---|---|---|
| amount* | integer | 1.000 – 100.000.000 |
| reference | string | ID order Anda. Unik per proyek; kirim ulang = data lama dikembalikan (idempoten) |
| description | string | Maks 255 karakter |
| customer_name | string | Maks 100 |
| customer_email | string | Email valid |
| expires_in_minutes | integer | 1 – 10, default 10 (maks. 10 menit, lalu otomatis ditutup) |
| metadata | object | Data bebas, maks 4KB, ikut dikirim di webhook |
Tagihan baru mendapat kode unik 0–100. Dua tagihan bersamaan dalam satu proyek mendapat total berbeda, termasuk saat nominal dasar berbeda tetapi rentang totalnya beririsan. Untuk nominal dasar yang sama, tersedia hingga 101 total pending; jika semuanya terpakai, pembuatan tagihan ditolak, bukan memakai total duplikat. Tagihan lama tetap memakai kode sebelumnya. Gunakan proyek terpisah hanya untuk rekening merchant terpisah; jangan kirim mutasi yang sama ke beberapa proyek.
curl -X POST https://payment.jhonaleystore.id/api/public/v1/payments \
-H "Authorization: Bearer $LUNAS_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": 75000,
"reference": "INV-2026-0042",
"description": "Paket Premium 1 bulan",
"customer_email": "budi@contoh.com",
"metadata": { "user_id": 42 }
}'const res = await fetch("https://payment.jhonaleystore.id/api/public/v1/payments", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.LUNAS_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ amount: 75000, reference: "INV-2026-0042" }),
});
const { data } = await res.json();
// arahkan pelanggan ke data.checkout_url$ch = curl_init("https://payment.jhonaleystore.id/api/public/v1/payments");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("LUNAS_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode(["amount" => 75000, "reference" => "INV-2026-0042"]),
]);
$data = json_decode(curl_exec($ch), true)["data"];Cek status
curl https://payment.jhonaleystore.id/api/public/v1/payments/INV-2026-0042 \
-H "Authorization: Bearer $LUNAS_KEY"Daftar pembayaran
limit 1–100 (default 20). status: pending, paid, cancelled.
Objek pembayaran
{
"id": "6c1f2b5e-…",
"reference": "INV-2026-0042",
"status": "pending", // pending | paid | cancelled | expired
"amount": 75000,
"unique_code": 42,
"amount_total": 75042, // nominal yang harus ditransfer
"currency": "IDR",
"description": "Paket Premium 1 bulan",
"customer_name": null,
"customer_email": "budi@contoh.com",
"metadata": { "user_id": 42 },
"checkout_url": "https://payment.jhonaleystore.id/pay/tokoku/6c1f2b5e-…",
"expires_at": "2026-10-21T04:00:00Z",
"paid_at": null,
"created_at": "2026-10-20T04:00:00Z"
}Webhook
Isi Webhook URL (https) di Pengaturan proyek. Saat pembayaran ditandai lunas atau dibatalkan di dasbor, kami mengirim POST dengan event payment.paid atau payment.cancelled.
X-Lunas-Timestamp: 1760932800
X-Lunas-Signature: sha256=<hex HMAC-SHA256(webhook_secret, timestamp + "." + raw_body)>import crypto from "node:crypto";
app.post("/webhook/lunas", express.raw({ type: "*/*" }), (req, res) => {
const ts = req.header("x-lunas-timestamp");
const sig = req.header("x-lunas-signature")?.replace("sha256=", "") ?? "";
const expected = crypto
.createHmac("sha256", process.env.LUNAS_WEBHOOK_SECRET)
.update(`${ts}.${req.body}`)
.digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(ts)) < 300;
if (!fresh || !crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected)))
return res.status(401).end();
const event = JSON.parse(req.body);
if (event.event === "payment.paid") {
// tandai order event.data.reference sebagai lunas
}
res.sendStatus(200);
});Kode error
{ "error": { "code": "unauthorized", "message": "API key tidak valid atau sudah dicabut" } }- 400 invalid_json — body bukan JSON
- 401 unauthorized — API key salah / dicabut
- 404 not_found — pembayaran tidak ada di proyek ini
- 409 unique_code_exhausted — semua total 0–100 untuk nominal ini sedang dipakai
- 422 validation_error — field tidak valid
- 500 server_error — coba lagi