<?php
|
|
if(!defined('QUINTACMS') ) die('No Quinta.');
|
|
|
|
if (!defined("PAYMENTACTION.CLASS.PHP")){
|
|
define("PAYMENTACTION.CLASS.PHP",1);
|
|
|
|
/**
|
|
* Class PaymentActionBase - base class for classes that perform payment actions with a web service
|
|
*
|
|
* This class provides the basic actions and properties for all of the PaymentAction classes. This includes
|
|
* making the connection to the payment service provider, sending the request in either GET or POST (usually
|
|
* in XML ..) and accepting the response from the server. The response is stored in strResponse regardless
|
|
* of format.
|
|
*
|
|
* Subclasses are responsible for formatting the request. Users of the subclasses are responsible for initializing
|
|
* the required properties.
|
|
*
|
|
* Subclasses must implement these methods:
|
|
* - PreProcess: perform any actions to set up the transaction
|
|
* - Process: perform the actual transaction, ie. connect to server and make a request in most cases.
|
|
* - PostProcess: perform any validation checks and update the order_status_history table in most cases.
|
|
* PayPal Express Checkout redirects the user to the PayPal site at this point and order_status
|
|
* is set to "Pending" (??), there is a special return page for this kind of case that will complete the
|
|
* order_status update (??).
|
|
*
|
|
* See the PaymentModule class documentation and documentation for the PaymentAction subclasses
|
|
* for more details.
|
|
*
|
|
*@todo
|
|
* - port payment actions to use WebServiceRequest .. and this class should go away ..
|
|
*
|
|
*@author Erik Winn <sidewalksoftware@gmail.com>
|
|
*
|
|
*@version 0.3
|
|
*
|
|
*@package Quinta
|
|
* @subpackage Classes
|
|
*/
|
|
|
|
abstract class PaymentActionBase {
|
|
/**
|
|
*@var Order local reference to the Order being processed
|
|
*/
|
|
protected $objOrder;
|
|
/**
|
|
*@var string Order id (aka Invoice number)
|
|
*/
|
|
protected $strOrderId;
|
|
/**
|
|
* In some cases (eg. Authorize.net) this is returned by the provider, in others
|
|
* we may provide it for tracking the transaction.
|
|
*@var string Transaction identifier
|
|
*/
|
|
protected $strTransactionId;
|
|
/**
|
|
* In some cases (eg. Authorize.net) this is used in place of RemotePassword
|
|
* - it is provided merely for literate purposes.
|
|
*@var string Transaction Key - aka Password ..
|
|
*/
|
|
protected $strTransactionKey;
|
|
/**
|
|
*@var string Username, login or account id for the service
|
|
*/
|
|
protected $strRemoteAccountId;
|
|
/**
|
|
*@var string password for the service
|
|
*/
|
|
protected $strRemotePassword;
|
|
/**
|
|
* Note: this is currently unused, the Quinta built in PaymentModule displays the payment
|
|
* action selection as QRadioButtons ..
|
|
*@var string the template file to use for this method
|
|
*/
|
|
protected $strTemplateUri;
|
|
/**
|
|
*@var string the FQD for the payment service provider API server
|
|
*/
|
|
protected $strRemoteDomainName;
|
|
/**
|
|
* Note: This must contain the separater character that follows the script name, eg. '?' or '&'
|
|
* if the request is of type GET
|
|
*@var string the URL portion after the domain name leading to API script
|
|
*/
|
|
protected $strRemoteCgiUrl;
|
|
/**
|
|
* This is the URL to which a customer is redirected to make a payment (eg. www.paypal.com ..)
|
|
*@var string the FQD for the payment service provider redirect target
|
|
*/
|
|
protected $strRedirectDomainName;
|
|
/**
|
|
* Note: This must contain the separater character that follows the script name, eg. '?' or '&'
|
|
* if the request is of type GET
|
|
*@var string the URL portion after the domain name leading to redirect script
|
|
*/
|
|
protected $strRedirectCgiUrl;
|
|
/**
|
|
*@var string storage for response from service
|
|
*/
|
|
protected $strResponse;
|
|
/**
|
|
*@var string storage for POST request to service
|
|
*/
|
|
protected $strPOSTRequest;
|
|
/**
|
|
*@var string query string appended to CGI URL for a GET request
|
|
*/
|
|
protected $strGETRequest;
|
|
/**
|
|
*@var string The type of request to be made (GET | POST )
|
|
*/
|
|
protected $strRequestType;
|
|
/**
|
|
*@var boolean True if we should use CURL to connect to the provider
|
|
*/
|
|
protected $blnUseCurl = false;
|
|
/**
|
|
*@var boolean True if we should use SSL to connect to the provider
|
|
*/
|
|
protected $blnUseSsl = true;
|
|
/**
|
|
*@var boolean True if we should use SSL certificate to authenticate ourselves ..
|
|
*/
|
|
protected $blnUseSslCertificate = false;
|
|
/**
|
|
*@var string full path to our SSL certificate
|
|
*/
|
|
protected $strSslCertificateUri;
|
|
/**
|
|
*@var integer Port number to use for the connection (80, 443)
|
|
*/
|
|
protected $intPort = 443;
|
|
/**
|
|
*@var integer Connection time out in seconds
|
|
*/
|
|
protected $intTimeOut = 60;
|
|
/**
|
|
*@var boolean True if there were errors or if the transaction/connection failed for any reason
|
|
*/
|
|
protected $blnHasErrors;
|
|
/**
|
|
*@var string Errors
|
|
*/
|
|
protected $strErrors;
|
|
/**
|
|
* This contains a string which either confirms the approval of a payment or gives a reason
|
|
* for its failure.
|
|
*@var string status explanation text from the transaction
|
|
*/
|
|
protected $strStatusText;
|
|
/**
|
|
*@var boolean True if the transaction was approved
|
|
*/
|
|
protected $blnApproved = false;
|
|
/**
|
|
* NOTE: You must explicitly set this to disable testing mode ..
|
|
*@var boolean True for testing (and by default)
|
|
*/
|
|
protected $blnTestMode = true;
|
|
|
|
/**
|
|
*@var float total charges for items
|
|
*/
|
|
protected $fltSubTotal;
|
|
/**
|
|
*@var float Charges for shipping
|
|
*/
|
|
protected $fltShippingCharge;
|
|
/**
|
|
*@var float Charges for handling
|
|
*/
|
|
protected $fltHandlingCharge;
|
|
/**
|
|
*@var float Taxes
|
|
*/
|
|
protected $fltTax;
|
|
|
|
/**
|
|
*@var string CC for customer
|
|
*/
|
|
protected $strCCNumber;
|
|
/**
|
|
*@var string CC expiration data for customer
|
|
*/
|
|
protected $strCCExpirationYear;
|
|
protected $strCCExpirationMonth;
|
|
/**
|
|
*@var string CCV number for customer
|
|
*/
|
|
protected $strCCVNumber;
|
|
|
|
/**
|
|
*@var float Total of all charges, items, shipping, tax and handling
|
|
*/
|
|
protected $fltTotalPrice;
|
|
|
|
/**
|
|
* PaymentActionBase Constructor
|
|
*
|
|
* @param Order objOrder - the order for which we are attempting to pay..
|
|
*/
|
|
public function __construct(Order $objOrder){
|
|
$this->objOrder =& $objOrder;
|
|
$this->fltHandlingCharge = $objOrder->HandlingCharged;
|
|
$this->fltShippingCharge = $objOrder->ShippingCharged;
|
|
$this->fltTax = $objOrder->Tax;
|
|
|
|
$this->fltTotalPrice = $objOrder->ProductTotalCharged
|
|
+ $objOrder->HandlingCharged
|
|
+ $objOrder->ShippingCharged
|
|
+ $objOrder->Tax;
|
|
|
|
}
|
|
/**
|
|
* Performs any preparation steps prior to submitting an actual payment. For example
|
|
* the PayPal NVP (Express Checkout) calls the SetExpressCheckoutDetails API here
|
|
*@return bool true on success
|
|
*/
|
|
abstract public function PreProcess();
|
|
/**
|
|
* Performs any steps necessary after submitting an actual payment. For example
|
|
* the PayPal NVP (Express Checkout) calls the GetExpressCheckoutDetails API here.
|
|
* Updating order_status_history is also performed here.
|
|
*@return bool true on success
|
|
*/
|
|
abstract public function PostProcess();
|
|
/**
|
|
* Performs the actual payment submission - in some cases this is a request/response
|
|
* routine and in some (eg. PayPal), this simply redirects the user to the provider site.
|
|
*@return bool true on success
|
|
*/
|
|
abstract public function Process();
|
|
/**
|
|
* Parses the response from the payment service provider
|
|
*/
|
|
abstract protected function handleResponse();
|
|
/**
|
|
* Creates GET query string for the transaction appropriate to the provider API, storing
|
|
* the result in strGETRequest.
|
|
*/
|
|
abstract protected function createGETRequest();
|
|
/**
|
|
* Creates GET query string for the transaction appropriate to the provider API, storing
|
|
* the result in strGETRequest.
|
|
*/
|
|
abstract protected function createPOSTRequest();
|
|
|
|
/**
|
|
* This function directs the call to the appropriate creation function and returns
|
|
* a string containing either a query string for a GET or a content string for a POST.
|
|
*
|
|
* A RequestType may be provided to override a default as an alternative to setting
|
|
* it explicitly. Note that this will set the RequestType for the object.
|
|
*
|
|
*@param string strRequestType - you may provide the RequestType
|
|
*/
|
|
protected function createRequest($strRequestType=null){
|
|
if(null != $strRequestType)
|
|
$this->strRequestType = $strRequestType;
|
|
|
|
switch($this->strRequestType){
|
|
case 'GET':
|
|
$this->createGETRequest();
|
|
return $this->strGETRequest;
|
|
break;
|
|
case 'POST':
|
|
$this->createPOSTRequest();
|
|
return $this->strPOSTRequest;
|
|
break;
|
|
case 'SOAP':
|
|
default:
|
|
throw new QCallerException('PaymentAction - Unsupported RequestType: ' . $this->strRequestType);
|
|
}
|
|
}
|
|
/**
|
|
* Connects to payment service and submits the request. Note that
|
|
* this function merely constructs a request URL from internal variables
|
|
* that are set in createRequest, it may therefor contain a GET query
|
|
* string or not depending on the subclass requirements.
|
|
*
|
|
*@return bool true on success
|
|
*/
|
|
protected function submitRequest(){
|
|
if($this->UseCurl)
|
|
return $this->submitCurlRequest();
|
|
|
|
if($this->UseSsl)
|
|
$strProtocol = 'ssl://';
|
|
else
|
|
$strProtocol = 'http://';
|
|
|
|
//attempt to connect ..
|
|
$fp = fsockopen($strProtocol . $this->strRemoteDomainName,
|
|
$this->intPort,
|
|
$intError,
|
|
$strError,
|
|
$this->intTimeOut
|
|
);
|
|
|
|
//did we connect?
|
|
if (!$fp){
|
|
/* $this->blnHasErrors = true;
|
|
return 0;*/
|
|
throw new QCallerException("Payment Action base: Connection request failed: $strError ($intError) ");
|
|
}else{
|
|
//construct the request ..
|
|
switch( $this->strRequestType ){
|
|
case 'GET':
|
|
$out = "GET " . $this->strRemoteCgiUrl . $this->strGETRequest . " HTTP/1.1\r\n";
|
|
$out .= "Host:" . $this->strRemoteDomainName . "\r\n";
|
|
$out .= "User-Agent: QuintaCMS " . QUINTA_VERSION . "\r\n";
|
|
$out .= "Connection: Close\r\n\r\n";
|
|
break;
|
|
case 'POST':
|
|
$out = "POST " . $this->strRemoteCgiUrl . " HTTP/1.1\r\n";
|
|
$out .= "Host:" . $this->strRemoteDomainName . "\r\n";
|
|
$out .= "User-Agent: QuintaCMS " . QUINTA_VERSION . "\r\n";
|
|
// $out .= "MIME-Version: 1.0\r\n";
|
|
$out .= "Content-Type: application/x-www-form-urlencoded\r\n";
|
|
// $out .= "Accept: text/xml\r\n";
|
|
$out .= "Content-length: " . strlen($this->strPOSTRequest) . "\r\n";
|
|
$out .= "Cache-Control: no-cache\r\n";
|
|
$out .= "Connection: Close\r\n\r\n";
|
|
$out .= $this->strPOSTRequest . "\r\n\r\n";
|
|
break;
|
|
default:
|
|
throw new QCallerException('WebService RequestType unsupported: ' . $this->RequestType);
|
|
}
|
|
//send the request
|
|
fwrite($fp, $out );
|
|
|
|
$this->strResponse = '';
|
|
//store the response
|
|
while ( !feof($fp) )
|
|
$this->strResponse .= fgets($fp, 128);
|
|
$this->strResponse .= $out;
|
|
fclose($fp);
|
|
}
|
|
return true;
|
|
}
|
|
/**
|
|
* This is an alternate request submission method using CURL
|
|
* @return bool true on sucess
|
|
*/
|
|
protected function submitCurlRequest(){
|
|
if($this->UseSsl)
|
|
$strProtocol = 'https://';
|
|
else
|
|
$strProtocol = 'http://';
|
|
|
|
$objCurlHandle = curl_init();
|
|
curl_setopt($objCurlHandle, CURLOPT_USERAGENT, 'QuintaCMS ' . QUINTA_VERSION);
|
|
curl_setopt($objCurlHandle, CURLOPT_TIMEOUT, $this->intTimeOut);
|
|
curl_setopt ($objCurlHandle, CURLOPT_RETURNTRANSFER, 1);
|
|
|
|
switch( $this->strRequestType ){
|
|
case 'GET':
|
|
$strUri = $strProtocol . $this->strRemoteDomainName . $this->strRemoteCgiUrl . $this->strGETRequest;
|
|
curl_setopt($objCurlHandle, CURLOPT_URL, $strUri);
|
|
break;
|
|
case 'POST':
|
|
$strUri = $strProtocol . $this->strRemoteDomainName . $this->strRemoteCgiUrl;
|
|
curl_setopt($objCurlHandle, CURLOPT_POST, 1);
|
|
curl_setopt($objCurlHandle, CURLOPT_URL, $strUri);
|
|
curl_setopt($objCurlHandle, CURLOPT_POSTFIELDS, $this->strPOSTRequest);
|
|
break;
|
|
default:
|
|
throw new QCallerException('WebService RequestType unsupported: ' . $this->RequestType);
|
|
}
|
|
|
|
if($this->UseSsl){
|
|
curl_setopt($objCurlHandle, CURLOPT_SSL_VERIFYHOST, 0);
|
|
curl_setopt($objCurlHandle, CURLOPT_SSL_VERIFYPEER, 0);
|
|
}
|
|
|
|
if($this->UseSslCertificate)
|
|
curl_setopt($objCurlHandle, CURLOPT_SSLCERT,$this->strSslCertificateUri);
|
|
|
|
$this->strResponse = curl_exec($objCurlHandle);
|
|
if(false === $this->strResponse ){
|
|
$this->HasErrors = true;
|
|
$this->strErrors .= curl_error($objCurlHandle);
|
|
return false;
|
|
}
|
|
curl_close($objCurlHandle);
|
|
}
|
|
/**
|
|
*This function sets the order status for an order when payment is approved.
|
|
* It also inserts a new order_status_history and clears the shopping cart. The confirmation
|
|
* email is handled by Order when the status is set.
|
|
*/
|
|
public function completeOrder(){
|
|
/* this is now done with __set magic in Order and the notes seem superflous, disabled pending removal
|
|
$objOrderStatusHistory = new OrderStatusHistory();
|
|
$objOrderStatusHistory->OrderId = $this->objOrder->Id;
|
|
$objOrderStatusHistory->StatusId = OrderStatusType::Paid;
|
|
$objOrderStatusHistory->Notes = $this->objOrder->Notes;
|
|
$objOrderStatusHistory->Save();
|
|
*/
|
|
// Order can send its own email confirmations ..
|
|
$this->objOrder->SetStatus(OrderStatusType::Paid);
|
|
|
|
IndexPage::$objShoppingCart->DeleteAllShoppingCartItems();
|
|
}
|
|
|
|
public function __get($strName){
|
|
switch ($strName){
|
|
case 'TotalPrice':
|
|
return $this->fltTotalPrice;
|
|
case 'StatusText':
|
|
return $this->strStatusText;
|
|
case 'TemplateUri':
|
|
return $this->strTemplateUri ;
|
|
case 'Order':
|
|
return $this->objOrder ;
|
|
case 'OrderId':
|
|
return $this->objOrder->Id ;
|
|
case 'RemotePassword':
|
|
return $this->strRemotePassword ;
|
|
case 'RemoteAccountId':
|
|
return $this->strRemoteAccountId ;
|
|
case 'RemoteCgiUrl':
|
|
return $this->strRemoteCgiUrl ;
|
|
case 'RemoteDomainName':
|
|
return $this->strRemoteDomainName ;
|
|
case 'Errors':
|
|
return $this->strErrors ;
|
|
case 'Approved':
|
|
return $this->blnApproved ;
|
|
case 'HasErrors':
|
|
return $this->blnHasErrors ;
|
|
case 'UseCurl':
|
|
return $this->blnUseSsl ;
|
|
case 'UseSsl':
|
|
return $this->blnUseSsl ;
|
|
case 'UseSslCertificate':
|
|
return $this->blnUseSslCertificate ;
|
|
case 'SslCertificateUri':
|
|
return $this->strSslCertificateUri;
|
|
case 'TestMode':
|
|
return $this->blnTestMode ;
|
|
case 'CCNumber':
|
|
return $this->strCCNumber ;
|
|
case 'CCExpirationYear':
|
|
return $this->strCCExpirationYear ;
|
|
case 'CCExpirationMonth':
|
|
return $this->strCCExpirationMonth ;
|
|
case 'CCVNumber':
|
|
return $this->strCCVNumber ;
|
|
default:
|
|
throw new QCallerException('Payment Action - Access Unknown property: ' . $strName);
|
|
}
|
|
}
|
|
|
|
public function __set($strName, $mixValue){
|
|
switch ($strName){
|
|
case 'TemplateUri':
|
|
try {
|
|
return ($this->strTemplateUri = QType::Cast($mixValue, QType::String ));
|
|
} catch (QInvalidCastException $objExc) {
|
|
$objExc->IncrementOffset();
|
|
throw $objExc;
|
|
}
|
|
case 'TransactionId':
|
|
try {
|
|
return ($this->strTransactionId = QType::Cast($mixValue, QType::String ));
|
|
} catch (QInvalidCastException $objExc) {
|
|
$objExc->IncrementOffset();
|
|
throw $objExc;
|
|
}
|
|
case 'OrderId':
|
|
try {
|
|
return ($this->strOrderId = QType::Cast($mixValue, QType::String ));
|
|
} catch (QInvalidCastException $objExc) {
|
|
$objExc->IncrementOffset();
|
|
throw $objExc;
|
|
}
|
|
case 'RemoteDomainName':
|
|
try {
|
|
return ($this->strRemoteDomainName = QType::Cast($mixValue, QType::String ));
|
|
} catch (QInvalidCastException $objExc) {
|
|
$objExc->IncrementOffset();
|
|
throw $objExc;
|
|
}
|
|
case 'RemoteCgiUrl':
|
|
try {
|
|
return ($this->strRemoteCgiUrl = QType::Cast($mixValue, QType::String ));
|
|
} catch (QInvalidCastException $objExc) {
|
|
$objExc->IncrementOffset();
|
|
throw $objExc;
|
|
}
|
|
case 'RemoteAccountId':
|
|
try {
|
|
return ($this->strRemoteAccountId = QType::Cast($mixValue, QType::String ));
|
|
} catch (QInvalidCastException $objExc) {
|
|
$objExc->IncrementOffset();
|
|
throw $objExc;
|
|
}
|
|
case 'RemotePassword':
|
|
try {
|
|
return ($this->strRemotePassword = QType::Cast($mixValue, QType::String ));
|
|
} catch (QInvalidCastException $objExc) {
|
|
$objExc->IncrementOffset();
|
|
throw $objExc;
|
|
}
|
|
case 'Approved':
|
|
try {
|
|
return ($this->blnApproved = QType::Cast($mixValue, QType::Boolean ));
|
|
} catch (QInvalidCastException $objExc) {
|
|
$objExc->IncrementOffset();
|
|
throw $objExc;
|
|
}
|
|
case 'Errors':
|
|
try {
|
|
return ($this->strErrors = QType::Cast($mixValue, QType::String ));
|
|
} catch (QInvalidCastException $objExc) {
|
|
$objExc->IncrementOffset();
|
|
throw $objExc;
|
|
}
|
|
case 'HasErrors':
|
|
try {
|
|
return ($this->blnHasErrors = QType::Cast($mixValue, QType::Boolean ));
|
|
} catch (QInvalidCastException $objExc) {
|
|
$objExc->IncrementOffset();
|
|
throw $objExc;
|
|
}
|
|
case 'UseCurl':
|
|
try {
|
|
return ($this->blnUseCurl = QType::Cast($mixValue, QType::Boolean ));
|
|
} catch (QInvalidCastException $objExc) {
|
|
$objExc->IncrementOffset();
|
|
throw $objExc;
|
|
}
|
|
case 'UseSsl':
|
|
try {
|
|
return ($this->blnUseSsl = QType::Cast($mixValue, QType::Boolean ));
|
|
} catch (QInvalidCastException $objExc) {
|
|
$objExc->IncrementOffset();
|
|
throw $objExc;
|
|
}
|
|
case 'UseSslCertificate':
|
|
try {
|
|
return ($this->blnUseSslCertificate = QType::Cast($mixValue, QType::Boolean ));
|
|
} catch (QInvalidCastException $objExc) {
|
|
$objExc->IncrementOffset();
|
|
throw $objExc;
|
|
}
|
|
case 'SslCertificateUri':
|
|
try {
|
|
return ($this->strSslCertificateUri = QType::Cast($mixValue, QType::String ));
|
|
} catch (QInvalidCastException $objExc) {
|
|
$objExc->IncrementOffset();
|
|
throw $objExc;
|
|
}
|
|
case 'TestMode':
|
|
try {
|
|
return ($this->blnTestMode = QType::Cast($mixValue, QType::Boolean ));
|
|
} catch (QInvalidCastException $objExc) {
|
|
$objExc->IncrementOffset();
|
|
throw $objExc;
|
|
}
|
|
case 'Port':
|
|
try {
|
|
return ($this->intPort = QType::Cast($mixValue, QType::Integer ));
|
|
} catch (QInvalidCastException $objExc) {
|
|
$objExc->IncrementOffset();
|
|
throw $objExc;
|
|
}
|
|
case 'TimeOut':
|
|
try {
|
|
return ($this->intTimeOut = QType::Cast($mixValue, QType::Integer ));
|
|
} catch (QInvalidCastException $objExc) {
|
|
$objExc->IncrementOffset();
|
|
throw $objExc;
|
|
}
|
|
case 'CCNumber':
|
|
try {
|
|
return ($this->strCCNumber = QType::Cast($mixValue, QType::String ));
|
|
} catch (QInvalidCastException $objExc) {
|
|
$objExc->IncrementOffset();
|
|
throw $objExc;
|
|
}
|
|
case 'CCExpirationYear':
|
|
try {
|
|
return ($this->strCCExpirationYear = QType::Cast($mixValue, QType::String ));
|
|
} catch (QInvalidCastException $objExc) {
|
|
$objExc->IncrementOffset();
|
|
throw $objExc;
|
|
}
|
|
case 'CCExpirationMonth':
|
|
try {
|
|
return ($this->strCCExpirationMonth = QType::Cast($mixValue, QType::String ));
|
|
} catch (QInvalidCastException $objExc) {
|
|
$objExc->IncrementOffset();
|
|
throw $objExc;
|
|
}
|
|
case 'CCVNumber':
|
|
try {
|
|
return ($this->strCCVNumber = QType::Cast($mixValue, QType::String ));
|
|
} catch (QInvalidCastException $objExc) {
|
|
$objExc->IncrementOffset();
|
|
throw $objExc;
|
|
}
|
|
|
|
default:
|
|
throw new QCallerException('Payment Action - Set Unknown property: ' . $strName);
|
|
}
|
|
}
|
|
|
|
}//end class
|
|
}//end define
|
|
|
|
?>
|