fix(billing): handle Stripe auth/config errors distinctly; document Stripe env

The /billing Checkout/Portal failed opaquely when the live Stripe secret key was revoked/expired: the generic rescue showed "Could not start checkout. Please try again", which blames the user for a server-side misconfig. Now rescue Stripe::AuthenticationError / PermissionError separately, log it loudly for ops, and show an honest "Billing is temporarily unavailable" message. Also document STRIPE_SECRET_KEY / STRIPE_WEBHOOK_SECRET in .env.example (they were undocumented, which is how a rotated/expired key went unnoticed). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Seto Elkahfi committed Jun 30, 2026 at 21:37 UTC 1ff18bde5e19c2e6768aeeea416e344447307fc5
2 files changed +23 -2
.env.example
+13
index e623caf..fbe2a57 100644 --- a/.env.example +++ b/.env.example @@ -37,6 +37,19 @@ ONDE_CLOUD_APP_SECRET=your-onde-app-secret-here # ONDE_CLOUD_BASE_URL=https://cloud.ondeinference.com/v1 +# ----------------------------------------------------------------------------- +# Stripe — siGit Code Pro/Team billing (the /billing page + Checkout/Portal). +# Separate from Onde Cloud billing. Billing is a no-op unless STRIPE_SECRET_KEY +# is set. Use sk_test_… in dev; sk_live_… in production. Rotating the key in the +# Stripe dashboard revokes the old one, so update this value too or Checkout +# fails with "Expired API Key". +# ----------------------------------------------------------------------------- + +STRIPE_SECRET_KEY=sk_test_your-stripe-secret-key +# Signing secret for the /stripe/webhooks endpoint (Stripe CLI or dashboard). +STRIPE_WEBHOOK_SECRET=whsec_your-webhook-signing-secret + + # ----------------------------------------------------------------------------- # Database (PostgreSQL) # The defaults below work with a local Postgres.app installation.
app/controllers/billing_controller.rb
+10 -2
index 6386aaf..e1eba40 100644 --- a/app/controllers/billing_controller.rb +++ b/app/controllers/billing_controller.rb @@ -28,8 +28,13 @@ class BillingController < ApplicationController cancel_url: billing_url(billing: "cancel") ) redirect_to url, allow_other_host: true + rescue Stripe::AuthenticationError, Stripe::PermissionError => e + # A revoked/expired key or wrong-account key — a server-side misconfig, not a + # user error. Don't tell the user to "try again"; flag it loudly for ops. + Rails.logger.error("Stripe is misconfigured (checkout): #{e.class}: #{e.message}") + redirect_to billing_path, alert: "Billing is temporarily unavailable. Please try again later." rescue StandardError => e - Rails.logger.error("Stripe checkout failed: #{e.message}") + Rails.logger.error("Stripe checkout failed: #{e.class}: #{e.message}") redirect_to billing_path, alert: "Could not start checkout. Please try again." end @@ -40,8 +45,11 @@ class BillingController < ApplicationController return redirect_to billing_path, alert: "No subscription to manage yet." unless url redirect_to url, allow_other_host: true + rescue Stripe::AuthenticationError, Stripe::PermissionError => e + Rails.logger.error("Stripe is misconfigured (portal): #{e.class}: #{e.message}") + redirect_to billing_path, alert: "Billing is temporarily unavailable. Please try again later." rescue StandardError => e - Rails.logger.error("Stripe portal failed: #{e.message}") + Rails.logger.error("Stripe portal failed: #{e.class}: #{e.message}") redirect_to billing_path, alert: "Could not open the billing portal." end end