diff --git a/app/controllers/settings/payments_controller.rb b/app/controllers/settings/payments_controller.rb index eb4026799..69a422d52 100644 --- a/app/controllers/settings/payments_controller.rb +++ b/app/controllers/settings/payments_controller.rb @@ -1,6 +1,8 @@ class Settings::PaymentsController < ApplicationController layout "settings" + guard_feature unless: -> { Current.family.can_manage_subscription? } + def show @family = Current.family end diff --git a/app/controllers/subscriptions_controller.rb b/app/controllers/subscriptions_controller.rb index 754cfbc3a..da02534bd 100644 --- a/app/controllers/subscriptions_controller.rb +++ b/app/controllers/subscriptions_controller.rb @@ -1,6 +1,9 @@ class SubscriptionsController < ApplicationController # Disables subscriptions for self hosted instances - guard_feature if: -> { self_hosted? } + before_action :guard_self_hosted, if: -> { self_hosted? } + + # Disables Stripe portal for users without stripe_customer_id (demo users, manually created users) + guard_feature unless: -> { Current.family.can_manage_subscription? }, only: :show # Upgrade page for unsubscribed users def upgrade @@ -58,6 +61,10 @@ class SubscriptionsController < ApplicationController end private + def guard_self_hosted + render plain: "Feature disabled: subscriptions are not available in self-hosted mode", status: :forbidden + end + def stripe @stripe ||= Provider::Registry.get_provider(:stripe) end diff --git a/app/models/family/subscribeable.rb b/app/models/family/subscribeable.rb index 83fb3597d..fdbbaab8b 100644 --- a/app/models/family/subscribeable.rb +++ b/app/models/family/subscribeable.rb @@ -41,6 +41,10 @@ module Family::Subscribeable subscription&.active? end + def can_manage_subscription? + stripe_customer_id.present? + end + def needs_subscription? subscription.nil? && !self_hoster? end diff --git a/app/views/settings/_settings_nav.html.erb b/app/views/settings/_settings_nav.html.erb index 6771cfd87..de5442173 100644 --- a/app/views/settings/_settings_nav.html.erb +++ b/app/views/settings/_settings_nav.html.erb @@ -8,7 +8,7 @@ nav_sections = [ { label: t(".preferences_label"), path: settings_preferences_path, icon: "bolt" }, { label: t(".profile_label"), path: settings_profile_path, icon: "circle-user" }, { label: t(".security_label"), path: settings_security_path, icon: "shield-check" }, - { label: t(".payment_label"), path: settings_payment_path, icon: "circle-dollar-sign", if: !self_hosted? } + { label: t(".payment_label"), path: settings_payment_path, icon: "circle-dollar-sign", if: !self_hosted? && Current.family.can_manage_subscription? } ] }, { diff --git a/test/controllers/settings/payments_controller_test.rb b/test/controllers/settings/payments_controller_test.rb index b35252ef8..fc0a34760 100644 --- a/test/controllers/settings/payments_controller_test.rb +++ b/test/controllers/settings/payments_controller_test.rb @@ -1,7 +1,22 @@ require "test_helper" class Settings::PaymentsControllerTest < ActionDispatch::IntegrationTest - # test "the truth" do - # assert true - # end + setup do + sign_in @user = users(:empty) + @family = @user.family + end + + test "returns forbidden when family has no stripe_customer_id" do + assert_nil @family.stripe_customer_id + + get settings_payment_path + assert_response :forbidden + end + + test "shows payment settings when family has stripe_customer_id" do + @family.update!(stripe_customer_id: "cus_test123") + + get settings_payment_path + assert_response :success + end end diff --git a/test/controllers/subscriptions_controller_test.rb b/test/controllers/subscriptions_controller_test.rb index 4599b9530..c8076218a 100644 --- a/test/controllers/subscriptions_controller_test.rb +++ b/test/controllers/subscriptions_controller_test.rb @@ -11,9 +11,10 @@ class SubscriptionsControllerTest < ActionDispatch::IntegrationTest end test "disabled for self hosted users" do - Rails.application.config.app_mode.stubs(:self_hosted?).returns(true) - post subscription_path - assert_response :forbidden + with_self_hosting do + post subscription_path + assert_response :forbidden + end end # Trial subscriptions are managed internally and do NOT go through Stripe @@ -73,4 +74,23 @@ class SubscriptionsControllerTest < ActionDispatch::IntegrationTest assert @family.subscription.active? assert_equal "Welcome to Sure! Your contribution is appreciated.", flash[:notice] end + + test "show action returns forbidden when family has no stripe_customer_id" do + assert_nil @family.stripe_customer_id + + get subscription_path + assert_response :forbidden + end + + test "show action redirects to stripe portal when family has stripe_customer_id" do + @family.update!(stripe_customer_id: "cus_test123") + + @mock_stripe.expects(:create_payment_portal_session_url).with( + customer_id: "cus_test123", + return_url: settings_payment_url + ).returns("https://billing.stripe.com/session/test") + + get subscription_path + assert_redirected_to "https://billing.stripe.com/session/test" + end end diff --git a/test/models/family/subscribeable_test.rb b/test/models/family/subscribeable_test.rb index 597f41e7a..0b1aafe71 100644 --- a/test/models/family/subscribeable_test.rb +++ b/test/models/family/subscribeable_test.rb @@ -10,4 +10,19 @@ class Family::SubscribeableTest < ActiveSupport::TestCase @family.subscription.update!(trial_ends_at: 1.day.ago, status: "trialing") assert_not @family.trialing? end + + test "can_manage_subscription? returns true when stripe_customer_id is present" do + @family.update!(stripe_customer_id: "cus_test123") + assert @family.can_manage_subscription? + end + + test "can_manage_subscription? returns false when stripe_customer_id is nil" do + @family.update!(stripe_customer_id: nil) + assert_not @family.can_manage_subscription? + end + + test "can_manage_subscription? returns false when stripe_customer_id is blank" do + @family.update!(stripe_customer_id: "") + assert_not @family.can_manage_subscription? + end end