The FTC Click-to-Cancel Rule (2024) permits retention offers during the cancellation flow only if they do not add mandatory friction — the consumer must be able to skip any retention step and complete cancellation without taking additional required actions. Absent retention alternatives (downgrade, pause), the application misses a business opportunity to retain users who would have accepted a lighter-weight option. Stripe's pause_collection API makes subscription pausing a low-effort implementation. Presenting alternatives also demonstrates good faith in regulatory context: a structured, skippable retention flow is evidence of intent to honor cancellation, which reduces FTC enforcement risk.
Low because absent retention alternatives fail a UX best practice and a weak FTC good-faith signal, but do not constitute an affirmative regulatory violation — the user can still cancel without them.
Add a skippable alternatives step before the cancellation confirmation. The step must have a clearly labeled bypass. In components/CancelFlow.tsx:
if (step === 'alternatives') {
return (
<div>
<h3>Before you go</h3>
<button type="button" onClick={handleDowngradeToFree}>Switch to the free plan</button>
<button type="button" onClick={handlePauseSubscription}>Pause for 1–3 months</button>
{/* Bypass must be clearly labeled and require no additional effort */}
<button
type="button"
className="text-sm text-gray-500 underline mt-4"
onClick={() => setStep('confirm')}
>
No thanks, continue cancelling
</button>
</div>
)
}
For pause, use stripe.subscriptions.update({ pause_collection: { behavior: 'mark_uncollectible' } }) in app/api/subscription/pause/route.ts. The 'continue cancelling' path must complete in the same number of total steps as direct cancellation.
ID: subscription-compliance.cancellation.downgrade-pause
Severity: low
What to look for: Examine the cancellation flow for retention alternatives offered to the user before they complete cancellation. Look for: (1) a plan downgrade option (if the application has multiple tiers, is the user offered a free or lower tier instead of full cancellation?), (2) a subscription pause feature (Stripe supports pausing subscriptions via pause_collection), (3) a "contact support to discuss alternatives" offer. These options reduce churn and are a UX best practice — but the key audit point is that they are offered before the final cancellation step, not presented as obstacles. A "downgrade to free" link should appear in the cancellation flow, not only in account settings. Check whether the Stripe Billing Portal configuration includes plan switching. Count all instances found and enumerate each.
Pass criteria: Before completing cancellation, the user is presented with at least one retention alternative (plan downgrade, subscription pause, or support contact). The alternative is clearly labeled and easy to skip — the user can still complete cancellation without taking the alternative. The offer does not add extra mandatory steps to cancellation. At least 1 implementation must be confirmed.
Fail criteria: No retention alternatives are presented at any point in the cancellation flow. The user goes directly from "cancel" click to confirmation with no options offered.
Skip (N/A) when: The application offers only a single subscription tier with no free tier, no pause functionality, and no meaningful alternative to cancellation. Document why skip applies.
Detail on fail: Example: "Cancellation flow proceeds directly to confirmation with no downgrade offer, pause option, or support link presented." or "Only one plan tier exists and Stripe pause_collection is not implemented.".
Remediation: Add a lightweight retention step before the cancellation confirmation:
// components/CancelFlow.tsx
type CancelStep = 'reason' | 'alternatives' | 'confirm'
export function CancelFlow() {
const [step, setStep] = useState<CancelStep>('reason')
if (step === 'alternatives') {
return (
<div>
<h3>Before you go — here are some other options</h3>
{/* Option 1: Downgrade */}
<button type="button" onClick={handleDowngradeToFree}>
Switch to the free plan (keep basic access)
</button>
{/* Option 2: Pause */}
<button type="button" onClick={handlePauseSubscription}>
Pause for 1–3 months (no charges during pause)
</button>
{/* Option 3: Support */}
<a href="mailto:support@example.com?subject=Subscription question">
Talk to support
</a>
{/* Must be easy to skip */}
<button
type="button"
className="text-sm text-gray-500 underline"
onClick={() => setStep('confirm')}
>
No thanks, continue cancelling
</button>
</div>
)
}
// ...confirm step
}