Payment Element
The Payment Element is Stripe's recommended way to collect payments. It dynamically displays 40+ payment methods based on your customer's location, currency, and device.
Basic Setup
vue
<script setup>
import { ref, onMounted } from 'vue'
import {
StripeProvider,
StripeElements,
StripePaymentElement,
useStripe,
useStripeElements
} from '@vue-stripe/vue-stripe'
const publishableKey = import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY
const clientSecret = ref('')
onMounted(async () => {
// Fetch from your backend
const response = await fetch('/api/create-payment-intent')
const data = await response.json()
clientSecret.value = data.clientSecret
})
const handleSubmit = async () => {
const { stripe } = useStripe()
const { elements } = useStripeElements()
const { error } = await stripe.value.confirmPayment({
elements: elements.value,
confirmParams: {
return_url: `${window.location.origin}/complete`
}
})
if (error) {
console.error(error.message)
}
}
</script>
<template>
<VueStripeProvider :publishable-key="publishableKey">
<VueStripeElements v-if="clientSecret" :client-secret="clientSecret">
<form @submit.prevent="handleSubmit">
<VueStripePaymentElement />
<button type="submit">Pay</button>
</form>
</VueStripeElements>
</VueStripeProvider>
</template>Customizing Appearance
Use Stripe's Appearance API via VueStripeElements:
vue
<VueStripeElements
:client-secret="clientSecret"
:options="{
appearance: {
theme: 'stripe',
variables: {
colorPrimary: '#0570de',
colorBackground: '#ffffff',
colorText: '#30313d',
colorDanger: '#df1b41',
fontFamily: 'Ideal Sans, system-ui, sans-serif',
spacingUnit: '4px',
borderRadius: '4px'
},
rules: {
'.Input': {
border: '1px solid #e6e6e6'
},
'.Input:focus': {
border: '1px solid #0570de',
boxShadow: '0 0 0 1px #0570de'
}
}
}
}"
>
<VueStripePaymentElement />
</VueStripeElements>Pre-filling Customer Data
vue
<VueStripePaymentElement
:options="{
defaultValues: {
billingDetails: {
name: customer.name,
email: customer.email,
phone: customer.phone,
address: {
line1: customer.address,
city: customer.city,
state: customer.state,
postal_code: customer.zip,
country: customer.country
}
}
}
}"
/>Handling Payment Confirmation
Standard Flow (with redirect)
js
const { error } = await stripe.value.confirmPayment({
elements: elements.value,
confirmParams: {
return_url: 'https://yoursite.com/payment-complete'
}
})
// If error, show it (user stays on page)
// If success, user is redirected to return_urlWithout Redirect
For SPA flows where you don't want to redirect:
js
const { error, paymentIntent } = await stripe.value.confirmPayment({
elements: elements.value,
confirmParams: {
return_url: 'https://yoursite.com/payment-complete'
},
redirect: 'if_required'
})
if (error) {
// Show error
} else if (paymentIntent.status === 'succeeded') {
// Payment succeeded, show success UI
}WARNING
Some payment methods (like bank redirects) always require a redirect. Use redirect: 'if_required' to handle both cases.
Payment Methods
Enable payment methods in your Stripe Dashboard. The Payment Element automatically shows enabled methods.
Common payment methods:
- Cards - Visa, Mastercard, Amex, etc.
- Wallets - Apple Pay, Google Pay, Link
- Bank debits - ACH, SEPA, BACS
- Bank redirects - iDEAL, Bancontact, Sofort
- Buy now, pay later - Klarna, Afterpay, Affirm
Complete Example with Error Handling
vue
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import {
StripeProvider,
StripeElements,
StripePaymentElement,
useStripe,
useStripeElements
} from '@vue-stripe/vue-stripe'
const publishableKey = import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY
const clientSecret = ref('')
const loading = ref(true)
const processing = ref(false)
const errorMessage = ref('')
const isComplete = ref(false)
onMounted(async () => {
try {
const response = await fetch('/api/create-payment-intent', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ amount: 2000 })
})
const data = await response.json()
clientSecret.value = data.clientSecret
} catch (err) {
errorMessage.value = 'Failed to initialize payment'
} finally {
loading.value = false
}
})
const handleChange = (event: { complete: boolean }) => {
isComplete.value = event.complete
errorMessage.value = ''
}
const handleSubmit = async () => {
const { stripe } = useStripe()
const { elements } = useStripeElements()
if (!stripe.value || !elements.value) return
processing.value = true
errorMessage.value = ''
const { error } = await stripe.value.confirmPayment({
elements: elements.value,
confirmParams: {
return_url: `${window.location.origin}/payment-complete`
}
})
if (error) {
if (error.type === 'card_error' || error.type === 'validation_error') {
errorMessage.value = error.message || 'Payment failed'
} else {
errorMessage.value = 'An unexpected error occurred'
}
processing.value = false
}
}
</script>
<template>
<div class="payment-form">
<div v-if="loading">Loading payment form...</div>
<VueStripeProvider v-else :publishable-key="publishableKey">
<VueStripeElements
v-if="clientSecret"
:client-secret="clientSecret"
:options="{ appearance: { theme: 'stripe' } }"
>
<form @submit.prevent="handleSubmit">
<VueStripePaymentElement @change="handleChange" />
<div v-if="errorMessage" class="error">
{{ errorMessage }}
</div>
<button
type="submit"
:disabled="processing || !isComplete"
>
{{ processing ? 'Processing...' : 'Pay $20.00' }}
</button>
</form>
</VueStripeElements>
<div v-else-if="errorMessage" class="error">
{{ errorMessage }}
</div>
</VueStripeProvider>
</div>
</template>
<style scoped>
.payment-form {
max-width: 400px;
margin: 0 auto;
}
form {
display: flex;
flex-direction: column;
gap: 16px;
}
button {
background: #5469d4;
color: #fff;
padding: 12px 16px;
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.error {
color: #df1b41;
font-size: 14px;
}
</style>Next Steps
- Express Checkout — Add Apple Pay and Google Pay
- Customization — Theme and style your payment form
- Error Handling — Handle all error cases
- API Reference — Full props, events, and options
