Here's an approach that I took to put in a better error message for two specific filters: the from date and to date.
Those filters are pretty simple compared to digging into the actual rule conditions such as subtotal, as you've mentioned in your question, but I think still provide a significant usability improvement with a pretty straight forward implementation.
There are two pretty clean rewrites that can be done to accomplish this.
Mage_SalesRule_Model_Resource_Rule_Collection::addWebsiteGroupDateFilter
Overload the addWebsiteGroupDateFilter method to prevent rules that don't match the date filter from being excluded entirely from the rules that are processed.
public function addWebsiteGroupDateFilter($websiteId, $customerGroupId, $now = null)
{
parent::addWebsiteGroupDateFilter($websiteId, $customerGroupId, $now);
$where = $this->_removeDateFilters();
$this->getSelect()->setPart('where', $where);
return $this;
}
protected function _removeDateFilters()
{
$where = $this->getSelect()->getPart('where');
foreach ($where as $index => $whereLine) {
if (strpos($whereLine, "from_date is null or from_date <") !== false) {
unset($where[$index]);
} elseif (strpos($whereLine, "to_date is null or to_date >") !== false) {
unset($where[$index]);
}
}
$where = array_values($where);
return $where;
}
Clean_Checkout_Model_SalesRule_Validator::_canProcessRule
Overload the _canProcessRule method to check the dates and add a specific error message to the session.
protected function _canProcessRule($rule, $address)
{
if ($this->_isRuleExpired($rule, $address)) {
return false;
}
return parent::_canProcessRule($rule, $address);
}
protected function _isRuleExpired($rule, $address)
{
if ($rule->getFromDate() && date('Y-m-d', time()) < $rule->getFromDate()) {
$message = "This coupon won't be active until {$rule->getFromDate()}";
Mage::getSingleton('checkout/session')->addUniqueMessages(new Mage_Core_Model_Message_Error($message));
return true;
}
if ($rule->getToDate() && date('Y-m-d', time()) > $rule->getToDate()) {
$message = "This coupon expired on {$rule->getToDate()}";
Mage::getSingleton('checkout/session')->addUniqueMessages(new Mage_Core_Model_Message_Error($message));
return true;
}
}
You can do this simply with some javascript. The javascript implementation can be done in a few ways. This is but one.
The quickest would be to copy the core template to your theme
/app/design/frontend/base/default/template/checkout/cart/coupon.phtml
and then set the coupon element to display:none with some in-line css (or you can adjust the carts stylesheet if you do not want to use in-line css, which is better)
Then all you have to do is place your 'click here' element to have a click event to toggle the coupon display and hide the click element.
Have not tested this code for 100% correctiveness
<form id="discount-coupon-form" action="<?php echo $this->getUrl('checkout/cart/couponPost') ?>" method="post">
<a class="show-coupon-box" href="#">Have a coupon? Click to enter</a>
<div class="discount" style="display:none">
<h2><?php echo $this->__('Discount Codes') ?></h2>
<div class="discount-form">
<label for="coupon_code"><?php echo $this->__('Enter your coupon code if you have one.') ?></label>
<input type="hidden" name="remove" id="remove-coupone" value="0" />
<div class="input-box">
<input class="input-text" id="coupon_code" name="coupon_code" value="<?php echo $this->escapeHtml($this->getCouponCode()) ?>" />
</div>
<div class="buttons-set">
<button type="button" title="<?php echo $this->__('Apply Coupon') ?>" class="button" onclick="discountForm.submit(false)" value="<?php echo $this->__('Apply Coupon') ?>"><span><span><?php echo $this->__('Apply Coupon') ?></span></span></button>
<?php if(strlen($this->getCouponCode())): ?>
<button type="button" title="<?php echo $this->__('Cancel Coupon') ?>" class="button" onclick="discountForm.submit(true)" value="<?php echo $this->__('Cancel Coupon') ?>"><span><span><?php echo $this->__('Cancel Coupon') ?></span></span></button>
<?php endif;?>
</div>
</div>
</div>
</form>
and some javascript to deal with the click
$$('.show-coupon-box')[0].observe('click', function(e){
$$('.discount')[0].toggle(); $$('.show-coupon-box')[0].toggle();
e.stop();
});
Best Answer
The only way to solve this problem is to either require users to create a customer account prior to using this coupon or implement custom logic to validate against billing and/or shipping information to try and accomplish the same thing. Anything else would be unreliably flakey. Checking for orders placed with the same info is already pretty flaky, complicated, but doable.
My recommendation is simply not to do this and require users to login. If you have a coupon and give the customer a message telling them they need to login or create an account to use it, they will do that to save a few bucks.