Last active
February 18, 2025 22:07
-
-
Save RadGH/69a1621791d74676d8443121fc2ddeca to your computer and use it in GitHub Desktop.
WooCommerce Membership Discount
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// Posted for Stack Overflow: | |
// @see https://stackoverflow.com/questions/77198717/temporary-global-sale-price-discount-for-all-products-in-woocommerce | |
// Note this code supports WooCommerce Subscriptions. | |
// If you aren't using that, you need to remove the references to WC_Subscriptions_Product and the related functions. | |
// 1. Custom function that should return true if the user is a member. | |
// Modified to just check if you are an admin, but you could change this to look at your previous subscriptions | |
function MyPluginName_get_user_membership() { | |
// $subscriptions = wcs_get_users_subscriptions( get_current_user_id() ); | |
return current_user_can('administrator'); | |
} | |
// 2. Custom function that returns the membership product ID | |
// In my case, we only have one membership product | |
function MyPluginName_get_membership_product_id() { | |
return 1234; | |
} | |
// 3. Object which handles all the hooks and logic for applying a membership discount | |
// Applies a 30% discount to all products except for the membership product itself | |
// Discount is also applied immediately if membership product is added to cart while not being a member yet. | |
class MyPluginName_Membership_Discount { | |
public function __construct() { | |
// Do not apply to the dashboard | |
if ( is_admin() && ! is_ajax() ) return; | |
// Add initial hooks | |
$this->add_hooks(); | |
} | |
/** | |
* Adds multiple hooks to apply the membership discount | |
* | |
* @return void | |
*/ | |
public function add_hooks() { | |
// Display the product price and membership price by default | |
add_filter( 'woocommerce_get_price_html', array( $this, 'display_membership_price' ), 10, 2 ); | |
// Display the membership price for subscriptions | |
add_filter( 'woocommerce_subscriptions_product_price_string', array( $this, 'display_membership_price_for_subscriptions' ), 10, 3 ); | |
// Replace the product prices with the membership prices | |
add_filter( 'woocommerce_product_get_price', array( $this, 'replace_product_price_with_discount' ), 20, 2 ); | |
add_filter( 'woocommerce_product_variation_get_price', array( $this, 'replace_product_price_with_discount' ), 20, 2 ); | |
// Display the original product price below the product title on the cart page | |
// (Without this, the sale price will show as the original price) | |
add_filter( 'woocommerce_cart_product_price', array( $this, 'apply_member_discount_on_cart_price' ), 10, 2 ); | |
// Apply membership price to displayed cart subtotals | |
// (Works for displayed subtotal on cart. Does not affect calculations) | |
add_filter( 'woocommerce_cart_product_subtotal', array( $this, 'apply_member_discount_on_cart_product_subtotal' ), 10, 4 ); | |
// ❌ These filters did not work as expected: | |
// --- add_filter( 'woocommerce_variable_subscription_price_html', array( $this, 'display_membership_price_for_subscriptions' ), 10, 2 ); | |
// --- add_filter( 'woocommerce_get_discounted_price', array( $this, 'apply_member_discount_on_cart_item' ), 10, 4 ); | |
// --- add_action( 'woocommerce_cart_calculate_fees', array( $this, 'add_membership_discount_on_checkout' ) ); | |
// --- add_filter( 'woocommerce_subscriptions_cart_get_price', array( $this, 'apply_member_discount_to_subscription_products' ), 10, 2 ); | |
} | |
/** | |
* Removes hooks that apply a membership discount | |
* | |
* @return void | |
*/ | |
public function remove_hooks() { | |
remove_filter( 'woocommerce_get_price_html', array( $this, 'display_membership_price' ), 10, 2 ); | |
remove_filter( 'woocommerce_subscriptions_product_price_string', array( $this, 'display_membership_price_for_subscriptions' ), 10, 3 ); | |
remove_filter( 'woocommerce_product_get_price', array( $this, 'replace_product_price_with_discount' ), 20, 2); | |
remove_filter( 'woocommerce_product_variation_get_price', array( $this, 'replace_product_price_with_discount' ), 20, 2); | |
remove_filter( 'woocommerce_cart_product_price', array( $this, 'apply_member_discount_on_cart_price' ), 10, 2 ); | |
remove_filter( 'woocommerce_cart_product_subtotal', array( $this, 'apply_member_discount_on_cart_product_subtotal' ), 10, 4 ); | |
} | |
// Singleton instance | |
protected static $instance = null; | |
public static function get_instance() { | |
if ( !isset( self::$instance ) ) self::$instance = new static(); | |
return self::$instance; | |
} | |
// Utilities | |
/** | |
* Returns the amount of discount to apply as a float. 0.3 = 30% | |
* | |
* @return float | |
*/ | |
public function get_discount_amount() { | |
return 0.3; | |
} | |
/** | |
* Returns true if the membership discount should apply to the current order. | |
* You must either already be a member, or have the membership product in your cart. | |
* | |
* @return bool | |
*/ | |
public function is_discount_applied() { | |
$has_membership = false; // already a member, or product in cart | |
// Remove hooks to prevent infinite loops when getting membership product | |
$this->remove_hooks(); | |
// Check if the user is a member or has the membership product in their cart | |
do { | |
// 1. Check if the user is a member | |
$current_membership = MyPluginName_get_user_membership(); | |
if ( $current_membership ) { | |
$has_membership = true; | |
break; | |
} | |
// 2. Check if the membership product is in the cart | |
if ( WC()->cart ) foreach ( WC()->cart->get_cart() as $item ) { | |
$product = wc_get_product( $item['product_id'] ); | |
if ( $product->is_type( 'membership' ) ) { | |
$has_membership = true; | |
break 2; | |
} | |
} | |
}while(false); | |
// Re-add hooks | |
$this->add_hooks(); | |
return $has_membership; | |
} | |
/** | |
* Checks if a product supports membership discount | |
* @param WC_Product|int $product | |
* @return bool | |
*/ | |
public function is_product_eligible_for_discount( $product ) { | |
$product = wc_get_product( $product ); | |
// 1. Do not apply membership discount to the membership itself | |
$membership_product_id = MyPluginName_get_membership_product_id(); | |
if ( $product->get_id() === $membership_product_id || $product->get_parent_id() === $membership_product_id ) { | |
return false; | |
} | |
return true; | |
} | |
/** | |
* Gets the text to add after membership prices | |
* | |
* @return string | |
*/ | |
public function get_membership_discount_suffix() { | |
$discount = $this->get_discount_amount(); | |
return ' (' . $discount * 100 . '% Member Discount)'; | |
} | |
/** | |
* Get the regular price for a product | |
* | |
* @param WC_Product $product | |
* | |
* @return string | |
*/ | |
public function get_regular_price( $product ) { | |
$this->remove_hooks(); | |
// Prefer regular price by default | |
$regular_price = $product->get_regular_price(); | |
// If blank, use the default price (For variations) | |
if ( ! $regular_price ) $regular_price = $product->get_price(); | |
$this->add_hooks(); | |
return $regular_price; | |
} | |
/** | |
* Get the membership price for a product | |
* @param WC_Product|int $product | |
* @return float | |
*/ | |
public function get_product_membership_price( $product ) { | |
$this->remove_hooks(); | |
$product = wc_get_product( $product ); | |
$current_price = $product->get_price(); | |
$discount = $this->get_discount_amount(); | |
$this->add_hooks(); | |
if ( ! $current_price ) { | |
return $current_price; | |
}else{ | |
return $current_price - ( $current_price * $discount ); | |
} | |
} | |
/** | |
* Convert a price to a subscription term, for a subscription product. For example "$1 / month" | |
* | |
* @param string|float $price | |
* @param WC_Product $product | |
* | |
* @return string | |
*/ | |
public function apply_subscription_formatting( $price, $product ) { | |
$this->remove_hooks(); | |
if ( ! str_contains($price, '$') ) { | |
$price = wc_price($price); | |
} | |
$formatted_price = WC_Subscriptions_Product::get_price_string( $product, array( 'price' => $price ) ); | |
$this->add_hooks(); | |
return $formatted_price; | |
} | |
/** | |
* Check if a product is a subscription | |
* | |
* @param WC_Product|int $product | |
* | |
* @return bool | |
*/ | |
public function is_product_subscription( $product ) { | |
return WC_Subscriptions_Product::is_subscription( $product ); | |
} | |
/** | |
* Apply the membership discount formatting to products on the product page | |
* | |
* @param string $regular_price Should already be formatted with wc_price() | |
* @param string $membership_price Should already be formatted with wc_price() | |
* | |
* @return string | |
*/ | |
public function apply_product_discount_html( $regular_price, $membership_price ) { | |
$discount_html = $this->get_membership_discount_suffix(); | |
return | |
'<span class="woocommerce-Price-amount amount">' . | |
'<span class="regular-price"><span class="label screen-reader-text">Price: </span>'. $regular_price . ' </span>' . | |
'<span class="membership-price"><span class="label">Member\'s Price: </span>'. $membership_price . ' <span class="discount">'. $discount_html .'</span> </span>' . | |
'</span>'; | |
} | |
/** | |
* Apply the membership discount formatting to products on the cart | |
* | |
* @param string $regular_price Should already be formatted with wc_price() | |
* @param string $membership_price Should already be formatted with wc_price() | |
* | |
* @return string | |
*/ | |
public function apply_cart_discount_html( $regular_price, $membership_price ) { | |
$discount_html = $this->get_membership_discount_suffix(); | |
return | |
'<span class="cart-product-subtotal">'. | |
'<span class="regular-price"><del>'. $regular_price . '</del> </span>'. | |
'<span class="membership-price">' . $membership_price . '</span>'. | |
'<span class="membership-discount">' . $discount_html . '</span>'. | |
'</span>'; | |
} | |
// Hooks | |
/** | |
* Display the membership price | |
*/ | |
public function display_membership_price( $price, $product ) { | |
// Do not apply to admin | |
if ( is_admin() ) return $price; | |
// Do not apply to subscriptions | |
/** @see self::display_membership_price_for_subscriptions() */ | |
if ( $this->is_product_subscription( $product ) ) { | |
return $price; | |
} | |
// Product must support discount | |
if ( ! $this->is_product_eligible_for_discount( $product ) ) { | |
return $price; | |
} | |
// Ignore for grouped products, shows a price range instead | |
if ( $product->is_type( 'grouped' ) ) { | |
return $price; | |
} | |
// Get the regular price | |
$regular_price = $this->get_regular_price( $product ); | |
// Do not format if price is empty | |
if ( ! $regular_price ) { | |
return $price; | |
} | |
// Get the membership price | |
$membership_price = $this->get_product_membership_price( $product ); | |
return $this->apply_product_discount_html( wc_price( $regular_price ), wc_price( $membership_price ) ); | |
} | |
/** | |
* Display the membership price for subscriptions | |
* | |
* @param string $price | |
* @param WC_Product $product | |
* @param array $include | |
* | |
* @return string | |
*/ | |
public function display_membership_price_for_subscriptions( $price, $product, $include ) { | |
// Do not apply to admin | |
if ( is_admin() ) return $price; | |
// Product must support discount | |
if ( ! $this->is_product_eligible_for_discount( $product ) ) { | |
return $price; | |
} | |
$this->remove_hooks(); | |
// Get the regular price | |
$regular_price = $this->get_regular_price($product); | |
// Do not show if price is empty | |
if ( ! $regular_price ) { | |
$this->add_hooks(); | |
return $regular_price; | |
} | |
// Get the membership price | |
$membership_price = $this->get_product_membership_price( $product ); | |
$regular_price_subscription = $this->apply_subscription_formatting( $regular_price, $product ); | |
$membership_price_subscription = $this->apply_subscription_formatting( $membership_price, $product ); | |
// Display regular and membership prices | |
if ( is_cart() ) { | |
$html = $this->apply_cart_discount_html( $regular_price_subscription, $membership_price_subscription ); | |
}else{ | |
$html = $this->apply_product_discount_html( $regular_price_subscription, $membership_price_subscription ); | |
} | |
$this->add_hooks(); | |
return $html; | |
} | |
/** | |
* Replace the product price with the membership price | |
* | |
* @param float $price | |
* @param WC_Product $product | |
* | |
* @return float | |
*/ | |
public function replace_product_price_with_discount( $price, $product ) { | |
// Product must support discount | |
if ( ! $this->is_product_eligible_for_discount( $product ) ) { | |
return $price; | |
} | |
if ( $this->is_discount_applied() ) { | |
// Return the membership price | |
return $this->get_product_membership_price( $product ); | |
}else{ | |
// Return the regular price | |
return $price; | |
} | |
} | |
/** | |
* Display the original product price below the product title on the cart page | |
* | |
* @param string $price | |
* @param WC_Product $product | |
* | |
* @return string | |
*/ | |
public function apply_member_discount_on_cart_price( $price, $product ) { | |
if ( ! $this->is_discount_applied() ) { | |
return $price; | |
} | |
// Product must support discount | |
if ( ! $this->is_product_eligible_for_discount( $product ) ) { | |
return $price; | |
} | |
// Disable hooks to prevent infinite loops | |
$this->remove_hooks(); | |
// Get the product | |
$product = wc_get_product( $product ); | |
// Get the regular price | |
$regular_price = $product->get_price(); | |
// Re-add hooks | |
$this->add_hooks(); | |
return wc_price( $regular_price ); | |
} | |
/** | |
* Apply the member discount on the cart subtotal | |
* | |
* @param string $subtotal | |
* @param WC_Product $product | |
* @param int $quantity | |
* @param WC_Cart $cart | |
* | |
* @return string | |
*/ | |
public function apply_member_discount_on_cart_product_subtotal( $subtotal, $product, $quantity, $cart ) { | |
if ( ! $this->is_discount_applied() ) { | |
return $subtotal; | |
} | |
// Product must support discount | |
if ( ! $this->is_product_eligible_for_discount( $product ) ) { | |
return $subtotal; | |
} | |
// Remove hooks to prevent infinite loops | |
$this->remove_hooks(); | |
// Get the product | |
$product = wc_get_product( $product ); | |
// Get the regular price | |
$regular_price = $product->get_price() * $quantity; | |
$regular_price_html = wc_price( $regular_price ); | |
// Get the membership price | |
$membership_price = $this->get_product_membership_price( $product ) * $quantity; | |
$membership_price_html = wc_price( $membership_price ); | |
return $this->apply_cart_discount_html( $regular_price_html, $membership_price_html ); | |
} | |
} | |
// Initialize the object | |
MyPluginName_Membership_Discount::get_instance(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment