Memulai

  1. Daftar dan masuk ke dasbor.
  2. Buat proyek — slug proyek dipakai di URL halaman bayar.
  3. Isi rekening/akun tujuan di tab Pengaturan proyek.
  4. 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_0123456789abcdef0123456789abcdef01234567

Buat pembayaran

POST/api/public/v1/payments
FieldTipeKeterangan
amount*integer1.000 – 100.000.000
referencestringID order Anda. Unik per proyek; kirim ulang = data lama dikembalikan (idempoten)
descriptionstringMaks 255 karakter
customer_namestringMaks 100
customer_emailstringEmail valid
expires_in_minutesinteger1 – 10, default 10 (maks. 10 menit, lalu otomatis ditutup)
metadataobjectData 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
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 }
  }'
Node.js
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
PHP
$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

GET/api/public/v1/payments/{id atau reference}
curl https://payment.jhonaleystore.id/api/public/v1/payments/INV-2026-0042 \
  -H "Authorization: Bearer $LUNAS_KEY"

Daftar pembayaran

GET/api/public/v1/payments?limit=20&status=paid

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.

Header
X-Lunas-Timestamp: 1760932800
X-Lunas-Signature: sha256=<hex HMAC-SHA256(webhook_secret, timestamp + "." + raw_body)>
Verifikasi (Node.js)
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