I'm going to explain how I handled an ajax scenario in Magento and let's see if you can use this to help yourself!
I needed to update an area of the page via Ajax when the user presses a button that looks like this:
<button type="button" title="<?php echo $buttonTitle ?>" id="buttonAddToCart" class="button btn-cart" onclick="productAddToCartForm.submit(this)"><?php echo $buttonTitle ?></button>
So, when the user presses this button it passes the contents of the form to a function called productAddToCartForm.submit(). This is the function that will initiate the ajax request. Within this function a number of things need to be specified. First, you need a URL pointing towards a controller. This is the 'link' in the ajax 'chain' connecting the user to the server. So let's take a look at that function.
<script type="text/javascript">
//<![CDATA[
var productAddToCartForm = new VarienForm('product_addtocart_form');
productAddToCartForm.submit = function (button, url) {
if (this.validator.validate()) {
var form = this.form;
var oldUrl = form.action;
if (url) {
form.action = url;
}
var e = null;
if (!url) {
url = jQuery('#product_addtocart_form').attr('action');
}
if (url.search('wishlist') !== -1) {
this.form.submit();
} else {
/**
* add to cart form data is sent to indexcontroller via AJAX. TopCartContent is updated and displayed to the user.
**/
url = url.replace("checkout/cart", "checkout/index");
var data = jQuery('#product_addtocart_form').serialize();
data += '&isAjax=1';
jQuery('#ajax_loader').show();
jQuery('#buttonAddToCart').prop("disabled", true);
try {
jQuery.ajax({
url: url,
dataType: 'json',
type: 'post',
data: data,
success: function (data) {
jQuery('#img-thumb').attr('src', data.image);
jQuery('#prod-name').html(data.name);
jQuery('#prod-price').html(data.price);
jQuery('#header-cart-checkout').html(data.topCart);
Enterprise.TopCart.initialize('topCartContent');
Enterprise.TopCart.showCart(8);
jQuery('#ajax_loader').hide();
jQuery('#buttonAddToCart').prop('disabled', false);
}
});
} catch (e) {
}
this.form.action = oldUrl;
if (e) {
throw e;
}
}
}
}.bind(productAddToCartForm);
This instantiates the forms information and validates it's contents. The meat-and-potatoes of this function is wedged inbetween the 'try' section. It issues a jQuery.Ajax request to the URL, it expects a 'json' format response. It uses a post method to query the database, and is sending data to our controller.
All the things after the success:function(data) area is upon a successful response from our controller. So, before I explain that to you -- I'll show you a section of the code from my controller that will hopefully clear things up for you. It is here where the data is sent and new data is fetched from the server and given to the user, remember you must add a URL that is directed at this controller. In the above example, I overrode the core checkout/cartcontroller and replaced it with a custom checkout/indexcontroller.
class Companyspace_Checkout_IndexController extends Mage_Checkout_CartController{
$product = $this->_initProduct();
if ($params['isAjax'] == 1) {
$response = array();
try {
$message = $this->__('%s was added to your shopping cart.', Mage::helper('core')->escapeHtml($product->getName()));
$response['status'] = 'SUCCESS';
$response['message'] = $message;
$this->loadLayout();
$topCart = $this->getLayout()->getBlock('topCart')->toHtml();
$response['topCart'] = $topCart;
$response['image'] = (string)Mage::helper('catalog/image')->init($product, 'thumbnail');
$response['name'] = $product->getname();
$response['price'] = Mage::helper('core')->formatPrice($product->getPrice(), true);
}
$this->getResponse()->setBody(Mage::helper('core')->jsonEncode($response));
return;
}
This recieves the request from the previous function and prepares a response array. This response array is populated by a number of things, including a re-initialized $topCart section (this is what I mainly use to refresh a certain section of my page). It then encodes this using jsonEncode and sends it back to the user.
Then, this function uses JQuery to populate the elements with new information!
I hope this helps!
In your custom module, add the following to config.xml
:
<models>
<salesrule>
<rewrite>
<quote_discount>Namespace_Module_Rewrite_SalesRule_Model_Quote_Discount</quote_discount>
</rewrite>
</salesrule>
</models>
<frontend>
<routers>
<checkout>
<args>
<modules>
<Namespace_Module before="Mage_Checkout">Namespace_Module_Checkout</Namespace_Module>
</modules>
</args>
</checkout>
</routers>
</frontend>
The first is a rewrite of Mage_SalesRule_Model_Quote_Discount
to Namespace_Module_Rewrite_SalesRule_Model_Quote_Discount
The second is the overloaded controller Mage_Checkout_CartController
Next add the following file app/code/community/Namespace/Module/controllers/Checkout/CartController.php
and insert the following code:
<?php
require_once 'Mage/Checkout/controllers/CartController.php';
class Namespace_Module_Checkout_CartController extends Mage_Checkout_CartController
{
/**
* Initialize coupon
*/
public function couponPostAction()
{
/**
* No reason continue with empty shopping cart
*/
if (!$this->_getCart()->getQuote()->getItemsCount()) {
$this->_goBack();
return;
}
$couponCode = (string) $this->getRequest()->getParam('coupon_code');
if ($this->getRequest()->getParam('remove') == 1) {
$couponCode = '';
}
$oldCouponCode = $this->_getQuote()->getCouponCode();
if (!strlen($couponCode) && !strlen($oldCouponCode)) {
$this->_goBack();
return;
}
try {
$codeLength = strlen($couponCode);
$isCodeLengthValid = $codeLength && $codeLength <= Mage_Checkout_Helper_Cart::COUPON_CODE_MAX_LENGTH;
// Combine multiple coupons
$couponFlag = true;
if ($isCodeLengthValid) {
$del = ',';
if ($oldCouponCode) {
if ($oldCouponCode == $couponCode) {
$couponCode = $oldCouponCode;
} else {
$couponCode = $oldCouponCode . $del . $couponCode;
}
}
} else {
$couponCode = '';
}
$this->_getQuote()->getShippingAddress()->setCollectShippingRates(true);
$this->_getQuote()->setCouponCode($couponCode)
->collectTotals()
->save();
if ($codeLength) {
if ($isCodeLengthValid && $couponFlag) {
$this->_getSession()->addSuccess(
$this->__('Coupon code "%s" was applied.', Mage::helper('core')->escapeHtml($couponCode))
);
} else {
$this->_getSession()->addError(
$this->__('Coupon code "%s" is not valid.', Mage::helper('core')->escapeHtml($couponCode))
);
}
} else {
$this->_getSession()->addSuccess($this->__('Coupon code was canceled.'));
}
} catch (Mage_Core_Exception $e) {
$this->_getSession()->addError($e->getMessage());
} catch (Exception $e) {
$this->_getSession()->addError($this->__('Cannot apply the coupon code.'));
Mage::logException($e);
}
$this->_goBack();
}
}
You will notice i added a section to combine coupon codes delimited by ",". This can obviously be more refined and you may want to add additional checking etc, but this code should work straight off the bat.
And finally we need to add the piece that does all the magic. Add the file app/code/community/Namespace/Module/Rewrite/SalesRule/Model/Quote/Discount.php
and add the content:
<?php
class Namespace_Module_Rewrite_SalesRule_Model_Quote_Discount extends Mage_SalesRule_Model_Quote_Discount
{
/**
* Collect address discount amount
*
* @param Mage_Sales_Model_Quote_Address $address
* @return Mage_SalesRule_Model_Quote_Discount
*/
public function collect(Mage_Sales_Model_Quote_Address $address)
{
Mage_Sales_Model_Quote_Address_Total_Abstract::collect($address);
$quote = $address->getQuote();
$store = Mage::app()->getStore($quote->getStoreId());
$this->_calculator->reset($address);
$items = $this->_getAddressItems($address);
if (!count($items)) {
return $this;
}
$couponCode = $quote->getCouponCode();
$couponArray = explode(',',$couponCode);
foreach ($couponArray as $couponCode) {
$this->_calculator->init($store->getWebsiteId(), $quote->getCustomerGroupId(), $couponCode);
$this->_calculator->initTotals($items, $address);
$eventArgs = array(
'website_id' => $store->getWebsiteId(),
'customer_group_id' => $quote->getCustomerGroupId(),
'coupon_code' => $couponCode,
);
$address->setDiscountDescription(array());
$items = $this->_calculator->sortItemsByPriority($items);
foreach ($items as $item) {
if ($item->getNoDiscount()) {
$item->setDiscountAmount(0);
$item->setBaseDiscountAmount(0);
}
else {
/**
* Child item discount we calculate for parent
*/
if ($item->getParentItemId()) {
continue;
}
$eventArgs['item'] = $item;
Mage::dispatchEvent('sales_quote_address_discount_item', $eventArgs);
if ($item->getHasChildren() && $item->isChildrenCalculated()) {
foreach ($item->getChildren() as $child) {
$this->_calculator->process($child);
$eventArgs['item'] = $child;
Mage::dispatchEvent('sales_quote_address_discount_item', $eventArgs);
$this->_aggregateItemDiscount($child);
}
} else {
$this->_calculator->process($item);
$this->_aggregateItemDiscount($item);
}
}
}
/**
* process weee amount
*/
if (Mage::helper('weee')->isEnabled() && Mage::helper('weee')->isDiscounted($store)) {
$this->_calculator->processWeeeAmount($address, $items);
}
/**
* Process shipping amount discount
*/
$address->setShippingDiscountAmount(0);
$address->setBaseShippingDiscountAmount(0);
if ($address->getShippingAmount()) {
$this->_calculator->processShippingAmount($address);
$this->_addAmount(-$address->getShippingDiscountAmount());
$this->_addBaseAmount(-$address->getBaseShippingDiscountAmount());
}
$this->_calculator->prepareDescription($address);
}
return $this;
}
}
Basically, what this does is breaks the coupon sting up, loops through each coupon code, calculates and updates the quote totals.
To test, i have setup 2 shopping cart rules:
- test 1 - 10 % product price discount - Stop Further Rules Processing: No
- test 2 - 10 % product price discount - Stop Further Rules Processing: No
No coupon:
Added coupon test 1:
Added coupon test 2
I have tested with fixed amount discount and this works as expected as well.
And like i said, you may need to add additional checking, possibly for duplicates, but this is where you would start. For the frontend, you could add some logic split the codes however you prefer or leave as is.
Best Answer
There are lot issue in your code.
JavaScript
is wrong.
you have callopcheckout.js
functionpayment.payment.onSave
and checkout.ajaxFailure.bind(checkout
), etc whichdoes not work on all pages
.So you need to
Custom code
which will apply coupon code on ajax on all pagesYou can follow the link for full your requirement:
Custom AJAX discount-form not processing coupons
lelandcope