Eskil Posted January 3, 2006 Share Posted January 3, 2006 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 katapofatico and camargo_w 2 Quote Link to comment Share on other sites More sharing options...
Delawen Posted November 23, 2007 Share Posted November 23, 2007 Is there any other "howto" for payment modules? Quote Link to comment Share on other sites More sharing options...
Guest Posted March 28, 2010 Share Posted March 28, 2010 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. Quote Link to comment Share on other sites More sharing options...
raja_cbt Posted November 2, 2012 Share Posted November 2, 2012 Hi I need to integrate hdfc payment gateway with oscommerce. Can anyone please help me Please? Thanks Raja Mumbai/India Quote Link to comment Share on other sites More sharing options...
JuanIgnacio Posted November 15, 2012 Share Posted November 15, 2012 Great post. Would be great to have it updated for osCommerce 2.3.3 Quote Link to comment Share on other sites More sharing options...
camargo_w Posted January 11, 2013 Share Posted January 11, 2013 Thanks for the post ! Will be very helpfull :) Best regards Quote Link to comment Share on other sites More sharing options...
princepatel Posted April 24, 2014 Share Posted April 24, 2014 Thank you. Really a nice post. Can we use after_process() in place of validate_payment() Once again thank you. Quote Link to comment Share on other sites More sharing options...
okstmtcc Posted May 5, 2015 Share Posted May 5, 2015 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? Quote Link to comment Share on other sites More sharing options...
pcha Posted July 21, 2015 Share Posted July 21, 2015 Thank you, it's helpfu, but how can I set the information and configuration to show in the dashboard? Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.