Review: Copy-to-clipboard subscription links + webhook
Verdict: NEEDS_REFINEMENT
Template Completeness
- [x] Type — Feature
- [x] Lineage — follow-up from spike #22
- [x] Repo — ldraney/westside-ror
- [x] User Story — present and well-formed
- [x] Context — thorough, includes Stripe data audit
- [x] File Targets — 5 files listed with rationale
- [x] Acceptance Criteria — 12 criteria
- [x] Test Expectations — 6 test cases + run command
- [x] Constraints — 5 constraints listed
- [x] Open Questions — 3 questions for Lucas/Marcus
- [x] Checklist — present
- [x] Related — lists parent spike and upstream PR
All required sections for a Feature template are present. Issue is thorough and well-structured.
Traceability
- [x] story:subscription-links label — present on board item
- [ ] story note MISSING —
project-westside-rornote does not exist in pal-e-docs. Cannot verify user story entry. [SCOPE] Create project pageproject-westside-rorwith user-stories section. - [x] arch:rails label — present on board item
- [ ] arch note MISSING — no
arch-railsnote found in pal-e-docs. [SCOPE] Create architecture notearch-railsfor the Rails component. - [x] Forgejo issue — ldraney/westside-ror#23, open
File Targets
- [x]
config/routes.rb— verified: exists, currently has admin namespace and email routes but no webhook route. Correct target for addingpost '/webhooks/stripe'. - [x]
app/controllers/webhooks_controller.rb— verified: does NOT exist yet (new file). Correct — needs creation. - [x]
app/services/stripe_client.rb— verified: exists at 127 lines, uses rawnet/httppattern, haspayment_linksandcheckout_sessionsmethods but no subscription-mode link creation. Correct target. - [x]
app/controllers/admin/roster_report_controller.rb— verified: exists at 35 lines, loads tenant/teams/players. Currently does not load payment_links. Correct target. - [x]
app/views/admin/roster_report/index.html.erb— verified: exists at 98 lines with desktop table and mobile card layout. No "Copy Link" button yet. Correct target. - [x]
app/controllers/admin/payments_controller.rb— verified: exists, separate one-time payment flow. Correctly listed as "do NOT touch".
Issue: Context section references
docs/reports/2026-06-01-stripe-payment-link-audit.md but this file does not exist. The actual spike output is at docs/stripe-subscription-architecture.md.Repo Placement
OK — issue is filed on
ldraney/westside-ror and all file targets are within this repo. Single-repo change.Dependencies
- Ordering conflict: The architecture doc (
docs/stripe-subscription-architecture.md, line 172-173) specifies implementation order as #24 first, then #23. However, #23 is being reviewed for advancement while #24 (inline-editable monthly fee with Stripe sync) is also in backlog. If #24 lands first, it changes howmonthly_feeworks and could affect which Stripe price tier the subscription link uses. The issue should explicitly document whether it depends on #24 or can proceed independently. - Board item #1334 (Inline-editable monthly fee, Forgejo #24) — same
story:subscription-linkslabel, also in backlog. These two tickets share the same Stripe product and price tiers. No documented dependency between them on the board. - Board item #1207 (Stripe payment links + email blast, Forgejo #2) — in_progress. Uses the same
StripeClientandpayment_linkstable but for one-time payments. No conflict but blast radius overlap. - CSRF exemption needed:
ApplicationControllerinherits fromActionController::Basewhich has CSRF protection enabled by default. The webhook controller must skip this. Issue mentions this in Constraints but not in File Targets or AC. - Webhook signing secret: Issue says "must go through secrets management" but does not specify the env var name or where it will be configured (docker-compose.yml currently only has
STRIPE_API_KEY). File target for docker-compose.yml or deployment config is missing.
Acceptance Criteria
12 acceptance criteria — exceeds the 5-AC decomposition threshold.
- AC 1-7 cover the copy-to-clipboard link generation flow (UI + Stripe API + storage)
- AC 8-12 cover the webhook endpoint (signature verification, event handling, status updates)
- These are two distinct subsystems that could be implemented and tested independently.
Schema concern: AC 9 says webhook "sets
paid_at" — this column is on payment_links table (not players), which is correct but the AC text is ambiguous about which record.Unique constraint concern:
payment_links has a unique constraint on (player_id, product_id, tenant_id). If all subscription links use the same "Monthly Club Dues" product, a player can only have one link. The ticket says "reused on subsequent page loads" (AC 7) which aligns. But if a player's fee tier changes, the old link must be deactivated and a new one created — this edge case is not covered in the AC.Missing AC: No acceptance criterion for players without a parent record (Open Question #3 acknowledges this but doesn't resolve it).
Blast Radius
StripeClientis used by bothAdmin::PaymentsController(one-time payments) and the roster report. Adding subscription link methods is additive and should not break existing one-time flows.payment_linkstable is shared with the one-time payment system. New subscription links will coexist. Thepaymentlinkstatusenum already includesactive,paid,canceledvalues.- No other controllers currently handle webhooks, so the new endpoint is isolated.
- The
subscriptionstatusenum already hasactive,past_due,canceled,none— matches webhook events perfectly.
Decomposition Assessment
NEEDS DECOMPOSITION
- 12 acceptance criteria (threshold: 5)
- 5 file targets (threshold: 3)
- Two distinct subsystems: (a) link generation + copy UI, (b) webhook endpoint + status updates
- Estimated agent work: well over 5 minutes — involves Stripe API integration, webhook signature verification, JS clipboard logic, and multiple controller/model changes
- 8 story points further confirms this is too large for a single pass
Route to
skill-decompose-ticket for sub-ticket creation.Recommendations
- [BODY] Fix audit doc reference:
docs/reports/2026-06-01-stripe-payment-link-audit.md→docs/stripe-subscription-architecture.md - [BODY] Add dependency note: document whether #23 depends on #24 or can proceed independently (architecture doc says #24 first)
- [BODY] Add file target:
docker-compose.ymlor deployment config forSTRIPE_WEBHOOK_SECRETenv var - [BODY] Add AC for fee-tier-change edge case: when monthly_fee changes, old link must be deactivated and new link created
- [BODY] Add AC for players without a parent record (resolve Open Question #3)
- [SCOPE] Create project page
project-westside-rorwith user-stories section includingsubscription-linksstory - [SCOPE] Create architecture note
arch-railsfor the Rails component - [DECOMPOSE] 12 AC across 2 subsystems (link generation + webhook), 5 file targets, 8 points — 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-12)