Hooks for Discount Calculation
Filters for modifying discount calculation and price rounding.
Calculation Base Mode
Section titled “Calculation Base Mode”Each rule can have a calculation_base_mode setting that determines which price the discount is calculated from:
| Value | Constant | Description |
|---|---|---|
current_price | CalculationBaseModes::CURRENT_PRICE | Discount from current price (after previous discounts) - default |
original_price | CalculationBaseModes::ORIGINAL_PRICE | Discount always from regular price (regular_price) |
Behavior Example — multiple Auto Price rules
Section titled “Behavior Example — multiple Auto Price rules”Product: regular_price = $100Rule 1 (Product): -20% → $80Rule 2 (Bulk): -5%
With calculation_base_mode = 'current_price' on Rule 2: → 5% of $80 = $4 → final $76 (stacks on Rule 1)
With calculation_base_mode = 'original_price' on Rule 2: → 5% of $100 = $5 → final $95 (overrides Rule 1, recalculated from regular_price)When multiple Auto Price rules match the same product, they are applied in priority order (lower menu_order first). Each rule’s calculation_base_mode decides whether it stacks on the running price or restarts from regular_price. The stop_processing flag halts iteration over lower-priority rules.
Behavior Example — Auto Price + BOGO
Section titled “Behavior Example — Auto Price + BOGO”Product: regular_price = $100Rule 1 (Product): -20% → price = $80Rule 2 (BOGO): -50%
With calculation_base_mode = 'current_price': → 50% of 80 = $40 (discounts stack)
With calculation_base_mode = 'original_price': → 50% of 100 = $50 (discount from original price)Accessing the Setting in Code
Section titled “Accessing the Setting in Code”use WpifyWooDiscountRule\Services\Rules;use WpifyWooDiscountRule\Enums\CalculationBaseModes;
// Within a rule$calc_base_mode = $rule[ Rules::CALCULATION_BASE_MODE ] ?? CalculationBaseModes::CURRENT_PRICE;
if ( CalculationBaseModes::ORIGINAL_PRICE === $calc_base_mode ) { // Use regular_price $base = $product->get_regular_price();} else { // Use current price (may include other discounts) $base = $product->get_price();}Modifying Data Before Discount Calculation
Section titled “Modifying Data Before Discount Calculation”This filter allows you to modify data before the actual discount calculation.
apply_filters( 'wpify_woo_discounts_calculate_data', $data, $product, $rule, $range );Filter Parameters
Section titled “Filter Parameters”| Parameter | Type | Description |
|---|---|---|
$data | array | Data for discount calculation (original_price, calculation_base, discount_type, discount_value) |
$product | WC_Product | Product the discount is applied to |
$rule | array | Rule settings (including calculation_base_mode) |
$range | array | Current discount range/tier |
Usage Examples
Section titled “Usage Examples”Adjusting Discount for a Specific Category
Section titled “Adjusting Discount for a Specific Category”/** * Increase discount for products in the "sale" category * * @param array $data Calculation data * @param WC_Product $product Product * @param array $rule Rule * @param array $range Discount range * * @return array */function boost_discount_for_sale_category( $data, $product, $rule, $range ): array { if ( has_term( 'akce', 'product_cat', $product->get_id() ) ) { // Increase percentage discount by 5% if ( $data['discount_type'] === 'percentage' && isset( $data['discount_value'] ) ) { $data['discount_value'] += 5; } } return $data;}add_filter( 'wpify_woo_discounts_calculate_data', 'boost_discount_for_sale_category', 10, 4 );Setting a Minimum Discount
Section titled “Setting a Minimum Discount”/** * Ensure a minimum discount of 10% * * @param array $data Calculation data * @param WC_Product $product Product * @param array $rule Rule * @param array $range Discount range * * @return array */function ensure_minimum_discount( $data, $product, $rule, $range ): array { if ( $data['discount_type'] === 'percentage' ) { $data['discount_value'] = max( 10, $data['discount_value'] ?? 0 ); } return $data;}add_filter( 'wpify_woo_discounts_calculate_data', 'ensure_minimum_discount', 10, 4 );Modifying the Discount Calculation Result
Section titled “Modifying the Discount Calculation Result”This filter allows you to modify the result after the discount has been calculated.
apply_filters( 'wpify_woo_discounts_calculate_product_discount', $result, $product, $rule, $range );Filter Parameters
Section titled “Filter Parameters”| Parameter | Type | Description |
|---|---|---|
$result | array | Calculation result (discounted_price, discount_amount, discount_percentage) |
$product | WC_Product | Product |
$rule | array | Rule settings |
$range | array | Discount range |
Usage Examples
Section titled “Usage Examples”Logging Discount Calculations
Section titled “Logging Discount Calculations”/** * Log all discount calculations * * @param array $result Calculation result * @param WC_Product $product Product * @param array $rule Rule * @param array $range Discount range * * @return array */function log_discount_calculation( $result, $product, $rule, $range ): array { if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { error_log( sprintf( 'Discount calculated: Product #%d, Original: %s, Discounted: %s', $product->get_id(), $result['original_price'] ?? 'N/A', $result['discounted_price'] ?? 'N/A' ) ); } return $result;}add_filter( 'wpify_woo_discounts_calculate_product_discount', 'log_discount_calculation', 10, 4 );Modifying Price Rounding
Section titled “Modifying Price Rounding”This filter allows you to modify the rounding logic of the final discounted price.
apply_filters( 'wpify_woo_discounts_round_price', $rounded_price, $original_price, $rounding_mode, $rule );Filter Parameters
Section titled “Filter Parameters”| Parameter | Type | Description |
|---|---|---|
$rounded_price | float | Price after rounding (according to the configured mode) |
$original_price | float | Price before rounding (after discount) |
$rounding_mode | string | Mode: whole, ending_99, ending_90, ending_9 |
$rule | array | Complete rule data |
Usage Examples
Section titled “Usage Examples”Custom Rounding for VIP Customers
Section titled “Custom Rounding for VIP Customers”/** * Always round down for VIP customers * * @param float $rounded_price Rounded price * @param float $original_price Price before rounding * @param string $rounding_mode Rounding mode * @param array $rule Rule * * @return float */function vip_rounding( $rounded_price, $original_price, $rounding_mode, $rule ): float { if ( current_user_can( 'vip_member' ) ) { return floor( $original_price ); } return $rounded_price;}add_filter( 'wpify_woo_discounts_round_price', 'vip_rounding', 10, 4 );Rounding to Multiples of 10
Section titled “Rounding to Multiples of 10”/** * Round price to multiples of 10 * * @param float $rounded_price Rounded price * @param float $original_price Price before rounding * @param string $rounding_mode Rounding mode * @param array $rule Rule * * @return float */function round_to_tens( $rounded_price, $original_price, $rounding_mode, $rule ): float { return round( $original_price / 10 ) * 10;}add_filter( 'wpify_woo_discounts_round_price', 'round_to_tens', 10, 4 );Controlling Discount Application (Subscriptions)
Section titled “Controlling Discount Application (Subscriptions)”Filter to control whether a discount should be applied. Primarily used for WooCommerce Subscriptions.
apply_filters( 'wpify_woo_discounts_should_apply_discount', $should_apply, $rule );Filter Parameters
Section titled “Filter Parameters”| Parameter | Type | Description |
|---|---|---|
$should_apply | bool | Default value (true) |
$rule | array | Rule data including renewal_behavior |
Usage Examples
Section titled “Usage Examples”Disabling Bulk Discounts on Renewals
Section titled “Disabling Bulk Discounts on Renewals”/** * Never apply bulk discounts on renewal orders * * @param bool $should_apply Default value * @param array $rule Rule * * @return bool */function no_bulk_on_renewals( $should_apply, $rule ): bool { if ( function_exists( 'wcs_cart_contains_renewal' ) && wcs_cart_contains_renewal() ) { if ( ( $rule['rule_type'] ?? '' ) === 'bulk' ) { return false; } } return $should_apply;}add_filter( 'wpify_woo_discounts_should_apply_discount', 'no_bulk_on_renewals', 10, 2 );Available renewal behavior values:
first_order_only- Discount only on the first orderrenewals_only- Discount only on renewal ordersboth- Discount on both (default)
Modifying Target Products
Section titled “Modifying Target Products”This filter allows you to modify the list of target products after they have been prepared.
apply_filters( 'wpify_woo_discounts_targets_prepared', $targets, $rule );Filter Parameters
Section titled “Filter Parameters”| Parameter | Type | Description |
|---|---|---|
$targets | array | Array of target products |
$rule | array | Rule settings |
Usage Example
Section titled “Usage Example”/** * Exclude low-margin products from discount targets * * @param array $targets Target products * @param array $rule Rule * * @return array */function exclude_low_margin_products( $targets, $rule ): array { return array_filter( $targets, function( $target ) { $product_id = $target['product_id'] ?? 0; $margin = get_post_meta( $product_id, '_profit_margin', true ); return $margin > 10; // Exclude products with margin below 10% } );}add_filter( 'wpify_woo_discounts_targets_prepared', 'exclude_low_margin_products', 10, 2 );Modifying Subrule Metadata
Section titled “Modifying Subrule Metadata”This filter allows you to add custom metadata to subrules (discount tiers).
apply_filters( 'wpify_woo_discounts_subrule_metadata', $metadata, $subrule, $rule );Filter Parameters
Section titled “Filter Parameters”| Parameter | Type | Description |
|---|---|---|
$metadata | array | Subrule metadata |
$subrule | array | Subrule data |
$rule | array | Rule settings |
Usage Example
Section titled “Usage Example”/** * Add custom metadata for tracking * * @param array $metadata Metadata * @param array $subrule Subrule * @param array $rule Rule * * @return array */function add_tracking_metadata( $metadata, $subrule, $rule ): array { $metadata['campaign_id'] = $rule['campaign_id'] ?? ''; $metadata['applied_at'] = current_time( 'mysql' ); return $metadata;}add_filter( 'wpify_woo_discounts_subrule_metadata', 'add_tracking_metadata', 10, 3 );Modifying the Quantity Limit
Section titled “Modifying the Quantity Limit”This filter allows you to modify the maximum quantity for a discount.
apply_filters( 'wpify_woo_discounts_quantity_limit', $limit, $product, $rule );Filter Parameters
Section titled “Filter Parameters”| Parameter | Type | Description |
|---|---|---|
$limit | int | Quantity limit |
$product | WC_Product | Product |
$rule | array | Rule settings |
Usage Example
Section titled “Usage Example”/** * Limit quantity for wholesale customers * * @param int $limit Limit * @param WC_Product $product Product * @param array $rule Rule * * @return int */function limit_wholesale_quantity( $limit, $product, $rule ): int { if ( current_user_can( 'wholesale_customer' ) ) { return min( $limit, 100 ); // Max 100 pcs for wholesale } return $limit;}add_filter( 'wpify_woo_discounts_quantity_limit', 'limit_wholesale_quantity', 10, 3 );Fee Application Type
Section titled “Fee Application Type”This filter allows you to modify the fee/discount application type in the cart.
apply_filters( 'wpify_woo_discounts_fee_application_type', $type, $rule );Filter Parameters
Section titled “Filter Parameters”| Parameter | Type | Description |
|---|---|---|
$type | string | Application type (fixed, percent) |
$rule | array | Rule settings |
Usage Example
Section titled “Usage Example”/** * Change application type for specific rules * * @param string $type Application type * @param array $rule Rule * * @return string */function change_fee_application_type( $type, $rule ): string { // Use percentage for rules with the high_value tag if ( in_array( 'high_value', $rule['tags'] ?? [], true ) ) { return 'percent'; } return $type;}add_filter( 'wpify_woo_discounts_fee_application_type', 'change_fee_application_type', 10, 2 );Stacked Selection Price (Service Method)
Section titled “Stacked Selection Price (Service Method)”Compute the per-item discounted prices for a synthetic product selection — useful for custom selectors, blocks or templates that need to display the price of a not-yet-added-to-cart selection with all stacked price-modifying rules applied (Product + Bulk + Bundle).
Unlike a filter, this is a service method on StackedPriceResolver. Resolve it through the plugin’s DI container:
use WpifyWooDiscountRule\Modules\DiscountRules\Utils\StackedPriceResolver;
$resolver = wpify_woo_discount_rules_container()->get( StackedPriceResolver::class );$result = $resolver->compute_selection_price( $lines, $options );Input — $lines
Section titled “Input — $lines”A list of selection lines. Each line is an associative array:
| Key | Type | Required | Default | Notes |
|---|---|---|---|---|
product | WC_Product | one of these two | — | Used directly when present (no DB roundtrip). |
product_id | int | one of these two | — | If product missing, the service calls wc_get_product( product_id ). |
qty | int | no | 1 | qty <= 0 → line silently skipped. |
Normalisation rules (applied in order):
- If both
productandproduct_idare provided,productwins. - If neither is provided → line silently skipped.
- If
wc_get_product()returnsfalseforproduct_id→ line silently skipped. - Duplicate products across multiple lines (same effective
product_id) → merged,qtysummed.
Input — $options
Section titled “Input — $options”| Key | Type | Default | Effect |
|---|---|---|---|
include_cart | bool | false | When true, the current WC()->cart content is merged into the qualifying pool. Cart entries contribute to Bulk tier counts and Bundle slot matching, but never appear in the per-item output. |
Return Value
Section titled “Return Value”Always an array (never null). For empty input or input where every line is silently skipped:
[ 'items' => [], 'totals' => [ 'regular_total' => 0.0, 'discounted_total' => 0.0, 'savings' => 0.0, ],]For a non-empty selection:
[ 'items' => [ 123 => [ 'product' => WC_Product, 'product_id' => 123, 'qty' => 2, 'unit_regular_price' => 1000.00, 'unit_discounted_price' => 720.00, 'line_regular_total' => 2000.00, 'line_discounted_total' => 1440.00, 'savings' => 560.00, ], // ... one entry per unique sent product ], 'totals' => [ 'regular_total' => 2000.00, 'discounted_total' => 1440.00, 'savings' => 560.00, ],]items is keyed by product_id so templates can index directly ($result['items'][ 123 ]). PHP preserves insertion order, so iteration yields products in the order they first appeared in $lines. All numeric values are rounded to wc_get_price_decimals() precision.
The service returns raw numbers only — tax presentation (wc_get_price_to_display()) and HTML formatting (wc_price(), wc_format_sale_price()) are the template’s responsibility.
Usage Example
Section titled “Usage Example”use WpifyWooDiscountRule\Modules\DiscountRules\Utils\StackedPriceResolver;
/** * Render the bundle selector price inside a custom template. * * @param WC_Product[] $selected_products Products the customer picked. */function render_bundle_selector_price( array $selected_products ): void { $resolver = wpify_woo_discount_rules_container()->get( StackedPriceResolver::class );
$lines = array_map( fn( WC_Product $p ) => [ 'product' => $p, 'qty' => 1 ], $selected_products );
$result = $resolver->compute_selection_price( $lines );
if ( empty( $result['items'] ) ) { return; }
echo wc_format_sale_price( wc_price( $result['totals']['regular_total'] ), wc_price( $result['totals']['discounted_total'] ) );}Mode 2 (include_cart = true)
Section titled “Mode 2 (include_cart = true)”Pass include_cart to compute prices as if the selection were added to the current cart:
$result = $resolver->compute_selection_price( $lines, [ 'include_cart' => true ]);- Rule triggers see combined quantity (sent + cart). A Bulk tier or Bundle slot requirement can qualify based on the merged pool even when the sent selection alone would not satisfy it.
- Rule discounts are applied only to sent products — cart-only entries never appear in
items[].
Migration from wpify_woo_discounts_bundle_set_price
Section titled “Migration from wpify_woo_discounts_bundle_set_price”The filter wpify_woo_discounts_bundle_set_price and the public method Bundle::calculate_set_price() have been removed and are replaced by this service method.
// OLD — no longer works$price = apply_filters( 'wpify_woo_discounts_bundle_set_price', null, $selected_products, [ 'application_mode' => 'auto_price' ]);if ( null === $price ) { return; }echo $price['html'];
// NEWuse WpifyWooDiscountRule\Modules\DiscountRules\Utils\StackedPriceResolver;
$resolver = wpify_woo_discount_rules_container()->get( StackedPriceResolver::class );$lines = array_map( fn( $p ) => [ 'product' => $p, 'qty' => 1 ], $selected_products );$result = $resolver->compute_selection_price( $lines );if ( empty( $result['items'] ) ) { return; }echo wc_format_sale_price( wc_price( $result['totals']['regular_total'] ), wc_price( $result['totals']['discounted_total'] ));The new method applies all active price-modifying rules (Product + Bulk + Bundle), not just Bundle. The output is per-item (keyed by product_id) plus aggregate totals, instead of the previous Bundle-specific payload.
Where to Place the Code
Section titled “Where to Place the Code”You can add custom functions either in your child theme’s functions.php or using the Code Snippets plugin.