User Tools

Site Tools


how_to_create_a_payment_module

How to Create a Payment Module

Here goes a brief explaination over what all methods do in a payment module.

First let's see a simple module example:

/includes/modules/payment/pm_mymodule.inc.php
class pm_mymodule {
  public $id = __CLASS__;
  public $name = 'My Module';
  public $description = 'Lorem ipsum dolor';
  public $author = 'ACME Corp.';
  public $version = '1.0';
  public $website = 'http://www.litecart.net';
  public $priority = 1;
 
  public function options($items, $subtotal, $tax, $currency_code, $customer) {
    return array(
      'title' => 'My Payment module',
      'options' => array(
        array(
          'id' => 'method1',
          'icon' => 'images/payment/mymodule-method1.png',
          'name' => 'Method 1',
          'description' => 'Select this option for method 1.',
          'fields' => '',
          'cost' => 0,
          'tax_class_id' => 0,
          'confirm' => 'Confirm Order',
        ),
      )
    );
  }
 
  public function pre_check($order) {
  }
 
  public function transfer($order) {
  }
 
  public function verify($order) {
  }
 
  public function after_process($order) {
  }
 
  public function receipt($order) {
  }
 
  function settings() {
    return array(
      array(
        'key' => 'status',
        'default_value' => '0',
        'title' => 'Status',
        'description' => 'Enables or disables the module.',
        'function' => 'toggle("e/d")',
      ),
      array(
        'key' => 'icon',
        'default_value' => 'images/payment/'.__CLASS__.'.png',
        'title' => 'Icon',
        'description' => 'Web path of the icon to be displayed.',
        'function' => 'input()',
      ),
      array(
        'key' => 'order_status_id',
        'default_value' => '0',
        'title' => 'Order Status:',
        'description' => 'Give successful orders made with this payment module the following order status.',
        'function' => 'order_status()',
      ),
      array(
        'key' => 'priority',
        'default_value' => '0',
        'title' => 'Priority',
        'description' => 'Process this module in the given priority order.',
        'function' => 'int()',
      ),
    );
  }
 
  public function install() {
  }
 
  public function uninstall() {
  }
}

Then let's have a look at the process chain:

  1. First we list available payment options.
  2. Then we transfer money
  3. Then we verify the transaction, making sure everything is fine.
  4. Order is saved, now we can perform any after order operations.

options()

Called in checkout used to display all avaliable payment options. A payment module may output several payment options if necessary i.e. card, directbank, etc.

  public function options($items, $subtotal, $tax, $currency_code, $customer) {
    return array(
      'title' => 'My Payment module',
      'options' => array(
        array(
          'id' => 'card',
          'icon' => 'images/payment/card.png',
          'name' => 'Card Payment',
          'description' => 'Select this option for card payment.',
          'fields' => '',
          'cost' => 0,
          'tax_class_id' => 0,
          'confirm' => 'Pay Now',
        ),
        array(
          'id' => 'directbank',
          'icon' => 'images/payment/bank.png',
          'name' => 'Direct Bank Payment',
          'description' => 'Select this option for direct bankpayment.',
          'fields' => '',
          'cost' => 0,
          'tax_class_id' => 0,
          'confirm' => 'Pay Now',
        ),
      )
    );
  }

If you need to collect some user data, here is an example. Inside the module you can access any submitted form fields through the array $this→userdata.

        array(
          'id' => 'method',
          'icon' => 'images/payment/icon.png',
          'name' => 'Title',
          'description' => 'This is a payment method.',
          'fields' => '<input type="text" name="foo" value="'. @$this->userdata['foo'] .'" />' . PHP_EOL
                    . '<input type="text" name="bar" value="'. @$this->userdata['bar'] .'" />',
          'cost' => 0,
          'tax_class_id' => 0,
          'confirm' => 'Button Text',
        ),

If nothing is returned no option will be returned.

transfer()

The transfer() method is used to send the user to a payment gateway. The return is an array of the destination and transaction data.

If not declared or not returning anything the transfer operation will be skipped.

The total amount of the order that include all the fees (shipping fees, payment fees, VAT, etc.) can be accessed via :

$order->data['payment_due']

Using HTTP POST

Keep in mind associative arrays are also recognized as fields.

  public function transfer($order) {
 
    ...
 
    return array(
      'method' => 'post',
      'action' => 'http://www.paymentgateway.com/form_process.ext',
      'fields' => array(
                    'foo' => 'bar',
                    'this' => 'that',
                  ),
    );
  }

Using HTTP GET

  public function transfer($order) {
 
    ...
 
    return array(
      'method' => 'get',
      'action' => 'http://www.paymentgateway.com/token/0123456789abcdef',
    );
  }

Using HTML

  public function transfer($order) {
 
    ...
 
    $myview = new view();
    $myview->snippets = array(
      userdata => @$this->userdata,
    );
 
    $html = $myview->stitch('views/myview');
 
    return array(
      'method' => 'html',
      'content' => $html,
    );
  }
~/includes/templates/mytemplate.catalog/views/myview.inc.php
<h1>Additional Payment Details</h1>
<?php echo functions::form_draw_form_begin('myform', 'post'); ?>
  <?php echo functions::form_draw_text_field('foo', @$userdata['foo']);
<?php echo functions::form_draw_form_end(); ?>

Using Your Own M2M Transaction

  public function transfer($order) {
 
    ...
 
  // Do whatever
    $client = new someclientyoucreated();
    $result = $client->methodyoucreated($mydata);
 
    if (empty($result) || !empty($result['error'])) {
      return array('error' => 'There was an error: '. $result['error']);
    }
 
  // Since we don't need to send the user anywhere we simply don't return anything
  }

Order Object

The $order object is passed to the method as the first passed variable.

You may see what's inside by displaying it's content:

  var_dump($order->data);
  exit;

This is how we make use of the order object to build order lines. This is just an example as the structure is payment service specific:

  $item_no = 0;
  foreach ($order->data['items'] as $item) {
    $fields['item_name_'.$item_no] = $item['name'];
    $fields['item_number_'.$item_no] = $item['product_id'] . (!empty($item['option_id']) ? ':'.$item['product_id'] : '');
    $fields['quantity_'.$item_no] = $item['quantity'];
    $fields['amount_'.$item_no] = currency::format_raw($item['price'], $order->data['currency_code'], $order->data['currency_value']);
    $fields['tax_'.$item_no] = currency::format_raw($item['tax'], $order->data['currency_code'], $order->data['currency_value']);
    $item_no++;
  }
 
  $item_no = 0;
  foreach ($order->data['order_total'] as $row) {
    if (empty($row['calculate'])) continue;
    $fields['item_name_'.$item_no] = $row['title'];
    $fields['item_number_'.$item_no] = $row['module_id'];
    $fields['quantity_'.$item_no] = '1';
    $fields['amount_'.$item_no] = currency::format_raw($row['value'], $order->data['currency_code'], $order->data['currency_value']);
    $fields['tax_'.$item_no] = currency::format_raw($row['tax'], $order->data['currency_code'], $order->data['currency_value']);
    $item_no++;
  }

Return URLs

Example of return URLs:

  public function transfer($order) {
 
    //$order->save(); // Save session order to database before transaction creates an $order->data['id']. Not recommended
 
    $fields = array(
      ...
      'cancel_url' => document::ilink('checkout'),
      'success_url' => document::ilink('order_process'),
      'callback_url' => document::link(WS_DIR_EXT . 'payment_service_provider/my_external_callback_file.php', array('order_uid' => $order->data['uid'])), // An order always have a uid even though it is not saved
    );
 
    ...
 
    if ($error == true) {
      return array('error' => 'There was an error verifying your transaction');
    }
 
    return array(
      'action' => 'http://www.paymentgateway.com/form_process.ext',
      'method' => 'post',
      'fields' => $fields,
    );
  }

verify()

The verify() method is used to verify the transaction. There are a few security questions you may ask yourself:

  • Does the transaction result come from a trusted source?
  • Is this a valid order ID or UID
  • Is the payment flagged as okay by the payment service provider?
  • Is the payment amount the same as the order amount? Be aware of rounding.
  • Is the payment currency same as the order currency?
  public function verify($order) {
 
  // Verify some data
    ...
 
    if (isset($error) && $error == true) {
      return array('error' => 'There was an error verifying your transaction');
    }
 
    return array(
      'order_status_id' => $this->settings['order_status_id'],
      'payment_transaction_id' => '123456789',
      'comments' => 'This is an order comment',
    );
  }

after_process()

This method does not have a return. It is available for after order operations if necessary i.e. updating order reference with the order number.

receipt()

This method returns html code that is output on the order success page. It was intended to display a payment receipt but your imagination sets the limit.

settings()

This method sets up the payment module with a settings structure. The return is an array of the structure.

  function settings() {
    return array(
      array(
        'key' => 'status',
        'default_value' => '0',
        'title' => 'Status',
        'description' => 'Enables or disables the module.',
        'function' => 'toggle("e/d")',
      ),
      array(
        'key' => 'icon',
        'default_value' => 'images/payment/paypal.png',
        'title' => 'Icon',
        'description' => 'Web path of the icon to be displayed.',
        'function' => 'input()',
      ),
      array(
        'key' => 'order_status_id',
        'default_value' => '0',
        'title' => 'Order Status:',
        'description' => 'Give successful orders made with this payment module the following order status.',
        'function' => 'order_status()',
      ),
      array(
        'key' => 'priority',
        'default_value' => '0',
        'title' => 'Priority',
        'description' => 'Process this module in the given priority order.',
        'function' => 'int()',
      ),
    );
  }

Keep in mind status and priority are mandatory for all modules.

Get a Settings Value

  $this->settings['key_name']

install()

This method does not have a return. It is executed upon installing the module in the admin panel. It can be used for creating third party mysql tables etc. Note: install() doesn't run until the “Save” button is clicked.

update()

This method does not have a return. It is executed upon saving the module settings in the admin panel. This method is triggered when the module is already installed. It can be used for updating third party mysql tables etc. Note: update() doesn't run until the “Save” button is clicked.

uninstall()

This method does not have a return. It is executed upon uninstalling the module in the admin panel. It can be used for deleting orphan data.

Translations

It is not user friendly to hardcode text in a single language. LiteCart recognizes the following syntax for translating any translations for a module.

  language::translate(__CLASS__.':title_hello_world', 'Hello World')

Callbacks

Some payment service providers offers machine-to-machine data exchange during the transaction takes part. In such cases you will need a callback function. Here is an example of an external script that will call a method inside the module called callback().

/ext/provider/callback.php
<?php
  define('REQUIRE_POST_TOKEN', false); // Allow unsigned external incoming POST data
  require_once('../../includes/app_header.inc.php');
 
// Make sure callback comes from a trusted IP address
  if (!in_array($_SERVER['REMOTE_ADDR'], array('123.456.789.0', '123.456.789.1', '123.456.789.2'))) {
    error_log('Unauthorized access by '. $_SERVER['REMOTE_ADDR'] .' to file '. __FILE__);
    header('HTTP/1.1 403 Access Denied');
    die('Access Denied');
  }
 
// Make sure we have a UID
  if (empty($_GET['order_uid'])) {
    header('HTTP/1.1 400 Bad Request');
    die('Bad Request');
  }
 
// Get Order From Database (In this example using UID)
// To be able to find the order in the database, the order must previously have been saved.
  $orders_query = database::query(
    "select id from ". DB_TABLE_ORDERS ."
    where uid = '". database::input($_GET['order_uid']) ."'
    limit 1;"
  );
 
  if (!$order = database::fetch($orders_query)) {
    header('HTTP/1.1 404 File Not Found');
    die('File Not Found');
  }
 
// Initiate $order as the order object
  //$order = new ctrl_order('load', $order['id']); // LiteCart 1.0
  $order = new ctrl_order($order['id']); // LiteCart 2.0
 
// Get the order's payment option
  list($module_id, $option_id) = explode(':', $order->data['payment_option']['id']);
 
// Pass the call to the payment module's method callback()
  $payment = new mod_payment();
  $result = $payment->run('callback', $module_id, $order);
 
  // The rest in handled inside the payment module
  // Define the funtion by: public function callback($order) {}
?>

We suggest storing the transaction details in the callback and verify them later in the method verify().

how_to_create_a_payment_module.txt · Last modified: 2017/09/05 00:03 by admin