Skip to content

feat(deploy): wire Stripe credentials through ExternalSecrets, gated off - #876

Merged
lilyshen0722 merged 2 commits into
mainfrom
feat/stripe-secrets-wiring
Aug 6, 2026
Merged

feat(deploy): wire Stripe credentials through ExternalSecrets, gated off#876
lilyshen0722 merged 2 commits into
mainfrom
feat/stripe-secrets-wiring

Conversation

@lilyshen0722

Copy link
Copy Markdown
Contributor

Wires the three Stripe env vars the merged billing code reads (#875) through the
existing api-keys ExternalSecret, with the remote refs gated behind a new
billing.enabled flag that defaults off.

Why the gate exists

api-keys.yaml already carries this warning above the Sentry block:

ESO rejects the whole ExternalSecret when any referenced remote key is missing.

So adding an unconditional commonly-dev-stripe-secret-key ref before that
secret exists would not fail quietly on Stripe alone — it would stop api-keys
syncing entirely, taking JWT, LiteLLM, and every OAuth credential with it.
Gating means this PR is safe to merge and deploy right now, before any
credential is in place.

A subtlety worth writing down: an empty secret container is not enough.
gcloud secrets describe succeeds on a secret with zero versions while ESO
still fails to resolve it. The check is gcloud secrets versions list <name>.

Why the deployment env vars are not gated

Every secretKeyRef is optional: true, so with the flag off the vars are
simply absent — and billingService.STRIPE_ENABLED() is
Boolean(process.env.STRIPE_SECRET_KEY), so checkout, portal, and the webhook
all disable together. There is no state where we accept a webhook we cannot
verify. Keeping them unconditional makes enabling billing a one-line values
change with no template edit to forget.

Secret Manager state

Containers are created and IAM is already sufficient — commonly-secrets-sa
holds roles/secretmanager.secretAccessor at the project level, and the
existing secrets carry no per-secret policy, so nothing extra was granted.

secret populated
commonly-dev-stripe-price-id ✅ v1
commonly-dev-stripe-secret-key ⬜ awaiting operator
commonly-dev-stripe-webhook-secret ⬜ awaiting operator

The price id is not secret, but it lives in Secret Manager anyway so swapping
test credentials for live ones is a versions add rather than a chart edit and
redeploy.

Verification

  • helm template with the flag off: zero stripe refs in the
    ExternalSecret, commonly-dev-jwt-secret still present.
  • helm template with the flag on: all three refs render.
  • Backend env vars render as 3 in both states, as intended.
  • helm lint clean in both states.

Flipping billing.enabled: true in values-dev.yaml is the follow-up, once
the two remaining secrets have versions.

lilyshen0722 and others added 2 commits August 6, 2026 01:49
Adds the three env vars billing needs — STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET,
STRIPE_PRICE_ID — sourced from Secret Manager via the existing api-keys
ExternalSecret.

The remote refs are gated behind `billing.enabled` (default false), following
the errorTracking precedent directly above them. That gate is not stylistic:
ESO rejects the ENTIRE ExternalSecret when any referenced remote key is
missing, so referencing a Stripe secret before it exists would stop api-keys
syncing altogether and take JWT, LiteLLM and every OAuth credential down with
it. An empty secret container is not enough either — `describe` succeeds while
`versions list` is empty, and ESO needs a version.

The deployment env vars are deliberately NOT gated. Every ref is
`optional: true`, so with the flag off the keys are simply absent and
billingService.STRIPE_ENABLED() reports false, which makes /api/billing/*
return 503 billing_not_configured rather than half-working. Keeping them
unconditional means enabling billing is a one-line values change with no
template edit to forget.

The price id is not secret but lives in Secret Manager anyway, so swapping test
credentials for live ones is `gcloud secrets versions add` rather than a chart
edit and redeploy.

Rendered both ways: flag off yields zero stripe refs with jwt-secret intact;
flag on yields all three. helm lint clean in both states.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XeUH4HVDsDHYPsHJthXjB8
…ed present

Flipped only after confirming each of commonly-dev-stripe-{secret-key,
webhook-secret,price-id} has an ENABLED version, and that each value carries
the expected prefix with no trailing whitespace. The whitespace check is not
ceremony: a newline captured by `echo` instead of `printf '%s'` survives into
the secret and surfaces later as a webhook signature rejection that looks
nothing like its cause.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XeUH4HVDsDHYPsHJthXjB8
@lilyshen0722
lilyshen0722 merged commit cda70e3 into main Aug 6, 2026
11 checks passed
@lilyshen0722
lilyshen0722 deleted the feat/stripe-secrets-wiring branch August 6, 2026 09:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant