Payment Operations - Hosted Payment Page
Simple payment integration with our pre-built hosted payment solution
Overview
The Hosted Payment Page API enables a streamlined payment experience where customers can select their preferred bank after the payment session is initialized. This approach provides better user experience compared to pre-selecting banks, allowing customers to choose from available banking options during checkout.
Integration Flow
The hosted payment page integration follows a simple redirect-based flow that minimizes the integration complexity on your end while providing a secure and user-friendly payment experience.
Flow Diagram
sequenceDiagram
participant M as Merchant
participant Z as ZenPay API
participant C as Customer
participant B as Bank (FPX)
M->>Z: POST /v1/checkout-sessions (Create Session)
Z-->>M: Session ID & Checkout URL
M-->>C: Redirect to Checkout URL
C->>Z: GET /v1/checkout-sessions/{id}/checkout
Z-->>C: Checkout Page (Bank Selection)
C->>Z: Select Bank & Confirm Payment
Z->>B: Initiate FPX Payment Flow
B-->>C: Bank Authentication & Payment
B-->>Z: Payment Result (Success/Failed)
Z->>M: Webhook Notification (callback_url)
Z-->>C: Redirect to return_url/decline_url
Step-by-Step Integration
- ↳Create Checkout Session - Initialize payment session with order details
- ↳Redirect Customer - Send customer to the provided checkout URL
- ↳Bank Selection - Customer selects preferred bank on checkout page
- ↳Payment Processing - Customer completes payment through FPX
- ↳Notification - Receive payment status via webhook
- ↳Customer Return - Customer redirected to success/failure page
This endpoint requires Signature Authentication, IP Whitelisting, and is subject to Rate Limiting.
Request
Http Method & URL
POST /v1/checkout-sessions
Headers
| Header | Type | Required | Description |
|---|---|---|---|
|
Content-Type
|
string | Yes | application/json |
|
X-Signature
|
string | Yes | HMAC-SHA256 signature for request authentication |
Signature Generation: Refer to the Signature Generation section for detailed steps on generating the X-Signature header value.
Timestamp Validation: Requests with timestamps older than 5 minutes will be rejected. Ensure your system clock is synchronized with UTC.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
|
biller_code
|
string | Yes | Your merchant biller code |
|
order_id
|
string | Yes | Unique order identifier (max 50 chars) |
|
email
|
string | Yes | Customer email address (valid email format) |
|
amount
|
string | Yes | Payment amount (decimal, 2 decimal places, e.g., "150.50") |
|
callback_url
|
string | Yes | URL for payment status notifications (webhooks) |
|
return_url
|
string | No | URL to redirect after successful payment |
|
decline_url
|
string | No | URL to redirect after failed/declined payment |
|
currency
|
string | Yes | Currency code ("MYR" only) |
|
timestamp
|
string | Yes | ISO 8601 timestamp (within 5 min past, 1 min future) |
Request Body Example
{
"biller_code": "202500039",
"order_id": "ORDER123456",
"email": "customer@example.com",
"amount": "150.50",
"callback_url": "https://merchant.com/payment/callback",
"return_url": "https://merchant.com/payment/success",
"decline_url": "https://merchant.com/payment/failed",
"currency": "MYR",
"timestamp": "2025-01-15T10:30:00Z",
}
Response
Success Response (200)
{
"success": true,
"message": "Checkout session created successfully",
"data": {
"id": "cs_abc123def456ghi789",
"status": "open",
"amount": "150.50",
"currency": "MYR",
"order_id": "ORDER123456",
"email": "customer@example.com",
"url": "https://api.zenpay.com/v1/checkout-sessions/cs_abc123def456ghi789/checkout",
"expires_at": "2025-01-15T11:30:00Z",
"created_at": "2025-01-15T10:30:00Z",
"updated_at": "2025-01-15T10:30:00Z"
}
}
Error Response (400/401/500)
{
"success": false,
"message": "Validation failed",
"errors": [
{
"field": "email",
"message": "Invalid email format"
}
]
}
Checkout Page Response
The checkout page endpoint returns an HTML page with:
- ↳ Payment amount and order details
- ↳ Bank selection interface (retail/corporate)
- ↳ Real-time bank list fetching
- ↳ Responsive design for mobile/desktop
- ↳ Session expiration countdown
API Endpoints
Create Checkout Session
Create a new checkout session with payment details. The customer can then select their preferred bank on the checkout page.
Endpoint: POST /v1/checkout-sessions
Authentication: HMAC-SHA256 signature
Get Checkout Session
Retrieve details of an existing checkout session.
Endpoint: GET /v1/checkout-sessions/{session_id}
Authentication: Not required
Serve Checkout Page
Serves the interactive checkout page where customers can select their bank and complete payment.This url will be returned in the response of the Create Checkout Session endpoint from url field.
Endpoint: GET /v1/checkout-sessions/{session_id}/checkout
Authentication: Not required
Frontend Integration
Redirecting to Checkout
After creating a checkout session, redirect the customer to the provided checkout URL:
// After creating checkout session
const response = await fetch("/v1/checkout-sessions", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Signature": signature,
},
body: JSON.stringify(requestData),
});
const session = await response.json();
if (session.success) {
// Redirect customer to checkout page
window.location.href = session.data.url;
}
Checkout Page Features
The checkout page provides:
- ↳ Session Validation - Checks if session exists and is valid
- ↳ Payment Details Display - Shows amount, order ID, and expiration
- ↳ Bank Selection - Dynamic list of available banks
- ↳ Bank Type Toggle - Retail vs Corporate banking options
- ↳ Payment Processing - Handles FPX form submission
- ↳ Responsive Design - Works on mobile and desktop
Handling Returns
After payment completion, customers are redirected to:
- ↳ return_url - For successful payments
- ↳ decline_url - For failed/declined payments
Handle these redirects in your application:
// Check URL parameters for payment result
const urlParams = new URLSearchParams(window.location.search);
const status = urlParams.get("status");
const payrefId = urlParams.get("payref_id");
if (status === "success") {
// Payment successful - show success page
showPaymentSuccess(payrefId);
} else if (status === "failed") {
// Payment failed - show error page
showPaymentFailed(payrefId);
}
Error Handling
Session Errors
{
"success": false,
"message": "Invalid or expired session",
"errors": [
{
"field": "session_id",
"message": "Session has expired"
}
]
}
Validation Errors
{
"success": false,
"message": "Validation failed",
"errors": [
{
"field": "amount",
"message": "Amount must be greater than 0"
},
{
"field": "email",
"message": "Invalid email format"
}
]
}