Claude Skill

wc-variations-pricing-filters

Customize WooCommerce variation prices without stale or cross-user parent price caches. Covers direct variation getter filters, parent aggregation filters, `woocommerce_get_variation_prices_hash`, WooCommerce 11.0 tax-influence cache partitioning, bounded pricing contexts, role c

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download lonsdale201-wp-agent-skills-woocommerce_wc-variations-pricing-filters-52f6020.zip · 3 KB
Part of lonsdale201/wp-agent-skills — 226 skills

Install

skills CLI npx skills add https://github.com/Lonsdale201/wp-agent-skills/tree/main/woocommerce/wc-variations-pricing-filters
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install lonsdale201-wp-agent-skills@llmmart
Git git clone https://github.com/Lonsdale201/wp-agent-skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole lonsdale201/wp-agent-skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

WooCommerce variation pricing filters

A selected WC_Product_Variation price and a parent variable product's min/max price range use different filter paths. Implementing only one produces inconsistent catalog and variation UI.

Filter chain

Direct variation object

In view context, variation getters use:

woocommerce_product_variation_get_price
woocommerce_product_variation_get_regular_price
woocommerce_product_variation_get_sale_price

These affect $variation->get_price() but do not by themselves rebuild a parent variable product's cached price range.

Parent aggregation

WC_Product_Variable::get_variation_prices() loads raw child values and applies:

woocommerce_variation_prices_price
woocommerce_variation_prices_regular_price
woocommerce_variation_prices_sale_price

Each receives ( $price, WC_Product_Variation $variation, WC_Product_Variable $parent ). The resulting arrays are cached in the parent's wc_var_prices_<id> transient by a context hash.

Display/tax edge

get_variation_prices( false ) is raw business data. get_variation_prices( true ) adapts for shop tax display. Do not use display-adjusted values for authorization, thresholds, or stored calculations.

WooCommerce 11.0 adds woocommerce_variable_product_taxes_influence_price. Core uses taxability and configured tax rates to decide whether opposite display-tax variants need separate cache entries. If an extension makes displayed prices differ by country/tax context even where WooCommerce has no configured rates, force the safe split:

add_filter(
    'woocommerce_variable_product_taxes_influence_price',
    static function ( bool $influences, WC_Product $product ): bool {
        return $influences || myplugin_has_location_price_adjustment( $product->get_id() );
    },
    10,
    2
);

Return true only when the two variants can differ. This filter complements, and does not replace, adding every extension-owned pricing dimension to woocommerce_get_variation_prices_hash.

Role-based example

function myplugin_is_wholesale_request(): bool {
    if ( ! is_user_logged_in() ) {
        return false;
    }

    return in_array( 'wholesale_customer', wp_get_current_user()->roles, true );
}

function myplugin_apply_wholesale_price( $price ) {
    if ( '' === $price || ! myplugin_is_wholesale_request() ) {
        return $price;
    }

    return wc_format_decimal( (float) $price * 0.90, wc_get_price_decimals() );
}

add_filter(
    'woocommerce_product_variation_get_price',
    static function ( $price, WC_Product_Variation $variation ) {
        return myplugin_apply_wholesale_price( $price );
    },
    20,
    2
);

add_filter(
    'woocommerce_variation_prices_price',
    static function ( $price, WC_Product_Variation $variation, WC_Product_Variable $parent ) {
        return myplugin_apply_wholesale_price( $price );
    },
    20,
    3
);

add_filter(
    'woocommerce_get_variation_prices_hash',
    static function ( array $hash, WC_Product_Variable $product, bool $for_display ): array {
        $hash['myplugin_pricing_context'] = myplugin_is_wholesale_request() ? 'wholesale' : 'retail';
        $hash['myplugin_rules_version']   = (int) get_option( 'myplugin_pricing_rules_version', 1 );
        return $hash;
    },
    20,
    3
);

Do not call current_user_can( 'wholesale_customer' ) when wholesale_customer is merely a role slug. Roles and capabilities are different. Prefer a real custom capability when the pricing entitlement is security-sensitive; otherwise check the user's roles explicitly.

Cache context is mandatory

The default variation-price hash includes tax display/customer tax context and active variation pricing callbacks. It cannot infer values read inside your callback, such as:

  • user role or B2B group;
  • currency/price list;
  • region not represented by Woo tax context;
  • A/B bucket;
  • pricing rule version;
  • bounded campaign period.

Add every output-changing context to woocommerce_get_variation_prices_hash. Keep dimensions bounded. Never append current timestamps, random values, raw user IDs, session IDs, or arbitrary request parameters: each unique hash creates another cached price array and can cause transient bloat.

For time-based pricing, use a finite campaign ID or coarse period key and increment a rule version on configuration changes.

Which values to filter

Price semantics must remain coherent:

  • price is the effective current value.
  • regular_price is the non-sale reference.
  • sale_price is the sale value or empty.

A permanent role discount usually changes effective price only; decide whether it should appear as a WooCommerce sale. If you also filter sale/regular values, ensure sale_price < regular_price, empty sale values remain empty, and parent range/on-sale badges match the selected variation.

Do not return formatted HTML or currency strings from numeric price filters. Return decimal-compatible numeric strings.

Avoid recursion and global state leaks

Inside a variation price getter filter, do not call the same getter on the same object:

// WRONG: recursive.
add_filter( 'woocommerce_product_variation_get_price', static function ( $price, $variation ) {
    return $variation->get_price() * 0.9;
}, 10, 2 );

Use the $price argument. If a rule needs stored unfiltered data, read with edit context deliberately and document that this bypasses view filters:

$stored_regular = $variation->get_regular_price( 'edit' );

Do not mutate and save products from a price read filter. Filters can execute many times and during cache generation.

Invalidation when rules change

Changing callback code does not necessarily invalidate existing parent price arrays. Use one of these strategies:

  1. Add a bounded rules version to the variation price hash and increment it on settings migration/update.
  2. For known affected parents, call wc_delete_product_transients( $parent_id ) after the rule changes.
  3. For a true store-wide semantic change, invalidate the WooCommerce product transient version deliberately, understanding the broad cache impact.

Do not clear product transients on every frontend price read.

When filters are the wrong model

Use product CRUD and stored prices when the value is canonical for all shoppers and should drive reports, exports, REST responses, indexing, and admin screens. Use filters only for request-context pricing where every relevant read path is controlled.

Cart/order totals capture prices at the transaction boundary. Revalidate entitlement during add-to-cart/checkout; a display filter alone is not protection against crafted requests or stale sessions.

Test matrix

Test simple and variable product displays, selected variation, cart, checkout, Store API, taxes inclusive/exclusive, VAT exemption, guest/each role, cache warm order (retail then wholesale and reverse), sale prices, currency context, and rule version changes.

Inspect both:

$variation->get_price();
$parent->get_variation_prices( false );
$parent->get_variation_prices( true );

Critical rules

  • Cover direct variation and parent aggregation paths.
  • Add all bounded pricing contexts to the hash.
  • Never use role names as capabilities accidentally.
  • Never use unbounded user/session/time data in the hash.
  • Return numeric values, not formatted HTML.
  • Keep filters side-effect free and non-recursive.
  • Revalidate price entitlement when creating cart/order values.

Cross-references

  • wc-variations-data for stored variation CRUD and deferred parent sync.
  • wc-cart-checkout-classic for captured cart prices.

References

  • Parent aggregation/cache hash: includes/data-stores/class-wc-product-variable-data-store-cpt.php.
  • Variation getter prefix: includes/class-wc-product-variation.php and includes/abstracts/abstract-wc-data.php.
  • Verified source paths:
    • wp-content/plugins/woocommerce/includes/class-wc-product-variable.php
Files (wp-agent-skills)
  • SKILL.md 8.6 KB
    ---
    name: wc-variations-pricing-filters
    description: Customize WooCommerce variation prices without stale or cross-user parent price caches. Covers direct variation getter filters, parent aggregation filters, `woocommerce_get_variation_prices_hash`, WooCommerce 11.0 tax-influence cache partitioning, bounded pricing contexts, role checks, cache invalidation, and when stored CRUD prices are preferable. Use for B2B, role, segment, campaign, tax/location, or context-dependent variation pricing.
    metadata:
      wp-skills-author: "Soczó Kristóf"
      wp-skills-contact: "mailto:lonsdale201@hotmail.com"
      wp-skills-plugin: "woocommerce"
      wp-skills-plugin-version-tested: "11.0.0"
      wp-skills-php-min: "7.4"
      wp-skills-last-updated: "2026-08-05"
    ---
    
    # WooCommerce variation pricing filters
    
    A selected `WC_Product_Variation` price and a parent variable product's min/max price range use different filter paths. Implementing only one produces inconsistent catalog and variation UI.
    
    ## Filter chain
    
    ### Direct variation object
    
    In view context, variation getters use:
    
    ```text
    woocommerce_product_variation_get_price
    woocommerce_product_variation_get_regular_price
    woocommerce_product_variation_get_sale_price
    ```
    
    These affect `$variation->get_price()` but do not by themselves rebuild a parent variable product's cached price range.
    
    ### Parent aggregation
    
    `WC_Product_Variable::get_variation_prices()` loads raw child values and applies:
    
    ```text
    woocommerce_variation_prices_price
    woocommerce_variation_prices_regular_price
    woocommerce_variation_prices_sale_price
    ```
    
    Each receives `( $price, WC_Product_Variation $variation, WC_Product_Variable $parent )`. The resulting arrays are cached in the parent's `wc_var_prices_<id>` transient by a context hash.
    
    ### Display/tax edge
    
    `get_variation_prices( false )` is raw business data. `get_variation_prices( true )` adapts for shop tax display. Do not use display-adjusted values for authorization, thresholds, or stored calculations.
    
    WooCommerce 11.0 adds `woocommerce_variable_product_taxes_influence_price`. Core uses taxability and configured tax rates to decide whether opposite display-tax variants need separate cache entries. If an extension makes displayed prices differ by country/tax context even where WooCommerce has no configured rates, force the safe split:
    
    ```php
    add_filter(
        'woocommerce_variable_product_taxes_influence_price',
        static function ( bool $influences, WC_Product $product ): bool {
            return $influences || myplugin_has_location_price_adjustment( $product->get_id() );
        },
        10,
        2
    );
    ```
    
    Return `true` only when the two variants can differ. This filter complements, and does not replace, adding every extension-owned pricing dimension to `woocommerce_get_variation_prices_hash`.
    
    ## Role-based example
    
    ```php
    function myplugin_is_wholesale_request(): bool {
        if ( ! is_user_logged_in() ) {
            return false;
        }
    
        return in_array( 'wholesale_customer', wp_get_current_user()->roles, true );
    }
    
    function myplugin_apply_wholesale_price( $price ) {
        if ( '' === $price || ! myplugin_is_wholesale_request() ) {
            return $price;
        }
    
        return wc_format_decimal( (float) $price * 0.90, wc_get_price_decimals() );
    }
    
    add_filter(
        'woocommerce_product_variation_get_price',
        static function ( $price, WC_Product_Variation $variation ) {
            return myplugin_apply_wholesale_price( $price );
        },
        20,
        2
    );
    
    add_filter(
        'woocommerce_variation_prices_price',
        static function ( $price, WC_Product_Variation $variation, WC_Product_Variable $parent ) {
            return myplugin_apply_wholesale_price( $price );
        },
        20,
        3
    );
    
    add_filter(
        'woocommerce_get_variation_prices_hash',
        static function ( array $hash, WC_Product_Variable $product, bool $for_display ): array {
            $hash['myplugin_pricing_context'] = myplugin_is_wholesale_request() ? 'wholesale' : 'retail';
            $hash['myplugin_rules_version']   = (int) get_option( 'myplugin_pricing_rules_version', 1 );
            return $hash;
        },
        20,
        3
    );
    ```
    
    Do not call `current_user_can( 'wholesale_customer' )` when `wholesale_customer` is merely a role slug. Roles and capabilities are different. Prefer a real custom capability when the pricing entitlement is security-sensitive; otherwise check the user's roles explicitly.
    
    ## Cache context is mandatory
    
    The default variation-price hash includes tax display/customer tax context and active variation pricing callbacks. It cannot infer values read inside your callback, such as:
    
    - user role or B2B group;
    - currency/price list;
    - region not represented by Woo tax context;
    - A/B bucket;
    - pricing rule version;
    - bounded campaign period.
    
    Add every output-changing context to `woocommerce_get_variation_prices_hash`. Keep dimensions bounded. Never append current timestamps, random values, raw user IDs, session IDs, or arbitrary request parameters: each unique hash creates another cached price array and can cause transient bloat.
    
    For time-based pricing, use a finite campaign ID or coarse period key and increment a rule version on configuration changes.
    
    ## Which values to filter
    
    Price semantics must remain coherent:
    
    - `price` is the effective current value.
    - `regular_price` is the non-sale reference.
    - `sale_price` is the sale value or empty.
    
    A permanent role discount usually changes effective `price` only; decide whether it should appear as a WooCommerce sale. If you also filter sale/regular values, ensure `sale_price < regular_price`, empty sale values remain empty, and parent range/on-sale badges match the selected variation.
    
    Do not return formatted HTML or currency strings from numeric price filters. Return decimal-compatible numeric strings.
    
    ## Avoid recursion and global state leaks
    
    Inside a variation price getter filter, do not call the same getter on the same object:
    
    ```php
    // WRONG: recursive.
    add_filter( 'woocommerce_product_variation_get_price', static function ( $price, $variation ) {
        return $variation->get_price() * 0.9;
    }, 10, 2 );
    ```
    
    Use the `$price` argument. If a rule needs stored unfiltered data, read with edit context deliberately and document that this bypasses view filters:
    
    ```php
    $stored_regular = $variation->get_regular_price( 'edit' );
    ```
    
    Do not mutate and save products from a price read filter. Filters can execute many times and during cache generation.
    
    ## Invalidation when rules change
    
    Changing callback code does not necessarily invalidate existing parent price arrays. Use one of these strategies:
    
    1. Add a bounded rules version to the variation price hash and increment it on settings migration/update.
    2. For known affected parents, call `wc_delete_product_transients( $parent_id )` after the rule changes.
    3. For a true store-wide semantic change, invalidate the WooCommerce product transient version deliberately, understanding the broad cache impact.
    
    Do not clear product transients on every frontend price read.
    
    ## When filters are the wrong model
    
    Use product CRUD and stored prices when the value is canonical for all shoppers and should drive reports, exports, REST responses, indexing, and admin screens. Use filters only for request-context pricing where every relevant read path is controlled.
    
    Cart/order totals capture prices at the transaction boundary. Revalidate entitlement during add-to-cart/checkout; a display filter alone is not protection against crafted requests or stale sessions.
    
    ## Test matrix
    
    Test simple and variable product displays, selected variation, cart, checkout, Store API, taxes inclusive/exclusive, VAT exemption, guest/each role, cache warm order (retail then wholesale and reverse), sale prices, currency context, and rule version changes.
    
    Inspect both:
    
    ```php
    $variation->get_price();
    $parent->get_variation_prices( false );
    $parent->get_variation_prices( true );
    ```
    
    ## Critical rules
    
    - Cover direct variation and parent aggregation paths.
    - Add all bounded pricing contexts to the hash.
    - Never use role names as capabilities accidentally.
    - Never use unbounded user/session/time data in the hash.
    - Return numeric values, not formatted HTML.
    - Keep filters side-effect free and non-recursive.
    - Revalidate price entitlement when creating cart/order values.
    
    ## Cross-references
    
    - `wc-variations-data` for stored variation CRUD and deferred parent sync.
    - `wc-cart-checkout-classic` for captured cart prices.
    
    ## References
    
    - Parent aggregation/cache hash: `includes/data-stores/class-wc-product-variable-data-store-cpt.php`.
    - Variation getter prefix: `includes/class-wc-product-variation.php` and `includes/abstracts/abstract-wc-data.php`.
    - Verified source paths:
      - `wp-content/plugins/woocommerce/includes/class-wc-product-variable.php`
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related