Skip to content

feat(saas): free-plan lookup, expired-trial scan, and downgradeToFree() - #141

Merged
pierredup merged 6 commits into
mainfrom
feature/auto-downgrade-expired-trials
Sep 15, 2026
Merged

pierredup merged 6 commits into
mainfrom
feature/auto-downgrade-expired-trials

Conversation

@pierredup

Copy link
Copy Markdown
Member

Summary

Adds the model-level building blocks for SolidInvoice's "auto-downgrade expired trials" feature. All changes are additive — no behavior change to existing methods.

What's included (src/Bundle/Saas)

  • PlanRepository::findFree() (+ interface) — resolves the Free plan by shape (price === 0 && planId === '0' && active) rather than the default flag, so a downgrade always lands on the actual free tier.
  • SubscriptionRepository::findExpiredTrials(DateTimeImmutable $now) (+ interface) — returns TRIAL subscriptions past their endDate, excluding externally-billed ones (subscriptionId null/empty). Provider-managed (on_trial-webhook) trials are therefore never auto-downgraded, so external billing is never silently bypassed.
  • SubscriptionManager::downgradeToFree(Subscription) — the single semantic operation behind the feature: resolve the free plan → changePlan (only when not already free) → activate. Throws NoFreePlanConfiguredException when no free plan exists (never downgrades onto a paid plan). Mirrors the existing "choose Free" flow and never invokes the payment integration.
  • NoFreePlanConfiguredException.

Testing

  • Unit (SubscriptionManagerDowngradeTest): changes plan + activates when not already free; activates without a plan change when already free; throws when no free plan is configured. 3/3 passing.
  • ECS clean; PHPStan Level Max clean on the changed files.

Consumed by

The SolidInvoice DowngradeExpiredTrialsCommand (hourly scheduled task) — see the SolidInvoice/SolidInvoice PR for feature/auto-downgrade-expired-trials. Merge this and composer update solidworx/platform there.

@coveralls

coveralls commented Jul 16, 2026 •

Copy link
Copy Markdown

Coverage Report for CI Build 34945889939

Warning

No base build found for commit 591ba13 on main.
Coverage changes can't be calculated without a base build.
If a base build is processing, this comment will update automatically when it completes.

Coverage: 32.034%

Details

  • Patch coverage: 32 uncovered changes across 4 files (0 of 32 lines covered, 0.0%).

Uncovered Changes

File Changed Covered %
src/Bundle/Saas/Repository/PlanRepository.php 11 0 0.0%
src/Bundle/Saas/Repository/SubscriptionRepository.php 10 0 0.0%
src/Bundle/Saas/Subscription/SubscriptionManager.php 7 0 0.0%
src/Bundle/Saas/Exception/NoFreePlanConfiguredException.php 4 0 0.0%

Coverage Regressions

Requires a base build to compare against. How to fix this →


Coverage Stats

Coverage Status
Relevant Lines: 4183
Covered Lines: 1340
Line Coverage: 32.03%
Coverage Strength: 2.62 hits per line

💛 - Coveralls

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds the core SaasBundle building blocks to support an “auto-downgrade expired trials” flow: resolving the configured free plan, querying for expired trials (excluding externally billed subscriptions), and performing a single semantic downgrade-to-free operation via SubscriptionManager.

Changes:

  • Add PlanRepository::findFree() (+ interface) to locate the active free tier by “free-plan shape” (price 0, planId "0").
  • Add SubscriptionRepository::findExpiredTrials(DateTimeImmutable $now) (+ interface) to list TRIAL subscriptions past endDate, excluding externally billed ones.
  • Add SubscriptionManager::downgradeToFree() and NoFreePlanConfiguredException, plus a unit test covering downgrade behavior.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/Bundle/Saas/Subscription/SubscriptionManagerDowngradeTest.php Adds unit coverage for the new downgradeToFree() operation.
src/Bundle/Saas/Subscription/SubscriptionManager.php Introduces downgradeToFree() to swap to free (if needed) and activate.
src/Bundle/Saas/Repository/PlanRepositoryInterface.php Defines the new findFree() repository contract.
src/Bundle/Saas/Repository/PlanRepository.php Implements findFree() query for the active free plan.
src/Bundle/Saas/Repository/SubscriptionRepositoryInterface.php Defines findExpiredTrials() contract for expired-trial lookup.
src/Bundle/Saas/Repository/SubscriptionRepository.php Implements findExpiredTrials() query excluding externally billed trials.
src/Bundle/Saas/Exception/NoFreePlanConfiguredException.php Adds a dedicated exception for “no free plan configured” downgrade attempts.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/Bundle/Saas/Subscription/SubscriptionManager.php Outdated
Comment thread src/Bundle/Saas/Exception/NoFreePlanConfiguredException.php
pierredup and others added 5 commits September 15, 2026 10:12
Auto-downgrade a subscription onto the free plan by resolving the free
plan, swapping it in (unless already free), and activating. Throws
NoFreePlanConfiguredException when no active free plan exists. This is
the single semantic operation behind both the manual "choose free"
flow and the upcoming expired-trial scheduler command.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@pierredup
pierredup force-pushed the feature/auto-downgrade-expired-trials branch from 9fd7419 to 32a9b1b Compare September 15, 2026 08:13
@pierredup
pierredup merged commit 7a436f4 into main Sep 15, 2026
10 checks passed
@pierredup
pierredup deleted the feature/auto-downgrade-expired-trials branch September 15, 2026 08:16
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.

3 participants