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
51 changes: 47 additions & 4 deletions STRIPE_COMMERCE_DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -893,7 +893,7 @@ Stop the merge if invalid Charge time reaches a target or fallback query, bindin

There is no schema, Rule, index, package, workflow, stored-data migration, or backfill. Valid Events, earlier admission precedence, target resolution, bindings, money checks, refund transitions, and existing `paidAt` values remain compatible. Invalid embedded time changes from target-capable behavior, fabricated current time, or an above-range retrying `500` to durable pre-target review. Already-processed historical Events remain authoritative on replay. Previously stored `paidAt` values are not inspected or repaired.

Residual work remains under #106: clock-skew and freshness policy, Charge/Event chronology, Checkout Session and Dispute embedded-time decisions, provider endpoint/API-version proof, provider retrieval and history repair, PAY-003B/C, reconciliation, alerts, protected release, and live verification. This child proves no provider truth, payment/refund success, historical correctness, or production behavior.
At A9 delivery, Checkout Session and Dispute embedded-time decisions remained deliberately outside this child. PAY-003A12 and A13 below now own Checkout Session structure and chronology. PAY-003A14 below supersedes only A9's historical compatibility of unusable Dispute-created evidence; Dispute/Event chronology remains unchanged. Other residual work remains under #106: clock-skew and freshness policy, provider endpoint/API-version proof, provider retrieval and history repair, PAY-003B/C, reconciliation, alerts, protected release, and live verification. This child proves no provider truth, payment/refund success, historical correctness, or production behavior.

PAY-003A9 delivery evidence must separately name source changed, synthetic tests passed, code merged, website published, `runmprc.com` verified, Firebase deployed and read back, Stripe/provider configured or acted on, production data changed, payment/refund/dispute activity, and live behavior verified. Source and synthetic tests prove none of the later states. Officer impact and officer documentation are both none: this server-only evidence boundary adds no officer screen, task, field, permission, approval, topology, or available recovery step. The root design, security, implementation, operations, issue, and officer guides remain compatible.

Expand Down Expand Up @@ -936,7 +936,7 @@ Stop the merge if impossible chronology reaches a target or fallback query, bind

There is no schema, Rule, index, package, workflow, stored-data migration, or backfill. Valid chronology, earlier admission precedence, target resolution, bindings, money checks, refund transitions, and existing `paidAt` values remain compatible. A newly delivered impossible Event changes from target-capable behavior to durable pre-target review. Already-processed historical Events remain authoritative on replay. Previously stored ledger, binding, refund, and `paidAt` values are not inspected or repaired.

Residual work remains under #106: current-clock freshness and skew policy, Checkout Session and Dispute embedded-time decisions, provider endpoint and API-version proof, provider retrieval and history repair, PAY-003B/C, reconciliation, alerts, protected release, and live verification. This child proves no provider truth, payment/refund success, historical correctness, or production behavior.
Residual work remains under #106: current-clock freshness and skew policy; Checkout Session expiry evidence; Dispute/Event chronology; provider endpoint and API-version proof; provider retrieval and history repair; PAY-003B/C; reconciliation; alerts; protected release; and live verification. This child proves no provider truth, payment/refund success, historical correctness, or production behavior.

PAY-003A10 delivery evidence must separately name source changed, synthetic tests passed, code merged, website published, `runmprc.com` verified, Firebase deployed and read back, Stripe/provider configured or acted on, production data changed, payment/refund/dispute activity, and live behavior verified. Source and synthetic tests prove none of the later states. Officer impact and officer documentation are both none: this server-only evidence-ordering boundary adds no officer screen, task, field, permission, approval, topology, or available recovery step. The remaining root design, security, implementation, operations, issue, and officer guides stay compatible.

Expand Down Expand Up @@ -1013,13 +1013,13 @@ flowchart TD

Text alternative: replay returns its stored result first; a new Checkout Event keeps every earlier admission result; invalid embedded Session time writes a fixed review without target work, while valid time may continue to existing business checks.

The expected result is durable, target-free review for unusable Session time and unchanged Checkout behavior for valid time. Success proof requires separately named registration and order tests across completion, asynchronous success, asynchronous failure, and expiry; missing and hostile shapes; parser-return Proxy evidence without coercion; inclusive epoch and Firestore-maximum boundaries; fixed redacted logs; valid outer ledger time; exact ledger-read and timestamp isolation; metadata, client-reference, matching-dual, legacy Session, Payment Link, ambiguous-target, binding-conflict, malformed-reference, claimed-missing-target, money, PaymentIntent, and predecessor isolation; every earlier admission precedence; valid missing-target retry; exact replay; richer historical-ledger preservation; and explicit refund, all Dispute lifecycle, unsupported Event, and, at A12 delivery, Session/Event chronology exclusions. PAY-003A13 below supersedes only that historical chronology exclusion. The complete webhook and Functions suites and prior PAY-003A2/A4/A8 through A11 matrices must remain green.
The expected result is durable, target-free review for unusable Session time and unchanged Checkout behavior for valid time. Success proof requires separately named registration and order tests across completion, asynchronous success, asynchronous failure, and expiry; missing and hostile shapes; parser-return Proxy evidence without coercion; inclusive epoch and Firestore-maximum boundaries; fixed redacted logs; valid outer ledger time; exact ledger-read and timestamp isolation; metadata, client-reference, matching-dual, legacy Session, Payment Link, ambiguous-target, binding-conflict, malformed-reference, claimed-missing-target, money, PaymentIntent, and predecessor isolation; every earlier admission precedence; valid missing-target retry; exact replay; richer historical-ledger preservation; and explicit refund, all Dispute lifecycle, unsupported Event, and, at A12 delivery, Session/Event chronology exclusions. PAY-003A13 below supersedes only A12's historical Session/Event chronology exclusion. PAY-003A14 below supersedes only A12's historical compatibility control for unusable Dispute-created evidence. The complete webhook and Functions suites and prior PAY-003A2/A4/A8 through A11 matrices must remain green.

Stop the A12 merge if invalid Session time reaches a target or fallback query, provider binding, business read or mutation, money or state check, timestamp construction, response detail, ledger detail, or log; if a rejected value is coerced; if an earlier result changes; if either inclusive boundary regresses; if valid lifecycle behavior changes; if Session/Event chronology changes inside A12 rather than a separately reviewed successor; or if a test needs a real credential, provider object or call, payment, customer or member record, or production service. Escalate to the platform owner and payment reviewer. Undo is one small reviewed revert of the single admission predicate, fixed reason, fixture evidence, separately named tests, the one reconciled PAY-003A9 compatibility control, and this section. Do not edit a provider object, processed ledger, binding, order, registration, or payment field as an undo path.

There is no schema, Rule, index, package, workflow, API, provider-configuration, migration, backfill, or history-reprocessing change. A newly delivered invalid or missing Session time changes from target-capable behavior to pre-target durable review. Valid new Events keep their current outcomes. Already-processed ledgers remain authoritative on replay. Existing business records, bindings, payment fields, and historical ledger rows are not inspected or repaired.

At A12 delivery, Session/Event chronology remained deliberately compatible: a structurally valid Session time later than the outer Event time still reached existing processing. PAY-003A13 below now owns that later-versus-equal-or-earlier comparison and its compatibility changes. Other residual work remains under #106: current-clock freshness and skew; Session `expires_at`; endpoint API-version proof; provider retrieval and history repair; already-paid missing-time repair; paid-after-cancellation policy; other Charge and Dispute decisions; PAY-003B/C; reconciliation; alerts; protected release; and live verification. A12 proves no provider truth, payment or refund success, historical correctness, or production behavior.
At A12 delivery, Session/Event chronology remained deliberately compatible: a structurally valid Session time later than the outer Event time still reached existing processing. PAY-003A13 below now owns that later-versus-equal-or-earlier comparison and its compatibility changes. Unusable Dispute-created evidence also remained deliberately compatible at A12 delivery; PAY-003A14 below now owns that structure check, while Dispute/Event chronology remains excluded. Other residual work remains under #106: current-clock freshness and skew; Session `expires_at`; endpoint API-version proof; provider retrieval and history repair; already-paid missing-time repair; paid-after-cancellation policy; Dispute/Event chronology; PAY-003B/C; reconciliation; alerts; protected release; and live verification. A12 proves no provider truth, payment or refund success, historical correctness, or production behavior.

PAY-003A12 delivery evidence must separately name source changed, synthetic tests passed, code merged, website published, `runmprc.com` verified, Firebase deployed and read back, Stripe/provider configured or acted on, production data changed, payment/refund/dispute activity, and live behavior verified. Source and synthetic tests prove none of the later states. Officer impact and officer documentation are both none: this server-only pre-target admission boundary adds no officer screen, task, field, permission, approval, topology, or available recovery step. The remaining root design, security, implementation, operations, issue, and officer guides stay compatible.

Expand Down Expand Up @@ -1067,6 +1067,49 @@ Residual work remains under #106: current-clock freshness and skew; Session `exp

PAY-003A13 delivery evidence must separately name source changed, synthetic tests passed, code merged, website published, `runmprc.com` verified, Firebase deployed and read back, Stripe/provider configured or acted on, production data changed, payment/refund/dispute activity, and live behavior verified. Source and synthetic tests prove none of the later states. Officer impact is none. Officer documentation is `None — this server-only pre-target evidence comparison adds no officer screen, task, field, permission, approval, topology, incident step, or available recovery action.` The remaining root design, security, implementation, operations, issue, and officer guides stay compatible.

### PAY-003A14 Dispute-created admission — SOURCE ONLY, NOT LIVE

PAY-003A14 [#592](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/592) closes the structural half of the Dispute-time residual. Its purpose is to reject a newly delivered supported Dispute Event when the embedded Dispute does not carry a usable `created` second. Stripe describes both [Dispute `created`](https://docs.stripe.com/api/disputes/object?api-version=2024-06-20) and [Event `created`](https://docs.stripe.com/api/events/object?api-version=2024-06-20) as object-creation time in seconds since the Unix epoch. Stripe's [Event-type reference](https://docs.stripe.com/api/events/types?api-version=2024-06-20) says `charge.dispute.created`, `charge.dispute.updated`, and `charge.dispute.closed` carry a Dispute in `data.object`. The installed Stripe SDK also types both fields as required numbers. Signed JSON parsing and type declarations do not provide runtime shape or range validation, so this source checks the embedded value explicitly. It does not prove the configured endpoint or API version, canonical provider history, exact dispute timing, or Dispute/Event chronology.

The embedded value must be a primitive safe integer from Unix epoch second `0` through Firestore maximum second `253402300799`, inclusive. Missing, null, negative, fractional, string, Boolean, object, array, non-finite, and larger values produce fixed target-free `needs_review:invalid_dispute_created`. The valid outer Event time remains the processed ledger's `stripeCreatedAt`; target fields remain null. The rejected embedded value is never coerced, converted to a timestamp, stored, returned, or logged.

Approvers are the platform owner and payment reviewer. Prerequisites are verified webhook signature handling; the processed-Event ledger; embedded Dispute realm admission; PAY-003A6 Event/status lifecycle admission; claimed-MPRC metadata-version admission; PAY-003A7 and A8 outer Event-time admission; signed synthetic fixtures; and the existing nullable ledger target fields. A new supported Dispute Event keeps this order:

1. Return an exact processed-Event replay without changing its stored result.
2. Check the outer Event realm.
3. Check the embedded Dispute realm.
4. Check that the Dispute status is compatible with the Event lifecycle.
5. Check an explicit claimed-MPRC metadata schema version.
6. Check the outer Event-created value.
7. Check the embedded Dispute-created value.
8. Only after those checks pass, resolve references and fallback targets.
9. Then evaluate ownership, provider bindings, object identity, currency, amount, and predecessor state.

```mermaid
flowchart TD
A["Verified supported Dispute Event"] --> B{"Already in the processed-Event ledger?"}
B -- "Yes" --> C["Return the stored result without changes"]
B -- "No" --> D{"Earlier realm, status, metadata, and outer-time checks pass?"}
D -- "No" --> E["Keep the existing earlier review result"]
D -- "Yes" --> F{"Embedded Dispute-created value is usable?"}
F -- "No" --> G["Write fixed review with no target"]
F -- "Yes" --> H["Continue to target, binding, money, and state checks"]
```

Text alternative: replay returns its stored result first; a new supported Dispute Event keeps every earlier admission result; unusable embedded creation evidence writes a fixed review without target work, while usable evidence may continue to existing business checks.

The expected result is durable, target-free review for unusable Dispute-created evidence and unchanged Dispute processing for usable evidence. Success proof requires separately named registration and order tests across created, updated, and closed lifecycles; missing and hostile shapes; post-parser Proxy evidence without coercion; inclusive epoch and Firestore-maximum boundaries; fixed redacted logs; valid outer ledger time; exact ledger-read and timestamp isolation; metadata-only, client-reference-only, matching-dual, legacy PaymentIntent, legacy Charge, unmatched, ambiguous-target, malformed-reference, conflicting-reference, binding-conflict, claimed-missing-target, object, currency, amount, Charge, PaymentIntent, and predecessor isolation; every earlier admission precedence; valid missing-target retry; exact replay; richer historical-ledger preservation; structurally valid later Dispute time; and explicit Checkout Session, refund Charge, unsupported Event, and unsupported dispute-like Event compatibility. These tests must reject coercing, wrong-field, wrong-gate, wrong-lifecycle, wrong-domain, lower-bound, upper-bound, off-by-one, wrong-reason, retryable, late-check, value-disclosure, timestamp-conversion, replay-recomputation, clock-freshness, chronology, and broad-prefix implementations. The complete webhook and Functions suites and prior PAY-003A2/A4/A5 through A13 matrices must remain green.

Stop the merge if unusable Dispute time reaches a target or fallback query, provider-binding read or write, business read or mutation, money or predecessor evaluation, timestamp construction, response detail, ledger detail, or log; if a rejected value is coerced; if an earlier admission result changes; if either inclusive boundary regresses; if a valid later Dispute time is rejected; if a claimed missing target remains retryable despite unusable evidence; if a valid missing target stops retrying; if the outer Event ledger time changes; if Checkout, refund, unsupported, or unsupported dispute-like Event behavior changes; or if a test needs a real credential, provider object or call, payment, refund, dispute, customer or member record, or production service. Escalate to the platform owner and payment reviewer.

Success proof is a reviewed source diff plus signed synthetic test output only. Undo is one small reviewed revert of the Dispute-only predicate and fixed reason, the shared synthetic fixture default, the PAY-003A9 and A12 compatibility corrections, the named A14 tests, and this section. Do not edit a Stripe object, processed ledger, binding, order, registration, dispute, or payment field as an undo path.

There is no schema, Rule, index, package, workflow, API, provider-configuration, migration, backfill, or history-reprocessing change. Newly delivered supported Dispute Events with unusable embedded creation evidence change from target-capable behavior to pre-target durable review. Valid new Events keep their current outcomes, including a structurally valid embedded time later than the outer Event time. Already-processed ledgers remain authoritative on replay. Existing business records, bindings, dispute fields, payment fields, and historical ledger rows are not inspected or repaired.

Residual work remains under #106: Dispute/Event chronology; current-clock freshness and skew; endpoint and API-version proof; provider retrieval and history repair; PAY-003B/C; reconciliation; alerts; protected release; and live verification. This child proves no provider truth, payment, refund, or dispute success, historical correctness, or production behavior.

PAY-003A14 delivery evidence must separately name source changed, synthetic tests passed, code merged, website published, `runmprc.com` verified, Firebase deployed and read back, Stripe or another outside provider configured or acted on, production data changed, checkout/payment/refund/dispute activity, and live behavior verified. Source and synthetic tests prove none of the later states. Officer impact is none. Officer documentation is `None — this server-only pre-target evidence structure adds no officer screen, task, field, permission, approval, topology, incident step, or available recovery action.` The remaining root design, security, implementation, operations, issue, and officer guides stay compatible.

Store actual total, discount, tax, shipping, Stripe customer reference if required, PaymentIntent ID, and Charge reference. An anomaly enters `payment_review`/quarantine and alerts operations; it never silently marks paid.

## 12. Payment and business state machines
Expand Down
8 changes: 8 additions & 0 deletions functions/stripeWebhook.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,14 @@ function validateEventAdmission(event, expectedLivemode) {
reason: 'invalid_event_created',
};
}
if (DISPUTE_EVENT_TYPES.has(event.type)
&& !isValidStripeCreated(event.data?.object?.created)) {
return {
ok: false,
expected: expectedLivemode,
reason: 'invalid_dispute_created',
};
}
if (CHECKOUT_EVENT_TYPES.has(event.type)
&& !isValidStripeCreated(event.data?.object?.created)) {
return {
Expand Down
Loading