DEV Community

Jonathan
Jonathan

Posted on

Integrating Stripe Payment Elements in Nuxt 3

This guide will show you how to integrate Stripe's Payment Element into a Nuxt 3 application to process payments for purchasing a cat. We'll cover setting up Stripe on both the client and server sides, and handling the payment process.

Prerequisites

  • A Nuxt 3 application set up.
  • Stripe account with API keys (STRIPE_SECRET_KEY and STRIPE_PUBLIC_KEY).

Step 1 - Install Stripe

Install the necessary Stripe packages:

npm install @stripe/stripe-js stripe

Step 2 - Configure Environment Variables

Add your Stripe API keys to the .env file.

NUXT_STRIPE_PUBLIC_KEY=your_public_key_here
STRIPE_SECRET_KEY=your_secret_key_here
Enter fullscreen mode Exit fullscreen mode

Step 3: Create a Server API Endpoint

Create an endpoint to handle the creation of PaymentIntents. Create a file in this directory:server/api/stripe.js

import Stripe from 'stripe';

export default defineEventHandler(async (event) => {
  const body = await readBody(event);
  const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);

  try {
    const paymentIntent = await stripe.paymentIntents.create({
      amount: Number(body.amount), // Amount in cents
      currency: 'usd',
      automatic_payment_methods: { enabled: true },
    });

    return {
      client_secret: paymentIntent.client_secret
    };
  } catch (error) {
    console.error('Error creating PaymentIntent:', error);
    throw createError({
      statusCode: 500,
      statusMessage: 'Error creating payment intent',
    });
  }
});
Enter fullscreen mode Exit fullscreen mode

Step 4: Set Up the Basic Nuxt Page

Create a component for purchasing a cat that integrates the Payment Element and handles the payment process.

pages/purchase/[catId].vue

<template>
  <div>
    <h1>Purchase Cat {{ catId }}</h1>
    <div>
      <p>Select your favorite cat and proceed to checkout.</p>
    </div>

    <!-- Payment form for Stripe Payment Element -->
    <form @submit.prevent="pay">
      <div class="border border-gray-500 p-2 rounded-sm" id="payment-element"></div>
      <p id="payment-error" role="alert" class="text-red-700 text-center font-semibold"></p>
      <button
        :disabled="isProcessing"
        type="submit"
        class="mt-4 bg-gradient-to-r from-[#FE630C] to-[#FF3200] w-full text-white text-[21px] font-semibold p-1.5 rounded-full"
        :class="isProcessing ? 'opacity-70' : 'opacity-100'"
        id="processing"
        aria-label="loading"
      >
        <p v-if="isProcessing">I'm processing payment</p>
        <div v-else>Buy Now</div>
      </button>
    </form>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { loadStripe } from '@stripe/stripe-js';
import { useRuntimeConfig } from '#app';

// Accessing environment variables
const config = useRuntimeConfig();
const stripePk = config.public.STRIPE_PUBLIC_KEY;

const route = useRoute();
const catId = route.params.catId; // Get the cat ID from the URL
const isProcessing = ref(false);
let stripe;
let elements;
let paymentElement;
let clientSecret;

const total = 2000; // Example fixed amount for purchasing a cat in cents ($20)

onMounted(async () => {
  await initializeStripe();
});

const initializeStripe = async () => {
  stripe = await loadStripe(stripePk);

  // Create a payment intent on your server
  const res = await fetch('/api/stripe', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      amount: total // Fixed amount in cents
    })
  });

  const result = await res.json();
  clientSecret = result.client_secret;

  elements = stripe.elements({ clientSecret });

  // Create and mount the Payment Element
  paymentElement = elements.create('payment');
  paymentElement.mount('#payment-element');

  isProcessing.value = false;
};

const pay = async () => {
  isProcessing.value = true;

  try {
    const { error } = await stripe.confirmPayment({
      elements,
      confirmParams: {
        payment_method_data: {
          billing_details: {
            name: 'John Doe',
            email: 'john.doe@example.com',
            phone: '+1234567890',
          },
        },
      },
      redirect: 'if_required' // Stay on the same page unless redirect is necessary
    });

    if (error) {
      console.error('Payment error:', error);
      document.querySelector('#payment-error').textContent = error.message;
      isProcessing.value = false;
    } else {
      console.log('Payment succeeded');
      // Handle post-payment success actions, like showing a success message
    }
  } catch (error) {
    console.error('Payment processing error:', error);
    document.querySelector('#payment-error').textContent = 'An error occurred. Please try again.';
    isProcessing.value = false;
  }
};
</script>

<style scoped>
/* Add any styles you want to apply to the purchase page */
</style>
Enter fullscreen mode Exit fullscreen mode

This tutorial shows how to integrate Stripe's Payment Element into a Nuxt 3 application for a fictional online cat purchase scenario. You can further customize the form, handle additional payment methods, or expand functionality as needed.

For more detailed information, refer to the Stripe Payment Element documentation.

Top comments (5)

Collapse
 
jgdevelops profile image
Julian Gaston

Great article! A suggestion:

  • I highly recommend some syntax highlighting for these code block.

  • I believe you can do this in md by adding jsx or whatever your language is immediately after the third backtick.

Collapse
 
jmkweb profile image
Jonathan

Thanks Julian, first time using dev.to to post code so I'll give it a whirl! πŸ‘

Collapse
 
thomasbnt profile image
Thomas Bnt

Hello ! Don't hesitate to put colors on your codeblock like this example for have to have a better understanding of your code 😎

console.log('Hello world!');
Enter fullscreen mode Exit fullscreen mode

Example of how to add colors and syntax in codeblocks

Thread Thread
 
jgdevelops profile image
Julian Gaston

I didn't know how to visualize this without doing it. I didn't even think of including a picture. Thanks for adding to the point haha!!

Thread Thread
 
jmkweb profile image
Jonathan

Haha! I should of done images and stuff, though I'd use it as a jumping off point 🀣