Skip to content

Instantly share code, notes, and snippets.

@xlplugins
Created July 15, 2025 07:10
Show Gist options
  • Save xlplugins/7f6dee957d0f4daf379cfa7d83cae73b to your computer and use it in GitHub Desktop.
Save xlplugins/7f6dee957d0f4daf379cfa7d83cae73b to your computer and use it in GitHub Desktop.
Funnelkit Checkout: Conflict with WooCommerce Fees and Discounts and PayPal for WooCommerce
class WCFAD_Dynamic_Pricing {
public function __construct() {
add_action( 'wfacp_before_process_checkout_template_loader', [ $this, 'remove_actions' ] );
add_action( 'wfacp_after_checkout_page_found', [ $this, 'remove_actions' ] );
}
public function remove_actions() {
remove_action( 'woocommerce_before_calculate_totals', 'wcfad_before_calculate_totals', 2, 1 );
add_action( 'woocommerce_before_calculate_totals',[$this, 'wcfad_before_calculate_totals'],3 );
}
public function wcfad_before_calculate_totals( $cart_obj = false ) {
if( is_admin() && ! defined( 'DOING_AJAX' ) ) {
return;
}
if( did_action( 'woocommerce_before_calculate_totals' ) >= 4 ) {
return;
}
// Check that we have at least one pricing rule
// $rules = get_option( 'wcfad_dynamic_pricing_rules', array() );
$rules = wcfad_get_dynamic_pricing_rules();
if ( ! $cart_obj ) {
// this is false in a mini cart, so create it
$cart_obj = WC()->cart;
}
//$cart = WC()->cart->get_cart();
$cart = $cart_obj->get_cart(); // since 1.9.2, let's try to use $cart_obj
// Check the total
$cart_total = 0;
// Use when deciding what notice to display
$cart_total_before_discount = 0;
// Use this to check if we've got an order total rule
$order_total_rule = false;
foreach( $cart as $cart_item_key=>$cart_item ) {
if ( 'yes' === wcfad_is_role_pricing_enabled() && empty( $cart_item['product_extras'] ) && apply_filters( 'wcfad_disable_user_role_pricing_before_calculate_totals', true ) ) {
// 2.0.3, the following are needed by order total rules
$quantity = $cart_item['quantity'];
$price = $cart_item['data']->get_price();
$cart_total += ( $price * $quantity );
$cart_total_before_discount += ( $price * $quantity ) ;
continue; // skip
// 2.0.3: $cart_item['data']->get_price() also triggers the filter woocommerce_product_get_price(), which causes double adjustments for user-role pricing. We only proceed if cart item has AOU
}
$product_id = $cart_item['data']->get_id();
$add_ons = 0;
// Check for any role based pricing rules, provided the price hasn't been set by a calculation field
if( isset( $cart_item['product_extras']['original_price'] ) && empty( $cart_item['product_extras']['use_calc_set_price'] ) ) {
remove_action( 'woocommerce_before_calculate_totals', 'pewc_wc_calculate_total', 10, 1 );
// Added in 1.8.6 to prevent discounts being doubled for global user role rules
// Commented out in 1.9.2 because after removing the actions, products with no add-ons lose their user-role pricing
//remove_action( 'woocommerce_product_get_price', 'wcfad_get_regular_price', 10, 2 );
//remove_action( 'woocommerce_product_variation_get_price', 'wcfad_get_regular_price', 10, 2 );
// Since 1.9.2, this is used in wcfad_get_regular_price()
$cart_item['data']->update_meta_data( 'wcfad_has_aou', 'yes' );
// If add-ons are present, just apply the discount to the base price
$price = $cart_item['product_extras']['original_price'];
// Split the base product price from the add-on costs
$add_ons = floatval( $cart_item['product_extras']['price_with_extras'] ) - floatval( $cart_item['product_extras']['original_price'] );
// Since 1.9.3, to be used later in bulk discounts, e.g. wcfad_set_qualifying_bulk_product_meta()
$cart_item['data']->update_meta_data( 'wcfad_add_on_price_only', $add_ons );
// the line below might not be needed
//$add_ons = wc_get_price_excluding_tax($cart_item['data'], array('price'=>$add_ons, 'quantity'=>1));
} else {
// Remove this filter so that we don't double the adjustment
remove_filter( 'woocommerce_product_get_price', 'wcfad_get_regular_price', 10, 2 );
remove_filter( 'woocommerce_product_variation_get_price', 'wcfad_get_regular_price', 10, 2 );
$price = $cart_item['data']->get_price();
// if below is not commented out, line items are correct, total is incorrect
// total is only incorrect if a product has dynamic pricing -> fees
add_filter( 'woocommerce_product_get_price', 'wcfad_get_regular_price', 10, 2 );
add_filter( 'woocommerce_product_variation_get_price', 'wcfad_get_regular_price', 10, 2 );
}
if( ! empty( $cart_item['product_extras']['use_calc_set_price'] ) && isset( $cart_item['product_extras']['price_with_extras'] ) ) {
// added in 1.8.9 so that the discounted calc price is not overwritten when totals are calculated
remove_action( 'woocommerce_before_calculate_totals', 'pewc_wc_calculate_total', 10, 1 );
// Ensure the price is set to the calculated price in the cart
// Commented out in 1.9.2 because after removing the actions, products with no add-ons lose their user-role pricing
//remove_filter( 'woocommerce_product_get_price', 'wcfad_get_regular_price', 10, 2 );
//remove_filter( 'woocommerce_product_variation_get_price', 'wcfad_get_regular_price', 10, 2 );
// Since 1.9.2, this is used in wcfad_get_regular_price()
$cart_item['data']->update_meta_data( 'wcfad_has_aou', 'yes' );
$price = $cart_item['product_extras']['price_with_extras'];
}
if( ! empty( $cart_item['product_extras']['use_calc_set_price'] ) ) {
// Ensure the price is set to the calculated price in the cart
// Commented out on 1.8.9 because this puts back the original price and not the calculated price
/*remove_filter( 'woocommerce_product_get_price', 'wcfad_get_regular_price', 10, 2 );
remove_filter( 'woocommerce_product_variation_get_price', 'wcfad_get_regular_price', 10, 2 );
$price = $cart_item['data']->get_price();*/
}
// Check for role-based price - the normal filters for this are removed above
$price = wcfad_get_regular_price( $price, $cart_item['data'] );
$best_price = floatval( $price );
// Add the add-on fields back on to the discount product price
$best_price += floatval( $add_ons );
// Set the product price to the best price set according to user role
$cart_item['data']->set_price( $best_price );
// Reset all items back to original price before we validate each rule
if( ! $add_ons && empty( $cart_item['product_extras']['use_calc_set_price'] ) && $cart_item['data']->get_meta( 'wcfad_original_price' ) ) {
// Set each product back to its original price
$original_price = $cart_item['data']->get_meta( 'wcfad_original_price' );
// Reset all items back to original price
// Do we need to set this to the min of $original_price and $best_price?
// 2.0.3, added wcfad_set_min_price_before_rule_validation filter so that prices with user-role pricing fees can be displayed correctly in the cart. Issue only happens with AOU
if ( apply_filters( 'wcfad_set_min_price_before_rule_validation', false ) ) {
$cart_item['data']->set_price( min( $best_price, $original_price ) );
}
$cart_item['data']->update_meta_data( 'wcfad_rule_set', 0 );
$cart_item['data']->update_meta_data( 'wcfad_get_qualifies', 0 );
$cart_item['data']->update_meta_data( 'wcfad_original_price', 0 );
$cart_item['data']->update_meta_data( 'wcfad_original_total', 0 );
$cart_item['data']->update_meta_data( 'wcfad_free_items', 0 );
$cart_item['data']->update_meta_data( 'wcfad_paid_items', 0 );
$cart_item['data']->update_meta_data( 'wcfad_discounted_total', 0 );
$cart_item['data']->update_meta_data( 'wcfad_discounted_price', 0 );
} else {
// $check_price = $cart_item['data']->get_price();
}
// $price = $value['data']->get_price();
$quantity = $cart_item['quantity'];
$cart_total += ( $best_price * $quantity );
$cart_total_before_discount += ( $best_price * $quantity ) ;
}
// Create an array of notices
// $rule_id = array( $product_id => $quantity );
$notices = array();
if( wcfad_is_dynamic_pricing_enabled() == 'yes' ) {
$user_roles = wcfad_get_current_user_roles();
foreach( $rules as $rule ) {
// For 2.0, need to get the rule formatted as in previous versions
$rule = wcfad_get_dynamic_pricing_rule_by_id( $rule );
$rule_id = ! empty( $rule['rule_id'] ) ? $rule['rule_id'] : false;
$validation_function = '';
// Iterate through each rule
if( ! empty( $rule['rule'] ) && wcfad_is_rule_active( $rule ) ) {
// 2.0.3. Check for roles
$roles = isset( $rule['roles'] ) ? $rule['roles'] : array();
if( $roles ) {
if( ! is_user_logged_in() ) {
// If there's a role defined for this and the user is logged out, then the rule can't apply
continue;
}
$has_role = wcfad_is_user_role_included( $user_roles, $rule['roles'] );
if( empty( $has_role ) ) {
continue;
}
}
$rule_type = $rule['rule'];
if( $rule_type == 'order' ) {
$order_total_rule = $rule;
} else {
$validation_function = 'wcfad_validate_rule_' . $rule_type;
if( $validation_function && function_exists( $validation_function ) ) {
$notices[$rule_id] = call_user_func( $validation_function, $rule, $cart, $rule_id );
}
}
}
}
// 2.0.4, added ! defined( 'DOING_AJAX' ) to avoid duplicate notices
if( $notices && ! defined( 'DOING_AJAX' ) ) {
wc_clear_notices();
foreach( $notices as $rule_id=>$notice ) {
if( empty( $notices[$rule_id] ) ) {
continue;
}
$rule = wcfad_get_dynamic_pricing_rule_by_id( $rule_id );
$count_by = ! empty( $rule['count_by'] ) ? $rule['count_by'] : false;
if( $count_by == 'all' ) {
// Get the combined amounts/quantities for this cart-wide rule
$combined_total = 0; // Could be quantity or value
if( $notice ) {
foreach( $notice as $value ) {
$combined_total += $value;
}
}
// Because this is cart-wide, the notices function will get the product IDs from the rule - we just need to pass the combined total
wcfad_do_notice( $rule, array( '1' => $combined_total ) );
} else {
// Do each line item in this rule individually
if( $notice ) {
foreach( $notice as $value ) {
wcfad_do_notice( $rule, $notice );
}
}
}
}
}
// Check if we've got an order total rule
if( $order_total_rule && did_action( 'woocommerce_calculate_totals' ) < 2 ) {
$tier = wcfad_get_matching_tier( $order_total_rule, $cart, $cart_total );
$adjustment = ! empty( $tier['type'] ) ? $tier['type'] : '';
$amount = ! empty( $tier['amount'] ) ? $tier['amount'] : '';
$label = ! empty( $tier['label'] ) ? $tier['label'] : '';
if( strpos( $adjustment, 'percentage' ) > -1 && $amount ) {
// Adjust each line item
foreach( $cart_obj->get_cart() as $hash=>$value ) {
$original_price = $adjusted_price = $value['data']->get_price();
if( $adjustment == 'percentage-discount' ) {
if ($original_price <= 0)
continue; // only do this for positive prices
$adjusted_price = $original_price - ( $original_price * $amount/100 );
$value['data']->set_price( $adjusted_price );
} else if( $adjustment == 'percentage-fee' ) {
$adjusted_price = $original_price + ( $original_price * $amount/100 );
$value['data']->set_price( $adjusted_price );
}
// we do the following in case there are no other adjustments yet, so there's an indication that there's a discount
// but if there's already an adjustment, it takes the adjusted price and adjusts it again
$value['data']->update_meta_data( 'wcfad_adjusted_items', $value['quantity'] );
$value['data']->update_meta_data( 'wcfad_original_price', $original_price );
$value['data']->update_meta_data( 'wcfad_original_total', $original_price * $value['quantity'] );
$value['data']->update_meta_data( 'wcfad_adjusted_price', $adjusted_price );
$value['data']->update_meta_data( 'wcfad_line_total', $adjusted_price * $value['quantity'] );
// if there was an adjustment already, get the previous label, then add the order total label
$wcfad_label = $value['data']->get_meta( 'wcfad_label' );
if ( $wcfad_label != '' )
$wcfad_label .= ', ';
if ( $label != '' ) {
// use tier label if available
$value['data']->update_meta_data( 'wcfad_label', $wcfad_label . $label );
}
else if ( $order_total_rule['label'] != '' ) {
// use rule label if tier label is not available
$value['data']->update_meta_data( 'wcfad_label', $wcfad_label . $order_total_rule['label'] );
}
}
}
if( wcfad_is_order_notice_active() == 'yes' ) {
// Let the user know what they need to add to qualify for the next best tier
if( $order_total_rule['tiers'] ) {
wcfad_do_order_notice( $order_total_rule, $cart_total, $cart_total_before_discount );
}
}
}
}
}
public function is_enable() {
return function_exists('wcfad_before_calculate_totals');
}
}
new WCFAD_Dynamic_Pricing();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment