useStripeCheckout
A composable for programmatically redirecting users to Stripe's hosted checkout page.
@stripe/stripe-js v8.x Compatibility
In @stripe/stripe-js v8.x, the redirectToCheckout method was removed. Use redirectToUrl() or redirectToCheckout({ url }) with the Checkout Session URL from your backend instead.
What is useStripeCheckout?
A composable for Stripe Checkout with built-in state management:
| Capability | Description |
|---|---|
| URL Redirect | Redirect using session URL (v8.x compatible) |
| Legacy Redirect | Wraps stripe.redirectToCheckout() (v7.x only) |
| Loading State | Tracks whether redirect is in progress |
| Error State | Captures and exposes checkout errors |
How It Works
flowchart TD
A["Component calls useStripeCheckout()<br/>Returns { redirectToCheckout, redirectToUrl, loading, error }"] --> B["User triggers checkout<br/><br/>// v8.x: URL-based (recommended)<br/>redirectToUrl('https://checkout.stripe.com/...')<br/>// OR<br/>await redirectToCheckout({ url: 'https://checkout...' })<br/><br/>// v7.x: Session ID-based (legacy)<br/>await redirectToCheckout({ sessionId: 'cs_test_xxx' })"]
B --> C["Composable sets loading.value = true<br/>Redirects via window.location.replace() or redirectToCheckout"]
C --> D{Result?}
D -->|Success| E["<b>SUCCESS</b><br/>User redirected to<br/>Stripe Checkout<br/>(page navigates away)"]
D -->|Error| F["<b>ERROR</b><br/>loading = false<br/>error.value = message<br/>Throws StripeProviderError"]Usage Context
This composable must be called within a component that is a descendant of VueStripeProvider. It does not require VueStripeElements.
Usage
v8.x (Recommended)
<script setup>
import { ref, onMounted } from 'vue'
import { useStripeCheckout } from '@vue-stripe/vue-stripe'
const { redirectToUrl, loading, error } = useStripeCheckout()
const sessionUrl = ref('')
onMounted(async () => {
const response = await fetch('/api/create-checkout-session', {
method: 'POST',
body: JSON.stringify({ priceId: 'price_xxx' })
})
const data = await response.json()
sessionUrl.value = data.url // Backend returns session URL
})
const handleCheckout = () => {
redirectToUrl(sessionUrl.value)
}
</script>
<template>
<button @click="handleCheckout" :disabled="loading || !sessionUrl">
{{ loading ? 'Redirecting...' : 'Checkout' }}
</button>
</template>v7.x (Legacy)
<script setup>
import { useStripeCheckout } from '@vue-stripe/vue-stripe'
const { redirectToCheckout, loading, error } = useStripeCheckout()
const handleCheckout = async () => {
try {
await redirectToCheckout({
sessionId: 'cs_test_xxx'
})
} catch (err) {
console.error('Checkout failed:', err.message)
}
}
</script>Return Value
interface UseStripeCheckoutReturn {
redirectToCheckout: (options: CheckoutRedirectOptions | LegacyCheckoutOptions) => Promise<void>
redirectToUrl: (url: string) => void
loading: Readonly<Ref<boolean>>
error: Readonly<Ref<string | null>>
}| Property | Type | Description |
|---|---|---|
redirectToUrl | (url: string) => void | Redirect using session URL (v8.x, recommended) |
redirectToCheckout | Function | Redirect with options (supports both v7.x and v8.x) |
loading | Readonly<Ref<boolean>> | Whether redirect is in progress |
error | Readonly<Ref<string | null>> | Error message from the last redirect attempt |
redirectToCheckout Options
URL-Based Options (v8.x Compatible)
interface CheckoutRedirectOptions {
/** Checkout Session URL from your backend */
url: string
}Session-Based Options (v7.x Only)
interface LegacyCheckoutOptions {
/** Checkout Session ID from your backend */
sessionId?: string
/** Line items to purchase */
lineItems?: Array<{
price: string
quantity: number
}>
/** Checkout mode */
mode?: 'payment' | 'subscription' | 'setup'
/** Redirect URL after successful payment */
successUrl?: string
/** Redirect URL when user cancels */
cancelUrl?: string
}v7.x Only
sessionId, lineItems, and other legacy options require stripe.redirectToCheckout() which was removed in @stripe/stripe-js v8.x. Use url or redirectToUrl() for v8.x compatibility.
Examples
URL-Based Checkout (v8.x)
<script setup>
import { ref, onMounted } from 'vue'
import { useStripeCheckout } from '@vue-stripe/vue-stripe'
const { redirectToUrl, loading, error } = useStripeCheckout()
const sessionUrl = ref('')
onMounted(async () => {
const response = await fetch('/api/create-checkout-session', {
method: 'POST',
body: JSON.stringify({ priceId: 'price_xxx' })
})
const data = await response.json()
sessionUrl.value = data.url // Backend returns session URL
})
const handleCheckout = () => {
redirectToUrl(sessionUrl.value)
}
</script>
<template>
<button @click="handleCheckout" :disabled="loading || !sessionUrl">
{{ loading ? 'Redirecting...' : 'Checkout' }}
</button>
<p v-if="error" class="error">{{ error }}</p>
</template>Session-Based Checkout (v7.x)
<script setup>
import { ref, onMounted } from 'vue'
import { useStripeCheckout } from '@vue-stripe/vue-stripe'
const { redirectToCheckout, loading, error } = useStripeCheckout()
const sessionId = ref('')
// Create session on mount
onMounted(async () => {
const response = await fetch('/api/create-checkout-session', {
method: 'POST',
body: JSON.stringify({ priceId: 'price_xxx' })
})
const data = await response.json()
sessionId.value = data.sessionId
})
const handleCheckout = async () => {
try {
await redirectToCheckout({ sessionId: sessionId.value })
} catch (err) {
// User stays on page if redirect fails
console.error('Checkout failed:', err.message)
}
}
</script>
<template>
<button @click="handleCheckout" :disabled="loading || !sessionId">
{{ loading ? 'Redirecting...' : 'Checkout' }}
</button>
<p v-if="error" class="error">{{ error }}</p>
</template>Multiple Products
v7.x Only
The lineItems/mode/successUrl/cancelUrl options below rely on stripe.redirectToCheckout(), which was removed in @stripe/stripe-js v8.x. On v8.x this call throws. Create a Checkout Session on your server and use redirectToUrl() or redirectToCheckout({ url }) instead.
<script setup>
import { useStripeCheckout } from '@vue-stripe/vue-stripe'
const { redirectToCheckout, loading } = useStripeCheckout()
// Shopping cart items
const cart = ref([
{ priceId: 'price_shirt', quantity: 2 },
{ priceId: 'price_hat', quantity: 1 }
])
const handleCheckout = async () => {
await redirectToCheckout({
lineItems: cart.value.map(item => ({
price: item.priceId,
quantity: item.quantity
})),
mode: 'payment',
successUrl: `${window.location.origin}/order-complete`,
cancelUrl: `${window.location.origin}/cart`
})
}
</script>Complete Integration Example
v7.x Only
The commented-out price-based (lineItems) option below relies on stripe.redirectToCheckout(), which was removed in @stripe/stripe-js v8.x and throws there. Prefer the backend session flow with redirectToUrl() or redirectToCheckout({ url }).
<script setup>
import { ref, onMounted } from 'vue'
import { VueStripeProvider, useStripeCheckout } from '@vue-stripe/vue-stripe'
const publishableKey = import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY
const selectedPlan = ref('')
const isCreatingSession = ref(false)
const plans = [
{ id: 'basic', name: 'Basic', priceId: 'price_basic', price: '$9/mo' },
{ id: 'pro', name: 'Pro', priceId: 'price_pro', price: '$29/mo' },
{ id: 'enterprise', name: 'Enterprise', priceId: 'price_enterprise', price: '$99/mo' }
]
</script>
<template>
<VueStripeProvider :publishable-key="publishableKey">
<CheckoutHandler :plans="plans" />
</VueStripeProvider>
</template><!-- CheckoutHandler.vue -->
<script setup>
import { useStripeCheckout } from '@vue-stripe/vue-stripe'
const props = defineProps<{ plans: Array<{ id: string; name: string; priceId: string; price: string }> }>()
const { redirectToCheckout, loading, error } = useStripeCheckout()
const handleSelectPlan = async (priceId: string) => {
try {
// Option 1: Create session on backend (recommended)
const response = await fetch('/api/create-checkout-session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ priceId })
})
const { sessionId } = await response.json()
await redirectToCheckout({ sessionId })
// Option 2: Price-based (simpler but less secure)
// await redirectToCheckout({
// lineItems: [{ price: priceId, quantity: 1 }],
// mode: 'subscription',
// successUrl: `${window.location.origin}/welcome`,
// cancelUrl: window.location.href
// })
} catch (err) {
console.error('Failed to start checkout:', err.message)
}
}
</script>
<template>
<div class="pricing-grid">
<div v-for="plan in plans" :key="plan.id" class="plan-card">
<h3>{{ plan.name }}</h3>
<p class="price">{{ plan.price }}</p>
<button @click="handleSelectPlan(plan.priceId)" :disabled="loading">
{{ loading ? 'Processing...' : 'Select Plan' }}
</button>
</div>
</div>
<p v-if="error" class="error">{{ error }}</p>
</template>TypeScript
import { useStripeCheckout } from '@vue-stripe/vue-stripe'
import type { CheckoutRedirectOptions, LegacyCheckoutOptions } from '@vue-stripe/vue-stripe'
const { redirectToCheckout, redirectToUrl, loading, error } = useStripeCheckout()
// URL-based (v8.x compatible, recommended)
const urlCheckout = async (url: string) => {
const options: CheckoutRedirectOptions = { url }
await redirectToCheckout(options)
}
// Session-based (v7.x only)
const sessionCheckout = async (sessionId: string) => {
const options: LegacyCheckoutOptions = { sessionId }
await redirectToCheckout(options)
}
// Price-based (v7.x only)
const priceCheckout = async (priceId: string) => {
const options: LegacyCheckoutOptions = {
lineItems: [{ price: priceId, quantity: 1 }],
mode: 'payment',
successUrl: 'https://example.com/success',
cancelUrl: 'https://example.com/cancel'
}
await redirectToCheckout(options)
}Error Handling
The composable handles errors in two ways:
- Stripe Redirect Errors - Stored in
error.valueand thrown - Initialization Errors - Thrown immediately
<script setup>
import { useStripeCheckout } from '@vue-stripe/vue-stripe'
const { redirectToCheckout, loading, error } = useStripeCheckout()
const handleCheckout = async () => {
try {
await redirectToCheckout({ sessionId: 'cs_test_xxx' })
// If successful, user is redirected (this line won't execute)
} catch (err) {
// User stays on page
if (err.message.includes('Stripe not initialized')) {
// Fixed string thrown by the composable when Stripe hasn't loaded
console.error('Please wait for Stripe to load')
} else if (err.message.includes('expired')) {
// Wording comes from Stripe's result.error.message — match loosely
await refreshCheckoutSession()
} else {
console.error('Checkout error:', err.message)
}
}
}
</script>Common Errors
Stripe not initialized is a fixed string thrown by the composable. The remaining messages come from Stripe's result.error.message (forwarded verbatim into error.value), so the exact wording is determined by Stripe, not Vue Stripe. The phrases below are illustrative examples of Stripe-returned errors.
| Error (source) | Cause | Solution |
|---|---|---|
Stripe not initialized (Vue Stripe) | Stripe hasn't loaded | Wait for StripeProvider to load |
Session expired (Stripe result.error.message) | Session too old | Create a new session |
Invalid session (Stripe result.error.message) | Wrong session ID | Check session ID format |
Redirect failed (Stripe result.error.message) | Network or Stripe error | Show retry option |
useStripeCheckout vs StripeCheckout Component
| Aspect | useStripeCheckout | StripeCheckout Component |
|---|---|---|
| Use Case | Custom UI, programmatic control | Quick integration |
| UI | You build it | Pre-styled button |
| Flexibility | Full control | Limited customization |
| Code | More code | Less code |
Use useStripeCheckout when you need custom checkout triggers (e.g., form submission, multi-step flow). Use StripeCheckout component for simple button-click checkout.
Requirements
| Requirement | Details |
|---|---|
| StripeProvider | Must be used within StripeProvider context |
| Checkout Session | Need a session ID or valid Price ID |
| No StripeElements | Does not require StripeElements wrapper |
Creating Checkout Sessions (Backend)
// Node.js example
const stripe = require('stripe')('sk_test_...')
app.post('/create-checkout-session', async (req, res) => {
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
line_items: [
{
price: req.body.priceId,
quantity: 1
}
],
mode: 'payment', // or 'subscription'
success_url: 'https://example.com/success?session_id={CHECKOUT_SESSION_ID}',
cancel_url: 'https://example.com/cancel'
})
// v8.x: Return URL (recommended)
res.json({ url: session.url })
// v7.x: Return session ID (legacy)
// res.json({ sessionId: session.id })
})See Also
- StripeCheckout - Pre-built checkout button component
- StripeProvider - Required parent component
- Checkout Guide - Step-by-step implementation guide
- Stripe Checkout Docs - Official documentation
