Critical path — Payment endpoint
Synced verbatim from the repo on every site build. Edit the source file on GitHub (link in the page footer); do not edit the rendered copy here. Use when the diff charges, refunds, holds in escrow, pays out, splits revenue, manages subscriptions, or handles webhooks from a Payment Service Provider. Always combine with
auth-protected-action.mdfor the user-facing actions.
When to load this path
PRIMARY trigger (load this path as core when):
- A new endpoint that creates a Charge / Refund / Payout / Subscription / Dispute aggregate
- A new PSP webhook handler (signature-verify-before-parse)
- A new column / table for
ledger_entriesor any monetary amount - A new use of the
MoneyVO in Domain code - A new entry in
pricestable or any pricing-config write
SECONDARY trigger (load only when no primary path covers the diff already):
- A new daily reconciliation job
- A new
{PROVIDER}_WEBHOOK_SECRETrow insecrets-manifest.md - A frontend page rendering money or capturing a payment method
DO NOT load when:
- The diff only modifies tests
- The diff only modifies
*.md - The amount is non-monetary (e.g. quantity, count) —
MoneyVO does not apply
Backend
Money modeling
- PA-001 Every monetary value uses the
MoneyVO (integer minor units + ISO 4217) - PA-002 Money columns pair
amount_minor BIGINT+currency CHAR(3)with CHECK - PA-003 Currency mismatches throw
CurrencyMismatchException - PA-004 Splits use a sum-preserving algorithm (largest-remainder method)
Idempotency on outbound calls
- PA-005 Every PSP mutation carries a deterministic
Idempotency-Key
Webhooks
- PA-006 Verify provider signature on RAW body BEFORE parsing JSON
- PA-007
processed_webhooks(provider, event_id)dedup; insert in same DB tx as state change BEFORE returning 200 - PA-008 Out-of-order tolerance: handlers check current state, never trust arrival order
Ledger
- PA-009 Every balance change produces immutable
ledger_entries; transactions sum to zero per currency at write time - PA-010 Ledger append-only; corrections are NEW entries with
cause_type='adjustment' - PA-011 Account names stable; rename via reversal entries in same tx
- PA-018 Multi-party splits record both legs at capture time; payout settlement debits
payout_pending
State machines
- PA-012 Charge / Subscription / Refund / Payout / Dispute have explicit state machines enforced by aggregate + DB CHECK
- PA-013 Subscriptions are webhook-driven (no polling cron)
- PA-014 Refunds are first-class aggregates; charge state derived
Pricing
- PA-015 Prices in DB (
pricestable withvalid_from/valid_until); never hardcoded in PHP
API serialization
- PA-016 Money serialized as
{ amount_minor, currency }; serializer refuses Money → float
Reconciliation
- PA-017 Daily reconciliation; non-zero delta is SEV-2 incident
- PA-019 Span attributes + metrics bounded; no customer identifiers as labels
- PA-020
{PROVIDER}_WEBHOOK_SECRETinsecrets-manifest.mdwith rotation policy; HTTP webhooks rejected with 426 in dev/staging
Authorization & audit (carried over)
- AZ-001 Voter check before mutation
- AU-006 Audit write in same DB tx
- AU-007 Entries on success / denied / failed
Hard blockers
- BE-001 Quality gates green
- SC-001 No secrets committed
- LO-001 No unredacted sensitive fields in logs
Frontend (when payments touch UI)
- PA-021 API payloads use
{ amount_minor, currency } - PA-022 Render via
Intl.NumberFormatstyle: 'currency'+ explicit locale - PA-023 Card capture uses PSP hosted element; raw card numbers NEVER touch frontend state
- PA-024 Payment confirmation pages re-fetch backend (webhook-driven) before showing success
Coverage map vs full checklist
This path covers these sections of backend-review-checklist.md / frontend-review-checklist.md:
- §Payments / Money — PA-001..PA-024 (Money VO, ledger, webhooks, state machines, reconciliation, frontend money rendering)
- §Authorization (carried over) — AZ-001
- §Audit (carried over) — AU-006, AU-007
- §Hard blockers — BE-001, SC-001, LO-001
- §Frontend Payments — PA-021..PA-024
This path does NOT cover. Load the corresponding checklist section ONLY when the diff touches:
tests/directory → load §Testing- The CRUD shape of the payment endpoint (controller, validation, OpenAPI) → load
crud-endpoint.md(path) - New encrypted column for payment instrument tokens → load §Secrets (SC-*) + §Database
- Refund frontend UX flows beyond PA-024 → load §Frontend UX states
What this path does NOT cover
- Authorization →
auth-protected-action.md - Sub-processor inventory entry for the PSP →
pii-write-endpoint.md(GD-011) - Stored payment instruments / KYC documents →
file-upload-feature.md