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

┌─────────────────────────────────────────────────────────────┐
│  Component calls usePaymentIntent()                         │
│  Returns { confirmPayment, loading, error }                 │
└─────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│  User clicks Pay button, triggers confirmPayment()          │
│                                                             │
│  await confirmPayment({                                     │
│    clientSecret: 'pi_xxx_secret_xxx',                       │
│    confirmParams: { return_url: '...' },                    │
│    redirect: 'if_required'                                  │
│  })                                                         │
└─────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│  Composable sets loading.value = true                       │
│  Calls stripe.confirmPayment() with injected elements       │
└─────────────────────────────────────────────────────────────┘

              ┌───────────────┴───────────────┐
              ▼                               ▼
┌─────────────────────────┐     ┌─────────────────────────────┐
│  SUCCESS                │     │  ERROR                      │
│                         │     │                             │
│  loading = false        │     │  loading = false            │
│  Returns {              │     │  error.value = message      │
│    paymentIntent: {     │     │  Returns {                  │
│      status: 'succeeded'│     │    error: { message, code } │
│    }                    │     │  }                          │
│  }                      │     │                             │
└─────────────────────────┘     └─────────────────────────────┘
              │                               │
              ▼                               ▼
┌─────────────────────────┐     ┌─────────────────────────────┐
│  Show success message   │     │  Display error to user      │
│  or redirect to         │     │  Allow retry                │
│  confirmation page      │     │                             │
└─────────────────────────┘     └─────────────────────────────┘

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 {
  StripeProvider,
  StripeElements,
  StripePaymentElement,
  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.