Skip to content

[Eval-Backlog] SUBSCRIPTION-MGMT: subscription management route + 4 edge functions + grace-period wiring #5

@TortoiseWolfe

Description

@TortoiseWolfe

Summary

User-facing subscription management surface end-to-end. ScriptHammer has subscription infrastructure that's partially built and sitting disconnected. The database schema is complete with all five status values. Webhook handlers for Stripe and PayPal already upsert subscription events. The SubscriptionManager component renders subscription cards with cancel and resume buttons. But the edge functions those buttons call don't exist, there's no route wiring the component to anything, and the grace-period field in the schema never gets populated.

Greenfield across four layers (route, edge functions, service, UI) with real design choices on grace-period ownership and duplicate prevention location.

What this exposes

Signal What to watch
Discovery first Does the implementer find SubscriptionManager and the existing webhook handlers before writing new code, or rebuild from scratch?
Grace period layer Webhook, trigger, or cron for populating the expires timestamp? Each has tradeoffs — which one ships, and why?
Duplicate prevention Is it a real server-side guard (RLS, trigger, or edge function), or a client-only check that can be bypassed by hitting the edge function directly?
Edge function hygiene Signature verification where the provider requires it (Stripe webhook-style), proper error responses, not "happy path only"
Feature flag cascade Subscribed to the same featureFlags.stripeEnabled/paypalEnabled gate as the rest of the payment surface
Protected route pattern Wraps in <ProtectedRoute> the way /payment-demo does
Grace-period UI Countdown warning that actually surfaces remaining time when the user lands on the page during grace
Test discipline At least 3 of the 6 stubbed tests in the PayPal subscription E2E spec get un-skipped and pass (the 3 that don't need real PayPal keys: details display, cancellation, grace period warning)

Starting prompt

Read CLAUDE.md first. ScriptHammer has subscription infrastructure that's partially built and sitting disconnected. The database schema is complete with all five status values. Webhook handlers for Stripe and PayPal already upsert subscription events. The SubscriptionManager component renders subscription cards with cancel and resume buttons. But the edge functions those buttons call don't exist, there's no route wiring the component to anything, and the grace period field in the schema never gets populated.

Before you write any new component or edge function, read what's there. The subscriptions table is in the monolithic migration. The types are in the payment types module. The SubscriptionManager is under the payment components tree. The Stripe and PayPal webhook handlers are in the Supabase functions directory. The payment service module has the pattern to follow for new service functions. The payment demo page shows the protected route and consent pattern.

Then build the user-facing subscription management surface. It needs: a dedicated route that composes the SubscriptionManager component, the four missing edge functions the component calls, a subscription service layer in the payment library, grace period handling that actually populates the expires field and surfaces it in the UI, and duplicate-subscription prevention so a user can't accidentally end up paying twice.

Grace period is the interesting decision here. The webhook can populate the expires timestamp when status flips to past_due, or a trigger can do it on update, or a cron job can sweep. Each has tradeoffs. Pick one, explain why, and make it work end to end. Whatever you pick, the UI needs to show a warning with time remaining when the user lands on the page during grace period.

Duplicate prevention is the other design choice. RLS, trigger, edge function, client-side check, or combination. Don't just add a client check that can be bypassed by hitting the edge function directly. The real guard belongs server-side.

The subscription flow stays protected-route and feature-flag-gated like the rest of the payment surfaces. When neither provider is configured, the feature flag gate renders the not-configured banner instead of the page.

You don't need real Stripe or PayPal keys. The edge functions can be built and tested with mocked provider responses. The subscription lifecycle is testable with service-role inserts that simulate webhook events. That's expected eval mode.

Run all commands through Docker. Follow the five-file component pattern for any new components. Styling through DaisyUI semantic tokens only.

Goal

Build the user-facing subscription management surface end to end. The route exists at a sensible path (slash-account slash-subscriptions or similar), wraps the SubscriptionManager component, and renders inside the protected route pattern with the feature flag gate. The four missing edge functions (cancel subscription, resume subscription, create Stripe subscription, create PayPal subscription) are implemented with signature verification where the provider requires it and proper error responses. The subscription service functions are added to the payment library module (get active subscriptions, cancel subscription, get subscription by id). Grace period handling populates the expires field at the right layer and surfaces a countdown warning in the UI. Duplicate prevention has a real server-side guard, not just a client check. At least three of the stubbed tests in the PayPal subscription end-to-end spec are un-skipped and passing. Follow-ups cover grace-period layer choice, duplicate prevention implementation, cancellation UX, webhook simulation for testing, and the accessibility pass on the new route.

Follow-up prompts

F1 — Empty state:

The subscription management route loads. You're logged in with no active subscriptions. What does the empty state look like? Is there a clear path to subscribe, or does the page dead-end?

F2 — Grace period layer choice:

Where did you put grace period expiration population: webhook handler, database trigger, or cron? Walk through the tradeoff. What happens if the webhook fires late or not at all? What happens if a user lands on the page mid-grace-period?

F3 — Duplicate prevention:

A user has an active subscription. They open a second tab and click the subscribe button. What stops them? Trace it from the button click to the guard. If the guard is client-only, it's not a real guard.

F4 — Cancellation flow:

Cancel a subscription. Does it cancel immediately or at period end? Is the user asked for a reason? Does the UI reflect the canceled state without a page refresh? Can they undo within a grace window?

F5 — Webhook simulation:

You said the grace period field gets populated by the webhook. Prove it. Write a test that inserts a past-due subscription directly and verifies the grace period logic fires. Or simulate the webhook payload and watch the subscription transition.

F6 — Un-skip the PayPal subscription spec:

Open the PayPal subscription end-to-end spec. There are six skipped tests. Three of them don't need real PayPal keys: details display, cancellation, and grace period warning. Un-skip those three, run the spec, make them pass. Don't un-skip the PayPal-SDK ones.

F7 — Accessibility pass:

Run the accessibility test for the subscription management route. What does it flag? Heading hierarchy on the cards? Button labels on the cancel affordance? Status announcements for the grace period warning?

Key references

  • supabase/migrations/ — monolithic migration, subscriptions table and status enum
  • src/types/payment.ts (or payment types module) — subscription types
  • src/components/payment/SubscriptionManager/ — existing component, renders cards with cancel/resume
  • supabase/functions/stripe-webhook/ and paypal-webhook/ — existing webhook handlers that upsert subscription events
  • src/lib/payments/payment-service.ts — service layer pattern to follow for new subscription service functions
  • src/app/payment-demo/page.tsx — protected route + feature flag reference
  • PayPal subscription E2E spec — stubbed acceptance criteria (6 tests, 3 un-skippable without real PayPal keys)

Context

Corresponds to SPEC-057 in the Technical Debt Backlog in the repo README. The infrastructure was built incrementally over several sprints but never wired end-to-end. This issue is the connective tissue: route + 4 edge functions + service layer + grace-period wiring + duplicate-prevention guard.


Filed from the Mercor Code Agent eval rotation (good_prompt_bad_prompt #17 SUBSCRIPTION-MGMT). Used as an iterative A/B eval prompt — 90-120 min, 5+ follow-up turns. Kept here as a tracked work item.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions