Skip to main content
RapidDev - Software Development Agency
stripe-guide

How to use Stripe with Vue.js

Integrate Stripe.js into a Vue.js application by loading the Stripe script, mounting a Payment Element inside a Vue component, and confirming payments with a PaymentIntent client secret from your backend. This guide covers the full flow from form rendering to successful payment confirmation.

What you'll learn

  • How to load Stripe.js in a Vue 3 application
  • How to build a reusable payment form component with Stripe Elements
  • How to confirm a PaymentIntent from the frontend
  • How to handle payment errors and loading states in Vue
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate6 min read25 minutesVue 3+, Stripe.js v3, Node.js 18+ backendMarch 2026RapidDev Engineering Team
TL;DR

Integrate Stripe.js into a Vue.js application by loading the Stripe script, mounting a Payment Element inside a Vue component, and confirming payments with a PaymentIntent client secret from your backend. This guide covers the full flow from form rendering to successful payment confirmation.

Building a Stripe Payment Form in Vue.js

Stripe.js and Elements let you collect card details securely without sensitive data touching your server. In a Vue.js app, you load Stripe.js, create an Elements instance with a client secret from your backend, and mount the Payment Element inside a Vue component. When the user submits the form, you call stripe.confirmPayment to finalize the charge. This tutorial shows you every step.

Prerequisites

  • A Vue 3 project created with Vite or Vue CLI
  • A backend endpoint that creates a PaymentIntent and returns its client_secret
  • Your Stripe publishable key (pk_test_...)
  • Basic familiarity with Vue 3 Composition API

Step-by-step guide

1

Install the Stripe.js loader

Use the official @stripe/stripe-js package to load Stripe.js asynchronously. This avoids adding a script tag manually and gives you TypeScript types.

typescript
1npm install @stripe/stripe-js

Expected result: @stripe/stripe-js is added to your package.json dependencies.

2

Create a Stripe composable

Create a composable that initializes the Stripe instance once and provides it to your components. Use your publishable key (pk_test_...) — never the secret key.

typescript
1// src/composables/useStripe.ts
2import { loadStripe } from '@stripe/stripe-js';
3import type { Stripe } from '@stripe/stripe-js';
4import { ref } from 'vue';
5
6const stripePromise = loadStripe(import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY);
7const stripe = ref<Stripe | null>(null);
8
9export function useStripe() {
10 const init = async () => {
11 stripe.value = await stripePromise;
12 };
13
14 return { stripe, init };
15}

Expected result: A reusable composable that provides the Stripe instance throughout your Vue app.

3

Build the PaymentForm component

Create a component that fetches a PaymentIntent client secret from your backend, mounts the Stripe Payment Element, and handles form submission.

typescript
1<!-- src/components/PaymentForm.vue -->
2<template>
3 <form @submit.prevent="handleSubmit">
4 <div id="payment-element" />
5 <button type="submit" :disabled="loading">
6 {{ loading ? 'Processing...' : 'Pay now' }}
7 </button>
8 <p v-if="errorMessage" class="error">{{ errorMessage }}</p>
9 </form>
10</template>
11
12<script setup lang="ts">
13import { ref, onMounted } from 'vue';
14import { useStripe } from '../composables/useStripe';
15
16const { stripe, init } = useStripe();
17const loading = ref(false);
18const errorMessage = ref('');
19let elements: any = null;
20
21onMounted(async () => {
22 await init();
23 const res = await fetch('/api/create-payment-intent', {
24 method: 'POST',
25 headers: { 'Content-Type': 'application/json' },
26 body: JSON.stringify({ amount: 2000 })
27 });
28 const { client_secret } = await res.json();
29
30 elements = stripe.value!.elements({ clientSecret: client_secret });
31 const paymentElement = elements.create('payment');
32 paymentElement.mount('#payment-element');
33});
34
35async function handleSubmit() {
36 if (!stripe.value || !elements) return;
37 loading.value = true;
38 errorMessage.value = '';
39
40 const { error } = await stripe.value.confirmPayment({
41 elements,
42 confirmParams: {
43 return_url: window.location.origin + '/payment-success'
44 }
45 });
46
47 if (error) {
48 errorMessage.value = error.message || 'Payment failed.';
49 }
50 loading.value = false;
51}
52</script>

Expected result: A payment form renders with card fields. Submitting the form confirms the payment and redirects to /payment-success.

4

Create the backend PaymentIntent endpoint

Your backend creates a PaymentIntent and returns the client_secret. This Node.js Express example uses the Stripe secret key (sk_test_...) which must stay server-side only.

typescript
1// server.js (Node.js + Express)
2const express = require('express');
3const Stripe = require('stripe');
4const stripe = Stripe(process.env.STRIPE_SECRET_KEY);
5const app = express();
6
7app.use(express.json());
8
9app.post('/api/create-payment-intent', async (req, res) => {
10 try {
11 const intent = await stripe.paymentIntents.create({
12 amount: req.body.amount, // amount in cents
13 currency: 'usd',
14 automatic_payment_methods: { enabled: true }
15 });
16 res.json({ client_secret: intent.client_secret });
17 } catch (err) {
18 res.status(400).json({ error: err.message });
19 }
20});
21
22app.listen(3001, () => console.log('Server on port 3001'));

Expected result: POST /api/create-payment-intent returns { client_secret: 'pi_..._secret_...' }.

5

Test the payment flow

Run both your Vue dev server and your backend. Use the test card number 4242424242424242 with any future expiry and any CVC to complete a test payment.

typescript
1// Test card:
2// Number: 4242 4242 4242 4242
3// Expiry: 12/34
4// CVC: 123

Expected result: The payment succeeds, you are redirected to /payment-success, and the PaymentIntent appears as succeeded in your Stripe test Dashboard.

Complete working example

src/components/PaymentForm.vue
1<!-- src/components/PaymentForm.vue -->
2<template>
3 <div class="payment-container">
4 <h2>Complete Your Payment</h2>
5 <form @submit.prevent="handleSubmit">
6 <div id="payment-element" />
7 <button type="submit" :disabled="loading || !ready">
8 {{ loading ? 'Processing...' : `Pay $${(amount / 100).toFixed(2)}` }}
9 </button>
10 <p v-if="errorMessage" class="error">{{ errorMessage }}</p>
11 </form>
12 </div>
13</template>
14
15<script setup lang="ts">
16import { ref, onMounted } from 'vue';
17import { loadStripe } from '@stripe/stripe-js';
18import type { Stripe, StripeElements } from '@stripe/stripe-js';
19
20const props = defineProps<{ amount: number }>();
21
22const loading = ref(false);
23const ready = ref(false);
24const errorMessage = ref('');
25let stripe: Stripe | null = null;
26let elements: StripeElements | null = null;
27
28onMounted(async () => {
29 stripe = await loadStripe(import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY);
30 if (!stripe) {
31 errorMessage.value = 'Failed to load Stripe.';
32 return;
33 }
34
35 const res = await fetch('/api/create-payment-intent', {
36 method: 'POST',
37 headers: { 'Content-Type': 'application/json' },
38 body: JSON.stringify({ amount: props.amount })
39 });
40
41 if (!res.ok) {
42 errorMessage.value = 'Could not initialize payment.';
43 return;
44 }
45
46 const { client_secret } = await res.json();
47 elements = stripe.elements({
48 clientSecret: client_secret,
49 appearance: { theme: 'stripe' }
50 });
51
52 const paymentElement = elements.create('payment');
53 paymentElement.mount('#payment-element');
54 paymentElement.on('ready', () => { ready.value = true; });
55});
56
57async function handleSubmit() {
58 if (!stripe || !elements) return;
59 loading.value = true;
60 errorMessage.value = '';
61
62 const { error } = await stripe.confirmPayment({
63 elements,
64 confirmParams: {
65 return_url: `${window.location.origin}/payment-success`
66 }
67 });
68
69 if (error) {
70 errorMessage.value = error.message || 'An unexpected error occurred.';
71 }
72 loading.value = false;
73}
74</script>
75
76<style scoped>
77.payment-container {
78 max-width: 480px;
79 margin: 2rem auto;
80 padding: 2rem;
81}
82#payment-element {
83 margin-bottom: 1.5rem;
84}
85button {
86 width: 100%;
87 padding: 0.75rem;
88 background: #635bff;
89 color: white;
90 border: none;
91 border-radius: 6px;
92 font-size: 1rem;
93 cursor: pointer;
94}
95button:disabled {
96 opacity: 0.6;
97 cursor: not-allowed;
98}
99.error {
100 color: #df1b41;
101 margin-top: 0.75rem;
102}
103</style>

Common mistakes when using Stripe with Vue.js

Why it's a problem: Using the secret key (sk_test_) in frontend Vue code

How to avoid: Only use the publishable key (pk_test_) on the client. The secret key must remain on your server.

Why it's a problem: Mounting the Payment Element before the client secret is available

How to avoid: Always await the API call that returns the client_secret before calling stripe.elements().

Why it's a problem: Not handling the redirect after confirmPayment

How to avoid: Stripe redirects the user to return_url after confirmation. Make sure that route exists in your Vue Router config.

Why it's a problem: Forgetting to set VITE_STRIPE_PUBLISHABLE_KEY in environment

How to avoid: Create a .env file with VITE_STRIPE_PUBLISHABLE_KEY=pk_test_... and restart the Vite dev server.

Best practices

  • Always use the @stripe/stripe-js loader instead of adding a script tag manually
  • Keep the Stripe secret key (sk_test_) on your server only — never in Vue components
  • Use the Payment Element instead of the Card Element for automatic support of multiple payment methods
  • Show a loading state while the Payment Element mounts and during payment confirmation
  • Set appearance options on Elements to match your app's design system
  • Validate the amount on the server before creating the PaymentIntent
  • Handle both the error return from confirmPayment and network failures with try/catch

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I have a Vue 3 app using Vite and a Node.js Express backend. Show me how to create a PaymentForm component that loads Stripe.js, mounts a Payment Element, and confirms a PaymentIntent. Include the backend endpoint that creates the PaymentIntent with automatic_payment_methods enabled.

Stripe Prompt

Build a Vue 3 payment page with a Stripe Payment Element. Use the Composition API, fetch the client_secret from /api/create-payment-intent, mount the element, and handle submit with stripe.confirmPayment. Add loading and error states.

Frequently asked questions

Do I need a backend to use Stripe with Vue.js?

Yes. You need a server-side endpoint to create PaymentIntents using your Stripe secret key. The frontend only handles the UI and payment confirmation.

Can I use Stripe Elements with Vue 2?

Yes, but you will need to use the Options API instead of the Composition API. The Stripe.js integration itself is framework-agnostic.

Why use the Payment Element instead of the Card Element?

The Payment Element automatically supports multiple payment methods like cards, Apple Pay, Google Pay, and bank transfers based on your Stripe Dashboard settings. The Card Element only collects card details.

How do I style the Stripe Payment Element in Vue?

Pass an appearance object when creating the Elements instance. You can set the theme to 'stripe', 'night', or 'flat', and override individual variables like colorPrimary, borderRadius, and fontFamily.

What happens after stripe.confirmPayment is called?

Stripe redirects the user to the return_url you specified. On that page, you can retrieve the payment status from the URL query parameters using stripe.retrievePaymentIntent.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.