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
@@ -37,6 +37,19 @@ ONDE_CLOUD_APP_SECRET=your-onde-app-secret-here
37 # ONDE_CLOUD_BASE_URL=https://cloud.ondeinference.com/v1
38
39
40 +# -----------------------------------------------------------------------------
41 +# Stripe — siGit Code Pro/Team billing (the /billing page + Checkout/Portal).
42 +# Separate from Onde Cloud billing. Billing is a no-op unless STRIPE_SECRET_KEY
43 +# is set. Use sk_test_… in dev; sk_live_… in production. Rotating the key in the
44 +# Stripe dashboard revokes the old one, so update this value too or Checkout
45 +# fails with "Expired API Key".
46 +# -----------------------------------------------------------------------------
47 +
48 +STRIPE_SECRET_KEY=sk_test_your-stripe-secret-key
49 +# Signing secret for the /stripe/webhooks endpoint (Stripe CLI or dashboard).
50 +STRIPE_WEBHOOK_SECRET=whsec_your-webhook-signing-secret
51 +
52 +
53 # -----------------------------------------------------------------------------
54 # Database (PostgreSQL)
55 # The defaults below work with a local Postgres.app installation.
app/controllers/billing_controller.rb
+10 -2
@@ -28,8 +28,13 @@ class BillingController < ApplicationController
28 cancel_url: billing_url(billing: "cancel")
29 )
30 redirect_to url, allow_other_host: true
31 + rescue Stripe::AuthenticationError, Stripe::PermissionError => e
32 + # A revoked/expired key or wrong-account key — a server-side misconfig, not a
33 + # user error. Don't tell the user to "try again"; flag it loudly for ops.
34 + Rails.logger.error("Stripe is misconfigured (checkout): #{e.class}: #{e.message}")
35 + redirect_to billing_path, alert: "Billing is temporarily unavailable. Please try again later."
36 rescue StandardError => e
32 - Rails.logger.error("Stripe checkout failed: #{e.message}")
37 + Rails.logger.error("Stripe checkout failed: #{e.class}: #{e.message}")
38 redirect_to billing_path, alert: "Could not start checkout. Please try again."
39 end
40
@@ -40,8 +45,11 @@ class BillingController < ApplicationController
45 return redirect_to billing_path, alert: "No subscription to manage yet." unless url
46
47 redirect_to url, allow_other_host: true
48 + rescue Stripe::AuthenticationError, Stripe::PermissionError => e
49 + Rails.logger.error("Stripe is misconfigured (portal): #{e.class}: #{e.message}")
50 + redirect_to billing_path, alert: "Billing is temporarily unavailable. Please try again later."
51 rescue StandardError => e
44 - Rails.logger.error("Stripe portal failed: #{e.message}")
52 + Rails.logger.error("Stripe portal failed: #{e.class}: #{e.message}")
53 redirect_to billing_path, alert: "Could not open the billing portal."
54 end
55 end