DEV Community

Cover image for Your CI is green. Your billing logic is broken.
Matthew
Matthew

Posted on • Originally published at prodverdict.com

Your CI is green. Your billing logic is broken.

User paid. Stripe looked fine. The app still gated them.

They had an active subscription. The checkout page worked. Tests passed. But has_paid_access in Postgres stayed false, so the user hit the paywall anyway.

This is not a rare edge case. It is the default failure mode when you ship billing logic with AI tools and mock Stripe in tests.

How the bug actually happens

The sequence is usually boring:

  1. Agent writes checkout and a webhook handler for customer.subscription.created.
  2. Tests mock Stripe. CI goes green.
  3. invoice.payment_failed or customer.subscription.deleted never updates the DB flag.
  4. Stripe says active. Your app says free.

Nothing in a unit test suite checks both systems at once. You are testing each side in isolation. The bug lives in the gap between them.

Stack Overflow's 2025 survey put AI coding tool adoption around 84%. Veracode found roughly 45% of AI-generated code samples contained OWASP Top 10 issues. Only about 33% of developers say they trust AI accuracy. Speed went up. The intersection of billing and database state did not get safer.

What v0.10.0 changed

Before v0.10, showing someone this bug took four steps: clone the SDK, cd into it, pass config flags, point at a fixtures directory. Fine for docs. Too much friction for a Reddit comment or a tweet.

v0.10.0 bundles the revenue-leak fixture inside the CLI:

npx prodverdict@0.10.0 demo
Enter fullscreen mode Exit fullscreen mode

One command. No git clone. No Stripe keys. You get a FAIL on purpose: active subscription, has_paid_access false.

Two other commands round out discovery:

npx prodverdict scan    # static repo analysis, no credentials
npx prodverdict init --mcp --cursor-rule   # auto-detects stack from package.json
Enter fullscreen mode Exit fullscreen mode

Architecturally, v0.10 adds a discovery layer on top of the existing engine. runContracts() still powers everything. The new commands just remove the setup tax before someone sees value.

Billing state gap — before and after ProdVerdict

The fix: check the intersection in CI

ProdVerdict compares billing state (Stripe or Paddle) against who your database thinks has paid access. Rules only. No LLM in the evaluation path. Missing credentials = fail, not a silent pass.

After the demo, wire it into your repo:

npx prodverdict init --mcp --cursor-rule
export STRIPE_SECRET_KEY=rk_test_...
export DATABASE_URL=postgresql://readonly:...@host/db
npx prodverdict check access
Enter fullscreen mode Exit fullscreen mode

Block merges in GitHub Actions:

- uses: prodv-dev/prodverdict-action@v0.10.0
  with:
    config: ./prodverdict.yml
    contract: access
  env:
    STRIPE_SECRET_KEY: ${{ secrets.STRIPE_SECRET_KEY }}
    DATABASE_URL: ${{ secrets.DATABASE_URL }}
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Enter fullscreen mode Exit fullscreen mode

Secrets stay on your runner. ProdVerdict cloud never sees subscription rows.

Other contracts (after access is wired)

Once the billing check is trusted, add more: config (env drift), migration (unsafe Postgres DDL), boundary (mass-assignment), webhook (signature + idempotency lint), restore (backup smoke tests). Run all six with npx prodverdict check all.

Start with access. It ties directly to revenue. The rest is expansion.

Try it

npx prodverdict@0.10.0 demo
Enter fullscreen mode Exit fullscreen mode

Free CLI, Action, and local MCP on private repos. I built this after catching the same bug one too many times. Curious if you have shipped this class of mismatch before.

Top comments (0)