Skip to content

Redesign manage-billing cancellation flow#248

Open
BASIC-BIT wants to merge 23 commits intomainfrom
feat/issue-242-cancel-flow
Open

Redesign manage-billing cancellation flow#248
BASIC-BIT wants to merge 23 commits intomainfrom
feat/issue-242-cancel-flow

Conversation

@BASIC-BIT
Copy link
Owner

Summary

  • replace the inline cancellation textarea with an intentional two-step cancel panel that collects optional feedback and requires explicit confirmation
  • redirect successful cancellations to a calm follow-up page that explains when access ends and links back to membership options or support
  • add smoke, unit, and visual coverage for the new manage-billing cancellation flow

Closes #242

@vercel
Copy link

vercel bot commented Mar 26, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
entexchange_perkcord Ready Ready Preview, Comment Mar 26, 2026 10:43am
perkcord Ready Ready Preview, Comment Mar 26, 2026 10:43am

Request Review

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 26, 2026

Greptile Summary

This PR replaces the inline single-step cancellation form with a deliberate two-step panel (confirm gate + optional feedback), adds a calm /subscribe/account/canceled follow-up page with contextual access-end copy, and extracts the shared session-guard helper. It also hardens provider event processing against archived/deleted tier conflicts and upgrades the E2E seed to attach an account owner before linking the provider customer.

Key changes:

  • BillingSubscriptionCancellationPanel — new client component with open/closed state; cancellation form is hidden until explicitly opened and requires checking a confirmation checkbox before the submit button is enabled
  • canceled/page.tsx — new follow-up page that reads session context, optionally loads the live subscription record, and shows distinct copy blocks (access ends, what changed, what's next); all three previously-threaded concerns (duplicate text, shared session guard, mismatched-guildId redirect stripping params) are resolved
  • billingSession.tsrequireBillingSession extracted from both account pages so the /subscribe/connect redirect lives in one place
  • billing.tsbuildSubscribeRedirectLocation with path-safety check; successful cancellation now redirects to the follow-up page with guildId, subscription, tier, and until params instead of the old ?cancel=success banner
  • providerEventProcessing.ts — archived/deleted tiers are now silently skipped (with a "skipped" result) rather than throwing; when multiple tiers match, the single live tier is preferred over drafts
  • convexTestData.ts — seed links an account owner before the provider customer link, checks the processing result for immediate failures, and polls multiple billing URL forms in parallel; the ProviderEventProcessingResult type does not yet include "skipped" as a valid status value, which can cause a harder-to-diagnose timeout when that branch is hit rather than a fast-fail

Confidence Score: 4/5

Safe to merge; one targeted fix to the E2E seed type/guard would harden CI diagnostics but does not block the primary user path

All three prior review threads are fully resolved. The core cancellation flow, follow-up page, redirect logic, and convex tier-matching changes are clean and well-covered by unit, visual, and smoke tests. The one remaining concrete issue is a type/guard gap in the E2E seed helper ("skipped" status not checked) that would produce a slow timeout rather than a fast-fail in an edge case — it does not affect production behavior.

apps/web/e2e/convexTestData.ts — the ProviderEventProcessingResult type and the seed's processing-result guard should be updated to cover the new "skipped" status emitted by providerEventProcessing.ts

Important Files Changed

Filename Overview
apps/web/app/subscribe/account/billing-subscription-cancellation-panel.tsx New two-step cancellation panel with client-side confirmation gate; clean React state management and accessible form structure
apps/web/app/subscribe/account/canceled/page.tsx New follow-up page with session guard, guildId mismatch redirect (with full param forwarding), graceful fetch-error banner, and distinct copy blocks — prior review concerns fully resolved
apps/web/lib/api/subscribe/billing.ts Adds buildSubscribeRedirectLocation with path-safety validation, buildCancellationFollowUpRedirectLocation wrapper, and redirects successful cancellations to the new follow-up page with contextual URL params
convex/convex/providerEventProcessing.ts Tier matching now filters archived/deleted tiers and prefers the single live tier over drafts; the new "skipped" result status is not reflected in the E2E seed type, which can make seed failures harder to diagnose
apps/web/e2e/convexTestData.ts Seed now links an account owner before attaching provider customer, checks for immediate processing failures, and polls multiple billing URL forms; ProviderEventProcessingResult type is missing the new "skipped" status
apps/web/lib/billingSession.ts Minimal shared guard that redirects unauthenticated visitors to /subscribe/connect; extracted from duplicated inline logic in both account pages

Sequence Diagram

sequenceDiagram
    participant Member
    participant BillingPage as /subscribe/account
    participant Panel as CancellationPanel
    participant API as /api/subscribe/billing/cancel
    participant CanceledPage as /subscribe/account/canceled

    Member->>BillingPage: Load page
    BillingPage-->>Member: Show "Stop future renewals" button
    Member->>Panel: Click "Stop future renewals"
    Panel-->>Member: Show confirmation panel (submit disabled)
    Member->>Panel: Check confirmation checkbox
    Panel-->>Member: Submit button enabled
    Member->>Panel: Click "Confirm cancellation"
    Panel->>API: POST billingSubscriptionId, tierName, currentPeriodEnd, reason
    API-->>API: resolveMemberBillingCancelRedirect()
    API-->>CanceledPage: 303 redirect → /subscribe/account/canceled?guildId=&subscription=&tier=&until=
    CanceledPage->>CanceledPage: requireBillingSession() or redirect /subscribe/connect
    CanceledPage->>CanceledPage: Correct guildId mismatch if needed (preserve all params)
    CanceledPage->>CanceledPage: loadSubscriptions() + loadSupportHref()
    CanceledPage-->>Member: Show "Future renewals are off" + access-end copy + links
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: apps/web/e2e/convexTestData.ts
Line: 55-60

Comment:
**`"skipped"` status missing from local type union**

`ProviderEventProcessingResult` only lists `"processed" | "failed"`, but the updated `providerEventProcessing.ts` can now emit a third status — `"skipped"` — when all matching tiers are archived or deleted. Because the seed guard only checks `status === "failed"`, a skipped seed event slips through silently: the early-exit never fires and the polling loop burns through its 120 attempts before surfacing a generic timeout error, which is much harder to diagnose than an immediate throw.

```suggestion
type ProviderEventProcessingResult = {
  providerEventId: string;
  status: "processed" | "failed" | "skipped";
  reason?: string;
};
```

You'd also want to extend the guard to cover the new status so the seed fails fast if skipped:

```typescript
if (currentEventResult?.status === "failed" || currentEventResult?.status === "skipped") {
  throw new Error(
    `Unable to process recurring billing seed for ${args.discordUserId}: ${JSON.stringify(currentEventResult)}`,
  );
}
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (18): Last reviewed commit: "Probe billing seed ownership fallbacks" | Re-trigger Greptile

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 016189d5d6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@BASIC-BIT
Copy link
Owner Author

@perkcord preview

@github-actions
Copy link

Manual Vercel Preview

Triggered from PR comment for branch feat/issue-242-cancel-flow.
Preview URL: https://perkcord-5d3i2v4yi-basicbit.vercel.app

Use @perkcord preview or /vercel-preview on this PR when you need a fresh manual preview deployment.

@github-actions
Copy link

github-actions bot commented Mar 26, 2026

Snapshot Preview

6 changed snapshot file(s) in this PR.
Run: https://github.com/BASIC-BIT/perkcord/actions/runs/23627380656
Artifact: pr-snapshot-preview

Storybook snapshots

No changed snapshots.

Playwright visual snapshots (6)

  • apps/web/e2e/visual.spec.ts-snapshots/subscribe-account-cancel-panel-chromium-linux.png
    apps/web/e2e/visual.spec.ts-snapshots/subscribe-account-cancel-panel-chromium-linux.png
  • apps/web/e2e/visual.spec.ts-snapshots/subscribe-account-cancel-panel-chromium-win32.png
    apps/web/e2e/visual.spec.ts-snapshots/subscribe-account-cancel-panel-chromium-win32.png
  • apps/web/e2e/visual.spec.ts-snapshots/subscribe-account-canceled-full-chromium-linux.png
    apps/web/e2e/visual.spec.ts-snapshots/subscribe-account-canceled-full-chromium-linux.png
  • apps/web/e2e/visual.spec.ts-snapshots/subscribe-account-canceled-full-chromium-win32.png
    apps/web/e2e/visual.spec.ts-snapshots/subscribe-account-canceled-full-chromium-win32.png
  • apps/web/e2e/visual.spec.ts-snapshots/subscribe-account-canceled-viewport-chromium-linux.png
    apps/web/e2e/visual.spec.ts-snapshots/subscribe-account-canceled-viewport-chromium-linux.png
  • apps/web/e2e/visual.spec.ts-snapshots/subscribe-account-canceled-viewport-chromium-win32.png
    apps/web/e2e/visual.spec.ts-snapshots/subscribe-account-canceled-viewport-chromium-win32.png

@BASIC-BIT
Copy link
Owner Author

Reviewed the snapshot preview from https://github.com/BASIC-BIT/perkcord/actions/runs/23600188630. The three changed Playwright snapshots line up with the intended UI changes in this PR: the revealed cancel panel plus the new canceled follow-up viewport/full-page states. No code change needed for this bot comment.

@BASIC-BIT
Copy link
Owner Author

Investigated the failing metrics job from https://github.com/BASIC-BIT/perkcord/actions/runs/23600360515/job/68728380938. The failure was a real lizard complexity regression on apps/web/app/subscribe/account/canceled/page.tsx after the merge brought in account-session support. I split the page’s session/subscription/support-loading logic into small helpers in apps/web/app/subscribe/account/canceled/page.tsx, reran bash scripts/check-metrics.sh, and the complexity warning is now gone locally. Pushed fix commit 095c57d.

@BASIC-BIT
Copy link
Owner Author

Investigated the failing web job from https://github.com/BASIC-BIT/perkcord/actions/runs/23600567391/job/68729133038. The failure was not a test assertion; npm --prefix apps/web run test:coverage was tripping the global function coverage gate at 93.93% < 94% after the canceled-page refactor. I added focused unit coverage for apps/web/lib/purchaseTypeLabel.ts and apps/web/lib/useDebounce.ts, reran npm --prefix apps/web run test:coverage, and coverage now passes at 95.15% functions. Pushed fix commit 7013cdf.

@BASIC-BIT
Copy link
Owner Author

Investigated the latest failing job from https://github.com/BASIC-BIT/perkcord/actions/runs/23601130295/job/68731177135. The failure was the cancellation smoke test timing out at 30s before the follow-up page assertions. The most likely bottleneck is , which polls Convex billing state and can legitimately take longer after the recent account-session/main-branch changes. I raised that single test's timeout to 90s in , then reran local gates through the push hook (, , , and all unit suites for web/bot/convex), and pushed fix commit .

@BASIC-BIT
Copy link
Owner Author

Investigated the latest failing web job from https://github.com/BASIC-BIT/perkcord/actions/runs/23601130295/job/68731177135. The failure was the cancellation smoke test timing out at 30s before the follow-up page assertions. The most likely bottleneck is ensureMemberRecurringBillingSeed(...), which polls Convex billing state and can legitimately take longer after the recent account-session/main-branch changes. I raised that single test's timeout to 90s in apps/web/e2e/smoke.spec.ts, then reran local gates through the push hook (format, lint, typecheck, and all unit suites for web/bot/convex), and pushed fix commit 58e6fc5.

Use the public Discord account-link REST endpoint in Playwright billing seeding so CI cloud runs can attach account ownership before billing lookups.
Copilot AI review requested due to automatic review settings March 26, 2026 17:22
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Redesigns the manage-billing cancellation experience to be more deliberate (two-step UI + explicit confirmation) and redirects successful cancellations to a dedicated follow-up page, with added unit/smoke/visual coverage.

Changes:

  • Replaced inline cancellation textarea with a two-step cancellation panel and confirmation checkbox.
  • Redirected cancellation success to /subscribe/account/canceled follow-up page with access-end messaging and support links.
  • Added Vitest + Playwright smoke/visual coverage for the updated flow, plus related support utilities.

Reviewed changes

Copilot reviewed 16 out of 20 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
docs/agentic/agentic-improvement-log.md Logs new Playwright coverage for the cancellation redesign.
apps/web/lib/useDebounce.test.ts Adds unit tests for useDebounce.
apps/web/lib/purchaseTypeLabel.test.ts Adds unit tests for purchase type/entitlement policy labels.
apps/web/lib/billingSession.ts Introduces a helper to require a billing session or redirect.
apps/web/lib/api/subscribe/billing.ts Adds generalized redirect builder + follow-up redirect + timestamp parsing for cancel redirect.
apps/web/lib/api/subscribe/billing.test.ts Updates tests to assert new follow-up redirect behavior and params.
apps/web/lib/accountBilling.ts Renames cancelability helper and adds support-link resolution utility.
apps/web/lib/accountBilling.test.ts Updates tests for renamed helper and adds coverage for support-link resolution.
apps/web/e2e/visual.spec.ts Updates “Account” heading expectation and snapshots the new cancellation panel + follow-up page.
apps/web/e2e/smoke.spec.ts Adds smoke coverage asserting deliberate cancellation gating and follow-up page links.
apps/web/e2e/convexTestData.ts Extends seed helper to link discord account + process provider events for recurring billing seed.
apps/web/app/subscribe/account/page.tsx Renames page/title to “Manage billing”, uses requireBillingSession, removes inline success banner.
apps/web/app/subscribe/account/canceled/page.tsx Adds dedicated follow-up page that loads subscription info and shows next steps.
apps/web/app/subscribe/account/billing-subscription-card.tsx Swaps inline form for new cancellation panel component.
apps/web/app/subscribe/account/billing-subscription-cancellation-panel.tsx Adds the new two-step cancellation panel UI.
apps/web/app/subscribe/account/billing-subscription-cancellation-panel.test.tsx Adds unit tests for reveal state + confirmation gating.
.github/workflows/convex-backup-export.yml Removes trailing whitespace in step summary output.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Owner Author

@BASIC-BIT BASIC-BIT left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed the valid Copilot feedback locally: removed the extra provider-event processing pass from the recurring billing seed, scoped the seeded provider event to the matched guild to avoid unrelated historical failures, switched the smoke timeout to testInfo.setTimeout, guarded redirect helpers against external paths, clamped the canceled-page fallback tier param, extracted canceled-page copy/timestamp helpers with focused tests, and tightened requireBillingSession typing so the account pages no longer need casts.

@BASIC-BIT
Copy link
Owner Author

Addressed the remaining valid follow-up items locally before the next push: the canceled-page fallback copy no longer promises a support link when that button may be absent, and the provider-event matcher now ignores deleted tiers when resolving price ids so stale tier rows do not break the recurring-billing smoke seed in CI. I reran the focused web helper test, web typecheck, and the Convex provider-event test suite locally.

@BASIC-BIT
Copy link
Owner Author

Investigated the fresh failing web check from https://github.com/BASIC-BIT/perkcord/actions/runs/23623235341/job/68806825987. The failure was still the recurring-billing smoke seed, but the CI backend had an archived tier reusing the same Authorize.Net subscription id, so deleted-tier filtering alone was not enough. I updated convex/convex/providerEventProcessing.ts to ignore both archived and deleted tiers during provider price-id matching, expanded the regression in convex/tests/providerEventProcessing.test.ts, reran the focused Convex/web tests plus the full pre-push format/lint/typecheck/unit suite, and pushed commit 6395da6. Fresh checks are now running on the new head.

@BASIC-BIT
Copy link
Owner Author

Investigated the next failing web run from https://github.com/BASIC-BIT/perkcord/actions/runs/23623471437/job/68807530146. The archived/deleted filter still was not enough because the shared CI backend can also contain a draft tier with the same Authorize.Net subscription id as the active seeded tier, and provider-event matching was still treating that as an ambiguity. I updated convex/convex/providerEventProcessing.ts to prefer a single live tier when both draft/live candidates share the same provider price id, added a regression for the live-vs-draft case in convex/tests/providerEventProcessing.test.ts, reran the focused Convex test and full pre-push format/lint/typecheck/unit suite, and pushed commit 478b4d4. Fresh checks are now running again on the new head.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Redesign manage-account cancellation flow to require intentional confirmation

2 participants