Beluga Posted September 28, 2013 Share Posted September 28, 2013 (edited) I'm using Bankwire as base to build a Checkout.fi module (link to their reference PHP implementation). I have printed the code of their PHP example, their osCommerce module and PrestaShop's Bankwire on paper and studied them carefully, so I have a pretty good idea of how they work, but I'm a newbie to PrestaShop and I'm having trouble figuring out how to integrate this. Anyways, I dived into practice to see what type of interesting error messages I can produce and unsurprisingly I soon got a taste of them. When confirming an order and moving to payment.php, this error appears, pointing to the $coData = array(); line (it is near the bottom of the code): Parse error: syntax error, unexpected '$coData' (T_VARIABLE) I have no idea, why this is unexpected and would appreciate any advice. Here are the contents of my payment.php: class Checkout { private $version = "0001"; private $language = "FI"; private $country = "FIN"; private $currency = "EUR"; private $device = "1"; private $content = "1"; private $type = "0"; private $algorithm = "2"; private $merchant = ""; private $password = ""; private $stamp = 0; private $amount = 0; private $reference = ""; private $message = ""; private $return = ""; private $cancel = ""; private $reject = ""; private $delayed = ""; private $delivery_date = ""; private $firstname = ""; private $familyname = ""; private $address = ""; private $postcode = ""; private $postoffice = ""; private $status = ""; private $email = ""; public function __construct($merchant, $password) { $this->merchant = $merchant; // merchant id $this->password = $password; // security key (about 80 chars) } /* * generates MAC and prepares values for creating payment */ public function getCheckoutObject($data) { // overwrite default values foreach($data as $key => $value) { $this->{$key} = $value; } $mac = strtoupper(md5("{$this->version}+{$this->stamp}+{$this->amount}+{$this->reference}+{$this->message}+{$this->language}+{$this->merchant}+{$this->return}+{$this->cancel}+{$this->reject}+{$this->delayed}+{$this->country}+{$this->currency}+{$this->device}+{$this->content}+{$this->type}+{$this->algorithm}+{$this->delivery_date}+{$this->firstname}+{$this->familyname}+{$this->address}+{$this->postcode}+{$this->postoffice}+{$this->password}")); $post['VERSION'] = $this->version; $post['STAMP'] = $this->stamp; $post['AMOUNT'] = $this->amount; $post['REFERENCE'] = $this->reference; $post['MESSAGE'] = $this->message; $post['LANGUAGE'] = $this->language; $post['MERCHANT'] = $this->merchant; $post['RETURN'] = $this->return; $post['CANCEL'] = $this->cancel; $post['REJECT'] = $this->reject; $post['DELAYED'] = $this->delayed; $post['COUNTRY'] = $this->country; $post['CURRENCY'] = $this->currency; $post['DEVICE'] = $this->device; $post['CONTENT'] = $this->content; $post['TYPE'] = $this->type; $post['ALGORITHM'] = $this->algorithm; $post['DELIVERY_DATE'] = $this->delivery_date; $post['FIRSTNAME'] = $this->firstname; $post['FAMILYNAME'] = $this->familyname; $post['ADDRESS'] = $this->address; $post['POSTCODE'] = $this->postcode; $post['POSTOFFICE'] = $this->postoffice; $post['MAC'] = $mac; $post['EMAIL'] = $this->email; $post['PHONE'] = $this->phone; return $post; } /* * returns payment information in XML */ public function getCheckoutXML($data) { $this->device = "10"; return $this->sendPost($this->getCheckoutObject($data)); } private function sendPost($post) { $options = array( CURLOPT_POST => 1, CURLOPT_HEADER => 0, CURLOPT_URL => 'https://payment.checkout.fi', CURLOPT_FRESH_CONNECT => 1, CURLOPT_RETURNTRANSFER => 1, CURLOPT_FORBID_REUSE => 1, CURLOPT_TIMEOUT => 20, CURLOPT_POSTFIELDS => http_build_query($post) ); $ch = curl_init(); curl_setopt_array($ch, $options); $result = curl_exec($ch); curl_close($ch); return $result; } public function validateCheckout($data) { $generatedMac = strtoupper(md5("{$this->password}&{$data['VERSION']}&{$data['STAMP']}&{$data['REFERENCE']}&{$data['PAYMENT']}&{$data['STATUS']}&{$data['ALGORITHM']}")); if($data['MAC'] == $generatedMac) return true; else return false; } public function isPaid($status) { if(in_array($status, array(2, 4, 5, 6, 7, 8, 9, 10))) return true; else return false; } } // class Checkout class checkoutfiPaymentModuleFrontController extends ModuleFrontController { public $ssl = true; /** * @see FrontController::initContent() */ public function initContent() { $this->display_column_left = false; parent::initContent(); $cart = $this->context->cart; /*if (!$this->module->checkCurrency($cart)) Tools::redirect('index.php?controller=order');*/ $summa = number_format($this->context->cart->getOrderTotal * 100, 0, '',''); $co = new Checkout(375917, "SAIPPUAKAUPPIAS"); // merchantID and securitykey (normally about 80 chars) $return = $this->context->link->getModuleLink('checkoutfi', 'validation', [], true) // Order information $coData = array(); $coData["stamp"] = time(); $coData["reference"] = $coData['stamp']; $coData["message"] = "testiviesti"; $coData["return"] = $return; $coData["cancel"] = $cancel; $coData["reject"] = $reject; $coData["delayed"] = $delayed; $coData["amount"] = $summa; $coData["delivery_date"] = date("Ymd"); $coData["firstname"] = "testietunimi"; $coData["familyname"] = "testisukunimi"; $coData["address"] = "testiosoite"; $coData["postcode"] = "00340"; $coData["postoffice"] = "testipostikonttori"; // change stamp for xml method $coData['stamp'] = time() + 1; $response = $co->getCheckoutXML($coData); // get payment button data $xml = simplexml_load_string($response); $this->context->smarty->assign(array( 'nbProducts' => $cart->nbProducts(), 'cust_currency' => $cart->id_currency, 'xmlbanks' => $xml->payments->payment->banks, 'total' => $cart->getOrderTotal(true, Cart::BOTH), 'this_path' => $this->module->getPathUri(), 'this_path_bw' => $this->module->getPathUri(), 'this_path_ssl' => Tools::getShopDomainSsl(true, true).__PS_BASE_URI__.'modules/'.$this->module->name.'/' )); $this->setTemplate('payment_execution.tpl'); } } Edited October 12, 2013 by Beluga (see edit history) Link to comment Share on other sites More sharing options...
PascalVG Posted September 29, 2013 Share Posted September 29, 2013 Hi Beluga, Look at the line before the $coData = array(): $return = $this->context->link->getModuleLink('checkoutfi', 'validation', [], true); is missing :-) pascal Link to comment Share on other sites More sharing options...
Beluga Posted September 29, 2013 Author Share Posted September 29, 2013 I love you, Pascal! Now I got into the real errors. First I realized that cURL over SSL won't work on my local (Windows 7 64-bit) machine, not with Bitnami Wampstack or XAMPP. So I copied my PS to a server where it works (I mean the PHP example from Checkout.fi renders just fine). The first error was about the '[' character in $return = $this->context->link->getModuleLink('checkoutfi', 'validation', [], true); Well, I simplified things a bit and used the "dummy data" from the PHP example. Now I'd like to see Smarty working. I have this in my payment_execution.tpl: {foreach $xmlbanks as $bankX} {foreach $bankX as $bank} <div class='C1'> <form action='{$bank@url}' method='post'> {foreach $bank as $key=>$value} <input type='hidden' name='{$key}' value='{$value|escape:"html"}'> {/foreach} <span><input type='image' src='{$bank@icon}'> </span> <div> {$bank@name} </div> </form> </div> {/foreach} {/foreach} And it throws: Notice: Undefined property: Smarty_Variable::$name ... on line 87 And doesn't render the bank buttons (the page renders otherwise fine). Here is how Smarty has processed it in the cached file: <?php $_smarty_tpl->tpl_vars['bankX'] = new Smarty_Variable; $_smarty_tpl->tpl_vars['bankX']->_loop = false; $_from = $_smarty_tpl->tpl_vars['xmlbanks']->value; if (!is_array($_from) && !is_object($_from)) { settype($_from, 'array');} foreach ($_from as $_smarty_tpl->tpl_vars['bankX']->key => $_smarty_tpl->tpl_vars['bankX']->value){ $_smarty_tpl->tpl_vars['bankX']->_loop = true; ?> <?php $_smarty_tpl->tpl_vars['bank'] = new Smarty_Variable; $_smarty_tpl->tpl_vars['bank']->_loop = false; $_from = $_smarty_tpl->tpl_vars['bankX']->value; if (!is_array($_from) && !is_object($_from)) { settype($_from, 'array');} foreach ($_from as $_smarty_tpl->tpl_vars['bank']->key => $_smarty_tpl->tpl_vars['bank']->value){ $_smarty_tpl->tpl_vars['bank']->_loop = true; ?> <div class='C1'> <form action='<?php echo $_smarty_tpl->tpl_vars['bank']->url;?> ' method='post'> <?php $_smarty_tpl->tpl_vars['value'] = new Smarty_Variable; $_smarty_tpl->tpl_vars['value']->_loop = false; $_smarty_tpl->tpl_vars['key'] = new Smarty_Variable; $_from = $_smarty_tpl->tpl_vars['bank']->value; if (!is_array($_from) && !is_object($_from)) { settype($_from, 'array');} foreach ($_from as $_smarty_tpl->tpl_vars['value']->key => $_smarty_tpl->tpl_vars['value']->value){ $_smarty_tpl->tpl_vars['value']->_loop = true; $_smarty_tpl->tpl_vars['key']->value = $_smarty_tpl->tpl_vars['value']->key; ?> <input type='hidden' name='<?php echo $_smarty_tpl->tpl_vars['key']->value;?> ' value='<?php echo htmlspecialchars($_smarty_tpl->tpl_vars['value']->value, ENT_QUOTES, 'UTF-8', true);?> '> <?php } ?> <span><input type='image' src='<?php echo $_smarty_tpl->tpl_vars['bank']->icon;?> '> </span> <div> <?php echo $_smarty_tpl->tpl_vars['bank']->name;?> </div> </form> Line 87 is <?php echo $_smarty_tpl->tpl_vars['bank']->name;?> What might be the correct syntax? Link to comment Share on other sites More sharing options...
PascalVG Posted September 30, 2013 Share Posted September 30, 2013 I think the @ is causing the problem here, as it doesn't get escaped. Try the way they use it in the sample code: $bank['name'] (and $bank['icon'] and $bank['url'] for that matter) give it a try pascal Link to comment Share on other sites More sharing options...
Beluga Posted September 30, 2013 Author Share Posted September 30, 2013 (edited) I think the @ is causing the problem here, as it doesn't get escaped. Try the way they use it in the sample code: $bank['name'] (and $bank['icon'] and $bank['url'] for that matter) give it a try pascal Thank you, it worked! I used the @ because I read from the Smarty docs Although you can retrieve the array key with the syntax {foreach $myArray as $myKey => $myValue}, the key is always available as $myValue@key within the foreach loop. I feel like I'm making some progress. I figured out the way to get the order total in cents is: $summa = number_format($cart->getOrderTotal(true, 3) * 100, 0, '',''); But now I'm interested in validating the order as paid in PrestaShop. I moved the "class Checkout" from payment.php to the main checkoutfi.php and it worked ok, but when I initially tried to install the module it didn't allow me to have the class Checkout in checkoutfi.php.. so I wonder why it works now or if the cache hasn't been updated? Here is the error I get in the Modules page, if I have "class Checkout" in checkoutfi.php: Parse error: syntax error, unexpected end of file in ..\classes\module\Module.php(1077) : eval()'d code on line 237 The following module(s) could not be loaded:: checkoutfi (parse error in /modules/checkoutfi/checkoutfi.php) checkoutfi (class missing in /modules/checkoutfi/checkoutfi.php) I have this in validation.php: class checkoutfiValidationModuleFrontController extends ModuleFrontController { /** * @see FrontController::postProcess() */ public function postProcess() { $cart = $this->context->cart; if ($cart->id_customer == 0 || $cart->id_address_delivery == 0 || $cart->id_address_invoice == 0 || !$this->module->active) Tools::redirect('index.php?controller=order&step=1'); // Check that this payment option is still available in case the customer changed his address just before the end of the checkout process $authorized = false; foreach (Module::getPaymentModules() as $module) if ($module['name'] == 'checkoutfi') { $authorized = true; break; } if (!$authorized) die($this->module->l('This payment method is not available.', 'validation')); $customer = new Customer($cart->id_customer); if (!Validate::isLoadedObject($customer)) Tools::redirect('index.php?controller=order&step=1'); $currency = $this->context->currency; $total = (float)$cart->getOrderTotal(true, Cart::BOTH); if(isset($_GET['MAC'])) { if($co->validateCheckout($_GET)) { if($co->isPaid($_GET['STATUS'])) { $this->module->validateOrder($cart->id, Configuration::get('PS_OS_CHECKOUTFI'), $total, $this->module->displayName, NULL, $mailVars, (int)$currency->id, false, $customer->secure_key); Tools::redirect('index.php?controller=order-confirmation&id_cart='.$cart->id.'&id_module='.$this->module->id.'&id_order='.$this->module->currentOrder.'&key='.$customer->secure_key); } } } } } I'm passing this as the return url for the bank through payment.php: $return = $this->context->link->getModuleLink('checkoutfi', 'validate'); It outputs the url http://mysite.com/index.php?fc=module&module=checkoutfi&controller=validate&id_lang=2 Then when the payment gateway is supposed to redirect back to the store, it produces an error about infinite redirects and I can see this URL in the address bar: https://payment.checkout.fi/IEsuX02FRS/fi/done?VERSION=0001&STAMP=1380520435&REFERENCE=12344&PAYMENT=10247204&STATUS=2&ALGORITHM=2&MAC=0306F690FE65627D1594F65BAFF9643E So the status is ok (2), but is my validation.php set up incorrectly to receive the info? Edited October 2, 2013 by Beluga (see edit history) Link to comment Share on other sites More sharing options...
Beluga Posted October 2, 2013 Author Share Posted October 2, 2013 It seems to be that Prestashop does not accept the redirect from the gateway because of the lack of session info or cookie. How can I get PS to accept the redirect? It is explicitly mentioned in the gateway API that the ecommerce software must accept the HTTP GET request even without cookie or session info. Link to comment Share on other sites More sharing options...
Beluga Posted October 9, 2013 Author Share Posted October 9, 2013 Just popping in to say that the problem was entirely due to my sloppiness or code blindness. I fully expect to get this working in the next couple of days and I will publish the module for review. Link to comment Share on other sites More sharing options...
Beluga Posted October 11, 2013 Author Share Posted October 11, 2013 Ok, here is a work in progress version of the module: checkoutfi.zip It still needs a bit more polish. It has a config with the testing merchant id and security key prefilled. Most crucially, I'm interested in how I could pass the session info to validate.php. In payment.php I have $tilaustunniste = time().rand(0,9999); // unique order identification $tilaustunniste = str_pad($tilaustunniste, 9, "1", STR_PAD_RIGHT); session_start(); $_SESSION['timestamp'] = $tilaustunniste; $psStamp = md5($_SESSION['timestamp'] . $this->lisaaViitenumeroonTarkiste($tilaustunniste) . "SAIPPUAKAUPPIAS"); if(substr_count($return, '?')) $return .= '&psStamp='.$psStamp; else $return .= '?psStamp='.$psStamp; Then in validation.php I have: if (isset($_SESSION['timestamp']) && $_SESSION['timestamp'] != null) { $psStamp = md5($_SESSION['timestamp'] . $_GET['STAMP'] . Configuration::get('CHECKOUTFI_PASSWORD')); if ($psStamp != $_GET['psStamp']){ die("Tilauksen tunnisteesta '" . $_GET['STAMP'] . "' laskettu md5 '" . $psStamp . "' tilauksen md5:n '" . $_GET['psStamp'] . "' kanssa."); } } These come from an osCommerce module implementation of the Checkout.fi payment gateway. In the payment_return.tpl I have Session: {$smarty.session.timestamp} which causes Smarty to throw an error that session is not defined. I'd like to get rid of useless info being passed (firstname, lastname etc.), but had some trouble when I simply removed them from payment.php and checkoutdata.php. I'll investigate this later. I'll probably have to make a new payment status type for the delayed payment. Link to comment Share on other sites More sharing options...
Beluga Posted October 12, 2013 Author Share Posted October 12, 2013 (edited) Why can PayPal module's install do this: $orderState = new OrderState(); $orderState->name = array(); but when I do it in my install, I get "Property OrderState->name is not valid" ? It does, however, create the new order state - it just doesn't create the lang description in the ps_order_state_lang table. Here is my full install function for now: public function install() { if (!parent::install() || !$this->registerHook('payment') || !$this->registerHook('paymentReturn')) return false; Configuration::updateValue('CHECKOUTFI_PASSWORD', "SAIPPUAKAUPPIAS"); Configuration::updateValue('CHECKOUTFI_MERCHANT', 375917); if (!Configuration::get('PS_OS_DELAYED')) { $orderState = new OrderState(); $orderState->name = array(); foreach (Language::getLanguages() as $language) { if (strtolower($language['iso_code']) == 'fi') $orderState->name[$language['id_lang']] = 'Checkout.fi-maksun tila viivästetty'; else $orderState->name[$language['id_lang']] = 'Checkout.fi payment state: delayed'; } $orderState->send_email = false; $orderState->color = '#DDEEFF'; $orderState->hidden = false; $orderState->delivery = false; $orderState->logable = true; $orderState->invoice = false; if ($orderState->add()) { $source = dirname(__FILE__).'/logo.gif'; $destination = dirname(__FILE__).'/../../img/os/'.(int)$orderState->id.'.gif'; copy($source, $destination); } Configuration::updateValue('PS_OS_DELAYED', (int)$orderState->id); } return true; } EDIT: Found this solution: http://www.prestashop.com/forums/topic/7643-how-to-change-default-order-status-for-some-payment-module/?view=findpost&p=1304944So now I have this working code in my install function: if (!Configuration::get('PS_OS_DELAYED')) { $values_to_insert = array( 'invoice' => 1, 'send_email' => 1, 'module_name' => $this->name, 'color' => 'RoyalBlue', 'unremovable' => 0, 'hidden' => 0, 'logable' => 1, 'delivery' => 0, 'shipped' => 0, 'paid' => 1, 'deleted' => 0); if(!Db::getInstance()->autoExecute(_DB_PREFIX_.'order_state', $values_to_insert, 'INSERT')) return false; $id_order_state = (int)Db::getInstance()->Insert_ID(); $languages = Language::getLanguages(false); foreach ($languages as $language) Db::getInstance()->autoExecute(_DB_PREFIX_.'order_state_lang', array('id_order_state'=>$id_order_state, 'id_lang'=>$language['id_lang'], 'name'=>'Checkout.fi payment state: delayed', 'template'=>''), 'INSERT'); if (!@copy(dirname(__FILE__).DIRECTORY_SEPARATOR.'logo.gif', _PS_ROOT_DIR_.DIRECTORY_SEPARATOR.'img'.DIRECTORY_SEPARATOR.'os'.DIRECTORY_SEPARATOR.$id_order_state.'.gif')) return false; Configuration::updateValue('PS_OS_DELAYED', $id_order_state); unset($id_order_state); } Edited October 14, 2013 by Beluga (see edit history) Link to comment Share on other sites More sharing options...
Beluga Posted October 13, 2013 Author Share Posted October 13, 2013 (edited) I realized it was useless for me to obsess with the session and I did it with Prestashop's cookie method, in payment.php I have: $idCookie = $this->context->cookie->id_customer; $psStamp = md5($idCookie . $this->lisaaViitenumeroonTarkiste($tilaustunniste) . Configuration::get('CHECKOUTFI_PASSWORD')); and in validation.php I have: $idCookie = $this->context->cookie->id_customer; $md5 = md5(Configuration::get('CHECKOUTFI_PASSWORD')."&{$_GET['VERSION']}&{$_GET['STAMP']}&{$_GET['REFERENCE']}&{$_GET['PAYMENT']}&{$_GET['STATUS']}&{$_GET['ALGORITHM']}"); if (strtoupper($md5) == $_GET['MAC']) { if ($_GET['STATUS'] == 2 || $_GET['STATUS'] == 3) { // 2 = maksu suoritettu / payment done // 3 = maksu viivästetty / payment delayed $psStamp = md5($idCookie . $_GET['STAMP'] . Configuration::get('CHECKOUTFI_PASSWORD')); if ($psStamp != $_GET['psStamp']){ die("Tilauksen tunnisteesta '" . $_GET['STAMP'] . "' laskettu md5 '" . $psStamp . "' tilauksen md5:n '" . $_GET['psStamp'] . "' kanssa."); } Here is the module so far, including the broken order state adding at install: checkoutfi.zip Please only use it on a test install of PS. Edited October 13, 2013 by Beluga (see edit history) Link to comment Share on other sites More sharing options...
Jerz Posted April 19, 2015 Share Posted April 19, 2015 Hi, any progress on that? I was trying to resolve it myself but for now it is looking much worse than your version. Link to comment Share on other sites More sharing options...
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now