A QCodo powered CMS
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.

287 lines
12 KiB

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