Created
October 27, 2026 15:21
-
-
Save xlplugins/48419ffe61c1886d1592f247ba12c02c to your computer and use it in GitHub Desktop.
Sublium : Order bump discount on the first payment only
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Plugin Name: Sublium - Order bump discount on the first payment only | |
| * Description: A Funnel Builder order bump discount on a subscription product applies to the first payment only. Every renewal, whatever the billing frequency, is charged at the full plan price. | |
| * Version: 1.0.0 | |
| * | |
| * Install as a must-use plugin (wp-content/mu-plugins/) or as a Code Snippets entry | |
| * (everything below the opening tag, run everywhere). | |
| * | |
| * How it works | |
| * - Sublium stores the discounted price on the subscription at purchase time and | |
| * rebuilds every renewal order from it, so the bump discount repeats forever. | |
| * - Funnel Builder tags the order line of a bump product with _bump_purchase = yes. | |
| * - At checkout this snippet also records the full (undiscounted) recurring line total | |
| * on that order line; Sublium copies the line meta onto the subscription item. | |
| * - When any renewal order is built, the line is reset to the full price before | |
| * WooCommerce calculates totals and taxes. | |
| * | |
| * Subscriptions bought before this snippet was installed have no recorded full price; | |
| * for those the full price is read from the plan at renewal time instead. | |
| */ | |
| if ( ! defined( 'ABSPATH' ) ) { | |
| exit; | |
| } | |
| /** | |
| * Full recurring line total (excluding tax) of a plan product, ignoring any bump discount. | |
| * | |
| * @param int $product_id Product (or variation) ID. | |
| * @param int $plan_id Sublium plan ID. | |
| * @param int $quantity Line quantity. | |
| * | |
| * @return float|null Null when the price cannot be worked out. | |
| */ | |
| function sublium_bump_first_payment_full_total( $product_id, $plan_id, $quantity ) { | |
| if ( ! class_exists( '\Sublium_WCS\Includes\Main\Plans' ) || empty( $plan_id ) ) { | |
| return null; | |
| } | |
| // A fresh product object: the one in the cart carries the bump price. | |
| $product = wc_get_product( $product_id ); | |
| if ( ! $product instanceof WC_Product ) { | |
| return null; | |
| } | |
| try { | |
| $plan = \Sublium_WCS\Includes\Main\Plans::get_plan_by_id( absint( $plan_id ), $product ); | |
| if ( empty( $plan ) || ! in_array( (int) $plan->get_type(), array( 1, 2, 3 ), true ) ) { | |
| return null; | |
| } | |
| $unit = (float) $plan->get_recurring_cart_price( (float) $product->get_price(), $product ); | |
| } catch ( Throwable $e ) { | |
| return null; | |
| } | |
| if ( $unit <= 0 ) { | |
| return null; | |
| } | |
| return (float) wc_get_price_excluding_tax( | |
| $product, | |
| array( | |
| 'price' => $unit, | |
| 'qty' => max( 1, (int) $quantity ), | |
| ) | |
| ); | |
| } | |
| /** | |
| * Remember the full recurring line total on the order line of a bump subscription product. | |
| * | |
| * @param WC_Order_Item_Product $item Order line. | |
| * @param string $cart_item_key Cart item key. | |
| * @param array $values Cart item. | |
| */ | |
| function sublium_bump_first_payment_record_full_total( $item, $cart_item_key, $values ) { | |
| unset( $cart_item_key ); | |
| if ( empty( $values['_wfob_product'] ) || empty( $values['sublium_wcs_plan'] ) ) { | |
| return; | |
| } | |
| $product_id = ! empty( $values['variation_id'] ) ? $values['variation_id'] : $values['product_id']; | |
| $total = sublium_bump_first_payment_full_total( $product_id, $values['sublium_wcs_plan'], isset( $values['quantity'] ) ? $values['quantity'] : 1 ); | |
| if ( null !== $total ) { | |
| $item->add_meta_data( '_sublium_bump_full_total', wc_format_decimal( $total ), true ); | |
| } | |
| } | |
| add_action( 'woocommerce_checkout_create_order_line_item', 'sublium_bump_first_payment_record_full_total', 20, 3 ); | |
| /** | |
| * Charge bump lines at the full plan price on every renewal. | |
| * | |
| * @param WC_Order $order Renewal order, before totals are calculated. | |
| */ | |
| function sublium_bump_first_payment_restore_full_price( $order ) { | |
| if ( ! $order instanceof WC_Order ) { | |
| return; | |
| } | |
| foreach ( $order->get_items() as $item ) { | |
| // Sublium writes the copied line meta straight to the database, so read it from there. | |
| $item_id = $item->get_id(); | |
| if ( 'yes' !== wc_get_order_item_meta( $item_id, '_bump_purchase', true ) ) { | |
| continue; | |
| } | |
| $full_total = wc_get_order_item_meta( $item_id, '_sublium_bump_full_total', true ); | |
| if ( '' === $full_total ) { | |
| $product_id = $item->get_variation_id() ? $item->get_variation_id() : $item->get_product_id(); | |
| $full_total = sublium_bump_first_payment_full_total( $product_id, wc_get_order_item_meta( $item_id, '_sublium_wcs_plan', true ), $item->get_quantity() ); | |
| } | |
| if ( null === $full_total || (float) $full_total <= 0 || (float) $full_total <= (float) $item->get_total() ) { | |
| continue; | |
| } | |
| $item->set_subtotal( $full_total ); | |
| $item->set_total( $full_total ); | |
| $item->save(); | |
| wc_update_order_item_meta( $item_id, '_sublium_bump_discount_ended', 'yes' ); | |
| } | |
| } | |
| add_action( 'sublium_wcs_renewal_order_before_calculate_totals', 'sublium_bump_first_payment_restore_full_price' ); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment