From d50bccf4e5ed9f5769bff6ff9fa2a90f352a3219 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 9 Jul 2026 17:51:32 +0530 Subject: [PATCH] feat: reconcile api_and_webhooks entitlement from billing plan --- .../reconcile_plan_features_service.rb | 6 +++ .../accounts/internal_attributes_service.rb | 2 +- .../reconcile_plan_features_service_spec.rb | 54 +++++++++++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 spec/enterprise/services/enterprise/billing/reconcile_plan_features_service_spec.rb 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 205bc348e..2522682a4 100644 --- a/enterprise/app/services/enterprise/billing/reconcile_plan_features_service.rb +++ b/enterprise/app/services/enterprise/billing/reconcile_plan_features_service.rb @@ -37,8 +37,10 @@ class Enterprise::Billing::ReconcilePlanFeaturesService def perform account.disable_features(*PREMIUM_PLAN_FEATURES) account.disable_features('captain_integration_v2') if default_plan? + account.disable_features('api_and_webhooks') account.enable_features(*current_plan_features) account.enable_features('captain_integration_v2') if captain_v2_default_eligible? + account.enable_features('api_and_webhooks') if api_and_webhooks_eligible? account.enable_features(*manually_managed_features) account.save! end @@ -56,6 +58,10 @@ class Enterprise::Billing::ReconcilePlanFeaturesService end end + def api_and_webhooks_eligible? + !default_plan? && account.custom_attributes['subscription_status'] == 'active' + end + def default_plan? default_plan_name = cloud_plans.first&.dig('name') return false if default_plan_name.blank? diff --git a/enterprise/app/services/internal/accounts/internal_attributes_service.rb b/enterprise/app/services/internal/accounts/internal_attributes_service.rb index 593cea799..00c3d3636 100644 --- a/enterprise/app/services/internal/accounts/internal_attributes_service.rb +++ b/enterprise/app/services/internal/accounts/internal_attributes_service.rb @@ -54,7 +54,7 @@ class Internal::Accounts::InternalAttributesService def valid_feature_list Enterprise::Billing::ReconcilePlanFeaturesService::BUSINESS_PLAN_FEATURES + Enterprise::Billing::ReconcilePlanFeaturesService::ENTERPRISE_PLAN_FEATURES + - %w[inbound_emails] + %w[inbound_emails api_and_webhooks] end # Account notes functionality removed for now diff --git a/spec/enterprise/services/enterprise/billing/reconcile_plan_features_service_spec.rb b/spec/enterprise/services/enterprise/billing/reconcile_plan_features_service_spec.rb new file mode 100644 index 000000000..471ec2a4d --- /dev/null +++ b/spec/enterprise/services/enterprise/billing/reconcile_plan_features_service_spec.rb @@ -0,0 +1,54 @@ +require 'rails_helper' + +describe Enterprise::Billing::ReconcilePlanFeaturesService do + let(:account) { create(:account) } + + before do + create(:installation_config, { + name: 'CHATWOOT_CLOUD_PLANS', + value: [ + { 'name' => 'Hacker', 'product_id' => ['plan_id_hacker'], 'price_ids' => ['price_hacker'] }, + { 'name' => 'Startups', 'product_id' => ['plan_id_startups'], 'price_ids' => ['price_startups'] } + ] + }) + end + + describe '#perform' do + context 'with api_and_webhooks feature' do + it 'enables the feature for a paid plan with an active subscription' do + account.update!(custom_attributes: { 'plan_name' => 'Startups', 'subscription_status' => 'active' }) + + described_class.new(account: account).perform + + expect(account.reload).to be_feature_enabled('api_and_webhooks') + end + + it 'disables the feature for a paid plan on trial' do + account.enable_features!('api_and_webhooks') + account.update!(custom_attributes: { 'plan_name' => 'Startups', 'subscription_status' => 'trialing' }) + + described_class.new(account: account).perform + + expect(account.reload).not_to be_feature_enabled('api_and_webhooks') + end + + it 'disables the feature on the default plan' do + account.enable_features!('api_and_webhooks') + account.update!(custom_attributes: { 'plan_name' => 'Hacker', 'subscription_status' => 'active' }) + + described_class.new(account: account).perform + + expect(account.reload).not_to be_feature_enabled('api_and_webhooks') + end + + it 'keeps the feature enabled when manually managed' do + account.update!(custom_attributes: { 'plan_name' => 'Hacker', 'subscription_status' => 'trialing' }) + Internal::Accounts::InternalAttributesService.new(account).manually_managed_features = ['api_and_webhooks'] + + described_class.new(account: account).perform + + expect(account.reload).to be_feature_enabled('api_and_webhooks') + end + end + end +end