Stripe Hosted
PCI-compliant checkout page
Global Payments
135+ currencies supported
Free Trials
Built-in trial period support
Coupon Codes
Promotion codes & discounts
Checkout Flow
1
User Selects Plan
User clicks a subscribe button on your pricing page.
2
Create Session
Your server creates a Stripe Checkout session via the SDK.
3
Redirect to Stripe
User is redirected to Stripe's secure hosted checkout page.
4
Payment Complete
After payment, user is redirected back to your success page.
Create Checkout Session
Create a checkout session to start the subscription flow:
app/api/checkout/route.ts
import { platform } from '@/lib/platform'
import { currentUser } from '@sylphx/platform-sdk/nextjs'
import { NextRequest } from 'next/server'
export async function POST(req: NextRequest) {
const user = await currentUser()
if (!user) {
return new Response('Unauthorized', { status: 401 })
}
const { planSlug, interval } = await req.json()
const { url } = await platform.billing.createCheckout({
userId: user.id,
planSlug,
interval, // 'monthly' or 'annual'
successUrl: `${process.env.NEXT_PUBLIC_APP_URL}/billing/success?session_id={CHECKOUT_SESSION_ID}`,
cancelUrl: `${process.env.NEXT_PUBLIC_APP_URL}/pricing`,
})
return Response.json({ url })
}Checkout Button Component
'use client'
import { useState } from 'react'
interface CheckoutButtonProps {
planSlug: string
interval: 'monthly' | 'annual'
}
export function CheckoutButton({ planSlug, interval }: CheckoutButtonProps) {
const [loading, setLoading] = useState(false)
const handleCheckout = async () => {
setLoading(true)
try {
const res = await fetch('/api/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ planSlug, interval }),
})
const { url } = await res.json()
window.location.href = url
} catch (error) {
console.error('Checkout error:', error)
} finally {
setLoading(false)
}
}
return (
<button onClick={handleCheckout} disabled={loading}>
{loading ? 'Redirecting...' : 'Subscribe'}
</button>
)
}Success Page
Handle the redirect after successful payment:
app/billing/success/page.tsx
import { platform } from '@/lib/platform'
import { currentUser } from '@sylphx/platform-sdk/nextjs'
import Link from 'next/link'
export default async function CheckoutSuccessPage({
searchParams,
}: {
searchParams: { session_id?: string }
}) {
const user = await currentUser()
if (!user || !searchParams.session_id) {
return <div>Invalid session</div>
}
// Verify the checkout session
const session = await platform.billing.getCheckoutSession(searchParams.session_id)
if (session.status !== 'complete') {
return <div>Payment not completed</div>
}
// Get updated subscription
const subscription = await platform.billing.getSubscription(user.id)
return (
<div className="text-center py-16">
<div className="text-5xl mb-4">🎉</div>
<h1 className="text-2xl font-bold">Welcome to {subscription?.plan.name}!</h1>
<p className="text-gray-600 mt-2">Your subscription is now active.</p>
<Link href="/console" className="mt-6 inline-block ...">Go to Console</Link>
</div>
)
}SDK Hook (Alternative)
Use the SDK hook for a simpler client-side integration:
'use client'
import { useBilling } from '@sylphx/platform-sdk/react'
import { useState } from 'react'
export function PricingCard({ plan }) {
const { checkout } = useBilling()
const [loading, setLoading] = useState(false)
const handleSubscribe = async () => {
setLoading(true)
try {
await checkout({
planSlug: plan.slug,
interval: 'monthly',
successUrl: '/billing/success',
cancelUrl: '/pricing',
})
// User is redirected to Stripe
} catch (error) {
console.error(error)
setLoading(false)
}
}
return (
<div className="border rounded-lg p-6">
<h3>{plan.name}</h3>
<p>${plan.monthlyPrice / 100}/month</p>
<button onClick={handleSubscribe} disabled={loading}>
{loading ? 'Loading...' : 'Subscribe'}
</button>
</div>
)
}Checkout Options
Customize the checkout experience:
const { url } = await platform.billing.createCheckout({
userId: user.id,
planSlug: 'pro',
interval: 'annual',
successUrl: 'https://example.com/success',
cancelUrl: 'https://example.com/pricing',
// Optional customizations
customerEmail: user.email, // Pre-fill email
allowPromotionCodes: true, // Enable coupon codes
trialDays: 14, // Add trial period
metadata: { // Custom metadata
referralCode: 'ABC123',
},
})| Property | Type | Description |
|---|---|---|
planSlug | string | Plan to subscribe to |
interval | 'monthly' | 'annual' | Billing interval |
successUrl | string | Redirect after success |
cancelUrl | string | Redirect if cancelled |
trialDays | number? | Free trial period |
allowPromotionCodes | boolean? | Allow coupon codes |
customerEmail | string? | Pre-fill customer email |
metadata | object? | Custom metadata for tracking |
Testing
Use Stripe's test mode to develop and test checkout flows:
Test Card Number
4242 4242 4242 4242Expiry & CVC
Any future date, any 3 digitsTesting Cards
Stripe provides various test cards for different scenarios: declined cards, 3D Secure, etc. See Stripe Testing Docs