Skip to content

feat(payment-gated-subs): Apply activation rules on upgrade of a subscription#5533

Merged
osmarluz merged 9 commits into
mainfrom
feat/payment-gated-subs-3/apply-activation-rules-on-upgrades
May 26, 2026
Merged

feat(payment-gated-subs): Apply activation rules on upgrade of a subscription#5533
osmarluz merged 9 commits into
mainfrom
feat/payment-gated-subs-3/apply-activation-rules-on-upgrades

Conversation

@osmarluz
Copy link
Copy Markdown
Contributor

@osmarluz osmarluz commented May 19, 2026

Context

Part of the payment-gated subscriptions effort. Activation rules were already supported on subscription creation; this extends them to plan upgrades, so an upgrade can be gated until its rules (e.g. payment) resolve before the new plan takes effect.

Description

  • Subscriptions::ActivateService becomes the single entry point for all activations. It auto-detects an upgrade via subscription.previous_subscription (different plan, yearly_amount_cents ≥ the previous) and branches into a new activate_for_upgrade path that terminates the previous, marks the new active, emits fixed-charge events, fires subscription.started, and bills both with :upgrading.
  • Subscriptions::PlanUpgradeService#call collapses to pending! → optional ActivationRules::ApplyServiceActivateService.call!. Gated upgrades now follow the same gating path used at subscription creation.
  • Subscriptions::TerminateService#cancel_next_subscription early-returns when upgrade: true, so the upgrade target we just persisted isn't canceled mid-flow.

@osmarluz osmarluz requested a review from ancorcruz May 19, 2026 08:56
Copy link
Copy Markdown
Contributor

@ancorcruz ancorcruz left a comment

Choose a reason for hiding this comment

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

I'm worried about the gated + upgrade flow actually gets the previous subscription terminated... We should include in this PR a E2E scenario test to ensure the flow is ok. My concern is ActivateService#activate_from_incomplete only activates the subscription but not consider the upgrade branch as #activate_from_pending does... Is this left on purpose fo a next PR perhaps?

scenario test should live in spec/scenarios/subscriptions/payment_gated_activation_spec.rb and has something like...

  describe "plan upgrade with payment successful" do
    # 1) Create active subscription on a cheaper plan (no gating).
    # 2) Call create_subscription on a pricier plan with activation_rules.
    #    Assert: previous still active, new sub incomplete, invoice open,
    #            activation_rules.sole pending.
    # 3) simulate_stripe_webhook(status: "succeeded")
    #    Assert: previous terminated, new sub active, BillSubscriptionJob
    #            ran with :upgrading for [previous, new] (or [previous] for
    #            pay-in-arrears new plan), invoice finalized.
  end

  describe "plan upgrade with payment failure" do
    # Same setup; webhook returns "payment_failed".
    # Assert: new sub canceled, previous remains active and untouched.
  end

  describe "plan upgrade with timeout (rules unresolved)" do
    # Optional but valuable — covers what happens when nothing resolves
    # within timeout_hours and the gated upgrade ages out.
  end

Comment thread spec/services/subscriptions/plan_upgrade_service_spec.rb Outdated
Comment thread spec/services/subscriptions/activate_service_spec.rb Outdated
Comment thread spec/services/subscriptions/activate_service_spec.rb Outdated
Comment thread app/services/subscriptions/activate_service.rb Outdated
Comment thread app/services/subscriptions/activate_service.rb Outdated
Comment thread app/services/subscriptions/terminate_service.rb Outdated
Comment thread spec/services/subscriptions/activate_service_spec.rb
@osmarluz
Copy link
Copy Markdown
Contributor Author

I'm worried about the gated + upgrade flow actually gets the previous subscription terminated... We should include in this PR a E2E scenario test to ensure the flow is ok. My concern is ActivateService#activate_from_incomplete only activates the subscription but not consider the upgrade branch as #activate_from_pending does... Is this left on purpose fo a next PR perhaps?

scenario test should live in spec/scenarios/subscriptions/payment_gated_activation_spec.rb and has something like...

  describe "plan upgrade with payment successful" do
    # 1) Create active subscription on a cheaper plan (no gating).
    # 2) Call create_subscription on a pricier plan with activation_rules.
    #    Assert: previous still active, new sub incomplete, invoice open,
    #            activation_rules.sole pending.
    # 3) simulate_stripe_webhook(status: "succeeded")
    #    Assert: previous terminated, new sub active, BillSubscriptionJob
    #            ran with :upgrading for [previous, new] (or [previous] for
    #            pay-in-arrears new plan), invoice finalized.
  end

  describe "plan upgrade with payment failure" do
    # Same setup; webhook returns "payment_failed".
    # Assert: new sub canceled, previous remains active and untouched.
  end

  describe "plan upgrade with timeout (rules unresolved)" do
    # Optional but valuable — covers what happens when nothing resolves
    # within timeout_hours and the gated upgrade ages out.
  end

@ancorcruz yes, this is going to be handled in a separate PR so it's not too much work on just one PR. It's a separate bullet point on the dive-in

@osmarluz osmarluz merged commit 7aaec35 into main May 26, 2026
12 checks passed
@osmarluz osmarluz deleted the feat/payment-gated-subs-3/apply-activation-rules-on-upgrades branch May 26, 2026 14:54
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.

2 participants