A Qcodo based CMS/ecommerce framework
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

329 lines
15 KiB

12 years ago
  1. <?php
  2. /**
  3. * This file is a part of Quasi CMS
  4. *@package Quasi
  5. */
  6. if(!defined('QUASICMS') ) die('No Quasi.');
  7. if (!defined("PAYPALEXPRESSCHECKOUTACTION.CLASS.PHP")){
  8. define("PAYPALEXPRESSCHECKOUTACTION.CLASS.PHP",1);
  9. /**
  10. * Class PayPalExpressCheckoutAction - PayPal Express Checkout payment action
  11. *
  12. * This class provides an implementation of the PayPal Express Checkout System including the
  13. * two points of entry required. It can be activated either as a selected payment method on the
  14. * checkout review page or by a click on the "Check out with PayPal" button.
  15. *
  16. * Express Checkout consists of 2 phases:
  17. * 1. A request is sent to the server to set up the transaction details and a "token" is returned.
  18. * This token is stored in _SESSION['PayPalToken'] to be used in the second phase
  19. * Order status is left at pending and the customer is redirected to PayPal with the token as
  20. * part of the redirect URL
  21. * 2. PayPal returns the customer to the PayPalExpressReturn page - this page must exist in
  22. * database and have in it the PayPalExpressReturnModule. This module will use this action
  23. * again, calling getExpressCheckoutDetails to complete the transaction. The function will
  24. * DoExpressCheckoutPayment
  25. *
  26. * Note that this payment action requires the existance of the return and cancel pages to which
  27. * the customer is returned from PayPal - and that these pages must contain a content block with
  28. * the PayPalExpressReturnModule to complete the process. Completion will consist of everything
  29. * normally done in PaymentModule in btnPurchase_Click.
  30. *
  31. *@todo
  32. * - finish stage two, sending confirmation email on approval and completing the order
  33. *
  34. *@author Erik Winn <erikwinnmail@yahoo.com>
  35. *
  36. * $Id$
  37. *@version 0.1
  38. *
  39. *@copyright (C) 2008 by Erik Winn
  40. *@license GPL v.2
  41. This program is free software; you can redistribute it and/or modify
  42. it under the terms of the GNU General Public License as published by
  43. the Free Software Foundation; either version 2 of the License, or
  44. (at your option) any later version.
  45. This program is distributed in the hope that it will be useful,
  46. but WITHOUT ANY WARRANTY; without even the implied warranty of
  47. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  48. GNU General Public License for more details.
  49. You should have received a copy of the GNU General Public License
  50. along with this program; if not, write to the Free Software
  51. Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA
  52. *
  53. *@package Quasi
  54. * @subpackage Classes
  55. */
  56. class PayPalExpressCheckoutAction extends PayPalNVPAction
  57. {
  58. /**
  59. * PayPalExpressCheckoutAction Constructor
  60. *
  61. * This sets various defaults specific to the Express Checkout process
  62. *
  63. *@todo
  64. * - safeguard the return URLs - send session id? or, encode account id and order id ..
  65. *
  66. * @param Order objOrder - the Order to process
  67. */
  68. public function __construct(Order $objOrder)
  69. {
  70. try {
  71. parent::__construct($objOrder);
  72. } catch (QCallerException $objExc) {
  73. $objExc->IncrementOffset();
  74. throw $objExc;
  75. }
  76. //the order id may be redundant - we already send INVNUM, this may go away ..
  77. $this->aryRequestValues['RETURNURL'] = 'http://' . Quasi::$ServerName . __QUASI_SUBDIRECTORY__
  78. . '/index.php/PayPalExpressReturn?orderid=' . $this->objOrder->Id;
  79. $this->aryRequestValues['CANCELURL'] = 'http://' . Quasi::$ServerName . __QUASI_SUBDIRECTORY__
  80. . '/index.php/PayPalExpressCancel?orderid=' . $this->objOrder->Id;
  81. $this->strRedirectCgiUrl = '/cgi-bin/webscr?';
  82. $this->strTemplateUri = __QUASI_CORE_TEMPLATES__ . '/PayPalExpressCheckoutAction.tpl.php';
  83. }
  84. /**
  85. * The createRequest functions are handled by get/setExpressCheckoutDetails
  86. * as they occur before and after a customer is redirected to PayPal and are
  87. * therefor two separate requests.
  88. */
  89. protected function createPOSTRequest(){}
  90. protected function createGETRequest(){}
  91. /**
  92. * Performs any preparation steps prior to submitting an actual payment.
  93. * We submit a call to the SetExpressCheckoutDetails API here and set up
  94. * the values for the transaction. If successful, aryResponseValues['TOKEN']
  95. * will contain the identifier for the transaction.
  96. *@return bool true on success
  97. */
  98. public function PreProcess()
  99. {
  100. $this->aryRequestValues['METHOD'] = 'SetExpressCheckout';
  101. $this->setExpressCheckoutDetails();
  102. return ! $this->HasErrors;
  103. }
  104. /**
  105. *
  106. *@return bool true on success
  107. */
  108. public function Process()
  109. {
  110. return ! $this->HasErrors;
  111. }
  112. /**
  113. * This function simply checks the return from setExpressCheckoutDetails and then
  114. * redirects the user to PayPal's site to complete the payment
  115. * Updating order_status_history is also performed here.
  116. *@return bool true on success
  117. */
  118. public function PostProcess()
  119. {
  120. $strRedirectTarget = $this->strRedirectDomainName . $this->strRedirectCgiUrl
  121. . 'cmd=_express-checkout&token=' . $this->objPaypalTransaction->PpToken;
  122. Quasi::Redirect($strRedirectTarget );
  123. }
  124. /**
  125. * Creates POST string for the first half of the PayPal transaction, setting the checkout details
  126. * and storing the result in strPOSTRequest. Then a query is sent to NVP API server and the PayPal
  127. * "token" is recieved which will be used by getExpressCheckoutDetails to validate the payment.
  128. */
  129. protected function setExpressCheckoutDetails()
  130. {
  131. $this->aryRequestValues['METHOD'] = 'SetExpressCheckout';
  132. //optionally send a shipping address to display ..
  133. if( $this->ShowShippingAddress )
  134. $this->initShippingDetails();
  135. else
  136. $this->aryRequestValues['NOSHIPPING'] = 1;
  137. $this->aryRequestValues['AMT'] = $this->fltTotalPrice;
  138. $this->aryRequestValues['ITEMAMT'] = $this->objOrder->ProductTotalCharged;
  139. $this->aryRequestValues['INVNUM'] = $this->objOrder->Id;
  140. $this->aryRequestValues['SHIPPINGAMT'] = $this->objOrder->ShippingCharged;
  141. $this->aryRequestValues['HANDLINGAMT'] = $this->objOrder->HandlingCharged;
  142. $this->aryRequestValues['TAXAMT'] = $this->objOrder->Tax;
  143. //truncated to fit PP specs in case of long store names ..
  144. $this->aryRequestValues['DESC'] = urlencode(substr(STORE_NAME . ' Order ' . $this->objOrder->Id, 0, 127));
  145. foreach( $this->aryRequestValues as $strName => $strValue )
  146. if('' != $strValue )
  147. $this->strPOSTRequest .= '&' . $strName . '=' . $strValue;
  148. $this->submitRequest();
  149. $this->handleResponse();
  150. /* if( ! $this->submitRequest())
  151. throw new Exception('PayPal EC submit failed: ' . $this->strPOSTRequest);
  152. if( ! $this->handleResponse())
  153. throw new Exception('PayPal EC submit failed: ' . $this->strErrors
  154. . ' <br /><br /> Response: ' . $this->strResponse);*/
  155. }
  156. /**
  157. * This function is called by PayPalExpressReturnModule when the buyer returns from PayPal.
  158. *Here we determine the payer id and status in preparation for completing the order ..
  159. */
  160. public function getExpressCheckoutDetails()
  161. {
  162. $this->strPOSTRequest = '';
  163. $this->aryRequestValues['METHOD'] = 'GetExpressCheckoutDetails';
  164. $this->aryRequestValues['TOKEN'] = $_GET['token'];
  165. // also adds default settings ..
  166. foreach( $this->aryRequestValues as $strName => $strValue )
  167. if('' != $strValue )
  168. $this->strPOSTRequest .= '&' . $strName . '=' . $strValue;
  169. $this->submitRequest();
  170. $this->handleResponse();
  171. if( $this->HasErrors )
  172. return false;
  173. if( '' == $this->objPaypalTransaction->PayerId )
  174. {
  175. $this->HasErrors = true;
  176. $this->objPaypalTransaction->Messages .= '| Quasi PP Express: No PayerId returned! |';
  177. $this->objPaypalTransaction->Save();
  178. return false;
  179. }
  180. return true;
  181. }
  182. /**
  183. * This function overrides the PaymentActionBase completion - it is called by PayPalExpressReturnModule
  184. * when the buyer returns from PayPal.
  185. *@todo handle PENDINGREASON, REASONCODE, PAYMENTTYPE != INSTANT ..
  186. */
  187. public function doExpressCheckoutPayment()
  188. {
  189. $this->strPOSTRequest = '';
  190. $this->aryRequestValues['METHOD'] = 'DoExpressCheckoutPayment';
  191. $this->aryRequestValues['TOKEN'] = $this->objPaypalTransaction->PpToken;
  192. $this->aryRequestValues['PAYERID'] = $this->objPaypalTransaction->PayerId;
  193. $this->aryRequestValues['INVNUM'] = $this->objOrder->Id;
  194. $this->aryRequestValues['AMT'] = $this->fltTotalPrice;
  195. $this->aryRequestValues['ITEMAMT'] = $this->objOrder->ProductTotalCharged;
  196. $this->aryRequestValues['SHIPPINGAMT'] = $this->objOrder->ShippingCharged;
  197. $this->aryRequestValues['HANDLINGAMT'] = $this->objOrder->HandlingCharged;
  198. $this->aryRequestValues['TAXAMT'] = $this->objOrder->Tax;
  199. // also adds default settings ..
  200. foreach( $this->aryRequestValues as $strName => $strValue )
  201. if('' != $strValue )
  202. $this->strPOSTRequest .= '&' . $strName . '=' . $strValue;
  203. // die($this->strPOSTRequest);
  204. $this->submitRequest();
  205. $this->handleResponse();
  206. if( $this->HasErrors )
  207. return false;
  208. // check for the payment status
  209. $strStatus = $this->objPaypalTransaction->PaymentStatus;
  210. if('' == $strStatus )
  211. {
  212. $this->HasErrors = true;
  213. $this->objPaypalTransaction->Messages .= '| Quasi PP Express: No Payment status returned! |';
  214. $this->objPaypalTransaction->Save();
  215. return false;
  216. }
  217. ///@todo handle PENDINGREASON, REASONCODE, PAYMENTTYPE != INSTANT ..
  218. switch( strtoupper($strStatus) )
  219. {
  220. case 'COMPLETED':
  221. $this->blnApproved = true;
  222. break;
  223. case 'PENDING':
  224. default:
  225. $this->blnApproved = false;
  226. }
  227. }
  228. protected function initShippingDetails()
  229. {
  230. $this->aryRequestValues['NOSHIPPING'] = 0;
  231. $this->aryRequestValues['ADDRESSOVERRIDE'] = 1;
  232. //We must concatenate all the name fields into NAME ..
  233. if( '' != $this->objOrder->ShippingNamePrefix )
  234. $this->aryRequestValues['SHIPTONAME'] = urlencode($this->objOrder->ShippingNamePrefix . ' ');
  235. $this->aryRequestValues['SHIPTONAME'] .= urlencode($this->objOrder->ShippingFirstName);
  236. if( '' != $this->objOrder->ShippingMiddleName )
  237. $this->aryRequestValues['SHIPTONAME'] .= urlencode(' ' . $this->objOrder->ShippingMiddleName);
  238. $this->aryRequestValues['SHIPTONAME'] .= urlencode(' ' . $this->objOrder->ShippingLastName);
  239. if( '' != $this->objOrder->ShippingNameSuffix )
  240. $this->aryRequestValues['SHIPTONAME'] .= urlencode(' ' . $this->objOrder->ShippingNameSuffix);
  241. $this->aryRequestValues['SHIPTOSTREET'] = urlencode($this->objOrder->ShippingStreet1);
  242. if( '' != $this->objOrder->ShippingStreet2 )
  243. $this->aryRequestValues['SHIPTOSTREET2'] = urlencode($this->objOrder->ShippingStreet2);
  244. //PayPal offers no field for County or Suburb so we must put them in street2 ..
  245. if( '' != $this->objOrder->ShippingSuburb )
  246. $this->aryRequestValues['SHIPTOSTREET2'] .= urlencode(', ' . $this->objOrder->ShippingSuburb);
  247. if( '' != $this->objOrder->ShippingCounty )
  248. $this->aryRequestValues['SHIPTOSTREET2'] .= urlencode(', ' . $this->objOrder->ShippingCounty);
  249. $this->aryRequestValues['SHIPTOCITY'] = urlencode($this->objOrder->ShippingCity);
  250. $this->aryRequestValues['SHIPTOSTATE'] = urlencode($this->objOrder->ShippingState);
  251. $this->aryRequestValues['SHIPTOCOUNTRY'] = urlencode($this->objOrder->ShippingCountry);
  252. $this->aryRequestValues['SHIPTOZIP'] = urlencode($this->objOrder->ShippingPostalCode);
  253. }
  254. /**
  255. * Creates GET query string for the second half of the PayPal transaction. This function is called
  256. * by the PayPalExpressReturnModule only, which is effectively the return URL given to PayPal
  257. * in the first half of the transaction to which the user is redirected after paying. The "token"
  258. */
  259. public function __get($strName)
  260. {
  261. switch ($strName)
  262. {
  263. case 'ShowShippingAddress':
  264. return $this->blnShowShippingAddress;
  265. default:
  266. try {
  267. return parent::__get($strName);
  268. } catch (QCallerException $objExc) {
  269. $objExc->IncrementOffset();
  270. throw $objExc;
  271. }
  272. }
  273. }
  274. public function __set($strName, $mixValue)
  275. {
  276. switch ($strName)
  277. {
  278. case 'ShowShippingAddress':
  279. try {
  280. $this->blnShowShippingAddress = QType::Cast($mixValue, QType::Boolean );
  281. } catch (QInvalidCastException $objExc) {
  282. $objExc->IncrementOffset();
  283. throw $objExc;
  284. }
  285. //careful, its backwards ..
  286. $this->aryRequestValues['NOSHIPPING'] = (true === $mixValue) ? 0 : 1;
  287. default:
  288. try {
  289. return (parent::__set($strName, $mixValue));
  290. } catch (QCallerException $objExc) {
  291. $objExc->IncrementOffset();
  292. throw $objExc;
  293. }
  294. }
  295. }
  296. }//end class
  297. }//end define
  298. ?>