From b05f22da8df103a349c3e820afcb76acb129692a Mon Sep 17 00:00:00 2001 From: Aakash Bakhle <48802744+aakashb95@users.noreply.github.com> Date: Fri, 31 Jul 2026 11:51:00 +0530 Subject: [PATCH] fix(captain): default paid accounts to V2 (#15262) Paid Chatwoot Cloud accounts now receive Captain V2 during plan reconciliation unless they are explicitly held on Captain V1. Older accounts could otherwise start on V1 when they became paid after the V2 rollout because only newly created accounts carried the rollout eligibility value. ## Closes No linked issue. ## How to reproduce 1. Start with a cloud account created before the Captain V2 rollout. 2. Upgrade the account from the default plan to a paid plan. 3. Reconcile the Stripe subscription. 4. Confirm that Captain is enabled but Captain V2 remains disabled. ## What changed 1. Treat a missing rollout eligibility value as eligible for Captain V2 on paid plans. 2. Keep Captain V2 disabled when the rollout eligibility value is explicitly set to false. 3. Keep the default plan behavior unchanged. 4. Update the billing reconciliation specs to cover existing paid accounts, new accounts, and explicit V1 exceptions. --- enterprise/app/models/enterprise/account.rb | 6 +++--- .../reconcile_plan_features_service.rb | 4 ++-- .../handle_stripe_event_service_spec.rb | 19 +++++++++++++++++-- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/enterprise/app/models/enterprise/account.rb b/enterprise/app/models/enterprise/account.rb index eda28712f..6adc7cc48 100644 --- a/enterprise/app/models/enterprise/account.rb +++ b/enterprise/app/models/enterprise/account.rb @@ -1,7 +1,7 @@ module Enterprise::Account - # Transitional marker for the Captain V1 to V2 rollout. New cloud accounts get - # this marker so plan reconciliation can enable V2 for them without upgrading - # existing paid accounts. Remove once every account is migrated to V2. + # Transitional marker for the Captain V1 to V2 rollout. Set this to false only + # for accounts that must remain on V1 during paid plan reconciliation. + # Remove once every account is migrated to V2. CAPTAIN_V2_DEFAULT_ELIGIBLE = 'captain_v2_default_eligible'.freeze class << self diff --git a/enterprise/app/services/enterprise/billing/reconcile_plan_features_service.rb b/enterprise/app/services/enterprise/billing/reconcile_plan_features_service.rb index 435b1f3d1..119fa3ea0 100644 --- a/enterprise/app/services/enterprise/billing/reconcile_plan_features_service.rb +++ b/enterprise/app/services/enterprise/billing/reconcile_plan_features_service.rb @@ -37,7 +37,7 @@ class Enterprise::Billing::ReconcilePlanFeaturesService def perform account.disable_features(*PREMIUM_PLAN_FEATURES) - account.disable_features('captain_integration_v2') if default_plan? + account.disable_features('captain_integration_v2') account.enable_features(*current_plan_features) account.enable_features('captain_integration_v2') if captain_v2_default_eligible? account.enable_features(*manually_managed_features) @@ -74,6 +74,6 @@ class Enterprise::Billing::ReconcilePlanFeaturesService end def captain_v2_default_eligible? - !default_plan? && account.internal_attributes[Enterprise::Account::CAPTAIN_V2_DEFAULT_ELIGIBLE] == true + !default_plan? && account.internal_attributes[Enterprise::Account::CAPTAIN_V2_DEFAULT_ELIGIBLE] != false end end diff --git a/spec/enterprise/services/enterprise/billing/handle_stripe_event_service_spec.rb b/spec/enterprise/services/enterprise/billing/handle_stripe_event_service_spec.rb index 5116e1ec3..080bd184c 100644 --- a/spec/enterprise/services/enterprise/billing/handle_stripe_event_service_spec.rb +++ b/spec/enterprise/services/enterprise/billing/handle_stripe_event_service_spec.rb @@ -221,13 +221,13 @@ describe Enterprise::Billing::HandleStripeEventService do end end - it 'does not enable Captain V2 for existing paid accounts during reconciliation' do + it 'enables Captain V2 for existing paid accounts during reconciliation' do allow(subscription).to receive(:[]).with('plan') .and_return({ 'id' => 'test', 'product' => 'plan_id_startups', 'name' => 'Startups' }) stripe_event_service.new.perform(event: event) - expect(account.reload).not_to be_feature_enabled('captain_integration_v2') + expect(account.reload).to be_feature_enabled('captain_integration_v2') end it 'enables Captain V2 for new cloud accounts marked as default eligible' do @@ -243,6 +243,21 @@ describe Enterprise::Billing::HandleStripeEventService do expect(account.reload).to be_feature_enabled('captain_integration_v2') end + + it 'disables Captain V2 for accounts explicitly held on V1' do + account.enable_features!('captain_integration_v2') + account.update!( + internal_attributes: account.internal_attributes.merge( + Enterprise::Account::CAPTAIN_V2_DEFAULT_ELIGIBLE => false + ) + ) + allow(subscription).to receive(:[]).with('plan') + .and_return({ 'id' => 'test', 'product' => 'plan_id_startups', 'name' => 'Startups' }) + + stripe_event_service.new.perform(event: event) + + expect(account.reload).not_to be_feature_enabled('captain_integration_v2') + end end context 'with Business plan' do