Posted By

chopstik on 11/18/09


Tagged

payment virtuemart SagePay


Versions (?)

SagePay Payment Method for VirtueMart


 / Published in: PHP
 

URL: http://forum.virtuemart.net/index.php?topic=55753

  1. <?php
  2.  
  3. /**
  4. * SagePay Order Confirmation Handler
  5. Tony Coyle / Chopstik Internet / 15/5/2009 / [email protected]
  6. */
  7.  
  8. if( !defined( '_VALID_MOS' ) && !defined( '_JEXEC' ) ) die( 'Direct Access to '.basename(__FILE__).' is not allowed.' );
  9.  
  10. /*************************************************************
  11. Send a post request with cURL
  12. $url = URL to send request to
  13. $data = POST data to send (in URL encoded Key=value pairs)
  14. *************************************************************/
  15. function requestPost($url, $data){
  16. // Set a one-minute timeout for this script
  17.  
  18. // Initialise output variable
  19. $output = array();
  20.  
  21. // Open the cURL session
  22. $curlSession = curl_init();
  23.  
  24. // Set the URL
  25. curl_setopt ($curlSession, CURLOPT_URL, $url);
  26. // No headers, please
  27. curl_setopt ($curlSession, CURLOPT_HEADER, 0);
  28. // It's a POST request
  29. curl_setopt ($curlSession, CURLOPT_POST, 1);
  30. // Set the fields for the POST
  31. curl_setopt ($curlSession, CURLOPT_POSTFIELDS, $data);
  32. // Return it direct, don't print it out
  33. curl_setopt($curlSession, CURLOPT_RETURNTRANSFER,1);
  34. // This connection will timeout in 30 seconds
  35. curl_setopt($curlSession, CURLOPT_TIMEOUT,30);
  36. //The next two lines must be present for the kit to work with newer version of cURL
  37. //You should remove them if you have any problems in earlier versions of cURL
  38. curl_setopt($curlSession, CURLOPT_SSL_VERIFYPEER, FALSE);
  39. curl_setopt($curlSession, CURLOPT_SSL_VERIFYHOST, 1);
  40.  
  41. //Send the request and store the result in an array
  42.  
  43. $rawresponse = curl_exec($curlSession);
  44. //Store the raw response for later as it's useful to see for integration and understanding
  45. $_SESSION["rawresponse"]=$rawresponse;
  46. //Split response into name=value pairs
  47. $response = split(chr(10), $rawresponse);
  48. // Check that a connection was made
  49. if (curl_error($curlSession)){
  50. // If it wasn't...
  51. $output['Status'] = "FAIL";
  52. $output['StatusDetail'] = curl_error($curlSession);
  53. }
  54.  
  55. // Close the cURL session
  56. curl_close ($curlSession);
  57.  
  58. // Tokenise the response
  59. for ($i=0; $i<count($response); $i++){
  60. // Find position of first "=" character
  61. $splitAt = strpos($response[$i], "=");
  62. // Create an associative (hash) array with key/value pairs ('trim' strips excess whitespace)
  63. $output[trim(substr($response[$i], 0, $splitAt))] = trim(substr($response[$i], ($splitAt+1)));
  64. } // END for ($i=0; $i<count($response); $i++)
  65.  
  66. // Return the output
  67. return $output;
  68.  
  69.  
  70. } // END function requestPost()
  71.  
  72. // Filters unwanted characters out of an input string. Useful for tidying up FORM field inputs
  73. function cleanInput($strRawText,$strType)
  74. {
  75.  
  76. if ($strType=="Number") {
  77. $strClean="0123456789.";
  78. $bolHighOrder=false;
  79. }
  80. else if ($strType=="VendorTxCode") {
  81. $strClean="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.";
  82. $bolHighOrder=false;
  83. }
  84. else {
  85. $strClean=" ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.,'/{}@():?-_&£$=%~<>*+\"";
  86. $bolHighOrder=true;
  87. }
  88.  
  89. $strCleanedText="";
  90. $iCharPos = 0;
  91.  
  92. do
  93. {
  94. // Only include valid characters
  95. $chrThisChar=substr($strRawText,$iCharPos,1);
  96.  
  97. if (strspn($chrThisChar,$strClean,0,strlen($strClean))>0) {
  98. $strCleanedText=$strCleanedText . $chrThisChar;
  99. }
  100. else if ($bolHighOrder==true) {
  101. // Fix to allow accented characters and most high order bit chars which are harmless
  102. if (bin2hex($chrThisChar)>=191) {
  103. $strCleanedText=$strCleanedText . $chrThisChar;
  104. }
  105. }
  106.  
  107. $iCharPos=$iCharPos+1;
  108. }
  109. while ($iCharPos<strlen($strRawText));
  110.  
  111. $cleanInput = ltrim($strCleanedText);
  112. return $cleanInput;
  113.  
  114. }
  115.  
  116.  
  117. /* Begin process of colelcting posted card details and Post to SagePay for Authorisation */
  118.  
  119. /* Load the SagePay Configuration File */
  120. require_once( CLASSPATH. 'payment/ps_sagepay.cfg.php' );
  121.  
  122. /* order_id is the name of the variable that holds OUR order_number */
  123. $order_id = vmGet( $_REQUEST, "order_id" );
  124.  
  125. if(!isset($order_id) || empty($order_id)){
  126. echo $VM_LANG->_('VM_CHECKOUT_ORDERIDNOTSET');
  127. } else {
  128.  
  129. // Gather order & user details
  130. $qv = "SELECT order_id, order_number, order_total, user_info_id, user_id FROM #__{vm}_orders WHERE order_id='".$order_id."'";
  131. $db = new ps_DB;
  132. $db->query($qv);
  133. $db->next_record();
  134. $d['order_id'] = $db->f("order_id");
  135.  
  136. // SagePay config
  137. $strConnectTo = 'LIVE'; // `TEST` in development and `LIVE` when you're into production
  138. $strProtocol="2.23";
  139. $strTransactionType = "PAYMENT";
  140. $strVendorName = SAGEPAY_VENDOR_NAME;
  141. $strCurrency = "GBP";
  142. $strVendorTxCode = $db->f("order_number");
  143.  
  144. /* For testing transactions on the Protx test server, use the following card numbers.
  145. NB: there are NO dummy cards to use on the Live server. Actual Live bank cards must be used.
  146.  
  147. Card Type Protx Card Name Card Number Issue Number
  148. Visa VISA 4929000000006 n/a
  149. Visa Delta DELTA 4462000000000003 n/a
  150. Visa Electron UK Debit UKE 4917300000000008 n/a
  151. Mastercard MC 5404000000000001 n/a
  152. UK Maestro MAESTRO 5641820000000005 01
  153. International Maestro MAESTRO 300000000000000004 n/a
  154. Solo SOLO 6334900000000005 1
  155. American Express AMEX 374200000000004 n/a
  156. Japan Credit Bureau (JCB) JCB 3569990000000009 n/a
  157. Diners Club DC 36000000000008 n/a
  158.  
  159. You'll also need to supply an Expiry Date in the future and the following values for CV2, Billing Address Numbers and Billing Post Code Numbers. These are the only values which will return as Matched. Any other values will return a Not Matched.
  160.  
  161. CV2: 123
  162. Billing Address: 88
  163. Billing PostCode: 412
  164.  
  165. You'll also need to enter the 3D Secure password as password (it's case sensitive) so that the 3D Secure authentication returns Fully Authenticated.
  166. Please note that the Protx test server is not integrated with any banks, therefore no monies will be transferred as a result of these tests.
  167. */
  168.  
  169. if ($strConnectTo=="LIVE")
  170. {
  171. $strAbortURL="https://live.sagepay.com/gateway/service/abort.vsp";
  172. $strAuthoriseURL="https://live.sagepay.com/gateway/service/authorise.vsp";
  173. $strCancelURL="https://live.sagepay.com/gateway/service/cancel.vsp";
  174. $strPurchaseURL="https://live.sagepay.com/gateway/service/vspdirect-register.vsp";
  175. $strRefundURL="https://live.sagepay.com/gateway/service/refund.vsp";
  176. $strReleaseURL="https://live.sagepay.com/gateway/service/release.vsp";
  177. $strRepeatURL="https://live.sagepay.com/gateway/service/repeat.vsp";
  178. $strVoidURL="https://live.sagepay.com/gateway/service/void.vsp";
  179. $str3DCallbackPage="https://live.sagepay.com/gateway/service/direct3dcallback.vsp";
  180. $strPayPalCompletionURL="https://live.sagepay.com/gateway/service/complete.vsp";
  181. }
  182. elseif ($strConnectTo=="TEST")
  183. {
  184. $strAbortURL="https://test.sagepay.com/gateway/service/abort.vsp";
  185. $strAuthoriseURL="https://test.sagepay.com/gateway/service/authorise.vsp";
  186. $strCancelURL="https://test.sagepay.com/gateway/service/cancel.vsp";
  187. $strPurchaseURL="https://test.sagepay.com/gateway/service/vspdirect-register.vsp";
  188. $strRefundURL="https://test.sagepay.com/gateway/service/refund.vsp";
  189. $strReleaseURL="https://test.sagepay.com/gateway/service/release.vsp";
  190. $strRepeatURL="https://test.sagepay.com/gateway/service/repeat.vsp";
  191. $strVoidURL="https://test.sagepay.com/gateway/service/void.vsp";
  192. $str3DCallbackPage="https://test.sagepay.com/gateway/service/direct3dcallback.vsp";
  193. $strPayPalCompletionURL="https://test.sagepay.com/gateway/service/complete.vsp";
  194. }
  195. else
  196. {
  197. $strAbortURL="https://test.sagepay.com/simulator/VSPServerGateway.asp?Service=VendorAbortTx";
  198. $strAuthoriseURL="https://test.sagepay.com/simulator/VSPServerGateway.asp?Service=VendorAuthoriseTx";
  199. $strCancelURL="https://test.sagepay.com/simulator/VSPServerGateway.asp?Service=VendorCancelTx";
  200. $strPurchaseURL="https://test.sagepay.com/simulator/VSPDirectGateway.asp";
  201. $strRefundURL="https://test.sagepay.com/simulator/VSPServerGateway.asp?Service=VendorRefundTx";
  202. $strReleaseURL="https://test.sagepay.com/simulator/VSPServerGateway.asp?Service=VendorReleaseTx";
  203. $strRepeatURL="https://test.sagepay.com/simulator/VSPServerGateway.asp?Service=VendorRepeatTx";
  204. $strVoidURL="https://test.sagepay.com/simulator/VSPServerGateway.asp?Service=VendorVoidTx";
  205. $str3DCallbackPage="https://test.sagepay.com/simulator/VSPDirectCallback.asp";
  206. $strPayPalCompletionURL="https://test.sagepay.com/simulator/paypalcomplete.asp";
  207. }
  208.  
  209. // get our user details from the joomla / vm tables
  210. $q = "SELECT * FROM #__vm_user_info WHERE user_id='".$db->f("user_id")."' AND address_type='BT'";
  211. $dbbt = new ps_DB;
  212. $dbbt->setQuery($q);
  213. $dbbt->query();
  214. $dbbt->next_record();
  215.  
  216. // Get ship_to information
  217. $q2 = "SELECT * FROM #__vm_user_info WHERE user_id='".$db->f("user_id")."' AND address_type='ST'";;
  218. $dbst = new ps_DB;
  219. $dbst->setQuery($q2);
  220. $dbst->query();
  221. if($dbst->num_rows() >= 1){
  222. $dbst->next_record();
  223. }else{
  224. $dbst = $dbbt;
  225. }
  226.  
  227. // Extract Card Details from the page
  228. $strCardType=cleanInput($_REQUEST["CardType"],"Text");
  229. $strCardHolder=substr($_REQUEST["CardHolder"],0,100);
  230. $strCardNumber=cleanInput($_REQUEST["CardNumber"],"Number");
  231. $strStartDate=cleanInput($_REQUEST["StartDate"],"Number");
  232. $strExpiryDate=cleanInput($_REQUEST["ExpiryDate"],"Number");
  233. $strIssueNumber=cleanInput($_REQUEST["IssueNumber"],"Number");
  234. $strCV2=cleanInput($_REQUEST["CV2"],"Number");
  235.  
  236. // Now create the Sage Pay Direct POST
  237.  
  238. /* Now to build the Sage Pay Direct POST. For more details see the Sage Pay Direct Protocol 2.23
  239. ** NB: Fields potentially containing non ASCII characters are URLEncoded when included in the POST */
  240. $strPost="VPSProtocol=" . $strProtocol;
  241. $strPost=$strPost . "&TxType=" . $strTransactionType; //PAYMENT by default. You can change this in the includes file
  242. $strPost=$strPost . "&Vendor=" . $strVendorName;
  243. $strPost=$strPost . "&VendorTxCode=" . $strVendorTxCode; //As generated above
  244. $strPost=$strPost . "&Amount=" . number_format($db->f("order_total"),2); //Formatted to 2 decimal places with leading digit but no commas or currency symbols **
  245. $strPost=$strPost . "&Currency=" . $strCurrency;
  246. // Up to 100 chars of free format description
  247. $strPost=$strPost . "&Description=" . urlencode('Tikidrums Order (#'.$db->f("order_id").')');
  248. $strPost=$strPost . "&CardHolder=" . $strCardHolder;
  249. $strPost=$strPost . "&CardNumber=" . $strCardNumber;
  250. if (strlen($strStartDate)>0)
  251. $strPost=$strPost . "&StartDate=" . $strStartDate;
  252. $strPost=$strPost . "&ExpiryDate=" . $strExpiryDate;
  253. if (strlen($strIssueNumber)>0)
  254. $strPost=$strPost . "&IssueNumber=" . $strIssueNumber;
  255. $strPost=$strPost . "&CV2=" . $strCV2;
  256. $strPost=$strPost . "&CardType=" . $strCardType;
  257. $strPost=$strPost . "&CustomerEMail=" . urlencode($_SESSION["strCustomerEMail"]);
  258. $strPost=$strPost . "&ClientIPAddress=" . $_SERVER['REMOTE_ADDR'];
  259. $strPost=$strPost . "&AccountType=E";
  260.  
  261. // SagePay requires a 2-letter country code, not 3 which Joomla is producing
  262. // Gather the 2-letter version from `jos_vm_country`
  263. $q = "SELECT country_2_code FROM #__vm_country WHERE country_3_code='".$dbbt->f("country")."'";
  264. $dbbtt = new ps_DB;
  265. $dbbtt->setQuery($q);
  266. $dbbtt->query();
  267. $dbbtt->next_record();
  268.  
  269. /* Billing Details
  270. ** This section is optional in its entirety but if one field of the address is provided then all non-optional fields must be provided
  271. ** If AVS/CV2 is ON for your account, or, if paypal cardtype is specified and its not via PayPal Express then this section is compulsory */
  272. $strPost=$strPost . "&BillingFirstnames=" . urlencode($dbbt->f("first_name"));
  273. $strPost=$strPost . "&BillingSurname=" . urlencode($dbbt->f("last_name"));
  274. $strPost=$strPost . "&BillingAddress1=" . urlencode($dbbt->f("address_1"));
  275. if (strlen($dbbt->f("address_2")) > 0) $strPost=$strPost . "&BillingAddress2=" . urlencode($dbbt->f("address_2"));
  276. $strPost=$strPost . "&BillingCity=" . urlencode($dbbt->f("city"));
  277. $strPost=$strPost . "&BillingPostCode=" . urlencode($dbbt->f("zip"));
  278. $strPost=$strPost . "&BillingCountry=" . urlencode($dbbtt->f("country_2_code"));
  279. // only supply a state for US customers
  280. if ((strlen($dbbt->f("state")) > 0) && ($dbbtt->f("country_2_code") == "US")) $strPost=$strPost . "&BillingState=" . urlencode($dbbt->f("state"));
  281. if (strlen($dbbt->f("phone_1")) > 0) $strPost=$strPost . "&BillingPhone=" . urlencode($dbbt->f("phone_1"));
  282.  
  283. // SagePay requires a 2-letter country code, not 3 which Joomla is producing
  284. // Search the 2-letter version from `jos_vm_country`
  285. $q = "SELECT country_2_code FROM #__vm_country WHERE country_3_code='".$dbst->f("country")."'";
  286. $dbstt = new ps_DB;
  287. $dbstt->setQuery($q);
  288. $dbstt->query();
  289. $dbstt->next_record();
  290.  
  291. /* Delivery Details
  292. ** This section is optional in its entirety but if one field of the address is provided then all non-optional fields must be provided
  293. ** If paypal cardtype is specified then this section is compulsory */
  294. $strPost=$strPost . "&DeliveryFirstnames=" . urlencode($dbst->f("first_name"));
  295. $strPost=$strPost . "&DeliverySurname=" . urlencode($dbst->f("last_name"));
  296. $strPost=$strPost . "&DeliveryAddress1=" . urlencode($dbst->f("address_1"));
  297. if (strlen($dbst->f("address_2")) > 0) $strPost=$strPost . "&DeliveryAddress2=" . urlencode($dbst->f("address_2"));
  298. $strPost=$strPost . "&DeliveryCity=" . urlencode($dbst->f("city"));
  299. $strPost=$strPost . "&DeliveryPostCode=" . urlencode($dbst->f("zip"));
  300. $strPost=$strPost . "&DeliveryCountry=" . urlencode($dbstt->f("country_2_code"));
  301. // only supply a state for US customers
  302. if ((strlen($dbst->f("state")) > 0) && ($dbstt->f("country_2_code") == "US")) $strPost=$strPost . "&DeliveryState=" . urlencode($dbst->f("state")); if (strlen($dbst->f("phone_1")) > 0) $strPost=$strPost . "&DeliveryPhone=" . urlencode($dbst->f("phone_1"));
  303.  
  304. /* The full transaction registration POST has now been built **
  305. ** Send the post to the target URL
  306. ** if anything goes wrong with the connection process:
  307. ** - $arrResponse["Status"] will be 'FAIL';
  308. ** - $arrResponse["StatusDetail"] will be set to describe the problem
  309. ** Data is posted to strPurchaseURL which is set depending on whether you are using SIMULATOR, TEST or LIVE */
  310. $arrResponse = requestPost($strPurchaseURL, $strPost);
  311.  
  312. /* Analyse the response from Sage Pay Direct to check that everything is okay
  313. ** Registration results come back in the Status and StatusDetail fields */
  314. $strStatus=$arrResponse["Status"];
  315. $strStatusDetail=$arrResponse["StatusDetail"];
  316.  
  317. /* If this isn't 3D-Auth, then this is an authorisation result (either successful or otherwise) **
  318. ** Get the results form the POST if they are there */
  319. $strVPSTxId=$arrResponse["VPSTxId"];
  320. $strSecurityKey=$arrResponse["SecurityKey"];
  321. $strTxAuthNo=$arrResponse["TxAuthNo"];
  322. $strAVSCV2=$arrResponse["AVSCV2"];
  323. $strAddressResult=$arrResponse["AddressResult"];
  324. $strPostCodeResult=$arrResponse["PostCodeResult"];
  325. $strCV2Result=$arrResponse["CV2Result"];
  326. $str3DSecureStatus=$arrResponse["3DSecureStatus"];
  327. $strCAVV=$arrResponse["CAVV"];
  328.  
  329. // Update the database and redirect the user appropriately
  330. if ($strStatus=="OK")
  331. $strDBStatus="AUTHORISED - The transaction was successfully authorised with the bank.";
  332. elseif ($strStatus=="MALFORMED")
  333. $strDBStatus="MALFORMED - The StatusDetail was:" . mysql_real_escape_string(substr($strStatusDetail,0,255));
  334. elseif ($strStatus=="INVALID")
  335. $strDBStatus="INVALID - The StatusDetail was:" . mysql_real_escape_string(substr($strStatusDetail,0,255));
  336. elseif ($strStatus=="NOTAUTHED")
  337. $strDBStatus="DECLINED - The transaction was not authorised by the bank.";
  338. elseif ($strStatus=="REJECTED")
  339. $strDBStatus="REJECTED - The transaction was failed by your 3D-Secure or AVS/CV2 rule-bases.";
  340. elseif ($strStatus=="AUTHENTICATED")
  341. $strDBStatus="AUTHENTICATED - The transaction was successfully 3D-Secure Authenticated and can now be Authorised.";
  342. elseif ($strStatus=="REGISTERED")
  343. $strDBStatus="REGISTERED - The transaction was could not be 3D-Secure Authenticated, but has been registered to be Authorised.";
  344. elseif ($strStatus=="ERROR")
  345. $strDBStatus="ERROR - There was an error during the payment process. The error details are: " . mysql_real_escape_string($strStatusDetail);
  346. else
  347. $strDBStatus="UNKNOWN - An unknown status was returned from Sage Pay. The Status was: " . mysql_real_escape_string($strStatus) . ", with StatusDetail:" . mysql_real_escape_string($strStatusDetail);
  348.  
  349.  
  350. // UPDATE THE ORDER STATUS to 'CONFIRMED'
  351. if (($strStatus=="OK")||($strStatus=="AUTHENTICATED")||($strStatus=="REGISTERED")){
  352.  
  353. // SUCCESS: UPDATE THE ORDER STATUS to 'CONFIRMED'
  354. $d['order_status'] = 'C';
  355. require_once ( CLASSPATH . 'ps_order.php' );
  356. $ps_order= new ps_order;
  357. $ps_order->order_status_update($d);
  358.  
  359. $d["order_payment_log"] = $VM_LANG->_('PHPSHOP_PAYMENT_TRANSACTION_SUCCESS').": ".$strDBStatus;
  360. $d["order_payment_trans_id"] = $strVendorTxCode;
  361.  
  362. // Right-o we have a paid for order, lets e-mail the customer
  363. require_once( CLASSPATH. 'ps_checkout.php' );
  364. $ps_checkout = new ps_checkout;
  365. $ps_checkout->email_receipt($d['order_id']);
  366.  
  367. } else {
  368.  
  369. // FAILED: UPDATE THE ORDER STATUS to 'PENDING'
  370. $d['order_status'] = 'P';
  371. require_once ( CLASSPATH . 'ps_order.php' );
  372. $ps_order= new ps_order;
  373. $ps_order->order_status_update($d);
  374.  
  375. $d["order_payment_log"] = $VM_LANG->_('PHPSHOP_PAYMENT_TRANSACTION_FAILURE').": ".$strDBStatus;
  376. $d["order_payment_trans_id"] = $strVendorTxCode;
  377. }
  378.  
  379. // record additional transaction details
  380. $q = "UPDATE #__{vm}_order_payment SET ";
  381. $q .="order_payment_log='".$d["order_payment_log"]."',";
  382. $q .="order_payment_trans_id='".$d["order_payment_trans_id"]."' ";
  383. $q .="WHERE order_id='".$d['order_id']."' ";
  384. $db->query( $q );
  385.  
  386. // Having updated our order transfer user to the order details page
  387. vmRedirect(SECUREURL."index.php?option=com_virtuemart&page=account.order_details&order_id=".$d['order_id']);
  388. }
  389.  
  390. ?>

Report this snippet  

Comments

RSS Icon Subscribe to comments
Posted By: chopstik on August 14, 2013

This code is now available as a git repo - https://github.com/chopstik/sagepay-virtuemart

You need to login to post a comment.