php
No Comments HSBC PHP CPI Integration Without Installing Software
This is an old post I wrote 3 years ago, I’ve put it up here as it might be helpful to someone!
Recently, I’ve been working on integrating HSBC’s CPI payment system in to a website. After working with Protx, PayPal, and World Pay for many years, it couldn’t be that difficult, could it?
The main issue is documentation. The “guide” provided by HSBC, is not much more use that toilet paper. After spending many an hour scouring Google, installing software on to my server, bashing my keyboard, and being continually bounced away from HSBC’s server, I’ve finally got it working.
The HSBC documentation insists you need to install their Order Hash generator software on your server for it to work, this PHP class does away with that. All you need are:
• Your StorefrontId – this is your 8 digit ID, this will need to be prefixed with your 2-letter country code and have the 3-letter currency code appended to the end. E.G. UK12345678GBP
• Your encryption key this is a 32 character BASE64 encoded string unique to you.
• MHASH & MCRYPT available to PHP.
• SSL up and running on your server, HSBC will reject any requests that don’t come from an SSL page.
You can’t go any further without these!
Here’s the class I use:
<?php
class HSBC{
private $cpiKey ='zEeWQNKelqPE2DRFueuDq1QrASjux2lM'; // YOUR own hash
private $_fldif;
private $a;
function __construct(){
$s = 'KmJTwzVPwjoxQdWJb1BxbuhBSa2RuM05+/aUdgYoGdFWWf04CKIQTxtxLeKCp+5J';
$s1 = 'y8YhmjsAoMUW9RxfXBSos0A6LwGd+5pXv/MRAKCYFLG';
$s2 = 'BqRkPAG8DFFAdeN5SMAArktCYuUGXi2q88EDoOs3Ykw0k';
$this->a = chr(98).chr(84).chr(120).chr(114).chr(66).chr(87).chr(80).chr(112);
$this->_fldif = $this->initKey($s, $s1, $s2);
$this->_fldif = substr($this->_fldif,0,44);
}
public function GenerateHash($vector){
$vector1 = array();
for($i = 0; $i < sizeof($vector); $i++)
{
$flag = false;
$s2 = $vector[$i];
$vSize= sizeof($vector1);
for($k = 0; $k < $vSize && !$flag; $k++)
{
$s4 = $vector1[$k];
$l = strcmp($s2, $s4);
if($l <= 0)
{
array_push($vector1, '');
for($r = sizeof($vector1)-2; $r >= $k; $r--)
$vector1[$r+1] = $vector1[$r];
$vector1[$k] = $s2;
$flag = true;
}
}
if(!$flag) array_push($vector1, $s2);
}
$s1 = '';
for($j = 0; $j < sizeof($vector1); $j++)
{
$s3 = $vector1[$j];
$s1 = $s1 . $s3;
}
$abyte0 = $this->decryptToBinary($this->cpiKey);
$ret = base64_encode(mhash(MHASH_SHA1, $s1.$abyte0, $abyte0));
return $ret;
}
private function rot13(&$abyte0)
{
for($i = 0; $i < strlen($abyte0); $i++)
{
$c = ord($abyte0[$i]);
if($c >= ord('a') && $c <= ord('m') || $c >= ord('A') && $c <= ord('M'))
$abyte0[$i] = chr($c + 13);
else
if($c >= ord('n') && $c <= ord('z') || $c >= ord('N') && $c <= ord('Z'))
$abyte0[$i] = chr($c - 13);
}
}
private function encode($abyte0) {
return base64_encode($abyte0);
}
private function decode($s) {
return base64_decode($s);
}
private function encrypt($abyte0, $abyte1)
{
$td = mcrypt_module_open (MCRYPT_DES, '', MCRYPT_MODE_CBC, '');
$iv = $this->a;
$ks = mcrypt_enc_get_key_size ($td);
$key = substr($abyte1, 0, $ks);
/* Intialize encryption */
mcrypt_generic_init ($td, $key, $iv);
return mcrypt_generic ($td, $abyte0);
}
private function decrypt($abyte0, $abyte1)
{
$td = mcrypt_module_open (MCRYPT_DES, '', MCRYPT_MODE_CBC, '');
$iv = $this->a;
$ks = mcrypt_enc_get_key_size ($td);
$key = substr($abyte1, 0, $ks);
/* Intialize encryption */
mcrypt_generic_init ($td, $key, $iv);
$ret = mdecrypt_generic($td, $abyte0);
while($ret[strlen($ret)-1] == "\4" && strlen($ret) > 0){
$ret=substr($ret, 0, strlen($ret)-1);
}
return $ret;
}
private function encryptEncode($abyte0, $abyte1)
{
return $this->encode($this->encrypt($abyte0, $abyte1));
}
private function decodeDecrypt($s, $abyte0)
{
return $this->decrypt($this->decode($s), $abyte0);
}
private function initKey($s, $s1, $s2)
{
$abyte0 = chr(0);
$abyte1 = $s1;
$abyte2 = $s2;
$byte0 = 4;
$i = $byte0 + 9;
$j = rand(0, 30);
$j = 0;
if($j > $byte0 * $i) $j -= $byte0 * $i;
$k = 0;
for($l = 0; $l < $byte0 * $i; $l++)
{
switch(($j + $l) % $i)
{
case 0: // '\0'
if($k == 2)
{
$abyte0 = $this->encrypt($abyte1, $abyte2);
$k++;
}
break;
case 1: // '\001'
if($k == 1)
{
$abyte2 = $abyte1;
$this->rot13($abyte2);
$k++;
}
break;
case 2: // '\002'
if($k == 0)
{
$i1 = 48 + (ord($abyte1[0]) + 10) % 10;
$abyte1[0] = chr($i1);
$k++;
}
break;
case 3: // '\003'
if($k == 3) $k++;
break;
case 5: // '\005'
case 7: // '\007'
case 10: // '\n'
if($k < 2) $abyte0 = $this->encrypt($abyte1, $abyte2);
break;
case 4: // '\004'
case 6: // '\006'
case 8: // '\b'
case 9: // '\t'
default:
break;
}
}
return $this->decodeDecrypt($s, $abyte0);
}
public function decryptToBinary($s)
{
if ($s == NULL)
return NULL;
else
return $this->decodeDecrypt($s, $this->_fldif);
}
}
?>
With this in place, all you need to do, to get your OrderHash, is pass the function GenerateHash() an array of variables in a set order.
array(
$successURL,
$failureURL,
$mode,
$orderDescription,
$orderID,
$orderTotal,
$currencyCode,
$storeID,
$timestamp,
$transactionType,
$userID
);
Description of the variables can be found in the PDF documentation. If you need to submit the delivery/billing address details, then all fields must be POSTed (even if blank). Pay attention to the character limits for each field, as well!
To generate the timestamp, use the PHP function time() in your script and put the result in to a variable as you’ll need to use the same timestamp when generating the hash and in your HTML form.
$timestamp = time()*1000;
You may need to cast it as a string depending on your server.
Next step is to set up the HTML form which is used to submit the details to HSBC. Below is a sample form remember to use your OWN data for every field bar the form “action”:
<form action="https://www.cpi.hsbc.com/servlet" method="post"> <input type="hidden" name="CpiDirectResultUrl" value="https://www.example.com/success.php" /> <input type="hidden" name="CpiReturnUrl" value="https://www.example.com/failure.php" /> <input type="hidden" name="Mode" value="P" /> <input type="hidden" name="OrderDesc" value="New order for www.davebarnes.co.uk" /> <input type="hidden" name="OrderHash" value="<?= $orderHash ?>" /> <input type="hidden" name="OrderId" value="12345" /> <input type="hidden" name="PurchaseAmount" value="1999" /> <input type="hidden" name="PurchaseCurrency" value="826" /> <input type="hidden" name="StorefrontId" value="UK12345678GBP" /> <input type="hidden" name="TimeStamp" value="1120066806" /> <input type="hidden" name="TransactionType" value="Capture" /> <input type="hidden" name="UserId" value="7" /> <input type="submit" value="Pay!" /> </form>
Both Result URLs will need to be HTTPS and the purchase amount must be without decimals.
Once it’s all up and running you should then be able to send payments through to HSBC, and get an appropriate reply. It’s always worth checking the OrderHash returned matches the hash of all the returned POST variables (bar $_POST['OrderHash'], obviously). For example:
if($hsbc->GenerateHash(array($_POST['CpiResultCode'],$_POST['PurchaseDate'],.......)) == $_POST['OrderHash']){
// safe to carry on
}else{
// Someone's tampered with the data!
}
You must also check the returned code is 0 (zero) for successful transactions.
Hope that helps someone who’s going through same pain! Leave any comments if you have any suggestions/additions. I haven’t tested any of the above code so there may be mistakes, parts you’ll need to adapt but this should point you in the right direction.