Accepting payments is a critical feature for many web applications. If you're using Nuxt, you might be looking for a clean way to integrate Stripe.
In this guide, we'll walk through building a complete payment flow using vue-stripe (a wrapper for Stripe.js with 1:1 parity with the React SDK) and Nuxt's server routes.
🚀 Check out this fully styled checkout demo built with Nuxt, Tailwind CSS, and Vue Stripe.
Prerequisites
Before we start, make sure you have:
- A Nuxt project set up
- A Stripe account (and your test API keys)
1. Installation
First, install the vue-stripe package along with the official Stripe JS loader:
npm install vue-stripe @stripe/stripe-js stripe
2. Creating a Payment Intent
Stripe requires you to create a payment intent on your server before you can collect payment details on the client. This ensures the transaction amount and currency are secure.
Create a new server route in your Nuxt project at server/api/create-payment-intent.post.ts:
import Stripe from 'stripe'
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig(event)
const stripe = new Stripe(runtimeConfig.stripeSecretKey)
const paymentIntent = await stripe.paymentIntents.create({
amount: 1099, // $10.99 (amount in cents)
currency: 'usd',
automatic_payment_methods: {
enabled: true,
},
})
return {
clientSecret: paymentIntent.client_secret,
}
})
Make sure to add your STRIPE_SECRET_KEY to your .env file.
3. Building the Checkout Form
Now let's create the payment form. We'll use the PaymentElement component, which automatically handles multiple payment methods (cards, wallets, bank transfers) with a single UI.
Create a component called CheckoutForm.vue:
<script setup>
import { ref } from 'vue'
import {
PaymentElement,
useElements,
useStripe,
} from 'vue-stripe'
const stripe = useStripe()
const elements = useElements()
const errorMessage = ref(null)
const handleSubmit = async () => {
if (!stripe.value || !elements.value) {
// Stripe.js has not loaded yet
return
}
// 1. Validate the form fields
const { error: submitError } = await elements.value.submit()
if (submitError) {
errorMessage.value = submitError.message
return
}
// 2. Confirm the payment
const { error } = await stripe.value.confirmPayment({
elements: elements.value,
confirmParams: {
return_url: 'http://localhost:3000/success', // Redirect after success
},
})
if (error) {
errorMessage.value = error.message
}
}
</script>
<template>
<form @submit.prevent="handleSubmit">
<PaymentElement />
<button type="submit" :disabled="!stripe || !elements">
Pay Now
</button>
<div v-if="errorMessage" class="error">
{{ errorMessage }}
</div>
</form>
</template>
4. Integrating the Elements Provider
Finally, we need to wrap our form with the Elements provider. This component initializes Stripe and passes the necessary context down to your form.
In your page (e.g., app/pages/checkout.vue):
<script setup>
import { loadStripe } from '@stripe/stripe-js'
import { Elements } from 'vue-stripe'
// Load your publishable key
const config = useRuntimeConfig()
const stripePromise = loadStripe(config.public.stripePublishableKey)
// Fetch the client secret from our server endpoint
const { data } = await useFetch('/api/create-payment-intent', {
method: 'POST'
})
</script>
<template>
<div class="checkout-page">
<h1 class="text-2xl font-bold mb-4">Secure Checkout</h1>
<div v-if="data?.clientSecret">
<Elements :stripe="stripePromise" :options="{ clientSecret: data.clientSecret }">
<CheckoutForm />
</Elements>
</div>
<div v-else>
Loading payment details...
</div>
</div>
</template>
Conclusion
Integrating payments doesn't have to be complicated. By combining Nuxt's server routes with the PaymentElement, you can accept cards, Apple Pay, Google Pay, and more with just a few lines of code.
Check out the documentation or the GitHub repo for more examples like custom styling and express checkout buttons.
Top comments (0)