创建新的Prestashop支付插件原理 Creating a PrestaShop Payment

Code 2010-04-20

设计prestashop国内信用卡支付的插件,好不容易找到的英文教材。

Introduction

Having been looking for a Open Source shopping cart solution I stumbled on prestashop. It is a good solution which seemed to be what I was looking for. Like a lot of modern carts and CMSes it uses system of plug-in modules to add extra functionality that doesn't come as standard. The only slight problem with this module system, is how do you create a plug-in module!? It isn't exactly obvious.
This guide will walk you through creating a simple off line payments module. By "off-line" payments I mean the module will allow the store to capture a customers payment card details during a purchase then store the details in the database for the store owner to process through their payment machine or checkout at a later time.
NOTE! Please bear in mind I am not advocating this as a payment method for live stores. There are security issues surrounding gathering payment details in this manner. I am using it as an example as it is a simple enough demonstration of creating payment module.

Intended Audience

Any one with basic PHP object orientated programming and HTML skills who would like to get an overview on how to create a prestashop module. You should be familiar with installing prestashop.

Development Environment

I created the module using open source (free) software. I used the Eclipse PHP editor PHP Development Tools (PDT) which is nice and easy to use. I also use WAMP which is a webserver and MYSQL database server package designed for easy setup. You can use any thing you feel comfortable with - I use them because they are good and free. Since I am using the WAMP server my prestashops code will be located here C:\wamp\www\prestashop and my url is http://localhost\prestashop

Getting Started

I will assume you have installed prestashop on your development computer and you are ready to begin with the basics.

Name your Module

Your module can be called anything you like. My module will be called offlinepayments as that is what is will be doing - taking off line payments.
So create a directory under your prestahop C:\wamp\www\prestashop\modules\offlinepayments see below for an example
Prestashop module

Create your module's "class" .php file

Prestashop modules must have one php file which contains the Class definition. This class definition contains the core functions of your module.
The file needs to be given the same name as your module so in my case it must called offlinepayments.php and it needs to live in the root of the modules directory.

Create the skeleton Class Entry in the file offlinepayments.php

Now we have the class file we need to create the offlinepayments class. This class is core to the module within this file. It controls the way the module works. At this stage the class is very basic it will only have enough functionality to list itself as an available module in your shop.
Open the file offlinepayments.php and paste the following code into it:

<?php

classofflinecardpaymentextendsPaymentModule //this declares the class and specifies it will extend the standard payment module
{
   
    private
$_html='';
    private
$_postErrors= array();
   
    function
__construct()
    {
       
$this->name='offlinecardpayment';
       
$this->tab='Payment';
       
$this->version=1;

       
parent::__construct();// The parent construct is required for translations

       
$this->page=basename(__FILE__,'.php');
       
$this->displayName=$this->l('Offline Payments Module');
       
$this->description=$this->l('Take Payment Card details for offline processing');

}
}
?>

The first part of the code

class offlinecardpayment extends PaymentModule


names the class - for it to work the class should be the same as the module name so I have to call mine offlinepayment

The next significant part of the module is the _construct function

function __construct()
{
$this->name = 'offlinecardpayment';
$this->tab = 'Payment';
$this->version = 1;

parent::__construct(); // The parent construct is required for translations

$this->page = basename(__FILE__, '.php');
$this->displayName = $this->l('Offline Payments Module');
$this->description = $this->l('Take Payment Card details for offline processing');

}

This function sets the modules name, in this case offlinecardpayment. Keep it the same as the folder and the class name.

$this->name = 'offlinecardpayment';

It also sets the area within the admin section that the module should appear in, in this case the payment. You can put your own here and prestashop will create new section. Ours is a payment module so it makes sense to keep it with the other payment modules.

$this->tab = 'Payment';

and sets a version of the module - mine is version 1

$this->version = 1;

Further down the page it gives the module a friendly name so the administrator can identify what it is easily.

$this->displayName = $this->l('Offline Payments Module');

and gives the module a simple brief description. This is displayed in the module section of the admin section of the site

$this->description = $this->l('Take Payment Card details for offline processing');

See the module listed in the admin section

Save the file and then open the administration section of your test shop.

Now navigate to the modules tab and scroll down the payment section . If all has gone well you should see the new module listed.

We have now well and truly got started as the payment module is taking shape. see the attached zip file gettingstarted.zip for the progress so far.


Making the module's install routine

Building the standard install() function

Prestashop modules have an install link. This link needs to be clicked to install the module to make it actually function within the store. NOTE! Basically, what although the module is listed in your store as an available module, you need to to click install to actually activate the module for it to be active in your store.

See below:

When that install link is clicked a function within the class named "install" is called. So for our module to be installed correctly we need to create that function within our offlinecardpayment class.

This is what our install function will look like:

public function install()
{
if (!parent::install()
or !$this->createPaymentcardtbl() //calls function to create payment card table
            or !$this->registerHook('invoice')
or !$this->registerHook('payment')
or !$this->registerHook('paymentReturn'))
return false;
return true;
}


Here is a breakdown of what the function does.

OR !$this->createPaymentcardtbl()

This line will call a function called createPaymentcardtbl that creates an additional table in the prestashop stores database for storing the payment card details. NOTE! At present that function does not exist so we are going to create that function further in this article.

OR !$this->registerHook('invoice')

This line tells prestashop that we want this module to register itself with the invoice hook. This will allow us to create functionailty to display the payment card details against each order. If you don't know what a hook is then see appendices for details.

OR !$this->registerHook('payment')

This line tells prestashop that we want this module to register itself with the payment "hook". It will run the function hookPayment.

OR !$this->registerHook('paymentReturn')

This line tells prestashop that we want this module to register itself with the payment return hook. This is the hook which allow us to add functionality to the part after the payment is complete. Something like displaying a "thanks for ordering message" for example.

Creating a payment card database table

Now the install function is complete. Next we build the createPaymentcardtbl function.

This function creates a table within the prestashop database to hold the payment card details related to the each order taken. It is a very basic table which is designed to hold the following information

id_payment = primary key
id_order = holds the order information to associate the payment details with the order this was taken with.
cardholder_name = hold the card holders name
cardnumber = holds the card number details
expiry date = the holds cards expiry date.

function createPaymentcardtbl()
{
/**Function called by install -
* creates the "order_paymentcard" table required for storing payment card details
     * Column Descriptions: id_payment the primary key.
     * id order: Stores the order number associated with this payment card
     * cardholder_name: Stores the card holder name
     * cardnumber: Stores the card number
     * expiry date: Stores date the card expires
     */
       
                    $db = Db::getInstance();
            $query = "Create TABLE `"._DB_PREFIX_."order_paymentcard` (
`id_payment` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`id_order` INT NOT NULL ,
`cardholdername` TEXT NOT NULL ,
`cardnumber` TEXT NOT NULL
) ENGINE = MYISAM ";
           
        $db->Execute($query);

return true;
}

Testing the Install

Click the "install" button next to the Offline Payments.

If successfuly you should see a message displayed that says "Module installed successfully"

We can also see within our prestastore database that the payment module is now listed as a module. Below is an example (using phpmyadmin) which shows that our module has been added to the list of prestashop modules - we can verify that by the fact it has been added to the prestashop modules table.

We can also check to see if our module has been registered with the hooks by displaying the hook_module table.

If the prestashop module is listed in the module table and within the hook_module table it's a good sign our install function is working well.


Displaying our module as a payment option in the checkout

Since we have a registered our module with the payment hook a function which tells the payment hook to do something. The hook payment expects a function called hookPayment by default so thats what we need to create. This is where the hook payment allows us to have our module appear (image below)

Create the hookPayment() function

All we need our hook payment screen to do is present a very simple template called "payment.tpl" that lets our users select the offline option and when selected take them to the payment form called "payment.php" (we need to create payment.php)

Here is our hookPayment function. Make sure you create it within your offlinecardpayment class.

/**
* hookPayment($params)
* Called in Front Office at Payment Screen - displays user this module as payment option
*/
function hookPayment($params)
{
global $smarty;

$smarty->assign(array(
            'this_path' => $this->_path,
            'this_path_ssl' => Configuration::get('PS_FO_PROTOCOL').$_SERVER['HTTP_HOST'].__PS_BASE_URI__."modules/{$this->name}/"));

return $this->display(__FILE__, 'payment.tpl');
}

The first line

global $smarty;

creates a $smarty template

$smarty->assign(array( 'this_path' => $this->_path,
'this_path_ssl' => Configuration::get('PS_FO_PROTOCOL').$_SERVER['HTTP_HOST'].__PS_BASE_URI__."modules/{$this->name}/"));

sends the url information to the new smarty template

and finally calls the template

return $this->display(__FILE__, 'payment.tpl');

Creating the payment.tpl smarty template file

<p class="payment_module">

<a href="{$this_path}payment.php" title="{l s='Pay with your Credit or Debit Card' mod='offlinecardpayment'}">

<img src="{$this_path}cards.png" alt="{l s='Pay with your Credit or Debit Card' mod='offlinecardpayment'}" />

{l s='Pay with a credit card' mod='offlinecardpayment'}

</a>

</p>

This is a simple template file that will display the Offline Payment Module as a payment option. The most important part of the template is the line:

<a href="{$this_path}payment.php" title="{l s='Pay with your Credit or Debit Card' mod='offlinecardpayment'}">


this calls the payment.php file which will take our customers payment card details.

We also create an image file called card.png This is optional the logo that will be displayed as part of our payment option.
and it is called her in in our payment.tpl file

<img src="{$this_path}cards.png" alt="{l s='Pay with your Credit or Debit Card' mod='offlinecardpayment'}" />


So with this template file we should now see out payment option being displayed as an option at the payment stage of the checkout process see below:

Now our payment option is displayed we need to create the actual page that is displayed when the option is clicked on. As part of this process a form will be displayed for the user to enter their card details and then store them in the database for later viewing.

If you recall it was payment.php So that is the name of the next file we need to create in our payment modules directory.

Okay so create a new file called payment.php and place the following code into it:

<?php

include(dirname(__FILE__).'/../../config/config.inc.php');
include(
dirname(__FILE__).'/../../header.php');
include(
dirname(__FILE__).'/offlinecardpayment.php');

if (!
$cookie->isLogged())
   
Tools::redirect('authentication.php?back=order.php');
   
$offlinecardpayment= newofflinecardpayment();
echo
$offlinecardpayment->execPayment($cart);

include_once(
dirname(__FILE__).'/../../footer.php');

?>


This file will call some files it requires for operation -

include(dirname(__FILE__).'/../../config/config.inc.php');
include(dirname(__FILE__).'/../../header.php');
include(dirname(__FILE__).'/offlinecardpayment.php');


the first config.inc.php is prestashop's configuration file, the second is the header used build the store and finally we include our offlinecardpayment.php so we have access to our module's class file.

Next we ensure the user is logged in if they aren't we direct them to an authentication

if (!$cookie->isLogged())
    Tools::redirect('authentication.php?back=order.php');


We then call this function

$offlinecardpayment = new offlinecardpayment();
echo $offlinecardpayment->execPayment($cart);

Which calls our offlinecardpayment class and then calls the execPayment function
which is a method/function of our offlinecardpayment. At this stage the function doesn't exist so we need to create it.

Open the offlinecardpayment.php file and within it paste the following code:

/*
* This function will check display the card details form payment_execution.tpl
* It will check if the submit button on the form has been pressed and submit the card details to the database
*/

public function execPayment($cart)
{
if (!$this->active)
return ;
  
global $cookie, $smarty;

$smarty->assign(array(
'this_path' => $this->_path,
'this_path_ssl' => (Configuration::get('PS_SSL_ENABLED') ? 'https://' : 'http://').htmlspecialchars($_SERVER['HTTP_HOST'], ENT_COMPAT, 'UTF-8').__PS_BASE_URI__.'modules/'.$this->name.'/'
));

return $this->display(__FILE__, 'payment_execution.tpl');
}

This is the paymentExec function we have added which will begin the payment process and if you look at this line of code:

return $this->display(__FILE__, 'payment_execution.tpl');

you can see it is calling the payment_execution.tpl which is the "form" that your user will enter their payment detail in. And at this stage it doesn't exist. So let's get on and make this file.

Displaying the Card Payment Entry details form

Create a new file called payment_execution.tpl. Open it in and a paste the following smarty markup code

capture name=path}{l s='Shipping'}{/capture}
{include file=$tpl_dir./breadcrumb.tpl}
<h2>{l s='Order summary' mod='offlinecardpayment'}</h2>

{assign var='current_step' value='payment'}
{include file=$tpl_dir./order-steps.tpl}

<h3>{l s='Payment Card Details' mod='offlinecardpayment'}</h3>

<form action="{$this_path_ssl}validation.php" method="post">
<table border="0">

<tr>

<td>

{l s='Name on Card:' mod='offlinecardpayment'}
</td>
<td>
<input type="text" name="cardholderName" id="cardholderName" value="{$cardholderName}"/>

</td>

</tr>

<tr>
<td>
{l s='Credit Card Number:' mod='offlinecardpayment'}
</td>
<td>
<input type="text" name="cardNumber" id="cardNumber" value="{$cardNumber}" />
</td>
</tr>

</table>

<p class="cart_navigation">
<a href="{$base_dir_ssl}order.php?step=3" class="button_large">{l s='Other payment methods' mod='offlinecardpayment'}</a>
<input type="submit" name="paymentSubmit" value="{l s='Submit order' mod='creditcard'}" class="exclusive_large" />
</p>
</form>

With this template our payment option will now display a payment card entry form when selected. Its a fairly simple affair but it is enough to collect the username and credit card number. Of course in real life you would most likely want to collect other items such as expiry date and valid from date and perhaps card type. But for now this is an effective enough example.

This form has its action set to validation.php. validation.php is the code that will be taking the details the user enters and adds it to our order.

<form action="{$this_path_ssl}validation.php" method="post">

As you probably guessed we need a validation.php for us to post to so lets go ahead and create it.
Create a file called validation.php in your modules directory and paste the following code into it.

<?php

include(dirname(__FILE__).'/../../config/config.inc.php');
include(
dirname(__FILE__).'/../../header.php');
include(
dirname(__FILE__).'/offlinecardpayment.php');
           

/* Gather submitted payment card details */
$cardholderName    =$_POST['cardholderName'];
$cardNumber        =$_POST['cardNumber'];


$currency= newCurrency(intval(isset($_POST['currency_payement']) ?$_POST['currency_payement'] :$cookie->id_currency));
$total=floatval(number_format($cart->getOrderTotal(true,3),2,'.',''));

$offlinecardpayment= newofflinecardpayment();
$offlinecardpayment->validateOrder($cart->id_PS_OS_PREPARATION_,$total,$offlinecardpayment->displayName,NULL,NULL,$currency->id);
$order= newOrder($offlinecardpayment->currentOrder);
$offlinecardpayment->writePaymentcarddetails($order->id,$cardholderName,$cardNumber);
   
Tools::redirectLink(__PS_BASE_URI__.'order-confirmation.php?id_cart='.$cart->id.'&id_module='.$offlinecardpayment->id.'&id_order='.$offlinecardpayment->currentOrder.'&key='.$order->secure_key);

?>

This part of the validation calls a function to write the information entered by our user to the database in the paymentcard table.

$offlinecardpayment = new offlinecardpayment();
$offlinecardpayment->validateOrder($cart->id,  _PS_OS_PREPARATION_, $total, $offlinecardpayment->displayName, NULL, NULL, $currency->id);
$order = new order($offlinecardpayment->currentOrder);
$offlinecardpayment->writePaymentcarddetails($order->id, $cardholderName, $cardNumber);

With this information written to the database we have successfully captured our users payment information for off line processing. Which is what we wanted.

Of course we need to add it to our offlinepayments core class. So please add that now

     *  Call this function to save the payment card details to the payment card table
     */

function writePaymentcarddetails($id_order, $cardholderName, $cardNumber)
{
$db = Db::getInstance();
$result = $db->Execute('
Insert INTO `ps_order_paymentcard`
( `id_order`, `cardholdername`,`cardnumber`)
VALUES
("'.intval($id_order).'","'.$cardholderName.'","'.$cardNumber.'")');
return;
}

Thats all Folks

Well that's almost done, there is just a bit more work though - now that our users can enter their payment details we need to be able to retrieve them in our administration panel.

Displaying the order info in the admin panel

We need to hook into the invoice part of the administration panel. We have already declared we want to access the invoice hook earlier in our install() function

OR !$this->registerHook('invoice')

This tells prestashop we want to hook into the invoice and that it should look for a standard invoice function called hookInvoice

So let's go ahead and add this function to our core offlinepayment class

function hookInvoice($params)
{
$id_order = $params['id_order'];

global $smarty;
$paymentCarddetails = $this->readPaymentcarddetails($id_order);

$smarty->assign(array(
    'cardHoldername'          => $paymentCarddetails['cardholdername'],
'cardNumber'         => $paymentCarddetails['cardnumber'],
'id_order' => $id_order,
'this_page' => $_SERVER['REQUEST_URI'],
'this_path' => $this->_path,
            'this_path_ssl' => Configuration::get('PS_FO_PROTOCOL').$_SERVER['HTTP_HOST'].__PS_BASE_URI__."modules/{$this->name}/"));
return $this->display(__FILE__, 'invoice_block.tpl');

}

This function is quite simple it calls another function called readPaymentcarddetails() which retrieves the appropriate payment details from the database

    /*
     *  Call this function to read the payment card details from the payment card table
     */
function readPaymentcarddetails($id_order)
{
$db = Db::getInstance();
$result = $db->ExecuteS('
Select * FROM `ps_order_paymentcard`
Where `id_order` ="'.intval($id_order).'";');
return $result[0];
}

and then displays them in the invoice part of the administration panel. It does this by calling a template file called invoice_block.tpl

So go ahead and create a that file in your modules directory. And put the following code into it.

<fieldset style="width: 400px;">

<legend>

     {l s='Payment Card Information' mod='offlinecardpayment'}

</legend>

<div id="info" border: solid red 1px;">
<table>
<tr><td>Card Holder Name:</td> <td>{$cardHoldername}</td></tr>
<tr><td>Card Number:</td> <td>{$cardNumber}</td></tr>
</table>
</div>

</fieldset>

Now payment details are displayed in the invoice for the order.

Prestashop Invoice

That's that we now have create a module which will take a users payment card details during the prestashop order process. We have also enabled our administrator to retrieve them in the invoice for processing. I hope this tutorial is useful to presta people out there - if it gives you a an idea and a bit of headstart for your own module development efforts then it has achieved it's goal.

Extra HomeWork

Well we have a working module but it is a little basic. We could do with adding some extra bits and pieces. For example we don't have any validation on the form - to make sure that the user doesn't enter a load of nonsense insead of valid card details. Also we need to create a paymentReturn function that will give our user some feedback to let them know their payment had been successful. Also storing creditcard numbers in plain text is not the done thing -we should encrypt first at a minimum.

Files

These are the files for each of the chapters. Each file contains the files and functions of the named chapter. The finished module is offlinecardpayment.zip

AttachmentSize
gettingstarted.zip566 bytes
Making_the_modules_install_routine.zip1.08 KB
offlinecardpayment.zip21.08 KB