Appearance
Bill Presentment Web SDK
The @iimmpact-sdn-bhd/billpresentment-sdk package provides a JavaScript/TypeScript wrapper that opens the bill selection UI in an iframe. It handles session authentication, iframe lifecycle, and postMessage communication. Outstanding amounts load progressively in the hosted UI, and failed lookups show a retry action per bill.
Installation
bash
npm install @iimmpact-sdn-bhd/billpresentment-sdkBasic Usage
typescript
import { BillPresentmentSDK } from "@iimmpact-sdn-bhd/billpresentment-sdk";
const sdk = new BillPresentmentSDK({
sessionToken: "bp_sess_xxxx",
displayMode: "modal",
});
const result = await sdk.open();Configuration
| Option | Type | Default | Description |
|---|---|---|---|
sessionToken | string | required | Session token from POST /v2/sdk/sessions |
displayMode | "modal" | "fullscreen" | "inline" | "modal" | How the UI is presented |
container | HTMLElement | — | Target element for inline mode |
environment | "production" | "staging" | "production" | API environment. Use "staging" for testing |
appUrl | string | — | Override hosted web app URL (for self-hosting) |
targetOrigin | string | "*" | Restrict postMessage origin for security |
Environment
By default, the SDK connects to the production API. For testing, pass environment: "staging":
typescript
const sdk = new BillPresentmentSDK({
sessionToken,
displayMode: "modal",
environment: "staging", // Uses staging API internally
});INFO
The environment controls which API the hosted web app connects to internally. API URLs are never exposed to your frontend code. This follows the same pattern as Stripe and Plaid — the token determines the environment.
Security: targetOrigin
In production, set targetOrigin to restrict which origin can receive postMessage results:
typescript
const sdk = new BillPresentmentSDK({
sessionToken,
displayMode: "modal",
targetOrigin: "https://your-app.com",
});WARNING
Leaving targetOrigin as the default ("*") means any page could intercept the result if it embeds the SDK. Always set this in production.
Display Modes
Modal
Opens the bill UI as a centered overlay with a backdrop. The user can close it by clicking outside or pressing Escape.
typescript
const sdk = new BillPresentmentSDK({
sessionToken,
displayMode: "modal",
});
const result = await sdk.open();
// Promise resolves when user confirms or cancelsBest for: trigger buttons ("View Bills"), secondary flows.
Fullscreen
Opens the bill UI as a full-viewport overlay. Covers the entire screen.
typescript
const sdk = new BillPresentmentSDK({
sessionToken,
displayMode: "fullscreen",
});
const result = await sdk.open();Best for: mobile web, dedicated bill management pages.
Inline
Embeds the bill UI inside a specific container element. Does not create any overlay.
typescript
const sdk = new BillPresentmentSDK({
sessionToken,
displayMode: "inline",
container: document.getElementById("bill-container")!,
});
const result = await sdk.open();Best for: embedding within an existing page layout.
WARNING
For inline mode, the container element must be in the DOM before calling sdk.open(). The iframe will fill the container's dimensions — set appropriate width and height on the container.
Result Handling
sdk.open() returns a Promise<BillSelectionResult> that resolves when:
- The user confirms their bill selection (
status: "success") - The user closes the SDK without selecting (
status: "cancelled", resolves as{ status: "cancelled" }) - An error occurs (
status: "error")
typescript
const result = await sdk.open();
switch (result.status) {
case "success":
// User selected bills
console.log(result.user); // { fullName, icNumber }
console.log(result.selectedBills); // Array of { billerCode, billerName, ref1, ref2, amount }
console.log(result.totalAmount); // Sum of selected bill amounts
break;
case "cancelled":
// User dismissed without selecting
break;
case "error":
// Something went wrong
console.error(result.error);
break;
}Result Types
typescript
interface User {
fullName: string;
icNumber: string;
}
interface SelectedBill {
billerCode: string;
billerName: string;
ref1: string;
ref2: string;
amount: number;
}
type BillSelectionResult =
| { status: "success"; user: User; selectedBills: SelectedBill[]; totalAmount: number }
| { status: "cancelled" }
| { status: "error"; error: string };Event Listeners
The SDK is Promise-first (await sdk.open()), and also supports optional event listeners for lifecycle hooks.
typescript
sdk.on("loaded", () => {
/* WebView loaded */
});
sdk.on("error", (error) => {
/* Error occurred */
});
sdk.on("close", () => {
/* User closed */
});Complete Example
html
<!DOCTYPE html>
<html>
<body>
<button id="view-bills">View My Bills</button>
<div id="result"></div>
<script type="module">
import { BillPresentmentSDK } from "@iimmpact-sdn-bhd/billpresentment-sdk";
document.getElementById("view-bills").addEventListener("click", async () => {
// 1. Get session token from your backend
const res = await fetch("/api/create-bill-session", { method: "POST" });
const { session_token } = await res.json();
// 2. Open the SDK
const sdk = new BillPresentmentSDK({
sessionToken: session_token,
displayMode: "modal",
});
const result = await sdk.open();
// 3. Handle the result
if (result.status === "success") {
document.getElementById("result").textContent =
`${result.user.fullName}: ${result.selectedBills.length} bills, total: RM${result.totalAmount.toFixed(2)}`;
sessionStorage.setItem("user", JSON.stringify(result.user));
sessionStorage.setItem("selectedBills", JSON.stringify(result.selectedBills));
sessionStorage.setItem("totalAmount", String(result.totalAmount));
window.location.href = "/payment";
}
if (result.status === "cancelled") {
// User dismissed without selecting
}
if (result.status === "error") {
console.error("SDK error:", result.error);
}
});
</script>
</body>
</html>Framework Examples
React
tsx
import { BillPresentmentSDK } from "@iimmpact-sdn-bhd/billpresentment-sdk";
import { useCallback } from "react";
function BillButton({ sessionToken }: { sessionToken: string }) {
const handleClick = useCallback(async () => {
const sdk = new BillPresentmentSDK({
sessionToken,
displayMode: "modal",
});
const result = await sdk.open();
if (result.status === "success") {
// Navigate to payment with user and bills
}
}, [sessionToken]);
return <button onClick={handleClick}>View Bills</button>;
}Vue
vue
<script setup lang="ts">
import { BillPresentmentSDK } from "@iimmpact-sdn-bhd/billpresentment-sdk";
const props = defineProps<{ sessionToken: string }>();
async function openBills() {
const sdk = new BillPresentmentSDK({
sessionToken: props.sessionToken,
displayMode: "modal",
});
const result = await sdk.open();
if (result.status === "success") {
// Navigate to payment with user and bills
}
}
</script>
<template>
<button @click="openBills">View Bills</button>
</template>Troubleshooting
SDK fails to load / blank iframe
- Verify the session token is valid and not expired
- Check browser console for CORS errors
- Ensure
bills.iimmpact.comis not blocked by CSP or ad blockers
"Session expired" error
- Session tokens use a 15-minute sliding TTL. Each successful API call extends the expiry window.
- Sessions have a 1-hour absolute lifetime cap. Create a new session token if the user returns after long inactivity.
postMessage not received
- Ensure the SDK is not blocked by an iframe sandbox policy
- Check that your page's
Content-Security-Policyallowsframe-src https://bills.iimmpact.com
Modal not closing
- For
modalandfullscreenmodes, the SDK cleans up the iframe when the Promise resolves - If using a SPA router, ensure the SDK is not unmounted before the Promise resolves
