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.

651 lines
26 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("PAYMENTACTION.CLASS.PHP")){
  8. define("PAYMENTACTION.CLASS.PHP",1);
  9. /**
  10. * Class PaymentActionBase - base class for classes that perform payment actions with a web service
  11. *
  12. * This class provides the basic actions and properties for all of the PaymentAction classes. This includes
  13. * making the connection to the payment service provider, sending the request in either GET or POST (usually
  14. * in XML ..) and accepting the response from the server. The response is stored in strResponse regardless
  15. * of format.
  16. *
  17. * Subclasses are responsible for formatting the request. Users of the subclasses are responsible for initializing
  18. * the required properties.
  19. *
  20. * Subclasses must implement these methods:
  21. * - PreProcess: perform any actions to set up the transaction
  22. * - Process: perform the actual transaction, ie. connect to server and make a request in most cases.
  23. * - PostProcess: perform any validation checks and update the order_status_history table in most cases.
  24. * PayPal Express Checkout redirects the user to the PayPal site at this point and order_status
  25. * is set to "Pending" (??), there is a special return page for this kind of case that will complete the
  26. * order_status update (??).
  27. *
  28. * See the PaymentModule class documentation and documentation for the PaymentAction subclasses
  29. * for more details.
  30. *
  31. *@todo
  32. * - port payment actions to use WebServiceRequest .. and this class should go away ..
  33. *
  34. *@author Erik Winn <erikwinnmail@yahoo.com>
  35. *
  36. * $Id: PaymentActionBase.class.php 497 2009-01-26 20:56:28Z erikwinn $
  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. abstract class PaymentActionBase
  57. {
  58. /**
  59. *@var Order local reference to the Order being processed
  60. */
  61. protected $objOrder;
  62. /**
  63. *@var string Order id (aka Invoice number)
  64. */
  65. protected $strOrderId;
  66. /**
  67. * In some cases (eg. Authorize.net) this is returned by the provider, in others
  68. * we may provide it for tracking the transaction.
  69. *@var string Transaction identifier
  70. */
  71. protected $strTransactionId;
  72. /**
  73. * In some cases (eg. Authorize.net) this is used in place of RemotePassword
  74. * - it is provided merely for literate purposes.
  75. *@var string Transaction Key - aka Password ..
  76. */
  77. protected $strTransactionKey;
  78. /**
  79. *@var string Username, login or account id for the service
  80. */
  81. protected $strRemoteAccountId;
  82. /**
  83. *@var string password for the service
  84. */
  85. protected $strRemotePassword;
  86. /**
  87. * Note: this is currently unused, the Quasi built in PaymentModule displays the payment
  88. * action selection as QRadioButtons ..
  89. *@var string the template file to use for this method
  90. */
  91. protected $strTemplateUri;
  92. /**
  93. *@var string the FQD for the payment service provider API server
  94. */
  95. protected $strRemoteDomainName;
  96. /**
  97. * Note: This must contain the separater character that follows the script name, eg. '?' or '&'
  98. * if the request is of type GET
  99. *@var string the URL portion after the domain name leading to API script
  100. */
  101. protected $strRemoteCgiUrl;
  102. /**
  103. * This is the URL to which a customer is redirected to make a payment (eg. www.paypal.com ..)
  104. *@var string the FQD for the payment service provider redirect target
  105. */
  106. protected $strRedirectDomainName;
  107. /**
  108. * Note: This must contain the separater character that follows the script name, eg. '?' or '&'
  109. * if the request is of type GET
  110. *@var string the URL portion after the domain name leading to redirect script
  111. */
  112. protected $strRedirectCgiUrl;
  113. /**
  114. *@var string storage for response from service
  115. */
  116. protected $strResponse;
  117. /**
  118. *@var string storage for POST request to service
  119. */
  120. protected $strPOSTRequest;
  121. /**
  122. *@var string query string appended to CGI URL for a GET request
  123. */
  124. protected $strGETRequest;
  125. /**
  126. *@var string The type of request to be made (GET | POST )
  127. */
  128. protected $strRequestType;
  129. /**
  130. *@var boolean True if we should use CURL to connect to the provider
  131. */
  132. protected $blnUseCurl = false;
  133. /**
  134. *@var boolean True if we should use SSL to connect to the provider
  135. */
  136. protected $blnUseSsl = true;
  137. /**
  138. *@var boolean True if we should use SSL certificate to authenticate ourselves ..
  139. */
  140. protected $blnUseSslCertificate = false;
  141. /**
  142. *@var string full path to our SSL certificate
  143. */
  144. protected $strSslCertificateUri;
  145. /**
  146. *@var integer Port number to use for the connection (80, 443)
  147. */
  148. protected $intPort = 443;
  149. /**
  150. *@var integer Connection time out in seconds
  151. */
  152. protected $intTimeOut = 60;
  153. /**
  154. *@var boolean True if there were errors or if the transaction/connection failed for any reason
  155. */
  156. protected $blnHasErrors;
  157. /**
  158. *@var string Errors
  159. */
  160. protected $strErrors;
  161. /**
  162. * This contains a string which either confirms the approval of a payment or gives a reason
  163. * for its failure.
  164. *@var string status explanation text from the transaction
  165. */
  166. protected $strStatusText;
  167. /**
  168. *@var boolean True if the transaction was approved
  169. */
  170. protected $blnApproved = false;
  171. /**
  172. * NOTE: You must explicitly set this to disable testing mode ..
  173. *@var boolean True for testing (and by default)
  174. */
  175. protected $blnTestMode = true;
  176. /**
  177. *@var float total charges for items
  178. */
  179. protected $fltSubTotal;
  180. /**
  181. *@var float Charges for shipping
  182. */
  183. protected $fltShippingCharge;
  184. /**
  185. *@var float Charges for handling
  186. */
  187. protected $fltHandlingCharge;
  188. /**
  189. *@var float Taxes
  190. */
  191. protected $fltTax;
  192. /**
  193. *@var string CC for customer
  194. */
  195. protected $strCCNumber;
  196. /**
  197. *@var string CC expiration data for customer
  198. */
  199. protected $strCCExpirationYear;
  200. protected $strCCExpirationMonth;
  201. /**
  202. *@var string CCV number for customer
  203. */
  204. protected $strCCVNumber;
  205. /**
  206. *@var float Total of all charges, items, shipping, tax and handling
  207. */
  208. protected $fltTotalPrice;
  209. /**
  210. * PaymentActionBase Constructor
  211. *
  212. * @param Order objOrder - the order for which we are attempting to pay..
  213. */
  214. public function __construct(Order $objOrder)
  215. {
  216. $this->objOrder =& $objOrder;
  217. $this->fltHandlingCharge = $objOrder->HandlingCharged;
  218. $this->fltShippingCharge = $objOrder->ShippingCharged;
  219. $this->fltTax = $objOrder->Tax;
  220. $this->fltTotalPrice = $objOrder->ProductTotalCharged
  221. + $objOrder->HandlingCharged
  222. + $objOrder->ShippingCharged
  223. + $objOrder->Tax;
  224. }
  225. /**
  226. * Performs any preparation steps prior to submitting an actual payment. For example
  227. * the PayPal NVP (Express Checkout) calls the SetExpressCheckoutDetails API here
  228. *@return bool true on success
  229. */
  230. abstract public function PreProcess();
  231. /**
  232. * Performs any steps necessary after submitting an actual payment. For example
  233. * the PayPal NVP (Express Checkout) calls the GetExpressCheckoutDetails API here.
  234. * Updating order_status_history is also performed here.
  235. *@return bool true on success
  236. */
  237. abstract public function PostProcess();
  238. /**
  239. * Performs the actual payment submission - in some cases this is a request/response
  240. * routine and in some (eg. PayPal), this simply redirects the user to the provider site.
  241. *@return bool true on success
  242. */
  243. abstract public function Process();
  244. /**
  245. * Parses the response from the payment service provider
  246. */
  247. abstract protected function handleResponse();
  248. /**
  249. * Creates GET query string for the transaction appropriate to the provider API, storing
  250. * the result in strGETRequest.
  251. */
  252. abstract protected function createGETRequest();
  253. /**
  254. * Creates GET query string for the transaction appropriate to the provider API, storing
  255. * the result in strGETRequest.
  256. */
  257. abstract protected function createPOSTRequest();
  258. /**
  259. * This function directs the call to the appropriate creation function and returns
  260. * a string containing either a query string for a GET or a content string for a POST.
  261. *
  262. * A RequestType may be provided to override a default as an alternative to setting
  263. * it explicitly. Note that this will set the RequestType for the object.
  264. *
  265. *@param string strRequestType - you may provide the RequestType
  266. */
  267. protected function createRequest($strRequestType=null)
  268. {
  269. if(null != $strRequestType)
  270. $this->strRequestType = $strRequestType;
  271. switch($this->strRequestType)
  272. {
  273. case 'GET':
  274. $this->createGETRequest();
  275. return $this->strGETRequest;
  276. break;
  277. case 'POST':
  278. $this->createPOSTRequest();
  279. return $this->strPOSTRequest;
  280. break;
  281. case 'SOAP':
  282. default:
  283. throw new QCallerException('PaymentAction - Unsupported RequestType: ' . $this->strRequestType);
  284. }
  285. }
  286. /**
  287. * Connects to payment service and submits the request. Note that
  288. * this function merely constructs a request URL from internal variables
  289. * that are set in createRequest, it may therefor contain a GET query
  290. * string or not depending on the subclass requirements.
  291. *
  292. *@return bool true on success
  293. */
  294. protected function submitRequest()
  295. {
  296. if($this->UseCurl)
  297. return $this->submitCurlRequest();
  298. if($this->UseSsl)
  299. $strProtocol = 'ssl://';
  300. else
  301. $strProtocol = 'http://';
  302. //attempt to connect ..
  303. $fp = fsockopen($strProtocol . $this->strRemoteDomainName,
  304. $this->intPort,
  305. $intError,
  306. $strError,
  307. $this->intTimeOut
  308. );
  309. //did we connect?
  310. if (!$fp)
  311. {
  312. /* $this->blnHasErrors = true;
  313. return 0;*/
  314. throw new QCallerException("Payment Action base: Connection request failed: $strError ($intError) ");
  315. }
  316. else
  317. {
  318. //construct the request ..
  319. switch( $this->strRequestType )
  320. {
  321. case 'GET':
  322. $out = "GET " . $this->strRemoteCgiUrl . $this->strGETRequest . " HTTP/1.1\r\n";
  323. $out .= "Host:" . $this->strRemoteDomainName . "\r\n";
  324. $out .= "User-Agent: QuasiCMS " . QUASI_VERSION . "\r\n";
  325. $out .= "Connection: Close\r\n\r\n";
  326. break;
  327. case 'POST':
  328. $out = "POST " . $this->strRemoteCgiUrl . " HTTP/1.1\r\n";
  329. $out .= "Host:" . $this->strRemoteDomainName . "\r\n";
  330. $out .= "User-Agent: QuasiCMS " . QUASI_VERSION . "\r\n";
  331. // $out .= "MIME-Version: 1.0\r\n";
  332. $out .= "Content-Type: application/x-www-form-urlencoded\r\n";
  333. // $out .= "Accept: text/xml\r\n";
  334. $out .= "Content-length: " . strlen($this->strPOSTRequest) . "\r\n";
  335. $out .= "Cache-Control: no-cache\r\n";
  336. $out .= "Connection: Close\r\n\r\n";
  337. $out .= $this->strPOSTRequest . "\r\n\r\n";
  338. break;
  339. default:
  340. throw new QCallerException('WebService RequestType unsupported: ' . $this->RequestType);
  341. }
  342. //send the request
  343. fwrite($fp, $out );
  344. $this->strResponse = '';
  345. //store the response
  346. while ( !feof($fp) )
  347. $this->strResponse .= fgets($fp, 128);
  348. $this->strResponse .= $out;
  349. fclose($fp);
  350. }
  351. return true;
  352. }
  353. /**
  354. * This is an alternate request submission method using CURL
  355. * @return bool true on sucess
  356. */
  357. protected function submitCurlRequest()
  358. {
  359. if($this->UseSsl)
  360. $strProtocol = 'https://';
  361. else
  362. $strProtocol = 'http://';
  363. $objCurlHandle = curl_init();
  364. curl_setopt($objCurlHandle, CURLOPT_USERAGENT, 'QuasiCMS ' . QUASI_VERSION);
  365. curl_setopt($objCurlHandle, CURLOPT_TIMEOUT, $this->intTimeOut);
  366. curl_setopt ($objCurlHandle, CURLOPT_RETURNTRANSFER, 1);
  367. switch( $this->strRequestType )
  368. {
  369. case 'GET':
  370. $strUri = $strProtocol . $this->strRemoteDomainName . $this->strRemoteCgiUrl . $this->strGETRequest;
  371. curl_setopt($objCurlHandle, CURLOPT_URL, $strUri);
  372. break;
  373. case 'POST':
  374. $strUri = $strProtocol . $this->strRemoteDomainName . $this->strRemoteCgiUrl;
  375. curl_setopt($objCurlHandle, CURLOPT_POST, 1);
  376. curl_setopt($objCurlHandle, CURLOPT_URL, $strUri);
  377. curl_setopt($objCurlHandle, CURLOPT_POSTFIELDS, $this->strPOSTRequest);
  378. break;
  379. default:
  380. throw new QCallerException('WebService RequestType unsupported: ' . $this->RequestType);
  381. }
  382. if($this->UseSsl)
  383. {
  384. curl_setopt($objCurlHandle, CURLOPT_SSL_VERIFYHOST, 0);
  385. curl_setopt($objCurlHandle, CURLOPT_SSL_VERIFYPEER, 0);
  386. }
  387. if($this->UseSslCertificate)
  388. curl_setopt($objCurlHandle, CURLOPT_SSLCERT,$this->strSslCertificateUri);
  389. $this->strResponse = curl_exec($objCurlHandle);
  390. if(false === $this->strResponse )
  391. {
  392. $this->HasErrors = true;
  393. $this->strErrors .= curl_error($objCurlHandle);
  394. return false;
  395. }
  396. curl_close($objCurlHandle);
  397. }
  398. /**
  399. *This function sets the order status for an order when payment is approved.
  400. * It also inserts a new order_status_history and clears the shopping cart. The confirmation
  401. * email is handled by Order when the status is set.
  402. */
  403. public function completeOrder()
  404. {
  405. /* this is now done with __set magic in Order and the notes seem superflous, disabled pending removal
  406. $objOrderStatusHistory = new OrderStatusHistory();
  407. $objOrderStatusHistory->OrderId = $this->objOrder->Id;
  408. $objOrderStatusHistory->StatusId = OrderStatusType::Paid;
  409. $objOrderStatusHistory->Notes = $this->objOrder->Notes;
  410. $objOrderStatusHistory->Save();
  411. */
  412. // Order can send its own email confirmations ..
  413. $this->objOrder->SetStatus(OrderStatusType::Paid);
  414. IndexPage::$objShoppingCart->DeleteAllShoppingCartItems();
  415. }
  416. public function __get($strName)
  417. {
  418. switch ($strName)
  419. {
  420. case 'TotalPrice':
  421. return $this->fltTotalPrice;
  422. case 'StatusText':
  423. return $this->strStatusText;
  424. case 'TemplateUri':
  425. return $this->strTemplateUri ;
  426. case 'Order':
  427. return $this->objOrder ;
  428. case 'OrderId':
  429. return $this->objOrder->Id ;
  430. case 'RemotePassword':
  431. return $this->strRemotePassword ;
  432. case 'RemoteAccountId':
  433. return $this->strRemoteAccountId ;
  434. case 'RemoteCgiUrl':
  435. return $this->strRemoteCgiUrl ;
  436. case 'RemoteDomainName':
  437. return $this->strRemoteDomainName ;
  438. case 'Errors':
  439. return $this->strErrors ;
  440. case 'Approved':
  441. return $this->blnApproved ;
  442. case 'HasErrors':
  443. return $this->blnHasErrors ;
  444. case 'UseCurl':
  445. return $this->blnUseSsl ;
  446. case 'UseSsl':
  447. return $this->blnUseSsl ;
  448. case 'UseSslCertificate':
  449. return $this->blnUseSslCertificate ;
  450. case 'SslCertificateUri':
  451. return $this->strSslCertificateUri;
  452. case 'TestMode':
  453. return $this->blnTestMode ;
  454. case 'CCNumber':
  455. return $this->strCCNumber ;
  456. case 'CCExpirationYear':
  457. return $this->strCCExpirationYear ;
  458. case 'CCExpirationMonth':
  459. return $this->strCCExpirationMonth ;
  460. case 'CCVNumber':
  461. return $this->strCCVNumber ;
  462. default:
  463. throw new QCallerException('Payment Action - Access Unknown property: ' . $strName);
  464. }
  465. }
  466. public function __set($strName, $mixValue)
  467. {
  468. switch ($strName)
  469. {
  470. case 'TemplateUri':
  471. try {
  472. return ($this->strTemplateUri = QType::Cast($mixValue, QType::String ));
  473. } catch (QInvalidCastException $objExc) {
  474. $objExc->IncrementOffset();
  475. throw $objExc;
  476. }
  477. case 'TransactionId':
  478. try {
  479. return ($this->strTransactionId = QType::Cast($mixValue, QType::String ));
  480. } catch (QInvalidCastException $objExc) {
  481. $objExc->IncrementOffset();
  482. throw $objExc;
  483. }
  484. case 'OrderId':
  485. try {
  486. return ($this->strOrderId = QType::Cast($mixValue, QType::String ));
  487. } catch (QInvalidCastException $objExc) {
  488. $objExc->IncrementOffset();
  489. throw $objExc;
  490. }
  491. case 'RemoteDomainName':
  492. try {
  493. return ($this->strRemoteDomainName = QType::Cast($mixValue, QType::String ));
  494. } catch (QInvalidCastException $objExc) {
  495. $objExc->IncrementOffset();
  496. throw $objExc;
  497. }
  498. case 'RemoteCgiUrl':
  499. try {
  500. return ($this->strRemoteCgiUrl = QType::Cast($mixValue, QType::String ));
  501. } catch (QInvalidCastException $objExc) {
  502. $objExc->IncrementOffset();
  503. throw $objExc;
  504. }
  505. case 'RemoteAccountId':
  506. try {
  507. return ($this->strRemoteAccountId = QType::Cast($mixValue, QType::String ));
  508. } catch (QInvalidCastException $objExc) {
  509. $objExc->IncrementOffset();
  510. throw $objExc;
  511. }
  512. case 'RemotePassword':
  513. try {
  514. return ($this->strRemotePassword = QType::Cast($mixValue, QType::String ));
  515. } catch (QInvalidCastException $objExc) {
  516. $objExc->IncrementOffset();
  517. throw $objExc;
  518. }
  519. case 'Approved':
  520. try {
  521. return ($this->blnApproved = QType::Cast($mixValue, QType::Boolean ));
  522. } catch (QInvalidCastException $objExc) {
  523. $objExc->IncrementOffset();
  524. throw $objExc;
  525. }
  526. case 'Errors':
  527. try {
  528. return ($this->strErrors = QType::Cast($mixValue, QType::String ));
  529. } catch (QInvalidCastException $objExc) {
  530. $objExc->IncrementOffset();
  531. throw $objExc;
  532. }
  533. case 'HasErrors':
  534. try {
  535. return ($this->blnHasErrors = QType::Cast($mixValue, QType::Boolean ));
  536. } catch (QInvalidCastException $objExc) {
  537. $objExc->IncrementOffset();
  538. throw $objExc;
  539. }
  540. case 'UseCurl':
  541. try {
  542. return ($this->blnUseCurl = QType::Cast($mixValue, QType::Boolean ));
  543. } catch (QInvalidCastException $objExc) {
  544. $objExc->IncrementOffset();
  545. throw $objExc;
  546. }
  547. case 'UseSsl':
  548. try {
  549. return ($this->blnUseSsl = QType::Cast($mixValue, QType::Boolean ));
  550. } catch (QInvalidCastException $objExc) {
  551. $objExc->IncrementOffset();
  552. throw $objExc;
  553. }
  554. case 'UseSslCertificate':
  555. try {
  556. return ($this->blnUseSslCertificate = QType::Cast($mixValue, QType::Boolean ));
  557. } catch (QInvalidCastException $objExc) {
  558. $objExc->IncrementOffset();
  559. throw $objExc;
  560. }
  561. case 'SslCertificateUri':
  562. try {
  563. return ($this->strSslCertificateUri = QType::Cast($mixValue, QType::String ));
  564. } catch (QInvalidCastException $objExc) {
  565. $objExc->IncrementOffset();
  566. throw $objExc;
  567. }
  568. case 'TestMode':
  569. try {
  570. return ($this->blnTestMode = QType::Cast($mixValue, QType::Boolean ));
  571. } catch (QInvalidCastException $objExc) {
  572. $objExc->IncrementOffset();
  573. throw $objExc;
  574. }
  575. case 'Port':
  576. try {
  577. return ($this->intPort = QType::Cast($mixValue, QType::Integer ));
  578. } catch (QInvalidCastException $objExc) {
  579. $objExc->IncrementOffset();
  580. throw $objExc;
  581. }
  582. case 'TimeOut':
  583. try {
  584. return ($this->intTimeOut = QType::Cast($mixValue, QType::Integer ));
  585. } catch (QInvalidCastException $objExc) {
  586. $objExc->IncrementOffset();
  587. throw $objExc;
  588. }
  589. case 'CCNumber':
  590. try {
  591. return ($this->strCCNumber = QType::Cast($mixValue, QType::String ));
  592. } catch (QInvalidCastException $objExc) {
  593. $objExc->IncrementOffset();
  594. throw $objExc;
  595. }
  596. case 'CCExpirationYear':
  597. try {
  598. return ($this->strCCExpirationYear = QType::Cast($mixValue, QType::String ));
  599. } catch (QInvalidCastException $objExc) {
  600. $objExc->IncrementOffset();
  601. throw $objExc;
  602. }
  603. case 'CCExpirationMonth':
  604. try {
  605. return ($this->strCCExpirationMonth = QType::Cast($mixValue, QType::String ));
  606. } catch (QInvalidCastException $objExc) {
  607. $objExc->IncrementOffset();
  608. throw $objExc;
  609. }
  610. case 'CCVNumber':
  611. try {
  612. return ($this->strCCVNumber = QType::Cast($mixValue, QType::String ));
  613. } catch (QInvalidCastException $objExc) {
  614. $objExc->IncrementOffset();
  615. throw $objExc;
  616. }
  617. default:
  618. throw new QCallerException('Payment Action - Set Unknown property: ' . $strName);
  619. }
  620. }
  621. }//end class
  622. }//end define
  623. ?>