Admin endpoint: record non-Stripe payments (cash/Apple Pay/check) #509
Labels
No labels
domain:backend
domain:devops
domain:frontend
status:approved
status:in-progress
status:needs-fix
status:qa
type:bug
type:devops
type:feature
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
ldraney/basketball-api#509
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Type
Feature
Lineage
Standalone — discovered during 2026-04-22 T2 blast scoping. Two orders (Romial #51, Vince #70) required manual UPDATE to mark T1 Utah Invitational cash payments as paid. Need a self-serve path for future off-Stripe receipts.
Repo
forgejo_admin/basketball-apiUser Story
As a Westside admin,
I want to record cash / Apple Pay / check receipts against an order,
So that parents who paid off-Stripe aren't double-billed in future blasts and aren't falsely shown as unpaid in reports.
Context
Our Stripe webhook (
routes/webhooks.py::_handle_generic_order_completed) correctly flipsorders.status='paid'and stampsstripe_payment_intent_idwhen parents pay via Stripe link. The 2026-04-22 reconciliation verified zero Stripe↔DB mismatches — the automated side works.The gap is DB↔reality: cash, Apple Pay-to-Marcus, and check payments never enter Stripe, so nothing marks those orders as paid. Marcus has tracked off-ledger; our DB has always been incomplete by the cash-cohort size. This endpoint is the side-channel intake.
Uses existing
orders.status+orders.custom_datajsonb columns — no schema change, no migration.File Targets
Files the agent should modify or create:
src/basketball_api/routes/admin.py— addPOST /admin/orders/{order_id}/record-paymentroute, follow existing admin-route styletests/routes/test_admin.py— new tests for the endpointFiles the agent should NOT touch:
src/basketball_api/models.py— no schema change needed; existing columns are sufficientsrc/basketball_api/routes/webhooks.py— Stripe webhook is correct; do not modifysrc/basketball_api/routes/checkout.py— unrelatedAcceptance Criteria
POST /admin/orders/{order_id}/record-paymentexists, requires admin auth{method: "cash" | "apple_pay" | "check", amount_cents: int, notes?: str, recorded_by: str}orders.statusflips frompendingtopaid;orders.custom_datajsonb merged with{payment_method, recorded_by, recorded_at (iso8601), notes}— does NOT overwrite existing custom_data keyspaidorder returns 409 with the current statusTest Expectations
pendingorderpaidwith correctcustom_dataaudit trailpaidorder returns 409, does not re-stamp audit trailGET /admin/orders?status=paidincludes this orderpytest tests/routes/test_admin.py -k record_payment -vConstraints
admin.pycurrently uses for admin gating)custom_datamerge must useCOALESCE(custom_data, '{}'::jsonb) || jsonb_build_object(...)equivalent — do not replace existing keysamount_centsstored in custom_data for audit; does NOT changeorders.amount_cents(that's the product price)Checklist
Related
project-westside-basketball— project this affects(player_id, product_id)when no Order row exists (orphan-roster case), (b) reconciliation report endpoint "who paid T1 in cash", (c) admin UI for this endpoint