I have got a problem with Magento single coupon code that is marked as having been used at the time the customer clicks on the Place Order button. If the Paypal payment fails or the client leaves the page before the order is complete, he won't able to go back and re-order with this coupon which is set to be only used once, and has been marked already been used.
I have found a piece of code that decreases the number of times the coupons has been used by the user and allows him to reuse the coupon. Unfortunately, he gets an error when trying to connect the Paypal page when clicking the place order button. In order to be able to use the coupon another time and access the Paypal page, I have to delete the lines in SQL database in tables salesrule_coupon_usage and salesrule_customer with this customer's ID.
Here is the code I need to change to automatically delete coupon usage information for a customer ID:
public function cancel($observer)
{
$order = $observer->getEvent()->getPayment()->getOrder();
if ($order->canCancel()) {
if ($code = $order->getCouponCode()) {
$coupon = Mage::getModel('salesrule/coupon')->load($code, 'code');
if ($coupon->getTimesUsed() > 0) {
$coupon->setTimesUsed($coupon->getTimesUsed() - 1);
$coupon->save();
}
$rule = Mage::getModel('salesrule/rule')->load($coupon->getRuleId());
error_log("\nrule times used=" . $rule->getTimesUsed(), 3, "var/log/debug.log");
if ($rule->getTimesUsed() > 0) {
$rule->setTimesUsed($rule->getTimesUsed()-1);
$rule->save();
}
if ($customerId = $order->getCustomerId()) {
if ($customerCoupon = Mage::getModel('salesrule/rule_customer')->loadByCustomerRule($customerId, $rule->getId())) {
$couponUsage = new Varien_Object();
Mage::getResourceModel('salesrule/coupon_usage')->loadByCustomerCoupon($couponUsage, $customerId, $coupon->getId());
if ($couponUsage->getTimesUsed() > 0) {
/* I can't find any #@$!@$ interface to do anything but increment a coupon_usage record */
$resource = Mage::getSingleton('core/resource');
$writeConnection = $resource->getConnection('core_write');
$tableName = $resource->getTableName('salesrule_coupon_usage');
$query = "UPDATE {$tableName} SET times_used = times_used-1 "
. "WHERE coupon_id = {$coupon->getId()} AND customer_id = {$customerId} AND times_used > 0";
$writeConnection->query($query);
}
if ($customerCoupon->getTimesUsed() > 0) {
$customerCoupon->setTimesUsed($customerCoupon->getTimesUsed()-1);
$customerCoupon->save();
}
}
}
}
}
}
I believe this was an old bug from around 1.4 to maybe 1.6. But whether you've got an old version or not, this can fairly easily be fixed in place if you know your way around Magento.
The problem is, you have code that is updating the salesrule_coupon_usage table right when they click the pay button. This isn't really what you want at all. You want this to be wrapped into the payment transaction. I don't know if this bug is happening because you have custom code or are using a older version of Magento, but I'll tell you how I would fix the problem. Then I'll give you a fix similar to what you proposed:
Magento already has an abstraction called a "transaction". A transaction is used to put and group of objects together that need to either all succeed or all fail, no half-measures. Magento will go through the transaction and attempt to save each of the objects you have placed in it. If any of them fail (for example, payment doesn't go through), all the events that have been already saved are "rolled back".
Luckily, Magento already creates a transaction object to handle payment for the various things that need to update together on successful payment. You can tap into that transaction and use it to update the coupon usage correctly.
Here is the 10,000 foot view of what you need to do.
And your done. The transaction will automatically try to save all of the objects that have been added to it. If any part fails (including a failed payment), it'll rollback the whole process and your coupon won't be used. Yay!
Now, in my opinion, the above is the best way to handle the issue, but if you are having issues for some reason, here is an alternative based on catching an unsuccessful payment and then using your code.
Again, here is the 10,000 foot view:
Now when a sale fails, you go back through and unwind everything manually. This isn't as clean as my first solution, but might be easier for you depending on your familiarity with Magento.
I'm fairly sure that new, clean versions of Magento don't have this issue, so let me offer a fairly obvious suggestion as a third solution.
Good luck!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With