Skip to content

VueStripePaymentElement

Stripe's recommended all-in-one payment component that supports cards, wallets (Apple Pay, Google Pay), bank transfers, and more.

Recommended

This is Stripe's recommended component for accepting payments. It automatically displays the most relevant payment methods based on the customer's location and transaction.

What is Payment Element?

Payment Element is Stripe's modern, unified payment interface that:

CapabilityDescription
40+ Payment MethodsCards, wallets, bank transfers, BNPL, and regional methods
Dynamic DisplayAutomatically shows relevant methods based on customer location
Built-in ValidationReal-time error handling and input validation
Responsive DesignAdapts to any screen size and device
LocalizationAutomatic translation based on customer's locale

How It Works

┌─────────────────────────────────────────────────────────────┐
│  Customer lands on checkout page                            │
└─────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│  PaymentElement renders inside StripeElements               │
│  (Receives Elements instance + clientSecret from context)   │
└─────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│  Stripe determines available payment methods based on:      │
│  - PaymentIntent currency and amount                        │
│  - Customer's location and device                           │
│  - Your Dashboard payment method settings                   │
└─────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│  Customer fills payment details                             │
│  (Emits @change with { complete, value } on each input)    │
└─────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│  On submit: Call stripe.confirmPayment()                    │
└─────────────────────────────────────────────────────────────┘

              ┌───────────────┴───────────────┐
              ▼                               ▼
┌─────────────────────────┐     ┌─────────────────────────────┐
│  SUCCESS                │     │  REQUIRES ACTION            │
│                         │     │                             │
│  PaymentIntent status:  │     │  Redirect to 3D Secure,     │
│  "succeeded"            │     │  bank auth, or other flow   │
│                         │     │  then return to return_url  │
└─────────────────────────┘     └─────────────────────────────┘

Usage

vue
<template>
  <VueStripeProvider :publishable-key="publishableKey">
    <VueStripeElements :client-secret="clientSecret">
      <VueStripePaymentElement
        :options="paymentOptions"
        @ready="onReady"
        @change="onChange"
      />
      <button @click="handleSubmit" :disabled="!isComplete">
        Pay
      </button>
    </VueStripeElements>
  </VueStripeProvider>
</template>

<script setup>
import {
  StripeProvider,
  StripeElements,
  StripePaymentElement,
  usePaymentIntent
} from '@vue-stripe/vue-stripe'
import { ref } from 'vue'

const publishableKey = 'pk_test_...'
const clientSecret = 'pi_xxx_secret_xxx' // From your backend

const isComplete = ref(false)

const paymentOptions = {
  layout: 'tabs'
}

const onReady = (element) => {
  console.log('Payment element ready', element)
}

const onChange = (event) => {
  isComplete.value = event.complete
}
</script>

Requirements

The Payment Element requires a clientSecret from a PaymentIntent. You can:

  1. Create via your backend - Recommended for production
  2. Create via Stripe Dashboard - For testing:

Props

PropTypeRequiredDescription
optionsStripePaymentElementOptionsNoPayment element configuration

Options Object

ts
interface StripePaymentElementOptions {
  layout?: 'tabs' | 'accordion' | 'auto' | {
    type: 'tabs' | 'accordion'
    defaultCollapsed?: boolean
    radios?: boolean
    spacedAccordionItems?: boolean
  }
  defaultValues?: {
    billingDetails?: {
      name?: string
      email?: string
      phone?: string
      address?: {
        line1?: string
        line2?: string
        city?: string
        state?: string
        postal_code?: string
        country?: string
      }
    }
  }
  business?: {
    name?: string
  }
  paymentMethodOrder?: string[]
  fields?: {
    billingDetails?: 'auto' | 'never' | {
      name?: 'auto' | 'never'
      email?: 'auto' | 'never'
      phone?: 'auto' | 'never'
      address?: 'auto' | 'never' | {
        line1?: 'auto' | 'never'
        line2?: 'auto' | 'never'
        city?: 'auto' | 'never'
        state?: 'auto' | 'never'
        postal_code?: 'auto' | 'never'
        country?: 'auto' | 'never'
      }
    }
  }
  wallets?: {
    applePay?: 'auto' | 'never'
    googlePay?: 'auto' | 'never'
  }
  terms?: {
    card?: 'auto' | 'always' | 'never'
    auBecsDebit?: 'auto' | 'always' | 'never'
    bancontact?: 'auto' | 'always' | 'never'
    ideal?: 'auto' | 'always' | 'never'
    sepaDebit?: 'auto' | 'always' | 'never'
    sofort?: 'auto' | 'always' | 'never'
    usBankAccount?: 'auto' | 'always' | 'never'
  }
}

Events

EventPayloadDescription
@readyVueStripePaymentElementEmitted when the element is fully rendered
@changeStripePaymentElementChangeEventEmitted when the element value changes
@focus-Emitted when the element gains focus
@blur-Emitted when the element loses focus
@escape-Emitted when the escape key is pressed
@loaderstart-Emitted when the element starts loading
@loaderstop-Emitted when the element finishes loading

Change Event

ts
interface StripePaymentElementChangeEvent {
  elementType: 'payment'
  empty: boolean
  complete: boolean
  collapsed?: boolean
  value: {
    type: string // e.g., 'card', 'ideal', 'sepa_debit'
  }
}

Slots

Loading Slot

Rendered while the payment element is initializing:

vue
<VueStripePaymentElement>
  <template #loading>
    <div class="skeleton-loader">Loading payment form...</div>
  </template>
</VueStripePaymentElement>

Error Slot

Rendered when there's an error during element creation:

vue
<VueStripePaymentElement>
  <template #error="{ error }">
    <div class="payment-error">{{ error }}</div>
  </template>
</VueStripePaymentElement>

Exposed Properties

PropertyTypeDescription
elementRef<VueStripePaymentElement | null>The Stripe payment element instance
loadingRef<boolean>Whether the element is loading
errorRef<string | null>Current error message

Examples

Tabs Layout

vue
<VueStripePaymentElement
  :options="{ layout: 'tabs' }"
/>

Accordion Layout

vue
<VueStripePaymentElement
  :options="{
    layout: {
      type: 'accordion',
      defaultCollapsed: false,
      radios: true,
      spacedAccordionItems: true
    }
  }"
/>

Pre-fill Billing Details

vue
<script setup>
const options = {
  defaultValues: {
    billingDetails: {
      name: 'John Doe',
      email: 'john@example.com',
      phone: '+1 (555) 123-4567',
      address: {
        line1: '123 Main St',
        city: 'San Francisco',
        state: 'CA',
        postal_code: '94111',
        country: 'US'
      }
    }
  }
}
</script>

<template>
  <VueStripePaymentElement :options="options" />
</template>

Hide Billing Fields

vue
<VueStripePaymentElement
  :options="{
    fields: {
      billingDetails: 'never'
    }
  }"
/>

Control Payment Method Order

vue
<VueStripePaymentElement
  :options="{
    paymentMethodOrder: ['card', 'apple_pay', 'google_pay', 'ideal']
  }"
/>

Disable Wallet Payments

vue
<VueStripePaymentElement
  :options="{
    wallets: {
      applePay: 'never',
      googlePay: 'never'
    }
  }"
/>

Complete Payment Form with usePaymentIntent

vue
<script setup>
import { ref } from 'vue'
import {
  StripeProvider,
  StripeElements,
  StripePaymentElement,
  usePaymentIntent
} from '@vue-stripe/vue-stripe'

const publishableKey = 'pk_test_...'
const clientSecret = 'pi_xxx_secret_xxx'

const isComplete = ref(false)
const status = ref('')

// This component must be inside StripeProvider
// Using a child component for the form is recommended
</script>

<template>
  <VueStripeProvider :publishable-key="publishableKey">
    <VueStripeElements :client-secret="clientSecret">
      <VueStripePaymentElement @change="e => isComplete = e.complete" />
      <PaymentFormButton :client-secret="clientSecret" :disabled="!isComplete" />
    </VueStripeElements>
  </VueStripeProvider>
</template>
vue
<!-- PaymentFormButton.vue - child component -->
<script setup>
import { ref } from 'vue'
import { usePaymentIntent } from '@vue-stripe/vue-stripe'

const props = defineProps<{
  clientSecret: string
  disabled: boolean
}>()

const { confirmPayment, loading, error } = usePaymentIntent()

const handleSubmit = async () => {
  const result = await confirmPayment({
    clientSecret: props.clientSecret,
    confirmParams: {
      return_url: window.location.href
    },
    redirect: 'if_required'
  })

  if (result.error) {
    console.error(result.error.message)
  } else if (result.paymentIntent?.status === 'succeeded') {
    console.log('Payment successful!')
  }
}
</script>

<template>
  <div>
    <button @click="handleSubmit" :disabled="disabled || loading">
      {{ loading ? 'Processing...' : 'Pay Now' }}
    </button>
    <p v-if="error" class="error">{{ error }}</p>
  </div>
</template>

With Appearance Customization

vue
<script setup>
const appearance = {
  theme: 'stripe',
  variables: {
    colorPrimary: '#635bff',
    colorBackground: '#ffffff',
    colorText: '#1a1a2e',
    colorDanger: '#df1b41',
    fontFamily: 'system-ui, sans-serif',
    spacingUnit: '4px',
    borderRadius: '8px'
  },
  rules: {
    '.Input': {
      border: '1px solid #e0e0e0',
      boxShadow: 'none'
    },
    '.Input:focus': {
      border: '1px solid #635bff',
      boxShadow: '0 0 0 3px rgba(99, 91, 255, 0.1)'
    }
  }
}
</script>

<template>
  <VueStripeElements
    :client-secret="clientSecret"
    :options="{ appearance }"
  >
    <VueStripePaymentElement />
  </VueStripeElements>
</template>

TypeScript

ts
import { ref } from 'vue'
import { StripePaymentElement } from '@vue-stripe/vue-stripe'
import type {
  StripePaymentElement as StripePaymentElementType,
  StripePaymentElementChangeEvent,
  StripePaymentElementOptions
} from '@stripe/stripe-js'

// Options
const options: StripePaymentElementOptions = {
  layout: 'tabs',
  defaultValues: {
    billingDetails: {
      email: 'customer@example.com'
    }
  }
}

// Event handlers
const handleReady = (element: StripePaymentElementType) => {
  console.log('Ready:', element)
}

const handleChange = (event: StripePaymentElementChangeEvent) => {
  console.log('Complete:', event.complete)
  console.log('Payment type:', event.value.type)
}

// Template ref
const paymentRef = ref<InstanceType<typeof StripePaymentElement>>()

Test Cards

Use these test card numbers in test mode:

CardNumber
Visa (success)4242 4242 4242 4242
Visa (declined)4000 0000 0000 0002
Requires 3D Secure4000 0025 0000 3155
Insufficient funds4000 0000 0000 9995

Use any future expiration date and any 3-digit CVC.

Advantages Over Card Element

FeaturePayment ElementCard Element
Multiple payment methodsYesCards only
Wallets (Apple/Google Pay)Built-inSeparate component
Bank transfersYesNo
Buy Now Pay LaterYesNo
Dynamic optimizationYesNo
LocalizationAutomaticManual

See Also

Released under the MIT License.