usePaymentIntent
A composable for handling payment confirmations with Stripe's Payment Element.
What is usePaymentIntent?
A composable that simplifies payment confirmation with built-in state management:
| Capability | Description |
|---|---|
| Payment Confirmation | Wraps stripe.confirmPayment() with error handling |
| Loading State | Tracks whether a payment is being processed |
| Error State | Captures and exposes payment errors |
| Auto Elements Injection | Automatically uses Elements from context |
| Redirect Handling | Supports both redirect and inline confirmation flows |
How It Works
flowchart TD
A["Component calls usePaymentIntent()<br/>Returns { confirmPayment, loading, error }"] --> B["User clicks Pay button, triggers confirmPayment()<br/><br/>await confirmPayment({<br/> clientSecret: 'pi_xxx_secret_xxx',<br/> confirmParams: { return_url: '...' },<br/> redirect: 'if_required'<br/>})"]
B --> C["Composable sets loading.value = true<br/>Calls stripe.confirmPayment() with injected elements"]
C --> D{Result?}
D -->|Success| E["<b>SUCCESS</b><br/>loading = false<br/>Returns {<br/> paymentIntent: { status: 'succeeded' }<br/>}"]
D -->|Error| F["<b>ERROR</b><br/>loading = false<br/>error.value = message<br/>Returns { error: { message, code } }"]
E --> G["Show success message<br/>or redirect to confirmation page"]
F --> H["Display error to user<br/>Allow retry"]Usage Context
This composable must be called within a component that is a descendant of both VueStripeProvider and optionally VueStripeElements. The elements instance is automatically injected if available.
Usage
<script setup>
import { ref } from 'vue'
import { usePaymentIntent } from '@vue-stripe/vue-stripe'
const { confirmPayment, loading, error } = usePaymentIntent()
const handleSubmit = async (clientSecret: string) => {
const result = await confirmPayment({
clientSecret,
confirmParams: {
return_url: window.location.href
},
redirect: 'if_required'
})
if (result.error) {
console.error('Payment failed:', result.error.message)
} else if (result.paymentIntent?.status === 'succeeded') {
console.log('Payment successful!')
}
}
</script>Return Value
interface UsePaymentIntentReturn {
// Resolves to Stripe's native confirmPayment result (typed `any`)
confirmPayment: (options: ConfirmPaymentOptions) => Promise<any>
// Resolves to Stripe's native retrievePaymentIntent result
retrievePaymentIntent: (clientSecret: string) => Promise<any>
loading: Readonly<Ref<boolean>>
error: Readonly<Ref<string | null>>
}| Property | Type | Description |
|---|---|---|
confirmPayment | Function | Async function to confirm a payment |
retrievePaymentIntent | Function | Async function to look up a PaymentIntent by its client secret |
loading | Readonly<Ref<boolean>> | Whether a confirm or retrieve is in progress |
error | Readonly<Ref<string | null>> | Error message from the last confirm or retrieve attempt |
retrievePaymentIntent
Look up the current status of a PaymentIntent by its client secret — useful after a redirect-based flow (e.g. on your return_url page) to read the final outcome. It shares the same loading/error refs as confirmPayment.
const { retrievePaymentIntent } = usePaymentIntent()
const { paymentIntent } = await retrievePaymentIntent('pi_xxx_secret_xxx')
if (paymentIntent?.status === 'succeeded') {
// payment complete
}confirmPayment Options
import type { ConfirmPaymentData, StripeElements } from '@stripe/stripe-js'
interface ConfirmPaymentOptions {
/** Client secret from the PaymentIntent (required) */
clientSecret: string
/**
* Additional confirmation parameters.
* Typed as `ConfirmPaymentData` from `@stripe/stripe-js`.
* The fields shown below are a subset of the full type.
*/
confirmParams?: ConfirmPaymentData /* {
return_url?: string
payment_method?: string
payment_method_data?: {
billing_details?: {
name?: string
email?: string
phone?: string
address?: {
line1?: string
line2?: string
city?: string
state?: string
postal_code?: string
country?: string
}
}
}
shipping?: {
name: string
address: {
line1: string
line2?: string
city?: string
state?: string
postal_code?: string
country?: string
}
}
} */
/** Redirect behavior */
redirect?: 'if_required' | 'always'
/** Override injected elements (optional) */
elements?: StripeElements
/**
* Skip the automatic `elements.submit()` validation call before
* `stripe.confirmPayment()` (not recommended). Defaults to `false`.
*/
skipSubmit?: boolean
}| Option | Type | Default | Description |
|---|---|---|---|
clientSecret | string | — | Client secret from the PaymentIntent (required) |
confirmParams | ConfirmPaymentData | {} | Confirmation parameters from @stripe/stripe-js (shown fields are a subset) |
redirect | 'if_required' | 'always' | 'if_required' | Redirect behavior |
elements | StripeElements | injected | Override the injected Elements instance |
skipSubmit | boolean | false | Skip the automatic elements.submit() validation call before confirmation |
Automatic form submission
Before calling stripe.confirmPayment(), this composable automatically calls elements.submit() to trigger form validation and collect wallet data (Apple Pay, Google Pay). This is required by Stripe.
If elements.submit() reports a validation error, confirmPayment short-circuits: error.value is set (to the validation message or 'Form validation failed') and the result resolves to { error: submitError } — stripe.confirmPayment() is never called. Pass skipSubmit: true to skip this step (not recommended).
confirmPayment Result
confirmPayment resolves to Stripe's native stripe.confirmPayment() result and is typed as any. The ConfirmPaymentResult interface below is illustrative only — it is not an importable type from this package. It documents the shape you can expect at runtime.
// Illustrative shape — not importable
interface ConfirmPaymentResult {
paymentIntent?: {
id: string
status: 'succeeded' | 'processing' | 'requires_action' | 'requires_confirmation' | 'requires_payment_method' | 'canceled'
client_secret: string
// ... other PaymentIntent fields
}
error?: {
type: string
code?: string
message: string
decline_code?: string
param?: string
}
}Examples
Basic Payment Confirmation
<script setup>
import { ref } from 'vue'
import { usePaymentIntent } from '@vue-stripe/vue-stripe'
const { confirmPayment, loading, error } = usePaymentIntent()
const paymentStatus = ref<'idle' | 'success' | 'error'>('idle')
const handlePay = async () => {
const result = await confirmPayment({
clientSecret: 'pi_xxx_secret_xxx',
confirmParams: {
return_url: `${window.location.origin}/payment-complete`
}
})
if (result.error) {
paymentStatus.value = 'error'
} else if (result.paymentIntent?.status === 'succeeded') {
paymentStatus.value = 'success'
}
}
</script>
<template>
<div>
<button @click="handlePay" :disabled="loading">
{{ loading ? 'Processing...' : 'Pay Now' }}
</button>
<p v-if="error" class="error">{{ error }}</p>
<p v-if="paymentStatus === 'success'" class="success">Payment successful!</p>
</div>
</template>Without Redirect (for embedded flows)
<script setup>
import { usePaymentIntent } from '@vue-stripe/vue-stripe'
const { confirmPayment, loading, error } = usePaymentIntent()
const handlePay = async (clientSecret: string) => {
const result = await confirmPayment({
clientSecret,
confirmParams: {
return_url: window.location.href // Required but won't redirect
},
redirect: 'if_required' // Only redirect if 3D Secure is needed
})
if (result.paymentIntent?.status === 'succeeded') {
// Handle success inline without redirect
showSuccessMessage()
}
}
</script>With Billing Details
<script setup>
import { ref } from 'vue'
import { usePaymentIntent } from '@vue-stripe/vue-stripe'
const { confirmPayment } = usePaymentIntent()
const customerName = ref('')
const customerEmail = ref('')
const handlePay = async (clientSecret: string) => {
const result = await confirmPayment({
clientSecret,
confirmParams: {
return_url: window.location.href,
payment_method_data: {
billing_details: {
name: customerName.value,
email: customerEmail.value
}
}
},
redirect: 'if_required'
})
if (result.error) {
console.error(result.error.message)
}
}
</script>With Shipping Information
<script setup>
import { usePaymentIntent } from '@vue-stripe/vue-stripe'
const { confirmPayment } = usePaymentIntent()
const handlePay = async (clientSecret: string) => {
const result = await confirmPayment({
clientSecret,
confirmParams: {
return_url: window.location.href,
shipping: {
name: 'John Doe',
address: {
line1: '123 Main St',
city: 'San Francisco',
state: 'CA',
postal_code: '94111',
country: 'US'
}
}
},
redirect: 'if_required'
})
}
</script>Complete Payment Form
<script setup>
import { ref } from 'vue'
import {
VueStripeProvider,
VueStripeElements,
VueStripePaymentElement,
usePaymentIntent
} from '@vue-stripe/vue-stripe'
const publishableKey = 'pk_test_...'
const clientSecret = ref('') // From your backend
const isComplete = ref(false)
const paymentStatus = ref<'idle' | 'processing' | 'success' | 'error'>('idle')
const errorMessage = ref('')
// PaymentForm component (must be inside StripeElements)
</script>
<template>
<VueStripeProvider :publishable-key="publishableKey">
<VueStripeElements :client-secret="clientSecret">
<VueStripePaymentElement @change="e => isComplete = e.complete" />
<PaymentFormButton
:client-secret="clientSecret"
:disabled="!isComplete"
@success="paymentStatus = 'success'"
@error="msg => { paymentStatus = 'error'; errorMessage = msg }"
/>
</VueStripeElements>
</VueStripeProvider>
<div v-if="paymentStatus === 'success'" class="success">
Payment successful!
</div>
<div v-if="paymentStatus === 'error'" class="error">
{{ errorMessage }}
</div>
</template><!-- PaymentFormButton.vue -->
<script setup lang="ts">
import { usePaymentIntent } from '@vue-stripe/vue-stripe'
const props = defineProps<{
clientSecret: string
disabled: boolean
}>()
const emit = defineEmits<{
success: []
error: [message: string]
}>()
const { confirmPayment, loading } = usePaymentIntent()
const handleSubmit = async () => {
const result = await confirmPayment({
clientSecret: props.clientSecret,
confirmParams: {
return_url: window.location.href
},
redirect: 'if_required'
})
if (result.error) {
emit('error', result.error.message || 'Payment failed')
} else if (result.paymentIntent?.status === 'succeeded') {
emit('success')
}
}
</script>
<template>
<button
@click="handleSubmit"
:disabled="disabled || loading"
class="pay-button"
>
{{ loading ? 'Processing...' : 'Pay Now' }}
</button>
</template>Custom Elements Override
<script setup>
import { usePaymentIntent, useStripeElements } from '@vue-stripe/vue-stripe'
const { confirmPayment } = usePaymentIntent()
const { elements } = useStripeElements()
// Use a different elements instance if needed
const customElements = createCustomElements()
const handlePay = async (clientSecret: string) => {
const result = await confirmPayment({
clientSecret,
elements: customElements, // Override the injected elements
confirmParams: {
return_url: window.location.href
}
})
}
</script>TypeScript
import { usePaymentIntent } from '@vue-stripe/vue-stripe'
import type {
PaymentIntent,
StripeError
} from '@stripe/stripe-js'
const { confirmPayment, loading, error } = usePaymentIntent()
// Type-safe result handling
const handlePayment = async (clientSecret: string) => {
const result = await confirmPayment({
clientSecret,
confirmParams: {
return_url: window.location.href
},
redirect: 'if_required'
})
// result.paymentIntent is typed as PaymentIntent | undefined
// result.error is typed as StripeError | undefined
if (result.paymentIntent) {
const { id, status, amount, currency } = result.paymentIntent
console.log(`Payment ${id}: ${status}`)
}
}Error Handling
The composable handles errors in two ways:
- Stripe API errors - Returned in
result.errorand stored inerror.value - Exceptions - Caught internally and returned as
{ error: { message: string } }
<script setup>
import { usePaymentIntent } from '@vue-stripe/vue-stripe'
const { confirmPayment, error } = usePaymentIntent()
const handlePay = async (clientSecret: string) => {
const result = await confirmPayment({
clientSecret,
confirmParams: {
return_url: window.location.href
}
})
if (result.error) {
// Check error type
switch (result.error.type) {
case 'card_error':
// Card was declined
console.error('Card declined:', result.error.message)
break
case 'validation_error':
// Invalid parameters
console.error('Validation error:', result.error.message)
break
default:
console.error('Payment error:', result.error.message)
}
}
}
</script>Common Error Codes
| Code | Description |
|---|---|
card_declined | The card was declined |
expired_card | The card has expired |
incorrect_cvc | The CVC is incorrect |
insufficient_funds | Insufficient funds |
processing_error | Processing error |
authentication_required | 3D Secure authentication required |
See Also
- StripePaymentElement - Payment UI component
- StripeElements - Elements container
- useSetupIntent - For saving payment methods
- Payment Element Guide - Step-by-step guide
