Vue Stripe.js Integration
vue-stripe-js (current stable version 2.0.2) is a library that provides Vue 3 components for integrating Stripe.js into web applications, enabling developers to build various payment UIs. It maintains an active release cadence with frequent minor updates for bug fixes and compatibility, including enhanced Nuxt 3 support. Major versions, like v2.0.0, introduce significant architectural shifts such as the transition to ES modules. The library's core philosophy emphasizes minimal abstractions, offering direct control over Stripe.js functionalities while leveraging Vue's reactivity system. It is fully TypeScript-ready and designed to work seamlessly with modern frontend toolchains, acting as a lightweight, unopinionated bridge between the Stripe.js SDK and the Vue component lifecycle for creating advanced payment integrations.
Common errors
-
Failed to resolve import "@stripe/stripe-js" from "..." (e.g., in Vite build)
cause The `@stripe/stripe-js` package is a peer dependency of `vue-stripe-js` and was not installed.fix`npm install @stripe/stripe-js` or `yarn add @stripe/stripe-js` -
SyntaxError: Cannot use import statement outside a module
cause `vue-stripe-js` was imported using CommonJS `require()` syntax in an environment configured for ES Modules, or vice-versa, specifically after the v2.0.0 breaking change to ESM-only.fixEnsure your project uses `import ... from '...'` statements for `vue-stripe-js` and configure your build system (e.g., `package.json` `"type": "module"`) to handle ES Modules correctly. Review the v2.0.0 upgrade guide. -
Property "instance" was accessed during render but is not defined on instance.
cause A component ref (e.g., `elementsComponent.value?.instance`) is being accessed before the component is fully mounted or before Stripe.js has finished loading and initialized the instance.fixEnsure that `stripeLoaded.value` is `true` and the component refs (`elementsComponent.value`, `paymentComponent.value`) are populated before attempting to access their properties or methods, typically within an `async`/`await` block after `loadStripe` completes or inside an event handler. -
ReferenceError: Stripe is not defined
cause The Stripe.js library (from `js.stripe.com/v3/`) has not been loaded into the page's global scope before `loadStripe` is called or Stripe components attempt to initialize.fixEnsure `loadStripe()` is called and awaited before mounting Stripe components, or include `<script src="https://js.stripe.com/v3/"></script>` in your `index.html` `<body>` and verify it loads successfully before Vue application initialization.
Warnings
- breaking Version 2.0.0 of `vue-stripe-js` transitioned to be ESM-only (ES Modules). CommonJS `require()` syntax is no longer supported and will cause runtime errors.
- gotcha The `@stripe/stripe-js` package is a peer dependency and must be explicitly installed alongside `vue-stripe-js`. Failing to do so will result in module resolution errors.
- gotcha Stripe.js must be loaded and available in the global scope (or via `loadStripe`) *before* `vue-stripe-js` components are mounted. Attempting to render Stripe components without Stripe.js loaded will lead to runtime errors.
- gotcha The `client_secret` for a PaymentIntent or SetupIntent must be obtained from your backend server. It should never be hardcoded or generated purely on the client-side, as this would expose your secret key.
Install
-
npm install vue-stripe-js -
yarn add vue-stripe-js -
pnpm add vue-stripe-js
Imports
- loadStripe
const loadStripe = require('@stripe/stripe-js').loadStripeimport { loadStripe, type Stripe } from '@stripe/stripe-js' - StripeElements
const StripeElements = require('vue-stripe-js').StripeElementsimport { StripeElements } from 'vue-stripe-js' - StripeElement
import StripeElement from 'vue-stripe-js'
import { StripeElement } from 'vue-stripe-js' - StripeElementsOptionsMode
import type { StripeElementsOptionsMode } from '@stripe/stripe-js'
Quickstart
<template>
<form
v-if="stripeLoaded"
@submit.prevent="handleSubmit"
>
<StripeElements
:stripe-key="publishableKey"
:instance-options="stripeOptions"
:elements-options="elementsOptions"
ref="elementsComponent"
>
<StripeElement
type="payment"
:options="paymentElementOptions"
ref="paymentComponent"
/>
</StripeElements>
<button type="submit">
Submit Payment
</button>
</form>
<div v-else>Loading Stripe...</div>
</template>
<script setup lang="ts">
import { onBeforeMount, ref } from "vue"
import { loadStripe } from "@stripe/stripe-js"
import { StripeElements, StripeElement } from "vue-stripe-js"
import type {
StripeElementsOptionsMode,
StripePaymentElementOptions,
} from "@stripe/stripe-js"
// Use your actual publishable key from Stripe Dashboard
const publishableKey = process.env.VITE_STRIPE_PUBLISHABLE_KEY ?? "pk_test_YOUR_KEY";
const stripeOptions = ref({
// Optional: https://stripe.com/docs/js/initializing#init_stripe_js-options
})
const elementsOptions = ref<StripeElementsOptionsMode>({
// Required: https://stripe.com/docs/js/elements_object/create#stripe_elements-options
mode: "payment",
amount: 1099, // Example: $10.99
currency: "usd",
appearance: {
theme: "flat",
},
})
const paymentElementOptions = ref<StripePaymentElementOptions>({
// Optional: https://docs.stripe.com/js/elements_object/create_payment_element#payment_element_create-options
})
const stripeLoaded = ref(false)
const clientSecret = ref("") // This should come from your backend
const elementsComponent = ref<typeof StripeElements | null>(null)
const paymentComponent = ref<typeof StripeElement | null>(null)
onBeforeMount(async () => {
try {
await loadStripe(publishableKey);
stripeLoaded.value = true;
// In a real application, you would call your backend here
// to create a PaymentIntent and fetch its client_secret.
// For this example, we'll simulate a client_secret.
clientSecret.value = "pi_FAKE_CLIENT_SECRET_TEST"; // Replace with actual client_secret
elementsOptions.value.clientSecret = clientSecret.value; // Assign clientSecret to elementsOptions
} catch (error) {
console.error("Failed to load Stripe.js or initialize elements:", error);
}
})
async function handleSubmit() {
if (!elementsComponent.value || !paymentComponent.value || !clientSecret.value) {
console.error("Stripe elements not ready or client secret missing.");
return;
}
const stripeInstance = elementsComponent.value.instance;
const elements = elementsComponent.value.elements;
if (!stripeInstance || !elements) {
console.error("Stripe or Elements instance not available.");
return;
}
console.log("Attempting to confirm payment...");
const { error } = await stripeInstance.confirmPayment({
elements,
confirmParams: {
return_url: window.location.origin + '/payment-complete',
},
});
if (error) {
console.error("Payment confirmation failed:", error.message);
// Display error to the user
} else {
console.log("Payment successful! Redirecting to return_url.");
// User will be redirected to return_url
}
}
</script>