Alertpay integration with CakePHP
Recently I implemented a payment module using Alertpay. If you searched for this kind of code you already know what Alertpay is. For those of you who don’t, Alertpay is a payment processor similar to Paypal.
To implement this type of payment you must register for an account and activate the IPN option from the control panel. Upon IPN activation you will be given a secret code that you will use to validate your transactions. Also, when you will activate the IPN option, you will be asked to enter the IPN alert URL but we’ll get to that later.
Before we get started, I want to list the steps we will take to achieve our set goal:
- add the configuration details to our core.php file;
- create a payments database;
- create the Payment model for the database;
- create the PaymentsController controller;
- create our PaymentGatewayComponent component;
- create the PaymentButtonsHelper helper to generate the payment button;
- create a basic view for our button;
- TEST IT!
Now that we have a basic road map, let’s get started.
Open your core.php file and paste the following code just after the debug settings (not mandatory just handy):
/** * CakePHP Payments options * * AlertPay information * * Variables: * 1: ap_securitycode - generated in the Alertpay IPN settings * 2: ap_merchant - merchant name set in the Alertpay business profile */ Configure::write('Alertpay.ap_securitycode', 'secretcodefromalertpay'); Configure::write('Alertpay.ap_merchant', 'merchant@email.com');
Now import the following database table using phpmyadmin or other similar tool:
CREATE TABLE IF NOT EXISTS `payments` ( `id` int(11) NOT NULL AUTO_INCREMENT, `product_id` int(11) NOT NULL, `product_code` varchar(255) NOT NULL, `transaction_service` varchar(25) NOT NULL, `transaction_number` varchar(50) NOT NULL, `transaction_currency` varchar(3) NOT NULL, `transaction_amount` decimal(5,2) NOT NULL, `transaction_status` varchar(15) NOT NULL, `transaction_date` datetime NOT NULL, `buyer_email` varchar(50) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
The product_id field is used for a belongsTo association with a products table.
The Payment model class is next. Let’s see it:
<?php class Payment extends AppModel { var $name = 'Payment'; var $belongsTo = array( 'Product' => array( 'className' => 'Product', 'foreignKey' => 'product_id' ) ); } ?>
The controller code is below.
<?php class PaymentsController extends AppController { var $name = 'Payments'; var $components = array('PaymentGateway'); var $helpers = array('PaymentButtons'); function apipn() { $transactionData = $this->PaymentGateway->parseAPData($_REQUEST); if(!is_array($transactionData) && $transactionData != 'Success') { // handle a bad transaction here } if($this->logPayment($transactionData['Payment'])) { // update the status of the order here } $this->render(null); } function logPayment($data = array()) { if($this->Payment->save($data)) return true; return false; } } ?>
Now that we have the basic elements in place, it’s time to handle the data that comes from Alertpay.
I decided to handle the data in a component to simplify the controller and because I think this is the normal CakePHP way. The code for the component is next.
<?php class PaymentGatewayComponent extends Object { var $dbFields = array( 'item_code', 'transaction_service', 'transaction_number', 'transaction_currency', 'transaction_amount', 'transaction_status', 'transaction_date', 'buyer_email' ); var $alertpayFields = array( 'ap_securitycode', 'ap_custemailaddress', 'ap_referencenumber', 'ap_status', 'ap_itemcode', 'ap_amount', 'ap_currency', 'ap_test' ); function parseAPData($data = null) { foreach ($data as $key => $value) { if(in_array($key, $this->alertpayFields)) { $this->alertpayFields[$key] = $value; } } if(Configure::read('Alertpay.ap_securitycode') == $this->alertpayFields['ap_securitycode']) { if($this->alertpayFields['ap_status'] == 'Success') { $data = $this->decodeAdData($this->alertpayFields['ap_itemcode']); $this->dbFields['Payment']['transaction_status'] = $this->alertpayFields['ap_status']; $this->dbFields['Payment']['transaction_service'] = 'Alertpay'; $this->dbFields['Payment']['product_id'] = $data[2]; $this->dbFields['Payment']['item_code'] = $this->alertpayFields['ap_itemcode']; $this->dbFields['Payment']['transaction_number'] = $this->alertpayFields['ap_referencenumber']; $this->dbFields['Payment']['transaction_currency'] = $this->alertpayFields['ap_currency']; $this->dbFields['Payment']['transaction_amount'] = $this->alertpayFields['ap_amount']; $this->dbFields['Payment']['transaction_status'] = $this->alertpayFields['ap_status']; $this->dbFields['Payment']['transaction_date'] = date('Y-m-d H:i:s'); $this->dbFields['Payment']['buyer_email'] = $this->alertpayFields['ap_custemailaddress']; $this->dbFields['PaymentOption']['days'] = $data[1]; return $this->dbFields; } } else { return $this->alertpayFields['ap_status']; } } // the encoded item_code as the following structure: sitename-days-productid function decodeAdData($adData = null) { $rawData = base64_decode($adData); $data = explode('-', $rawData); return $data; } } ?>
You notice that I encoded the item code as a supplementary security measure. Let me describe the work flow of the component. When the apipn() action is called it calls the parseAPData() passing the parameters received from Alertpay. I must say that there are many more parameters sent but I only needed the ones in the $alertpayFields array. Feel free to read the Alertpay documentation and add the parameters you need to the array. So, the first thing I do is to populate the $alertpayFields array with the data from Alertpay and then I validate it using the security code I mentioned before. If everything is OK so far, we check the status received. If it’s ‘Success‘ we decode the ap_itemcode field to extract the product id and options (separated by ‘-’) and populate the $dbFields array. We then simply return the conveniently formatted $dbFields array to our controller to be inserted in the payments table and to update the sale status.
To display the button at the checkout page I made a helper to preserve the CakePHP way of doing things. The code is below:
<?php class PaymentButtonsHelper extends AppHelper { var $helpers = array('Html', 'Form'); function generateApButton($title = null, $options = array()) { $retval = "<form action='https://www.alertpay.com/PayProcess.aspx' method='post'>"; $retval .= $this->__hiddenNameValue('ap_merchant', Configure::read('Alertpay.ap_merchant')); $retval .= $this->__hiddenNameValue('ap_purchasetype', 'service'); foreach($options as $name => $value){ $retval .= $this->__hiddenNameValue($name, $value); } $retval .= $this->__submitButton($title, '/img/alertpay-button.gif'); return $retval; } function __hiddenNameValue($name, $value){ return '<input type="hidden" name="'.$name.'" value="'.$value.'" />'; } function __submitButton($text, $image){ return $this->Form->end(array('type' => 'image', 'src' => $image, 'title' => $text)); } } ?>
To use the helper you must pass the required parameters to the helper using an array of options. An example of how to do this in the checkout view is below:
$itemcode = base64_encode('sitename-'.$payment['PaymentOption']['days'] .'-'.$product['Product']['id']); echo $paymentButtons->generateApButton('Pay with AlertPay', array( 'ap_custemailaddress' => $client['Client']['email'], 'ap_amount' => $payment['PaymentOption']['price'], 'ap_itemname' => 'The best product', 'ap_currency' => 'EUR', 'ap_quantity' => 1, 'ap_itemcode' => $itemcode, 'ap_returnurl' => 'http://www.yourwebsite.com/success-page', 'ap_cancelurl' => 'http://www.yoursite.com/cancel-page', ));
Make sure to activate the test mode and set the IPN alert URL in Alertpay to the apipn action like so http://www.yoursite.com/payments/apipn.
Well, that about covers it. I hope you find this code helpful and feel free to comment on it and give me feedback.
Happy baking.
Related posts
- How to switch between databases in CakePHP on the fly
- How to paginate search results in CakePHP
- Custom URL for CakePHP Paginator helper
- How to bake on Ubuntu using cakePHP
![[del.icio.us]](http://insanityville.com/wp-content/plugins/bookmarkify/delicious.png)
![[Digg]](http://insanityville.com/wp-content/plugins/bookmarkify/digg.png)
![[diigo]](http://insanityville.com/wp-content/plugins/bookmarkify/diigo.png)
![[Facebook]](http://insanityville.com/wp-content/plugins/bookmarkify/facebook.png)
![[Friendsite]](http://insanityville.com/wp-content/plugins/bookmarkify/friendsite.png)
![[LinkedIn]](http://insanityville.com/wp-content/plugins/bookmarkify/linkedin.png)
![[Reddit]](http://insanityville.com/wp-content/plugins/bookmarkify/reddit.png)
![[Simpy]](http://insanityville.com/wp-content/plugins/bookmarkify/simpy.png)
![[Slashdot]](http://insanityville.com/wp-content/plugins/bookmarkify/slashdot.png)
![[StumbleUpon]](http://insanityville.com/wp-content/plugins/bookmarkify/stumbleupon.png)
![[Twitter]](http://insanityville.com/wp-content/plugins/bookmarkify/twitter.png)
![[Email]](http://insanityville.com/wp-content/plugins/bookmarkify/email.png)







