Embedded Card Checkout

Hesabe Direct Payment lets you embed a secure, hosted payment form on your website without handling card data directly. Card information is entered inside an iframe served from Hesabe's domain, keeping your integration PCI-compliant

Prerequisites

  1. You have a Hesabe merchant account with direct payment (MPGS) enabled.
  2. Your website domain has been registered with Hesabe for iframe embedding (Web Checkout only).
  3. Your backend can call the Hesabe Checkout API to obtain a payment data token.

Domain Registration Required for Embedded Web Checkout

Contact Hesabe Support Team to register your domain for iframe embedding:support@hesabe.com
Web Checkout Form

How It Works

Merchant Side
Hesabe Side
1

Frontend: POST /checkout

2

response.data

encrypted payload

3

Frontend loads payment.js SDK

4

HesabeDirectPayment.init({ data })

5

Renders iframe payment form

6

Customer fills card details

Card data stays on Hesabe

7

onSuccess(token) or onError(error)

8

Backend verifies token via Hesabe API

The data passed to the SDK is the encrypted payload from the Checkout API (response.data). The raw payment token is never exposed in the browser.


Quick Start

Verify your web domain

Domain Registration Required for Embedded Web Checkout

Contact Hesabe team to register your domain for iframe embedding:support@hesabe.com

Include the SDK Script

Add the Hesabe SDK script to your checkout page:

Sandbox

Below JS Script strictly for sandbox testing only. Never use these sandbox URLs in production.

https://sandbox.hesabe.com/sdk/payment.js

Production

Use the following Checkout API strictly for production transactions.

https://api.hesabe.com/sdk/payment.js

Create a Container Element

Add a div where the payment iframe will be mounted:

checkout.html
<div id="payment-container"></div>

Obtain Encrypted Payment Data from Your Backend

Call the Hesabe Checkout API from your server and use the response.data field โ€” not the top-level token โ€” and pass it to your frontend.

{
  "status": true,
  "token": "...",
  "response": {
    "data": "<encrypted_payload>"
  }
}

Always pass response.data (the encrypted payload) to the SDK โ€” never the top-level token.

Initialize the SDK

Sandbox

Below JS Script strictly for sandbox testing only. Never use these sandbox URLs in production.

https://sandbox.hesabe.com/sdk/payment.js

Production

Use the following Checkout API strictly for production transactions.

https://api.hesabe.com/sdk/payment.js
checkout.html

<script>
  var payment = HesabeDirectPayment.init({
    container: '#payment-container',
    data: 'ENCRYPTED_DATA_FROM_CHECKOUT_API',
    onSuccess: function (result) {
      console.log('Payment successful', result.token);
    },
    onError: function (error) {
      console.error('Payment failed:', error.message);
    },
    onCancel: function () {
      console.log('Payment cancelled by user');
    }
  });
</script>

API Reference

HesabeDirectPayment.init(options)

Mounts the payment iframe into the specified container and returns a handle.

Returns: { destroy() } โ€” call destroy() to remove the iframe and clean up listeners.

Options

ParameterTypeRequiredDefaultDescription
container
string | ElementYesโ€”CSS selector (e.g. '#pay-box') or a DOM element where the iframe will be mounted.
data
stringYesโ€”Encrypted payment data from the Hesabe Checkout API (response.data field).
environment
stringNo'production'Target environment. Accepted values: 'production', 'staging', 'local'. Use 'staging' for sandbox testing.
locale
stringNo'en'UI language. Accepted values: 'en' (English), 'ar' (Arabic / RTL).
styles
objectNoHesabe defaultsVisual customization. See Style Options below.
onSuccess
function(result)NoRedirect to Hesabe response URLCalled when payment is completed successfully.
onError
function(error)NoRedirect to Hesabe response URLCalled when payment fails or is declined.
onCancel
function()Noโ€”Called when the user cancels the 3DS authentication step.

If onSuccess or onError are not provided, the SDK automatically redirects the page to the Hesabe response URL returned by the gateway.

Style Options

Pass any of these inside the styles object to customize the appearance of the payment form.

PropertyTypeDefaultDescription
primaryColor
string (CSS color)#1a73e8Accent color used for focus rings and highlights.
buttonColor
string (CSS color)#1a73e8Background color of the Pay button.
buttonHoverColor
string (CSS color)#1557b0Background color of the Pay button on hover.
buttonTextColor
string (CSS color)#ffffffText color of the Pay button.
fontFamily
string (CSS font stack)Inter, sans-serifFont family for the entire form. Accepts any system font or Google Font name. Google Fonts are loaded automatically โ€” no <link> tag needed.
borderRadius
string (CSS value)8pxBorder radius applied to input fields and the Pay button.
inputBorderColor
string (CSS color)#d1d5dbBorder color of card input fields.
errorColor
string (CSS color)#dc2626Color used for validation error messages.

Example:

HesabeDirectPayment.init({
  container: '#payment-container',
  data: paymentData,
  styles: {
    buttonColor: '#0f766e',
    buttonHoverColor: '#115e59',
    buttonTextColor: '#ffffff',
    primaryColor: '#0f766e',
    fontFamily: 'Poppins, sans-serif',
    borderRadius: '4px',
    inputBorderColor: '#94a3b8',
    errorColor: '#e11d48',
  },
  onSuccess: function (result) { /* ... */ }
});

Using Google Fonts

The SDK automatically loads any Google Font โ€” no <link> tag needed on your page. Simply pass the font name as the fontFamily style option.

HesabeDirectPayment.init({
  container: '#payment-container',
  data: paymentData,
  styles: {
    fontFamily: 'Poppins, sans-serif',
  },
  onSuccess: function (result) { /* ... */ }
});

The font name must match the exact name listed on Google Fonts. Always include a fallback generic family (sans-serif, serif, etc.) after the font name.

Popular examples:

FontfontFamily valueNotes
Poppins'Poppins, sans-serif'Modern geometric, great for UI
Inter'Inter, sans-serif'Clean, highly legible
Roboto'Roboto, sans-serif'Material Design default
Lato'Lato, sans-serif'Friendly and readable
Cairo'Cairo, sans-serif'Supports Arabic script
Tajawal'Tajawal, sans-serif'Arabic-first, also supports Latin
Noto Sans'Noto Sans, sans-serif'Wide language coverage

System fonts (Arial, Helvetica, Georgia, Verdana, etc.) work without any loading โ€” just pass them directly and no network request is made.

Callbacks

onSuccess(result)

Fired after the payment is authorized or captured.

FieldTypeDescription
result.tokenstringThe payment token that was processed. Use this to verify the payment status from your backend.
onSuccess: function (result) {
  fetch('/your-backend/verify', {
    method: 'POST',
    body: JSON.stringify({ token: result.token })
  });
}
onError(error)

Fired when the payment is declined or an error occurs.

FieldTypeDescription
error.messagestringHuman-readable error description.
error.codestring | numberError code returned by the payment gateway (may be undefined).
onError: function (error) {
  alert('Payment failed: ' + error.message);
}
onCancel()

Fired when the user dismisses the 3D Secure authentication popup without completing it.

onCancel: function () {
  console.log('User cancelled authentication');
}

destroy()

Removes the iframe from the DOM and detaches all internal event listeners. Call this when navigating away or if you need to re-mount the form.

var payment = HesabeDirectPayment.init({ /* ... */ });

// Later:
payment.destroy();

Environments

environment valueURLUse for
'production' (default)https://api.hesabe.comLive payments
'staging'https://sandbox.hesabe.comTesting and integration
'local'http://127.0.0.1:8000Local development
HesabeDirectPayment.init({
  container: '#payment-container',
  data: paymentData,
  environment: 'staging',
  onSuccess: function (result) { /* ... */ }
});

Arabic / RTL Support

Set locale: 'ar' to switch the form to Arabic with right-to-left layout:

HesabeDirectPayment.init({
  container: '#payment-container',
  data: paymentData,
  locale: 'ar',
  styles: {
    fontFamily: 'Cairo, sans-serif',
  },
  onSuccess: function (result) { /* ... */ }
});

Complete Example

Sandbox

Below JS Script strictly for sandbox testing only. Never use these sandbox URLs in production.

https://sandbox.hesabe.com/sdk/payment.js

Production

Use the following Checkout API strictly for production transactions.

https://api.hesabe.com/sdk/payment.js
checkout.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Checkout</title>
</head>
<body>

  <h2>Complete Your Payment</h2>
  <div id="payment-container"></div>


  <script>
    // Pass response.data from your backend Checkout API call (encrypted payload)
    var paymentData = '{{ $checkoutResponse["response"]["data"] }}';

    var payment = HesabeDirectPayment.init({
      container: '#payment-container',
      data: paymentData,
      environment: 'production',
      locale: 'en',
      styles: {
        buttonColor: '#2563eb',
        buttonHoverColor: '#1d4ed8',
        fontFamily: 'Inter, sans-serif',
        borderRadius: '6px',
      },
      onSuccess: function (result) {
        window.location.href = '/order/confirmation?token=' + result.token;
      },
      onError: function (error) {
        document.getElementById('error-msg').textContent = error.message;
      },
      onCancel: function () {
        document.getElementById('error-msg').textContent = 'Authentication was cancelled. Please try again.';
      }
    });
  </script>

  <p id="error-msg" style="color:red;margin-top:12px;"></p>

</body>
</html>
Merchant Side
Hesabe Side
1

Frontend: POST /checkout

channel: "mobile"

2

response.webviewUrl

ready-to-load URL

3

App opens webviewUrl in native WebView

4

Renders full-page payment form

5

Customer fills card details

Card data stays on Hesabe

6

Redirect to merchant callback URL

7

Backend verifies payment via Hesabe API

No domain registration required for WebView integration. The encrypted data in the URL is the only gate โ€” no raw token is exposed.


Integration Steps

Backend: Request a WebView URL

Add channel: 'mobile' to your Checkout API request:


POST /checkout
{
  "..existing checkout params..",
  "paymentType": "2", 
  "channel": "mobile", //,
  "version": "3.0"
}

The response will include a ready-to-use webviewUrl:

// Sandbox response

{
  "status": true,
  "response": {
    "data": "<encrypted_payload>", 
    "webviewUrl": "https://sandbox.hesabe.com/pay/webview?data=<encrypted_payload>"
  }
}

Mobile App: Open the URL in a WebView

Pass webviewUrl directly to your WebView โ€” no JS SDK needed.

import WebKit

let webView = WKWebView(frame: view.bounds)
view.addSubview(webView)

if let url = URL(string: webviewUrl) {
    webView.load(URLRequest(url: url))
}
val webView: WebView = findViewById(R.id.webView)
webView.settings.javaScriptEnabled = true
webView.webViewClient = WebViewClient()
webView.loadUrl(webviewUrl)

Install the package first:

npm install react-native-webview
# iOS: cd ios && pod install
import React from 'react';
import { WebView } from 'react-native-webview';

export default function PaymentScreen({ webviewUrl }: { webviewUrl: string }) {
  return (
    <WebView
      source={{ uri: webviewUrl }}
      javaScriptEnabled={true}
      style={{ flex: 1 }}
    />
  );
}

Add the dependency to pubspec.yaml:

dependencies:
  webview_flutter: ^4.0.0
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

class PaymentScreen extends StatefulWidget {
  final String webviewUrl;
  const PaymentScreen({required this.webviewUrl, super.key});

  @override
  State<PaymentScreen> createState() => _PaymentScreenState();
}

class _PaymentScreenState extends State<PaymentScreen> {
  late final WebViewController _controller;

  @override
  void initState() {
    super.initState();
    _controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..loadRequest(Uri.parse(widget.webviewUrl));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Payment')),
      body: WebViewWidget(controller: _controller),
    );
  }
}

No domain registration required for WebView integration. The encrypted data in the URL is the only gate โ€” no raw token is exposed.


Security Notes

  • Card data never touches your server. All card input is handled inside an iframe on Hesabe's domain.
  • Raw token is never exposed in the browser. The SDK and WebView URL both use an encrypted payload (response.data). Even if the URL is logged or intercepted, the token cannot be read without Hesabe's server-side key.
  • Domain allowlisting is enforced server-side (web only). The iframe will only load when the parent page's domain is registered with Hesabe. Requests from unregistered domains receive a 403 response. Mobile WebView integration is exempt from domain checks.
  • Always verify payment server-side. The onSuccess callback confirms the frontend flow completed โ€” always cross-check the payment status via the Hesabe API from your backend before fulfilling an order.
  • Use HTTPS in production. The SDK requires the merchant page to be served over HTTPS in production environments.

Troubleshooting

SymptomLikely causeFix
Iframe shows "Website Not Authorized"Domain not registered with HesabeContact itsupport@hesabe.com to add your domain
HesabeDirectPayment is not definedSDK script not loadedEnsure the <script> tag for payment.js is included before your init code
container element not foundSelector doesn't match any elementVerify the container value matches an existing element; run init after DOM is ready
data (payment token) is requireddata option missing or emptyPass the response.data field (encrypted payload) from your Checkout API response โ€” not the top-level token
Form renders but pay button does nothingToken already used or expiredGenerate a fresh token from the Checkout API
WebView shows "Invalid Request"Wrong data value passed in URLEnsure you pass response.data from the checkout response, not the raw token
WebView URL not returned in responsechannel: 'mobile' not sentAdd "channel": "mobile" to your Checkout API request body
Custom font not applyingFont name typoCheck the spelling matches exactly the Google Fonts name (e.g. 'Poppins' not 'poppins')
Payment form loads on staging but not productionWrong environment valueEnsure environment: 'production' (or omit it) for live payments

Support

For integration support or to register your domain:

Hesabe IT Support

Contact Hesabe Support Team:itsupport@hesabe.com