Checkout

Stripe

Integrate Stripe Checkout for secure subscription payments.

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',
  },
})
PropertyTypeDescription
planSlugstringPlan to subscribe to
interval'monthly' | 'annual'Billing interval
successUrlstringRedirect after success
cancelUrlstringRedirect if cancelled
trialDaysnumber?Free trial period
allowPromotionCodesboolean?Allow coupon codes
customerEmailstring?Pre-fill customer email
metadataobject?Custom metadata for tracking

Testing

Use Stripe's test mode to develop and test checkout flows:

Test Card Number

4242 4242 4242 4242

Expiry & CVC

Any future date, any 3 digits

Testing Cards

Stripe provides various test cards for different scenarios: declined cards, 3D Secure, etc. See Stripe Testing Docs