WooCommerce’s price hooks make it straightforward to implement dynamic pricing without a premium plugin: tiered quantity discounts (buy 5 get 10% off, buy 10 get 20% off), role-based prices (wholesale vs retail), and time-limited flash sales. All three patterns hook into woocommerce_product_get_price and woocommerce_cart_item_price.
Problem: A WooCommerce store needs tiered pricing — orders over $100 get 10% off, wholesale customers get 20% off — but WooCommerce's native discount system only supports flat coupons and does not apply discounts automatically based on cart total or user role.
Solution: Use the woocommerce_cart_calculate_fees action to add a negative fee (effectively a discount) based on WC()->cart->get_subtotal() and the current user's role from current_user_can(). Combine with woocommerce_product_get_price for role-based product-level pricing that overrides the base price before it reaches the cart.
The code below implements tiered quantity pricing stored in product meta, role-based price overrides, and a flash-sale price that activates between two timestamps — all without touching the database schema.
get_cart() as $item ) {
$product = $item['data'];
$qty = (int) $item['quantity'];
$tiers = $product->get_meta( '_qty_discount_tiers' ); // array of [min_qty, pct]
if ( empty( $tiers ) || ! is_array( $tiers ) ) {
continue;
}
$discount_pct = 0.0;
foreach ( $tiers as $tier ) {
if ( $qty >= (int) $tier['min_qty'] ) {
$discount_pct = (float) $tier['pct'];
}
}
if ( $discount_pct > 0 ) {
$discount = - round( (float) $product->get_price() * $qty * $discount_pct / 100, 2 );
$cart->add_fee(
sprintf( 'Qty discount (%s%%)', $discount_pct ),
$discount,
false // not taxable
);
}
}
} );
// ── 2. Role-based price override ──────────────────────────────────────────
add_filter( 'woocommerce_product_get_price', function ( string $price, WC_Product $product ): string {
if ( ! is_user_logged_in() ) {
return $price;
}
$user = wp_get_current_user();
$role_prices = $product->get_meta( '_role_prices' ); // [ 'wholesale' => '19.99', ... ]
if ( ! is_array( $role_prices ) ) {
return $price;
}
foreach ( $user->roles as $role ) {
if ( isset( $role_prices[ $role ] ) && $role_prices[ $role ] !== '' ) {
return (string) $role_prices[ $role ];
}
}
return $price;
}, 10, 2 );
// ── 3. Flash-sale price between two timestamps ────────────────────────────
add_filter( 'woocommerce_product_get_price', function ( string $price, WC_Product $product ): string {
$flash_price = $product->get_meta( '_flash_price' );
$flash_start = (int) $product->get_meta( '_flash_start' ); // Unix timestamp
$flash_end = (int) $product->get_meta( '_flash_end' );
if ( $flash_price && $flash_start && $flash_end ) {
$now = time();
if ( $now >= $flash_start && $now <= $flash_end ) {
return (string) $flash_price;
}
}
return $price;
}, 20, 2 );
NOTE: Filtering woocommerce_product_get_price affects every call to $product->get_price(), including those used for min/max price range display in variable products — add an early return when $product->is_type('variable') to avoid corrupting the price range display on shop archives.