Integration of new payment gateways

Printer-friendly versionPrinter-friendly version

This article describes integration of payment gateways based on IPN (Instant Payment Notification). IPN uses as the most popular method of aggregating transactions and receiving payment notifications. This method used in PayPal, 2Checkout, Alertpay, Authorize.net and others. When user pays for listings, banners or packages - payment gateways notifies your site and sends information. As soon as transaction has been made successfully the script closes invoice and create a record in transactions table.

We will provide a quite documented example on how to integrate AlertPay.com payment gateway based on IPN using their documentation. New payment gateway implemented as new module of Web 2.0 Directory script.

Files and folders needed to create in new module:

 

modules / alertpay / alertpay.module.php

In this file we place alertpayModule class, that defines routes and backend menu items:

class alertpayModule
{
    public $title = "AlertPay";
    public $version = "0.2";
    public $description = "AlertPay gateway module.";
   
    public $lang_files = "alertpay.php";
   
    /**
     * AlertPay module depends on payment module
     */
    public $depends_on = 'payment';
   
    public function routes()
    {
        $route['admin/payment/gateway/alertpay/invoice_id/:num/quantity/:num/'] = array(
            'title' => LANG_ALERTPAY_REDIRECT_TITLE,
        );
       

        // Set Alert URL in AlterPay account: http://www.yoursite.com/alertpay/ipn/
        $route['alertpay/ipn/'] = array(
            'action' => 'ipn',
        );
       
        $route['admin/payment/settings/alertpay/'] = array(
            'title' => LANG_ALERTPAY_SETTINGS,
            'action' => 'settings',
            'access' => 'Manage payment settings',
        );

        return $route;
    }

    public function menu()
    {
        $menu[LANG_PAYEMENT_MENU]['children'] = array(
            LANG_PAYMENT_GATEWAYS_MENU => array(
                'weight' => 1,
                'children' => array(
                    LANG_ALERTPAY_SETTINGS => array(
                        'url' => 'admin/payment/settings/alertpay',
                        'access' => 'Manage payment settings',
                    ),
                ),
            ),
        );
       
        return $menu;
    }
}

 

modules / alertpay / classes / alertpay.class.php

This is the file of AlertPay class payment gateway. This class extends from PaymentGateway class (already implemented in Web 2.0 Directory script). Usually such class require 1 property: $available_currencies and 3 methods: __construct(), enableTestMode() and validateIpn().

In $available_currencies we will list in array all currencies codes available for this payment gateway:

public $available_currencies = array(
        'AUD',  // Australian Dollar
        'BGN',  // Bulgarian Lev
        'CAD',  // Canadian Dollar
        'CHF',  // Swiss Franc
        'CZK',  // Czech Koruna
        'DKK',  // Danish Krone
        'EEK',  // Estonia Kroon
        'EUR',  // Euro
        'GBP',  // British Pounds
        'HKD',  // Hong Kong Dollar
        'HUF',  // Hungarian Forint
        'INR',  // Indian Rupee
        'LTL',  // Lithuanian Litas
        'MYR',  // Malaysian Ringgit
        'MKD',  // Macedonian Denar
        'NOK',  // Norwegian Krone
        'NZD',  // New Zealand Dollar
        'PLN',  // Polish Zloty
        'RON',  // Romanian New Leu
        'SEK',  // Swedish Krona
        'SGD',  // Singapore Dollar
        'USD',  // U.S. Dollars
        'ZAR',  // South African Rand
);

In contructor we set default 'gatewayUrl' and 'ipnLogFile' properties:

public function __construct()
 {
        parent::__construct();
        // Some default values of the class
        $this->gatewayUrl = 'https://www.alertpay.com/PayProcess.aspx';
        $this->ipnLogFile = 'alertpay.ipn_results.log';
}

enableTestMode() function need when we would like to test payment gateway, some payment system have sandboxes or testing systems. For Alertpay for test mode we just create additional field and use another gateway URL to the sandbox:

public function enableTestMode()
{
        $this->testMode = TRUE;
        $this->gatewayUrl = 'http://sandbox.alertpay.com/sandbox/payprocess.aspx';
        $this->addField('ap_test', 1);

}

validateIpn() function receives IPN response and validate is it not fake responce and is it really was sent from Alertpay system.

public function validateIpn()
{
        //The value is the url address of IPN V2 handler and the identifier of the token string
        if (!$this->testMode)
            define("IPN_V2_HANDLER", "https://www.alertpay.com/ipn2.ashx");
        else
            define("IPN_V2_HANDLER", "https://sandbox.alertpay.com/sandbox/IPN2.ashx");
        define("TOKEN_IDENTIFIER", "token=");

        // get the token from Alertpay
        $token = urlencode($_POST['token']);

        //preappend the identifier string "token="
        $token = TOKEN_IDENTIFIER.$token;

        /**
         *
         * Sends the URL encoded TOKEN string to the Alertpay's IPN handler
         * using cURL and retrieves the response.
         *
         * variable $response holds the response string from the Alertpay's IPN V2.
         */

        $response = '';
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, IPN_V2_HANDLER);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $token);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HEADER, false);
        curl_setopt($ch, CURLOPT_TIMEOUT, 60);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        $response = curl_exec($ch);
        curl_close($ch);
       
        if(strlen($response) > 0)
        {
            if(urldecode($response) == "INVALID TOKEN")
            {
                // Invalid IPN transaction.  Check the log for details.
                $this->lastError = "The token is not valid";
                $this->logResults(false);
                return false;
            }
            else
            {
                //urldecode the received response from Alertpay's IPN V2
                $response = urldecode($response);
                //split the response string by the delimeter "&"
                $aps = explode("&", $response);

                foreach ($aps as $ap)
                {
                    //put the IPN information into an associative array
                    $ele = explode("=", $ap);
                    $this->ipnData[$ele[0]] = $ele[1];
                }

                 // Valid IPN transaction.
                 $this->logResults(true);
                 return true;
            }
        }
        else
        {
            // Invalid IPN transaction.  Check the log for details.
            $this->lastError = "Something is wrong, no response is received from Alertpay";
            $this->logResults(false);
            return false;
        }

}

 

modules / alertpay / controllers / alertpay_controller.php

This file controls pages and routes of our new module, it requires 3 includes: 2 for standart and ready files

include_once(MODULES_PATH . 'payment/classes/paymentGateways/PaymentGateway.php');
include_once(MODULES_PATH . 'payment/classes/invoice.class.php');

and 1 for our new module file:

include_once(MODULES_PATH . 'alertpay/classes/alertpay.class.php');

Usually we use 3 functions inside controller class: index(), ipn(), settings()

Index() function completes html form, that will post hidden fields with information about purchase item and its seller. In our case it will post to Alertpay at this url: https://www.alertpay.com/PayProcess.aspx

public function index($invoice_id, $quantity)
{
        $system_settings = registry::get('system_settings');
        if (!isset($system_settings['alertpay_email']) || !$system_settings['alertpay_email']) {
            $this->setError(LANG_PAYMENT_GATEWAY_ERROR);
            redirect('admin/payment/invoices/pay/' . $invoice_id . '/');
        }

        $this->load->model('payment', 'payment');
        $invoice = $this->payment->getInvoiceById($invoice_id, true);

        $alertpay = new Alertpay;
        // May be we need to process transaction in test mode
        //$alertpay->enableTestMode();

        // Payment gateway merchant's email
        $alertpay->addField('ap_merchant', $system_settings['alertpay_email']);
        // We will return here after all steps of payment
        $alertpay->addField('ap_returnurl', site_url('admin/payment/invoices/success/' . $invoice_id));
        // We will return here when payment will be canceled on some step
        $alertpay->addField('ap_cancelurl', site_url('admin/payment/invoices/my/'));
        // specific field for only AlertPay
        $alertpay->addField('ap_purchasetype', 'item-goods');
        // The name of purchase item
        $alertpay->addField('ap_itemname', $invoice->goods_title);
        // ID of purchase item
        $alertpay->addField('ap_itemcode', $invoice_id);
        // Currency code (Alertpay::available_currencies - this list of available currencies is in alertpay class)
        $alertpay->addField('ap_currency', $invoice->currency);
        // Quantity
        $alertpay->addField('ap_quantity', $quantity);
        // Money amount for each individual purchase item
        $alertpay->addField('ap_amount', $invoice->value);

        $view = $this->load->view();
        $view->assign('alertpay', $alertpay);

        $view->addJsFile('jquery.timers-1.1.2.js');
        $view->display('alertpay/alertpay_pay_redirect.tpl');
}

This function will display the page that will redirect to AlertPay in 5 seconds, It contains hidden fields with information about purchase item and its seller, html form posts these fields. Then user have to complete the process of payment on AlerPay's site.

Now look at ipn() function, this function calls by Alertpay system when user completes the payment process. This function usually means "handler". Inside we validate received information and get sure that this request was from AlertPay system and this transaction is unique - wasn't processed yet, then if everything is fine - save transaction details in the database and complete the payment.

public function ipn()
{
        $system_settings = registry::get('system_settings');
       
        $this->load->model('payment', 'payment');
        $alertpay = new Alertpay;
        // May be we need to process transaction in test mode
        //$alertpay->enableTestMode();

        // Validate IPN response
        if ($alertpay->validateIpn()) {
            // Does merchant email from request match with email in settings?
            // and sucurity codes?
           
if ($alertpay->ipnData['ap_merchant'] == $system_settings['alertpay_email']
                // && $alertpay->ipnData['ap_securitycode'] == $system_settings['alertpay_securitycode']) {
                // Is transaction unique?
                if ($this->payment->isUniqueTransaction($alertpay->ipnData['ap_referencenumber'])) {
                    $invoice_id = $alertpay->ipnData['ap_itemcode'];
                    $invoice_value = $this->payment->getInvoiceValue($invoice_id, $alertpay->ipnData['ap_currency']);
                    // Check currency and total paid amount
                    if ($invoice_value && floatval($alertpay->ipnData['ap_totalamount']) == floatval($alertpay->ipnData['ap_quantity']*($invoice_value))) {
                        // Create transaction record in the DB
                        if ($this->payment->createTransaction(
                            'AlertPay',
                            $alertpay->ipnData['ap_itemcode'],
                            $alertpay->ipnData['ap_status'],
                            $alertpay->ipnData['ap_referencenumber'],
                            $alertpay->ipnData['ap_totalamount'],
                            $alertpay->ipnData['ap_feeamount'],
                            $alertpay->ipnData['ap_currency'],
                            $alertpay->ipnData['ap_quantity'],
                            $alertpay->ipnData
                        )) {
                            // If transaction was successfull - set invoice to "paid" status and complete the payment
                            if ($alertpay->ipnData['ap_status'] == 'Success') {
                                $invoice = $this->payment->getInvoiceById($invoice_id);
                                // update goods content status and expiration date
                                $invoice->goods_content->completePayment($alertpay->ipnData['ap_quantity']);
                                // Invoice was payed
                                $this->payment->saveInvoiceStatus($invoice_id, 2);
                            }
                        }
                    }
                }
            }
        }
}

In settings() function we store/edit Alertpay account settings: merchant email and security code

public function settings()
{
        $system_settings = registry::get('system_settings');
        $this->load->model('settings', 'settings');

        if ($this->input->post('submit')) {
            $this->form_validation->set_rules('alertpay_email', LANG_ALERTPAY_EMAIL, 'required|valid_email');
            $this->form_validation->set_rules('alertpay_securitycode', LANG_ALERTPAY_SECURITYCODE, 'required');

            if ($this->form_validation->run() !== FALSE) {
                if ($this->settings->saveSystemSettings($this->form_validation->result_array())) {
                    $this->setSuccess(LANG_SYSTEM_SETTING_SAVE_SUCCESS);
                    redirect('admin/payment/settings/alertpay/');
                }
            }
            $alertpay_email = $this->form_validation->set_value('alertpay_email');
            $alertpay_securitycode = $this->form_validation->set_value('alertpay_securitycode');
        } else {
            $alertpay_email = $system_settings['alertpay_email'];
            $alertpay_securitycode = $system_settings['alertpay_securitycode'];
        }

        $view = $this->load->view();
        $view->assign('alertpay_email', $alertpay_email);
        $view->assign('alertpay_securitycode', $alertpay_securitycode);
        $view->display('alertpay/alertpay_settings.tpl');
}

 

themes / default / views / alertpay / alertpay_pay_redirect.tpl

This is the template of redirecting page, its code is simple: just places transaction information into hidden inputs and redirects in 5 seconds to AlertPay's processor URL.

{include file="backend/admin_header.tpl"}
                <script language="javascript" type="text/javascript">
                    window.onload = function() {ldelim}
                        $(".content").oneTime("5s", function() {ldelim}
                            $("#alertpay_form").submit();
                        {rdelim});
                    {rdelim}
                </script>

                <div class="content">
                     <h3>{$LANG_PROCESSING_PAYMENT}</h3>
                     <h4>{$LANG_PROCESSING_PAYMENT_DESCR}</h4>
                    
                     <form action="{$alertpay->gatewayUrl}" method="post" name="alertpay_form" id="alertpay_form">
                     {foreach from=$alertpay->fields item=value key=name}
                         <input type="hidden" name="{$name}" value="{$value}" />
                     {/foreach}

                     {$LANG_ALERTPAY_REDIRECT_MANUAL}<br/><br/>
                     <input type="submit" value="{$LANG_CLICK_HERE}">
                     </form>
                </div>
{include file="backend/admin_footer.tpl"}

 

themes / default / views / alertpay / alertpay_settings.tpl

This is template of Alertpay's settings (merchant email and security code) page:

{include file="backend/admin_header.tpl"}
                <div class="content">
                    {$VH->validation_errors()}
                    <h3>{$LANG_ALERTPAY_SETTINGS}</h3>

                    <form action="" method="post">
                    <div class="admin_option">
                        <div class="admin_option_name">
                            {$LANG_ALERTPAY_EMAIL}<span class="red_asterisk">*</span>
                        </div>
                        <input type="text" name="alertpay_email" value="{$alertpay_email}" size="50">
                    </div>
                    <div class="admin_option">
                        <div class="admin_option_name">
                            {$LANG_ALERTPAY_SECURITYCODE}<span class="red_asterisk">*</span>
                        </div>
                        <div class="admin_option_description">
                            {$LANG_ALERTPAY_SECURITYCODE_DESCR}
                        </div>
                        <input type="text" name="alertpay_securitycode" value="{$alertpay_securitycode}" size="50">
                    </div>
                    <input class="button save_button" type=submit name="submit" value="{$LANG_BUTTON_SAVE_CHANGES}">
                    </form>
                </div>
{include file="backend/admin_footer.tpl"}

 

MySQL files for installation and uninstallation of new module

They places in 'modules/alertpay/install/' folder,

'modules/alertpay/install/alertpay.install.php' mysql code in this file executes when admin of the site installs the module, in our case we need to create the record in 'payment_gateways' table and 2 records with settings in 'system_settings' folder:

INSERT INTO `payment_gateways` (`gateway`, `module`) VALUES ('AlertPay', 'alertpay');
INSERT INTO `system_settings` (`name`, `value`) VALUES ('alertpay_email', '');
INSERT INTO `system_settings` (`name`, `value`) VALUES ('alertpay_securitycode', '');

'modules/alertpay/install/alertpay.uninstall.php' mysql code in this file executes when admin of the site uninstalls the module, here we delete records from tables:

DELETE FROM `payment_gateways` WHERE `gateway` = 'AlertPay';
DELETE FROM `system_settings` WHERE `name` = 'alertpay_email';
DELETE FROM `system_settings` WHERE `name` = 'alertpay_securitycode';

 

languages / en / alertpay.php

In this file we just place language constants, these constants use in templates and controller:

$language['LANG_ALERTPAY_SETTINGS'] = "AlertPay settings";
$language['LANG_ALERTPAY_REDIRECT_TITLE'] = "You will be redirected to AlertPay";
$language['LANG_ALERTPAY_REDIRECT_MANUAL'] = "If you are not automatically redirected to AlertPay within 5 seconds...";
$language['LANG_ALERTPAY_EMAIL'] = "AlertPay Business Email";
$language['LANG_ALERTPAY_SECURITYCODE'] = "AlertPay Security code.";
$language['LANG_ALERTPAY_SECURITYCODE_DESCR'] = "Should be used to confirm that the IPN received came from AlertPay. Compare it to the IPN Security Code in your AlertPay account.";

 

So, we ended development of new module for Web 2.0 Directory script. It's time for testing:

  1. uncomment such lines in alertpay_controller.php file: $alertpay->enableTestMode();
  2. sign up on AlertPay's sandbox https://sandbox.alertpay.com/center/signup.aspx
  3. there are 2 test accounts: seller and client
  4. go to seller account and enable IPN v2 and enter Alert URL field on 'Business Tools -> IPN Setup' page (Alert URL will be like http://www.yoursite.com/alertpay/ipn/). Also take IPN Security Code on that page.
  5. in the script enable AlertPay module on modules list page and fill in AlertPay gateway settings (seller's email and security code) on 'Payment/Gateways settings/AlertPay settings' page
  6. now you may pay any invoice using client's account

 

During writing this article we used information from this documentation:

+ the best advice for anybody who wants to implement new payment gateways: read documentation and analyze php examples