diff --git a/app/policies/account_policy.rb b/app/policies/account_policy.rb index 61e02ae77..8eca0e7b3 100644 --- a/app/policies/account_policy.rb +++ b/app/policies/account_policy.rb @@ -30,4 +30,8 @@ class AccountPolicy < ApplicationPolicy def toggle_deletion? @account_user.administrator? end + + def credits_balance? + @account_user.administrator? || @account_user.agent? + end end diff --git a/enterprise/app/controllers/enterprise/webhooks/stripe_controller.rb b/enterprise/app/controllers/enterprise/webhooks/stripe_controller.rb index 6be19c9c8..64fe190a3 100644 --- a/enterprise/app/controllers/enterprise/webhooks/stripe_controller.rb +++ b/enterprise/app/controllers/enterprise/webhooks/stripe_controller.rb @@ -31,7 +31,7 @@ class Enterprise::Webhooks::StripeController < ActionController::API def v2_billing_event?(event) %w[ billing.credit_grant.created - billing.credit_grant.expired + billing.credit_grant.updated ].include?(event.type) end @@ -40,7 +40,7 @@ class Enterprise::Webhooks::StripeController < ActionController::API return if customer_id.blank? account = Account.find_by("custom_attributes->>'stripe_customer_id' = ?", customer_id) - return unless account&.custom_attributes&.[]('stripe_billing_version').to_i == 2 + return unless account && account_v2_enabled?(account) service = ::Enterprise::Billing::V2::WebhookHandlerService.new(account: account) service.process(event) @@ -56,4 +56,8 @@ class Enterprise::Webhooks::StripeController < ActionController::API data['customer'] end end + + def account_v2_enabled?(account) + account.custom_attributes&.[]('stripe_billing_version').to_i == 2 + end end diff --git a/enterprise/app/jobs/enterprise/billing/report_usage_job.rb b/enterprise/app/jobs/enterprise/billing/report_usage_job.rb deleted file mode 100644 index 48a12f48b..000000000 --- a/enterprise/app/jobs/enterprise/billing/report_usage_job.rb +++ /dev/null @@ -1,12 +0,0 @@ -class Enterprise::Billing::ReportUsageJob < ApplicationJob - queue_as :low - - def perform(account_id:, credits_used:, feature:) - account = Account.find_by(id: account_id) - return if account.nil? - return unless account.custom_attributes&.[]('stripe_billing_version').to_i == 2 - - service = V2::UsageReporterService.new(account: account) - service.report(credits_used, feature) - end -end diff --git a/enterprise/app/services/enterprise/ai/captain_credit_service.rb b/enterprise/app/services/enterprise/ai/captain_credit_service.rb index 3da523b53..9ed7ebc51 100644 --- a/enterprise/app/services/enterprise/ai/captain_credit_service.rb +++ b/enterprise/app/services/enterprise/ai/captain_credit_service.rb @@ -8,10 +8,16 @@ class Enterprise::Ai::CaptainCreditService def check_and_use_credits(feature: 'ai_captain', amount: 1, metadata: {}) # V1 accounts don't use credit system - return { success: true } if account.custom_attributes['stripe_billing_version'].to_i != 2 + return { success: true } unless v2_enabled? # V2 accounts use credit-based billing service = Enterprise::Billing::V2::CreditManagementService.new(account: account) service.use_credit(feature: feature, amount: amount, metadata: metadata) end + + private + + def v2_enabled? + account.custom_attributes&.[]('stripe_billing_version').to_i == 2 + end end diff --git a/enterprise/app/services/enterprise/billing/v2/credit_management_service.rb b/enterprise/app/services/enterprise/billing/v2/credit_management_service.rb index 56c48c3bf..3d499b02c 100644 --- a/enterprise/app/services/enterprise/billing/v2/credit_management_service.rb +++ b/enterprise/app/services/enterprise/billing/v2/credit_management_service.rb @@ -1,5 +1,5 @@ class Enterprise::Billing::V2::CreditManagementService < Enterprise::Billing::V2::BaseService - def use_credit(feature: 'ai_captain', amount: 1) + def use_credit(feature: 'ai_captain', amount: 1, metadata: {}) return { success: true, credits_used: 0, remaining: total_credits } if amount <= 0 with_locked_account do @@ -17,7 +17,8 @@ class Enterprise::Billing::V2::CreditManagementService < Enterprise::Billing::V2 type: 'use', amount: amount, credit_type: determine_credit_type(amount), - description: "Used for #{feature}" + description: "Used for #{feature}", + metadata: metadata ) { success: true, credits_used: amount, remaining: total_credits } diff --git a/enterprise/app/services/enterprise/billing/v2/webhook_handler_service.rb b/enterprise/app/services/enterprise/billing/v2/webhook_handler_service.rb index 97ec85531..ea96d1444 100644 --- a/enterprise/app/services/enterprise/billing/v2/webhook_handler_service.rb +++ b/enterprise/app/services/enterprise/billing/v2/webhook_handler_service.rb @@ -3,13 +3,12 @@ class Enterprise::Billing::V2::WebhookHandlerService < Enterprise::Billing::V2:: case event.type when 'billing.credit_grant.created' handle_credit_grant_created(event.data.object) - when 'billing.credit_grant.expired' - handle_credit_grant_expired + when 'billing.credit_grant.updated' + handle_credit_grant_updated(event.data.object) else { success: true } end rescue StandardError => e - Rails.logger.error "V2 webhook error: #{e.message}" { success: false, error: e.message } end @@ -30,9 +29,19 @@ class Enterprise::Billing::V2::WebhookHandlerService < Enterprise::Billing::V2:: { success: true } end + def handle_credit_grant_updated(grant) + # Check if grant has expired + if grant.respond_to?(:expired_at) && grant.expired_at + # Grant has expired + handle_credit_grant_expired + else + # Other updates (voided, amount changes, etc) + { success: true } + end + end + def handle_credit_grant_expired - expired = Enterprise::Billing::V2::CreditManagementService.new(account: account).expire_monthly_credits - Rails.logger.info "Expired #{expired} monthly credits for account #{account.id}" + Enterprise::Billing::V2::CreditManagementService.new(account: account).expire_monthly_credits { success: true } end diff --git a/enterprise/lib/enterprise/integrations/openai_processor_service.rb b/enterprise/lib/enterprise/integrations/openai_processor_service.rb index 259f39e2f..70428c97d 100644 --- a/enterprise/lib/enterprise/integrations/openai_processor_service.rb +++ b/enterprise/lib/enterprise/integrations/openai_processor_service.rb @@ -5,7 +5,7 @@ module Enterprise::Integrations::OpenaiProcessorService def perform # Check and use credits if account is on V2 billing - if hook.account.custom_attributes['stripe_billing_version'].to_i == 2 + if v2_enabled? credit_service = Enterprise::Ai::CaptainCreditService.new( account: hook.account, conversation: conversation @@ -103,4 +103,8 @@ module Enterprise::Integrations::OpenaiProcessorService def label_suggestions_enabled? hook.settings['label_suggestion'].present? end + + def v2_enabled? + hook.account.custom_attributes&.[]('stripe_billing_version').to_i == 2 + end end diff --git a/spec/enterprise/services/enterprise/billing/v2/webhook_handler_service_spec.rb b/spec/enterprise/services/enterprise/billing/v2/webhook_handler_service_spec.rb index 6610511a7..4d87c590a 100644 --- a/spec/enterprise/services/enterprise/billing/v2/webhook_handler_service_spec.rb +++ b/spec/enterprise/services/enterprise/billing/v2/webhook_handler_service_spec.rb @@ -49,10 +49,11 @@ describe Enterprise::Billing::V2::WebhookHandlerService do end end - context 'when handling credit expiration' do - it 'expires monthly credits' do + context 'when handling credit grant update with expiration' do + it 'expires monthly credits when grant is expired' do allow(credit_service).to receive(:expire_monthly_credits).and_return(100) - event = build_event(type: 'billing.credit_grant.expired', object: {}) + grant = OpenStruct.new(expired_at: Time.current) + event = build_event(type: 'billing.credit_grant.updated', object: grant) result = service.process(event)