π³ Embedded Hosted Checkout
Live Demo
Experience the embedded checkout in action

Apple Pay Setup Required
Before using Apple Pay with embedded checkout, you must complete the domain verification process:
Request domain whitelisting and Apple verification setup:
itsupport@hesabe.com
Request to whitelist your merchant domain for Apple Pay integration
β Once whitelisted, Hesabe will provide an Apple Domain Verification Value
π― This file needs to be added on your websiteβs hosting server in the following path:
[MerchantWebsite]/.well-known/apple-developer-merchantid-domain-association.txt
π Implementation Stepsβ
All payment data must be encrypted using Hesabe's encryption library before sending to the API endpoint. Make sure to store merchant credentials like ivkey, secret key, accessCode, merchant code in environment variables
Include Hesabe SDK Script
Add the Hesabe embedded payment script to your HTML header:
<script src="https://unpkg.com/@hesabe-pay/embedded-hosted-checkout@latest/cdn/hesabe-payments.min.js"> </script>
Create Payment Container
Add a div element with the required ID where the payment form will be rendered:
<div id="hesabe-payments"></div>
β οΈ Important: Keep the ID exactly as "hesabe-payments" - this is required for proper initialization.
Create Checkout Request
Prepare your payment data and send an encrypted POST request to the Hesabe checkout endpoint:
let data = {
merchantCode: '842217', // Sandbox merchant code
amount: 2, // Payment amount (numeric)
paymentType: 0, // Always 0 for embedded checkout
responseUrl: 'https://sandbox.hesabe.com/customer-response?id=842217',
failureUrl: 'https://sandbox.hesabe.com/customer-response?id=842217',
version: 2.0, // API version (always 2.0)
orderReferenceNumber: '0494994949', // Your order reference
currency: 'KWD', // Currency code (always KWD)
embeddedPayment: 'true' // Enable embedded mode
};
// Continue next steps - Refere example below
π API Parametersβ
Handle API Response
After sending the encrypted request, you'll receive a response containing the session token:
{
"status": true,
"code": 200,
"message": "Authentication success!",
"response": {
"data": "eyJkYXRhIjoiOWU1YTk2MGRlOWE1YzlhYmI0M2ZhZDUyMmZhYjU..."
}
}
The response.data
field contains the encrypted session token needed for payment initialization.
Initialize Payment Form
Configure and initialize the Hesabe payment form with the session token:
// Payment result callback function
const paymentResult = (result) => {
console.log(result && result.status , "status")
console.log(result && result.method , "method")
console.log(result && result.data,'transaction result data');
}
// Payment configuration object
let config = {
environment: "sandbox", // "sandbox" or "production"
paymentTypes: ["knet","card","applepay"], // Available payment methods
sessionID: response.data, // Session token from API response
debug: true, // Set to false in production
callback: paymentResult // Optional: callback function
};
// Initialize the Hesabe payment form
hesabePayment.init(config);
βοΈ Configuration Parametersβ
Payment Method Limitations
These payment methods do not support embedded payments and will redirect customers to external pages.
These methods support embedded payments and return results through callbacks. Without a callback, users are redirected to your specified URLs.
π¨ Payment Interface Previewβ
Embedded Payment Options

π Complete Implementation Examplesβ
- HTML
- PHP
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/1.6.0/axios.min.js"></script>
<!-- Include your Hesabe Script -->
<script src="https://unpkg.com/@hesabe-pay/embedded-hosted-checkout@latest/cdn/hesabe-payments.min.js"></script>
<title>Checkout - Your Order</title>
<style>
/* Reset and base styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
min-height: 100vh;
line-height: 1.6;
color: #333;
padding: 1 !important;
}
.container {
max-width: 1350px;
margin: 0 auto;
padding: 10px;
}
/* Header */
.checkout-header {
text-align: center;
margin-bottom: 40px;
background: rgba(255, 255, 255, 0.95);
padding: 30px;
border-radius: 15px;
backdrop-filter: blur(10px);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
}
.checkout-header h1 {
color: #2c3e50;
font-size: 2.5rem;
font-weight: 700;
margin-bottom: 10px;
}
.checkout-header p {
color: #7f8c8d;
font-size: 1.1rem;
}
/* Main content layout */
.checkout-content {
display: flex;
gap: 30px;
align-items: flex-start;
}
.col-6 {
flex: 1;
min-height: 500px;
width: 50%;
}
/* Cart Items Styles */
.cart-item {
display: flex;
align-items: center;
padding: 20px 0;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
}
.cart-item:hover {
background: rgba(52, 152, 219, 0.05);
border-radius: 10px;
padding: 20px 15px;
}
.cart-item:last-child {
border-bottom: none;
}
.item-image {
margin-right: 15px;
flex-shrink: 0;
}
.item-image img {
width: 80px;
height: 80px;
object-fit: cover;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.item-details {
flex: 1;
margin-right: 15px;
}
.item-details h3 {
color: #2c3e50;
font-size: 1.1rem;
font-weight: 600;
margin-bottom: 5px;
}
.item-description {
color: #7f8c8d;
font-size: 0.9rem;
margin-bottom: 10px;
}
.quantity-controls {
display: flex;
align-items: center;
gap: 10px;
}
.qty-btn {
width: 30px;
height: 30px;
border: none;
border-radius: 50%;
background: #3498db;
color: white;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
font-weight: 600;
}
.qty-btn:hover {
background: #2980b9;
transform: scale(1.1);
}
.quantity {
font-weight: 600;
color: #2c3e50;
min-width: 20px;
text-align: center;
}
.item-price {
text-align: right;
flex-shrink: 0;
}
.price {
font-size: 1.2rem;
font-weight: 700;
color: #27ae60;
}
/* Order Summary */
.order-summary {
margin-top: 25px;
padding-top: 20px;
border-top: 2px solid rgba(0, 0, 0, 0.1);
}
.summary-row {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
font-size: 1rem;
}
.summary-row.total {
font-size: 1.3rem;
font-weight: 700;
color: #2c3e50;
margin-top: 15px;
padding-top: 15px;
border-top: 2px solid #3498db;
}
/* Callback Status Display */
.callback-status {
position: fixed;
top: 20px;
right: 20px;
padding: 15px 20px;
border-radius: 8px;
color: white;
font-weight: 600;
z-index: 1000;
max-width: 400px;
word-wrap: break-word;
}
.callback-status.success {
background: #27ae60;
}
.callback-status.error {
background: #e74c3c;
}
.callback-status.cancelled {
background: #f39c12;
}
.callback-status.timeout {
background: #9b59b6;
}
/* Debug Console */
.debug-console {
background: #2c3e50;
color: #ecf0f1;
padding: 15px;
border-radius: 8px;
margin-top: 20px;
font-family: 'Courier New', monospace;
font-size: 12px;
max-height: 200px;
overflow-y: auto;
}
.debug-console h3 {
color: #3498db;
margin-bottom: 10px;
}
.debug-log {
margin-bottom: 5px;
}
.debug-log.error {
color: #e74c3c;
}
.debug-log.success {
color: #27ae60;
}
.debug-log.warning {
color: #f39c12;
}
@media (max-width: 768px) {
.checkout-content {
flex-direction: column;
gap: 20px;
}
.checkout-header h1 {
font-size: 2rem;
}
.callback-status {
position: relative;
top: auto;
right: auto;
margin-bottom: 20px;
}
}
.cart-section{
width: 100% !important;
}
.payment-section{
width: 100% !important;
}
</style>
</head>
<body>
<div class="container">
<!-- Header -->
<header class="checkout-header text-center mb-4">
<h1>Embedded Demo Checkout</h1>
<p>Review your order</p>
</header>
<!-- Callback Status Display -->
<div id="callbackStatus" style="display: none;"></div>
<!-- Checkout Content -->
<div class="row checkout-content">
<!-- Cart Items Section (Left Column) -->
<div class="col-sm-12 col-md-6 cart-section">
<div class="section-card">
<h2>Order Summary</h2>
<!-- Cart Item 1 -->
<div class="cart-item">
<div class="item-image">
<img src="https://images.unsplash.com/photo-1505740420928-5e560c06d30e" alt="Premium Wireless Headphones">
</div>
<div class="item-details">
<h3>Premium Wireless Headphones</h3>
<p class="item-description">Noise-cancelling, Bluetooth 5.0</p>
</div>
<div class="item-price">
<span class="price" id="price-1">45.500 KWD</span>
</div>
</div>
<!-- Cart Item 2 -->
<div class="cart-item">
<div class="item-image">
<img src="https://images.unsplash.com/photo-1523275335684-37898b6baf30" alt="Smart Watch Pro">
</div>
<div class="item-details">
<h3>Smart Watch Pro</h3>
<p class="item-description">Fitness tracking, GPS enabled</p>
</div>
<div class="item-price">
<span class="price" id="price-2">89.750 KWD</span>
</div>
</div>
<!-- Cart Item 3 -->
<div class="cart-item">
<div class="item-image">
<img src="https://images.unsplash.com/photo-1572569511254-d8f925fe2cbb" alt="Portable HeadPhone">
</div>
<div class="item-details">
<h3>Portable HeadPhone</h3>
<p class="item-description">Waterproof, 12-hour battery</p>
</div>
<div class="item-price">
<span class="price" id="price-3">20.250 KWD</span>
</div>
</div>
<!-- Order Summary -->
<div class="order-summary mt-3">
<div class="summary-row">
<span>Subtotal:</span>
<span id="subtotal">155.500 KWD</span>
</div>
<div class="summary-row">
<span>Shipping:</span>
<span id="shipping">2.500 KWD</span>
</div>
<div class="summary-row">
<span>Tax:</span>
<span id="tax">7.775 KWD</span>
</div>
<div class="summary-row total">
<span>Total:</span>
<span id="total">165.775 KWD</span>
</div>
</div>
</div>
</div>
<!-- Payment Gateway Section (Right Column) -->
<div class="col-sm-12 col-md-6 payment-section">
<div id="hesabe-payments"></div>
</div>
</div>
</div>
<script>
// Sample credentials (REPLACE with actual secure ones)
const secretKey = 'PkW64zMe5NVdrlPVNnjo2Jy9nOb7v1Xg'; // 32 chars for AES-256
const ivKey = '5NVdrlPVNnjo2Jy9'; // 16 chars for AES
const accessCode = 'c333729b-d060-4b74-a49d-7686a8353481';
const environment = 'sandbox'; // or 'production'
async function fetchMPGSSession() {
try {
let data = {
merchantCode: "842217",
amount: 165.775,
paymentType: 0,
responseUrl: 'https://sandbox.hesabe.com/customer-response?id=842217', // Sandbox URL
failureUrl: 'https://sandbox.hesabe.com/customer-response?id=842217', // Sandbox URL
version: 2.0,
orderReferenceNumber: "08894994949",
currency: 'KWD',
embeddedPayment: true
};
let postUrl = environment === 'production' ? 'https://api.hesabe.com/checkout' : 'https://sandbox.hesabe.com/checkout'
console.log("Payload-details", data)
console.log("postUrl", postUrl)
let encrypted_results = await customHesabeUtils.encrypt(JSON.stringify(data), secretKey, ivKey);
const response = await axios.post(postUrl, { data: encrypted_results }, {
headers: {
accessCode: accessCode,
'Content-Type': 'application/json'
}
});
if (response && response.status === 200) {
let response_object = response.data;
let decrypted_results = await customHesabeUtils.decrypt(response_object, secretKey, ivKey);
let data_results = JSON.parse(decrypted_results);
if (data_results && data_results.response && data_results.response.data) {
let token_data = data_results.response.data;
loadConfig(token_data);
console.log(data_results)
}
}
} catch (error) {
console.log(error)
let errorMessage = 'Failed to initialize payment form';
if (error && error.response) {
try {
let error_object = error.response.data;
let error_message = await customHesabeUtils.decrypt(error_object, secretKey, ivKey);
errorMessage = error_message?.response?.message || error_message;
console.log('error', "Decrypted error message:", error_message);
} catch (decryptError) {
console.log('error', "Failed to decrypt error message:", decryptError);
}
} else {
customHesabeUtils.log('error', "Request error:", error);
errorMessage = error.message || errorMessage;
}
}
}
//Enhanced payment result callback
const paymentResult = (result) => {
console.log(result && result.status , "status")
console.log(result && result.method , "method")
console.log(result && result.data,'data');
}
function loadConfig(token_data) {
let config = {
environment: "sandbox", // or "production"
paymentTypes: ["knet","card","applepay"],
sessionID: token_data,
debug: true,
// callback: paymentResult //Optional - your callback function
};
// Initialize the Hesabe payment form
hesabePayment.init(config);
}
// Initialize when DOM is ready
document.addEventListener("DOMContentLoaded", async function () {
await fetchMPGSSession();
});
</script>
</body>
</html>
Hesabe PHP Payment Libraryβ
A simple PHP library for integrating with Hesabe payment gateway by referring to Hesabe documentation.
Installationβ
-
Copy the files to your project
-
Update
hesabe_config.php
with your credentials from Hesabe merchant panel
Usageβ
<?php
require_once 'src/HesabeClient.php';
use Hesabe\HesabeClient;
$config = require 'hesabe_config.php';
$hesabe = new HesabeClient($config);
// Create payment and generate scripts
$hesabe->createPayment(
amount: 10.500,
currency: 'KWD',
responseUrl: 'https://yoursite.com/success',
failureUrl: 'https://yoursite.com/failure',
paymentType: 0 // 0=Indirect, 1=KNET, 2=Visa/MC, 7=AMEX
)
->setOrderReference('ORDER_123')
->setCustomer('John Doe', 'john@example.com', '12345678')
->setWebhookUrl('https://yoursite.com/webhook');
// Output payment form with SDK integration
echo $hesabe->scripts();
Configurationβ
Update hesabe_config.php
:
return [
'merchant_code' => 'YOUR_MERCHANT_CODE',
'access_code' => 'YOUR_ACCESS_CODE',
'encryption_key' => 'YOUR_ENCRYPTION_KEY',
'iv_key' => 'YOUR_IV_KEY',
'environment' => 'sandbox', // or 'production'
'version' => '2.0',
];
Payment Typesβ
0
- Indirect (customer selects payment method)1
- KNET2
- Visa/MasterCard7
- American Express9
- Apple Pay10
- Cybersource15
- Recurring/Subscription
Methodsβ
createPayment()
- Create payment and get token internallysetOrderReference()
- Set order reference numbersetCustomer()
- Set customer detailssetWebhookUrl()
- Set webhook URL for notificationsscripts()
- Generate HTML/JS with Hesabe SDK integrationgetToken()
- Get current payment tokenqueryTransaction()
- Query transaction status
SDK Integrationβ
The scripts()
method now uses the official Hesabe SDK for better user experience:
// Basic usage (shows all payment types with default callback)
echo $hesabe->scripts();
// Custom container ID and payment types
echo $hesabe->scripts(
['knet', 'card', 'applepay'], // Payment types to show
false, // Debug mode (true for development)
'myCallbackFunction' // Custom JavaScript callback function (optional)
);
Custom Payment Callbackβ
You can provide your own JavaScript function to handle payment results. If no callback is provided, the Hesabe SDK will handle the results internally:
// Without callback - SDK handles everything
echo $hesabe->scripts(['knet', 'card']);
// With custom callback - your function handles results
echo $hesabe->scripts(['knet', 'card'], false, 'handlePayment');
?>
<script>
function handlePayment(result) {
if (result.success) {
// Custom success handling
showSuccessMessage(result.transactionId);
setTimeout(() => window.location.href = '/success', 2000);
} else {
// Custom error handling
showErrorMessage(result.errorMessage);
}
}
</script>
Note: When no callback function is provided, the callback parameter is omitted entirely, allowing the Hesabe SDK to handle payment results with its default behavior.
Available Payment Types for SDK:β
'knet'
- KNET payments'card'
- Visa/MasterCard payments'applepay'
- Apple Pay payments
Requirementsβ
- PHP 8.0+
- OpenSSL extension
- cURL extension
<?php
namespace Hesabe;
/**
* Hesabe Payment Gateway PHP Client
*
* A simple and secure PHP library for integrating with Hesabe payment gateway.
* Supports payment creation, customer management, and frontend script generation.
*
* @author Hesabe PHP Library
* @version 1.0.0
*/
class HesabeClient
{
/** @var array Configuration array containing merchant credentials */
private array $config;
/** @var string|null Current payment token returned from Hesabe API */
private ?string $currentToken = null;
/** @var array Payment data to be sent to Hesabe checkout endpoint */
private array $paymentData = [];
/**
* Initialize Hesabe client with configuration
*
* @param array $config Configuration array with required keys:
* - merchant_code: Merchant ID from Hesabe
* - access_code: Access code from Hesabe
* - encryption_key: Encryption key from Hesabe
* - iv_key: IV key from Hesabe
* - environment: 'sandbox' or 'production' (optional)
* - version: API version (optional, defaults to '2.0')
* @throws \InvalidArgumentException If the required configuration is missing
*/
public function __construct(array $config)
{
$this->validateConfig($config);
$this->config = $config;
}
/**
* Validate required configuration fields
*
* @param array $config Configuration array to validate
* @throws \InvalidArgumentException If any required field is missing
*/
private function validateConfig(array $config): void
{
$required = ['merchant_code', 'access_code', 'encryption_key', 'iv_key'];
foreach ($required as $field) {
if (empty($config[$field])) {
throw new \InvalidArgumentException("Required configuration field '{$field}' is missing");
}
}
}
/**
* Create a new payment and generate token
*
* @param float $amount Payment amount (formatted to 3 decimal places)
* @param string $currency Currency code (e.g., 'KWD', 'USD')
* @param string $responseUrl URL to redirect on successful payment
* @param string $failureUrl URL to redirect on failed payment
* @param int $paymentType Payment type: 0=Indirect, 1=KNET, 2=Visa/MC, 7=AMEX, 9=Apple Pay, 15=Recurring
* @return self Returns instance for method chaining
* @throws \RuntimeException If payment token creation fails
*/
public function createPayment(
float $amount,
string $currency,
string $responseUrl,
string $failureUrl,
int $paymentType = 0
): self
{
$this->paymentData = [
'merchantCode' => $this->config['merchant_code'],
'amount' => number_format($amount, 3, '.', ''),
'currency' => $currency,
'responseUrl' => $responseUrl,
'failureUrl' => $failureUrl,
'paymentType' => $paymentType,
'version' => $this->config['version'] ?? '2.0',
'embed' => true
];
$response = $this->processCheckout();
if ($response['success'] && !empty($response['data'])) {
$this->currentToken = $response['data'];
}
return $this;
}
/**
* Set order reference number for tracking
*
* @param string $orderReference Your internal order/reference number
* @return self Returns instance for method chaining
*/
public function setOrderReference(string $orderReference): self
{
$this->paymentData['orderReferenceNumber'] = $orderReference;
return $this;
}
/**
* Set customer information (required for KFast payment method)
*
* @param string|null $name Customer's full name (max 100 characters)
* @param string|null $email Customer's email address (max 100 characters)
* @param string|null $mobileNumber Customer's mobile number without country code (8 digits)
* @return self Returns instance for method chaining
*/
public function setCustomer(string $name = null, string $email = null, string $mobileNumber = null): self
{
if ($name) $this->paymentData['name'] = $name;
if ($email) $this->paymentData['email'] = $email;
if ($mobileNumber) $this->paymentData['mobile_number'] = $mobileNumber;
return $this;
}
/**
* Set webhook URL for payment status notifications
*
* @param string $webhookUrl Your endpoint URL to receive payment status updates
* @return self Returns instance for method chaining
*/
public function setWebhookUrl(string $webhookUrl): self
{
$this->paymentData['webhookUrl'] = $webhookUrl;
return $this;
}
/**
* Generate HTML and JavaScript for Hesabe payment integration
*
* Creates payment form using official Hesabe SDK with embedded payment options.
* Supports KNET, Card, and Apple Pay payment methods in a unified interface.
* Uses a static container ID 'hesabe-payments' for consistent integration.
* Allows developers to provide custom JavaScript callback functions for payment results.
*
* @param array $paymentTypes Array of payment types to enable: ['knet', 'card', 'applepay'] (default: all)
* @param bool $debug Enable debug mode for development (default: false)
* @param string|null $callbackFunction Name of JavaScript function to call on payment result (optional)
* @return string HTML string containing SDK script, static container, and initialization code
* @throws \RuntimeException If no payment token is available
*/
public function scripts(array $paymentTypes = ['knet', 'card', 'applepay'], bool $debug = false, ?string $callbackFunction = null): string
{
if (!$this->currentToken) {
throw new \RuntimeException('No payment token available. Call createPayment() first.');
}
$environment = $this->config['environment'] ?? 'sandbox';
$paymentTypesJson = json_encode($paymentTypes);
$debugString = $debug ? 'true' : 'false';
// Build configuration object
$configParts = [
"environment: '{$environment}'",
"paymentTypes: {$paymentTypesJson}",
"sessionID: '{$this->currentToken}'",
"debug: {$debugString}",
"containerId: 'hesabe-payments'"
];
// Add callback only if provided
$callbackSetup = '';
if ($callbackFunction !== null) {
$callbackSetup = "
// Payment result callback function
function paymentResultCallback(result) {
if (typeof {$callbackFunction} === 'function') {
{$callbackFunction}(result);
} else {
console.error('Callback function {$callbackFunction} is not defined');
}
}";
$configParts[] = "callback: paymentResultCallback";
}
$configString = implode(",\n ", $configParts);
return "
<script src=\"https://embed-demo.hesabe.com/hesabe-payments.js\"></script>
<div id=\"hesabe-payments\"></div>
<script>
document.addEventListener('DOMContentLoaded', function() {
{$callbackSetup}
// Initialize Hesabe payment configuration
let config = {
{$configString}
};
// Initialize the Hesabe payment form
if (window.hesabePayment) {
window.hesabePayment.init(config);
} else {
console.error('Hesabe SDK not loaded');
}
});
</script>
";
}
/**
* Process checkout request to Hesabe API
*
* Encrypts payment data and sends it to Hesabe checkout endpoint.
* Stores the returned token internally for later use.
*
* @return array Response array with success status, token, and message
*/
private function processCheckout(): array
{
try {
// Convert payment data to JSON
$jsonData = json_encode($this->paymentData);
$encryptedData = $this->encrypt($jsonData);
// Send encrypted data to Hesabe
$response = $this->makeApiCall($this->getCheckoutUrl(), [
'data' => $encryptedData
]);
if ($response['status_code'] === 200) {
$decryptedResponse = $this->decrypt($response['data']);
$responseData = json_decode($decryptedResponse, true);
return [
'success' => isset($responseData['status']) && $responseData['status'],
'data' => $responseData['response']['data'] ?? null,
'message' => $responseData['response']['message'] ?? null
];
}
return ['success' => false, 'message' => 'API request failed'];
} catch (\Exception $e) {
return ['success' => false, 'message' => $e->getMessage()];
}
}
/**
* Encrypt data using AES-256-CBC encryption (Hesabe specific method)
*
* Uses Hesabe's specific encryption format with PKCS5 padding and hex encoding
*
* @param string $data Data to encrypt
* @return string URL-encoded hex string
*/
private function encrypt(string $data): string
{
$key = $this->config['encryption_key'];
$ivKey = $this->config['iv_key'];
// PKCS5 padding
$data = $this->pkcs5_pad($data);
// Encrypt using AES-256-CBC
$encrypted = openssl_encrypt($data, 'AES-256-CBC', $key, OPENSSL_ZERO_PADDING, $ivKey);
$encrypted = base64_decode($encrypted);
$encrypted = unpack('C*', $encrypted);
$encrypted = $this->byteArray2Hex($encrypted);
$encrypted = urlencode($encrypted);
return $encrypted;
}
/**
* Add PKCS5 padding to data
*/
private function pkcs5_pad(string $text): string
{
$blocksize = 32;
$pad = $blocksize - (strlen($text) % $blocksize);
return $text . str_repeat(chr($pad), $pad);
}
/**
* Convert byte array to hex string
*/
private function byteArray2Hex(array $byteArray): string
{
$chars = array_map("chr", $byteArray);
$bin = join($chars);
return bin2hex($bin);
}
/**
* Decrypt data using AES-256-CBC decryption (Hesabe specific method)
*
* Uses Hesabe's specific decryption format matching their encryption
*
* @param string $encryptedData Hex encoded encrypted data
* @return string Decrypted data
*/
private function decrypt(string $encryptedData): string
{
$key = $this->config['encryption_key'];
$ivKey = $this->config['iv_key'];
// Check if it's valid hex
if (!(ctype_xdigit($encryptedData) && strlen($encryptedData) % 2 == 0)) {
return false;
}
$encryptedData = $this->hex2ByteArray(trim($encryptedData));
$encryptedData = $this->byteArray2String($encryptedData);
$encryptedData = base64_encode($encryptedData);
$decrypted = openssl_decrypt($encryptedData, 'AES-256-CBC', $key, OPENSSL_ZERO_PADDING, $ivKey);
return $this->pkcs5_unpad($decrypted);
}
/**
* Convert hex string to byte array
*/
private function hex2ByteArray(string $hexString): array
{
$string = hex2bin($hexString);
return unpack('C*', $string);
}
/**
* Convert byte array to string
*/
private function byteArray2String(array $byteArray): string
{
$chars = array_map("chr", $byteArray);
return join($chars);
}
/**
* Remove PKCS5 padding from data
*/
private function pkcs5_unpad(string $text): string
{
$pad = ord($text[strlen($text) - 1]);
if ($pad > strlen($text)) {
return false;
}
if (strspn($text, chr($pad), strlen($text) - $pad) != $pad) {
return false;
}
return substr($text, 0, -1 * $pad);
}
/**
* Make HTTP API call to Hesabe endpoints
*
* Sends POST request with proper headers including access code authentication.
* Uses cURL with SSL verification and 30-second timeout.
*
* @param string $url API endpoint URL
* @param array $data Request data to send
* @return array Response array with status_code and data
*/
private function makeApiCall(string $url, array $data): array
{
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($data),
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Accept: application/json',
'accessCode: ' . $this->config['access_code'] // Required by Hesabe API
],
CURLOPT_TIMEOUT => 30,
CURLOPT_SSL_VERIFYPEER => true,
]);
$response = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
return [
'status_code' => $httpCode,
'data' => $response
];
}
/**
* Get base URL based on environment configuration
*
* @return string Base URL for Hesabe API (sandbox or production)
*/
private function getBaseUrl(): string
{
$environment = $this->config['environment'] ?? 'sandbox';
return $environment === 'production'
? 'https://api.hesabe.com'
: 'https://sandbox.hesabe.com';
}
/**
* Get checkout endpoint URL
*
* @return string Complete checkout URL
*/
private function getCheckoutUrl(): string
{
return $this->getBaseUrl() . '/checkout';
}
/**
* Get payment URL with token parameter
*
* @return string Complete payment URL for redirecting customer
*/
private function getPaymentUrl(): string
{
return $this->getBaseUrl() . '/payment?data=' . urlencode($this->currentToken);
}
/**
* Get current payment token
*
* @return string|null Current payment token or null if not set
*/
public function getToken(): ?string
{
return $this->currentToken;
}
/**
* Query transaction status by token or reference
*
* @param string $tokenOrReference Payment token or order reference number
* @return array API response with transaction details
*/
public function queryTransaction(string $tokenOrReference): array
{
$url = $this->getBaseUrl() . '/api/transaction/' . $tokenOrReference;
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPGET => true, // GET request
CURLOPT_HTTPHEADER => [
'Accept: application/json',
],
CURLOPT_TIMEOUT => 30,
CURLOPT_SSL_VERIFYPEER => true,
]);
$response = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
$decodedResponse = json_decode($response, true);
return [
'status_code' => $httpCode,
'data' => $decodedResponse,
'success' => $httpCode === 200
];
}
}