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.

611 lines
19 KiB

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