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