Review: Custom charges on player detail page

review-1680-2026-07-02 Review

review ready

Verdict: READY

Template Completeness

  • [x] Type — Feature
  • [x] Lineage — Child of #144, depends on #145
  • [x] Repo — ldraney/westside-basketball
  • [x] User Story — As admin, I want to add custom charges so that all outstanding balances are tracked in one place
  • [x] Context — Explains gap in current charge sources (payment_links, orders, player_tournaments)
  • [x] File Targets — 4 files to modify/create, 3 files NOT to touch
  • [x] Feature Flag — none (internal admin feature, correct)
  • [x] Acceptance Criteria — 6 criteria
  • [x] Test Expectations — 3 test expectations + run command (bin/rails test)
  • [x] Constraints — 3 constraints documented
  • [x] Checklist — standard PR/tests/no unrelated changes
  • [x] Related — lists parent #144 and dependency #145

Traceability

  • [x] story:WS-S9 label — "As an admin, I want to track payment status per player so that I know who owes what"
  • [x] story note verified — found in project-westside-basketball user-stories section under Admin (Marcus)
  • [x] arch:rails-app label — Rails application component
  • [ ] arch note MISSING — [SCOPE] Create architecture note arch-rails-app for the Rails application component. Note: this is a systemic gap affecting many board items, not specific to this ticket.
  • [x] Forgejo issue — ldraney/westside-basketball#146, open

File Targets

  • [x] app/controllers/admin/players_controller.rb — verified: exists (2.1k), has index + show actions, needs create_charge action added
  • [x] app/views/admin/players/show.html.erb — verified: exists (11k), Outstanding Balances section at line 150, already handles nil stripe_payment_link_url (line 181-184 shows dash)
  • [x] config/routes.rb — verified: exists, admin players routes at line 24 with member block, needs POST route added
  • [x] app/javascript/controllers/add_charge_controller.js — NEW FILE: to be created. Reference pattern inline_fee_controller.js (3.8k) exists and is well-structured
  • [x] NOT TOUCH: app/models/payment_link.rb — verified: product already optional (belongs_to :product, optional: true), no changes needed
  • [x] NOT TOUCH: app/services/stripe_client.rb — correct: custom charges are DB-only
  • [x] NOT TOUCH: app/controllers/webhooks_controller.rb — verified: webhook finds by stripe_payment_link_id (line 69-70), so custom charges with nil IDs won't be matched. Safe.

Repo Placement

OK — issue filed on ldraney/westside-basketball, fix is in ldraney/westside-basketball. Single repo.

Dependencies

  • depends:145 (board item 1679) — Mark-as-paid + paid_method migration. Currently in backlog. MUST complete first: makes stripe_payment_link_id and stripe_payment_link_url nullable (currently NOT NULL per schema.rb lines 221-222). Without this migration, creating payment_links rows with nil Stripe fields will raise DB constraint violations.
  • parent:144 (board item 1678) — Player invoice builder parent issue, in backlog.
  • Siblings #147 (board item 1681) and #148 (board item 1682) do not depend on this ticket.
  • Dependencies are correctly documented in both the Lineage and Constraints sections of the issue.

Acceptance Criteria

All 6 AC are verifiable by an agent:
  • AC1-2: UI presence checks (button + inline form) — verifiable via view source or browser test
  • AC3: DB row creation with correct fields — verifiable via unit test
  • AC4: No-reload appearance — verifiable via Turbo/DOM test
  • AC5: Mark Paid integration — verifiable but requires #145 to be merged first
  • AC6: Amount validation — verifiable via unit test
Test commands are real (bin/rails test). No missing criteria detected.

Blast Radius

  • Unique constraint safety: payment_links has a unique constraint on [player_id, product_id, tenant_id]. Custom charges use product_id: nil. PostgreSQL treats NULLs as distinct in unique constraints, so multiple custom charges per player are safe. Note for awareness only.
  • Webhook safety: Stripe webhook at webhooks_controller.rb:69 finds payment_links by stripe_payment_link_id. Custom charges with nil stripe_payment_link_id will never be matched. No conflict.
  • Outstanding Balances view: Already filters by status == 'active' and handles nil stripe_payment_link_url by showing a dash. Custom charges will render correctly without view logic changes beyond the Add Charge form.
  • Total outstanding calculation: Controller line 48 sums all unpaid_links.amount_cents. Custom charges will be included automatically. Correct behavior.

Decomposition Assessment

4 file targets in 1 repo. 6 acceptance criteria (borderline on the >5 threshold). Estimated agent time: under 5 minutes — the work is a single POST action, a Stimulus controller following an existing pattern, an inline form in an existing view, and one route addition. All tightly coupled. No decomposition needed.

Recommendation

  • [SCOPE] Create architecture note arch-rails-app for the Rails application component. This is a systemic gap — multiple board items reference arch:rails-app but no backing note exists. Not a blocker for this ticket specifically.
No other action needed. Scope is solid, file targets verified, dependency chain correct.