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.

364 lines
16 KiB

13 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("PAYPALNVPACTION.CLASS.PHP")){
  8. define("PAYPALNVPACTION.CLASS.PHP",1);
  9. /**
  10. * Class PayPalNVPAction - PayPal NVP API action
  11. *
  12. * This class provides an interface to the PayPal NVP API.
  13. *
  14. * @todo - meaningful comments .. we do a bunch of stuff here:
  15. * connect, redirect, handle returns, save transaction ..etc.
  16. *
  17. *
  18. *@author Erik Winn <erikwinnmail@yahoo.com>
  19. *
  20. * $Id: PayPalNVPAction.class.php 451 2008-12-22 21:47:41Z erikwinn $
  21. *@version 0.1
  22. *
  23. *@copyright (C) 2008 by Erik Winn
  24. *@license GPL v.2
  25. This program is free software; you can redistribute it and/or modify
  26. it under the terms of the GNU General Public License as published by
  27. the Free Software Foundation; either version 2 of the License, or
  28. (at your option) any later version.
  29. This program is distributed in the hope that it will be useful,
  30. but WITHOUT ANY WARRANTY; without even the implied warranty of
  31. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  32. GNU General Public License for more details.
  33. You should have received a copy of the GNU General Public License
  34. along with this program; if not, write to the Free Software
  35. Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA
  36. *
  37. *@package Quasi
  38. * @subpackage Classes
  39. */
  40. class PayPalNVPAction extends PaymentActionBase
  41. {
  42. /**
  43. * @var array Name - Value pairs with which to construct GET query strings
  44. */
  45. protected $aryRequestValues = array(
  46. 'USER' => '',
  47. 'PWD' => '',
  48. 'PAYERID' => '',
  49. 'VERSION' => PAYPAL_NVP_VERSION,
  50. 'PAYMENTACTION' => 'Sale', //Sale | Autorization | Order
  51. 'METHOD' => '', //API method - REQUIRED
  52. 'TOKEN' => '', //transaction token, returned in response - OPTIONAL/required with get/doExpressCheckout ..
  53. 'AMT' => '', // total purchase amount, including tax and shipping - REQUIRED
  54. 'CURRENCYCODE' => 'USD', //AUD, CAD, CHF, CZK, DKK, EUR, GBP, HKD, HUF, JPY, NOK, NZD, PLN, SEK, SGD
  55. 'RETURNURL' => '', //URL to which customer is returned after paying - REQUIRED
  56. 'CANCELURL' => '', //URL to which customer is returned if they cancel - REQUIRED
  57. 'NOTIFYURL' => '', //URL for receiving Instant Payment Notification - optional
  58. 'IPADDRESS' => '', // Local ServerName
  59. 'MAXAMT' => '', // The expected maximum total amount of the complete order - OPTIONAL
  60. 'DESC' => '', // Description of purchase - OPTIONAL
  61. 'CUSTOM' => '', // Custom data, whatever you like, returned by GetExpressCheckoutDetails - OPTIONAL
  62. 'INVNUM' => '', // Invoice or Order number returned by DoExpressCheckoutPayment - OPTIONAL
  63. 'REQCONFIRMSHIPPING' => '', //Require PayPal to confirm customers address (filed at PayPal) - OPTIONAL
  64. 'NOSHIPPING' => '', //If true (1), PayPal is to display no shipping address info - OPTIONAL
  65. 'ALLOWNOTE' => '', //If true (1) user can add a note returned by GetExpressCheckoutDetails - OPTIONAL
  66. 'ADDRESSOVERRIDE' => '', //If true (1), PayPal displays address sent with request - OPTIONAL
  67. 'LOCALECODE' => '', //Display PayPal pages using this locale (AU, FR, DE, GB, IT, ES, US) - OPTIONAL
  68. 'PAGESTYLE' => '', //page style from the Profile subtab of the My Account tab - OPTIONAL
  69. 'HDRIMG' => '', // (https) URL for the image to appear at the top left of the payment page. - OPTIONAL
  70. 'HDRBORDERCOLOR' => '', //Sets the border color around the header of the payment page.- OPTIONAL
  71. 'HDRBACKCOLOR' => '', // Sets the background color for the header of the payment page - OPTIONAL
  72. 'PAYFLOWCOLOR' => '', // Sets the background color for the payment page - OPTIONAL
  73. 'EMAIL' => '', // Email address of the buyer to prefill field at PayPal - OPTIONAL
  74. 'LANDINGPAGE' => '', //Type of PayPal page to display ("Billing" for non-PayPal account else "Login") - OPTIONAL
  75. 'SHIPTONAME' => '', // (Customer name associated with shipping address - REQUIRED
  76. 'SHIPTOSTREET' => '', //First street address - REQUIRED
  77. 'SHIPTOSTREET2' => '', //Second street address - OPTIONAL
  78. 'SHIPTOCITY' => '', // Name of city. -REQUIRED
  79. 'SHIPTOSTATE' => '', // Name of state. -REQUIRED
  80. 'SHIPTOZIP' => '', // Postal code. -REQUIRED
  81. 'SHIPTOCOUNTRY' => '', // Country. -REQUIRED
  82. 'ITEMAMT' => '', //Sum of cost of all items in this order, REQUIRED if you use L_AMTn or shipping etc. ...
  83. 'SHIPPINGAMT' => '', //Total shipping costs for this order - optional.
  84. 'HANDLINGAMT' => '', //Total handling costs for this order - optional
  85. 'TAXAMT' => '', //Sum of tax for all items in this order - optional
  86. );
  87. /**
  88. *
  89. * @var array Response values will be stored here
  90. */
  91. protected $aryResponseValues;
  92. /**
  93. * PayPal transaction ORM object for logging transactions ..
  94. * @var PaypalTransaction - represents the paypal_transaction table ..
  95. */
  96. protected $objPaypalTransaction;
  97. /**
  98. * The following are base strings for constructing multiple item requests, eg. L_NAME0, L_NAME1, etc...
  99. * All are optional.
  100. */
  101. protected $strItemName = 'L_NAME';
  102. protected $strItemDescBase = 'L_DESC';
  103. protected $strItemAmountBase = 'L_AMT';
  104. protected $strItemQuantityBase = 'L_QTY';
  105. protected $strItemNumberBase = 'L_NUMBER';
  106. protected $strItemTaxBase = 'L_TAXAMT';
  107. protected $blnShowShippingAddress = false;
  108. /**
  109. * PayPalNVPAction Constructor
  110. *
  111. * This sets various defaults specific to the NVP API
  112. *
  113. * @param Order objOrder - the Order to process
  114. */
  115. public function __construct(Order $objOrder)
  116. {
  117. try {
  118. parent::__construct($objOrder);
  119. } catch (QCallerException $objExc) {
  120. $objExc->IncrementOffset();
  121. throw $objExc;
  122. }
  123. $this->blnTestMode = $objOrder->PaymentMethod->TestMode;
  124. if($this->TestMode)
  125. {
  126. $this->strRedirectDomainName = PAYPAL_REDIRECT_TESTURL;
  127. $this->aryRequestValues['USER'] = PAYPAL_NVP_TESTUSERNAME;
  128. $this->aryRequestValues['PWD'] = PAYPAL_NVP_TESTPASSWORD;
  129. if('' != PAYPAL_NVP_TESTSIGNATURE)
  130. {
  131. $this->aryRequestValues['SIGNATURE'] = PAYPAL_NVP_TESTSIGNATURE;
  132. $this->strRemoteDomainName = PAYPAL_NVP_TESTURL;
  133. }
  134. else
  135. {
  136. $this->UseCurl = true;
  137. $this->UseSslCertificate = true;
  138. $this->strRemoteDomainName = PAYPAL_NVP_CURL_TESTURL;
  139. $this->strSslCertificateUri = PAYPAL_CERT_TESTPATH;
  140. }
  141. }
  142. else
  143. /// FIXME: put these somewhere safer and load it .. currently in config file!
  144. {
  145. $this->strRedirectDomainName = PAYPAL_REDIRECT_URL;
  146. $this->aryRequestValues['USER'] = PAYPAL_NVP_USERNAME;
  147. $this->aryRequestValues['PWD'] = PAYPAL_NVP_PASSWORD;
  148. if('' != PAYPAL_NVP_TESTSIGNATURE)
  149. {
  150. $this->aryRequestValues['SIGNATURE'] = PAYPAL_NVP_SIGNATURE;
  151. $this->strRemoteDomainName = PAYPAL_NVP_URL;
  152. }
  153. else
  154. {
  155. $this->UseCurl = true;
  156. $this->UseSslCertificate = true;
  157. $this->strRemoteDomainName = PAYPAL_NVP_CURL_URL;
  158. $this->strSslCertificateUri = PAYPAL_CERT_PATH;
  159. }
  160. }
  161. $this->strRemoteCgiUrl = '/nvp';
  162. $this->strRequestType = 'POST';
  163. $this->aryRequestValues['IPADDRESS'] = Quasi::$ServerName;
  164. //unused ..
  165. $this->strTemplateUri = __QUASI_CORE_TEMPLATES__ . '/PayPalNVPAction.tpl.php';
  166. }
  167. /**
  168. * The createRequest functions are handled by separate functions which create requests
  169. * by stage as they may occur before and after a customer is redirected to PayPal and may
  170. * therefor be two separate requests.
  171. */
  172. protected function createPOSTRequest(){}
  173. protected function createGETRequest(){}
  174. /**
  175. * Performs any preparation steps prior to submitting an actual payment.
  176. * Eg. We submit a call to the SetExpressCheckoutDetails API here and set up
  177. * the values for the transaction. If successful, aryResponseValues['TOKEN']
  178. * will contain the identifier for the transaction.
  179. *@return bool true on success
  180. */
  181. public function PreProcess(){}
  182. /**
  183. *@return bool true on success
  184. */
  185. public function Process(){}
  186. /**
  187. * Performs any steps necessary after submitting an actual payment. For example
  188. * the PayPal Express Checkout redirects the user here ..
  189. *@return bool true on success
  190. */
  191. public function PostProcess(){}
  192. /**
  193. * Parses the direct API response from PayPal into an array of values. This also inserts an entry
  194. * into the paypal_transaction table and initializes the PaypalTransaction object to which other
  195. * functions may refer for information returned concerning the transaction.
  196. *
  197. *@todo
  198. * - handle errors gracefully !!
  199. * - L_ERRORCODE0=81100&L_SHORTMESSAGE0=Missing%20Parameter&L_LONGMESSAGE0
  200. * errornumber: 10415 - transaction already completed for token
  201. *
  202. * - optionally save address values from PP and use address confirmation ... this ain't gonna be
  203. * soon since it requires a whole reworking of the scheme to put shipping options after redirect
  204. * and adding the shipping charge.. ick, i have pp slime on my keyboard ..
  205. *@return boolean true if the response was successfully parsed.
  206. */
  207. protected function handleResponse()
  208. {
  209. $this->aryResponseValues = array();
  210. $strResponseRaw = $this->strResponse;
  211. $this->strResponse = urldecode($this->strResponse);
  212. $pos = strpos($this->strResponse, "TOKEN=" );
  213. if( false === $pos )
  214. {
  215. $this->HasErrors = true;
  216. return false;
  217. }
  218. $this->strResponse = substr( $this->strResponse, $pos);
  219. //split up the string and store the values in a map ..
  220. $aryTokens = explode('&', $this->strResponse );
  221. foreach($aryTokens as $strToken)
  222. {
  223. $aryTemp = explode('=', $strToken);
  224. $this->aryResponseValues[$aryTemp[0]] = $aryTemp[1];
  225. }
  226. if( empty($this->aryResponseValues) )
  227. {
  228. $this->HasErrors = true;
  229. $this->strErrors .= 'Response: ' . $strResponseRaw;
  230. return false;
  231. }
  232. //initialize a transaction logging object ..
  233. $this->objPaypalTransaction = new PaypalTransaction();
  234. $this->objPaypalTransaction->OrderId = $this->objOrder->Id;
  235. $this->objPaypalTransaction->PaymentMethodId = $this->objOrder->PaymentMethodId;
  236. $this->objPaypalTransaction->ApiAction = $this->aryRequestValues['METHOD'];
  237. $this->objPaypalTransaction->ApiVersion = $this->aryResponseValues['VERSION'];
  238. $this->objPaypalTransaction->CorrelationId = $this->aryResponseValues['CORRELATIONID'];
  239. $this->objPaypalTransaction->AckReturned = $this->aryResponseValues['ACK'];
  240. //clean up the timestamp .. note: the settor converts this to a QDateTime
  241. $strDateTime = str_replace('T',' ', $this->aryResponseValues['TIMESTAMP']);
  242. $this->objPaypalTransaction->TimeStamp = $strDateTime;
  243. $this->checkAckReturned();
  244. if($this->HasErrors)
  245. {
  246. foreach($this->aryResponseValues as $strName => $strValue)
  247. if( false !== strpos( $strName, 'L_ERRORCODE') || false !== strpos( $strName, 'MESSAGE') )
  248. $this->strErrors .= '<br />' . $strName . ': ' . $strValue;
  249. $this->objPaypalTransaction->Messages = $this->strErrors;
  250. $this->objPaypalTransaction->Save();
  251. return false;
  252. }
  253. //server transaction ok, finish with the payment ..
  254. switch(strtoupper($this->objPaypalTransaction->ApiAction))
  255. {
  256. case 'DOEXPRESSCHECKOUTPAYMENT':
  257. $this->objPaypalTransaction->PaymentStatus = $this->aryResponseValues['PAYMENTSTATUS'];
  258. $this->objPaypalTransaction->PpToken = $this->aryResponseValues['TOKEN'];
  259. break;
  260. case 'GETEXPRESSCHECKOUTDETAILS':
  261. $this->objPaypalTransaction->PayerId = $this->aryResponseValues['PAYERID'];
  262. $this->objPaypalTransaction->PayerStatus = $this->aryResponseValues['PAYERSTATUS'];
  263. case 'SETEXPRESSCHECKOUT':
  264. $this->objPaypalTransaction->PpToken = $this->aryResponseValues['TOKEN'];
  265. break;
  266. default:
  267. //unsupported method ..
  268. }
  269. $this->objPaypalTransaction->Save();
  270. return true;
  271. }
  272. protected function checkAckReturned()
  273. {
  274. $strAck =$this->objPaypalTransaction->AckReturned ;
  275. if( '' == $strAck)
  276. $this->HasErrors = true;
  277. else
  278. {
  279. switch( strtoupper( $strAck ))
  280. {
  281. case 'SUCCESS':
  282. case 'SUCCESSWITHWARNING':
  283. $this->HasErrors = false;
  284. break;
  285. case 'FAILURE':
  286. case 'FAILUREWITHWARNING':
  287. $this->HasErrors = true;
  288. break;
  289. default:
  290. //error ..
  291. $this->HasErrors = true;
  292. }
  293. }
  294. return ! $this->HasErrors;
  295. }
  296. public function __get($strName)
  297. {
  298. switch ($strName)
  299. {
  300. case 'PaypalTransaction':
  301. return $this->objPaypalTransaction;
  302. case 'ShowShippingAddress':
  303. return $this->blnShowShippingAddress;
  304. default:
  305. try {
  306. return parent::__get($strName);
  307. } catch (QCallerException $objExc) {
  308. $objExc->IncrementOffset();
  309. throw $objExc;
  310. }
  311. }
  312. }
  313. public function __set($strName, $mixValue)
  314. {
  315. switch ($strName)
  316. {
  317. case 'ShowShippingAddress':
  318. try {
  319. $this->blnShowShippingAddress = QType::Cast($mixValue, QType::Boolean );
  320. } catch (QInvalidCastException $objExc) {
  321. $objExc->IncrementOffset();
  322. throw $objExc;
  323. }
  324. //careful, its backwards ..
  325. $this->aryRequestValues['NOSHIPPING'] = (true === $mixValue) ? 0 : 1;
  326. default:
  327. try {
  328. return (parent::__set($strName, $mixValue));
  329. } catch (QCallerException $objExc) {
  330. $objExc->IncrementOffset();
  331. throw $objExc;
  332. }
  333. }
  334. }
  335. }//end class
  336. }//end define
  337. ?>