Update: I have a new version of this which uses the SOAP method and integrates 3D secure as well. If you are interested in this, leave a comment and I can send you a copy, if enough people are interested I’ll post it. Cheers.
Having recently integrated the commidea payment gateway into an online shop, I thought I would share the class in which I created to do this. I searched high and wide to find a class that was already out there but could not find one. So hopefully this will prove useful to some of you.
There are a few ways you can use the commidea gateway, you can use SOAP for example. This example uses Curl, so make sure that you have Curl installed on your server.
Please let me know if you find this class useful.
/* Class to deal with commidea payments
Developed by Ian Jenkins - www.Jenkins-Web.co.uk
Usage:
// Create a new object passing card details into the constructor. This should be an array of details such as type, number etc.
$payment_obj = new CommideaPayment($card_details);
// Validate the card
$error = $payment_obj->validateCard();
// Ok, if we have no errors we can move on
if (empty($error)) {
// Set the order id/reference
$payment_obj->setOrderID($order_id);
// Set the total for the order
$payment_obj->setOrderTotal($order_total);
// Make the payment
$payment_response = $payment_obj->makePayment();
// The response will be either true or false, depending on whether the payment was successful or not
if ($payment_response) {
// Payment was successful, do what you need to do, probably update order and send the user to a success page.
// To get the transaction id you can call this method.
$payment_obj->getTransactionID()
// If you are going to redirect the user to a success message, you will probably want something like:
header("Location: /success/");
exit;
} else {
// Payment was unsuccessful, do what you need to do, probably display an error message and give the user
// a chance to enter alternative payment details.
$error = 'Sorry, your payment was unsuccessful, please try alternative payment details.';
}
*/
class CommideaPayment {
// Define our commidea params
// Change if not in testing mode
private $post_url = 'https://testing.commidea.com';
// Your account id
private $account_id = 'XXXXXXXXX';
// Your account number
private $account_number = 'XXX';
// Your GUI ID
private $gui_id = 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX';
// Change to N to take payment as well as authorise
private $auth_only = 'Y';
public $card = array();
public $order_id;
public $order_total;
public $transaction_id;
/* Take in an array of card details, should have the following elements (elements with * are required):
$card['type']*
$card['number']*
$card['security_code']*
$card['expiry_month']*
$card['expiry_year']*
$card['start_month']
$card['start_year']
$card['issue']
*/
function __construct($card) {
if (is_array($card)) {
$this->card = $card;
} else {
die('Card details must be an array');
}
}
// Some setters
function setOrderID($order_id) {
$this->order_id = $order_id;
}
function setOrderTotal($order_total) {
$this->order_total = $order_total;
}
// Some getters
function getTransactionID() {
return $this->transaction_id;
}
function getCardType() {
return $this->card['type'];
}
function getCardRef() {
// Return the card number with stars for all the digits except the last 4.
return str_pad('', strlen($this->card['number'])-4, 'X').''.substr($this->card['number'], -4);
}
/* Validate our card.
- Ensure card number is long enough and matches the Luhn formula
- Ensure they have not entered an expired card!
- Ensure the security code is only 3 digits
*/
function validateCard() {
$err = array();
// Required fields array fieldname=>label
$required_fields = array('type'=>'Credit card type',
'number'=>'Credit card number',
'security_code'=>'Security code',
'expiry_month'=>'Expiry month',
'expiry_year'=>'Expiry year');
foreach ($required_fields as $field=>$label) {
// Have they filled in a credit card type?
if (empty($this->card[$field])) {
$error[] = '<strong>'.$label.'</strong> is missing.';
}
}
/* Lets check the credit card number now
- before we do anything, strip whitespace from the card number */
$card_number = str_replace(' ','',$this->card['number']);
if (!empty($card_number) && $card_number < 13) {
$error[] = '<strong>Cred card number</strong> is not long enough.';
}
/* Ensure the card number is valid using the Luhn formula */
if (!empty($card_number)) {
$is_valid = $this->_validateCreditCardNumber($card_number);
if (!$is_valid) {
$error[] = '<strong>Invalid card number</strong>, check your card.';
}
}
// Ensure card has not expired!
if (($this->card['expiry_month'] < date(m)) && ($this->card['expiry_year'] == date(Y))) {
$error[] = 'Credit card has <strong>expired</strong>';
}
// Ensure security code is numeric and only 3 digits
if ($this->card['securitycode']) {
if (!is_numeric($this->card['securitycode'])) {
$error[] = '<strong>Security code</strong> is invalid.';
}
if (strlen($this->card['securitycode']) != 3) {
$error[] = '<strong>Security code</strong> is invalid.';
}
}
// If a maestro card is used then make sure they have also provided an issue number
if ($this->card['type'] == 'Maestro' && empty($this->card['issue'])) {
$error[] = '<strong>Issue No.</strong> is missing.';
}
return $error;
}
/* Function to attempt to make a payment. Will post the data to commidea using curl and see what response we get */
function makePayment() {
// First lets get our data
$data = $this->_buildData();
// Get our response from commidea
$response = $this->_postData('cctransaction', '/cctransaction.asmx/CardTxn', 'CommideaTransaction', $data['cctransaction']['#data']);
// Assume payment was rejected unless told otherwise
$result = "REJECTED";
if (!isset($response["xml_array"]["TransactionResult"]) ) {
die('Curl error');
} else {
$result = $response["xml_array"]["TransactionResult"];
}
// Approved - Success!
if ($result == 'ACCEPTED') {
// Capture Transaction ID
$transaction_id = $response["xml_array"]["TransactionID"];
$this->transaction_id = $transaction_id;
return true;
} elseif ($result == 'REJECTED') {
// Payment was rejected
// Capture Transaction ID
$transaction_id = $response["xml_array"]["TransactionID"];
$this->transaction_id = $transaction_id;
return false;
} elseif ($result == '0') {
// Something went wrong...
// Capture Transaction ID
$transaction_id = $response["xml_array"]["TransactionID"];
$this->transaction_id = $transaction_id;
return false;
}
}
// Function to build the data we require for the commidea transaction
private function _buildData() {
$data = array(
'payerauth' => array(
'#page' => "/payerauth.asmx/CardCheck",
'#xmlroot' => 'CommideaPayerAuthCheck',
),
'cctransaction' => array(
'#page' => "/cctransaction.asmx/CardTxn",
'#xmlroot' => 'CommideaTransaction',
'#data' => array(
'Merchant' => array(
'GUID' => $this->gui_id,
'MerchantData' => $this->order_id,
),
'TRecord' => array(
'AccountID' => $this->account_id,
'AccountNumber' => $this->account_number,
'TxnType' => '01',
'Pan' => $this->card['number'],
'ExpiryDate' => ($this->card['expiry_month']).(substr($this->card['expiry_year'],2,4)),
'TxnValue' => $this->order_total,
'CSC' => ($this->card['security_code']),
//'AVS' => '',
'Issue' => ($this->card['issue']),
'StartDate' => ($this->card['start_month']).(substr($this->card['start_year'],2,4)),
'Reference' => $this->order_id,
'CNP' => 'Y',
'ECom' => 'Y',
'AuthOnly' => $this->auth_only
),
),
),
'confirmtxn' => array(
'#page' => "/cctransaction.asmx/ConfirmTransaction",
'#xmlroot' => 'CommideaConfirmation',
),
'serverstatus' => array(
'#page' => "/serverstatus.asmx/GetServerStatus",
'#xmlroot' => NULL,
),
);
return $data;
}
/* Using curl, lets post the data to commidea and return the response */
private function _postData($service, $page, $xmlRoot = NULL, $xml = array()) {
//Validate the service
if (!in_array($service, array("payerauth", "cctransaction", "confirmtxn", "serverstatus"))) {
die("Invalid Service");
}
//If there IS an XML root, grab the XML using the recursive function below and create an XML output string
if ($xmlRoot && is_array($xml)) {
$output = '<?xml version="1.0" ?>' . "\n";
$output .= '<'.$xmlRoot.' xmlns="https://www.commidea.webservices.com">' . "\n";
$output .= $this->_getXML($xml);
$output .= '</'.$xmlRoot.'>';
} else {
//Otherwise, output is blank.
$output = '';
}
//Set the headers as an array
$header[] = "Host: testing.commidea.com";
$header[] = "Content-type: application/x-www-form-urlencoded";
//If the output is not empty, set the xmlStream
if (empty($output)) {
$header[] = "Content-length: 0 \r\n";
} else {
$header[] = "Content-length: ".strlen('xmlStream=' . $output) . "\r\n";
$header[] = "xmlStream=" . $output;
}
// Create a curl object
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $this->post_url.$page);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
// Execute the curl and save the result
$returned = curl_exec($ch);
// Lets tidy up the xml a bit
if (curl_errno($ch) == 0) {
$p = xml_parser_create();
xml_parser_set_option($p, XML_OPTION_CASE_FOLDING, 0);
xml_parse_into_struct($p, $returned, $returnedxml, $returnedxmlindex);
xml_parser_free($p);
$temp = array();
foreach ($returnedxml as $key => $value) {
if ($value['type'] == 'complete') {
$temp[$value['tag']] = (isset($value['value']) ? $value['value'] : '');
}
}
$returnedxml = $temp;
unset($temp);
} else {
$returnedxml = array();
}
$res = array('curl_error' => curl_errno($ch), 'xml_array' => $returnedxml);
return $res;
}
// Validate credit cards using the LUHN formula
private function _validateCreditCardNumber($cardnumber) {
/* Credit card LUHN checker - coded '05 shaman - www.planzero.org *
* This code has been released into the public domain, however please *
* give credit to the original author where possible. */
$cardnumber=preg_replace("/\D|\s/", "", $cardnumber); # strip any non-digits
$cardlength=strlen($cardnumber);
$parity=$cardlength % 2;
$sum=0;
for ($i=0; $i<$cardlength; $i++) {
$digit=$cardnumber[$i];
if ($i%2==$parity) $digit=$digit*2;
if ($digit>9) $digit=$digit-9;
$sum=$sum+$digit;
}
$valid = ($sum%10==0);
return $valid;
}
/* Recursive XML creator.
- Pass an array into $data. It loops around each element in the array. If $value is not an array,
- It prints it out using the $key as the element tag.
- If $value IS an array, the function calls itself with the $value array as the new tree with an incremented depth.
- The $depth is used to tab indenting to make the XML human friendly. Option in the future to add an arg to make
code computer friendly by making it all one line.
- Returns the xml as a string.
*/
private function _getXML($data = array(), $depth = 1) {
$output = "";
foreach ($data as $key => $value) {
if (is_array($value)) {
$output .= str_repeat(" ", $depth) . "<$key>\n";
$output .= $this->_getXML($value, $depth+1);
$output .= str_repeat(" ", $depth) . "</$key>\n";
} elseif (empty($value)) {
$output .= str_repeat(" ", $depth) . "<$key />\n";
} else {
$output .= str_repeat(" ", $depth) . "<$key>$value</$key>\n";
}
}
return $output;
}
}