Review: Custom charges on player detail page
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 notearch-rails-appfor 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.