Skip to content

feat(payment-gated-subs): Cover activation of subscriptions coming from upgrades and downgrades#5586

Open
osmarluz wants to merge 5 commits into
mainfrom
feat/payment-gated-subs-3/activate-subscriptions-from-upgrade-downgrade
Open

feat(payment-gated-subs): Cover activation of subscriptions coming from upgrades and downgrades#5586
osmarluz wants to merge 5 commits into
mainfrom
feat/payment-gated-subs-3/activate-subscriptions-from-upgrade-downgrade

Conversation

@osmarluz
Copy link
Copy Markdown
Contributor

@osmarluz osmarluz commented May 27, 2026

Context

Continues the payment-gated subscriptions work. #5533 gated upgrades only on the from-pending path, but a gated upgrade actually completes from the incomplete state once payment resolves, and downgrades reach activation through a different route entirely — a pending downgrade is rotated in at the next billing day via TerminateService#terminate_and_start_next. This PR brings upgrades and downgrades under the same activation path so either can be payment-gated.

Description

  • TerminateService#terminate_and_start_next now delegates the whole rotation to Subscriptions::ActivateService.call! instead of terminating, activating and billing inline, so a rotated-in downgrade runs through rule evaluation and can be payment-gated (failures raise so the job retries).
  • ActivateService gains a downgrade? path: the from-pending and from-incomplete flows route downgrades to a new activate_for_downgrade, which terminates the previous subscription and activates the new one, mirroring activate_for_upgrade — including the from-incomplete handling that skips re-emitting fixed-charge events and re-billing the already-gated new subscription.
  • Upgrade and downgrade billing share a single bill_rotation_subscriptions(billable_subscriptions, billing_at:) (non-invoiceable fees always target the previous subscription), and notify_started now creates the Hubspot record for downgraded subscriptions too.

@osmarluz osmarluz requested a review from ancorcruz May 27, 2026 11:24
# events emitted during gate_subscription — only the previous needs billing.
unless from_incomplete
emit_fixed_charge_events
billable_subscriptions << subscription if subscription.fixed_charges.pay_in_advance.any? || subscription.plan.pay_in_advance?
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

terminate_and_start_next on app/services/subscriptions/terminate_service.rb previously did:

# NOTE: Create an invoice for the terminated subscription
#       if it has not been billed yet
#       or only for the charges if subscription was billed in advance
#       Also, add new pay in advance plan inside if applicable
billable_subscriptions = if next_subscription.plan.pay_in_advance? || next_subscription.fixed_charges.pay_in_advance.any?
  [subscription, next_subscription]
else
  [subscription]
end

It's omitting the !subscription.in_trial_period? check and this is probably a bug, but the current behavior was kept unchanged

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