Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions GITHUB_ISSUES.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ Every issue inherits `AGENTS.md` and the definition of done in `IMPLEMENTATION_P
| 15 | RACE-001 | Add transactional race-capacity reservations | P0 | L | proposed | PAY-002, PAY-003 event contract |
| 16 | MERCH-001 | Add SKU variants and transactional inventory reservations | P0 | L | proposed | PAY-002, PAY-003 event contract |
| 17 | PAY-004 | Make cancellation authoritative and replace reusable late Payment Links | P0 | L | proposed | PAY-002, PAY-003, RACE-001 |
| 18 | PAY-005 | Build idempotent refunds, disputes, and reconciliation | P0 | L | proposed | PAY-002, PAY-003 |
| 18 | PAY-005 | Build idempotent refunds, disputes, and reconciliation | P0 | L | partial: immediate amount/result containment tracked by #200/#204; complete lifecycle proposed | PAY-002, PAY-003 |
| 19 | MAIL-001 | Build an escaped, idempotent transactional email outbox | P1 | M | proposed | PAY-003 |
| 20 | DATA-001 | Replace confirmation bearer URLs and implement PII minimization | P1 | L | proposed | PAY-002, PAY-003 |
| 21 | DATA-002 | Create immutable, truthful waiver evidence | P1 | M | blocked_owner_decision | RACE-001, LEGAL-001 decision |
Expand Down Expand Up @@ -859,7 +859,7 @@ Do not mark local cancelled before considering the live Stripe Session and recov
## PAY-005 — Build idempotent refunds, disputes, and reconciliation

**Labels:** `priority:P0`, `type:security`, `type:reliability`, `area:stripe`, `size:L`
**Status:** Partial. Immediate amount-containment source/tests are tracked in [#200](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/200); the broader refund/dispute/reconciliation system remains proposed.
**Status:** Partial. Immediate amount containment is tracked in [#200](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/200), and resolved-result containment source/tests are tracked in [#204](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/204); the broader refund/dispute/reconciliation system remains proposed.
**Depends on:** PAY-002, PAY-003, AUTH-003 for final permissions

### Problem
Expand All @@ -868,6 +868,8 @@ Refund functions call Stripe without idempotency, immediately overwrite local st

PAY-005A1 [#200](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/200) is the narrow immediate containment outcome. It makes both current partial-refund entry points accept only a primitive positive safe-integer amount below the stored original cents, requires every admitted partial request to send that exact `amount` to Stripe, and derives partial/full classification from the explicit action. Invalid caller or stored values stop before Stripe construction/refund creation and before business-record writes. A provider exception returns a fixed result-not-confirmed, do-not-retry, and escalate message because Stripe may already have accepted the request. This does not add idempotency, remaining-refundable provider truth, concurrency control, event-finalized totals, capabilities, deployment, or live proof.

PAY-005A2 [#204](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/204) is the companion resolved-result containment outcome. Before Stripe construction, both endpoints require one exact stored `pi_` target, positive safe-integer original cents, and lowercase `usd`. After one provider call, only a plain `refund` object with a bounded `re_` ID, exact `succeeded` status, matching primitive PaymentIntent/currency, and a permitted positive safe-integer amount can reach the local success write. A partial must equal requested cents; an explicit full action accepts Stripe's returned full-remaining amount up to the original total and audits that validated actual amount. A thrown, malformed, mismatched, pending, action-required, failed, cancelled, or unknown Stripe result gets the same fixed do-not-retry response and no local success write is attempted. If the later local write reports an error or its acknowledgement is lost, the same fixed response is returned but the local record is unknown because the write may have committed. This still does not create a durable pre-send operation, idempotency, provider retrieval/reconciliation, event-finalized totals, concurrency control, capabilities, deployment, or live proof.

### Scope

- Create refund command records with stable ID, actor/capability, reason, requested integer cents, payment snapshot, status, and Stripe idempotency key.
Expand All @@ -892,10 +894,11 @@ PAY-005A1 [#200](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/200) is t

- Failure injection before/after Stripe response; duplicate/concurrent partials; pending/failed/succeeded events; full-after-partial; dispute lifecycle; reconciliation fixture for every category; repair idempotency.
- Staging finance review of report totals.
- PAY-005A2/#204: a 47-case pure validator matrix rejects coercion, prototypes, accessors, malformed/mismatched values, and every non-succeeded status while returning only a frozen validated projection. The initial dual-endpoint red matrix exposed 46 unsafe preflight/result cases, and two additional red cases exposed raw post-provider storage failures. The final 174 focused tests prove no Stripe call for invalid stored targets, one call for admitted requests, no local success write attempt for a rejected Stripe result, no success response after synthetic pre-commit or commit-then-error local-write failures, the same fixed do-not-retry response at both ambiguous boundaries, and validated full-remaining cents in the audit. The commit-then-error case proves why a lost acknowledgement leaves local state unknown and requires reconciliation.

### Agent handoff

Do not duplicate PAY-005A1 [#200](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/200). After its immediate containment, separate refund command/state from the broader reconciliation worker into sequential PRs. Never hand-edit Firestore to make reconciliation pass.
Do not duplicate PAY-005A1 [#200](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/200) or PAY-005A2 [#204](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/204). These are immediate containment only. Separate durable refund command/state from the broader reconciliation worker into sequential PRs. Never hand-edit Firestore to make reconciliation pass, and never retry an ambiguous provider result blindly.

---

Expand Down
1 change: 1 addition & 0 deletions GITHUB_ISSUE_SLICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ An agent-ready child has one observable outcome, one trust boundary, explicit de
| ID | Status/dependency | Bounded deliverable | Close evidence |
| --- | --- | --- | --- |
| PAY-005A1 | tracked by [#200](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/200); immediate containment only | Reject malformed/equal/over partial amounts in both existing admin Functions before Stripe/write side effects; every admitted partial request carries the exact integer amount and only explicit full refund omits it. | Red/green dual-endpoint matrices for caller/stored values, valid boundaries, provider failure, exact payload/classification; source/merge/deploy/provider/live states separated. |
| PAY-005A2 | source/tests tracked by [#204](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/204); A1; immediate containment only | Validate the stored PaymentIntent/currency/original before Stripe and accept a resolved Refund only when its type, ID, succeeded status, PaymentIntent, currency, and amount rule match. Partial cents must be exact; a full action accepts Stripe's positive full-remaining amount up to the original total. | Pure hostile-object matrix plus both endpoint no-write tests for malformed targets, malformed/mismatched/non-succeeded results, provider throw, one-call behavior, and validated actual-cents audit. Pre-commit and commit-then-error local-save tests return no success and the same fixed do-not-retry result; a lost acknowledgement leaves local state unknown and requires reconciliation. Source/merge/deploy/provider/live states stay separate. |
| PAY-005A | proposed; PAY-001D, PAY-002A1, PAY-002B2C3C, PAY-003B | Add idempotent refund commands, remaining-balance/concurrency validation, stable Stripe keys, and event-finalized totals. | Same/conflicting/concurrent partial/full/timeout and repeated `partially_refunded` status with increasing confirmed total tests. |
| PAY-005B | proposed; PAY-003B | Model per-dispute lifecycle for orders and registrations with finance alert references. | Duplicate/reordered warning lifecycle, inquiry escalation, created/updated/lost/won, rare lost-to-won late win, and `prevented` only under an explicitly validated upgraded webhook Event version. |
| PAY-005C | proposed; A/B | Build read-only scheduled/operator reconciliation and mismatch/quarantine report. | Orphan/mismatch/refund/event/counter synthetic report. |
Expand Down
2 changes: 1 addition & 1 deletion OPERATIONS_RUNBOOK.md
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,7 @@ Capture the commit, test IDs, timestamps, screenshots without secrets/PII, and t

## 12. Refund procedure

**Status: NOT AVAILABLE YET.** PAY-005A1 [#200](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/200) adds one source safeguard: an invalid partial amount stops instead of being omitted and becoming a full refund. It accepts only a positive whole number of cents below the stored original total, and only the explicit full-refund action may omit an amount. An unconfirmed provider result says do not retry and escalate because Stripe may already have accepted it. This source guard is not deployed or proven live and does not supply idempotency, remaining-balance/concurrency protection, verified final totals, finance capabilities, or approved policy. Do not use the steps below in production.
**Status: NOT AVAILABLE YET.** PAY-005A1 [#200](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/200) adds one source amount guard: an invalid partial amount stops instead of being omitted and becoming a full refund. PAY-005A2 [#204](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/204) adds one source returned-result guard: the stored payment/currency/original must be valid before Stripe, and local success requires a final succeeded Refund bound to that PaymentIntent and currency. A partial must return its exact requested cents. A full action omits the amount and records Stripe's validated full-remaining cents. A rejected Stripe result causes no local success write attempt. Any provider-result or later local-save uncertainty says do not retry and escalate; after a local-save error, both Stripe and local-record state require reconciliation because the write may have committed. These source guards are not deployed or proven live and do not supply idempotency, remaining-balance/concurrency protection, verified cumulative/event-finalized totals, finance capabilities, or approved policy. Do not use the steps below in production.

1. Authenticate with finance capability and satisfy recent-auth/MFA policy.
2. Locate the local business record and corresponding Stripe PaymentIntent/Charge.
Expand Down
Loading