@ianmenethil/zenpay-hpp
v5.1.0
Published
ZenPay Hosted Payment Page plugin — zero-dependency modal/redirect payment integration with optional jQuery and Bootstrap backward compatibility
Maintainers
Readme
ZenPay HPP Plugin
A hosted payment page (HPP) plugin for integrating ZenPay/Zenith Payments, B2BPay, TravelPay, RentalRewards, SchoolEasyPay, ChildCareEasyPay, and ThoroughbredPayments into merchant pages.
The plugin opens the hosted payment page in a modal iframe (displayMode=0) or returns a generated redirect URL (displayMode=1).
CDN
// v6 — new opt-in features (theming, modern typography, typed ESM import)
// await import("https://cdn.zenithpayments.support/zenpay.payment.v6.js");
// await import("https://cdn.zenithpayments.support/zenpay.payment.v6.min.js");
// await import("https://cdn.zenithpayments.support/zenpay.payment.v6.obf.min.js");
// v5 — current stable release
// await import("https://cdn.zenithpayments.support/zenpay.payment.v5.js");
// await import("https://cdn.zenithpayments.support/zenpay.payment.v5.min.js");
await import("https://cdn.zenithpayments.support/zenpay.payment.v5.obf.min.js");
// v4 — parity port, no bug fixes
// await import("https://cdn.zenithpayments.support/zenpay.payment.v4.js");
// await import("https://cdn.zenithpayments.support/zenpay.payment.v4.min.js");
// await import("https://cdn.zenithpayments.support/zenpay.payment.v4.obf.min.js");Modern ESM import (v6 only)
import { zpPayment, type ZpPaymentOptions, type ZpTheme } from "@ianmenethil/zenpay-hpp/v6";
const payment = zpPayment({
url: "https://payuat.travelpay.com.au/online/v5",
apiKey: "YOUR_API_KEY",
fingerprint: "...",
theme: "auto", // 'light' | 'dark' | 'auto'
});
payment.open();Test Status
529 tests across 54 files — all passing.
| Suite | Files | Tests | Description |
|-------|-------|-------|-------------|
| test:unit (v3) | 14 | 118 | Node-environment unit tests for v3 source |
| test:dom (v3) | 1 | 16 | JSDOM browser integration tests for v3 |
| test:v4 | 12 | 118 | Full v4 test suite |
| test:v5 | 13 | 136 | Full v5 test suite |
| test:v6 | 14 | 141 | Full v6 test suite (v5 clone + 5 new theme tests) |
| Total | 54 | 529 | |
Known-bug tests are marked test.fails() — they assert the correct behavior and are expected to fail against the buggy source. They do not inflate the passing count.
Coverage
| Version | Statements | Branches | Functions | Lines | |---------|-----------|----------|-----------|-------| | v3 | 100% | 100% | 100% | 100% | | v4 | 98.19% | 91.30% | 100% | 99.53% | | v5 | 97.70% | 89.47% | 97.43% | 100% | | v6 | 97.38% | 89.28% | 97.43% | 100% |
Run coverage:
bun run test:coverage:v3 # → coverage/v3/
bun run test:coverage:v4 # → coverage/v4/
bun run test:coverage:v5 # → coverage/v5/
bun run test:coverage:v6 # → coverage/v6/Plugin Versions
This repository ships one plugin in three versions. All three produce the same end-user payment experience.
v3 — Production (jQuery + Bootstrap required)
Source: src/v3/zenpay.payment.bs5.js
The current live version, pulled by thousands of integrators from CDN today. Plain JavaScript. Hard dependencies on jQuery and Bootstrap 5. Contains known bugs — frozen, never edited.
<!-- Requires jQuery and Bootstrap 5 on the page -->
<script src="https://cdnuat.travelpay.com.au/js/zenpay.payment.bs5.js"></script>
<script>
var payment = $.zpPayment({ ... });
payment.init();
</script>v4 — TypeScript Port (jQuery + Bootstrap optional)
Source: src/v4/zenpay.payment.v4.ts
Same behavior as v3. jQuery and Bootstrap are no longer required — the plugin detects them at runtime and uses them if present. Existing integrators with jQuery and Bootstrap see zero difference. New integrators without them get a pure JS/CSS fallback. No bug fixes — parity with v3 only.
<!-- jQuery and Bootstrap are optional -->
<script src="dist/zenpay.payment.v4.min.js"></script>
<script>
var payment = zpPayment({ ... }); // window.zpPayment — no $ needed
payment.init();
</script>v5 — Current Release (zero dependencies, bug fixes)
Source: src/v5/zenpay.payment.v5.ts
v4 with targeted bug fixes and improvements. Same TypeScript codebase, same zero-dependency design, same backward compatibility. The fixes applied are limited to cases where the change cannot break any existing or new integration.
<script src="https://cdn.zenithpayments.support/zenpay.payment.v5.obf.min.js"></script>
<script>
var payment = window.zpPayment({ ... });
payment.open();
</script>v6 — Opt-In Features (theming, typed ESM, modern baseline)
Source: src/v6/zenpay.payment.v6.ts
v5 behavior plus opt-in improvements that are zero-impact when unused. Same factory API (zpPayment({ ... })), same side-effect global registration (window.zpPayment, $.zpPayment), same tests and same passing behavior. Existing integrators who upgrade and don't pass any new options see byte-identical output to v5.
What's new in v6:
themeoption —"light" | "dark" | "auto". When set, stampsdata-zp-themeon the modal/backdrop and switches palette via CSS variables. Default brand dark palette:#1a1a1asurface,#ff5252accent."auto"follows OSprefers-color-scheme.- CSS custom properties —
--zp-modal-bg,--zp-modal-fg,--zp-modal-border,--zp-modal-header-border,--zp-modal-muted,--zp-accent,--zp-backdrop-bg,--zp-btn-fg,--zp-font-family,--zp-radius,--zp-shadow,--zp-ease,--zp-modal-width,--zp-modal-max-width. Integrators can override any of these in their own stylesheet. - Modern baseline typography — system font stack, antialiased, tighter title (
font-weight: 600,letter-spacing: -0.01em), bodyline-height: 1.55. - Responsive width —
clamp(500px, 90vw, 900px)at ≥768px (up from fixed 600px/690px). - Softer visual chrome —
border-radius: 8px(was 3px), subtlebox-shadow, Material easingcubic-bezier(0.4, 0, 0.2, 1)on transitions. - Interactive close-button states — hover/active/focus-visible with 150ms transitions. Zero-specificity via
:where()so Bootstrap pages continue to use Bootstrap's rules. - Named ESM export +
.d.tstypes —import { zpPayment, type ZpPaymentOptions, type ZpTheme } from "@ianmenethil/zenpay-hpp/v6"with full IDE autocomplete.
Usage — CDN / UMD (unchanged from v5):
<script src="https://cdn.zenithpayments.support/zenpay.payment.v6.obf.min.js"></script>
<script>
var payment = window.zpPayment({ theme: "dark", /* ... */ });
payment.open();
</script>Usage — ESM with full types:
import { zpPayment, type ZpPaymentOptions, type ZpTheme } from "@ianmenethil/zenpay-hpp/v6";
const theme: ZpTheme = "auto";
const payment = zpPayment({ theme, url: "...", apiKey: "...", fingerprint: "..." });
payment.open();All three consumption paths (ESM import, CJS require, CDN script tag) produce the same function. Types are shipped at dist/types/v6/zenpay.payment.v6.d.ts.
Quick Start
// In browser console or inline script — no build step required
await import("https://cdn.jsdelivr.net/npm/js-sha3/src/sha3.min.js");
await import("https://cdn.zenithpayments.support/zenpay.payment.v5.obf.min.js");
const apiKey = "YOUR_API_KEY";
const merchantCode = "YOUR_MERCHANT_CODE";
const password = "YOUR_PASSWORD";
const mode = "0";
const amount = "1.00";
const id = "uuid-" + Date.now();
const timestamp = new Date().toISOString().slice(0, 19);
const fingerprint = sha3_512([apiKey, merchantCode, password, mode, amount * 100, id, timestamp].join("|"));
window.zpPayment({
url: "https://payuat.travelpay.com.au/online/v5",
merchantCode: merchantCode,
apiKey: apiKey,
fingerprint: fingerprint,
redirectUrl: "https://your-site.com/payment/result",
merchantUniquePaymentId: id,
timestamp: timestamp,
displayMode: 0,
paymentAmount: amount,
customerName: "Jane Smith",
customerEmail: "[email protected]",
customerReference: "ORDER-001",
}).open();Registration
| Context | Call |
|---------|------|
| v3 | $.zpPayment(options) — jQuery only |
| v4/v5/v6, no jQuery | window.zpPayment(options) or zpPayment(options) |
| v4/v5/v6, jQuery present | Both $.zpPayment(options) and window.zpPayment(options) work |
| v6, ESM import | import { zpPayment } from "@ianmenethil/zenpay-hpp/v6" (also registers window globals) |
Options Reference
Required
| Option | Type | Description |
|--------|------|-------------|
| url | string | HPP endpoint base URL |
| apiKey | string | Merchant API key |
| fingerprint | string | SHA3-512 HMAC of payment parameters |
| action | string | Payment action (e.g. "Authorise") |
| mode | number | Payment mode: 0=DefaultPayment, 1=Tokenise, 3=CustomPayment |
URL & Redirect
| Option | Type | Description |
|--------|------|-------------|
| callbackUrl | string | Server-to-server callback URL (at least one of callbackUrl/redirectUrl required) |
| redirectUrl | string | Browser redirect on completion |
| displayMode | string\|number | 0=modal popup, 1=return redirect URL payload |
| merchantCode | string | Required for v4-format URLs (path contains v4) |
Customer
| Option | Type | Description |
|--------|------|-------------|
| customerName | string | Pre-fills customer name |
| customerEmail | string | Pre-fills customer email |
| customerReference | string | Merchant customer reference |
| paymentAmount | string | Pre-fills payment amount |
| merchantUniquePaymentId | string | Unique payment identifier (idempotency) |
| timestamp | string | ISO 8601 timestamp without timezone (YYYY-MM-DDTHH:MM:SS) |
| abn | string | Australian Business Number |
| cardProxy | string | Stored card token |
UI
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| title | string | "Process Payment" | Modal dialog title |
| hideHeader | 0\|1 | 0 | Hide HPP page header |
| hideAmount | 0\|1 | 0 | Hide amount display |
| hideReference | 0\|1 | 0 | Hide reference display |
| allowAmountChange | 0\|1 | — | Allow customer to change amount |
Theming (v6 only)
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| theme | "light" \| "dark" \| "auto" | undefined | Stamps data-zp-theme on the modal/backdrop; undefined = unchanged v5 output |
For custom palettes beyond the built-in themes, override any --zp-* CSS variable in your own stylesheet. No option needed — works alongside or instead of theme. See the v6 section for the full token list.
Behaviour
| Option | Type | Description |
|--------|------|-------------|
| onPluginClose | function | Callback invoked when modal closes |
| redirectOnError | boolean | Redirect instead of showing error in modal |
| applePayPlugin | object | Pre-initialized Apple Pay plugin instance |
Display Modes
displayMode=0 — Modal popup
Opens the hosted payment page in a full-screen iframe modal. Call .open() (or .init() in v3) on the returned instance.
displayMode=1 — Redirect URL
Does not open any UI. Returns { isSuccess: true, url, height, width } from .init(). Redirect the browser to url yourself. Useful for mobile apps or custom UI flows.
Instance API
const payment = window.zpPayment(options);
payment.open(); // Opens modal (displayMode=0) or returns URL payload (displayMode=1)
payment.init(); // Alias for open() — v3 style
payment.close(); // Closes and removes the modalFingerprint
The fingerprint is a SHA3-512 hash of pipe-delimited payment parameters:
import { sha3_512 } from "js-sha3"; // or load from CDN
const fingerprint = sha3_512([
apiKey,
merchantCode,
merchantPassword,
mode,
Math.round(parseFloat(amount) * 100), // amount in cents
merchantUniquePaymentId,
timestamp, // YYYY-MM-DDTHH:MM:SS
].join("|"));The exact parameter order is defined by the ZenPay HPP API. Contact [email protected] for merchant credentials.
Known Bugs (v3 and v4)
These bugs exist in v3 (frozen, never fixed) and are intentionally reproduced in v4 (parity rules). They are fixed in v5 and v6.
| # | Severity | Description | v3 | v4 | v5 | v6 |
|---|----------|-------------|----|----|----|-----|
| 001 | Medium | getPBoolValue strict equality — string "1" returns false instead of true | Bug | Bug (parity) | Fixed | Fixed |
| 002 | Low | Height calculation — mode="1" (string) gives 725px instead of 450px | Bug | Bug (parity) | Fixed | Fixed |
| 003 | Low | onPaymentPluginLoaded leaks as implicit global (window.onPaymentPluginLoaded) | Bug | Bug (parity) | Fixed | Fixed |
| 004 | Info | CSS class typo modal-dailog-payment (kept for backward compat) | Typo | Kept | Kept | Kept |
| 005 | Medium | closePayment() creates a new Bootstrap Modal instance instead of reusing the existing one | Bug | Bug (parity) | Fixed | Fixed |
All bugs are fixed in v5 and v6. Bug 004 is a deliberate backward-compat decision — both the original typo class and the corrected class are applied.
Development
Prerequisites
- Bun
1.3.9
bun installCommands
| Command | Description |
|---------|-------------|
| bun run test:unit | v3 unit tests (Node environment) |
| bun run test:dom | v3 DOM tests (JSDOM) |
| bun run test:v4 | v4 test suite |
| bun run test:v5 | v5 test suite |
| bun run test:v6 | v6 test suite |
| bun run test:vitest | All Vitest tests (unit + dom + v4 + v5 + v6) |
| bun run test:all | Full local suite |
| bun run test:coverage:v3 | v3 coverage → coverage/v3/ |
| bun run test:coverage:v4 | v4 coverage → coverage/v4/ |
| bun run test:coverage:v5 | v5 coverage → coverage/v5/ |
| bun run test:coverage:v6 | v6 coverage → coverage/v6/ |
| bun run test:e2e | Playwright live E2E (requires env vars) |
| bun run lint | ESLint |
| bun run typecheck | TypeScript type check |
| bun run build | Build all versions to dist/ (includes v6 ESM + .d.ts) |
| bun run build:v4 | Build v4 only (plain + min + obf) |
| bun run build:v5 | Build v5 only (plain + min + obf) |
| bun run build:v6 | Build v6 only (plain + min + obf + esm) |
| bun run build:v6:esm | Build v6 ESM module only (.mjs) |
| bun run build:types:v6 | Emit v6 TypeScript declarations to dist/types/v6/ |
| bun run build:local | Build + copy to local CDN server (hostname Anticide only) |
| bun run check | Full quality gate (lint + typecheck + knip + coverage including v6) |
Build Outputs
dist/
zenpay.payment.v4.js # v4 plain (IIFE)
zenpay.payment.v4.min.js # v4 minified (IIFE)
zenpay.payment.v4.obf.min.js # v4 obfuscated + minified
zenpay.payment.v5.js # v5 plain (IIFE)
zenpay.payment.v5.min.js # v5 minified (IIFE)
zenpay.payment.v5.obf.min.js # v5 obfuscated + minified
zenpay.payment.v6.js # v6 plain (IIFE)
zenpay.payment.v6.min.js # v6 minified (IIFE)
zenpay.payment.v6.obf.min.js # v6 obfuscated + minified
zenpay.payment.v6.mjs # v6 ESM module (named export: zpPayment)
types/
v6/
zenpay.payment.v6.d.ts # v6 public declarations
types.d.ts # v6 type re-exports (includes ZpTheme)
constants.d.ts
shared/
types.d.ts # shared option/result typesv4 and v5 are IIFE-only. Each file self-registers on window.zpPayment (and $.zpPayment if jQuery is detected). v6 ships both IIFE (for CDN / <script> tags) and ESM (.mjs with named export for import { zpPayment }); both side-effect-register the globals for backward compat.
Test Suite Structure
tests/
unit/ # v3 Node-environment tests (14 files)
dom/ # v3 JSDOM tests (1 file)
v4/ # v4 tests (12 files)
v5/ # v5 tests (13 files)
v6/ # v6 tests (14 files, incl. theme.v6.test.ts)
helpers/
plugin-loader.js # VM/eval loaders for v3 (V8 coverage compatible)
unit-browser-harness.js # JSDOM harness for v3 DOM tests
v4-dom-harness.ts # Shared harness for v4 tests
options.js # Canonical valid options baseline
fingerprint.js # Fingerprint generation utility
e2e/ # Playwright live tests (env-gated)
fixtures/ # HTML harness pages (4 variants)
support/ # Static server + visual masking CSStests/v5/v5-dom-harness.ts and tests/v6/v6-dom-harness.ts are co-located with their respective test suites (not in helpers/).
v3 test helpers
plugin-loader.js provides two loading strategies:
loadPluginInVm— compiles viaModule._compileso Vitest V8 coverage can instrument the sourceloadPluginInBrowserWindow— evaluates viawindow.evalfor JSDOM browser-like tests
v4/v5/v6 test pattern
Tests import the TypeScript source directly. There is no VM loader. The shared harness (setupV4Harness / setupV5Harness / setupV6Harness) resets modules between tests and configures window.jQuery and window.bootstrap mocks as needed.
E2E Tests
Live E2E tests require environment variables:
export ZENPAY_HPP_BASE_URL=https://payuat.travelpay.com.au
export ZENPAY_MERCHANT_CODE=1337
export ZENPAY_API_KEY=your-api-key
export ZENPAY_USER_NAME=your-username
export ZENPAY_PASSWORD=your-password
bun run test:e2eThree Playwright projects run against the live HPP:
| Project | Description |
|---------|-------------|
| hpp.live | Smoke flow — open modal, verify iframe loads |
| harness-matrix.live | Runs all four fixture harnesses (jQuery+BS, jQuery only, BS only, neither) |
| v3-v4.compare.live | Side-by-side v3 vs v4 behavioral comparison with screenshots |
Reports and screenshots go to reports/playwright/runs/{runId}/.
Repository Structure
src/
v3/ zenpay.payment.bs5.js ← frozen production source
v4/ zenpay.payment.v4.ts ← parity port
types.ts
v5/ zenpay.payment.v5.ts ← current stable release
types.ts
constants.ts
baseline.css
v6/ zenpay.payment.v6.ts ← v5 + opt-in theming + typed ESM export
types.ts (re-exports shared types + adds ZpTheme)
constants.ts
baseline.css (CSS vars + dark/auto theme blocks)
css.d.ts
shared/
types.ts
scripts/
build.ts ← esbuild pipeline (IIFE, minify, obfuscate, ESM)
deploy-local.ts ← local CDN copy (hostname-gated)
tsconfig.json ← base TS config (typecheck only)
tsconfig.v6.d.json ← v6 declaration emit → dist/types/
docs/
adr/ ← Architecture Decision Records
.github/
bugs/ ← known bug reports (001-005)
workflows/
ci.yml ← CI: lint, typecheck, jscpd, knip, tests, build, SnykLicense
UNLICENSED — proprietary. Contact [email protected].
