From f90c71a9050a161ca3823dd484da485aa5ef72f1 Mon Sep 17 00:00:00 2001 From: Rob Galanakis Date: Mon, 15 Jun 2026 16:40:30 -0700 Subject: [PATCH 1/2] Add VendorConfig#platform_payment_never_required Set it to Biketown for example can be used even if someone has a negative balance. --- adminapp/src/pages/VendorConfigurationDetailPage.jsx | 4 ++++ adminapp/src/pages/VendorConfigurationForm.jsx | 7 +++++++ db/migrations/112_vendor_config_platform_payment.rb | 9 +++++++++ lib/suma/admin_api/anon_proxy_vendor_configurations.rb | 2 ++ lib/suma/anon_proxy/vendor_configuration.rb | 6 ++++++ 5 files changed, 28 insertions(+) create mode 100644 db/migrations/112_vendor_config_platform_payment.rb diff --git a/adminapp/src/pages/VendorConfigurationDetailPage.jsx b/adminapp/src/pages/VendorConfigurationDetailPage.jsx index 8600d9aa5..33c22419a 100644 --- a/adminapp/src/pages/VendorConfigurationDetailPage.jsx +++ b/adminapp/src/pages/VendorConfigurationDetailPage.jsx @@ -30,6 +30,10 @@ export default function VendorConfigurationDetailPage() { }, { label: "Auth-to-Vendor", value: model.authToVendorKey }, { label: "Enabled", value: {model.enabled} }, + { + label: "Platform Payment Never Required", + value: {model.platformPaymentNeverRequired}, + }, { label: "Description (En)", value: model.descriptionText.en }, { label: "Description (Es)", value: model.descriptionText.es }, { label: "Help (En)", value: model.helpText.en }, diff --git a/adminapp/src/pages/VendorConfigurationForm.jsx b/adminapp/src/pages/VendorConfigurationForm.jsx index 09318c361..93026de66 100644 --- a/adminapp/src/pages/VendorConfigurationForm.jsx +++ b/adminapp/src/pages/VendorConfigurationForm.jsx @@ -43,6 +43,13 @@ export default function VendorConfigurationForm({ checked={resource.enabled} onChange={setFieldFromInput} /> + } + label="Platform Payment Never Required" + name="platformPaymentNeverRequired" + checked={resource.platformPaymentNeverRequired} + onChange={setFieldFromInput} + /> Description Date: Thu, 25 Jun 2026 09:00:35 -0700 Subject: [PATCH 2/2] wip work, but not doing this after all --- lib/suma/anon_proxy/vendor_account.rb | 7 ++++++- lib/suma/api/anon_proxy.rb | 1 + spec/suma/anon_proxy/vendor_account_spec.rb | 16 ++++++++++++++ spec/suma/api/anon_proxy_spec.rb | 23 ++++++++++++++++----- 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/lib/suma/anon_proxy/vendor_account.rb b/lib/suma/anon_proxy/vendor_account.rb index a0cdd4c82..cbf6c3d35 100644 --- a/lib/suma/anon_proxy/vendor_account.rb +++ b/lib/suma/anon_proxy/vendor_account.rb @@ -77,6 +77,7 @@ def needs_linking?(now:) = self.auth_to_vendor.needs_linking?(now:) # If we have any results from this, we probably need pricing, # and can ask the user to set up payment before provisioning/linking a vendor account. def require_payment_instrument?(as_of:) + return false if self.configuration.platform_payment_never_required? programs = self.configuration.programs_eligible_to(self.member, as_of:) nonzero_same_vendor_programs = Suma::Program.where(id: programs.map(&:id)). where( @@ -149,7 +150,11 @@ def ui_state_v1(now:) :relink end cash_ledger = self.member.payment_account&.cash_ledger! - balance_payoff_needed = has_payment_method && cash_ledger && Suma::Payment.chargeable_balance?(cash_ledger.balance) + balance_payoff_needed = + requires_payment_method && + has_payment_method && + cash_ledger && + Suma::Payment.chargeable_balance?(cash_ledger.balance) return UIStateV1.new( index_card_mode:, needs_linking:, diff --git a/lib/suma/api/anon_proxy.rb b/lib/suma/api/anon_proxy.rb index eaab62ffc..7144ef274 100644 --- a/lib/suma/api/anon_proxy.rb +++ b/lib/suma/api/anon_proxy.rb @@ -38,6 +38,7 @@ def lookup post :make_auth_request do apva = lookup if (code = Suma::Payment.service_usage_prohibited_reason(apva.member.payment_account)) + if code == "usage_prohibited_cash_balance" && merror!(402, "Account cannot use services", code:) end apva.auth_to_vendor.auth(now: current_time) diff --git a/spec/suma/anon_proxy/vendor_account_spec.rb b/spec/suma/anon_proxy/vendor_account_spec.rb index a3bde4a8a..6362d83ba 100644 --- a/spec/suma/anon_proxy/vendor_account_spec.rb +++ b/spec/suma/anon_proxy/vendor_account_spec.rb @@ -198,6 +198,11 @@ Suma::Eligibility::Assignment.dataset.delete expect(va).to_not be_require_payment_instrument(as_of:) end + + it "the vendor config never uses platform payments" do + vc.update(platform_payment_never_required: true) + expect(va).to_not be_require_payment_instrument(as_of:) + end end end @@ -268,6 +273,17 @@ balance_payoff_needed: false, ) end + + it "does not require a balance payoff if the vendor config does not use platform payments" do + va.configuration.update(platform_payment_never_required: true) + expect(va.ui_state_v1(now: as_of)).to have_attributes( + index_card_mode: :link, + needs_linking: true, + requires_payment_method: false, + has_payment_method: true, + balance_payoff_needed: false, + ) + end end end end diff --git a/spec/suma/api/anon_proxy_spec.rb b/spec/suma/api/anon_proxy_spec.rb index da9daeea4..505526917 100644 --- a/spec/suma/api/anon_proxy_spec.rb +++ b/spec/suma/api/anon_proxy_spec.rb @@ -151,13 +151,26 @@ expect(last_response).to have_json_body.that_includes(error: include(message: match(/config is not enabled/))) end - it "402s if the member cannot use services" do - Suma::Payment.minimum_cash_balance_for_services_cents = 5 - expect(Suma::Payment).to_not be_can_use_services(member.payment_account) + describe "when the member cannot use services due to balance" do + before(:each) do + Suma::Payment.minimum_cash_balance_for_services_cents = 5 + expect(Suma::Payment).to_not be_can_use_services(member.payment_account) + end - post "/v1/anon_proxy/vendor_accounts/#{va.id}/make_auth_request" + it "402s" do + post "/v1/anon_proxy/vendor_accounts/#{va.id}/make_auth_request" + + expect(last_response).to have_status(402) + end - expect(last_response).to have_status(402) + it "succeeds if the configuration does not use off-platform payments" do + va.configuration.update(platform_payment_never_required: true) + + post "/v1/anon_proxy/vendor_accounts/#{va.id}/make_auth_request" + + expect(last_response).to have_status(200) + expect(Suma::AnonProxy::AuthToVendor::Fake.calls).to eq(1) + end end it "auths to vendor and marks the code as requested" do