Commit 8bcc320
feat(worker): Layer-3 payment prober — the continuous money heartbeat (flag-gated OFF) (#98)
Builds the Layer-3 payment-health synthetic the tooling forum identified as the
real gap (`grep instant_payment_probe` → none today). Forum verdict
docs/ci/FORUM-PAYMENT-E2E-TOOLING.md §4: the fastest, most-deterministic
money-path signal is an in-cluster iframe-free Go prober, NOT a browser driver.
Mirrors the auth_probe / deploy_probe / flow_synthetic pattern exactly.
payment_probe.go — a River periodic job (every 5 min), flag-gated
PAYMENT_PROBE_ENABLED (default OFF, fully inert until the operator lights it):
Prod-safe legs (non-charging, contract-only):
- checkout_reachable — POST /api/v1/billing/checkout → non-5xx (a
402/409/502 blocked-but-alive shape is a PASS while
Razorpay live-recurring is operator-blocked; only a
5xx crash fails).
- billing_state — GET /api/v1/billing → non-5xx.
- invoices_reachable — GET /api/v1/billing/invoices → non-5xx.
- webhook_security — POST /razorpay/webhook with a garbage UNSIGNED
payload MUST be rejected 400 invalid_signature
(positive proof the signature gate is live; an
accepted unsigned payload is a CRITICAL fail).
Optional upgrade leg (only when the TEST webhook secret + test plan id are
set; skips clean/degraded otherwise — NO live Razorpay, NO real money):
- upgrade_webhook_e2e — mint a fresh is_test_cohort=true team → inject a
correctly-signed TEST-mode subscription.charged
(HMAC-SHA256 raw body, the api's verifier scheme) →
assert teams.plan_tier flipped (the rule-12
downstream truth surface, NOT the webhook 200) →
reap the cohort team (always, even on failure).
Observability (rule 25, infra PR ships in lockstep):
instant_payment_probe_outcome_total{leg,result} + instant_payment_probe_latency_seconds{leg}
(lazy *Vec, primed in metrics_test.go) + the InstantPaymentProbe NR event
(cohort=synthetic, excluded from billing/revenue dashboards) + an audit_log
row + structured ERROR slog line on fail. result="degraded" is the
config-unset / slow-but-correct state and never pages — so the prober is
inert AND non-paging until the operator wires the flag + (for the upgrade
leg) the test webhook secret.
Tests: flag-off proven inert (zero probes, no HTTP, no DB); each leg's
pass/fail/degraded outcome; the unsigned-webhook-rejected + accepted-unsigned
security cases; the upgrade tier-flip pass + the no-flip / webhook-non-200
fail (rule-12 discipline); cohort reap on every upgrade path; signer parity
(64-hex HMAC matching the api verifier); the leg vocabulary registry; the
panic boundary. 97.9% per-func coverage on payment_probe.go.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>1 parent 247ef49 commit 8bcc320
7 files changed
Lines changed: 2168 additions & 0 deletions
File tree
- internal
- config
- jobs
- metrics
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
180 | 180 | | |
181 | 181 | | |
182 | 182 | | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
183 | 201 | | |
184 | 202 | | |
185 | 203 | | |
| |||
305 | 323 | | |
306 | 324 | | |
307 | 325 | | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
308 | 340 | | |
309 | 341 | | |
310 | 342 | | |
| |||
0 commit comments