Status Tracking
Real-time subscription status
Plan Changes
Upgrade & downgrade handling
Billing Portal
Self-service management
Proration
Automatic billing adjustments
Get Subscription
Retrieve a user's current subscription:
Server-side
import { platform } from '@/lib/platform'
const subscription = await platform.billing.getSubscription(userId)
if (subscription) {
console.log('Plan:', subscription.plan.name)
console.log('Status:', subscription.status)
console.log('Current Period End:', subscription.currentPeriodEnd)
} else {
console.log('No active subscription (free tier)')
}Client-side (React)
'use client'
import { useBilling } from '@sylphx/platform-sdk/react'
export function SubscriptionStatus() {
const { subscription, isLoading } = useBilling()
if (isLoading) return <div>Loading...</div>
if (!subscription) {
return <div>You're on the free plan</div>
}
return (
<div>
<p>Plan: {subscription.plan.name}</p>
<p>Status: {subscription.status}</p>
<p>Renews: {new Date(subscription.currentPeriodEnd).toLocaleDateString()}</p>
</div>
)
}Subscription Status
Subscription status values and what they mean:
activeSubscription is active and paidtrialingUser is in free trial periodpast_duePayment failed, retryingcanceledCanceled, active until period endunpaidPayment failed, subscription paused// Check if user has active access
function hasActiveSubscription(subscription: Subscription | null): boolean {
if (!subscription) return false
return ['active', 'trialing', 'canceled'].includes(subscription.status)
// 'canceled' still has access until period end
}Billing Portal
Let users manage their subscription through Stripe's billing portal:
app/api/billing/portal/route.ts
import { platform } from '@/lib/platform'
import { currentUser } from '@sylphx/platform-sdk/nextjs'
export async function POST() {
const user = await currentUser()
if (!user) {
return new Response('Unauthorized', { status: 401 })
}
const { url } = await platform.billing.createPortalSession({
userId: user.id,
returnUrl: `${process.env.NEXT_PUBLIC_APP_URL}/settings/billing`,
})
return Response.json({ url })
}Manage Subscription Button
'use client'
// Option 1: Custom fetch
export function ManageSubscriptionButton() {
const handleManage = async () => {
const res = await fetch('/api/billing/portal', { method: 'POST' })
const { url } = await res.json()
window.location.href = url
}
return <button onClick={handleManage}>Manage Subscription</button>
}
// Option 2: SDK hook
import { useBilling } from '@sylphx/platform-sdk/react'
export function ManageBillingButton() {
const { openPortal } = useBilling()
return (
<button onClick={() => openPortal({ returnUrl: '/settings' })}>
Manage Billing
</button>
)
}Portal Features
The Stripe billing portal lets users update payment methods, view invoices, change plans, and cancel subscriptions.
Cancel & Resume
Cancel a subscription (effective at period end) or resume a canceled one:
// Cancel subscription (user keeps access until period end)
const subscription = await platform.billing.cancelSubscription(userId)
// subscription.status = 'canceled'
// subscription.cancelAtPeriodEnd = true
// Resume a canceled subscription before it expires
const resumed = await platform.billing.resumeSubscription(userId)
// resumed.status = 'active'
// resumed.cancelAtPeriodEnd = false
// Client-side
const { cancelSubscription, resumeSubscription } = useBilling()
await cancelSubscription()
await resumeSubscription()Change Plan
Upgrade or downgrade to a different plan:
// Using the billing portal (recommended)
const { url } = await platform.billing.createPortalSession({
userId: user.id,
returnUrl: '/settings/billing',
})
// Or programmatically (instant change)
const subscription = await platform.billing.changePlan({
userId: user.id,
newPlanSlug: 'enterprise',
prorate: true, // Adjust billing for partial periods
})Proration
When changing plans, Stripe prorates the charges. If upgrading mid-cycle, the user is charged the difference. If downgrading, they receive credit.
Subscription Object
Full subscription object structure:
| Property | Type | Description |
|---|---|---|
id | string | Unique subscription ID |
userId | string | User who owns the subscription |
status | enum | active | trialing | past_due | canceled | unpaid |
plan | object | Plan details (id, slug, name, features) |
interval | string | monthly | annual |
currentPeriodStart | Date | Start of current billing period |
currentPeriodEnd | Date | End of current billing period |
cancelAtPeriodEnd | boolean | Will cancel at period end |
trialEnd | Date? | When trial ends (if applicable) |