Skip to content

Instantly share code, notes, and snippets.

@RadGH
Last active February 18, 2025 22:07
Show Gist options
  • Save RadGH/69a1621791d74676d8443121fc2ddeca to your computer and use it in GitHub Desktop.
Save RadGH/69a1621791d74676d8443121fc2ddeca to your computer and use it in GitHub Desktop.
WooCommerce Membership Discount
<?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