Dave

01 Oct, 2008

HSBC CPI Integration

Posted by: Dave In: PHP

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 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.

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!:

    <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 in pence!

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!
}

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.

Tags: , ,

No Responses to "HSBC CPI Integration"

Comment Form

You must be logged in to post a comment.

Categories

Archives

About

This is Dave's blog.