|
<?php
|
|
if(!defined('QUINTACMS') ) die('No Quinta.');
|
|
|
|
if (!defined("PAYPALEXPRESSCHECKOUTACTION.CLASS.PHP")){
|
|
define("PAYPALEXPRESSCHECKOUTACTION.CLASS.PHP",1);
|
|
|
|
/**
|
|
* Class PayPalExpressCheckoutAction - PayPal Express Checkout payment action
|
|
*
|
|
* This class provides an implementation of the PayPal Express Checkout System including the
|
|
* two points of entry required. It can be activated either as a selected payment method on the
|
|
* checkout review page or by a click on the "Check out with PayPal" button.
|
|
*
|
|
* Express Checkout consists of 2 phases:
|
|
* 1. A request is sent to the server to set up the transaction details and a "token" is returned.
|
|
* This token is stored in _SESSION['PayPalToken'] to be used in the second phase
|
|
* Order status is left at pending and the customer is redirected to PayPal with the token as
|
|
* part of the redirect URL
|
|
* 2. PayPal returns the customer to the PayPalExpressReturn page - this page must exist in
|
|
* database and have in it the PayPalExpressReturnModule. This module will use this action
|
|
* again, calling getExpressCheckoutDetails to complete the transaction. The function will
|
|
* DoExpressCheckoutPayment
|
|
*
|
|
* Note that this payment action requires the existance of the return and cancel pages to which
|
|
* the customer is returned from PayPal - and that these pages must contain a content block with
|
|
* the PayPalExpressReturnModule to complete the process. Completion will consist of everything
|
|
* normally done in PaymentModule in btnPurchase_Click.
|
|
*
|
|
*@todo
|
|
* - finish stage two, sending confirmation email on approval and completing the order
|
|
*
|
|
*@author Erik Winn <sidewalksoftware@gmail.com>
|
|
*
|
|
*@version 0.3
|
|
*
|
|
*@package Quinta
|
|
* @subpackage Classes
|
|
*/
|
|
|
|
class PayPalExpressCheckoutAction extends PayPalNVPAction{
|
|
/**
|
|
* PayPalExpressCheckoutAction Constructor
|
|
*
|
|
* This sets various defaults specific to the Express Checkout process
|
|
*
|
|
*@todo
|
|
* - safeguard the return URLs - send session id? or, encode account id and order id ..
|
|
*
|
|
* @param Order objOrder - the Order to process
|
|
*/
|
|
public function __construct(Order $objOrder){
|
|
try {
|
|
parent::__construct($objOrder);
|
|
} catch (QCallerException $objExc) {
|
|
$objExc->IncrementOffset();
|
|
throw $objExc;
|
|
}
|
|
//the order id may be redundant - we already send INVNUM, this may go away ..
|
|
$this->aryRequestValues['RETURNURL'] = 'http://' . Quinta::$ServerName . __QUINTA_SUBDIRECTORY__
|
|
. '/index.php/PayPalExpressReturn?orderid=' . $this->objOrder->Id;
|
|
$this->aryRequestValues['CANCELURL'] = 'http://' . Quinta::$ServerName . __QUINTA_SUBDIRECTORY__
|
|
. '/index.php/PayPalExpressCancel?orderid=' . $this->objOrder->Id;
|
|
|
|
$this->strRedirectCgiUrl = '/cgi-bin/webscr?';
|
|
|
|
$this->strTemplateUri = __QUINTA_CORE_VIEWS__ . '/PayPalExpressCheckoutAction.tpl.php';
|
|
}
|
|
/**
|
|
* The createRequest functions are handled by get/setExpressCheckoutDetails
|
|
* as they occur before and after a customer is redirected to PayPal and are
|
|
* therefor two separate requests.
|
|
*/
|
|
protected function createPOSTRequest(){}
|
|
protected function createGETRequest(){}
|
|
|
|
/**
|
|
* Performs any preparation steps prior to submitting an actual payment.
|
|
* We submit a call to the SetExpressCheckoutDetails API here and set up
|
|
* the values for the transaction. If successful, aryResponseValues['TOKEN']
|
|
* will contain the identifier for the transaction.
|
|
*@return bool true on success
|
|
*/
|
|
public function PreProcess(){
|
|
$this->aryRequestValues['METHOD'] = 'SetExpressCheckout';
|
|
$this->setExpressCheckoutDetails();
|
|
return ! $this->HasErrors;
|
|
}
|
|
/**
|
|
*
|
|
*@return bool true on success
|
|
*/
|
|
public function Process(){
|
|
return ! $this->HasErrors;
|
|
}
|
|
/**
|
|
* This function simply checks the return from setExpressCheckoutDetails and then
|
|
* redirects the user to PayPal's site to complete the payment
|
|
* Updating order_status_history is also performed here.
|
|
*@return bool true on success
|
|
*/
|
|
public function PostProcess(){
|
|
$strRedirectTarget = $this->strRedirectDomainName . $this->strRedirectCgiUrl
|
|
. 'cmd=_express-checkout&token=' . $this->objPaypalTransaction->PpToken;
|
|
Quinta::Redirect($strRedirectTarget );
|
|
}
|
|
|
|
/**
|
|
* Creates POST string for the first half of the PayPal transaction, setting the checkout details
|
|
* and storing the result in strPOSTRequest. Then a query is sent to NVP API server and the PayPal
|
|
* "token" is recieved which will be used by getExpressCheckoutDetails to validate the payment.
|
|
*/
|
|
protected function setExpressCheckoutDetails(){
|
|
$this->aryRequestValues['METHOD'] = 'SetExpressCheckout';
|
|
|
|
//optionally send a shipping address to display ..
|
|
if( $this->ShowShippingAddress )
|
|
$this->initShippingDetails();
|
|
else
|
|
$this->aryRequestValues['NOSHIPPING'] = 1;
|
|
|
|
$this->aryRequestValues['AMT'] = $this->fltTotalPrice;
|
|
$this->aryRequestValues['ITEMAMT'] = $this->objOrder->ProductTotalCharged;
|
|
$this->aryRequestValues['INVNUM'] = $this->objOrder->Id;
|
|
$this->aryRequestValues['SHIPPINGAMT'] = $this->objOrder->ShippingCharged;
|
|
$this->aryRequestValues['HANDLINGAMT'] = $this->objOrder->HandlingCharged;
|
|
$this->aryRequestValues['TAXAMT'] = $this->objOrder->Tax;
|
|
//truncated to fit PP specs in case of long store names ..
|
|
$this->aryRequestValues['DESC'] = urlencode(substr(STORE_NAME . ' Order ' . $this->objOrder->Id, 0, 127));
|
|
|
|
foreach( $this->aryRequestValues as $strName => $strValue )
|
|
if('' != $strValue )
|
|
$this->strPOSTRequest .= '&' . $strName . '=' . $strValue;
|
|
$this->submitRequest();
|
|
|
|
$this->handleResponse();
|
|
/* if( ! $this->submitRequest())
|
|
throw new Exception('PayPal EC submit failed: ' . $this->strPOSTRequest);
|
|
|
|
if( ! $this->handleResponse())
|
|
throw new Exception('PayPal EC submit failed: ' . $this->strErrors
|
|
. ' <br /><br /> Response: ' . $this->strResponse);*/
|
|
}
|
|
/**
|
|
* This function is called by PayPalExpressReturnModule when the buyer returns from PayPal.
|
|
*Here we determine the payer id and status in preparation for completing the order ..
|
|
*/
|
|
public function getExpressCheckoutDetails(){
|
|
$this->strPOSTRequest = '';
|
|
$this->aryRequestValues['METHOD'] = 'GetExpressCheckoutDetails';
|
|
$this->aryRequestValues['TOKEN'] = $_GET['token'];
|
|
// also adds default settings ..
|
|
foreach( $this->aryRequestValues as $strName => $strValue )
|
|
if('' != $strValue )
|
|
$this->strPOSTRequest .= '&' . $strName . '=' . $strValue;
|
|
|
|
$this->submitRequest();
|
|
$this->handleResponse();
|
|
if( $this->HasErrors )
|
|
return false;
|
|
if( '' == $this->objPaypalTransaction->PayerId ){
|
|
$this->HasErrors = true;
|
|
$this->objPaypalTransaction->Messages .= '| Quinta PP Express: No PayerId returned! |';
|
|
$this->objPaypalTransaction->Save();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
/**
|
|
* This function overrides the PaymentActionBase completion - it is called by PayPalExpressReturnModule
|
|
* when the buyer returns from PayPal.
|
|
*@todo handle PENDINGREASON, REASONCODE, PAYMENTTYPE != INSTANT ..
|
|
*/
|
|
public function doExpressCheckoutPayment(){
|
|
$this->strPOSTRequest = '';
|
|
$this->aryRequestValues['METHOD'] = 'DoExpressCheckoutPayment';
|
|
$this->aryRequestValues['TOKEN'] = $this->objPaypalTransaction->PpToken;
|
|
$this->aryRequestValues['PAYERID'] = $this->objPaypalTransaction->PayerId;
|
|
$this->aryRequestValues['INVNUM'] = $this->objOrder->Id;
|
|
$this->aryRequestValues['AMT'] = $this->fltTotalPrice;
|
|
$this->aryRequestValues['ITEMAMT'] = $this->objOrder->ProductTotalCharged;
|
|
$this->aryRequestValues['SHIPPINGAMT'] = $this->objOrder->ShippingCharged;
|
|
$this->aryRequestValues['HANDLINGAMT'] = $this->objOrder->HandlingCharged;
|
|
$this->aryRequestValues['TAXAMT'] = $this->objOrder->Tax;
|
|
// also adds default settings ..
|
|
foreach( $this->aryRequestValues as $strName => $strValue )
|
|
if('' != $strValue )
|
|
$this->strPOSTRequest .= '&' . $strName . '=' . $strValue;
|
|
$this->submitRequest();
|
|
$this->handleResponse();
|
|
if( $this->HasErrors )
|
|
return false;
|
|
|
|
// check for the payment status
|
|
$strStatus = $this->objPaypalTransaction->PaymentStatus;
|
|
if('' == $strStatus ){
|
|
$this->HasErrors = true;
|
|
$this->objPaypalTransaction->Messages .= '| Quinta PP Express: No Payment status returned! |';
|
|
$this->objPaypalTransaction->Save();
|
|
return false;
|
|
}
|
|
|
|
///@todo handle PENDINGREASON, REASONCODE, PAYMENTTYPE != INSTANT ..
|
|
switch( strtoupper($strStatus) ){
|
|
case 'COMPLETED':
|
|
$this->blnApproved = true;
|
|
break;
|
|
case 'PENDING':
|
|
default:
|
|
$this->blnApproved = false;
|
|
}
|
|
}
|
|
|
|
protected function initShippingDetails(){
|
|
$this->aryRequestValues['NOSHIPPING'] = 0;
|
|
$this->aryRequestValues['ADDRESSOVERRIDE'] = 1;
|
|
|
|
//We must concatenate all the name fields into NAME ..
|
|
if( '' != $this->objOrder->ShippingNamePrefix )
|
|
$this->aryRequestValues['SHIPTONAME'] = urlencode($this->objOrder->ShippingNamePrefix . ' ');
|
|
$this->aryRequestValues['SHIPTONAME'] .= urlencode($this->objOrder->ShippingFirstName);
|
|
|
|
if( '' != $this->objOrder->ShippingMiddleName )
|
|
$this->aryRequestValues['SHIPTONAME'] .= urlencode(' ' . $this->objOrder->ShippingMiddleName);
|
|
$this->aryRequestValues['SHIPTONAME'] .= urlencode(' ' . $this->objOrder->ShippingLastName);
|
|
|
|
if( '' != $this->objOrder->ShippingNameSuffix )
|
|
$this->aryRequestValues['SHIPTONAME'] .= urlencode(' ' . $this->objOrder->ShippingNameSuffix);
|
|
|
|
$this->aryRequestValues['SHIPTOSTREET'] = urlencode($this->objOrder->ShippingStreet1);
|
|
|
|
if( '' != $this->objOrder->ShippingStreet2 )
|
|
$this->aryRequestValues['SHIPTOSTREET2'] = urlencode($this->objOrder->ShippingStreet2);
|
|
//PayPal offers no field for County or Suburb so we must put them in street2 ..
|
|
if( '' != $this->objOrder->ShippingSuburb )
|
|
$this->aryRequestValues['SHIPTOSTREET2'] .= urlencode(', ' . $this->objOrder->ShippingSuburb);
|
|
if( '' != $this->objOrder->ShippingCounty )
|
|
$this->aryRequestValues['SHIPTOSTREET2'] .= urlencode(', ' . $this->objOrder->ShippingCounty);
|
|
|
|
$this->aryRequestValues['SHIPTOCITY'] = urlencode($this->objOrder->ShippingCity);
|
|
$this->aryRequestValues['SHIPTOSTATE'] = urlencode($this->objOrder->ShippingState);
|
|
$this->aryRequestValues['SHIPTOCOUNTRY'] = urlencode($this->objOrder->ShippingCountry);
|
|
$this->aryRequestValues['SHIPTOZIP'] = urlencode($this->objOrder->ShippingPostalCode);
|
|
}
|
|
/**
|
|
* Creates GET query string for the second half of the PayPal transaction. This function is called
|
|
* by the PayPalExpressReturnModule only, which is effectively the return URL given to PayPal
|
|
* in the first half of the transaction to which the user is redirected after paying. The "token"
|
|
*/
|
|
|
|
public function __get($strName){
|
|
switch ($strName){
|
|
case 'ShowShippingAddress':
|
|
return $this->blnShowShippingAddress;
|
|
default:
|
|
try {
|
|
return parent::__get($strName);
|
|
} catch (QCallerException $objExc) {
|
|
$objExc->IncrementOffset();
|
|
throw $objExc;
|
|
}
|
|
}
|
|
}
|
|
|
|
public function __set($strName, $mixValue){
|
|
switch ($strName){
|
|
case 'ShowShippingAddress':
|
|
try {
|
|
$this->blnShowShippingAddress = QType::Cast($mixValue, QType::Boolean );
|
|
} catch (QInvalidCastException $objExc) {
|
|
$objExc->IncrementOffset();
|
|
throw $objExc;
|
|
}
|
|
//careful, its backwards ..
|
|
$this->aryRequestValues['NOSHIPPING'] = (true === $mixValue) ? 0 : 1;
|
|
default:
|
|
try {
|
|
return (parent::__set($strName, $mixValue));
|
|
} catch (QCallerException $objExc) {
|
|
$objExc->IncrementOffset();
|
|
throw $objExc;
|
|
}
|
|
}
|
|
}
|
|
|
|
}//end class
|
|
}//end define
|
|
|
|
?>
|