fix(billing): read Stripe keys from Rails credentials; rotate to valid key

Migrate STRIPE_SECRET_KEY / STRIPE_WEBHOOK_SECRET out of the server .profile and into Rails credentials (decrypted on the box by its existing master key). The production live key had been rolled in Stripe and revoked, so Checkout failed with "Expired API Key"; credentials now holds a valid restricted key scoped to recurring subscriptions + billing. The initializer and StripeService read credentials first, ENV second (dev fallback). Verified the new key resolves sigit_code_pro_monthly / _team_monthly and creates a subscription Checkout Session. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Seto Elkahfi committed Jun 30, 2026 at 22:18 UTC 2b675c1e3b9ef96cd7570b689fe06d5520abf448
3 files changed +25 -4
app/services/stripe_service.rb
+17 -2
index 991eeb0..cd19a4e 100644 --- a/app/services/stripe_service.rb +++ b/app/services/stripe_service.rb @@ -34,7 +34,21 @@ class StripeService class << self def configured? - ENV["STRIPE_SECRET_KEY"].present? + secret_key.present? + end + + # Stripe secret key — from Rails credentials (`stripe.secret_key`), falling + # back to ENV for local dev. Rotating the key in Stripe revokes the old one, + # so keep this value current or Checkout fails with "Expired API Key". + def secret_key + Rails.application.credentials.dig(:stripe, :secret_key).presence || + ENV["STRIPE_SECRET_KEY"].presence + end + + # Webhook signing secret, same credentials-then-ENV resolution. + def webhook_secret + Rails.application.credentials.dig(:stripe, :webhook_secret).presence || + ENV["STRIPE_WEBHOOK_SECRET"].presence end # Create a Checkout session for a user + plan; returns the redirect URL. @@ -74,7 +88,8 @@ class StripeService # Verify + parse a webhook payload into a Stripe::Event. def construct_event(payload, signature_header) - Stripe::Webhook.construct_event(payload, signature_header, ENV.fetch("STRIPE_WEBHOOK_SECRET")) + secret = webhook_secret or raise KeyError, "Missing Stripe webhook secret" + Stripe::Webhook.construct_event(payload, signature_header, secret) end # Upsert the local Subscription from a Stripe subscription object. Returns the
config/credentials.yml.enc
+1 -1
index 6b894af..d1533ef 100644 --- a/config/credentials.yml.enc +++ b/config/credentials.yml.enc @@ -1 +1 @@ -4FBLgT4lqYn6Xq573rcAdqYW8uoCip65xOaJcs9uI4+4GjgUy6mVxbbs9drNMkeR/hT1bmWxcc2yGahuZ4HCnCTHongcYSeRN+tq3ZKK6uBRtJ5n4VoF8bb79Y11XzpWYJRzI4+qI9XIwNBiCgB0LrLjJE1+1OPrphA26JBYPGq1CuepuIC1uhF6tRBTj3+mxjMLb9TZK/xnQPttlhPCryt4IC6Rl1223Yj7Hp3J4OKOOYisuK7PfpO/1HhCCnHVffeWfjOA+bunX0CMycaPQPNNgzEDQNy+3sr6pCRDU4PmIlQv2ED4ZNoldIyrdqvvXj7cyWmj8qe5nhoRRfskaLm3Pm6abN29DFxw9AnU/z4EXUCrcnd+dhfLPd2rW5X3qWGgHwRMounOc+HWHFR4snJR9gNAGbNrBZt9kfRPinB+pJDNVpvsKX+RUDsZ3HamouD4t951pc6MdzzSsAOYLXyeoNxkgK9pHAvfSHbGxnPwNR4X03wjhK/8--2sl/oC/jkTHhawD8--aK/HddPgwwngDeBQyxNY3g== \ No newline at end of file ++uuzge/fz5cak/4I5S2evwZeYm5qu/3zz94MxEpTo72a4X/Yuyn5xKULDCdjT65UFu4CYadvWbHtPBftrgxFIqRhniwaGkRpT/2z3ZAWAycU8qkNgkEtpy7Azu1OoVs4/IHJuTSklfh5cp3U9rkwQCe56Zn2pElfF8vzGsE1zo2AVdLdUCEmLrbHSX4yKPhauAR8Q1gh7KyuHkz+rPzXSclnNbDDZsv9iH8Ufao8YsAFzv6SvrI5vRNGR9SQXkLi42H6KaKoWIFlPCauiQH8l76Ot9YuJtVEEQ9schWQLrWpsvi+6oh+DRQjcmBTUhP5K5ge6qmlg+glaqMcfTuij50U/MPtjfwhizt4mg3qy3dNpuzen2Ginu1cWDCTCW2C25B0m1D4zLej4kEjoRqFjV61xwnly9fm51mj3l5xC3qppmj0JLf3BodLvxYTIQYCK7JNCKbUhXLnNdVStw==--fZoss+4YG+UXitvb--DS63/363pfwindrZHsFAjQ== \ No newline at end of file
config/initializers/stripe.rb
+7 -1
index 7b5ee88..b259c41 100644 --- a/config/initializers/stripe.rb +++ b/config/initializers/stripe.rb @@ -2,4 +2,10 @@ # siGit's own Stripe API key (siGit Code Pro/Team). Separate product line from # Onde Inference's billing, though it may share the Stripe account. -Stripe.api_key = ENV["STRIPE_SECRET_KEY"] if ENV["STRIPE_SECRET_KEY"].present? +# +# Source of truth is Rails credentials (`stripe.secret_key`); ENV is a fallback +# for local dev. (Inlined here rather than calling StripeService to avoid an +# autoload dependency during boot.) +stripe_key = Rails.application.credentials.dig(:stripe, :secret_key).presence || + ENV["STRIPE_SECRET_KEY"].presence +Stripe.api_key = stripe_key if stripe_key