Skip to content

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-sdk

Basic 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

OptionTypeDefaultDescription
sessionTokenstringrequiredSession token from POST /v2/sdk/sessions
displayMode"modal" | "fullscreen" | "inline""modal"How the UI is presented
containerHTMLElementTarget element for inline mode
environment"production" | "staging""production"API environment. Use "staging" for testing
appUrlstringOverride hosted web app URL (for self-hosting)
targetOriginstring"*"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

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 cancels

Best 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.com is 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-Policy allows frame-src https://bills.iimmpact.com
  • For modal and fullscreen modes, 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

IIMMPACT API Documentation