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.

317 lines
12 KiB

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