DEV Community

Cover image for Accepting payments with Stripe, Nuxt. js and vercel
Fayaz Ahmed
Fayaz Ahmed

Posted on

Accepting payments with Stripe, Nuxt. js and vercel

It's been a long time since my last post and I wanted to write a small article on how to accept payments with Stripe, as I was integrating Stripe into my SaaS project, which I am currently building.

Accepting payments is not that difficult and you don't even need a server.

I will be building this app with Nuxt.js, Tailwindcss and host it on vercel.

TLDR; the code and the live demo can be found at the bottom of this post

The site I have made is not complete and not responsive, but if someone wants to raise a PR and get it working, please go ahead.

  1. Scaffold a new Nuxt project with yarn create nuxt-app stripe-nuxt and you can select a CSS framework of your choice, I chose Tailwindcss, choose axios and I have also used nuxt-content for this, for storing the products database.

Clear the index.vue page and remove styles from default.vue files.

Add this markup and the script in index.vue, this will show a grid of products on the home page.

<template>
  <main class="min-h-screen">
    <section class="p-8 max-w-4xl mx-auto">
      <div class="grid grid-cols-1 lg:grid-cols-3 xl:grid-cols-3 gap-6">
        <nuxt-link
          :to="product.slug"
          class="overflow-hidden text-center"
          v-for="(product, p) in products"
          :key="p"
        >
          <img :src="product.images[0]" alt="product.name" class="mb-4" />
          <p class="font-semibold text-gray-700 mb-1">
            {{ product.name }}
          </p>
          <p class="text-sm">$ {{ product.amount }}</p>
        </nuxt-link>
      </div>
    </section>
  </main>
</template>

<script>
export default {
  transition: "fade",
  async asyncData({ $content }) {
    const products = await $content("products").fetch();
    return { products };
  },
};
</script>
Enter fullscreen mode Exit fullscreen mode

The above code will be rendered and look something like this.
Alt Text

Make a new file and name it _slug.vue in the same directory as index.vue, this will act as our product page and fill it with the below code.

<template>
   <main>
      <div class="flex">
         <div class="w-1/2 h-screen flex items-center justify-center">
            <img :src="product.images[0]" :alt="product.name" />
         </div>
         <div
            class="w-1/2 h-screen text-white flex items-center justify-center p-8 relative"
            :style="{ backgroundColor: `#${product.color.hex}` }"
            >
            <nuxt-link
               to="/"
               class="flex items-center space-x-2 absolute top-8 left-8"
               >
               <svg
                  class="w-5 h-5"
                  fill="none"
                  stroke="currentColor"
                  viewBox="0 0 24 24"
                  xmlns="http://www.w3.org/2000/svg"
                  >
                  <path
                     stroke-linecap="round"
                     stroke-linejoin="round"
                     stroke-width="2"
                     d="M7 16l-4-4m0 0l4-4m-4 4h18"
                     ></path>
               </svg>
               <p>Home</p>
            </nuxt-link>
            <div class="space-y-4">
               <p class="text-2xl font-bold">{{ product.name }}</p>
               <p>$ {{ product.amount }}</p>
               <p class="text-gray-100 text-sm">{{ product.description }}</p>
               <button
                  @click="buy()"
                  class="w-full py-3 bg-white text-gray-800 font-semibold flex items-center justify-center space-x-2"
                  :class="{ 'opacity-50 cursor-not-allowed': loading }"
                  >
                  <btn-loader v-if="loading" />
                  <p>Buy Now</p>
               </button>
            </div>
         </div>
      </div>
   </main>
</template>
<script>
   export default {
     transition: "fade",
     async asyncData({ $content, params }) {
       const product = await $content("products", params.slug).fetch();
       return { product };
     },
     data() {
       return {
         stripe: null,
         loading: false,
       };
     },
     methods: {
       async buy() {
         try {
           this.loading = true;
           const { data } = await this.$axios.post("/api/checkout", {
             order: {
               name: this.product.name,
               description: this.product.description,
               images: this.product.images,
               amount: this.product.amount * 100,
               currency: this.product.currency,
               quantity: 1,
             },
             slug: this.$route.params.slug,
           });
           this.stripe.redirectToCheckout({ sessionId: data.id });
         } catch (err) {
           alert(err);
           this.loading = false;
         }
       },
     },
     mounted() {
       this.stripe = Stripe("pk_test_ZaFKDdkCzVR4hCmDsUKWodm200fZIzrcmf");
     },
   };
</script>
Enter fullscreen mode Exit fullscreen mode

This will make a page looking like this, not very fancy, but looks good (not responsive).

Alt Text

We need to add the stripe checkout script in the nuxt.config.js file, add this in the head object.

script: [{src: "https://js.stripe.com/v3/"}]
Enter fullscreen mode Exit fullscreen mode

Let's focus on the script and see what's going on.
Alt Text

  1. Create an empty stripe object, this is where we will initialize the stripe object.

  2. Now pass the stripe public key to the Stripe method(the one we added in our head tag), you can get your public key from stripe dashboard

  3. Let's make a checkout API and use Vercels serverless functions. Any js file we add under a folder called api will act as a serverless function in Vercel, pretty cool right. So, I made one called checkout.js and wrote a small script.

const stripe = require("stripe")(process.env.STRIPE_TEST_SK);
const hostUrl = "http://localhost:3000";

export default async (req, res) => {
  const session = await stripe.checkout.sessions.create({
    payment_method_types: ["card"],
    line_items: [req.body.order],
    success_url: `${hostUrl}/${req.body.slug}?session_id={CHECKOUT_SESSION_ID}`,
    cancel_url: `${hostUrl}/${req.body.slug}?failed=true`
  });

  return res.status(200).json(session);
};

Enter fullscreen mode Exit fullscreen mode

You need to install the stripe package and import it and this is all you need to create a checkout session (the secret key can be found in the stripe dashboard).
The success URL and the cancel URL as the name suggest, tell stripe where to redirect respectively.

  1. Now that we have received a session id, just pass it the stripe redirect method
this.stripe.redirectToCheckout({ sessionId: data.id });
Enter fullscreen mode Exit fullscreen mode

Here's the code and here's the live demo.

If you like my work and want to get updates, please subscribe to my newsletter or if you'd like to buy me some coffee, you can donate here, we could have a online session over coffee.

Discussion (9)

Collapse
mikeoxhuge profile image
Mike • Edited on

Not working for me.
Says 'Stripe is undefined'
And if I do import Stripe from 'stripe' it fires an alert: TypeError: _this4.stripe.redirectToCheckout is not a function.
Then I cloned the repo, tried to run it and upon npm run dev I got an error:

 Vue packages version mismatch:                                                        
   │                                                                                           
   │   - vue@2.6.11                                                                            
   │   - vue-server-renderer@2.6.12                                                           
   │                                                                                           
   │   This may cause things to work incorrectly. Make sure to use the same version for both.  
   │                                                                                        
Enter fullscreen mode Exit fullscreen mode

Then I tried npm run generate so I could run npm run start, and I had the same error. I guess your tutorial is way outta date.

Collapse
aiyasahmed profile image
Aiyash Ahmed

// eslint-disable-next-line no-undef
stripe.value = Stripe(
'pk_test_ZaFKDdkCzVR4hCmDsUKWodm200fZIzrcmf'
)

OR

check you nuxt.config.js file whether the script is inside the head.

I had the same problem. but I fixed it through my above solution.

Collapse
fayaz profile image
Fayaz Ahmed Author

Hey there, looks like a race condition, your mounted function is running before the script in head is loaded or it hasn't been called.

Care to show the code?

Collapse
fajarsiddiq profile image
Fajar Siddiq

awesome!

Collapse
luciantartea profile image
Lucian Tartea

Awesome article Fayaz

Collapse
fayaz profile image
Fayaz Ahmed Author

Thanks

Collapse
alijcode profile image
alijcode

Great article, thanks for posting.

Collapse
fayaz profile image
Fayaz Ahmed Author

You're welcome Ali

Collapse
mddanishyusuf profile image
Mohd Danish

Amazing work Fayaz. Well written.