Skip to content

PAY-003A11 — Persist Checkout paidAt from admitted Event evidence #586

Closed

Description

@daliu

Outcome

When a newly admitted Checkout payment transition fills a missing paidAt, persist the already admitted outer Stripe Event time. Do not invent financial-evidence time from the webhook delivery/server clock. Preserve every existing paidAt value.

This is an atomic child of #106 and follows released PAY-003A10 / #584.

Defect

Exact main 1c85ec08c274ad9077118d4daf3a6ac9dad118bd validates every supported Checkout Event created value before target work. Three branches in successfulPaymentTransition still use:

paidAt: record.paidAt || Timestamp.now()

The affected branches are ordinary payment confirmation, payment observed after a partially-refunded/refunded state, and payment observed after a fulfilled/transferred state. A delayed or retried webhook can therefore persist receipt time as though it were provider payment evidence.

Priority and dependency state

Invariant and transition order

A new supported Checkout Event retains the existing order:

  1. Processed Event replay.
  2. Outer Event realm.
  3. Checkout Session lifecycle admission.
  4. Explicit MPRC metadata schema.
  5. Valid outer Event-created integer and Firestore range.
  6. Target, ownership, and provider binding.
  7. Mode, money, payment-status, PaymentIntent, and predecessor-state rules.
  8. Branch-specific successful-payment transition.
  9. Only when that transition needs to fill a missing paidAt, project the exact admitted Event second with Timestamp.fromMillis(event.created * 1000).

Existing paidAt remains authoritative. updatedAt, audit/action, processing, exception, and other operational times remain server/action-clock values. This adds no new Event admission or business outcome.

Exact implementation scope

  1. functions/stripeWebhook.js
    • replace only the three missing-paidAt receipt-clock fallbacks in successfulPaymentTransition.
  2. functions/stripeWebhook.test.js
    • one separately named PAY-003A11 Checkout payment-time projection block plus minimum local helper reuse/addition.
  3. STRIPE_COMMERCE_DESIGN.md
    • one separately named source-only PAY-003A11 section immediately after A10.

Required tests

  • Trustworthy test-only RED on exact main: unseeded registration and order confirmations, with Event time distinct from the mocked server clock, must currently store the wrong time while retaining payment_confirmed.
  • Registration and order completion project the exact admitted Event time.
  • Delayed asynchronous success projects the exact admitted Event time.
  • Payment observed after partially-refunded/refunded and fulfilled/transferred states projects the Event time without changing the existing branch outcome.
  • Existing paidAt is never overwritten.
  • Refund-first Charge-derived paidAt remains preserved.
  • Exact replay remains first and does not repair historical records.
  • Admitted epoch and maximum Event seconds project without off-by-one or local-clock substitution.
  • Unpaid completion, failure, expiry, and paid-after-cancellation exclusions do not fabricate paidAt.
  • Operational updatedAt, audit, binding, ledger, and processing times remain server/action-clock values.
  • Complete webhook and Functions suites remain green.

Explicit exclusions

No Checkout Session-created validation or chronology; no current-clock freshness/skew policy; no already-processed or already-paid missing-time repair; no paid-after-cancellation policy change; no Charge or Dispute admission change; no provider/API-version proof or retrieval; no history reprocessing, migration, or backfill; no schema, Rules, index, package, or workflow change; no PAY-003B/C, reconciliation, alert, reservation, inventory, or outbox work; no officer procedure; no Firebase/Stripe deployment or configuration; no provider action; no production-data change; no payment/refund/dispute action; no live-behavior claim. Preserve active #571/#504 and draft #462 byte-for-byte.

Compatibility and migration

No schema, migration, backfill, dependency, API, or configuration change. Existing processed Events and existing paidAt values remain immutable. Historical records with receipt-clock values are not reprocessed or repaired.

Officer impact: None — this is a server-only distinction between provider evidence time and operational receipt time. It adds no officer screen, task, field, permission, approval, or available recovery step.

Officer documentation: None — the current money and release guides already prohibit manual financial-record repair and separate source, deployment, provider, data, and live proof. Engineering truth changes only in STRIPE_COMMERCE_DESIGN.md.

Deployment evidence: None at issue creation. Source/tests/merge will not be described as website publication, runmprc.com verification, Firebase deployment, Stripe/provider configuration, production-data change, payment/refund/dispute activity, or live-behavior proof.

Activity

  1. added
    priority:P0Launch blocker or urgent security risk
    type:securitySecurity or privacy boundary
    area:firebaseFirebase services and data
    size:SSmall focused issue
    area:stripeStripe payments and financial lifecycle
    type:testingTest infrastructure and quality gates
    status:in-progressClaimed and actively being implemented
    on Aug 2, 2026
  2. self-assigned this
    on Aug 2, 2026
  3. daliu commented on Aug 2, 2026

    @daliu
    ContributorAuthor

    CLAIMED by /root at 2026-08-02T00:49:44Z on branch codex/issue-586-checkout-paid-at, exact released base 1c85ec08c274ad9077118d4daf3a6ac9dad118bd.

    Exact ownership:

    1. functions/stripeWebhook.js
      • only the three missing-paidAt Timestamp.now() fallbacks inside successfulPaymentTransition; project the already admitted outer Event second with Timestamp.fromMillis(event.created * 1000).
    2. functions/stripeWebhook.test.js
      • one separately named PAY-003A11 Checkout payment-time projection block plus minimum local helper reuse/addition.
    3. STRIPE_COMMERCE_DESIGN.md
      • one separately named PAY-003A11 Checkout payment-time projection source-only section immediately after A10.

    Invariant: after all existing replay, realm, Session-lifecycle, metadata, Event-created, target/binding, mode/money/payment-status/PaymentIntent, and predecessor checks, only a successful-payment branch that fills a missing paidAt projects the admitted Event time. Existing paidAt remains authoritative. Operational updatedAt, audit/action, ledger/processing, exception, and binding times remain server/action-clock values. No admission or business outcome changes.

    Trustworthy RED will be test-only on this exact base: unseeded registration and order confirmations with Event time deliberately distinct from the mocked receipt clock must retain payment_confirmed but expose that current source stored receipt time instead of Event evidence.

    Immutable exclusions: Checkout Session-created validation/chronology; current-clock freshness/skew; already-processed or already-paid missing-time repair; paid-after-cancellation policy; Charge/Dispute admission; provider/API-version proof/retrieval; history repair/reprocessing/migration; schema/Rules/indexes/packages/workflows; PAY-003B/C; reconciliation/alerts/reservations/inventory/outbox; officer docs; Firebase/Stripe deployment/configuration; provider calls/actions; production data; payment/refund/dispute/live work; active #571/#504; draft #462.

    The empty remote branch was pushed from exact released main before this marker. Live issue, PR, branch, dependency, and path searches found no competing paidAt claim. PAY-003A10/#584 is COMPLETE/CLOSED/CLAIM RELEASED. The higher-exposure Router P0 remains blocked by #571 active ownership of a required test path. No source, test, or documentation edit preceded this claim.

    Claim remains active through trustworthy RED, implementation, independent exact-head review, hosted CI, merge, exact-main/deployment truth audit, closure, and explicit release.

    Officer impact: None — server-only Stripe evidence-time projection.

    Officer documentation: None — no officer procedure, screen, field, permission, approval, topology, or available recovery step changes. Only STRIPE_COMMERCE_DESIGN.md changes.

    Deployment evidence: none at claim time. Source/tests/merge will not be described as website publication, runmprc.com verification, Firebase deployment, Stripe/provider configuration, production-data change, payment/refund/dispute activity, or live behavior.

  4. daliu commented on Aug 2, 2026

    @daliu
    ContributorAuthor

    TRUSTWORTHY RED on exact base 1c85ec08c274ad9077118d4daf3a6ac9dad118bd.

    Only functions/stripeWebhook.test.js is modified. functions/stripeWebhook.js and STRIPE_COMMERCE_DESIGN.md remain byte-identical to the base. Test-only binary diff SHA-256: 1e0ad263309df826acec22c11c1b78daf952aba47d903d44ffa5a7fe26a6a3da.

    Exact Node 20 focused command:

    npm --prefix functions run test:run -- --runInBand --testNamePattern=PAY-003A11

    Observed result: 2 failed and 7,107 skipped.

    Both separately named registration and order cases retained the existing successful outcome payment_confirmed. Each expected missing paidAt to equal the admitted Event evidence 1700000000000 ms. Released source instead stored the mocked receipt-clock values 1800000000001 and 1800000000006 ms. Status and paymentStatus were still paid. This isolates the defect to financial-time projection rather than admission, target resolution, money validation, or business outcome.

    No credential, provider object, production service, customer/member record, Firebase deployment, Stripe call/action, production-data change, payment/refund/dispute activity, or live surface was used.

    Officer impact: None — synthetic server-boundary proof only.

    Officer documentation: None — no officer procedure changes.

    Deployment evidence: test-only local RED; no source delivery, merge, website publication, runmprc.com verification, Firebase deployment, Stripe/provider configuration, production behavior, or live verification.

  5. daliu commented on Aug 2, 2026

    @daliu
    ContributorAuthor

    REVIEW CANDIDATE pushed at exact head 3f93a9af6eeb1c4b1965fbb2ed99265ebb0444e5 on base 1c85ec08c274ad9077118d4daf3a6ac9dad118bd.

    Candidate tree: 65ce5004636699a0b7f56ee1a787bcf9107fcdc1. Exact binary patch SHA-256: 9c476f07b195a1111a48891ef0e0ce93eb703c7e776d2664b089b8e8ea98afb4. Worktree clean; git diff --check passes; exact diff is limited to the three claimed files.

    Source outcome: the three successful Checkout branches that fill missing paidAt now project the already admitted outer Event second with Timestamp.fromMillis(event.created * 1000). Ordinary confirmation, payment observed after refund, and payment observed after fulfillment/transfer retain their exact existing statuses, review flags, and outcomes. Existing paidAt, exact replay, already-paid no-repair behavior, excluded paths, and every operational clock remain unchanged.

    Trustworthy RED remains recorded at test-only diff 1e0ad263309df826acec22c11c1b78daf952aba47d903d44ffa5a7fe26a6a3da: both domain cases retained payment_confirmed but stored mocked receipt-clock milliseconds instead of the distinct admitted Event milliseconds on released source.

    Green verification on Node 20 and only demo Firebase project IDs:

    • PAY-003A11 focused: 22/22.
    • Complete webhook file: 603/603.
    • Functions lint: passed.
    • Complete Functions Jest: 7,065 passed, 64 expected skips.
    • Frontend Jest: 951/951.
    • Workflow/release/Firebase-verification/artifact/dependency safety: 86/86.
    • SPA safety: 11/11.
    • Frontend lint baseline: 118 files, exact reviewed legacy baseline.
    • Both workflow YAML parses: passed.
    • Diagnostic production build: compiled successfully without sitemap regeneration.
    • Firestore Rules emulator: 418/418 on demo-rules-test.
    • Commerce command-journal emulator: 63/63 on demo-pay002b2-test.
    • Dependency audit baseline unchanged: root full 59, root production 4, Functions 8; no dependency changed or forced upgrade applied.

    Self-review: no authorization or App Check change; Event time is already a primitive safe integer within the Firestore range before these branches; exact replay remains first; all statuses/outcomes are asserted; no request body, token, Checkout URL, address, DOB, emergency contact, provider secret, timestamp detail, or production record is newly logged; no schema, Rules, index, package, workflow, API, migration, backfill, historical repair, or production fallback changes. The tests use only signed synthetic fixtures and mocked provider boundaries.

    Independent exact-head security/runtime, compatibility/test, and documentation/backup-officer reviews are in progress. Do not merge until all return GO and hosted CI is green.

    Officer impact: None — server-only Stripe evidence-time projection.

    Officer documentation: None — no officer screen, task, field, permission, approval, topology, data movement, deployment step, or available recovery procedure changes. STRIPE_COMMERCE_DESIGN.md contains the source-only engineering handoff.

    Deployment evidence: source changed and local synthetic tests passed. Code is not merged. Website publication, runmprc.com, Firebase deployment/readback, Stripe/provider configuration or action, production-data change, migration, payment/refund/dispute activity, and live behavior are not changed or verified.

  6. daliu commented on Aug 2, 2026

    @daliu
    ContributorAuthor

    INDEPENDENT EXACT-HEAD REVIEWS — all GO on 3f93a9af6eeb1c4b1965fbb2ed99265ebb0444e5, tree 65ce5004636699a0b7f56ee1a787bcf9107fcdc1, patch SHA-256 9c476f07b195a1111a48891ef0e0ce93eb703c7e776d2664b089b8e8ea98afb4.

    1. Security/runtime: GO. Verified admission and transaction order, replay/idempotency, safe timestamp range, all three and only three fallbacks, existing-value preservation, operational clocks, unchanged authorization/App Check/provider bindings, redacted logging, and no schema/migration/production fallback.
    2. Compatibility/tests: GO. Independently verified the recorded test-only RED and 22/22 final matrix across both domains, async success, all three branch families, real nullable shape, refund-first preservation, replay/no repair, boundaries, exclusions, exact outcomes, operational clocks, and unchanged A8-A10 behavior. Complete webhook: 603/603.
    3. Documentation/backup officer: GO. Verified the source-only NOT LIVE section is factual and contains purpose, approvers, prerequisites, steps, expected result, stop conditions, success proof, undo, escalation, Mermaid/text alternative, compatibility/migration, residuals, and separate delivery truths. No officer-guide or other root-doc change is warranted.

    No reviewer made a file edit or remote mutation. No finding remains. Hosted PR diff self-review and required CI remain before merge.

    Officer impact: None — server-only evidence-time projection.

    Officer documentation: None — no officer procedure changes; the source-only engineering handoff is complete.

    Deployment evidence: source changed and local synthetic checks passed; not merged, published, deployed, provider-configured, applied to production data, or live-verified.

  7. removed their assignment
    on Aug 2, 2026
  8. daliu commented on Aug 2, 2026

    @daliu
    ContributorAuthor

    FINAL — COMPLETE / CLOSED / CLAIM RELEASED at exact released main 51723b487ec67bb74620c97e47449ae1319f0daa.

    PR #587 is merged: #587

    Release identity:

    • Exact base: 1c85ec08c274ad9077118d4daf3a6ac9dad118bd.
    • Final reviewed head: 3f93a9af6eeb1c4b1965fbb2ed99265ebb0444e5.
    • Merge/main: 51723b487ec67bb74620c97e47449ae1319f0daa.
    • Reviewed and merged tree: 65ce5004636699a0b7f56ee1a787bcf9107fcdc1 — identical.
    • Final base-to-head binary patch SHA-256: 9c476f07b195a1111a48891ef0e0ce93eb703c7e776d2664b089b8e8ea98afb4.
    • Remote issue branch is absent. Issue is closed, the in-progress label is removed, and the assignee is cleared.

    Delivered outcome: when any of the three existing successful Checkout transition families fills missing paidAt, it now stores Timestamp.fromMillis(event.created * 1000) from the already admitted outer Event evidence. Ordinary confirmation, payment observed after refund, and payment observed after fulfillment/transfer retain their existing statuses, review flags, and outcomes. Existing paidAt, exact replay, already-paid no-repair behavior, excluded paths, operational clocks, and historical records remain unchanged.

    Review disposition: self-review and three independent exact-head reviews are GO with no remaining finding. Security/runtime verified admission, replay, bounds, bindings, state, operational clocks, logging, and no migration. Compatibility/test verified the trustworthy RED and mutant-sensitive 22-test matrix across both domains and all three source-line families. Documentation/backup-officer verified the complete source-only NOT LIVE handoff and the no-officer-guide conclusion.

    Verification:

    • Trustworthy test-only RED: 2 failures on released source while both outcomes remained payment_confirmed; stored receipt ticks differed from admitted Event milliseconds.
    • PAY-003A11: 22/22.
    • Complete webhook: 603/603.
    • Functions lint and exact-head full Functions: 7,065 passed, 64 expected skips.
    • Frontend: 951/951; SPA safety: 11/11.
    • Workflow/release/Firebase-verification/artifact/dependency safety: 86/86.
    • Firestore Rules demo emulator: 418/418.
    • Commerce command-journal demo emulator: 63/63.
    • Frontend lint baseline, both workflow YAML parses, and diagnostic build: passed.
    • Dependency findings unchanged: root full 59, root production 4, Functions 8; no dependency changed or forced upgrade applied.
    • PR CI run 30726546061: all five repository jobs green.
    • Exact-main CI run 30726642299: all five repository jobs green.

    Merge policy: ordinary merge was attempted and refused only by the independent GitHub-approval rule after every hosted check and read-only review was green. The administrator merge path then merged the exact reviewed head. Its command reported a local worktree-switch failure because another checkout owns main; remote verification proved the merge itself succeeded, the branch was deleted, and main/tree identity is exact.

    Deployment truth, kept separate:

    • Source changed: yes.
    • Tests passed: yes.
    • Code merged: yes, exact main above.
    • Website published for this change: no.
    • Netlify Deploy Preview 6a6e98c893f48300081cfbd2: ready for the reviewed head, context deploy-preview, but published_at: null; it is not production.
    • Exact-main Netlify production attempt 6a6e997f1614c10008c2fbce: state error, published_at: null. No raw build log was opened.
    • Published Netlify site: unchanged at bounded WEB-002A — Publish bounded Shop and Events permissions containment #473 deploy 6a6dc9ea588b0c0008036312, source 9ad6837756cdd409d296009fde5082eeeae5c059.
    • runmprc.com verified for this change: no; it was not changed or rechecked.
    • GitHub deployments for exact main: none.
    • Protected release workflow for exact main: none; the only exact-commit workflow was CI.
    • Firebase deployed/read back: no.
    • Stripe or another payment provider configured, called, or changed: no.
    • Production data, migration, payment, refund, or dispute action: none.
    • Production/live behavior verified: no; source and synthetic tests do not prove it live.

    Officer impact: None — this is a server-only evidence-time projection and provides no new officer action.

    Officer documentation: None — no officer screen, task, field, permission, approval, topology, deployment step, or available recovery procedure changed. STRIPE_COMMERCE_DESIGN.md contains the source-only engineering handoff and explicitly says NOT LIVE.

    Residuals remain with #106: Checkout Session-created admission/chronology, current-clock freshness/skew, already-paid missing-time repair, paid-after-cancellation policy, other Charge/Dispute decisions, provider endpoint/API-version proof, provider retrieval/history repair, PAY-003B/C, reconciliation, alerts, protected release, and live verification.

    The #586 path/outcome claim is now explicitly RELEASED.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:firebaseFirebase services and dataarea:stripeStripe payments and financial lifecyclepriority:P0Launch blocker or urgent security risksize:SSmall focused issuetype:reliabilityReliability and recoverytype:securitySecurity or privacy boundarytype:testingTest infrastructure and quality gates

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions