mirror of
https://github.com/yusufipk/OpenFrame.git
synced 2026-09-11 09:36:08 +00:00
feat(billing): cancel in-app with a one-question reason
Add a "Cancel subscription" button beside "Manage Subscription" in Settings. It opens a dialog with one optional question (five answers, no default, a note box under the two that want detail), then schedules the Stripe subscription to end at the close of the current period without a trip to the portal. The answer is stored in a new subscription_cancellations table and shown, with an all-time tally, on the admin dashboard; the category is also mirrored onto Stripe's cancellation feedback, the free text stays local. The cancel route claims the local cancel flag with a conditional update before calling Stripe, so two racing requests cannot both write a reason row, and hands the claim back when Stripe refuses. A subscription Stripe no longer knows answers 409 with a pointer to the portal instead of a 500. The route carries an account-keyed rate limit on top of the shared IP one. Two fixes found on the way: the pinned Stripe API version reports current_period_end on the subscription item rather than the subscription, so the sync stored null for every period end; a shared helper now reads the item first. And the RadioGroup styles targeted a data-checked attribute radix never writes, so the checked state was invisible in the light theme.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { Metadata } from 'next';
|
||||
import { Suspense } from 'react';
|
||||
import { db } from '@/lib/db';
|
||||
import { auth } from '@/lib/auth';
|
||||
import { isBunnyUploadsFeatureEnabled, isStripeBillingEnabled } from '@/lib/feature-flags';
|
||||
@@ -10,6 +11,7 @@ import {
|
||||
} from '@/lib/admin-stats';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { RefreshR2StatsButton } from '@/components/admin/refresh-r2-stats-button';
|
||||
import { CancellationReasonsCard } from '@/components/admin/cancellation-reasons-card';
|
||||
import {
|
||||
Users,
|
||||
Folder,
|
||||
@@ -289,6 +291,14 @@ export default async function AdminDashboardPage() {
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Outside the `stripeStats` guard on purpose: the answers live in our own
|
||||
table and must stay readable while a Stripe outage blanks the cards above. */}
|
||||
{isStripeBillingEnabled() && (
|
||||
<Suspense fallback={null}>
|
||||
<CancellationReasonsCard />
|
||||
</Suspense>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user