Skip to content

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:

CapabilityDescription
Payment ConfirmationWraps stripe.confirmPayment() with error handling
Loading StateTracks whether a payment is being processed
Error StateCaptures and exposes payment errors
Auto Elements InjectionAutomatically uses Elements from context
Redirect HandlingSupports both redirect and inline confirmation flows

How It Works

mermaid
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

vue
<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

ts
interface UsePaymentIntentReturn {
  confirmPayment: (options: ConfirmPaymentOptions) => Promise<ConfirmPaymentResult>
  loading: Readonly<Ref<boolean>>
  error: Readonly<Ref<string | null>>
}
PropertyTypeDescription
confirmPaymentFunctionAsync function to confirm a payment
loadingReadonly<Ref<boolean>>Whether a payment confirmation is in progress
errorReadonly<Ref<string | null>>Error message from the last confirmation attempt

confirmPayment Options

ts
interface ConfirmPaymentOptions {
  /** Client secret from the PaymentIntent (required) */
  clientSecret: string

  /** Additional confirmation parameters */
  confirmParams?: {
    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
}

confirmPayment Result

ts
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

vue
<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)

vue
<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

vue
<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

vue
<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

vue
<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>
vue
<!-- 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

vue
<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

ts
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:

  1. Stripe API errors - Returned in result.error and stored in error.value
  2. Exceptions - Caught internally and returned as { error: { message: string } }
vue
<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

CodeDescription
card_declinedThe card was declined
expired_cardThe card has expired
incorrect_cvcThe CVC is incorrect
insufficient_fundsInsufficient funds
processing_errorProcessing error
authentication_required3D Secure authentication required

See Also

Released under the MIT License.