* * $Id: CheckOutModule.class.php 457 2008-12-23 20:11:54Z erikwinn $ *@version 0.1 * *@copyright (C) 2008 by Erik Winn *@license GPL v.2 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA * *@package Quasi * @subpackage Modules */ class CheckOutModule extends QPanel { protected $objAccount = null; protected $objShoppingCart = null; protected $intCurrentStage = 0; /** *@var Order - the current order */ protected $objOrder = null; /** *@var ShippingMethod - reference to the selected shipping method */ protected $objShippingMethod = null; /** *@var PaymentMethod - reference to the selected payment method */ protected $objPaymentMethod = null; /** *@var boolean blnCartEmpty - set to true if there are no items in the cart */ protected $blnCartEmpty = true; /** * This indicates whether the customer has clicked "Purchase" yet - if they do this and * then hit back in the browser we want to clean up and start over .. * NOTE: this needs to be moved somewhere global to maintain state ( perhaps even $_SESSION since * the user may have been transferred offsite ) *@todo - handle browser back actions when payment already initiated *@var boolean */ protected $blnTransactionInitiated=false; /** *@var boolean blnTransactionApproved - indicates whether the payment was accepted or not */ protected $blnTransactionApproved = false; /** *@var string strTransactionMessage - string to contain messages from the payment server */ protected $strTransactionMessage; /** *@var string strErrors - general error storage .. */ protected $strErrors; /** *@var Address - reference to the selected or default shipping address */ public $objShippingAddress; /** *@var Address - reference to the selected or default billing address */ public $objBillingAddress; /** * @var OrderTotalsView - view panel to display the order totals */ public $objOrderTotalsView; /** * @var PaymentModule - module to manage the selection of a payment method */ public $objPaymentModule; /** * @var ShippingModule - module to manage the selection of a shipping method */ public $objShippingModule; /** * @var CheckOutReviewModule - module to display a review of the order and accept submission */ public $objCheckOutReviewModule; /** * @var CheckOutConfirmationModule - module to display order/payment confirmation or errors */ public $objCheckOutConfirmationModule; /** * @var Qlabel lblProgressBar - shows a progress image indicating the current stage */ public $lblProgressBar; /** * This refers to the currently active module - it is assigned a module according to the current * stage of the process *@var QPanel - the current panel. */ public $pnlCurrentPanel = null; /** * This panel shows the title heading for currently active module - it is assigned text according to the current * stage of the process *@var QPanel - the current panel title heading. */ public $pnlHeading; public $btnContinue; public $btnCancel; public $btnBack; public $objWaitIcon; public $pnlHeading; public $lblMessages; /** * Module constructor * NOTE: This module ignores the required extra parameters .. *@param ContentBlockView - parent controller object. *@param mixed - extra parameters, ignored */ public function __construct( ContentBlockView $objParentObject, $mixParameters=null) { try { parent::__construct($objParentObject); } catch (QCallerException $objExc) { $objExc->IncrementOffset(); throw $objExc; } $this->objAccount =& IndexPage::$objAccount; $this->objShoppingCart =& IndexPage::$objShoppingCart; $this->AutoRenderChildren = true; $this->strTemplate = __QUASI_CORE_TEMPLATES__ . '/CheckOutModule.tpl.php'; $this->pnlHeading = new QPanel($this); $this->pnlHeading->HtmlEntities = false; $this->pnlHeading->AddCssClass('CheckoutHeading'); $this->lblProgressBar = new QLabel($this); $this->lblProgressBar->HtmlEntities = false; $this->lblProgressBar->CssClass = 'ProgressBarShipping'; $this->lblProgressBar->Text = sprintf('%s%s %s%s %s%s', STORE_NAME . ' ' . Quasi::Translate('Checkout Process') . ':', Quasi::Translate('cart'), Quasi::Translate('shipping'), Quasi::Translate('payment'), Quasi::Translate('review order'), Quasi::Translate('receipt')); $this->lblMessages = new QLabel($this); $this->lblMessages->HtmlEntities = false; $this->pnlCurrentPanel = new QPanel($this); $this->pnlCurrentPanel->AutoRenderChildren = true; // Only show anything if there is an active Account and ShoppingCart if( $this->objAccount instanceof Account && $this->objShoppingCart instanceof ShoppingCart) { $this->objOrder = $this->objShoppingCart->CreateNewOrder(true); $aryOrderItems = $this->objOrder->GetNewOrderItemsArray(); if ( empty($aryOrderItems) ) { $this->pnlHeading->Text = '   ' . Quasi::Translate('Empty Cart'); // $this->pnlCurrentPanel = new QPanel($this); $this->pnlCurrentPanel->Text = Quasi::Translate('There are no items to check out') . '.'; return; } else $this->blnCartEmpty = false; //create a display of the order summary .. $this->objOrderTotalsView = new OrderTotalsView($this, $this->objOrder); //create buttons to manage work flow between panels - the signals are handled here. // Continue goes to the next panel, Back returns to previous panel, Cancel goes Home $this->btnContinue = new QButton($this); $this->btnContinue->Text = Quasi::Translate('Continue'); if(IndexPage::$blnAjaxOk) $this->btnContinue->AddAction(new QClickEvent(), new QAjaxControlAction($this, 'btnContinue_Click')); else $this->btnContinue->AddAction(new QClickEvent(), new QServerControlAction($this, 'btnContinue_Click')); $this->btnBack = new QButton($this); $this->btnBack->Text = Quasi::Translate('Back'); if(IndexPage::$blnAjaxOk) $this->btnBack->AddAction(new QClickEvent(), new QAjaxControlAction($this, 'btnBack_Click')); else $this->btnBack->AddAction(new QClickEvent(), new QServerControlAction($this, 'btnBack_Click')); $this->btnCancel = new QButton($this); $this->btnCancel->Text = Quasi::Translate('Cancel'); if(IndexPage::$blnAjaxOk) $this->btnCancel->AddAction(new QClickEvent(), new QAjaxControlAction($this, 'btnCancel_Click')); else $this->btnCancel->AddAction(new QClickEvent(), new QServerControlAction($this, 'btnCancel_Click')); $this->objWaitIcon = new QWaitIcon($this); //start out with the shipping options displayed $this->GoForward( CheckOutStage::Shipping ); } else $this->pnlCurrentPanel->Text = Quasi::Translate('We are sorry, you must be logged in to check out.'); } /** * This function is called when the user clicks "Back" - it checks for the current stage and * calls GoBack with the appropriate parameters. *@param string strFormId - a string representation of the CSS Id for the main form *@param string strControlId - a string representation of the CSS Id for the control calling this function *@param string strParameter - a string containing optionally set parameters */ public function btnBack_Click($strFormId, $strControlId, $strParameter) { switch( $this->intCurrentStage ) { case CheckOutStage::Shipping: return $this->GoBack(CheckOutStage::ShoppingCart); case CheckOutStage::Payment: return $this->GoBack(CheckOutStage::Shipping); case CheckOutStage::Review: return $this->GoBack(CheckOutStage::Payment); } } /** * This function is called when the user clicks "Continue" - it checks for the current stage and * calls GoForward with the appropriate parameters. *@param string strFormId - a string representation of the CSS Id for the main form *@param string strControlId - a string representation of the CSS Id for the control calling this function *@param string strParameter - a string containing optionally set parameters */ public function btnContinue_Click($strFormId, $strControlId, $strParameter) { switch( $this->intCurrentStage ) { case CheckOutStage::Shipping: return $this->GoForward(CheckOutStage::Payment); case CheckOutStage::Payment: return $this->GoForward(CheckOutStage::Review); case CheckOutStage::Review: return $this->GoForward(CheckOutStage::Confirmation); case CheckOutStage::Confirmation: QApplication::Redirect(__QUASI_SUBDIRECTORY__ . '/index.php/Home'); } } /** * This function is called when the user clicks "Cancel" - it deletes the order and redirects * the user to the home page .. *@param string strFormId - a string representation of the CSS Id for the main form *@param string strControlId - a string representation of the CSS Id for the control calling this function *@param string strParameter - a string containing optionally set parameters */ public function btnCancel_Click($strFormId, $strControlId, $strParameter) { //NOTE: perhaps we should clear the shopping cart here? or provide some // other way to do that somewhere .. $this->GoForward(CheckOutStage::Cancel); } public function GoBack($NextStage) { $this->pnlCurrentPanel->RemoveChildControls(false); switch($NextStage) { case CheckOutStage::ShoppingCart: Quasi::Redirect('http://' . Quasi::$ServerName . __QUASI_SUBDIRECTORY__ . '/index.php/ShoppingCart'); break; case CheckOutStage::Shipping: $this->pnlHeading->Text = '   ' . Quasi::Translate('Shipping Options'); $this->lblProgressBar->CssClass = 'ProgressBarShipping'; $this->objShippingModule->SetParentControl($this->pnlCurrentPanel); $this->objOrderTotalsView->Visible = true; break; case CheckOutStage::Payment: $this->pnlHeading->Text = '   ' . Quasi::Translate('Payment Options'); $this->lblProgressBar->CssClass = 'ProgressBarPayment'; $this->objPaymentModule->SetParentControl($this->pnlCurrentPanel); /* if($this->objCheckOutReviewModule instanceof CheckOutReviewModule) $this->objCheckOutReviewModule->Visible = false;*/ $this->objOrderTotalsView->Visible = true; break; } $this->btnContinue->Text = Quasi::Translate('Continue'); $this->intCurrentStage = $NextStage; } private function GoForward($NextStage) { if(null !== $this->pnlCurrentPanel) { $this->pnlCurrentPanel->Visible = false; $this->pnlCurrentPanel->RemoveChildControls(false); } switch($NextStage) { case CheckOutStage::Shipping: //the spaces make room for the CSS cart image .. $this->pnlHeading->Text = '   ' . Quasi::Translate('Shipping Options'); $this->lblProgressBar->CssClass = 'ProgressBarShipping'; //to debug: $this->objShippingModule = new ShippingModule($this->pnlCurrentPanel, $this, $this->objOrder, true); $this->objShippingModule = new ShippingModule($this->pnlCurrentPanel, $this, $this->objOrder); $this->intCurrentStage = CheckOutStage::Shipping; break; case CheckOutStage::Payment: $this->pnlHeading->Text = '   ' . Quasi::Translate('Payment Options'); $this->lblProgressBar->CssClass = 'ProgressBarPayment'; if($this->objPaymentModule instanceof PaymentModule) $this->objPaymentModule->SetParentControl($this->pnlCurrentPanel); else { $this->pnlCurrentPanel->RemoveChildControls(false); $this->objPaymentModule = new PaymentModule($this->pnlCurrentPanel, $this->objOrder); } $this->intCurrentStage = CheckOutStage::Payment; break; case CheckOutStage::Review: $this->pnlHeading->Text = '   ' . Quasi::Translate('Order Review'); $this->lblProgressBar->CssClass = 'ProgressBarReview'; if($this->objCheckOutReviewModule instanceof CheckOutReviewModule) { $this->objCheckOutReviewModule->RefreshView($this->objOrder); $this->objCheckOutReviewModule->SetParentControl($this->pnlCurrentPanel); } else { $this->pnlCurrentPanel->RemoveChildControls(true); $this->objCheckOutReviewModule = new CheckOutReviewModule($this->pnlCurrentPanel, $this, $this->objOrder); } //the review panel draws its own order totals $this->objOrderTotalsView->Visible = false; //We have to do a server post back to allow the header(Location:) redirect for PayPal to work // in all cases, else the browser just gets it in XML and does nothing (some complain in fact) .. $this->btnContinue->RemoveAllActions(QClickEvent::EventName); $this->btnContinue->AddAction(new QClickEvent(), new QServerControlAction($this, 'btnContinue_Click')); $this->btnContinue->Text = Quasi::Translate('Submit'); $this->intCurrentStage = CheckOutStage::Review; break; case CheckOutStage::Confirmation: $this->pnlHeading->Text = '   ' . Quasi::Translate('Your order has been received') . '.'; $this->lblProgressBar->CssClass = 'ProgressBarReceipt'; $this->pnlCurrentPanel->RemoveChildControls(true); //submit payment .. $this->SubmitPayment(); $this->btnBack->Visible = false; $this->btnCancel->Visible = false; $this->btnContinue->Text = Quasi::Translate('Continue'); // set up the confirmation panel .. $this->ShowCheckOutConfirmationModule(); $this->intCurrentStage = CheckOutStage::Confirmation; break; case CheckOutStage::ShoppingCart: Quasi::Redirect('http://' . Quasi::$ServerName . __QUASI_SUBDIRECTORY__ . '/index.php/ShoppingCart'); break; case CheckOutStage::Cancel: //this shouldn't happen (since we don't save til the end) but maybe: if($this->objOrder instanceof Order && null != $this->objOrder->Id) $this->objOrder->Delete(); QApplication::Redirect(__QUASI_SUBDIRECTORY__ . '/index.php/Home'); break; default: throw new QCallerException("Unknown Upload Stage passed."); } $this->pnlCurrentPanel->Visible = true; } public function RefreshOrderTotalsView() { $this->objOrderTotalsView->SetTotals($this->objOrder); } /** * The final act - this function recieves the signal to process payment for an Order. This * is where a PaymentAction for the order is created and triggered. * Here we also first save the order and update the order status, order_status_history - initially * to "Pending" and to "Paid" on approval. Note that if a PaymentAction fails the action itself * deletes the order so no further action is taken here in that case. * * NOTE: Payment methods that require redirecting the user away from here (eg. PayPal ) or * methods that do not accept immediate payment (Pay by Mail, Fax, wire transfer etc ..) * must naturally finish the process elsewhere so status will remain "Pending" for these. */ public function SubmitPayment() { ///@todo Fixme - this doesn't work if the user hits back in browser, figure out a check here .. if( $this->blnTransactionInitiated ) throw new QCallerException('Error: attempt to submit payment already initiated.'); if( $this->objOrder->Id ) throw new QCallerException('Error: attempt to submit payment on completed order.'); $this->blnTransactionInitiated = true; $this->objOrder->StatusId = OrderStatusType::Pending; $this->objOrder->Notes = $this->objShippingModule->Notes; //save the order and get an ID (reload is automatic).. $intOrderId = $this->objOrder->Save(); //make an entry into the order_status_history table $objOrderStatusHistory = new OrderStatusHistory(); $objOrderStatusHistory->StatusId = OrderStatusType::Pending; $objOrderStatusHistory->OrderId = $intOrderId; $objOrderStatusHistory->Notes = $this->objShippingModule->Notes; $objOrderStatusHistory->Save(); //save the order items - must be done after the order id is set .. $this->objOrder->SaveNewOrderItems(); //create a payment action object .. $strActionClass = $this->objPaymentModule->SelectedMethod->ActionClassName; if(!class_exists($strActionClass)) throw new QCallerException( 'Missing PaymentMethod Class: ' . $strActionClass ); $objPaymentAction = new $strActionClass($this->objOrder); //retrieve credit card input and initialize payment action if($this->objPaymentModule->SelectedMethod->RequiresCcNumber) { $objPaymentAction->CCNumber = $this->objPaymentModule->txtCCNumber->Text; $objPaymentAction->CCExpirationYear = $this->objPaymentModule->lstCCExpirationYear->SelectedValue; $objPaymentAction->CCExpirationMonth = $this->objPaymentModule->lstCCExpirationMonth->SelectedValue; $objPaymentAction->CCVNumber = $this->objPaymentModule->txtCCVNumber->Text; } $objPaymentAction->TestMode = $this->objPaymentModule->SelectedMethod->TestMode; //Note: at this point PayPal Express will redirect to paypal.com .. $objPaymentAction->PreProcess(); $objPaymentAction->Process(); $objPaymentAction->PostProcess(); //set some data for the confirmation module .. $this->strTransactionMessage = $objPaymentAction->StatusText; $this->strErrors = $objPaymentAction->Errors; $this->blnTransactionApproved = $objPaymentAction->Approved; } /** * This function is called when a purchase has been completed. * Basically it hides all the other panels/modules and shows the payment confirmation message. * Any changes to the confirmation display should be done here. */ public function ShowCheckOutConfirmationModule() { $this->objCheckOutConfirmationModule = new CheckOutConfirmationModule($this->pnlCurrentPanel, $this, $this->objOrder); $strApprovedText = '
' . Quasi::Translate("Thank You for your purchase") . '!
'; $strApprovedText .= '

' . Quasi::Translate('Your Order Number is') . ':     ' . $this->objOrder->Id . '

'; $strApprovedText .= '

' . Quasi::Translate('We will email you shortly with a confirmation of your order') . '.

' . '

' . Quasi::Translate('Please make sure that you have given a correct email address with which to contact you') . '.

'; $strDeclinedText = Quasi::Translate(" We're Sorry - Your payment has been declined - the message below may help to solve the problem:") . '.
'; if( $this->blnTransactionApproved ) $this->objCheckOutConfirmationModule->Message = $strApprovedText; else { //for testing - extra error messages .. // $msg = $strDeclinedText . '

Transaction Errors:
' . $this->strErrors . '

' ; // $this->objCheckOutConfirmationModule->Message = $msg ; $this->objCheckOutConfirmationModule->Message = $strDeclinedText ; } if('' != $this->strTransactionMessage) $this->objCheckOutConfirmationModule->Message .= '

Transaction Messages:
' . $this->strTransactionMessage . '

' ; $this->objCheckOutConfirmationModule->Visible = true; } public function Validate(){return true;} ///Gettors public function __get($strName) { switch ($strName) { case 'Errors': return $this->strErrors ; case 'Order': return $this->objOrder ; case 'ShippingAddress': return $this->objShippingModule->Address; case 'BillingAddress': return $this->objPaymentModule->Address; case 'ShippingMethod': return $this->objShippingModule->SelectedMethod; case 'PaymentMethod': return $this->objPaymentModule->SelectedMethod; case 'ShippingModule': return $this->objShippingModule; case 'TotalOunces': return $this->objOrder->fltTotalOunces; case 'TotalPounds': return $this->objOrder->intTotalPounds; case 'XAxisSize': return $this->objOrder->fltXAxisSize; case 'YAxisSize': return $this->objOrder->fltYAxisSize; case 'ZAxisSize': return $this->objOrder->fltZAxisSize; case 'CartEmpty': return $this->blnCartEmpty; default: try { return parent::__get($strName); } catch (QCallerException $objExc) { $objExc->IncrementOffset(); throw $objExc; } } } ///Settors public function __set($strName, $mixValue) { switch ($strName) { case 'Order': try { return ($this->objOrder = QType::Cast($mixValue, 'Order' )); } catch (QInvalidCastException $objExc) { $objExc->IncrementOffset(); throw $objExc; } default: try { return (parent::__set($strName, $mixValue)); } catch (QCallerException $objExc) { $objExc->IncrementOffset(); throw $objExc; } } } ////////////// temp .. //////////////////////////////////////////////////////////////// /** * This function refreshes the information in the order to match user input *@todo this should update the CheckOutReviewModule. */ /* private function refreshOrderItems() { // $this->lblMessages->Text=''; //rebuild Order items array according to user input (quantities) $this->aryOrderItems = array(); $aryItemViews =& $this->objCheckOutEditModule->ItemListModule->aryCheckOutItemViews; foreach( $aryItemViews as $objView ) { $item = $objView->objOrderItem; //objView->Quantity is from the input field $item->Quantity = $objView->Quantity; $this->aryOrderItems[] = $item; } //adjust shopping cart to match. $aryCartItems =$this->objShoppingCart->GetShoppingCartItemArray(); foreach($aryCartItems as &$objCartItem ) { foreach($this->aryOrderItems as $oi_index => $objOrderItem ) { if($objOrderItem->ProductId != $objCartItem->ProductId) continue; //if the quantity was set to zero or less, remove from both arrays . if($objOrderItem->Quantity <= 0) { $objCartItem->Delete(); unset($this->aryOrderItems[$oi_index]); } else { $objCartItem->Quantity = $objOrderItem->Quantity; $objCartItem->Save(); } } } //refresh the shopping cart's idea of its contents .. $this->objShoppingCart->Reload(); }*/ }//end class }//end define ?>