Skip to content

fix: allow trial for returning customers whose cancelled WCS sub had zero renewals#1061

Merged
superdav42 merged 1 commit into
mainfrom
bugfix/wcs-trial-eligibility-returning-customers
May 1, 2026
Merged

fix: allow trial for returning customers whose cancelled WCS sub had zero renewals#1061
superdav42 merged 1 commit into
mainfrom
bugfix/wcs-trial-eligibility-returning-customers

Conversation

@superdav42

@superdav42 superdav42 commented May 1, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Fixes ~14% of UM+WCS checkouts where _schedule_trial_end=0 and _schedule_next_payment is set to now+billing_period instead of now+trial_period, causing the first charge to fire immediately instead of after the advertised trial.
  • Real reported case: Sub #61444 (KURSO PRO, $99/mo, 15-day trial) — customer used 14 days and would never have been billed.

Root cause

has_trial() in inc/checkout/class-cart.php blocked trial eligibility for any customer with a cancelled WCS subscription whose parent WC order was completed — even when that subscription was cancelled due to a double-checkout or payment-gateway race and the customer never completed a billing cycle.

Failure chain

  1. Customer submits checkout form twice (double-click, page refresh, etc.)
  2. Two WCS subscriptions are created; UM/WCS cancels the first — parent WC order is completed
  3. Next valid checkout: has_trial() finds the cancelled sub → return false
  4. maybe_create_product gets $days_to_start = 0 → no _subscription_trial_length on WC product
  5. WCS creates subscription with _schedule_trial_end = 0 and next_payment = now+billing_period
  6. Customer is charged after one month instead of after the trial

The existing guard (added in 2.4.14) already skips cancelled subs whose parent order was not completed. This PR adds a second gate: also skip if the subscription had zero renewal orders, meaning it never completed a billing cycle.

Change

File: inc/checkout/class-cart.phphas_trial(), line ~2310

// After the existing parent-order guard:
if (method_exists($subscription, 'get_related_orders')) {
    $renewal_ids = $subscription->get_related_orders('ids', 'renewal');
    if (empty($renewal_ids)) {
        continue; // No renewals = never completed a billing cycle — allow trial.
    }
}

Verification

Reproduced and fixed in the dev WordPress environment:

Scenario Before After
Cancelled sub, completed parent, 0 renewals (error path) has_trial() = false has_trial() = true
Cancelled sub, completed parent, 1+ renewals (genuine returner) has_trial() = false has_trial() = false
Active / on-hold sub has_trial() = false has_trial() = false
Cancelled sub, incomplete parent (declined card) has_trial() = true has_trial() = true

Verified by running the exact subscription/cart logic directly in wp eval against a real cancelled+completed sub with 0 renewals (sub #73 in dev).

Impact

  • Fix is backward-compatible. No schema changes, no migrations.
  • Genuine returning customers (who completed at least one renewal payment) are still correctly blocked from getting another trial.
  • Customers whose checkout errored (double-checkout, race condition) are no longer penalised with lost trials.
  • The kp-trial-cancel-guard.php mu-plugin on Kursopro guards a related but distinct symptom — can be audited for removal once this and the sync_subscription_status guards (2.2.x) are confirmed stable in production.

aidevops.sh v3.13.78 plugin for OpenCode v1.3.17 with claude-sonnet-4-6 spent 1h 58m and 107,964 tokens on this with the user in an interactive session.

Summary by CodeRabbit

Release Notes

  • Bug Fixes
    • Enhanced trial eligibility evaluation for customers with cancelled subscriptions. Users may now qualify for trials when their cancelled subscription's parent order is completed or in certain statuses, improving trial access.

…zero renewals

Fixes ~14% of UM+WCS checkouts where _schedule_trial_end=0 and
_schedule_next_payment is set to now+billing_period instead of
now+trial_period, causing the first charge to fire immediately
rather than after the advertised trial.

Root cause: has_trial() blocked trial eligibility for any customer
with a cancelled WCS subscription whose parent WC order was completed
— even when that subscription was cancelled due to a double-checkout
or payment-gateway race and the customer never completed a billing
cycle.

Double-checkout path (seen in kp-trial-cancel-guard.php context):
1. Customer submits checkout form twice in quick succession
2. Two WCS subscriptions are created; UM/WCS cancels the first
3. The cancelled sub has a completed parent order
4. Next checkout: has_trial() finds the cancelled sub → returns false
5. WC product gets _subscription_trial_length=0
6. WCS schedules next_payment=now+billing_period instead of trial_end

Fix: for cancelled subs with a completed parent order, also require
at least one renewal order before blocking the trial. Zero renewals
means the subscription never completed a billing cycle and should not
count as prior subscription history.

Verified in dev (WordPress + WCS bundled):
- Sub: status=cancelled, parent_order=completed, renewal_orders=0
- Before fix: has_trial() = false (trial blocked, _schedule_trial_end=0)
- After fix:  has_trial() = true  (trial allowed, correct scheduling)
- Genuine returner with renewals: still correctly blocked
@coderabbitai

coderabbitai Bot commented May 1, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dac6d167-efee-41f6-9c95-a95f97dc8ace

📥 Commits

Reviewing files that changed from the base of the PR and between 695aa2c and 5ed6b1a.

📒 Files selected for processing (1)
  • inc/checkout/class-cart.php

📝 Walkthrough

Walkthrough

The has_trial() method in the cart class now applies refined logic for cancelled subscriptions. When a cancelled subscription's parent order has a completed, processing, or refunded status, the method checks for related renewal orders to determine trial eligibility rather than automatically blocking it.

Changes

Cohort / File(s) Summary
Trial Eligibility Logic
inc/checkout/class-cart.php
Modified has_trial() to conditionally inspect renewal orders when handling cancelled subscriptions with completed/processing/refunded parent orders. Trial is blocked only when renewal orders exist; allowed when renewal IDs are empty.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 A rabbit hops through cancelled tales,
Where trials bloom when renewal fails,
With renewed grace, the logic flows,
Eligibility where order-state grows! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and clearly describes the main fix: allowing trial eligibility for returning customers whose cancelled WCS subscriptions had zero renewals.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bugfix/wcs-trial-eligibility-returning-customers

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 60 minutes.

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented May 1, 2026

Copy link
Copy Markdown

🔨 Build Complete - Ready for Testing!

📦 Download Build Artifact (Recommended)

Download the zip build, upload to WordPress and test:

🌐 Test in WordPress Playground (Very Experimental)

Click the link below to instantly test this PR in your browser - no installation needed!
Playground support for multisite is very limitied, hopefully it will get better in the future.

🚀 Launch in Playground

Login credentials: admin / password

@github-actions

github-actions Bot commented May 1, 2026

Copy link
Copy Markdown

Performance Test Results

Performance test results for 0e929b4 are in 🛎️!

Note: the numbers in parentheses show the difference to the previous (baseline) test run. Differences below 2% or 0.5 in absolute values are not shown.

URL: /

Run DB Queries Memory Before Template Template WP Total LCP TTFB LCP - TTFB
0 41 (-5 / -12% ) 37.80 MB 811.50 ms (-87.00 ms / -11% ) 167.50 ms (+15.50 ms / +9% ) 1040.00 ms (-64.50 ms / -6% ) 1960.00 ms (-154.00 ms / -8% ) 1865.75 ms (-169.20 ms / -9% ) 88.40 ms (-1.85 ms / -2% )
1 56 49.10 MB 944.00 ms 139.50 ms (-3.50 ms / -3% ) 1086.50 ms 2068.00 ms 1990.05 ms 80.70 ms

@superdav42 superdav42 merged commit 413ee92 into main May 1, 2026
11 checks passed
@superdav42 superdav42 deleted the bugfix/wcs-trial-eligibility-returning-customers branch May 1, 2026 17:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant