I'm working on some code to display the discounted price on my pages, using the EDD_Discount class. i'm using this in a WP "Page", not an EDD Download ... hence the need to write code to get this info, instead of just letting EDD calculate the discount in the cart.
my display of the discount on my landing pages looks something like this:

i generally have it working and am quite happy at how easy it is to build code on top of EDD :)
But the a key scenario I have not covered in my code, is whether or not the discount code supplied in the URL is valid for the product the person is looking at.
here's the code i have so far (don't judge me too hard! I'm a JS dev, not a PHP dev :D):
function wmc_get_price($edd_id){
$download = new EDD_Download($edd_id);
if (!$download){ return; }
$get_args=array_change_key_case($_GET, CASE_LOWER);
$discount_code = $get_args["discount"];
$price = $download->get_price();
if ($discount_code){
$discount = edd_get_discount_by_code($discount_code);
$has_discount = !!$discount;
if ($has_discount) {
$discount_price = $discount->get_discounted_amount($price);
} else {
$discount_price = $price;
}
} else {
$has_discount = false;
$discount_price = $price;
}
return array(
"price" => number_format($price, 2),
"discount_price" => number_format($discount_price, 2),
"has_discount" => $has_discount
);
}
i was looking through the EDD_Discount class and found the is_product_requirements_met method... this generally looks like what I want, except this method relies on items being in the shopping cart already.
is there a way for me to check if the product requirements have been met, by passing in an array of product ids?
something like this would be great:
$edd_id_list = array(1234);
$discount->is_product_requirements_met(false, $edd_id_list);
that way I don't have to rely on the shopping cart, but can tell someone if the discount code in the URL applies to the product that I specify.
...
is_validI just saw the is_valid method on the EDD_Discount as well. it looks like this would be the option that i really want, but would need to pass in an array of ids.
$edd_id_list = array(1234);
$discount->is_product_requirements_met($user, false, $edd_id_list);
FWIW, i found a way to make this work using a global var and the edd_cart_contents filter.
this is a seriously ugly hack... but it appears to work.
function wmc_get_price($edd_id){
$download = new EDD_Download($edd_id);
if (!$download){ return; }
$get_args=array_change_key_case($_GET, CASE_LOWER);
$discount_code = $get_args["discount"];
$price = $download->get_price();
// ADD UGLY HACK
// -------------
// This is the only way I could find to override the "cart->get_contents()"
// call that is needed when calling $discount->is_valid...
$GLOBALS["wmc_global_cart_contents"] = $download;
add_filter("edd_cart_contents", 'wmc_get_cart_contents');
// -------------
if ($discount_code){
$discount = edd_get_discount_by_code($discount_code);
if ($discount and $discount->is_valid(false)) {
$has_discount = true;
$discount_price = $discount->get_discounted_amount($price);
} else {
$has_discount = false;
$discount_price = $price;
}
} else {
$has_discount = false;
$discount_price = $price;
}
// REMOVE UGLY HACK
// ----------------
remove_filter("edd_cart_contents", "wmc_get_cart_contents");
// ----------------
return array(
"price" => number_format($price, 2),
"discount_price" => number_format($discount_price, 2),
"has_discount" => $has_discount
);
}
function wmc_get_cart_contents($cart){
$download = $GLOBALS["wmc_global_cart_contents"];
$list = array();
$list[] = array(
"id" => $download->ID,
"options" => array("price_id" => 0),
"quantity" => 1
);
return $list;
}
i'd really love to see the cart class updated to let me specify the list of downloads to use in the check, so i don't need to do things this way.
We should be able to make this a lot simpler :+1:
@sunnyratilal See https://github.com/CalderaWP/cf-edd-pro/commit/4362ba0fa374bf61141ff42af8a8191c04551e59 for how I added support for this in our EDD Add-on. I extended EDD_Discount to check against an array of downloads. Also, I added REST API endpoints to do the checks. Please borrow as much as of that code as you want.
Hi @Shelob9
I've started working on integrating this into EDD_Discount, but after testing your code, I'm not getting the correct output (i.e. true is returning when it shouldn't be).
Also, I'm not sure I understand the logic here - surely, this would never return true as each $download_id is a subkey of the $product_reqs - is it supposed to be testing against the $items array instead?
@sunnyratilal You were right about the wrong array being checked. Fixed it here: https://github.com/CalderaWP/cf-edd-pro/commit/ca6d077c0580e426ca6b966f69af3c5ccf0b6ebb
Also, see how I refactored the validity check here: https://github.com/CalderaWP/cf-edd-pro/commit/82a485828d83acf38b44cebc068d9de98bc7dc27
I'm now returning a WP_Error object or true. Using that WP_Error for my REST API responses, but could also be applied to EDD cart errors. I think that is way better than how I was doing it before.
Please let me know if you want to set up a screenshare to go over how this all works on our end and how I'm using it on our site. Really looking forward to throwing out as much of my code as possible once this issue is closed :)
Thanks @Shelob9 - code makes more sense logically now :)
I'll have the PR ready within a few hours.
@Shelob9 please take a look at the PR I've opened at #5667.
I can't introduce all the changes you introduced into is_valid() because our AJAX functions rely on the edd-discount-error error key.
@sunnyratilal I don't see much advantage to this approach vs what exists now.
This maintains the strong coupling of the discount code validation to the cart. I see this anti-pattern quite a bit, and it's what makes it hard to use EDD for content management, while using my own checkout system.
The existing class' current concerns are validating discount codes against the cart contents, and discount CRUD. I don't think adding arbitrary item checks to it makes a ton of sense.
I would rather see a validator that took a EDD_Discount and an arbitrary array of download IDs and provided a boolean or a WP_Error as the output of the program. Then you could replace the validation parts of EDD_Discount with that object, fed with cart contents. You could pass the message from the WP_Error object generated by that new class to edd_set_error() as needed.
I would rather see a validator that took a EDD_Discount and an arbitrary array of download IDs and provided a boolean or a WP_Error as the output of the program.
This is what I was envisioning happening with this issue.
from a software design perspective, ++ to having a separate validator class like this. i like the way you think @Shelob9 :)
Going ahead with a new approach based on the comments above:
EDD_Discount_Validator classEDD_Discount to EDD_Discount_ValidatorEDD_Discount call the corresponding method in EDD_Discount_Validatoredd_get_cart_contents() with the EDD_Discount validation function pass the download IDs to EDD_Discount_Validator and continue validation as normalThis will break the coupling between the discount and the cart contents.
PR opened at #6045
After writing some initial unit tests, some of the logic is flawed somewhere in. Investigating...
Unit tests added and logic corrected
Punted to 2.8.8
This is necessary for 3.0 because it is needed for retroactively applying discounts for the refunds API. This will be implemented as part of #2721.
It will have the following method signature: bool : function edd_validate_discount( $discount_id = 0, $download_ids = array() ).
Closing as this has been done as part of #2721 and was introduced in https://github.com/easydigitaldownloads/easy-digital-downloads/commit/c49629126522f5f296bfa1dc39e4f743ee1a19ae.
Most helpful comment
We should be able to make this a lot simpler :+1: