Skip to content

PAY-003A: harden Stripe webhook event processing - #140

Merged
daliu merged 4 commits into
mainfrom
codex/issue-101-stripe-webhook-ingress-v2
Jul 13, 2026
Merged

daliu merged 4 commits into
mainfrom
codex/issue-101-stripe-webhook-ingress-v2

Conversation

@daliu

@daliu daliu commented Jul 13, 2026 •

Copy link
Copy Markdown
Contributor

Outcome

Stripe webhook source now fails closed when a payment event has a conflicting owner, incomplete money evidence, the wrong Stripe mode, an unsafe state transition, or an ambiguous local record. Exact event replay is idempotent, supported missing targets retry, unmatched refunds/disputes become durable review items, and accepted provider objects receive a server-only atomic binding so two records cannot claim the same Stripe object concurrently.

Source was merged as main commit 87bd121. It is not deployed Firebase behavior.

Tracks #101. Parent tracker: #106. #102 remains a separate follow-up for discount, tax, and shipping-adjustment policy.

Security invariant and failure behavior

  • Stripe signature verification uses the exact raw body and one webhook secret.
  • Expected test/live mode must be configured explicitly.
  • Metadata and client-reference claims must agree. A claimed target is authoritative and never falls back to another record or business type.
  • Legacy lookup succeeds only for one record of one type. Same-type, cross-type, or existing provider-binding ambiguity fails without a business mutation or final event ledger.
  • Valid ownership is atomically recorded in server-only stripeObjectBindings documents with the event ledger and business update. Unanchored refund/dispute objects are not bound unless their full transition evidence validates.
  • Checkout Session, PaymentIntent, Payment Link, Charge, amount, subtotal, total, and currency checks fail closed.
  • Refund amounts are cumulative and monotonic. Unknown legacy partial baselines quarantine; full cumulative refunds can still advance. Cancelled, comped, fulfilled, transferred, and already-refunded records are not silently resurrected.
  • Disputes require a valid dispute object, Charge, optional valid PaymentIntent, currency, positive bounded amount, known provider status, and deterministic ordering.
  • HTTP errors and structured logs omit request bodies, signatures, addresses, tokens, and raw exception messages.

Scope

  • functions/stripeWebhook.js
  • functions/stripeWebhook.test.js
  • functions/stripeHelpers.js (partially-refunded registration compatibility only)

Excluded: checkout creators, discount/tax/shipping adjustments, command idempotency, inventory/capacity, reconciliation workers, Firestore Rules, provider configuration, secrets, production data, and every public asset.

Test evidence

Node 20.20.2 on exact source head fd89a8d:

  • Focused signed webhook suite: 81/81 passed.
  • Complete Functions unit suite: 114 passed; one emulator-only suite skipped.
  • Functions ESLint: passed.
  • git diff --check: passed.
  • Secret/private-link and PROMO-001 — Disable unmodeled Stripe price adjustments #102 adjustment-boundary scans: passed.
  • Local Firebase emulator evidence is unavailable because this workstation lacks Firebase CLI/Java. Hosted Functions and Rules checks are required before merge.
  • Two independent exact-head security and adversarial reviews approved with no findings.
  • Hosted CI run 29243337795 passed frontend tests, callback and release gates, build, Functions lint/tests, and Firestore Rules on the exact head. Post-merge CI run 29243458623 passed the same three hosted job groups on exact main 87bd121.
  • The exact-head Netlify preview passed. It is public read-only preview evidence, not a production deployment.

No test contacts Stripe, Firebase production, or real member/payment data.

Schema, compatibility, and rollback

The stripeEvents inbox remains additive. The new stripeObjectBindings collection stores only provider object type/ID and the server-side target path/type. It is denied to browsers by the repository's default-deny Rules posture. No backfill is required: existing record fields are checked before a new binding is claimed, and ambiguous legacy data fails closed for repair.

Rollback is the four source commits. Existing binding documents are inert if the code is rolled back and must not be deleted without a separate reviewed migration.

Known residual work remains on #106: append-oriented canonical audit/dispute storage, TTL provider proof, reconciliation/reprocessing, checkout persistence/idempotency, inventory/capacity, and provider rehearsal.

Officer impact

No current officer steps change. Commerce and payment operations are still NOT AVAILABLE YET, and review-ledger tooling remains future work.

Officer documentation

None — this is an internal source-only payment safety boundary and does not make an officer procedure available. Existing officer guides already say commerce is unavailable; changing them now would falsely describe undeployed behavior.

Deployment evidence

  • Source: PR PAY-003A: harden Stripe webhook event processing #140 merged as exact main commit 87bd121.
  • Website: no public page change and no production publication attempted. Exact-head preview only.
  • Firebase: not deployed; no service-account or protected-release authority used.
  • Stripe/provider: no setting, endpoint registration, secret, live payment, refund, or dispute was inspected or changed.

@netlify

netlify Bot commented Jul 13, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for luminous-fox-7c393f ready!

Name Link
🔨 Latest commit fd89a8d
🔍 Latest deploy log https://app.netlify.com/projects/luminous-fox-7c393f/deploys/6a54bfa4f464a80007ff3e27
😎 Deploy Preview https://deploy-preview-140--luminous-fox-7c393f.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@daliu
daliu marked this pull request as ready for review July 13, 2026 10:37
@daliu
daliu merged commit 87bd121 into main Jul 13, 2026
7 checks passed
@daliu
daliu deleted the codex/issue-101-stripe-webhook-ingress-v2 branch July 13, 2026 10:38
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