Moving, Must Sell
The arguments in favor of using WooCommerce to power my moving/yard sale were many. I can accept various payment methods, items which have been sold will automatically be marked, etc, etc, etc. One feature of a yard sale that is hard to pull of via an online solution is the haggling part of it. Sure, you can list things as ‘…or best offer’, but that’s going to require some personal contact and I simply don’t want to deal with multiple phone calls or emails from people who may not be truly interested. I wanted to offer a way to allow people to haggle on my used wares without me having to actually interact. The Name Your Price extension for WooCommerce comes pretty close to an out-of-the-box solution to this problem. It did present some challenges, but I’ve created a few workarounds to get past those and now have a pretty decent haggling system built into my yard sale.
The first thing that Name Your Price does by default that I wanted to change is that it displays the minimum price I’m willing to accept for the item. This kind of defeats the purpose of haggling and I don’t know why anyone would willingly pay more than my minimum price for my used crap.
This is a pretty easy problem to solve. By using a template override this message can be customized. This actually went through several iterations, but the final iteration displays a message to the user that they only get one chance per 24 hour period to make an offer (I’ll get to that later). The contents of my template override are this:
The file is called minimum-price.php. It is provided by Name Your Price and it’s original location is wp-content/plugins/woocommerce-name-your-price/templates/single-product – simply copy that file to wp-content/themes/YOUR-THEME/woocommerce/single-product/ and edit it as appropriate. That’s it! Challenge solved!
If a user inputs a price that is lower than the minimum price, they are presented with an error message. By default, this error message includes the minimum price. That’s no good.
Very similar to challenge 2. The ‘Add to cart (Submit your offer)’ button is still enabled. Name Your Price has a handler for when that is pressed, if the price entered is below the minimum, the item can’t be added to the cart and a ‘cart notice’ will be added stating that. Again, this notice/error displays the minimum acceptable price. Again, no good:
Name Your Price doesn’t provide any filters for either of these messages so my first thought was to just edit the core code which I was initially fine with, this isn’t an extension that I will be using for a long period of time so I’m not concerned about losing changes on update. However about this same time this happened:
Solution for challenge 2:
That particular message is generated by php and then passed off to wp_localize_script which then outputs a JSON object in the HTML document – see the minimum_error message in the array?
Solution to Challenge 3
NYP calls wc_nyp_add_notice, which in turn calls wc_add_notice, which in turn makes a call to apply_filters. The filter is variably named based on the ‘type’ of notice, in this case ‘error’ is the type that is passed along:
The filter I want to hook into, therefore, will be named woocommerce_add_error. The string that is sent is variable based on the minimum price set, and that minimum price gets wrapped in a span element. To conditionally change the notice based on a known string and an unknown amount requires a regular expression. Regular expressions are hard, but this is what I came up with:
And there you have it, don’t alter error messages unless they contain an identifiable string that was most likely generated by NYP.
So, with my minimum price now hidden from all but the most technically minded shoppers, I had to come up with a solution to prevent people from simply adjusting the amount of their offer until it was finally accepted. Again, this went through several iterations. I eventually went with a two-tiered approach that, if the price input is lower than the original price it 1) sets a cookie and 2) sets a transient.
The cookie should be straightforward enough. The cookie uniquely identifies if the current browser has input a price for the current item being viewed. If that cookie exists, the price input is disabled and will maintain its value of the ‘suggested price’ that is configured for the product. I assume that some of my shoppers are smart enough to at least try with a different browser. To prevent this, a transient is set which is a simply a string consisting of their I.P. address and a product id. Of course the challenge here is what about shoppers who share an I.P. – say from a bar/café/library. I decided to set the transient’s expiration for one hour – that should be long enough for any one person to lose interest in gaming the system but won’t lock everyone out from that particular I.P. for more than one hour.
- on page load, initially set the NYP price input to disabled
- find the product id
- find the ‘suggested price’
- check for the existence of a cookie.
- if the cookie doesn’t exist, check for the existence of a transient
- if neither a cookie or transient exist, enable the price input
- bind an event listener to the ‘blur’ event of the price input
- the event listener will compare the price entered with the original price – if the price entered is lower than the original price, disable the input element, set a cookie, set a transient.
The AJAX handler also needs to be added. You’ll notice two calls to add_action. One is for logged in users, the other is for non-logged in users. In this example there isn’t any sanitization/validation done, but since data will be written to the database, the values should be sanitized/validated.
By no means is this a bullet-proof solution. It’s quite trivial for a tech-savvy user to ascertain the minimum price acceptable.