Wiki

Clone wiki

SOP / Home

eghl.png


#Optimize Allow your customers to check out and key in their credit/debit card details directly on merchant website. This eliminates the need to redirect customer to different pages and the best part is that Merchant does not need to be PCI certified. These features enabled merchants to host their own payment/order page.

eGHL are PCI certified, and by using Library(and API) will allows your server to never touch any of the sensitive data.

eGHL-by-GHL-logo.png

Optimize - Allow your customers to check out and key in their credit/debit card details directly on merchant website. This eliminates the need to redirect customer to different pages and the best part is that merchant does not need to be PCI certified. This repository is intended as a technical guide for merchant developers and system integrators who are responsible for designing or programming the respective applications to integrate with Payment Gateway.

eGHL | Wiki | Downloads | Follow eGHL Repos


Change Log

**v1.3

  • release v1.3.
  • support MGate Browser Data
  • Steps for Changes:

1) Change your JavaScript link from /eGHL_SOP-1.6.js to /eGHL_SOP-1.7.js

#!html
<!-- Make following change -->
<script type="text/javascript" src="https://securepay.e-ghl.com/IPG/optimize/eGHL_SOP-1.7.js?v=20221014"></script>

2) Add the following script into your checkout PHP page to fetch Browser Data

#!PHP
<?php
...
// Added 2022-10-04 for Mgate Requirement: Browser Data
if (array_key_exists('BrowserData', $_REQUEST) && !empty($_REQUEST['BrowserData'])) {
$browser_data = $_REQUEST['BrowserData'];
}
else{ // Send Default Value
$browser_data = 'FALSE|en-MY|24|873|393|-420|TEXT/HTML';
}
...

3) In your checkout PHP page, Make sure you hash the value according to this format:

#!PHP
<?php
...
$data = [
    'ServiceID'             => $config['ServiceID'], // String: Service ID comes from your Merchant ID with eGHL
    'PaymentID'             => $PaymentID, // String: Payment ID from $_REQUEST['PaymentID']
    'MerchantReturnURL'     => $merchantReturnURL, // String: Your own Return URL
    'MerchantCallBackURL'   => $merchantCallBackURL, // String: Your own Call Back URL
    'Amount'                => $amount, // String: Amount from your customer's checkout
    'CurrencyCode'          => $currencyCode, // String: Currency Code from your customer's checkout
    'CustIP'                => $custIP, // String: IP from your server
    'PageTimeout'           => $PageTimeout // String: Page time out as 600 seconds
    // Important: Do not include browserData here
   ];
...
* For full code sample, go to section Generate hash

4) In your input (to be submitted to eGHL), add in hidden input for Browser Data

...
<input type="hidden" name="HashValue" value="<?php echo $hashValue;?>"/>
<!-- Added 2022-10-06 for MGate Browser Data-->
<input type="hidden" name="BrowserData" value="<?php echo $browser_data;?>"/>
...

**v1.2

  • release v1.2.
  • support recurring payments
  • added populate other payment methods options

**v1.1

  • Fixed getting js error 'undefined is not an object' when using jquery 1.1.0

[**v1.0]

  • First public release

How It all works

Collecting credit cards would be handle by eGHL Javascript Library (eGHL JS). Integration of this library will be very easy and it wont modify the interface of your forms.

How it actually work is that eGHL JS will be tokenising the card before it is send to your server.

eGHL optimize 20180803185044.png

  1. User fill in their credit card & checkout.
  2. eGHL JS sends information to our server to be tokenised.
  3. Our Server send back token to eGHL JS.
  4. Payment information (exclude card info) and the token send to your server.
  5. Your server will then submit this token to eGHL to charge the user.

#####Back to top

Getting started

Include the code in your web. As of Oct 2022, the current JavaScript version is upgraded to 1.7.

#!html
<script type="text/javascript" src="https://code.jquery.com/jquery-3.2.0.min.js"></script>
<script type="text/javascript" src="https://pay.e-ghl.com/IPGSG/optimize/eGHL_SOP-1.7.js"></script>

Preparing the form

You will need to follow the following name in order for our script to work

  • CardHolder - Card holder name
  • CardNo - Card number
  • CardExp - Expiry date for the card
  • CardCVV2 - Card CVV*

Form Example

In the following example, we are going to make a plain Credit card form. You may change it to your desire design as long the name are remain the same.

#!html
<form id="frmPayment" method="POST" action="">
    <table>
        <tr>
            <td>Card Holder:</td><td>:</td><td><input type="text" name="CardHolder"/></td>
        </tr>
        <tr>
            <td>Card Number:</td><td>:</td><td><input type="text" name="CardNo" data-sop-hash="<?php echo $SOPHash;?>"/></td>
        </tr>
        <tr>
            <td>Card Expiry(MMYYYY):</td><td>:</td><td><input type="text" name="CardExp"/></td>
        </tr>
        <tr>
            <td>CVV</td><td>:</td><td><input type="text" name="CardCVV2"/></td>
        </tr>
        <tr>
            <td colspan="3">
                <input type="submit" id="submitButton" /> 
            </td>
        </tr>
    </table>
</form> 

#####Back to top

On load Javascript

Next, you will need to add the following code to initialise our library

#!javascript
eGHL_SOP.init(<form ID>, <service ID>, <payment ID>, <Customer IP>,<Redirect Page>)
| --------------|------------------------ Form ID | ID set in <form> tag Service ID | Service ID give by eGHL Support. Payment ID | Unique Payment ID (must be the one used when submitting payment request) Customer IP | Customer’s IP address captured by merchant system Redirect Page | Page to redirect after eGHL tokenised the card

Example

#!html
<script type="text/javascript">
    window.onload=function(){
        ...

        eGHL_SOP.init(  
            'frmPayment',
            '<?php echo $config["ServiceID"];?>',
            '<?php echo $PaymentID;?>',
            '<?php echo $CustIP;?>',
            '<?php echo $config["base_url"].'checkout.php';?>'
        );

        ...
    };
</script>
* Please note that the Payment ID & Cust IP must be the same with the payment used in eGHL_SOP.init(..), eGHL_Hash & payment request

** Please note that starting v1.2, you no longer need to send parameter hash value to init function. The hash value will be added as html attributes.

Generate Hash

You will need to generate hash value for optimize(SOP) before proceed.

Example of how to generate the hash value in the server side script(example in PHP):

#!PHP
<?php
    ...
    require_once('config.php');
    require_once('lib/eGHL_HASH.php');

    $paymentID = time();
    $custIP = (!empty($_SERVER['HTTP_X_FORWARDED_FOR']))?$_SERVER['HTTP_X_FORWARDED_FOR']:$_SERVER['REMOTE_ADDR'];

    $eGHLHashObj = new eGHL_Hash([
        'PaymentID' => $paymentID,
        'ServiceID' => $config['ServiceID'],
        'CustIP'    => $custIP
    ]);

    $sopHash = $eGHLHashObj->generateHashValueForPaymentInfo('sop', $config['MerchantPass']);

    ...

Note:

  • If you wish to use your own hash algorithm library (according to our API documentation), follow the following structure: Hash key = Password + ServiceID + PaymentID + Customer IP
  • The Payment ID & Cust IP must be the same with the payment used in eGHL_SOP.init(..), eGHL_Hash & payment request*

#####Back to top

Charging the card

In order to charge the card, you will need to include Token=<'Token' in the post body> & TokenType=SOP in the normal Payment Request.

#!PHP   
<?PHP
$Token = $_REQUEST['Token'];
$PaymentID = $_REQUEST['PaymentID'];

// Added 2022-10-04 for Mgate Requirement: Browser Data
if (array_key_exists('BrowserData', $_REQUEST) && !empty($_REQUEST['BrowserData'])) {
    $browser_data = $_REQUEST['BrowserData'];
}
else{ // Send Default Value
    $browser_data = 'FALSE|en-MY|24|873|393|-420|TEXT/HTML';
}

// -------------------------------------------------------------------
// Example ONLY: The following are sample code for your reference only
// -------------------------------------------------------------------
if (array_key_exists('customer_consent', $_REQUEST) && !empty($_REQUEST['customer_consent'])) {
    $custOCP = $_REQUEST['customer_consent'];
}
if (array_key_exists('amount', $_REQUEST) && !empty($_REQUEST['amount'])) {
    $amount = $_REQUEST['amount'];
}
if (array_key_exists('currencyCode', $_REQUEST) && !empty($_REQUEST['currencyCode'])) {
    $currencyCode = $_REQUEST['currencyCode'];
}
if (array_key_exists('productName', $_REQUEST) && !empty($_REQUEST['productName'])) {
    $productName = $_REQUEST['productName'];
}
if (array_key_exists('CustEmail', $_REQUEST) && !empty($_REQUEST['CustEmail'])) {
    $custEmail = $_REQUEST['CustEmail'];
}
if (array_key_exists('CustPhone', $_REQUEST) && !empty($_REQUEST['CustPhone'])) {
    $custPhone = $_REQUEST['CustPhone'];
}
if (array_key_exists('PymtMethod', $_REQUEST) && !empty($_REQUEST['PymtMethod'])) {
    $pymtMethod = $_REQUEST['PymtMethod'];
}
if (array_key_exists('IssuingBank', $_REQUEST) && !empty($_REQUEST['IssuingBank'])) {
    $issuingBank = $_REQUEST['IssuingBank'];
}
if (array_key_exists('Token', $_REQUEST) && !empty($_REQUEST['Token'])) {
    $token = $_REQUEST['Token'];
}

$orderDesc = 'Purchasing ' . $productName;
$merchantReturnURL = $config['base_url'].'return.php?urlType=return';
$merchantCallBackURL = $config['base_url'].'return.php?urlType=callback';
$custName = "John Doe";

$PageTimeout = 600;

$custIP = (!empty($_SERVER['HTTP_X_FORWARDED_FOR']))?$_SERVER['HTTP_X_FORWARDED_FOR']:$_SERVER['REMOTE_ADDR'];
$custIP = '127.0.0.1'; // FOR TESTING ONLY: Use your own IP 

// -------------------------------------------------------------------
// End Example
// -------------------------------------------------------------------

// Include eGHL_HASH.php provided if not included
include_once 'lib/eGHL_HASH.php';

// You need to hash the values according to this format
$data = [
    'ServiceID'             => $config['ServiceID'], // Service ID in String
    'PaymentID'             => $PaymentID, // Payment ID in String from $_REQUEST['PaymentID'];
    'MerchantReturnURL'     => $merchantReturnURL, // Your own Return URL
    'MerchantCallBackURL'   => $merchantCallBackURL, // Your own Call Back URL
    'Amount'                => $amount, // Amount from your customer's checkout
    'CurrencyCode'          => $currencyCode, // Currency Code from your customer's checkout
    'CustIP'                => $custIP, // IP from your server
    'PageTimeout'           => $PageTimeout // Page time out as 600 seconds
];

// Add Token field when you have the token
if($pymtMethod == "CC") {
    if(!empty($token)) {
        $data['Token'] = $token;
    }
    $tokenType = "SOP";
} else {
    $token = "";
    $tokenType = "";
}

if(!empty($custOCP)) {
    $data['CustOCP'] = $custOCP;
}

$eGHLHashObj = new eGHL_HASH($data);

$hashValue = $eGHLHashObj->generateHashValueForPaymentInfo( 'sale', $config['MerchantPass']);
?>
#!HTML 
...
<form method="POST" name='form1' action="<?php echo $config['payment_url'];?>">
    <input type="hidden" name="Token" value="<?php echo $Token;?>"/>
    <input type="hidden" name="TokenType" value="SOP"/>

    <input type="hidden" name="TransactionType" value="SALE"/>
    <input type="hidden" name="PymtMethod" value="<?php echo $pymtMethod;?>"/>
    <input type="hidden" name="ServiceID" value="<?php echo $config['ServiceID'];?>"/>
    <input type="hidden" name="PaymentID" value="<?php echo $PaymentID;?>"/>
    <input type="hidden" name="OrderNumber" value="<?php echo $OrderNumber;?>"/>
    <input type="hidden" name="PaymentDesc" value="<?php echo $orderDesc;?>"/>
    <input type="hidden" name="MerchantReturnURL" value="<?php echo $merchantReturnURL;?>"/>
    <input type="hidden" name="MerchantCallBackURL" value="<?php echo $merchantCallBackURL;?>"/>
    <input type="hidden" name="Amount" value="<?php echo $amount;?>"/>
    <input type="hidden" name="CurrencyCode" value="<?php echo $currencyCode;?>"/>
    <input type="hidden" name="CustName" value="<?php echo $custName;?>"/>
    <input type="hidden" name="CustPhone" value="<?php echo $custPhone;?>"/>
    <input type="hidden" name="CustEmail" value="<?php echo $custEmail;?>"/>
    <input type="hidden" name="CustIP" value="<?php echo $custIP;?>"/>
    <input type="hidden" name="IssuingBank" value="<?php if (!empty($issuingBank)) {echo $issuingBank;} ?>"/>
    <input type="hidden" name="PageTimeout" value="<?php echo $PageTimeout;?>"/>
    <input type="hidden" name="LanguageCode" value="en"/>
    <input type="hidden" name="HashValue" value="<?php echo $hashValue;?>"/>
    <!-- Added 2022-10-06 for MGate Browser Data-->
    <input type="hidden" name="BrowserData" value="<?php echo $browser_data;?>"/>

    <?PHP if (!empty($custOCP)) { ?>
        <input type="hidden" name="CustOCP" value="<?PHP echo $custOCP;?>"/>
    <?PHP } ?>
    <input type='submit' value="proceed.."/>
</form>
<script type="text/javascript">
    document.form1.submit();
</script>
...

* Please note that the Payment ID & Cust IP must be the same with the payment used in eGHL_SOP.init(..), eGHL_Hash & payment request

#####Back to top

Sending additional field

Add class name eghl-field to the input field that you wish the library send together with generated SOP token to the Redirect Page.

Example of sending customer consent for saving the card for future purchases.

#!HTML 
<input type="checkbox" id="custOCP" name="customer_consent" class="eghl-field" checked="checked" />&nbsp;I agree to have my payment details securely saved for future purchases

Card tokenization

To tokenize a card, you will need to include CustOCP=<'customer_consent' in the post body> when charging the card. Please note that you are required to get customer consent to tokenize the card. For example including a checkbox before the submit button.

Screen Shot 2018-07-12 at 8.46.54 PM.png

Include customer consent when charging the card:

#!HTML 
...
<form method="POST" name='form1' action="<?php echo $config['payment_url'];?>">
    ...
    <input type="hidden" name="CustOCP" value="<?PHP echo $custOCP;?>"/>
    ...
</form>
...

#####Back to top

Card info validation

By default, library will validate format, minimum length and maximum length of required field. However, the library will only print out to console log. Function name submitHandler will be trigger every time the library completes processing the card. Any error will be stated in the info object.

#!javascript 
eGHL_SOP.submitHandler = function (info) {
    console.log(info);
    if (typeof info == 'object') {
        for (var key in info) {
            $spanEL = jQuery("#error" + key + "");

            switch (info[key].status){
                case eGHL_SOP.responseCode.empty:{
                    $spanEL.html(key + ' is empty');
                    $spanEL.show();
                }
                break;

                case eGHL_SOP.responseCode.minLength:{
                    $spanEL.html(key + ' minimum length: ' + info[key].minLength);
                    $spanEL.show();
                }
                break;

                case eGHL_SOP.responseCode.maxLength:{
                    $spanEL.html(key + ' maximum length: ' + info[key].maxLength);
                    $spanEL.show();
                }
                break;

                case eGHL_SOP.responseCode.invalidFormat:{
                    $spanEL.html(key + ' invalid format: ' + info[key].dataType);
                    $spanEL.show();
                }
                break;

                case 0: 
                default:
                {
                    $spanEL.html('');
                    $spanEL.hide();
                }
                break;
            }
        }
    }
}

You can also add additional checking such as LUHN check in the same function. Set preventCardSubmit to true to prevent the library from submitting the form.

* Library does not validate card using LUHN algorithm

#!javascript
    ...
    default:
    {
        if (key == 'CardNo') {
            var cardNo = jQuery("form#frmPayment input[name='CardNo']").val()
            if(!luhnCheck(cardNo)){
                $spanEL.html(key + ' entered is invalid');
                $spanEL.show();
                eGHL_SOP.preventCardSubmit = true;
            } else {
                $spanEL.html('');
                $spanEL.hide();
                eGHL_SOP.preventCardSubmit = false;
            }
        } else {
            $spanEL.html('');
            $spanEL.hide();
        }
    }
    ...

Default validation: eGHL_SOP.fieldValidations

#!javascript
{
  CardExp: {
    dataType: 'N',
    minLength: 6,
    maxLength: 6
  },
  CardCVV2: {
    dataType: 'N',
    minLength: 3,
    maxLength: 4
  },
  CustEmail: {
    dataType: 'EM',
    maxLength: 60
  },
  CustPhone: {
    dataType: 'AN',
    maxLength: 25
  },
  CardHolder: {
    dataType: 'AN-name',
    maxLength: 30
  },
  CardNo: {
    dataType: 'N',
    minLength: 16,
    maxLength: 19
  },
  HashValue: {
    dataType: 'AN',
    maxLength: 100
  },
  TokenType: {
    dataType: 'AN',
    maxLength: 3
  },
  Token: {
    dataType: 'ANS',
    maxLength: 50
  }
};

Data Types: eGHL_SOP.dataTypes

#!javascript
{
  "A": {
    "regex": "^[A-Za-z]$",
    "error-title": "Invalid %s",
    "error-message": "This fields only accept alphabet"
  },
  "AN": {
    "regex": "^[A-Za-z0-9]+$",
    "error-title": "Invalid %s",
    "error-message": "This fields only accept alphabet & numeric"
  },
  "N": {
    "regex": "^\\d+$",
    "error-title": "Invalid %s",
    "error-message": "This fields only accept numeric"
  },
  "ND": {
    "regex": "^d+.+[0-9]{2}$",
    "error-title": "Invalid %s",
    "error-message": "Wrong format. Expected format 1000.00\nInvalid format: 1,000.00 or 100"
  },
  "EM": {
    "regex": "[A-Z0-9a-z!#$'*-/=?^_`{|}~.]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,5}$",
    "error-title": "Invalid %s",
    "error-message": "Valid format person@company-website.com.my\nOnly !#$'*-/=?^_`{|}~. are acceptable for the email id\ne.g person!#$'*-/=?^_`{|}~.@company-website.com.my"
  },
  "AN-name": {
    "regex": "^[A-Za-z0-9 ,.'-]+$",
    "error-title": "Invalid %s",
    "error-message": "This field only accept alphabet, numeric, the following symbol: \"[space],.'-\""
  },
  "ANS": {
    "regex": "^[A-Za-z0-9!#$'*-/=?^_`{|}~.]+$",
    "error-title": "Invalid %s",
    "error-message": "This field only accept alphabet, numeric, the following symbol: \"!#$'*-/=?^_`{|}~.\""
  }
}
#####Back to top

Recurring payments

The following fields are required to Fetch card info

  • data-date-time
  • data-card-hash

The following fields are required for recurring payment

  • data-sop-hash

#!php
// data-date-time
$SOPDateTime = time();
#!php
// data-card-hash
$eGHLHashObj = new eGHL_Hash(array(
    'ServiceID'     => $config['ServiceID'],
    'CustEmail'     => $email,
    'CustPhone'     => $phone,
    'Token'         => $postData['token'],
    'DateTime'      => $SOPDateTime
));

$cardHash = $eGHLHashObj->generateHashValueForPaymentInfo('sopcard', $config['MerchantPass']);
#!php
// data-sop-hash
$custIP = (!empty($_SERVER['HTTP_X_FORWARDED_FOR']))?$_SERVER['HTTP_X_FORWARDED_FOR']:$_SERVER['REMOTE_ADDR'];
$custIP = '127.0.0.1';

$eGHLHashObj = new eGHL_Hash(array(
    'PaymentID' => $postData['paymentId'],
    'ServiceID' => $config['ServiceID'],
    'CustIP'    => $custIP,
    'CustEmail' => $email,
    'CustPhone' => $phone,
    'Token'     => $postData['token']
));

$sop2Hash = $eGHLHashObj->generateHashValueForPaymentInfo('sop2', $config['MerchantPass']);

Assign required field to input name TokenType. Library will read these attributes when fetchCardInfo() is call.

#!html
<input type="text" name="TokenType" 
data-card-hash="92af3f609c5de23c6b1e634a350355ba7f9ced6d09d9d7d7f913bfa60350fb7e" 
data-sop-hash="4c262be9a92d6460314a09b9a83f9f771bb307170334d02dec8f2de2da3046c0" 
data-date-time="1531370964">

Displaying the Card info

Mask card number, card expiry & card holder will be fetch by calling fetchCardInfo().

The library will do the following:

  • add a drop down above CardNo.
  • hide CardNo & custOCP's parent
  • update image for cardType
  • update CardExp value

* You can also manage the card info by overriding the library function. Kindly refer the example in index.php

Example of dropdown selection generated by the library

#!html
<div class="dropdown" id="dropdown-token">
    <button class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown"><span class="dropdown-button-label" id="dropdown-button-label">XXXXXXXXXXXX2112</span> <span class="caret"></span></button>
    <ul class="dropdown-menu">
        <li class="dropdown-item" data-card-token="xfypazs0LZUfF4sPY5M5sw==" data-card-exp="012029" data-card-type="V" data-card-holder="John Doe">XXXXXXXXXXXX2112</li>
        <li class="dropdown-item">Other</li>
    </ul>
</div>

#####Back to top

Charging the card for recurring payments

Charging the card will be similar to normal payments.

Populate other payment methods

Screen Shot 2018-07-12 at 9.04.11 PM.png

The following fields are required to generate other payment methods:

  • data-date-time
  • data-opm-hash
#!php
// data-date-time
$SOPDateTime = time();

$product = [
    'name'          => 'pencil',
    'currencyCode'  => 'MYR',
    'price'         => '1.00'
];
#!PHP
$opmHash = $eGHLHashObj->generateHashValueForPaymentInfo('sopbl', $config['MerchantPass']);
#!html 
<div id="eghl-opm" 
    data-amount="<?PHP echo $product['price'];?>" 
    data-currency="<?PHP echo $product['currencyCode'];?>"
    data-opm-hash="<?PHP echo $opmHash">
    &nbsp;
</div>

Library will populate other payment methods in eghl-opm by calling function generateOtherPaymentMethod()

Preloader

The library will look for html element that contains class name eghl-card-preload when fetching the card info and eghl-opm-preload when fetching & populating other payment methods to display the loader

#!html
<div class="eghl-card-preload"></div>
<div class="eghl-opm-preload"></div>

#####Back to top

Going Live

Once all completed, eGHL would provide live credential to contact person registered in the staging account. He/she will be given the following:

  • Login credential to Merchant's Admin portal
  • Merchant ID*
  • Merchant Password*
  • Payment gateway production link
  • eGHL JS production link

* Please note that Merchant ID & Merchant Password are not the login credential to Admin portal

For more info, please contact us

#####Back to top

Updated