From 5a0dffbc4cd1038b4bc45575f2d322710833333d Mon Sep 17 00:00:00 2001 From: MaheshtheDev <38828053+MaheshtheDev@users.noreply.github.com> Date: Tue, 12 May 2026 05:36:27 +0000 Subject: [PATCH] feat: in-app subscription cancellation for paid plans (#934) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Add a "Cancel subscription" button to the billing card for Pro and Scale users, alongside the existing "Manage billing" portal link. - Cancellation is scheduled end-of-cycle via `autumn.updateSubscription({ planId, cancelAction: "cancel_end_of_cycle" })`, so users keep paid features until period end. - Confirmation dialog surfaces remaining days from `useTokenUsage` and the correct plan name (Pro/Scale). - Enterprise users do not see the cancel button — contract-based, portal/sales only. Previously, the only path off a paid plan was Autumn's external customer portal, which contradicted the in-app "cancel anytime from the Billing tab" copy in the support FAQ. --- apps/web/components/settings/account.tsx | 181 ++++++++++++++++++++--- bun.lock | 2 +- 2 files changed, 164 insertions(+), 19 deletions(-) diff --git a/apps/web/components/settings/account.tsx b/apps/web/components/settings/account.tsx index a7ae2d63..676e251a 100644 --- a/apps/web/components/settings/account.tsx +++ b/apps/web/components/settings/account.tsx @@ -197,6 +197,8 @@ export default function Account() { } = useAuth() const autumn = useCustomer() const [isUpgrading, setIsUpgrading] = useState(false) + const [isCancelling, setIsCancelling] = useState(false) + const [isCancelDialogOpen, setIsCancelDialogOpen] = useState(false) const [emailConfirm, setEmailConfirm] = useState("") const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false) const [isClosingAccount, setIsClosingAccount] = useState(false) @@ -287,6 +289,33 @@ export default function Account() { } } + // Enterprise is contract-based — direct those users to the portal/sales. + const cancellablePlanId = + currentPlan === "pro" || currentPlan === "scale" + ? (`api_${currentPlan}` as const) + : null + + const handleCancelSubscription = async () => { + if (!cancellablePlanId) return + setIsCancelling(true) + try { + await autumn.updateSubscription({ + planId: cancellablePlanId, + cancelAction: "cancel_end_of_cycle", + }) + autumn.refetch?.() + setIsCancelDialogOpen(false) + toast.success( + `Subscription cancelled. ${planDisplayNames[currentPlan]} features remain active until the end of your billing period.`, + ) + } catch (error) { + console.error(error) + toast.error("Failed to cancel subscription. Please try again.") + } finally { + setIsCancelling(false) + } + } + const handleDeleteAccount = async () => { if (!user?.email || !emailMatches || membershipsPending) return setIsClosingAccount(true) @@ -546,25 +575,141 @@ export default function Account() {

- + {cancellablePlanId && ( + + + + + +
+
+
+

+ Cancel {planDisplayNames[currentPlan]}{" "} + subscription? +

+

+ You'll keep Pro features until the end of + your current billing period + {daysRemaining !== null + ? ` (${daysRemaining} day${daysRemaining !== 1 ? "s" : ""} remaining)` + : ""} + . After that, your account will switch to the + Free plan. +

+
+ + + +
+ +
+ + + + +
+
+
+ +
)} - > - - Manage billing -
- +
) : ( <> diff --git a/bun.lock b/bun.lock index bda09023..f1d890ca 100644 --- a/bun.lock +++ b/bun.lock @@ -295,7 +295,7 @@ }, "packages/memory-graph": { "name": "@supermemory/memory-graph", - "version": "0.2.0", + "version": "0.2.1", "dependencies": { "d3-force": "^3.0.0", },