casaku
Casaku
SDK TypeScript ringan untuk Casaku Payment Gateway — QRIS dinamis/statis, e-wallet monitoring, polling status, dan verifikasi webhook. Zero runtime dependency, jalan di Node.js (≥22) & Bun.
- Full TypeScript typing untuk semua request & response
- Promise-based, modern (
async/await) - Webhook HMAC-SHA256 verification (constant-time)
- Request timeout & error handling bawaan
Daftar Isi
- Instalasi
- Quick Start
- Konfigurasi
- Konsep Penting: transactionId
- API Reference
- Monitoring Real-time
- QR Image Helper
- Webhook
- Error Handling
- Alur Production yang Direkomendasikan
- Keamanan
- Tipe Data
Instalasi
npm install casaku
# atau
pnpm add casaku
# atau
bun add casakuQuick Start
import { Casaku } from "casaku";
const casaku = new Casaku({ licenseKey: "YOUR_LICENSE_KEY" });
const trx = await casaku.generateQRISv2({
qr_id: "986b2a0a-5c95-4177-9800-02d84a35338e",
amount: 10000,
packageIds: ["id.dana"],
qrType: "dynamic",
paymentMethod: "qris",
useQris: true,
useUniqueCode: true,
});
console.log("Transaction ID:", trx.data.transactionId);
console.log("Total bayar:", trx.data.totalAmount); // sudah termasuk kode unik
console.log("QR String:", trx.data.qr_string);
// Tunggu pembayaran (polling)
await casaku.waitForPayment(trx.data.transactionId);
console.log("Lunas ✅");Server-side only.
licenseKeyJANGAN dipakai di frontend (React/Vue/Next client component). Selalu di server (Node.js/Bun).
Konfigurasi
const casaku = new Casaku({
licenseKey: "YOUR_LICENSE_KEY", // wajib
baseUrl: "https://api.casaku.id", // opsional, override base URL
requestTimeout: 30_000, // opsional, ms, default 30 detik
fetch: customFetch, // opsional, custom fetch (proxy/testing)
});| Opsi | Tipe | Default | Deskripsi |
|---|---|---|---|
licenseKey |
string |
— | Wajib. License key dari dashboard Casaku. |
qrId |
string |
— | Default ID QRIS Merchant untuk wallet shortcuts. |
baseUrl |
string |
https://api.casaku.id |
Override base URL API. |
requestTimeout |
number |
30000 |
Timeout per request (ms) sebelum di-abort. |
fetch |
typeof fetch |
globalThis.fetch |
Custom fetch implementation (proxy/mock testing). |
Semua method mengembalikan Promise<CasakuResponse<T>> dengan bentuk:
{
status: number; // 200 jika sukses
message?: string;
data: T; // payload spesifik per-endpoint
}Konsep Penting: transactionId
transactionId dibuat oleh server Casaku, tidak bisa dicustom. Server mengembalikannya di response generate (format txn_xxx).
Untuk menghubungkan transaksi Casaku dengan order di sistemmu, simpan mapping sendiri di database:
const trx = await casaku.generateQRISv2({ ...params });
// orderId milikmu ↔ transactionId milik Casaku
await db.orders.update(orderId, {
casakuTxnId: trx.data.transactionId,
totalAmount: trx.data.totalAmount,
});Webhook dan checkStatus mengembalikan transactionId Casaku → lookup balik ke order-mu lewat mapping ini.
API Reference
generateQRISv1
POST /api/generate/qris — QRIS dinamis sederhana.
const res = await casaku.generateQRISv1({
id: "986b2a0a-5c95-4177-9800-02d84a35338e", // ID QRIS Merchant (UUID)
amount: 10000,
packageIds: ["id.dana"],
useUniqueCode: true, // opsional
expiredInMinutes: 15, // opsional, default 15
prefix: "CSK", // opsional, prefix ID transaksi (maks 8 char)
});Params
| Param | Tipe | Wajib | Deskripsi |
|---|---|---|---|
id |
string |
ID QRIS Merchant (UUID). | |
amount |
number |
Nominal dasar (sebelum kode unik). | |
packageIds |
string[] |
App e-wallet yang dimonitor (mis. id.dana). |
|
useUniqueCode |
boolean |
Tambah kode acak unik di belakang nominal. | |
expiredInMinutes |
number |
Kadaluwarsa (menit), default 15. | |
prefix |
string |
Prefix ID transaksi, maks 8 karakter. |
Response data (QRISv1Data)
{
qr_string: string;
transactionId: string;
originalAmount: number;
totalAmount: number; // originalAmount + uniqueNominal
uniqueNominal: number;
useUniqueCode: boolean;
packageIds: string[];
}generateQRISv2
POST /api/generate/v2/qris — versi fleksibel. Pilih tipe QR (dinamis/statis) atau mode e-wallet (transfer manual tanpa QR).
// QRIS dinamis dengan QR string
const qris = await casaku.generateQRISv2({
qr_id: "986b2a0a-5c95-4177-9800-02d84a35338e",
amount: 15000,
packageIds: ["id.dana", "com.shopee.id"],
qrType: "dynamic",
paymentMethod: "qris",
useQris: true,
useUniqueCode: true,
expiredInMinutes: 15,
});// Mode e-wallet — monitoring transfer manual (tanpa QR)
const ewallet = await casaku.generateQRISv2({
qr_id: "986b2a0a-5c95-4177-9800-02d84a35338e",
amount: 50000,
packageIds: ["id.dana"],
qrType: "dynamic",
paymentMethod: "ewallet",
useQris: false, // tidak butuh QR
useUniqueCode: true, // wajib ON agar nominal unik bisa dideteksi
});
// Tampilkan instruksi transfer manual sesuai ewallet.data.totalAmountParams
| Param | Tipe | Wajib | Deskripsi |
|---|---|---|---|
qr_id |
string |
ID QRIS Merchant (UUID). | |
amount |
number |
Nominal dasar. | |
packageIds |
string[] |
App e-wallet yang dimonitor. | |
qrType |
"dynamic" | "static" |
Tipe QR yang dihasilkan. | |
paymentMethod |
"qris" | "ewallet" |
qris (scan) atau ewallet (transfer manual). |
|
useQris |
boolean |
Sertakan qr_string di response. |
|
useUniqueCode |
boolean |
Tambah kode acak unik. | |
expiredInMinutes |
number |
Kadaluwarsa (menit), default 15. | |
prefix |
string |
Prefix ID transaksi, maks 8 karakter. |
Response data (QRISv2Data)
{
transactionId: string;
originalAmount: number;
totalAmount: number;
uniqueNominal: number;
useUniqueCode: boolean;
packageIds: string[];
expiredInMinutes: number;
qrType: "dynamic" | "static";
paymentMethod: "qris" | "ewallet";
useQris: boolean;
qr_string?: string; // hanya ada bila useQris: true
status: PaymentStatus;
}Wallet Shortcuts
Generate QRIS untuk satu e-wallet/merchant tanpa nulis semua param. Default: qrType: "dynamic", paymentMethod: "qris", useQris: true, useUniqueCode: true. qr_id diambil dari config.qrId atau opsi per-call.
const casaku = new Casaku({ licenseKey: "...", qrId: "986b2a0a-..." });
await casaku.generateQRDana(10000); // cukup amount
await casaku.generateQRGopay(25000);
// Override default
await casaku.generateQRDana(50000, { paymentMethod: "ewallet", useQris: false });
// Tanpa qrId di config → kasih per-call
await casaku.generateQRDana(10000, { qr_id: "xxxx" });Method tersedia
| Method | Wallet / Merchant | Package ID |
|---|---|---|
generateQRDana |
DANA Bisnis | id.dana |
generateQROrderKuota |
Order Kuota | com.orderkuota.app |
generateQRGopay |
GoPay Merchant | com.gojek.gopaymerchant |
generateQRBca |
BCA Merchant | com.bca.msb |
generateQRShopee |
ShopeePay | com.shopee.id |
generateQRShopeePartner |
Shopee Partner | com.shopeepay.merchant.id |
generateQRMandiri |
Livin Merchant by Mandiri | id.bmri.livinmerchant |
generateQRBri |
BRI Merchant | id.co.bri.merchant |
generateQRCimb |
OCTO Merchant | com.cimbedc |
generateQRBni |
BNI Merchant | id.co.bni.merchant |
generateQRBsi |
BYOND Merchant by BSI | id.co.bankbsi.byond |
generateQRJakOne |
JakOne Merchant | com.bankdki.jakone.merchant |
generateQRBukalapak |
Bukalapak | com.bukalapak.android |
Signature semua sama: (amount: number, options?: WalletShortcutOptions) => Promise<CasakuResponse<QRISv2Data>>.
Generic / custom package — wallet di luar daftar, atau monitor banyak app sekaligus:
import { CASAKU_PACKAGES } from "casaku";
await casaku.generateQRWallet("id.dana", 10000); // autocomplete via CASAKU_PACKAGES
await casaku.generateQRWallet("com.app.lain", 10000); // package ID bebas
// Banyak app dalam 1 transaksi → pakai generateQRISv2 langsung
await casaku.generateQRISv2({ qr_id: "...", amount: 10000, packageIds: ["id.dana", "com.shopee.id"], qrType: "dynamic", paymentMethod: "qris", useQris: true });Shortcut hanya monitor satu package. Butuh multi-wallet dalam satu transaksi → pakai
generateQRISv2denganpackageIdsarray.
checkStatus
POST /api/generate/check-status — cek status pembayaran satu kali.
const res = await casaku.checkStatus("txn_fa48f93d4a");
console.log(res.data.status); // "pending" | "paid" | "cancel" | "expired"Response data (CheckStatusData)
{
transactionId: string;
amount: number;
status: PaymentStatus;
expiredAt: string; // ISO date
}cancelPayment
POST /api/generate/cancel-status — batalkan transaksi yang masih pending (mis. user tutup halaman checkout).
await casaku.cancelPayment("txn_fa48f93d4a");Response data (CancelData)
{
transactionId: string;
status: "cancel";
canceledAt: string;
}listPayments
GET /api/generate/list — daftar log transaksi dengan filter, pencarian, paginasi, dan sorting.
const res = await casaku.listPayments({
status: "paid", // opsional
page: 1, // opsional, default 1
limit: 10, // opsional, default 5
search: "txn_", // opsional, cari nominal/ID
sort: "newest", // opsional, "newest" | "oldest"
});
console.log(res.data.total, res.data.pages, res.data.items);Params (semua opsional)
| Param | Tipe | Deskripsi |
|---|---|---|
status |
PaymentStatus |
Filter status. |
page |
number |
Halaman saat ini (default 1). |
limit |
number |
Item per halaman (default 5). |
search |
string |
Keyword nominal atau ID transaksi. |
sort |
"newest" | "oldest" |
Pengurutan waktu. |
Response data (ListData)
{
page: number;
limit: number;
total: number;
pages: number;
items: Array<{
transactionId: string;
amount: number;
status: PaymentStatus;
packageName: string;
createdAt: string;
paidAt?: string; // hanya ada bila sudah lunas
expiredAt: string;
}>;
}deletePayment
POST /api/generate/list-delete — hapus log transaksi: tunggal, massal, atau semua.
// Hapus satu
await casaku.deletePayment({ transactionId: "txn_1" });
// Hapus banyak
await casaku.deletePayment({ transactionIds: ["txn_1", "txn_2"] });
// Hapus SEMUA history (permanen, hati-hati!)
await casaku.deletePayment({ clearAll: true });Params (isi salah satu)
| Param | Tipe | Deskripsi |
|---|---|---|
transactionId |
string |
Hapus satu transaksi. |
transactionIds |
string[] |
Hapus massal. |
clearAll |
boolean |
true = hapus seluruh history. |
getProfile
GET /api/profile — data profil lisensi aktif (status VIP, kuota generate, dll). Aman: tidak pernah mengembalikan license key atau secret key.
const res = await casaku.getProfile();
console.log(res.data.storeName, res.data.vipstatus, res.data.maxgenerateqris);Response data (ProfileData)
{
id: string;
name: string;
email: string;
vipstatus: boolean;
vipexpires: string; // ISO date
maxgenerateqris: number; // kuota QRIS aktif bersamaan
createdAt: string;
storeName: string;
}Monitoring Real-time
Dua cara polling status. Untuk production, webhook lebih disarankan (lihat Webhook) karena push & realtime. Polling cocok untuk script/prototyping.
watchPayment / stopWatch
Callback-based. SDK polling checkStatus berkala; onStatusChange dipanggil tiap status berubah; otomatis berhenti saat status terminal (paid/success/cancel/expired) atau timeout.
casaku.watchPayment("txn_1", {
interval: 3000, // ms, default 5000
timeout: 600_000, // ms, default 600000 (10 menit)
onStatusChange: (status, res) => {
if (status === "paid") console.log("Lunas!", res.data.amount);
},
onError: (err) => console.error(err),
});
// Hentikan manual bila perlu
casaku.stopWatch("txn_1");waitForPayment
Promise-based — resolve saat lunas (paid/success), reject saat cancel/expired/timeout/error.
try {
const res = await casaku.waitForPayment("txn_1", { interval: 3000, timeout: 600_000 });
console.log("Lunas ✅", res.data.amount);
} catch (err) {
console.log("Gagal/timeout/expired ❌", err);
}Opsi (WatchOptions, sama untuk keduanya)
| Opsi | Tipe | Default | Deskripsi |
|---|---|---|---|
interval |
number |
5000 |
Jeda polling (ms). Tick berikut dijadwalkan setelah tick sebelumnya selesai (tak menumpuk). |
timeout |
number |
600000 |
Auto-stop setelah (ms). |
maxErrors |
number |
5 |
Stop setelah sekian error beruntun. |
onStatusChange |
function |
— | (status, response) => void. |
onError |
function |
— | (error) => void. |
QR Image Helper
Bikin URL gambar QR code dari string QRIS (pakai generator resmi Casaku).
const url = casaku.getQRImageURL({
data: trx.data.qr_string, // konten QR
size: "500x500", // "200x200" | "300x300" | "500x500", default "300x300"
style: 2, // 1 (Classic Dots) | 2 (Dotted Block) | 3 (Rounded Dot), default 1
color: "ea580c", // hex tanpa #, default "000000"
});
// <img src={url} />Webhook
Casaku mengirim notifikasi pembayaran via webhook (POST) dengan signature HMAC-SHA256 di header x-casaku-signature. SDK menyediakan verifikasi constant-time.
Webhook Secret didapat di Dashboard → API Keys (prefix
casaku_sec_). Set sebagai env var.
parseWebhook(rawBody, signature, secret)
Verify + parse payload. Throw bila signature invalid. Field string yang URL-encoded otomatis di-decode.
import express from "express";
import { parseWebhook } from "casaku";
const app = express();
// WAJIB simpan raw body untuk kalkulasi signature
app.use(express.json({ verify: (req, _res, buf) => (req.rawBody = buf) }));
app.post("/webhook", (req, res) => {
try {
const event = parseWebhook(
req.rawBody,
req.headers["x-casaku-signature"],
process.env.WEBHOOK_SECRET,
);
// event: { transactionId, amount, packageName, appName, status, paidAt }
// ... proses bisnis (lihat Alur Production)
res.json({ success: true });
} catch {
res.status(401).json({ error: "Invalid signature" });
}
});verifyWebhookSignature(rawBody, signature, secret): boolean
Versi boolean kalau mau handle parsing sendiri.
import { verifyWebhookSignature } from "casaku";
if (!verifyWebhookSignature(req.rawBody, req.headers["x-casaku-signature"], process.env.WEBHOOK_SECRET)) {
return res.status(401).json({ error: "Invalid signature" });
}Payload webhook (WebhookPayload)
{
"transactionId": "986b2a0a-5c95-4177-9800-02d84a35338e",
"amount": 10000,
"packageName": "id.dana",
"appName": "DANA",
"status": "paid",
"paidAt": "2026-06-21T18:12:54.000Z"
}Selalu pakai raw body (
Buffer/string), BUKANJSON.stringify(req.body). Re-serialize mengubah byte → signature gagal.
Testing lokal (Ngrok)
ngrok http 8080
# Salin URL HTTPS → masukkan ke Dashboard Casaku (Webhook URL) + path /webhook
# Tekan "Test Webhook" di dashboardError Handling
Semua request yang gagal (HTTP non-2xx, atau status !== 200) melempar CasakuError.
import { Casaku, CasakuError } from "casaku";
try {
await casaku.checkStatus("txn_invalid");
} catch (err) {
if (err instanceof CasakuError) {
console.error(err.message, err.status); // pesan API + HTTP status code
}
}waitForPayment juga reject dengan CasakuError: Payment cancel/Payment expired (status 200) atau Payment wait timeout (status 408). Request timeout (dari requestTimeout) melempar AbortError bawaan.
Alur Production yang Direkomendasikan
1. generate → casaku.generateQRISv2()
2. simpan → orderId ↔ transactionId + totalAmount di DB-mu
3. tampilkan → QR / instruksi transfer sesuai totalAmount (termasuk kode unik)
4. notifikasi → webhook (push, disarankan) ATAU waitForPayment (polling)
5. verifikasi → cek signature + cross-check amount ke DB-mu
6. fulfill → set order PAID (idempotent, sekali saja)
Contoh handler webhook production-ready:
app.post("/webhook", async (req, res) => {
let event;
try {
event = parseWebhook(req.rawBody, req.headers["x-casaku-signature"], process.env.WEBHOOK_SECRET);
} catch {
return res.status(401).json({ error: "Invalid signature" });
}
const order = await db.orders.findByCasakuTxnId(event.transactionId);
if (!order) return res.status(404).json({ error: "Order not found" });
// Idempotent: Casaku bisa kirim event sama 2x
if (order.status === "PAID") return res.json({ success: true });
// Cross-check nominal — jangan percaya amount mentah
if (event.status === "paid" && event.amount === order.totalAmount) {
await db.orders.markPaid(order.id);
}
res.json({ success: true });
});Keamanan
- Rahasia hanya di server.
licenseKey&WEBHOOK_SECRETlewat env var, jangan commit, jangan ke frontend. - Selalu verify signature webhook. Tanpa itu, penyerang bisa spoof status
paidpalsu. - Cross-check
amountke DB-mu — jangan fulfill order cuma karena statuspaid. - Idempotent webhook. Tandai
transactionIdsudah diproses sebelum fulfill, cegah double-fulfill.
Tipe Data
Semua tipe di-export dan bisa diimport:
import type {
CasakuConfig,
CasakuResponse,
PaymentStatus,
GenerateQRISv1Params,
QRISv1Data,
GenerateQRISv2Params,
QRISv2Data,
CheckStatusData,
CancelData,
ListParams,
ListData,
ListItem,
DeleteParams,
ProfileData,
WatchOptions,
WalletShortcutOptions,
QRImageOptions,
WebhookPayload,
CasakuPackage,
} from "casaku";type PaymentStatus = "pending" | "paid" | "success" | "cancel" | "expired";License
MIT