mirror of
https://github.com/yusufipk/OpenFrame.git
synced 2026-09-11 17:46:06 +00:00
fix(billing): show paid cancellation dates instead of leftover trial
This commit is contained in:
@@ -492,7 +492,7 @@ export default function SettingsPage({ billingOnly = false }: { billingOnly?: bo
|
|||||||
<p className="text-sm text-muted-foreground mt-1">
|
<p className="text-sm text-muted-foreground mt-1">
|
||||||
{billing.subscription.hasActiveSubscription
|
{billing.subscription.hasActiveSubscription
|
||||||
? hasScheduledCancellation
|
? hasScheduledCancellation
|
||||||
? billing.subscription.hasActiveTrial
|
? billing.subscription.status === 'TRIALING'
|
||||||
? 'Trial canceled. Access remains active until the trial ends.'
|
? 'Trial canceled. Access remains active until the trial ends.'
|
||||||
: 'Subscription canceled. Access remains active until the end of the current billing period.'
|
: 'Subscription canceled. Access remains active until the end of the current billing period.'
|
||||||
: 'Paid account with workspace creation unlocked.'
|
: 'Paid account with workspace creation unlocked.'
|
||||||
@@ -512,11 +512,11 @@ export default function SettingsPage({ billingOnly = false }: { billingOnly?: bo
|
|||||||
!billing.subscription.hasActiveSubscription ? (
|
!billing.subscription.hasActiveSubscription ? (
|
||||||
<p className="rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-sm font-medium text-destructive">
|
<p className="rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-sm font-medium text-destructive">
|
||||||
Your latest payment didn't go through. Update your payment method to keep
|
Your latest payment didn't go through. Update your payment method to keep
|
||||||
your subscription — starting a new one would create a duplicate.
|
your subscription. Starting a new one would create a duplicate.
|
||||||
</p>
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{billing.subscription.hasActiveTrial &&
|
{billing.subscription.status === 'TRIALING' &&
|
||||||
billing.subscription.trialEndsAt &&
|
billing.subscription.trialEndsAt &&
|
||||||
hasScheduledCancellation ? (
|
hasScheduledCancellation ? (
|
||||||
<p className="rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-sm font-medium text-destructive">
|
<p className="rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-sm font-medium text-destructive">
|
||||||
@@ -535,7 +535,7 @@ export default function SettingsPage({ billingOnly = false }: { billingOnly?: bo
|
|||||||
|
|
||||||
{hasScheduledCancellation && billing.subscription.cancelAt ? (
|
{hasScheduledCancellation && billing.subscription.cancelAt ? (
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
Cancellation was scheduled on{' '}
|
Cancellation takes effect on{' '}
|
||||||
{new Date(billing.subscription.cancelAt).toLocaleDateString()}.
|
{new Date(billing.subscription.cancelAt).toLocaleDateString()}.
|
||||||
</p>
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import SettingsPage from '@/app/(dashboard)/settings/settings-page-client';
|
||||||
|
|
||||||
|
function renderScheduledCancellation(status: 'ACTIVE' | 'TRIALING') {
|
||||||
|
vi.stubGlobal(
|
||||||
|
'fetch',
|
||||||
|
vi.fn(async (url: string) => {
|
||||||
|
if (url !== '/api/billing') return { ok: false };
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
json: async () => ({
|
||||||
|
data: {
|
||||||
|
isEnabled: true,
|
||||||
|
isConfigured: true,
|
||||||
|
checkoutAvailable: false,
|
||||||
|
portalAvailable: true,
|
||||||
|
cancelAvailable: false,
|
||||||
|
needsPaymentFix: false,
|
||||||
|
openInvoice: null,
|
||||||
|
workspaceCreation: { canCreateWorkspace: true, canStartTrial: false },
|
||||||
|
subscription: {
|
||||||
|
status,
|
||||||
|
label: status === 'ACTIVE' ? 'Active' : 'Trialing',
|
||||||
|
hasActiveSubscription: true,
|
||||||
|
hasRecoverableSubscription: true,
|
||||||
|
hasActiveTrial: true,
|
||||||
|
hasBillingAccess: true,
|
||||||
|
currentPeriodEnd: '2026-10-08T12:00:00Z',
|
||||||
|
trialEndsAt: '2026-09-15T12:00:00Z',
|
||||||
|
cancelAtPeriodEnd: true,
|
||||||
|
cancelAt: '2026-10-08T12:00:00Z',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
render(<SettingsPage billingOnly />);
|
||||||
|
}
|
||||||
|
|
||||||
|
afterEach(() => vi.unstubAllGlobals());
|
||||||
|
|
||||||
|
describe('scheduled cancellation in billing settings', () => {
|
||||||
|
it('keeps a paid subscription distinct from its remaining cardless trial', async () => {
|
||||||
|
renderScheduledCancellation('ACTIVE');
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await screen.findByText(
|
||||||
|
'Subscription canceled. Access remains active until the end of the current billing period.'
|
||||||
|
)
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText(/Trial canceled/)).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByText(/Access ends on/)).not.toBeInTheDocument();
|
||||||
|
expect(screen.getByText(/Your subscription ends on/)).toHaveTextContent(
|
||||||
|
new Date('2026-10-08T12:00:00Z').toLocaleDateString()
|
||||||
|
);
|
||||||
|
expect(screen.getByText(/Cancellation takes effect on/)).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText(/Cancellation was scheduled on/)).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('still explains the trial end for a Stripe trial subscription', async () => {
|
||||||
|
renderScheduledCancellation('TRIALING');
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await screen.findByText('Trial canceled. Access remains active until the trial ends.')
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(screen.getByText(/Access ends on/)).toHaveTextContent(
|
||||||
|
new Date('2026-09-15T12:00:00Z').toLocaleDateString()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user