/** * Called by AJAX from checkout for Calculate Shipping. Builds a grid of * shipping scenarios including shipping price and cart total prices. The * results are cached client-side in the browser allowing the user to click * through and see updated cart totals without initiating another AJAX * request. * @return string JSON encoded shipping options. */ public function actionAjaxCalculateShipping() { if (isset($_POST['CheckoutForm']) === false) { return $this->renderJSON(array('result' => 'error', 'errormsg' => 'Must POST a CheckoutForm')); } Yii::log("Performing an AJAX Shipping Calculation Request", 'info', 'application.' . __CLASS__ . "." . __FUNCTION__); // We run the items through the model for verification. $model = new CheckoutForm(); $model->attributes = $_POST['CheckoutForm']; if (Yii::app()->params['SHIP_SAME_BILLSHIP']) { $model->billingSameAsShipping = 1; } $model->scenario = 'CalculateShipping'; // Copy address book to field if necessary. $model->fillFieldsFromPreselect(); // Set up the exception handle for the remainder of this function. $handleException = function (Exception $e) { Yii::log($e->getMessage(), 'error', 'application.' . __CLASS__ . "." . __FUNCTION__); return $this->renderJSON(array('result' => 'error', 'errormsg' => $e->getMessage())); }; try { self::validateCheckoutForm($model); } catch (Exception $e) { return $handleException($e); } Yii::log("Successfully validated shipping request", 'info', 'application.' . __CLASS__ . "." . __FUNCTION__); // Clone the model because we're going to make some changes we may // not want to retain. // TODO: Accommodate spaces in the postal code in the shipping modules themselves. $checkoutForm = clone $model; $checkoutForm->shippingPostal = str_replace(" ", "", $checkoutForm->shippingPostal); $hasPromoCodeBeforeUpdatingTaxCode = Yii::app()->shoppingcart->hasPromoCode; try { $arrCartScenario = Shipping::getCartScenarios($checkoutForm); } catch (Exception $e) { return $handleException($e); } // A promo code can be removed as a result of getting updated // cart scenarios if the tax code has changed and the promo // code theshold is no longer met. // TODO: Do not modify the shoppingcart tax code in getCartScenarios, // it is surprising. See a similar TODO in Shipping::getCartScenarios. $hasPromoCodeBeforeAfterUpdatingTaxCode = Yii::app()->shoppingcart->hasPromoCode; $wasPromoCodeRemoved = false; if ($hasPromoCodeBeforeUpdatingTaxCode === true && $hasPromoCodeBeforeAfterUpdatingTaxCode === false) { $wasPromoCodeRemoved = true; } // Sort the shipping options based on sort order, then on price. usort($arrCartScenario, function ($item1, $item2) { if ($item1['sortOrder'] === $item2['sortOrder'] && $item1['shippingPrice'] === $item2['shippingPrice']) { return 0; } if ($item1['sortOrder'] === $item2['sortOrder']) { return $item1['shippingPrice'] > $item2['shippingPrice'] ? 1 : -1; } return $item1['sortOrder'] > $item2['sortOrder'] ? 1 : -1; }); $arrFormattedCartScenario = $this->formatCartScenarios($arrCartScenario); // Store the results in our session so we don't have to recalculate whatever they picked. // TODO: Much of this may be unnecessary with the refactoring completed // as part of advanced checkout. Yii::log("Populating caches", 'info', 'application.' . __CLASS__ . "." . __FUNCTION__); Yii::app()->session['ship.htmlCartItems.cache'] = $arrFormattedCartScenario['renderedCartItems']; Yii::app()->session['ship.prices.cache'] = $arrFormattedCartScenario['shippingOptionPrices']; Yii::app()->session['ship.formattedPrices.cache'] = $arrFormattedCartScenario['formattedShippingOptionPrices']; Yii::app()->session['ship.priorityRadio.cache'] = $arrFormattedCartScenario['renderedPriorityRadioButtonList']; Yii::app()->session['ship.providerLabels.cache'] = $arrFormattedCartScenario['shippingProviderLabels']; Yii::app()->session['ship.priorityLabels.cache'] = $arrFormattedCartScenario['shippingPriorityLabels']; Yii::app()->session['ship.providerRadio.cache'] = $arrFormattedCartScenario['renderedProviderRadioButtonList']; Yii::app()->session['ship.formattedCartTotals.cache'] = $arrFormattedCartScenario['formattedCartTotals']; Yii::app()->session['ship.taxes.cache'] = $arrFormattedCartScenario['renderedCartTaxes']; $errormsg = null; if (Yii::app()->user->hasFlash('error')) { // The error message may contain HTML. Since the JavaScript // displays it in an alert, we don't want that. $errormsg = strip_tags(Yii::app()->user->getFlash('error')); } $arrReturn = array('cartitems' => $arrFormattedCartScenario['renderedCartItems'], 'paymentmodules' => $model->getPaymentMethodsAjax(), 'prices' => $arrFormattedCartScenario['formattedShippingOptionPrices'], 'priority' => $arrFormattedCartScenario['renderedPriorityRadioButtonList'], 'errormsg' => $errormsg, 'provider' => $arrFormattedCartScenario['renderedProviderRadioButtonList'], 'result' => 'success', 'taxes' => $arrFormattedCartScenario['renderedCartTaxes'], 'totals' => $arrFormattedCartScenario['formattedCartTotals'], 'wasPromoCodeRemoved' => $wasPromoCodeRemoved); Yii::log("Returning JSON encoded shipping", 'info', 'application.' . __CLASS__ . "." . __FUNCTION__); return $this->renderJSON($arrReturn); }