Skip to content

Switch between monthly and yearly from the workspace page - #94

Merged
magweter merged 11 commits into
devfrom
feature/yearly-switch
Nov 11, 2026
Merged

magweter merged 11 commits into
devfrom
feature/yearly-switch

Conversation

@magweter

Copy link
Copy Markdown
Owner

What

Owners and admins can switch a workspace between monthly and yearly billing themselves, without manual work from us.

  • Monthly to yearly: a fresh Lemon Squeezy checkout for the yearly variant of a separate no-trial product, for the current usage at the current list price. The customer is present to pay, so 3D Secure happens on the spot instead of an off-session charge that can be declined. The checkout link expires after an hour and hides the discount field. After a successful payment, the subscription_created webhook cancels the superseded monthly subscription. An abandoned checkout changes nothing.
  • During the year: the usage push and the hourly reconciliation leave yearly quantities alone. app:process-yearly-subscriptions runs daily:
    • On the monthly anniversary it invoices extra usage immediately for the rest of the year. Lower usage is never credited.
    • In the 3 days before renewal it sets the renewal quantity without proration, retried daily.
    • It retries cancelling a monthly subscription the yearly one replaced.
  • Yearly to monthly: the customer requests it, and can take it back until it happens. On the last day of the paid year the quantity is set first, then the subscription is swapped to the monthly variant.
  • Workspace page: a "Billed monthly / yearly" label, plus the switch controls for owners and admins.
  • Dashboard: coming back from a switch shows a confirmation and does not fire the purchase conversion.
  • Report: app:report-yearly-subscriptions is read-only and lists yearly subscriptions whose Lemon Squeezy quantity drifted from usage.

Configuration

New env vars, documented in config/settings.php:

  • CLOUD_HOSTED_PRO_SWITCH_PRODUCT_ID
  • CLOUD_HOSTED_PRO_SWITCH_MONTHLY_VARIANT_ID
  • CLOUD_HOSTED_PRO_SWITCH_YEARLY_VARIANT_ID
  • CLOUD_HOSTED_PRO_YEARLY_VARIANT_IDS

The switch controls stay hidden until the three switch ids are set.

Verified in Lemon Squeezy test mode

  • Checkout with quantity, the webhook and cancelling the monthly subscription all work.
  • The anniversary true-up with invoice_immediately bills immediately.
  • A variant swap from yearly to monthly starts and invoices a monthly cycle immediately, even with disable_prorations. That is why the swap runs only on the last day of the paid year, after the quantity is set.

Tests

Full suite passes (468 tests).

🤖 Generated with Claude Code

magweter and others added 11 commits November 1, 2026 22:42
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…enewals

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ch checkout

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…aid day

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Nov 11, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: ab5b5c08-b093-49b9-9555-c3f4df2de183

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@magweter
magweter merged commit ca99055 into dev Nov 11, 2026
7 checks passed
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