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.
Find whatever is updating the salesrule_coupon_usage table too early and kill it. We are going to add in our own transaction-safe version, so we don't want it being saved anywhere else. Easiest way to do this is to figure out what model connects to that table and search for the creation of that model. For 1.7 and 1.8, that is the rule/customer model.
Create an observer to catch the start of a payment transaction. In most modern versions of magento this event is called sales_order_payment_place_start and can be witnessed in app/code/core/Mage/Sales/Model/Order/Payment.php
Pull the order out of the event and pull the coupon code out of the event.
Pull the actual model you want to update. It looks like someone couldn't find it in your code, but there should be some model that uses the salesrule_coupon_usage table hiding somewhere. Search though the .xml files for "salesrule_coupon_usage" and see which model using that table. Again, for me, on 1.7, that is the rule/customer model.
Load that model up, with the customer change the value where your coupon code is concerned to indicate that the customer has used the coupon, but don't save yet.
Get the transaction out of the event and register your updated coupon object with the addObject method.
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:
Create an observer to catch the failed payment event. In most versions the event you want is sales_order_payment_cancel.
Run the code you've got... it should do it. But to clarify for others:
Pull the order out of the event, pull the coupon code and customer id out of that.
Update the customer, rule and salesrule_coupon_usage table. (Although there really should be a model for that, I'm sure you can find it)
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.
Update Magento
If Magento is up to date, test disabling any custom modules, because something broke it. I've noticed that Amasty coupon plugins are particularly buggy.
If you've made custom changes to Magento core... good luck with that.
Good luck!
Related
Current Issue:
My checkout calculator refreshes shipping rates way too often, currently it update/refreshes shipping rates when I change a payment method, or I choose a different shipping method as well as when I change an address field or modify a quantity in cart & it even does one upon cart opening in browser. (I understand some merchants may need these to update due to fees associated with certain payment gateways or shipping methods, but to me why do I need to pull live shipping rates again when all I did was click on local pickup, or something that shouldn't have any affect at all on shipping carrier rates)
What I'm trying to accomplish:
I would like the checkout/cart calculator to stop auto update/refresh shipping rates all together until the end of checkout where it should place 1 after I've entered all attributing factors into appropriate fields necessary to make the call, at least IMO.
I have read through hundreds of posts but all I'm turning up is ways to ensure checkout calculator refreshes on certain Ajax calls I need it to be disabled for any factor. I'm still pretty new here so I apologize if I'm breaking some protocols please forgive, and any help will be greatly appreciated.
Update:
After thinking on this some more I feel a perfect solution (if possible) would be to place a button in the checkout calc shipping area, or down by the place order button that would allow me or a customer to manually pull the shipping rate API and update totals when necessary and do away with the entire automatic process(this would also benefit me in that woocommerce would stop trying to make a new call after every keystroke), but this would also necessitate the need for a function or something the like to keep an order from being placed unless this manual button had been pressed immediately prior to place order button (and they would need to press it again upon making any address field changes, or changing a shipping method or any other factor that would affect the order totals, exclude things like name, phone number, Company Name, or any other field you can think of that wouldn't affect order total at all).
8 hours later -
I'm still banging my head at this no real results. The only thing I've accomplished is hiding the shipping calc in the cart with this
function disable_shipping_calc_on_cart( $show_shipping ) {
if( is_cart() ) {
return false;
}
return $show_shipping;
}
add_filter( 'woocommerce_cart_ready_to_calc_shipping', 'disable_shipping_calc_on_cart', 99 );
But it doesn't actually stop the API call just hides it. Although I think some time may be saved not having to generate the UI for the field, it's a negligible amount. If only cart would just display order total/tax breakdown like it does in desktop drop down or mobile sidebar that would speed my cart experience up a whole lot. Either way I prefer it not to be in the cart.
Depressing, if only the whole automated update waited for a button press at the end I would save so much time. As well as my customers. I mean doesn't this make sense. Let me describe my workflow through rounds of testing. Eight payment gateways custom configured with rules granting visibility need to all be tested through every variable & here is how it apparently must be done to test the front end(At least in my case, I'm very new to this & I never even considered building a website until I was approached 3-4 months ago about it). I'll shorten the entire experience by going right to the part where I'm entering the cart with 1 of my many orders.
cart opens in browser... Shipping API call 10-15 sec for callback, Update some order quantities if needed... Shipping API call 10-15 sec,(as stated above there is no shipping calc visible here. So thank god I can not be tripped up by another... Shipping API call 10-15 sec while still in the cart), Proceed to checkout, Account credentials auto populate fields... Shipping API call 10-15 sec, I may need to test shipping across a larger distance or commercial/residential rates so change address fields.... shipping API call 10-15 sec, Select Shipping Method... Shipping API call 10-15 sec, Select payment method & ... yep Shipping API call 10-15 sec..... & finally I can place the order through the front end. :( sound exhausting to you? wouldn't it make sense to bypass all those Shipping API calls and just done 1 at the very end of checkout? Hmm that's like 2 min of potentially lost time in the checkout experience. That call ould have been resolved in 1, 10-15 sec manual button press to update.
IDK something like 10 hours in now... I had a little more progress made when I removed update_totals_on_change from all checkout fields but ZIP code. I left ZIP enabled because I was afraid this method might allow you to run through the rest of the order process. Then come back to shipping fields change address and not trigger an update_total and have the order process. At this point I'm not sure if this was necessary or not but I thought if any factor truly affected the order total it would be the zip code & my shipping API will not let an order process if all the other address fields don't correlate with the zip code. So I though better safe. So that will save a bundle of time for those fields(although those fields don't lockout upon API call the way shipping and payment radios do, but at least it saves u waiting on 1 after the very last keystroke. Unless that last keystroke is in the ZIP field, hmm I may need to relocate the field farther up the form to allow it to get started earlier in the process. Whoops long tangent but thinking aloud...) hmmm if only the automated process allowed you to toggle these radios during its call you could potential complete the order in one sitting and allow the process to do its thing the whole time finally resolving itself at the end, seems a little inefficient overhead wise, but is sounding like the most rational idea I've thought of yet...Yeah, unlock all fields/radios, whatever.... while the API is processing... what I'm getting at is a way to get at the greyed out areas that have no way to be interacted with during the update/refresh... Any ideas?
Next day-
After passing out in my labor I didn't have time to test what removing update_totals_on_change from shipping fields did. Unfortunately I discovered this morning that this accomplished nothing. All address fields still auto update on change, disappointing. I'm considering adding better usability plugin to allow for quantity changes at checkout. This would at least eliminate any need to go to the cart page. So that could cut out a step or two.
Another Update:
So I found this bit of code in wp-content/plugins/woocommerce/templates/checkout/payment.php
<?php esc_html_e( 'Since your browser does not support JavaScript, or it is disabled, please ensure you click the <em>Update Totals</em> button before placing your order. You may be charged more than the amount stated above if you fail to do so.', 'woocommerce' ); ?>
<br/><button type="submit" class="button alt" name="woocommerce_checkout_update_totals" value="<?php esc_attr_e( 'Update totals', 'woocommerce' ); ?>"><?php esc_html_e( 'Update totals', 'woocommerce' ); ?></button>
</noscript>
So there seems to be something that will propogate said button if your browser does not support java script. However, I would not want to disable javascript for my whole site, but maybe I can disable it on checkout only and it won't affect much other than generating the above button at checkout. Looking into this, again any advice would be greatly appreciated.
New Info-
So after turning this over again and again. I discovered that by disabling jquery-blockui script It will then allow me to change any field in checkout without locking up during call. However when disabled there is no call being placed upon field changes, so I need to find a way to initiate the call manually and then lock it up somehow so customer must update immediately prior to placing order.
Not a complete answer to your issue but I am struggling with EXACTLY the same problem.
Woocommerce shipping modifications required to support HTTP API shipping calcs
Though I have managed to stop it calling out and calculating the shipping on cart item addition/modification by adding the following to calculate_Shipping method of my custom shipping plugin.
if ('https://aaa.bbb/checkout/' !== $_SERVER['HTTP_REFERER']) {
return;
}
I am starting to think I need to do the same as you were working on by adding a calculate shipping button that must be pressed before ordering. It must also be run again after any address update before checkout was allowed to be completed.
Did you solve your problem in the last months? If so would appreciate your solution!?!
Ok so I have customized the Mage_Checkout_OnepageController
I need to be able to connect to a DAL server and save certain things there. I got everything correct. It's RESTful, so I am sending a POST request. It's works all just fine. But I need to send one more thing aside from quoteData, item details etc. that one more thing is the magento orderId.
I understand that Magento hasn't created the order Id on Mage_Checkout_OnepageController yet, it generates it on Mage_Checkout_Block_Onepage_Success. I have my own X_Request_Block_Checkout_Success. Within this file the following code;
$order = Mage::getModel('sales/order')->loadByIncrementId(Mage::getSingleton('checkout/session')->getLastRealOrderId());
$orderInfo['orderId'] = $order->getIncrementId();
But in this Success.php, this time I can't get the data I get in Mage_Checkout_OnepageController, namely the followings; (any quoteData, shipping address etc)
$jobSiteAddress = Mage::getSingleton('checkout/session')->getQuote()->getShippingAddress()->toArray();
$quote = Mage::getSingleton('checkout/session')->getQuote();
$cartItems = $quote->getAllVisibleItems();
and some other things, I hope you get the idea.
Big Question
So my question; Is there a way to get magento orderId and all cart data either in Mage_Checkout_Block_Onepage_Success or in my Mage_Checkout_OnepageController
I tried
In My OnePageController I have;
$this->getOnepage()->getQuote()->save();
I tried getting the magento order id after this line but no luck
do let me know If I didn't explain clearly
SO, to be clear, you need to have an orderId before magento really generates it ?
Can you explain more why it's needed ? Why don't you do this call after the order is really generated (magento sales/order events can clearly do this)
Anyways, it seems hard to me to get the next orderId (you can't be sure of the next increment_id if 10 customers orders simultaneously, and i don't think you want to create an order if the user haven't pay already) but you should be able to determine the next OrderIncrementId as it's linked to customer history...
We are in the process of creating a specialty cart where our when a client makes a purchase it logs the Affiliate id in the database at time of purchase. I have created the following code to get the affiliate id/name using the Post Affiliate Pro API.
include ("affiliate/api/PapApi.class.php");
$session = new Gpf_Api_Session("https://www.elitespecialtymeats.com/affiliate/scripts/server.php");
if(!$session->login("user", "password")){ die("Cannot login. Message: ".$session->getMessage()); }
$clickTracker = new Pap_Api_ClickTracker($session);
try {
$clickTracker->track();
$clickTracker->saveCookies();
if ($clickTracker->getAffiliate() != null){ $affid=$clickTracker->getAffiliate()->getValue('username'); }
else{ $affid=''; }
}catch (Exception $e){
$affid='';
}
I am trying to figure out the best way of adding that id into the purchase. I think it may be best to add it in tpl_checkout_success_default.php where they log the purchase into PAP. My issue is that I dont know enough about the system to implement the code.
My best guess is
$db->Execute("UPDATE ".TABLE_ORDERS_TOTAL." SET affid='$affid' where orders_id = '".(int)$orders->fields['orders_id']."' AND class in ('ot_coupon', 'ot_gv', 'ot_subtotal', 'ot_group_pricing', 'ot_quantity_discount')");
Will this work or will i mess something up?
First of all, using the API you are loading affiliate username ( ...getValue('username') ), not affiliate ID as you stated in the first paragraph. You should use getValue('userid'); (for affiliate ID) or getValue('refid'); (for referral ID).
The database command you are trying to use will only work in case the the table TABLE_ORDERS_TOTAL has a column called 'affid'. You can add it manually to the table...
I am wondering why are you doing something like this... It seems like you want to use the ID for some special purpose later. Do you want to give a commission to the original referring person even the customer removed all the cookies or clicked another affiliate's banner meanwhile? If so, you should use Lifetime commissions instead:
http://support.qualityunit.com/541587-Lifetime-commissions
In case you need any other help, feel free to contact our online support.
You might want to look at the way JROX does it - they are an affiliate marketing system that is popular with Zen Cart. It's described here: http://jam.jrox.com/docs/index.php?article=111
Basically they just piggyback onto the checkout success page and stick their logic in the footer.
I'm trying to find a way to allow an order to be split for multiple shipping addresses, but to keep the charge to the customer as a one time payment rather than having to charge for each shipment separately. Is there a way to do this? Currently, customers are getting charged multiple times, corresponding to their "multiple orders", as magento splits the orders up into many smaller ones. This is a problem from the credit card company side since they are getting suspicious of so many consecutive charges to the card.
Any ideas?
Well, I'm not going to be able to provide a full solution because to be honest, the solution would take quite a bit of time and testing but I'll give out comprehensive information that could you to the right direction instead.
Basically, upfront -- there isn't an option to do this in default Magento.
So instead, we are going to rewrite a certain model called Mage_Checkout_Model_Type_Multishipping. If you're into the proper way of rewriting a model, please read this post by Inchoo.
But if you're lazy and looking into a quick way of rewriting, then please copy the whole file app/code/core/Mage/Checkout/Model/Type/Multishipping.php to app/code/local/Mage/Checkout/Model/Type/Multishipping.php
Once you've finished either of the two above, you can move on to rewriting stuff we need into this class. Check out the function around line 498:
public function createOrders()
{
$orderIds = array();
$this->_validate();
$shippingAddresses = $this->getQuote()->getAllShippingAddresses();
$orders = array();
if ($this->getQuote()->hasVirtualItems()) {
$shippingAddresses[] = $this->getQuote()->getBillingAddress();
}
try {
foreach ($shippingAddresses as $address) {
$order = $this->_prepareOrder($address);
$orders[] = $order;
....
}
foreach ($orders as $order) {
$order->place();
$order->save();
...
}
....
Mage::dispatchEvent('checkout_submit_all_after', array('orders' => $orders, 'quote' => $this->getQuote()));
return $this;
} catch (Exception $e) {
Mage::dispatchEvent('checkout_multishipping_refund_all', array('orders' => $orders));
throw $e;
}
}
So we can clearly see here that it's trying to create an order for each address and then (the most important part), triggering the checkout_submit_all_after event which is monitored by the payment modules (order and quote information are passed to all of the available/active payment modules and the payment method set inside the quote will determine if a certain payment module receiving the dispatched event can process it or not).
With that in mind, you can have a couple of options now:
1.) if you can live with just one order, then try and rewrite the createOrders routine to just combine all items again into one order, ->place() it, then pass into dispatchEvent for good measure.
2.) if you need multiple orders, well -- this is where it becomes convoluted as there's a slim chance that you can do either of the following:
create a payment first then link it into the orders (almost impossible to do)
create the orders and another 'combined' order in which you will pass into the dispatchEvent. sounds filthy and/or wrong so don't bother maybe?
So yeah, you guys are pretty much asking for a lot here so the best I can do is to shed some light on where you can first do serious damage. I'd do it myself, but it's above my pay-grade here.
Some additional pointers:
Watch out for the session methods right after order creation, know where they lead too
Also be wary of all the dispatched events. They all have a purpose why they are there and where they are.
Good luck!
Make sure this option is enabled:
If I'm not mistaken, when customer clicks one page checkout button, ship to multiple adresses option wouldn't be shown to him. But if he clicks on main checkout button it would.
I'm completely new on working with Magento and I'm going to create a module in order to validate and in some cases manipulate some cart/order information.
To be specific I'm gonna restrict the customer from buying an item more than X times.
I've started working on this a bit, but I'm not so satisfied with the solution.
This is how I've done it so far:
I've created a new module with a controller which subclasses Mage_Checkout_CartController and there I've implemented the addAction-method. So every time a product is added to the cart I search through the user's order history and look for previous orders containing this product. Then I prevent it from being added and trigger an error-message.
It has a lot of shortcomings. For instance, if the customer isn't logged in at the time he can add the product, you can update the cart with too many... etc.
I would be me comfortable if I could hook on events, but I don't know where to start. Haven't found so good guides about this.
I want to do this verification when listing cart, updating cart and before submitting order.
So, my questions are:
How do I add observers on these events in my module? I couldn't get config.xml-configuration for event observing to work. I also need to know the names of these events.
How do I manipulate the quantity of an item in cart / delete it? When updating cart with too many of the products I want to change the quantity and trigger an error.
If you have any other ideas on a better solution for this, you're very welcome to comment.
I appreciate any help. Thanks.
I think this article should answer on all your questions. See events list at the bottom of this article. Do not forget delete cache after each change made to config.xml and other xml files in your module etc folder.