Please consider leaving a donation if you appreciate this information. Lightning network now available
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.
Challenge 1:
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!
Challenge 2:
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.
Challenge 3:
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:
And so as an homage to this awesome moment in WooCommerce history I endeavored to find a solution for this without editing core code.
Solution for challenge 2:
That particular message is generated by php and then passed off to beardedguy_localize_script which then outputs a JSON object in the HTML document – see the minimum_error message in the array?
So, the solution to this is to simply load some JavaScript and re-assign the variable. I’ll be using a static message, so no big deal. I’ll be loading this JavaScript file later, but the relevant parts are:
Solution to Challenge 3
This one was a little tougher to figure out – Name Your Price doesn’t provide a filter for this particular message and I can’t reassign it via JavaScript as I could for the other error message. I had to dig through some code to figure this one out, but I learned about a new filter so…hooray! Name Your Price adds this particular notice to the cart by using a WooCommerce function. That particular function is filterable. The problem here is that there may be many different messages generated using that function so how do I conditionally change just this one? A challenge within a challenge, that.
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.
Challenge 4
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.
Writing a cookie with JavaScript is easy enough. Reading it requires something a little more. Somewhere along the lines, I realized that WooCommerce uses the jQuery cookie extension and so since it already exists, I will use it too.
All of that said, the sequence of JavaScript events goes something like this:
- 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 JavaScript in its entirety:
Load the JavaScript
I’ve saved the JavaScript to a file called movingsale.js in wp-content/themes/MY-THEME/js/ – It has some dependencies, first the jQuery cookie extension for reading/writing the cookies and numeral for handling number formats w/decimals, and of course, jQuery is a dependency. To properly load this file with its dependencies requires a call to beardedguy_enqueue_script – the jQuery cookie extension is already registered by WooCommerce but the numeral library is not so I’ll also have to bring that into my project and register it prior to enqueuing movingsale.js. Something like this will do:
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.
After Thoughts:
Initially, I was using native (experimental) JavaScript functionality for number formatting/manipulation. It wasn’t until I actually tried my yard sale site on mobile that I discovered that the functions that I was using aren’t supported across browsers. NYP already includes accounting.js which is very similar to numeral.js. I didn’t have to bring numeral.js in to this project, I should have just used the already included accounting.js
By no means is this a bullet-proof solution. It’s quite trivial for a tech-savvy user to ascertain the minimum price acceptable.
Please consider leaving a donation if you appreciate this information. Lightning network now available
Leave a Reply
You must be registered and logged in to post a comment.