paywuz-sdk
v1.0.0
Published
Node.js SDK for the Paywuz payment gateway — create payments, check status, verify webhooks
Downloads
419
Maintainers
Readme
Paywuz SDK
Node.js SDK for the Paywuz payment gateway — create Virtual Account & QRIS payments, check transaction status, and verify webhooks.
Install
pnpm add paywuz-sdkQuick Start
import { PaywuzClient } from "paywuz-sdk";
const paywuz = new PaywuzClient({
apiKey: "pk_live_your_api_key_here",
});
// List available payment methods
const methods = await paywuz.listPaymentMethods();
// Create a payment
const tx = await paywuz.createTransaction({
orderId: "INV-20260618-001",
amount: 150000, // Rp 150.000
paymentMethod: "BCAVA", // BCA Virtual Account
});
console.log("Payment Number:", tx.paymentNumber);
// => "9880912345678901"
// Check status later
const status = await paywuz.getTransaction("INV-20260618-001");
console.log(status.status); // "success" | "pending" | "failed" | "cancelled"Combined VA ("VA" meta-method)
Pass paymentMethod: "VA" to defer the bank choice to the customer. The transaction is created in pending state without a real VA number; the customer's /pay page shows a bank picker, and once they pick a bank the real VA is created via LinkQu. The fee is recalculated from the chosen bank's fee structure.
const tx = await paywuz.createTransaction({
orderId: "INV-20260618-002",
amount: 150000,
paymentMethod: "VA", // customer picks a bank on /pay
});
console.log(tx.paymentNumber); // null — customer hasn't picked a bank yet
console.log(tx.paymentUrl); // share this with the customerResolution flow:
- Transaction is stored as
pendingwithpaymentMethod: "VA"andpaymentNumber: null. - Share
paymentUrlwith the customer. - Customer opens the page, picks a bank from the list.
- Paywuz calls LinkQu to create the real VA, updates the row in place, and the page refetches to show the resolved VA number.
- When the customer pays, the webhook payload reports the resolved bank code (e.g.
"014"for BCA,"009"for BNI), not"VA". ThepaymentMethodreturned bygetTransactionis also the resolved bank code.
Limits & fees: the meta-method has flatIdr: 0 and percentBps: 0. The actual fee is determined by the customer's chosen bank at checkout, so the total amount on the webhook may differ from the original totalPayment returned at creation.
API
new PaywuzClient(config)
| Option | Type | Default | Description |
|---|---|---|---|
| apiKey | string | required | Your project API key (pk_live_... or pk_sand_...) |
| baseUrl | string | https://api.paywuz.id | Custom base URL for the API |
| timeout | number | 30000 | Request timeout in ms |
client.listPaymentMethods()
Returns available payment methods for your project.
const methods = await paywuz.listPaymentMethods();
// [{ code: "BNIVA", name: "BNI Virtual Account", type: "virtual_account", fee: { flatIdr: 3500, percentBps: 0 }, limits: { minIdr: 10000, maxIdr: 50000000 } }, ...]client.createTransaction(req)
Create a new payment transaction. Idempotent on orderId — calling twice with the same orderId returns the existing transaction.
| Field | Type | Required | Description |
|---|---|---|---|
| orderId | string | Yes | Your order/invoice ID (1–64 chars, unique per project) |
| amount | number | Yes | Amount in whole IDR (integer) |
| paymentMethod | string | Yes | Code from listPaymentMethods() |
| expiryMinutes | number | No | Payment expiry in minutes (default QRIS: 60, VA: 720) |
| redirectUrl | string | No | Post-payment redirect URL (max 2048 chars) |
| metadata | any | No | Arbitrary data returned in webhooks |
client.getTransaction(orderId)
Retrieve a transaction by your orderId. Throws PaywuzNotFoundError if not found.
client.cancelTransaction(orderId)
Cancel a pending transaction. Throws PaywuzNotFoundError if not found, or PaywuzValidationError if not pending.
Webhook Verification
Verify incoming webhooks from Paywuz:
import { verifyWebhookSignature } from "paywuz-sdk";
app.post("/paywuz-webhook", (req, res) => {
const rawBody = JSON.stringify(req.body);
const signature = req.headers["x-paywuz-signature"];
if (!verifyWebhookSignature(rawBody, process.env.PAYWUZ_API_KEY!, signature)) {
return res.status(403).send("Invalid signature");
}
const event = req.headers["x-paywuz-event"];
const deliveryId = req.headers["x-paywuz-delivery"];
// event: "transaction.paid" | "transaction.settlement" | "transaction.failed" | "transaction.cancelled"
// req.body contains the flat payload below
// Deduplicate by deliveryId
if (alreadyProcessed(deliveryId)) return res.send("OK");
switch (event) {
case "transaction.paid":
case "transaction.settlement":
// Mark order as paid
break;
case "transaction.failed":
// Handle failure
break;
case "transaction.cancelled":
// Handle cancellation
break;
}
res.send("OK");
});Webhook Payload
The body is a flat JSON object. The event type is sent in the X-Paywuz-Event header.
interface WebhookPayload {
id: string;
orderId: string;
amount: number;
fee: number;
totalPayment: number;
paymentMethod: string;
status: TransactionStatus;
timestamp: string;
}Example payload:
{
"id": "trx_01JQnXbP8kM2vR4w",
"orderId": "INV-20260618-001",
"amount": 150000,
"fee": 4500,
"totalPayment": 154500,
"paymentMethod": "BCAVA",
"status": "settlement",
"timestamp": "2026-06-18T10:00:00.000Z",
"metadata": { "key": "value" }
}| Field | Type | Description |
|---|---|---|
| id | string | Paywuz transaction ID |
| orderId | string | Your order/invoice ID |
| amount | number | Original payment amount (IDR) |
| fee | number | Admin fee charged to customer (IDR) |
| totalPayment | number | Amount + fee = total paid by customer |
| paymentMethod | string | Payment method code (e.g. BCAVA, QRIS) |
| status | string | pending, settlement, success, failed, or cancelled |
| timestamp | string | ISO 8601 timestamp of the event |
| metadata | object | Arbitrary JSON stored with the transaction (may be null) |
Headers sent:
X-Paywuz-Signature: sha256=<hex>— verify with your API keyX-Paywuz-Event— event name (e.g.transaction.paid)X-Paywuz-Delivery— unique delivery ID for deduplication
Retry policy: Up to 3 retries with 30s then 60s backoff. 4xx responses are not retried.
Signature Verification
Uses HMAC-SHA256 with your project API key as the secret. Constant-time comparison prevents timing attacks.
Error Handling
All errors extend PaywuzError:
| Error Class | Status | When |
|---|---|---|
| PaywuzValidationError | 400 | Invalid input |
| PaywuzNotFoundError | 404 | Transaction not found |
| PaywuzGatewayError | 502 | Payment gateway error |
| PaywuzError | other | Auth (401/403), rate limit (429), network errors |
import { PaywuzError } from "paywuz-sdk";
try {
await paywuz.createTransaction({ ... });
} catch (err) {
if (err instanceof PaywuzError) {
console.error(err.code, err.message);
}
}Sandbox Mode
Use a sandbox API key (pk_sand_...) for testing. All requests go to the same host.
const paywuz = new PaywuzClient({
apiKey: "pk_sand_your_sandbox_key_here",
});Requirements
- Node.js >= 18
- ESM and CJS compatible
