From 3051da1e441325fb19720c4e397bc008e977d34e Mon Sep 17 00:00:00 2001 From: Tanmay Deep Sharma <32020192+tds-1@users.noreply.github.com> Date: Mon, 8 Dec 2025 18:35:33 +0530 Subject: [PATCH] fix: add renewal identification for credit flow (#12999) --- .../billing/handle_stripe_event_service.rb | 79 +++++++++++++------ .../handle_stripe_event_service_spec.rb | 7 +- 2 files changed, 59 insertions(+), 27 deletions(-) diff --git a/enterprise/app/services/enterprise/billing/handle_stripe_event_service.rb b/enterprise/app/services/enterprise/billing/handle_stripe_event_service.rb index ecbc481a4..390786ec8 100644 --- a/enterprise/app/services/enterprise/billing/handle_stripe_event_service.rb +++ b/enterprise/app/services/enterprise/billing/handle_stripe_event_service.rb @@ -50,15 +50,19 @@ class Enterprise::Billing::HandleStripeEventService previous_usage = capture_previous_usage update_account_attributes(subscription, plan) update_plan_features - handle_subscription_credits(plan, previous_usage) - account.reset_response_usage + + if billing_period_renewed? + ActiveRecord::Base.transaction do + handle_subscription_credits(plan, previous_usage) + account.reset_response_usage + end + elsif plan_changed? + handle_plan_change_credits(plan, previous_usage) + end end def capture_previous_usage - { - responses: account.custom_attributes['captain_responses_usage'].to_i, - monthly: current_plan_credits[:responses] - } + { responses: account.custom_attributes['captain_responses_usage'].to_i, monthly: current_plan_credits[:responses] } end def current_plan_credits @@ -71,15 +75,15 @@ class Enterprise::Billing::HandleStripeEventService def update_account_attributes(subscription, plan) # https://stripe.com/docs/api/subscriptions/object account.update( - custom_attributes: { - stripe_customer_id: subscription.customer, - stripe_price_id: subscription['plan']['id'], - stripe_product_id: subscription['plan']['product'], - plan_name: plan['name'], - subscribed_quantity: subscription['quantity'], - subscription_status: subscription['status'], - subscription_ends_on: Time.zone.at(subscription['current_period_end']) - } + custom_attributes: account.custom_attributes.merge( + 'stripe_customer_id' => subscription.customer, + 'stripe_price_id' => subscription['plan']['id'], + 'stripe_product_id' => subscription['plan']['product'], + 'plan_name' => plan['name'], + 'subscribed_quantity' => subscription['quantity'], + 'subscription_status' => subscription['status'], + 'subscription_ends_on' => Time.zone.at(subscription['current_period_end']) + ) ) end @@ -131,6 +135,18 @@ class Enterprise::Billing::HandleStripeEventService account.update!(limits: current_limits.merge('captain_responses' => updated_credits)) end + def handle_plan_change_credits(new_plan, previous_usage) + current_limits = account.limits || {} + current_credits = current_limits['captain_responses'].to_i + + previous_plan_credits = previous_usage[:monthly] + new_plan_credits = get_plan_credits(new_plan['name'])[:responses] + + updated_credits = current_credits - previous_plan_credits + new_plan_credits + + account.update!(limits: current_limits.merge('captain_responses' => updated_credits)) + end + def get_plan_credits(plan_name) config = InstallationConfig.find_by(name: CAPTAIN_CLOUD_PLAN_LIMITS).value config = JSON.parse(config) if config.is_a?(String) @@ -141,20 +157,12 @@ class Enterprise::Billing::HandleStripeEventService plan_name = account.custom_attributes['plan_name'] return if plan_name.blank? - # Enable features based on plan hierarchy case plan_name - when 'Startups' - # Startups plan gets the basic features - account.enable_features(*STARTUP_PLAN_FEATURES) + when 'Startups' then account.enable_features(*STARTUP_PLAN_FEATURES) when 'Business' - # Business plan gets Startups features + Business features - account.enable_features(*STARTUP_PLAN_FEATURES) - account.enable_features(*BUSINESS_PLAN_FEATURES) + account.enable_features(*STARTUP_PLAN_FEATURES, *BUSINESS_PLAN_FEATURES) when 'Enterprise' - # Enterprise plan gets all features - account.enable_features(*STARTUP_PLAN_FEATURES) - account.enable_features(*BUSINESS_PLAN_FEATURES) - account.enable_features(*ENTERPRISE_PLAN_FEATURES) + account.enable_features(*STARTUP_PLAN_FEATURES, *BUSINESS_PLAN_FEATURES, *ENTERPRISE_PLAN_FEATURES) end end @@ -162,6 +170,25 @@ class Enterprise::Billing::HandleStripeEventService @subscription ||= @event.data.object end + def previous_attributes + @previous_attributes ||= JSON.parse((@event.data.previous_attributes || {}).to_json) + end + + def plan_changed? + return false if previous_attributes['plan'].blank? + + previous_plan_id = previous_attributes.dig('plan', 'id') + current_plan_id = subscription['plan']['id'] + + previous_plan_id != current_plan_id + end + + def billing_period_renewed? + return false if previous_attributes['current_period_start'].blank? + + previous_attributes['current_period_start'] != subscription['current_period_start'] + end + def account @account ||= Account.where("custom_attributes->>'stripe_customer_id' = ?", subscription.customer).first 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 db029ad0f..0f8ce1494 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 @@ -32,6 +32,7 @@ describe Enterprise::Billing::HandleStripeEventService do # Setup common subscription mocks allow(event).to receive(:data).and_return(data) allow(data).to receive(:object).and_return(subscription) + allow(data).to receive(:previous_attributes).and_return({}) allow(subscription).to receive(:[]).with('quantity').and_return('10') allow(subscription).to receive(:[]).with('status').and_return('active') allow(subscription).to receive(:[]).with('current_period_end').and_return(1_686_567_520) @@ -62,7 +63,7 @@ describe Enterprise::Billing::HandleStripeEventService do expect(account).not_to be_feature_enabled('audit_logs') end - it 'resets captain usage on subscription update' do + it 'resets captain usage on billing period renewal' do # Prime the account with some usage 5.times { account.increment_response_usage } expect(account.custom_attributes['captain_responses_usage']).to eq(5) @@ -70,6 +71,10 @@ describe Enterprise::Billing::HandleStripeEventService do # Setup for any plan allow(subscription).to receive(:[]).with('plan') .and_return({ 'id' => 'test', 'product' => 'plan_id_startups', 'name' => 'Startups' }) + allow(subscription).to receive(:[]).with('current_period_start').and_return(1_686_567_520) + + # Simulate billing period renewal with previous_attributes showing old period + allow(data).to receive(:previous_attributes).and_return({ 'current_period_start' => 1_683_975_520 }) stripe_event_service.new.perform(event: event)