fix: require amount match when reusing fresh pending Stripe session #480
No reviewers
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
forgejo_admin/basketball-api!480
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "479-fresh-pending-amount-check"
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?
Summary
Closes the bait-and-switch regression from #473: when a player's
monthly_feechanges between the first click and a subsequent one, the old Stripe session has the old amount. Previously the fresh-pending branch unconditionally reused that stale URL, so the email promised one amount and Stripe showed another. Now we require both freshness AND amount equality to reuse.Reproduced live in prod (test account): email promised "$5 prorated", Stripe showed $165 — because a fresh pending order from a pre-fee-change click was being reused.
Changes
src/basketball_api/routes/checkout.py— fresh-pending branch now checksexisting_order.amount_cents == amount_cents. If amount changed, cancel the old order and fall through to create a new session. If amount matches, reuse (idempotent repeat-click optimization preserved).tests/test_first_payment.py— new testtest_first_payment_fresh_pending_amount_changedasserts that a pending at $165 with fee dropped to $4 creates a new order at $5 and redirects to the new Stripe session (not the stale one).Review Checklist
ruff check+ruff format --checkcleanTest Plan
pytest tests/test_first_payment.py -v— new test + 3 existing related tests should all passRelated Notes
None.
Related
Closes #479
Follow-up to #473 (introduced the reuse behavior)
PR #480 Review
DOMAIN REVIEW
Stack: Python / FastAPI / SQLAlchemy / Stripe / pytest.
Fix correctness:
amount_centsis computed fresh at line 334 from the currentplayer.monthly_feevia_calculate_prorated_cents, then compared againstexisting_order.amount_cents(persisted when the prior pending order was created). This is the correct comparison: the source of truth for "what the new email/link promises" vs "what Stripe will actually charge from the cached session."is_fresh,amount_matches). Combined gate preserves the idempotent repeat-click optimization from #473 while closing the bait-and-switch window.Test coverage (
test_first_payment_fresh_pending_amount_changed):amount_cents=16500, freshcreated_at(5 min old), player fee dropped so new proration = 500 cents.mock_stripe.checkout.Session.retrieve.assert_not_called()— the strongest possible assertion that stale reuse did not occur.Python/FastAPI/SQLAlchemy:
is_fresh,amount_matches) is clear.BLOCKERS
None.
NITS
Customer.createbut the existing player may already have astripe_customer_id. Depending on fixture state, theCustomer.createmock may not be exercised. Not a correctness issue — just noting the mock isn't strictly required for the assertion set.SOP COMPLIANCE
479-fresh-pending-amount-check(issue-prefixed, kebab-case)checkout.py+test_first_payment.pyPROCESS OBSERVATIONS
VERDICT: APPROVED