Skip to content

PAY-005A1: prevent invalid partial refunds from becoming full - #201

Merged
daliu merged 1 commit into
mainfrom
codex/issue-200-partial-refund-containment
Jul 14, 2026
Merged

daliu merged 1 commit into
mainfrom
codex/issue-200-partial-refund-containment

Conversation

@daliu

@daliu daliu commented Jul 14, 2026 •

Copy link
Copy Markdown
Contributor

Outcome

Invalid partial-refund amounts now stop before Stripe or a business-record write. They can no longer lose their amount and silently become full refunds.

Both existing admin refund Functions use one non-coercing validator. Every admitted partial request sends the exact integer amount. Only an explicit full-refund action may omit it. An uncertain Stripe result returns a fixed message that says the result is unconfirmed, do not retry, and escalate.

Closes #200.

Financial-safety invariant

  • A partial amount must be a primitive positive safe integer in cents and strictly below the stored positive safe-integer original total.
  • Missing, null, zero, negative, equal, over-limit, fractional, non-finite, numeric-string, boolean, object/array, and unsafe-integer values fail closed.
  • Invalid caller or stored values fail before Stripe client construction/refund creation and before any Firestore mutation.
  • Valid partial requests always include exact Stripe amount.
  • Only explicit refund_full omits amount.
  • Status and audit action derive from the explicit action, not amount truthiness.
  • Provider errors expose no Stripe detail. Because the provider may already have accepted a timed-out request, the fixed result says not to retry and to escalate; local state remains unchanged for later reconciliation.

Red/green evidence

  • Before the runtime fix, the new endpoint matrix failed 52 cases. Equal, over-limit, coerced, non-finite, and malformed stored values could resolve as refunds.
  • After the fix, focused tests pass: 2 suites / 99 tests.
  • Full Functions pass: 17 suites / 1,077 tests (2 suites / 32 emulator-only tests skipped by the existing harness).
  • Functions lint passes.
  • Frontend Jest passes: 11 suites / 198 tests.
  • Frontend lint baseline passes: 104 files; 125 reviewed legacy errors and 7 reviewed legacy warnings unchanged.
  • Firestore Rules emulator passes: 4 suites / 348 tests.
  • SPA navigation passes: 11 tests.
  • Release/workflow/artifact safety passes: 42 tests.
  • Diagnostic production build passes.
  • git diff --check passes.
  • Independent security, test, and backup-officer reviews approved exact commit 346df6c6726ca46ac1f46e5b36af5364865e8ff1 and full diff SHA-256 091ad489adec4608f451569a7af3eda24de599e2c38018432d920616ad77ca1b with no findings.

Officer impact

After the exact Functions are safely deployed, an invalid partial amount will stop instead of becoming a full refund. If Stripe's result cannot be confirmed, the page will say not to retry and to escalate to the treasurer and platform owner.

Officer documentation

  • docs/officers/EVENTS_SHOP_MEMBERS.md

The procedure and diagram are marked SOURCE ONLY, NOT LIVE. They use made-up records and Stripe test mode only after protected staging and both Function readbacks exist.

Deployment evidence

  • Source changed: yes, exact commit 346df6c6726ca46ac1f46e5b36af5364865e8ff1.
  • Tests passed: yes locally, in PR CI run 29298036773, and in exact post-merge CI run 29298127585.
  • Code merged: yes, squash commit caae31ff427e91325d34a6c901faf81ad1dc49df.
  • Website published / runmprc.com verified: not applicable; no website runtime changed and nothing was published.
  • Firebase deployed: no deployment attempted. The current protected release plan does not include these two refund Functions.
  • Stripe configured/tested: no provider setting changed and no provider call was made.
  • Production behavior verified: no. No real refund, payment, order, registration, member, card, provider identifier, or production data was used.

Remaining PAY-005 work

This is immediate containment, not a safe production refund system. Stable command identity/idempotency, current remaining-refundable Stripe truth, concurrency control, pending/event-finalized totals, reconciliation, finance capability/MFA/recent-auth, approved policy, protected deployment, provider readback, and live verification remain open. An unconfirmed provider result must be reconciled, never blindly retried.

@netlify

netlify Bot commented Jul 14, 2026 •

Copy link
Copy Markdown

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

Name Link
🔨 Latest commit 346df6c
🔍 Latest deploy log https://app.netlify.com/projects/luminous-fox-7c393f/deploys/6a558d5be5f95d00087d6d9a
😎 Deploy Preview https://deploy-preview-201--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 merged commit caae31f into main Jul 14, 2026
9 checks passed
@daliu
daliu deleted the codex/issue-200-partial-refund-containment branch July 14, 2026 01:16
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.

PAY-005A1 — Prevent invalid partial refunds from becoming full refunds

1 participant