DEV Community

Robert
Robert

Posted on

How to Accept Payments with Nuxt and Stripe

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
Enter fullscreen mode Exit fullscreen mode

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,
  }
})
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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)