Skip to content

Commit 0c14697

Browse files
committed
fix(checkout): use original per-day rates for period-switch detection
Preserve $orig_old_price_per_day / $orig_new_price_per_day before search_for_same_period_plans() normalises the rates, then compare those originals in the $is_period_switch_to_shorter check. Previously the normalised (plan-variation) rates were used, which could mis-classify a period switch when the normalised rates happened to diverge from the true membership vs. cart rates. Also update test_get_billing_next_charge_date_null_membership_guard to actually exercise the null-membership guard: force cart_type to 'downgrade' via ReflectionProperty so the downgrade branch in get_billing_next_charge_date() is reached with a null membership, proving the guard prevents a fatal call to is_active() on null. Resolves #892
1 parent 458bc0c commit 0c14697

2 files changed

Lines changed: 23 additions & 9 deletions

File tree

inc/checkout/class-cart.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1319,6 +1319,14 @@ protected function build_from_membership($membership_id): bool {
13191319
$old_price_per_day = $days_in_old_cycle > 0 ? $membership->get_amount() / $days_in_old_cycle : $membership->get_amount();
13201320
$new_price_per_day = $days_in_new_cycle > 0 ? $this->get_recurring_total() / $days_in_new_cycle : $this->get_recurring_total();
13211321

1322+
/*
1323+
* Preserve the true per-day rates before search_for_same_period_plans()
1324+
* may normalise them. The shorter-period override must compare the raw
1325+
* membership vs. cart rates, not the normalised plan-variation rates.
1326+
*/
1327+
$orig_old_price_per_day = $old_price_per_day;
1328+
$orig_new_price_per_day = $new_price_per_day;
1329+
13221330
$is_same_product = $membership->get_plan_id() === $this->plan_id;
13231331

13241332
/**
@@ -1357,11 +1365,14 @@ protected function build_from_membership($membership_id): bool {
13571365
* fits this profile is treated as a scheduled downgrade regardless of
13581366
* whether the nominal per-period amounts suggest an "upgrade".
13591367
*
1368+
* We use the original rates ($orig_*) captured before search_for_same_period_plans()
1369+
* may have normalised them, so we compare the actual membership vs. cart rates.
1370+
*
13601371
* @since 2.6.2
13611372
*/
13621373
$is_period_switch_to_shorter = ! $membership->is_free()
13631374
&& $days_in_old_cycle > $days_in_new_cycle
1364-
&& $old_price_per_day < $new_price_per_day;
1375+
&& $orig_old_price_per_day < $orig_new_price_per_day;
13651376

13661377
/*
13671378
* If is the same product and the customer will start to pay less,

tests/WP_Ultimo/Checkout/Cart_Test.php

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3034,11 +3034,11 @@ public function test_yearly_plan_to_monthly_plan_different_products_is_downgrade
30343034
* null and cart_type happens to be 'downgrade' (defensive null guard).
30353035
*/
30363036
public function test_get_billing_next_charge_date_null_membership_guard() {
3037-
// Build a cart with cart_type=downgrade but no membership_id.
3038-
// The cart will default to type 'new' internally (no membership), but
3039-
// we can force the scenario by temporarily mocking; instead just verify
3040-
// that the guard at get_billing_next_charge_date does not throw on a
3041-
// null membership (regression test for the explicit null check added).
3037+
// Build a cart with no membership_id, then force cart_type to 'downgrade'
3038+
// via Reflection so the null-membership guard in get_billing_next_charge_date()
3039+
// is actually exercised. Before the guard was added this would fatal with
3040+
// "Call to member function is_active() on null" when cart_type was 'downgrade'
3041+
// and $this->membership was null.
30423042
$plan = $this->create_plan(['amount' => 30.00]);
30433043

30443044
$cart = new Cart([
@@ -3047,9 +3047,12 @@ public function test_get_billing_next_charge_date_null_membership_guard() {
30473047
'duration_unit' => 'month',
30483048
]);
30493049

3050-
// Call get_billing_next_charge_date() on a cart that has no membership.
3051-
// Before the fix this would fatal with "Call to member function is_active() on null"
3052-
// if cart_type was forced to 'downgrade'. With the null guard it must not throw.
3050+
// Force the cart into the downgrade branch while membership stays null.
3051+
$ref = new \ReflectionProperty(Cart::class, 'cart_type');
3052+
$ref->setAccessible(true);
3053+
$ref->setValue($cart, 'downgrade');
3054+
3055+
// Must return an int without throwing despite membership being null.
30533056
$this->assertIsInt($cart->get_billing_next_charge_date());
30543057

30553058
$plan->delete();

0 commit comments

Comments
 (0)