npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

casaku

v0.2.1

Published

Casaku — lightweight TypeScript client for the Casaku QRIS payment gateway API + webhook verification.

Downloads

470

Readme

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

npm install casaku
# atau
pnpm add casaku
# atau
bun add casaku

Quick 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. licenseKey JANGAN 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.totalAmount

Params

| 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 generateQRISv2 dengan packageIds array.


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), BUKAN JSON.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 dashboard

Error 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_SECRET lewat env var, jangan commit, jangan ke frontend.
  • Selalu verify signature webhook. Tanpa itu, penyerang bisa spoof status paid palsu.
  • Cross-check amount ke DB-mu — jangan fulfill order cuma karena status paid.
  • Idempotent webhook. Tandai transactionId sudah 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