Banklink (Pangalink) php usage

This article was published in Russian on my blog midbar.livejournal.com. Now, I decided to translate it to English and publish here.
This tutorial was written, due to the reason that there was no much information about Banklink (Pangalink) in Russian and English. So, here the document, how to set up BankLink connection, what it is in general and what are the specifics.

1. What is that?
In Baltic states, number of online shops is growing pretty fast and logical question appears: how to pay money?
There are lot of variants how to do it, each has own pluses and minuses:
а) Payment on delivery. Not the most convenient variant. You should deal with cash. It is possible that client will refuse to buy the product, when it is already delivered. This variant worked in Latvia about 10 years ago, I bought in shops this way.
б) Payment, using credit card. Not everybody has such credit cards. Credit card fraud lowers number of credit card paying clients.
в) Payment via PayPal system. It is also not that convenient and takes time and money for transferring funds from Paypal.
г) Payment via local bank, through the system of bank authorization. And it is that exact BankLink. There are also disadvantages: circle of clients is limited by clients of the banks, with which the agreement will be signed.
Ideally, you should provide as many as possible payment methods for your online shop.

2. Algorithm of work.
Internet shop Client inputs proper sum of money on the site of the Seller.
Seller offers payment via BankLink of the bank, which is more suitable for the Client.
Client selects proper bank and data about payment is sent to the bank.
Bank offers to authorize him/her-self and submit the payment.
After payment acceptance, bank returns a response to the site of the Seller.

3. Source Code.
All required scripts for work I took from here. Huge respect and thanks to Mihkel-Mikelis Putrinsh for the work he did.
3.1 The form of sending data to bank.
All data is sent by POST method through secure HTTPS page. Here is the page, from which we send data to the bank.

include( 'BankLink.class.php' ); // here is the class of functions, required for current script. Is taken from http://comeback.webhost.ee/pangalink/BankLink.class.php.txt
$BL =& new BankLink();

$VK_a['VK_SERVICE'] = '1002'; // ID of the service we request. In our case it is - "Invoice payment"
$VK_a['VK_VERSION'] = '008'; // ID of security type. Nowadays, all banks use 008, previously they were 001, 003, 007
$VK_a['VK_SND_ID'] = 'KAUPMEES'; // ID of the Seller. Is obtained in bank while signing the agreement.
$VK_a['VK_STAMP'] = $TehinguId; // Session ID. It is optional and its value can be random - more useful for the Seller to track payments.
$VK_a['VK_AMOUNT'] = $TehinguSumma; // Payment sum.
$VK_a['VK_CURR'] = 'EUR'; // Payment currency.
$VK_a['VK_REF'] = ''; // reference link. Some companies use it for dividing payments on one bank account. If none, leave blank.
$VK_a['VK_MSG'] = 'Payment via BankLink from Seller'; // Payment message.
$VK_a['VK_MAC'] = $BL->createSignature( $VK_a ); // Digital signature. See further how to generate it.
$VK_a['VK_RETURN'] = 'vk_return.php'; // page, there bank will return in case of successful payment.
$VK_a['VK_CANCEL'] = 'vk_return.php'; // page, there bank will return in case of payment cancellation. If it is the same page as  VK_RETURN, you can leave it blank.
$VK_a['VK_LANG'] = 'EST'; // Preferred Invoice language

echo( '
' ); // Here we create the form for sending data. In "action" field we set special address of internet bank, which will get our data. foreach( $VK_a as $VK_name => $VK_value ) { echo( '' ); } echo( '' ); echo( '
' );

3.2. The form of getting data from the bank.
All data is got from bank by POST method through HTTPS access. Let’s look on our page vk_return.php.

include( 'BankLink.class.php' ); //  here is the class of functions, required for current script. Is taken from http://comeback.webhost.ee/pangalink/BankLink.class.php.txt
$BL = new BankLink();

$sig_result = $BL->verifySignature( $_POST ); //  Digital signature verification. See further how to generate it.

switch ( $sig_result )
{
   case 1:
      switch( $_POST['VK_SERVICE'] ) // Service code from bank.
      {
         case '1101': // Payment is successful
            echo( 'Payment is successful!' ); 
            break;
            
         case '1901': // Payment was aborted by Client
            echo( 'Payment was aborted!' ); 
            break;
            
         default: // in the case of other code (low chance, but shit happens)
            echo( 'Bullshit' );
            break;
      }
      break;
      
   case 0: // In case of incorrect signature
      echo( 'Wrong Bank data' );
      break;
      
   default: // in the case of other code (low chance, but shit happens)
      echo( 'Bullshit' );
      break;
}

4. Security.
Security is one of the most important parts of all this process. How does it work?
4.1 Seller send data to bank.
In #3.1 we sent variable VK_MAC. It is generated in the following way:
Value of the function МАС008 is counted by the following algorithm of public key RSA. Length of blank fields is also taken in consideration – «000».
MAC008(x1,x2,…,xn) := RSA( SHA-1(p(x1 )|| x1|| p(x2 )|| x2 || … ||p( xn )||xn),d,n)
where:

|| – is an action on string addition
x1, x2, …, xn – are parameters of the request
p – function of parameter’s length.
d – hidden exponent of RSA
n – RSA module

Signature counting is made according with standard PKCS1 (RFC 2437).
It is an extract from SwedBank site.
In our case it looks as following:
There are lot of methods how to get a signature. One can be seen in this script. I will describe another further.

$mac_pre = '';

$reversed = array_reverse($VK_a);

foreach ($reversed as $name=>$value) {// In this loop we create the set of data for the signature. It will contain reversed array data as set up earlier in the array VK_A.
	$value_len = strlen($value);
	if (strlen($value_len) < 3) {
		while (strlen($value_len) < 3) {
		$value_len = '0'.$value_len;
		}
	}
	$mac_pre = $value_len.$value.$mac_pre; 
}

$fp = fopen('privkey.pem', 'r'); // Read the key file
$private_key = fread($fp, 8192);
fclose($fp);
$private_key_id = openssl_get_privatekey($private_key, 'password'); //Get ID number of the key

openssl_sign($mac_pre, $mac, $private_key_id); //Create the signature, signed by our key.
$mac = base64_encode($mac); base64 coding

Created signature is send along with other data to Bank. during signing the agreement, Seller sent public key to Bank. Band will recognize correctness of your signature using this key.

4.2 In #3.2 we verified correctness of Bank's signature. Bank issues it's public key for this purpose.
Let's see an example, how it works.

var $keyDir = "/webroot/osslkeys"; // location of public keys. 
function verifySignature( $VK_a )
   {
      $VK_MAC = $VK_a['VK_MAC']; // getting signature from Bank
      $signature = base64_decode( $VK_MAC ); //decode from base64

      switch( $VK_a['VK_SERVICE'] )
      {
         case '1101': // In case of success we get code 1101
            $data =
               $this->_padIt( $VK_a['VK_SERVICE'] ) . // Service code
               $this->_padIt( $VK_a['VK_VERSION'] ) . // ID of security type
               $this->_padIt( $VK_a['VK_SND_ID'] ) . // Bank ID (E.g. SwedBank has HP)
               $this->_padIt( $VK_a['VK_REC_ID'] ) . // Seller ID (it is our ID of Seller, we sent to Bank)
               $this->_padIt( $VK_a['VK_STAMP'] ) . // Session ID (it is Bank session - not Seller's)
               $this->_padIt( $VK_a['VK_T_NO'] ) . // Payment number
               $this->_padIt( $VK_a['VK_AMOUNT'] ) . // Sum paid
               $this->_padIt( $VK_a['VK_CURR'] ) . // currency
               $this->_padIt( $VK_a['VK_REC_ACC'] ) . // Seller bank account number
               $this->_padIt( $VK_a['VK_REC_NAME'] ) . // Name of Seller
               $this->_padIt( $VK_a['VK_SND_ACC'] ) . // Client bank account number
               $this->_padIt( $VK_a['VK_SND_NAME'] ) . // Name of client
               $this->_padIt( $VK_a['VK_REF'] ) . // Reference number
               $this->_padIt( $VK_a['VK_MSG'] ) . // Message
               $this->_padIt( $VK_a['VK_T_DATE'] );  // Date of payment
            break;
         case '1901': // In case of failure we get the code 1901
            $data =
               $this->_padIt( $VK_a['VK_SERVICE'] ) . // Service code
               $this->_padIt( $VK_a['VK_VERSION'] ) . // ID of security type
               $this->_padIt( $VK_a['VK_SND_ID'] ) . // Bank ID (E.g. SwedBank has HP)
               $this->_padIt( $VK_a['VK_REC_ID'] ) . // Seller ID (it is our ID of Seller, we sent to Bank)
               $this->_padIt( $VK_a['VK_STAMP'] ) . // Session ID (it is Bank session - not Seller's)
               $this->_padIt( $VK_a['VK_REF'] ) . // Reference number
               $this->_padIt( $VK_a['VK_MSG'] ); // Message
            break;
      }

      $cert = $this->_readPubKey( $VK_a['VK_SND_ID'] ); // Reading signature key
      $pubkey = openssl_get_publickey( $cert ); // getting ID number of the key.

      $out = openssl_verify( $data, $signature, $pubkey ); // Verification of signature

      openssl_free_key( $pubkey );
      return $out;
   }

function _readPubKey( $VK_SND_ID = '' )
   {
      $key_file = $this->keyDir . "/$VK_SND_ID/pubkey.pem"; // I placed public keys with common name in directories with the names of banks' ID. In such way I solved the problem with various bank support.
      $fp = fopen( $key_file, "r" );
      $cert = fread( $fp, 8192 );
      fclose( $fp );

      return $cert;
   }

5. Here you are.
These are all specifics, I wanted to tell you.
No animal was harmed in the process of investigation of the functionality.
I used information from the following sites:
HansaPank - technical description.
SEB EESTI Ühispank - technical description.
Sampo Pank - bank link.
Mihkel-Mikelis Putrinsh - scripts.