npm.io
0.2.0 • Published 3h ago

casaku

Licence
MIT
Version
0.2.0
Deps
0
Size
65 kB
Vulns
0
Weekly
0

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

Keywords