diff --git a/.gitignore b/.gitignore index 8f8e6d1a0..7ca033f87 100644 --- a/.gitignore +++ b/.gitignore @@ -99,12 +99,3 @@ CLAUDE.local.md # Histoire deployment .netlify .histoire - -.pnpm-store/ - -# External documentation -\[External\]* - - -pricing-plans/ -scripts/ \ No newline at end of file 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 d28fc2fac..efcaf4a02 100644 --- a/enterprise/app/services/enterprise/billing/handle_stripe_event_service.rb +++ b/enterprise/app/services/enterprise/billing/handle_stripe_event_service.rb @@ -114,6 +114,8 @@ class Enterprise::Billing::HandleStripeEventService # Fallback: extract from amount object amount_data = extract_attribute(grant, :amount) return 0 unless amount_data + + 0 end def extract_attribute(object, attribute) 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 23a194aa9..51221ac5c 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 @@ -327,8 +327,8 @@ describe Enterprise::Billing::HandleStripeEventService do end context 'when handling monthly credit grant' do - it 'syncs monthly credits from Stripe' do - allow(credit_service).to receive(:sync_monthly_credits) + it 'adds credits from Stripe' do + allow(credit_service).to receive(:add_response_topup_credits) # Webhook event object (minimal, just has ID) grant_event_object = OpenStruct.new( @@ -342,25 +342,26 @@ describe Enterprise::Billing::HandleStripeEventService do api_grant_response = OpenStruct.new( id: 'credgr_test_123', customer: 'cus_123', + metadata: { 'credits' => '2000' }, amount: OpenStruct.new( type: 'custom_pricing_unit', custom_pricing_unit: OpenStruct.new(value: 2000) ), expires_at: Time.current ) - allow(StripeV2Client).to receive(:request) - .with(:get, '/v1/billing/credit_grants/credgr_test_123') + allow(Stripe::Billing::CreditGrant).to receive(:retrieve) + .with('credgr_test_123') .and_return(api_grant_response) stripe_event_service.new.perform(event: event) - expect(credit_service).to have_received(:sync_monthly_credits).with(2000) + expect(credit_service).to have_received(:add_response_topup_credits).with(2000) end end context 'when handling topup credit grant' do it 'adds topup credits' do - allow(credit_service).to receive(:add_topup_credits) + allow(credit_service).to receive(:add_response_topup_credits) # Webhook event object (minimal, just has ID) grant_event_object = OpenStruct.new( @@ -374,42 +375,26 @@ describe Enterprise::Billing::HandleStripeEventService do api_grant_response = OpenStruct.new( id: 'credgr_test_456', customer: 'cus_123', + metadata: { 'credits' => '500' }, amount: OpenStruct.new( type: 'custom_pricing_unit', custom_pricing_unit: OpenStruct.new(value: 500) ), expires_at: nil ) - allow(StripeV2Client).to receive(:request) - .with(:get, '/v1/billing/credit_grants/credgr_test_456') + allow(Stripe::Billing::CreditGrant).to receive(:retrieve) + .with('credgr_test_456') .and_return(api_grant_response) stripe_event_service.new.perform(event: event) - expect(credit_service).to have_received(:add_topup_credits).with(500) - end - end - - 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) - - grant = OpenStruct.new( - customer: 'cus_123', - expired_at: Time.current - ) - allow(event).to receive(:type).and_return('billing.credit_grant.updated') - allow(data).to receive(:object).and_return(grant) - - stripe_event_service.new.perform(event: event) - - expect(credit_service).to have_received(:expire_monthly_credits) + expect(credit_service).to have_received(:add_response_topup_credits).with(500) end end context 'when handling monetary type credit grant' do - it 'syncs monthly credits from monetary grant' do - allow(credit_service).to receive(:sync_monthly_credits) + it 'adds credits from monetary grant' do + allow(credit_service).to receive(:add_response_topup_credits) # Webhook event object (minimal, just has ID) grant_event_object = OpenStruct.new( @@ -423,6 +408,7 @@ describe Enterprise::Billing::HandleStripeEventService do api_grant_response = OpenStruct.new( id: 'credgr_test_monetary', customer: 'cus_123', + metadata: { 'credits' => '1000' }, amount: OpenStruct.new( type: 'monetary', monetary: OpenStruct.new( @@ -432,13 +418,13 @@ describe Enterprise::Billing::HandleStripeEventService do ), expires_at: Time.current ) - allow(StripeV2Client).to receive(:request) - .with(:get, '/v1/billing/credit_grants/credgr_test_monetary') + allow(Stripe::Billing::CreditGrant).to receive(:retrieve) + .with('credgr_test_monetary') .and_return(api_grant_response) stripe_event_service.new.perform(event: event) - expect(credit_service).to have_received(:sync_monthly_credits).with(1000) + expect(credit_service).to have_received(:add_response_topup_credits).with(1000) end end @@ -462,8 +448,8 @@ describe Enterprise::Billing::HandleStripeEventService do ), expires_at: Time.current ) - allow(StripeV2Client).to receive(:request) - .with(:get, '/v1/billing/credit_grants/credgr_test_zero') + allow(Stripe::Billing::CreditGrant).to receive(:retrieve) + .with('credgr_test_zero') .and_return(api_grant_response) stripe_event_service.new.perform(event: event) diff --git a/spec/enterprise/services/enterprise/billing/v2/subscription_provisioning_service_spec.rb b/spec/enterprise/services/enterprise/billing/v2/subscription_provisioning_service_spec.rb index b07b5b2d9..4631e6c96 100644 --- a/spec/enterprise/services/enterprise/billing/v2/subscription_provisioning_service_spec.rb +++ b/spec/enterprise/services/enterprise/billing/v2/subscription_provisioning_service_spec.rb @@ -13,7 +13,9 @@ describe Enterprise::Billing::V2::SubscriptionProvisioningService do describe '#provision' do let(:subscription_response) do - OpenStruct.new( + # Mock Stripe::V2::Billing::PricingPlanSubscription object + instance_double( + Stripe::V2::Billing::PricingPlanSubscription, id: subscription_id, pricing_plan: pricing_plan_id, billing_cadence: 'bpc_cadence_123', @@ -22,7 +24,8 @@ describe Enterprise::Billing::V2::SubscriptionProvisioningService do end before do - allow(StripeV2Client).to receive(:request).and_return(subscription_response) + # Mock the retrieve_pricing_plan_subscription method directly + allow(service).to receive(:retrieve_pricing_plan_subscription).with(subscription_id).and_return(subscription_response) end it 'provisions subscription' do @@ -35,7 +38,9 @@ describe Enterprise::Billing::V2::SubscriptionProvisioningService do describe '#refresh' do let(:subscription_response) do - OpenStruct.new( + # Mock Stripe::V2::Billing::PricingPlanSubscription object + instance_double( + Stripe::V2::Billing::PricingPlanSubscription, id: 'bpps_sub_456', pricing_plan: pricing_plan_id, billing_cadence: 'bpc_cadence_123', @@ -45,7 +50,9 @@ describe Enterprise::Billing::V2::SubscriptionProvisioningService do before do account.update!(custom_attributes: { 'stripe_subscription_id' => 'bpps_sub_456' }) - allow(StripeV2Client).to receive(:request).and_return(subscription_response) + + # Mock the retrieve_pricing_plan_subscription method directly + allow(service).to receive(:retrieve_pricing_plan_subscription).with('bpps_sub_456').and_return(subscription_response) end it 'refreshes subscription' do @@ -54,4 +61,36 @@ describe Enterprise::Billing::V2::SubscriptionProvisioningService do expect(result[:pricing_plan_id]).to eq(pricing_plan_id) end end + + describe '#cancel subscription' do + let(:subscription_response) do + # Mock Stripe::V2::Billing::PricingPlanSubscription object with canceled status + instance_double( + Stripe::V2::Billing::PricingPlanSubscription, + id: subscription_id, + pricing_plan: pricing_plan_id, + billing_cadence: 'bpc_cadence_123', + servicing_status: 'canceled' + ) + end + + let(:hacker_plan_id) { 'bpp_hacker_plan_123' } + + before do + create(:installation_config, name: 'STRIPE_HACKER_PLAN_ID', value: hacker_plan_id) + + # Mock the retrieve_pricing_plan_subscription method directly + allow(service).to receive(:retrieve_pricing_plan_subscription).with(subscription_id).and_return(subscription_response) + end + + it 'cancels subscription and resets to hacker plan' do + result = service.provision(subscription_id: subscription_id) + + expect(result[:pricing_plan_id]).to be_nil + expect(result[:quantity]).to be_nil + expect(account.custom_attributes['plan_name']).to eq('Hacker') + expect(account.custom_attributes['stripe_subscription_id']).to be_nil + expect(account.custom_attributes['subscription_status']).to eq('canceled') + end + end end