From 8b0caa69098b8b7f6f7c2f5caa0915f6bd12e166 Mon Sep 17 00:00:00 2001 From: Tanmay Deep Sharma Date: Thu, 6 Nov 2025 19:39:15 +0530 Subject: [PATCH 1/6] update pricing plan credits and topup --- .../app/services/enterprise/billing/v2/plan_catalog.rb | 6 +++--- .../app/services/enterprise/billing/v2/topup_catalog.rb | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/enterprise/app/services/enterprise/billing/v2/plan_catalog.rb b/enterprise/app/services/enterprise/billing/v2/plan_catalog.rb index 50f38d4a0..c0d9583df 100644 --- a/enterprise/app/services/enterprise/billing/v2/plan_catalog.rb +++ b/enterprise/app/services/enterprise/billing/v2/plan_catalog.rb @@ -15,7 +15,7 @@ module Enterprise::Billing::V2::PlanCatalog key: :startup, display_name: 'Chatwoot Startup', base_fee: 19.0, - monthly_credits: 10_000, + monthly_credits: 300, config_key: 'STRIPE_STARTUP_PLAN_ID', licensed_item_lookup_key: 'chatwoot_startup_license_fee_v2' }, @@ -23,7 +23,7 @@ module Enterprise::Billing::V2::PlanCatalog key: :business, display_name: 'Chatwoot Business', base_fee: 39.0, - monthly_credits: 50_000, + monthly_credits: 500, config_key: 'STRIPE_BUSINESS_PLAN_ID', licensed_item_lookup_key: 'chatwoot_business_license_fee_v2' }, @@ -31,7 +31,7 @@ module Enterprise::Billing::V2::PlanCatalog key: :enterprise, display_name: 'Chatwoot Enterprise', base_fee: 99.0, - monthly_credits: 200_000, + monthly_credits: 800, config_key: 'STRIPE_ENTERPRISE_PLAN_ID', licensed_item_lookup_key: 'chatwoot_enterprise_license_fee_v2' } diff --git a/enterprise/app/services/enterprise/billing/v2/topup_catalog.rb b/enterprise/app/services/enterprise/billing/v2/topup_catalog.rb index c840fb1af..1106a15ed 100644 --- a/enterprise/app/services/enterprise/billing/v2/topup_catalog.rb +++ b/enterprise/app/services/enterprise/billing/v2/topup_catalog.rb @@ -1,9 +1,9 @@ module Enterprise::Billing::V2::TopupCatalog DEFAULT_TOPUPS = [ - { credits: 100, amount: 5.0 }, - { credits: 400, amount: 10.0 }, { credits: 1000, amount: 20.0 }, - { credits: 5000, amount: 50.0 } + { credits: 2000, amount: 40.0 }, + { credits: 3000, amount: 60.0 }, + { credits: 4000, amount: 80.0 } ].freeze module_function From cec1a64d6f2c20a37d953fa94a62148f8bdb7ede Mon Sep 17 00:00:00 2001 From: Tanmay Deep Sharma Date: Fri, 7 Nov 2025 00:02:11 +0530 Subject: [PATCH 2/6] fix customer creation flow --- .../billing/create_stripe_customer_service.rb | 16 +++------------- .../billing/handle_stripe_event_service.rb | 6 ------ .../api/v1/accounts_controller_spec.rb | 6 ++++-- .../enterprise/webooks/stripe_controller_spec.rb | 14 +++++++------- 4 files changed, 14 insertions(+), 28 deletions(-) diff --git a/enterprise/app/services/enterprise/billing/create_stripe_customer_service.rb b/enterprise/app/services/enterprise/billing/create_stripe_customer_service.rb index 742bac00a..5fc6bc082 100644 --- a/enterprise/app/services/enterprise/billing/create_stripe_customer_service.rb +++ b/enterprise/app/services/enterprise/billing/create_stripe_customer_service.rb @@ -12,7 +12,7 @@ class Enterprise::Billing::CreateStripeCustomerService customer_id = prepare_customer_id update_account_for_v2_billing(customer_id) - enable_plan_specific_features(default_plan['name']) + enable_plan_specific_features('Hacker') end private @@ -26,26 +26,16 @@ class Enterprise::Billing::CreateStripeCustomerService customer_id end - def default_quantity - default_plan['default_quantity'] || DEFAULT_QUANTITY - end - def billing_email account.administrators.first.email end - def default_plan - installation_config = InstallationConfig.find_by(name: 'CHATWOOT_CLOUD_PLANS') - @default_plan ||= installation_config&.value&.first - end - def v2_configs_present? - InstallationConfig.find_by(name: 'STRIPE_HACKER_PLAN_ID').present? && - default_plan.present? + InstallationConfig.find_by(name: 'STRIPE_HACKER_PLAN_ID').present? end def raise_config_error - raise StandardError, 'V2 billing configuration is required. Please configure STRIPE_HACKER_PLAN_ID and CHATWOOT_CLOUD_PLANS.' + raise StandardError, 'V2 billing configuration is required. Please configure STRIPE_HACKER_PLAN_ID.' end def existing_subscription? 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 efcaf4a02..5343e9ef0 100644 --- a/enterprise/app/services/enterprise/billing/handle_stripe_event_service.rb +++ b/enterprise/app/services/enterprise/billing/handle_stripe_event_service.rb @@ -78,12 +78,6 @@ class Enterprise::Billing::HandleStripeEventService cloud_plans.find { |config| config['product_id'].include?(plan_id) } end - def default_plan? - cloud_plans = InstallationConfig.find_by(name: CLOUD_PLANS_CONFIG)&.value || [] - default_plan = cloud_plans.first || {} - account.custom_attributes['plan_name'] == default_plan['name'] - end - def process_credit_grant_created grant_id = extract_credit_grant_id(@event.data.object) return if grant_id.blank? diff --git a/spec/enterprise/controllers/enterprise/api/v1/accounts_controller_spec.rb b/spec/enterprise/controllers/enterprise/api/v1/accounts_controller_spec.rb index 33941cee8..f56428d0a 100644 --- a/spec/enterprise/controllers/enterprise/api/v1/accounts_controller_spec.rb +++ b/spec/enterprise/controllers/enterprise/api/v1/accounts_controller_spec.rb @@ -205,8 +205,10 @@ RSpec.describe 'Enterprise Billing APIs', type: :request do }, 'conversation' => {}, 'captain' => { - 'documents' => { 'consumed' => 0, 'current_available' => ChatwootApp.max_limit, 'total_count' => ChatwootApp.max_limit }, - 'responses' => { 'consumed' => 0, 'current_available' => ChatwootApp.max_limit, 'total_count' => ChatwootApp.max_limit } + 'documents' => { 'consumed' => 0, 'current_available' => ChatwootApp.max_limit, 'total_count' => ChatwootApp.max_limit, + 'monthly' => nil, 'topup' => nil }, + 'responses' => { 'consumed' => 0, 'current_available' => ChatwootApp.max_limit, 'total_count' => ChatwootApp.max_limit, + 'monthly' => 0, 'topup' => 0 } }, 'non_web_inboxes' => {} } diff --git a/spec/enterprise/controllers/enterprise/webooks/stripe_controller_spec.rb b/spec/enterprise/controllers/enterprise/webooks/stripe_controller_spec.rb index 3b5b68e46..647add070 100644 --- a/spec/enterprise/controllers/enterprise/webooks/stripe_controller_spec.rb +++ b/spec/enterprise/controllers/enterprise/webooks/stripe_controller_spec.rb @@ -1,31 +1,32 @@ require 'rails_helper' -# rubocop:disable RSpec/VerifiedDoubles RSpec.describe 'Enterprise::Webhooks::StripeController', type: :request do describe 'POST /enterprise/webhooks/stripe' do let(:params) { { content: 'hello' } } + let(:v1_event_params) { { type: 'invoice.created', data: { object: {} } }.to_json } it 'delegates to the v1 handler for legacy events' do - handle_stripe = double - event = double('Stripe::Event', type: 'invoice.created') + handle_stripe = instance_double(Enterprise::Billing::HandleStripeEventService) + event = instance_double(Stripe::Event, type: 'invoice.created') allow(Stripe::Webhook).to receive(:construct_event).and_return(event) allow(Enterprise::Billing::HandleStripeEventService).to receive(:new).and_return(handle_stripe) allow(handle_stripe).to receive(:perform) - post '/enterprise/webhooks/stripe', headers: { 'Stripe-Signature': 'test' }, params: params + post '/enterprise/webhooks/stripe', headers: { 'Stripe-Signature': 'test' }, params: v1_event_params, as: :json expect(handle_stripe).to have_received(:perform).with(event: event) end it 'delegates v2 billing events to the v2 webhook handler' do - event = double('Stripe::Event', type: 'v2.billing.pricing_plan_subscription.servicing_activated') + v2_event_params = { type: 'v2.billing.pricing_plan_subscription.servicing_activated', data: { object: {} } }.to_json + event = instance_double(Stripe::Event, type: 'v2.billing.pricing_plan_subscription.servicing_activated') handler_double = instance_double(Enterprise::Billing::V2::WebhookHandlerService, perform: { success: true }) allow(Stripe::Webhook).to receive(:construct_event).and_return(event) allow(Enterprise::Billing::V2::WebhookHandlerService).to receive(:new).and_return(handler_double) - post '/enterprise/webhooks/stripe', headers: { 'Stripe-Signature': 'test' }, params: params + post '/enterprise/webhooks/stripe', headers: { 'Stripe-Signature': 'test' }, params: v2_event_params, as: :json expect(Enterprise::Billing::V2::WebhookHandlerService).to have_received(:new) expect(handler_double).to have_received(:perform).with(event: event) @@ -43,4 +44,3 @@ RSpec.describe 'Enterprise::Webhooks::StripeController', type: :request do end end end -# rubocop:enable RSpec/VerifiedDoubles From b7035de4002166a46b5d891bbfb466a56cd3b8f4 Mon Sep 17 00:00:00 2001 From: Tanmay Deep Sharma Date: Fri, 7 Nov 2025 00:05:05 +0530 Subject: [PATCH 3/6] fix webhook handler spec --- .../v2/webhook_handler_service_spec.rb | 39 +++++++++++++++---- 1 file changed, 31 insertions(+), 8 deletions(-) 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 8a339af23..20abeaf63 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 @@ -16,24 +16,47 @@ describe Enterprise::Billing::V2::WebhookHandlerService do end let(:cadence_response) { OpenStruct.new(payer: OpenStruct.new(customer: 'cus_123')) } let(:event) do - double('Stripe::Event', type: 'v2.billing.pricing_plan_subscription.servicing_activated', related_object: OpenStruct.new(id: 'bpps_sub_123')) # rubocop:disable RSpec/VerifiedDoubles + event_double = double + allow(event_double).to receive(:blank?).and_return(false) + allow(event_double).to receive(:type).and_return('v2.billing.pricing_plan_subscription.servicing_activated') + allow(event_double).to receive(:related_object).and_return(OpenStruct.new(id: 'bpps_sub_123')) + event_double end before do account # Ensure account is created create(:installation_config, name: 'STRIPE_BUSINESS_PLAN_ID', value: pricing_plan_id) - # Mock the Stripe API calls - allow(StripeV2Client).to receive(:request) - .with(:get, '/v2/billing/pricing_plan_subscriptions/bpps_sub_123', anything, anything) - .and_return(subscription_response) - allow(StripeV2Client).to receive(:request) - .with(:get, '/v2/billing/cadences/cad_123', anything, anything) - .and_return(cadence_response) + + # Mock the Stripe gem client methods + stripe_client_double = double + v2_double = double + billing_double = double + subscriptions_double = double + cadences_double = double + + allow(service).to receive(:stripe_client).and_return(stripe_client_double) + allow(stripe_client_double).to receive(:v2).and_return(v2_double) + allow(v2_double).to receive(:billing).and_return(billing_double) + allow(billing_double).to receive(:pricing_plan_subscriptions).and_return(subscriptions_double) + allow(billing_double).to receive(:cadences).and_return(cadences_double) + + allow(subscriptions_double).to receive(:retrieve).with('bpps_sub_123').and_return(subscription_response) + allow(cadences_double).to receive(:retrieve).with('cad_123').and_return(cadence_response) end it 'handles subscription activation' do + # Mock the subscription provisioning service + provisioning_service_double = instance_double( + Enterprise::Billing::V2::SubscriptionProvisioningService, + provision: { pricing_plan_id: pricing_plan_id, quantity: 1 } + ) + allow(Enterprise::Billing::V2::SubscriptionProvisioningService).to receive(:new) + .with(account: account) + .and_return(provisioning_service_double) + result = service.perform(event: event) + expect(provisioning_service_double).to have_received(:provision).with(subscription_id: 'bpps_sub_123') expect(result[:pricing_plan_id]).to eq(pricing_plan_id) end end From 4b8b33c4fd31ee2738d1716ff90199032d71f0f7 Mon Sep 17 00:00:00 2001 From: Tanmay Deep Sharma Date: Fri, 7 Nov 2025 02:23:58 +0530 Subject: [PATCH 4/6] stripe_billing_version should be integer two --- app/views/api/v1/models/_account.json.jbuilder | 2 ++ enterprise/app/jobs/enterprise/billing/credit_sync_job.rb | 2 +- .../enterprise/billing/create_stripe_customer_service.rb | 2 +- .../enterprise/billing/create_stripe_customer_service_spec.rb | 4 ++-- .../enterprise/billing/v2/usage_reporter_service_spec.rb | 2 +- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/views/api/v1/models/_account.json.jbuilder b/app/views/api/v1/models/_account.json.jbuilder index 886777017..463db1cd3 100644 --- a/app/views/api/v1/models/_account.json.jbuilder +++ b/app/views/api/v1/models/_account.json.jbuilder @@ -7,6 +7,8 @@ if resource.custom_attributes.present? json.subscription_status resource.custom_attributes['subscription_status'] json.subscription_ends_on resource.custom_attributes['subscription_ends_on'] json.stripe_subscription_id resource.custom_attributes['stripe_subscription_id'] if resource.custom_attributes['stripe_subscription_id'].present? + json.stripe_billing_version resource.custom_attributes['stripe_billing_version'] if resource.custom_attributes['stripe_billing_version'].present? + json.stripe_customer_id resource.custom_attributes['stripe_customer_id'] if resource.custom_attributes['stripe_customer_id'].present? if resource.custom_attributes['pending_stripe_pricing_plan_id'].present? json.pending_stripe_pricing_plan_id resource.custom_attributes['pending_stripe_pricing_plan_id'] end diff --git a/enterprise/app/jobs/enterprise/billing/credit_sync_job.rb b/enterprise/app/jobs/enterprise/billing/credit_sync_job.rb index bfcb7d4a5..6856f5523 100644 --- a/enterprise/app/jobs/enterprise/billing/credit_sync_job.rb +++ b/enterprise/app/jobs/enterprise/billing/credit_sync_job.rb @@ -15,7 +15,7 @@ class Enterprise::Billing::CreditSyncJob < ApplicationJob Rails.logger.info '[CreditSyncJob] Starting credit sync for all accounts' accounts_with_stripe = Account.where( - "custom_attributes->>'stripe_customer_id' IS NOT NULL AND custom_attributes->>'stripe_billing_version' = '2'" + "custom_attributes->>'stripe_customer_id' IS NOT NULL AND custom_attributes->>'stripe_billing_version' = 2" ) synced_count = 0 failed_count = 0 diff --git a/enterprise/app/services/enterprise/billing/create_stripe_customer_service.rb b/enterprise/app/services/enterprise/billing/create_stripe_customer_service.rb index 5fc6bc082..05f8048cf 100644 --- a/enterprise/app/services/enterprise/billing/create_stripe_customer_service.rb +++ b/enterprise/app/services/enterprise/billing/create_stripe_customer_service.rb @@ -57,7 +57,7 @@ class Enterprise::Billing::CreateStripeCustomerService attributes = { stripe_customer_id: customer_id, - stripe_billing_version: '2' + stripe_billing_version: 2 } if hacker_plan_config&.value.present? diff --git a/spec/enterprise/services/enterprise/billing/create_stripe_customer_service_spec.rb b/spec/enterprise/services/enterprise/billing/create_stripe_customer_service_spec.rb index 731a77762..802d56392 100644 --- a/spec/enterprise/services/enterprise/billing/create_stripe_customer_service_spec.rb +++ b/spec/enterprise/services/enterprise/billing/create_stripe_customer_service_spec.rb @@ -54,7 +54,7 @@ describe Enterprise::Billing::CreateStripeCustomerService do expect(Stripe::Customer).to have_received(:create).with({ name: account.name, email: admin1.email }) expect(account.reload.custom_attributes).to include( 'stripe_customer_id' => 'cus_random_number', - 'stripe_billing_version' => '2', + 'stripe_billing_version' => 2, 'stripe_pricing_plan_id' => 'bpp_hacker_123', 'plan_name' => 'Hacker', 'subscribed_quantity' => 2 @@ -79,7 +79,7 @@ describe Enterprise::Billing::CreateStripeCustomerService do expect(Stripe::Customer).not_to have_received(:create) expect(account.reload.custom_attributes).to include( 'stripe_customer_id' => 'cus_existing_v2', - 'stripe_billing_version' => '2', + 'stripe_billing_version' => 2, 'stripe_pricing_plan_id' => 'bpp_hacker_123', 'plan_name' => 'Hacker', 'subscribed_quantity' => 2 diff --git a/spec/enterprise/services/enterprise/billing/v2/usage_reporter_service_spec.rb b/spec/enterprise/services/enterprise/billing/v2/usage_reporter_service_spec.rb index 7bd2ce6e6..354463bb7 100644 --- a/spec/enterprise/services/enterprise/billing/v2/usage_reporter_service_spec.rb +++ b/spec/enterprise/services/enterprise/billing/v2/usage_reporter_service_spec.rb @@ -1,7 +1,7 @@ require 'rails_helper' describe Enterprise::Billing::V2::UsageReporterService do - let(:account) { create(:account, custom_attributes: { 'stripe_billing_version' => '2', 'stripe_customer_id' => 'cus_123' }) } + let(:account) { create(:account, custom_attributes: { 'stripe_billing_version' => 2, 'stripe_customer_id' => 'cus_123' }) } let(:service) { described_class.new(account: account) } describe '#report' do From 5ca8aec7c37882038b45c0b26c9f508494e5a25d Mon Sep 17 00:00:00 2001 From: Tanmay Deep Sharma Date: Fri, 7 Nov 2025 02:33:49 +0530 Subject: [PATCH 5/6] default plan is hacker plan for customer creation --- .../enterprise/billing/create_stripe_customer_service_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/enterprise/services/enterprise/billing/create_stripe_customer_service_spec.rb b/spec/enterprise/services/enterprise/billing/create_stripe_customer_service_spec.rb index 802d56392..7b3a5c300 100644 --- a/spec/enterprise/services/enterprise/billing/create_stripe_customer_service_spec.rb +++ b/spec/enterprise/services/enterprise/billing/create_stripe_customer_service_spec.rb @@ -59,7 +59,7 @@ describe Enterprise::Billing::CreateStripeCustomerService do 'plan_name' => 'Hacker', 'subscribed_quantity' => 2 ) - expect(service).to have_received(:enable_plan_specific_features).with('Startup') + expect(service).to have_received(:enable_plan_specific_features).with('Hacker') end it 'does not create new customer when customer already exists with V2' do From ce8f40debb572bd1c8b30cefc155ab2c90f3a3d4 Mon Sep 17 00:00:00 2001 From: Tanmay Deep Sharma Date: Fri, 7 Nov 2025 03:37:14 +0530 Subject: [PATCH 6/6] remove stripe v2 client --- .../enterprise/billing/credit_sync_job.rb | 2 +- .../concerns/billing_intent_workflow.rb | 4 +- .../concerns/stripe_v2_client_helper.rb | 37 ++---- enterprise/lib/stripe_v2_client.rb | 111 ------------------ 4 files changed, 15 insertions(+), 139 deletions(-) delete mode 100644 enterprise/lib/stripe_v2_client.rb diff --git a/enterprise/app/jobs/enterprise/billing/credit_sync_job.rb b/enterprise/app/jobs/enterprise/billing/credit_sync_job.rb index 6856f5523..7cc073b8e 100644 --- a/enterprise/app/jobs/enterprise/billing/credit_sync_job.rb +++ b/enterprise/app/jobs/enterprise/billing/credit_sync_job.rb @@ -15,7 +15,7 @@ class Enterprise::Billing::CreditSyncJob < ApplicationJob Rails.logger.info '[CreditSyncJob] Starting credit sync for all accounts' accounts_with_stripe = Account.where( - "custom_attributes->>'stripe_customer_id' IS NOT NULL AND custom_attributes->>'stripe_billing_version' = 2" + "custom_attributes->>'stripe_customer_id' IS NOT NULL AND (custom_attributes->>'stripe_billing_version')::integer = 2" ) synced_count = 0 failed_count = 0 diff --git a/enterprise/app/services/enterprise/billing/concerns/billing_intent_workflow.rb b/enterprise/app/services/enterprise/billing/concerns/billing_intent_workflow.rb index 0237e7bc6..6e87f0366 100644 --- a/enterprise/app/services/enterprise/billing/concerns/billing_intent_workflow.rb +++ b/enterprise/app/services/enterprise/billing/concerns/billing_intent_workflow.rb @@ -6,9 +6,9 @@ module Enterprise::Billing::Concerns::BillingIntentWorkflow # Execute a billing intent with automatic reserve and commit def execute_billing_intent(intent_params) intent = create_billing_intent(intent_params) - reserve_billing_intent(intent) + reserve_billing_intent(intent['id']) yield(intent) if block_given? - commit_billing_intent(intent) + commit_billing_intent(intent['id']) intent end end diff --git a/enterprise/app/services/enterprise/billing/concerns/stripe_v2_client_helper.rb b/enterprise/app/services/enterprise/billing/concerns/stripe_v2_client_helper.rb index fe27cf2d3..f48501efc 100644 --- a/enterprise/app/services/enterprise/billing/concerns/stripe_v2_client_helper.rb +++ b/enterprise/app/services/enterprise/billing/concerns/stripe_v2_client_helper.rb @@ -8,11 +8,6 @@ module Enterprise::Billing::Concerns::StripeV2ClientHelper @stripe_client ||= Stripe::StripeClient.new(ENV.fetch('STRIPE_SECRET_KEY', nil)) end - # Generic Stripe V2 API request wrapper (for methods not available in gem) - def stripe_v2_request(method, path, params = {}, api_version: nil) - StripeV2Client.request(method, path, params, stripe_api_options(api_version)) - end - # Pricing Plan Subscriptions def retrieve_pricing_plan_subscription(subscription_id) stripe_client.v2.billing.pricing_plan_subscriptions.retrieve(subscription_id) @@ -23,27 +18,27 @@ module Enterprise::Billing::Concerns::StripeV2ClientHelper stripe_client.v2.billing.pricing_plans.retrieve(pricing_plan_id) end - # gem not available - using custom HTTP client - def update_pricing_plan(plan_id, params) - stripe_v2_request(:post, "/v2/billing/pricing_plans/#{plan_id}", params) - end - - # Billing Cadences def retrieve_billing_cadence(cadence_id) stripe_client.v2.billing.cadences.retrieve(cadence_id) end - # gem not available - using custom HTTP client def create_billing_intent(params) - stripe_v2_request(:post, '/v2/billing/intents', params) + response = Faraday.post('https://api.stripe.com/v2/billing/intents') do |req| + req.headers['Authorization'] = "Bearer #{ENV.fetch('STRIPE_SECRET_KEY', nil)}" + req.headers['Stripe-Version'] = default_stripe_version + req.headers['Content-Type'] = 'application/json' + req.body = params.to_json + end + + JSON.parse(response.body) end - def reserve_billing_intent(billing_intent) - stripe_client.v2.billing.intents.reserve(billing_intent.id) + def reserve_billing_intent(billing_intent_id) + stripe_client.v2.billing.intents.reserve(billing_intent_id) end - def commit_billing_intent(billing_intent) - stripe_client.v2.billing.intents.commit(billing_intent.id) + def commit_billing_intent(billing_intent_id) + stripe_client.v2.billing.intents.commit(billing_intent_id) end # Checkout Sessions (V1 API but used with V2 plans) @@ -56,14 +51,6 @@ module Enterprise::Billing::Concerns::StripeV2ClientHelper Stripe::Billing::CreditGrant.retrieve(grant_id) end - # API Options with support for custom versions - def stripe_api_options(custom_version = nil) - { - api_key: ENV.fetch('STRIPE_SECRET_KEY', nil), - stripe_version: custom_version || default_stripe_version - } - end - def default_stripe_version '2025-10-29.preview' end diff --git a/enterprise/lib/stripe_v2_client.rb b/enterprise/lib/stripe_v2_client.rb deleted file mode 100644 index 33ed255a5..000000000 --- a/enterprise/lib/stripe_v2_client.rb +++ /dev/null @@ -1,111 +0,0 @@ -module StripeV2Client - class << self - def request(method, path, params = {}, options = {}) - api_key = options[:api_key] || ENV.fetch('STRIPE_SECRET_KEY', nil) - stripe_version = options[:stripe_version] || '2025-08-27.preview' - - uri = URI("https://api.stripe.com#{path}") - - http = Net::HTTP.new(uri.host, uri.port) - http.use_ssl = true - - request = build_request(method, uri, params, path) - request['Authorization'] = "Bearer #{api_key}" - request['Stripe-Version'] = stripe_version - - response = http.request(request) - parse_response(response) - end - - private - - def build_request(method, uri, params, path) - case method - when :get - Net::HTTP::Get.new(uri) - when :post - req = Net::HTTP::Post.new(uri) - if v2_endpoint?(path) - # V2 endpoints use JSON - req.body = params.to_json unless params.empty? - req['Content-Type'] = 'application/json' - else - # V1 endpoints (including checkout sessions) use form data - # even with preview API versions - req.body = encode_nested_params(params) unless params.empty? - req['Content-Type'] = 'application/x-www-form-urlencoded' - end - req - when :delete - Net::HTTP::Delete.new(uri) - end - end - - def v2_endpoint?(path) - path.start_with?('/v2/') - end - - # Encode nested parameters for form submission - # Stripe expects nested params like: checkout_items[0][type]=value - def encode_nested_params(params, prefix = nil) - pairs = [] - params.each do |key, value| - full_key = prefix ? "#{prefix}[#{key}]" : key.to_s - pairs.concat(encode_param_value(full_key, value)) - end - pairs.join('&') - end - - def encode_param_value(key, value) - case value - when Hash - [encode_nested_params(value, key)] - when Array - value.each_with_index.map { |item, index| encode_nested_params(item, "#{key}[#{index}]") } - when true, false - ["#{CGI.escape(key)}=#{value}"] - when nil - [] - else - ["#{CGI.escape(key)}=#{CGI.escape(value.to_s)}"] - end - end - - def parse_response(response) - body = JSON.parse(response.body) - - # Check for Stripe error responses - if body.is_a?(Hash) && body['error'] - error = body['error'] - raise Stripe::StripeError, "#{error['code']}: #{error['message']}" - end - - # Convert to OpenStruct for dot notation access (mimicking Stripe SDK objects) - case body - when Hash - recursive_to_struct(body) - when Array - body.map { |item| recursive_to_struct(item) } - else - body - end - rescue JSON::ParserError - response.body - end - - def recursive_to_struct(hash) - return hash unless hash.is_a?(Hash) - - OpenStruct.new(hash.transform_values do |value| - case value - when Hash - recursive_to_struct(value) - when Array - value.map { |item| item.is_a?(Hash) ? recursive_to_struct(item) : item } - else - value - end - end) - end - end -end