Jump to content
  • Checkout
  • Login
  • Get in touch

osCommerce

The e-commerce.

Payment module development how-to


Eskil

Recommended Posts

This is a lengthy post. This post is meant for anyone who wants to create/implement their custom payment module. The contents of this post hopefully answers many question anyone has. It is the result of my own acquired understanding of how osCommerce payment modules work. I appreciate any comments if you find this post useful. Please reply to this post if you identify any mistakes in my post or want to elaborate on the topic.

 

Background:

I originally replied to a post on how to create a module and received some PMs asking me to elaborate on this.

 

In this post I will include that same overview with extended explanations and sample code. This post does not attempt to explain how to write a module, as there are many considerations one needs to take and each payment gateway is different. I will outline the standard functions in a module that are required and when and how they are called/used. I assume we are using a gateway which requires a http-post redirect.

 

First, a simple overview of the standard/required payment module functions (the standard PayPal module or cc module are good references as they implement standard functions and have minimal code):

 

PAYMENT MODULE STRUCTURE

 

Function name

Description

Calling script

 

class constructor

Sets initial member variables and module status (enabled/disabled).

Define the form_action_url variable which is your gateway url (that will recieve the POST data).

Include any static variables you may require in your code here.

 

update_status()

Here you can implement using payment zones (refer to standard PayPal module as reference).

Called by module's class constructor, checkout_confirmation.php, checkout_process.php

 

javascript_validation()

Here you may define client side javascript that will verify any input fields you use in the payment method selection page. Refer to standard cc module as reference (cc.php).

Called by checkout_payment.php

 

selection()

This function outputs the payment method title/text and if required, the input fields.

Called by checkout_payment.php

 

pre_confirmation_check()

Use this function implement any checks of any conditions after payment method has been selected. You most probably don't need to implement anything here.

Called by checkout_confirmation.php before any page output.

 

confirmation()

Implement any checks or processing on the order information before proceeding to payment confirmation. You most probably don't need to implement anything here.

Called by checkout_confirmation.php

 

process_button()

Outputs the html form hidden elements sent as POST data to the payment gateway.

Called by checkout_confirmation.php

 

before_process()

This is where you will implement any payment verification. This function can be quite complicated to implement. I will elaborate in this later in this post.

Called by checkout_process.php before order is finalised

 

after_process()

Here you may implement any post proessing of the payment/order after the order has been finalised. At this point you now have a reference to the created osCommerce order id and you would typically update any custom database tables you may have for your module. You most probably don't need to implement anything here.

Called by checkout_process.php after order is finalised

 

get_error()

For more advanced error handling. When your module logic returns any errors you will redirect to checkout_payment.php with some error information. When implemented corretly, this function can be used to genereate the proper error texts for particular errors. The redirect must be formatted like this:

tep_redirect(tep_href_link(FILENAME_CHECKOUT_PAYMENT, 'payment_error=' . $this->code.'&error='.urlencode('some error'), 'NONSSL', true, false));

I will examplify this in my sample code later in this post (the standard PayPal module does not implement this method).

Called by checkout_payment.php

 

check()

Standard functionlity for osCommerce to see if the module is installed.

 

install()

This is where you define module's configurations (displayed in admin).

 

remove()

Standard functionality to uninstall the module.

 

keys()

This array must include all the configuration setting keys defined in your install() function.

 

PAYMENT VERIFICATION

 

A good payment gateway (and payment module) should allow for payment verification. Without any form of payment verification your module is open for url-hacking. This is a very simple consept where a malicious user simply pastes the success url of the e-commerce system (in this case checkout_process.php) and the order is completed assuming the payment has been completed. You can try this by installing the standard PayPal module, completing the order as far as not confirming the order. Then paste in the full url for checkout_process.php for your server.

 

I will with the following sample code show how to implement payment verification. There are probably numerous ways this may be done. I assume a simple scenario where the payment information is verified by the gateway by re-submitting the data first recieved from the gateway. The information is first checked by the payment module (simple verification) and then re-submitted back the gateway for verification (advanced verification). A result code is passed back to the module. This communication is passed on an underlying layer using secure sockets (SSL) and is invisible to the user (no re-directs). My sample uses cURL to enable underlying connections over SSL.

 

Note: The module's internal check of the payment data requires to compare the payment amount passed from the gateway with the order amount. To achieve this the $order_totals object must be instantiated from within the module and requires a little hack/work around to be able to complete the order processing: the rest of the code in checkout_process.php from the point where the module's before_process() function is called, must be executed from within this module function. This is related to the chicken&egg problem described here . I have made notes in the sample code where this occurs. (I discovered this problem when tax was added to shipping. The payment amounts would differ.)

 

Listing 1: usage of before_process() to verify a payment.

 

function before_process() {
global $HTTP_POST_VARS, $cart, $order, $currency, $currencies, $customer_id;

// return error if key values are missing
if(!isset($HTTP_POST_VARS['PaymentID']) || !isset($HTTP_POST_VARS['OrderID']) || !isset($HTTP_POST_VARS['Amount']) )
	tep_redirect(tep_href_link(FILENAME_CHECKOUT_PAYMENT, 'error_message=' . urlencode('No payment information found.'), 'NONSSL', true, false));

$in_paymentID = '';
$in_amount = '';
$in_orderID = '';
$in_currency = '';

// read returned data from gateway

$in_paymentID = urldecode($HTTP_POST_VARS['PaymentID']);
$in_amount = urldecode($HTTP_POST_VARS['Amount']);
$in_orderID = urldecode($HTTP_POST_VARS['OrderID']);
$in_currency = urldecode($HTTP_POST_VARS['Currency']);


// add PaymentID as comment to order	
$order->info['comments'] .= "\nPaymentID: ". $in_paymentID;

/* From checkout_process.php: create $order_totals object, otherwise $order object will have incorrect data
*  This is why the rest of checkout_process.php must be included in this function. (see end of function def)
*  The $order_totalts object needs to be instantiated here and cannot be instantiated twice!
*/ 
require(DIR_WS_CLASSES . 'order_total.php');
		  $order_total_modules = new order_total;
$order_totals = $order_total_modules->process();


// Check that values are untampered (simple verification).
// Note: get_orderno is an added custom function to generate an orderid as an osc order has no id until it has been completely processed....
if( ($in_amount != number_format($order->info['total'] * $currencies->get_value($currency), $currencies->get_decimal_places($currency), '.', '')) 
	|| ($in_orderID != $this->get_orderno($customer_id)) 
	|| ($in_currency != $currency) ) ){
		tep_redirect(tep_href_link(FILENAME_CHECKOUT_PAYMENT, 'error_message=' . urlencode('Data has been tampered! Payment aborted.'), 'NONSSL', true, false));
}


/* Verify transaction with gateway. 
*  The actual verification is defined in a separate function (validate_payment()).
*  This code assumes using cURL for posting data over SSL.
*/

$this->validate_payment( $HTTP_POST_VARS );

// Processing continues here if there is no error

/* Here we continue the processing that otherwise is done by checkout_process.php.
*  Include everything in checkout_process starting from line #57
*  Alternatively extract that code into an include file (i.e include('module_checkout_process.inc.php'); )
*/

include('sample_module_checkout_process.inc.php');
}

 

Lising 2: The payment validation method

/* Payment verification
*  Note that this processing is an example only. All gateways are different. 
*  This function shows how to post data back to the gateway.
*
*  The recieved payment data are posted back to the gateway to verify that they are correct.
*  Gateway verifies amount, currency, transaction_id and order_id.
*  Gateway returns 0 for success.
*
*  Also make note of how the errors are handled. 
*  The error information can include your own codes that later are interpreted by the module's get_error() function.
*/
function validate_payment( $request ){

$postfields = null;
foreach($request as $key=>$value){
	if($key && $value){
		$postfields.=$key."=".urlencode($value)."&";
	}
}

curl_setopt($ch, CURLOPT_URL, 'https://insert.gateway.verification.url.here');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
curl_setopt($ch, CURLOPT_HEADER, 0); // ignore http headers
curl_setopt($ch, CURLOPT_TIMEOUT, 10);	//returns error 28
curl_setopt($ch, CURLOPT_FAILONERROR, 1); //returns error 22 on HTTP error (http statuscode >= 300)
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);

// Post data back to gateway
$result = curl_exec($ch);

// check for error
if (curl_errno($ch) != CURLE_OK) {
	$result['errno'] = curl_errno($ch);
	$result['errstr'] = curl_error($ch);
}
curl_close ($ch);

// process verification result

// if result is array then a CURL error has occurred
if(is_array($result) && isset($result['errno'])){ 
	$errornumber = $result['errno'];
	$errortext = $result['errstr'];
	$this->luup_debug('Curl or socket error: ', $errornumber . ' ' . $errortext);

	switch($errornumber){
		case CURLE_HTTP_RETURNED_ERROR: // HTTP error
		tep_redirect(tep_href_link(FILENAME_CHECKOUT_PAYMENT, 'payment_error=' . $this->code.'&error='.urlencode($errortext), 'NONSSL', true, false));
		break;
		case CURLE_OPERATION_TIMEOUTED:
		tep_redirect(tep_href_link(FILENAME_CHECKOUT_PAYMENT, 'payment_error=' . $this->code.'&error='.urlencode('TIMEOUT'), 'NONSSL', true, false));
		break;
		case 0: // connection failure
		tep_redirect(tep_href_link(FILENAME_CHECKOUT_PAYMENT, 'payment_error=' . $this->code.'&error='.urlencode('CONNECT_FAIL'), 'NONSSL', true, false));
		break;
		default: // curl/socket error
		tep_redirect(tep_href_link(FILENAME_CHECKOUT_PAYMENT, 'payment_error=' . $this->code.'&error='.urlencode($errornumber.': '.$errortext), 'NONSSL', true, false));
		break;
	}
}
else{
	// is the payment verified?
	if($result != '0'){
		// payment is not ok 
	tep_redirect(tep_href_link(FILENAME_CHECKOUT_PAYMENT, 'payment_error='.$this->code.'&error='.urlencode($result), 'NONSSL', true, false));
}
// payment is ok
return true;
}

 

Listing 3: Formatting errors

// displays/formats error
function get_error() {
	global $HTTP_GET_VARS, $language;

	// ensure we have all texts defined
	require_once(DIR_WS_LANGUAGES . $language . '/modules/payment/'.$this->code.'.php');

	$error = '';
	$error_text['title'] = MODULE_PAYMENT_SAMPLE_TEXT_ERROR_TITLE;
	if(isset($HTTP_GET_VARS['error']))
		$error = urldecode($HTTP_GET_VARS['error']); // otherwise default error is displayed
	switch($error){
	case 'TIMEOUT':
		$error_text['error'] = MODULE_PAYMENT_SAMPLE_TEXT_ERROR_TIMEOUT;
		break;
	case 'CONNECT_FAIL':
		$error_text['error'] = MODULE_PAYMENT_LUUP_SAMPLE_ERROR_CONNECT_FAIL;
		break;
	/*
		include additional handling for gateway specific errors here
	*/
	default: //other error
		$error_text['error'] = MODULE_PAYMENT_SAMPLE_TEXT_ERROR_UNKNOWN ." ($error)";
		break;
}
return $error_text;
}

 

Developing a payment module may be a complicated and time-consuming task. I hope this post helps you to understand how the payment process works and how the payment module's functions are used - and in the end makes it easier for you to write your own custom payment module. :thumbsup:

 

Eskil

Link to comment
Share on other sites

  • 1 year later...
  • 2 years later...

I try to make a payment module, but I do not know, how to pass avariables to gateway.

 

in function process_button(), What to do with the the Variable $process_button_string.

is it the parameter add after the $this->form_action_url?

 

who can tell me?

 

thanks.

Link to comment
Share on other sites

  • 2 years later...
  • 2 weeks later...
  • 1 month later...
  • 1 year later...
  • 1 year later...

Hi....

 

So my system is using paypal standard and banktransfer as thier payment method.

I have some custom functions such as discount coupon and member credit feature.. Some requires to update/deduct the credit balance and coupon status after being applied through successful transactions. However, my banktransfer (checkout_process.php) works fine without any issue (can update all the credit and coupon status), wheareas my paypal standard seems cannot update the tables..

 

Currently I placed my code under before_process, that's mean I should place under after_process?

Link to comment
Share on other sites

  • 2 months later...

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...