Review: Copy-to-clipboard subscription links + webhook (re-review)

review-1335-2026-06-06-r2 Review

review needs-refinement

Verdict: NEEDS_REFINEMENT

Re-review after refinement. 5 of 8 previous findings addressed. Remaining blocker: decomposition still required (14 AC, 6 file targets, 8 points).

Previous Findings Status

  • [x] [BODY] Fix audit doc reference — FIXED. Now references docs/stripe-subscription-architecture.md in Related section.
  • [x] [BODY] Add dependency on #24 — FIXED. Lineage and Context both document #24 must ship first.
  • [x] [BODY] Add STRIPE_WEBHOOK_SECRET file target — FIXED. Deployment config listed in File Targets.
  • [x] [BODY] Add AC for players without parent email — FIXED. AC 1 covers it, test expectation added, Context section documents behavior.
  • [~] [BODY] Add AC for fee-tier-change edge case — DEFERRED. Not added, but acceptable: #24 owns fee editing and should handle link invalidation when fee changes. Dependency is documented.
  • [ ] [SCOPE] Create project-westside-ror page — Still missing in pal-e-docs. Cannot verify story note.
  • [ ] [SCOPE] Create arch-rails note — Still missing in pal-e-docs. Cannot verify arch note.
  • [ ] [DECOMPOSE] Ticket too large — Still needs decomposition. Issue has grown from 12 to 14 AC.

Template Completeness

  • [x] Type — Feature
  • [x] Lineage — follow-up from spike #22, dependency on #24 documented
  • [x] Repo — ldraney/westside-ror
  • [x] User Story — well-formed (As Marcus, I want copy-to-clipboard links)
  • [x] Context — thorough, includes Stripe data, dependency rationale, dollars-vs-cents, no-email handling
  • [x] File Targets — 6 targets listed with rationale, plus 1 do-not-touch
  • [x] Acceptance Criteria — 14 criteria (up from 12)
  • [x] Test Expectations — 8 test cases + run command
  • [x] Constraints — 6 constraints listed (added dollars-vs-cents)
  • [x] Checklist — present
  • [x] Related — lists spike, prerequisite, arch doc, live URL
All required sections for a Feature template are present and well-structured.

Traceability

  • [x] story:subscription-links label — present on board item
  • [ ] story note MISSING — project-westside-ror note does not exist in pal-e-docs. Cannot verify user story entry. [SCOPE] Create project page project-westside-ror with user-stories section.
  • [x] arch:rails label — present on board item
  • [ ] arch note MISSING — no arch-rails note found in pal-e-docs. [SCOPE] Create architecture note arch-rails for the Rails component.
  • [x] Forgejo issue — ldraney/westside-ror#23, open

File Targets

  • [x] config/routes.rb — verified: exists (39 lines), has admin namespace but no webhook route. Correct target.
  • [x] app/controllers/webhooks_controller.rb — verified: does NOT exist yet (new file). Correct.
  • [x] app/services/stripe_client.rb — verified: exists (127 lines), uses raw net/http, has payment_links and checkout_sessions methods but no subscription-mode link creation. Correct target.
  • [x] app/controllers/admin/roster_report_controller.rb — verified: exists (35 lines), loads tenant/teams/players but not payment_links. Correct target.
  • [x] app/views/admin/roster_report/index.html.erb — verified: exists (98 lines) with desktop table and mobile cards. No Copy Link button yet. Correct target.
  • [x] Deployment config for STRIPE_WEBHOOK_SECRET — verified: docker-compose.yml currently only has STRIPE_API_KEY (line 30). Correct target for adding webhook secret.
  • [x] app/controllers/admin/payments_controller.rb — correctly listed as do-NOT-touch.

Repo Placement

OK — issue filed on ldraney/westside-ror, all file targets within this repo. Single-repo change.

Dependencies

  • #24 (editable fees) — prerequisite: Now explicitly documented in Lineage and Context. Architecture doc confirms order: #24 first, then #23. Board item #1334 is also in backlog. Dependency is clear and correctly stated.
  • #1207 (Stripe payment links + email blast, issue #2) — in_progress: Uses same StripeClient and payment_links table for one-time payments. No direct conflict but shared blast radius.
  • CSRF exemption: ApplicationController inherits from ActionController::Base with default CSRF protection. Issue now has AC 13 explicitly requiring CSRF exemption for webhook route. Covered.
  • Schema constraint: payment_links.parent_id is NOT NULL, meaning a PaymentLink record requires a parent. The issue handles this correctly — players without parent email get "No email" indicator instead of a link. Since Player belongs_to :parent (required in Rails 7), all players have a parent, but parent.email may be nil.

Acceptance Criteria

14 acceptance criteria — exceeds the 5-AC decomposition threshold by nearly 3x.
  • AC 1-7: Copy-to-clipboard link generation flow (UI + Stripe API + storage + no-email handling)
  • AC 8-13: Webhook endpoint (signature, event handling, status updates, CSRF)
  • AC 14: End-to-end status reflection on roster report
All criteria are testable and specific. The added ACs (CSRF exemption, no-email handling, status reflection) improve completeness.
Dollars vs cents: Now documented in Context ("multiply by 100") and Constraints. AC 4 says "correct recurring price tier based on player's monthly_fee" which is clear enough given the constraint.

Blast Radius

  • StripeClient changes are additive — new methods, existing methods untouched. Safe.
  • payment_links table shared with one-time payment system. New subscription links coexist. The unique constraint uq_payment_links_player_product_tenant means one link per player per product — aligns with AC 7 (reuse on reload).
  • No other controllers handle webhooks — new endpoint is isolated.
  • subscriptionstatus enum already has active, past_due, canceled, none — matches webhook events.
  • paymentlinkstatus enum has active, paid, canceled — sufficient for webhook updates.

Decomposition Assessment

NEEDS DECOMPOSITION
  • 14 acceptance criteria (threshold: 5) — up from 12 in first review
  • 6 file targets (threshold: 3) — up from 5
  • 8 test expectations
  • Two distinct subsystems: (a) link generation + copy-to-clipboard UI, (b) webhook endpoint + status sync
  • Estimated agent work: well over 5 minutes — involves Stripe API integration, webhook signature verification, JS clipboard logic, multiple controller/model changes, deployment config
  • 8 story points further confirms this is too large for a single pass
Natural decomposition split:
  • Sub-ticket A: Subscription link generation + copy-to-clipboard UI (AC 1-7, ~5 points)
  • Sub-ticket B: Webhook endpoint + status sync (AC 8-14, ~3 points)
Route to skill-decompose-ticket for sub-ticket creation.

Recommendations

  • [SCOPE] Create project page project-westside-ror with user-stories section including subscription-links story
  • [SCOPE] Create architecture note arch-rails for the Rails component
  • [DECOMPOSE] 14 AC across 2 subsystems (link generation + webhook), 6 file targets, 8 points, 8 test expectations — route to skill-decompose-ticket. Natural split: (a) subscription link generation + copy-to-clipboard UI (AC 1-7), (b) webhook endpoint + status sync (AC 8-14)
Note: All [BODY] findings from the first review have been addressed. The issue body is now clean and complete. The remaining blocker is decomposition (ticket size) and missing backing notes in pal-e-docs.