November 12th, 2019

Serverless stripe payment via Netlify functions and Gatsby

  • #serverless
  • #stripe
  • #payment
  • #netlify
  • #netlify functions
  • #gatsby
Handling payments can be a difficult task. Whether this is on a traditional server-client architecture or even when you go to a JAM stack approach. This article covers the decision making process in the variety of payment providers available and how to build it serverless on the JAM stack.

Select your payment provider/gateway

Payment is definitely a niche market. While some big vendors include payment as an out of the box feature, I prefer to go for a service fully focused on payments. Payment is a fast-evolving business. We went from cash to cards to wireless payment to crypto in 2 decades. To obtain this kind of flexibility and agility I have a strong preference for a dedicated payment service.
Doing a quick Google search shows the variety of services available for the job. I encountered following services
  • Ingenico
  • Stripe
  • Adyen
  • Paypal
Choosing between these services can be difficult as well. Ranking them can be difficult, to just focus on what matters for your project. For me it was a combination of these aspects, priority sorted.
  1. Transparent pricing
  2. Developer friendly (API driven)
  3. Europe friendly (local payment methods)
Based on my 3 most important requests, was the chosen one. But every project or setup is different, so make your own choice. Stripe is a great fit for developers because of the API driven approach and the test-mode as well. Starting out with only accepting credit cards like Visa and Mastercard, Stripe is a good fit. Stripe also supports European local Payments like IDEAL or Bancontact, but more on this topic in a follow-up post.

Setting up Stripe

Start by browsing to and click on Dashboard. Now you get full access to an environment where you can create products, accepts payments and follow up completed orders. Next to the business related information like pricing and products, you'll also find a developer overview. Here you will API keys to connect your Gatsby applications. You can also find logging, web hooks and events, but in this blog we'll just make use of the API keys.

Connecting Stripe and Gatsby

Accessing your Stripe keys in Gatsby

After setting up your Stripe account, you can wire your Stripe API keys to use in Gatsby. Make use of environment variables to hide your Stripe API's. You can use Stripe and Gatsby in different ways. Let's go into detail over the different options

Gatsby plugin Stripe

The Gatsby plugin Stripe merely ads stripe.js to every webpage. This enables you to make use of Stripe Elements to develop the checkout process yourself or you can use it to navigate to the hosted checkout by Stripe itself.

Gatsby source Stripe

Next to this, you can also make use of Gatsby Source Stripe. This is a plugin which gives you access to the Stripe API, to pull in all the date you need via GraphQL. This is a plugin you'll call during build time instead during run time, like the previous plugin. A great use case for this plugin is to list all the products which exist in Stripe.

Creating a serverless Netlify function

In this blogpost we'll focus on this use case
  • List products and prices
  • Checkout on the same site, within look and feel of the site
For the listing of products we can make use of the Gatsby source plugin. For a checkout you want to build in the look and feel of your bland or flavour, there is more effort needed. Because you're building a JAM stack (Gatsby) website you need some API calls to add dynamic behaviour to your static hosted site. This is where we stumble upon serverless. You want dynamic results in a static environment. To make this even more tangible, when a visitor adds several different products to his basket, you want the full price calculation and payment process to happen on a server. Even if you don't have a server on a JAM stack website.
Netlify functions to the rescue! Since we are hosting our website already on Netlify, we can make use of their free tier again with serverless Netlify Functions. To migrate away from the abstract terms, Netlify offers us a server which we can use to add dynamic behaviour in our website. To steal their words, it's AWS Lambda, simplified by Netlify.

Create a netlify.toml file and functions folder

    publish = "public/"
    functions = "functions"

Write a node function which returns a result

Create a file called hello-world.js inside your functions folder
Below function uses ES5 callback mechanism to return a 200 success message when calling this endpoint.
exports.handler = (event, context, callback) => {
  const response = {
    statusCode: 200,
    body: JSON.stringify({
      message: 'Hi there!',
      event: event,
  return callback(null, response)

Deploy your function to Netlify and test

After writing your first function and creating the required netlify.toml you can deploy your function to Netlify to the result.
To deploy our newly created function we'll make use of Netlify CLI. Do note that you should create your site in Netlify first, in this example, to link it in your CLI.
To read more about Netlify CLI, you can read this expert article by David Wells.
npm install netlify-cli -g
netlify login
netlify link
netlify deploy -p
After deploying to Netlify, you can now test the result. Hit and check the network request tab. It should print out the desired result

Call the function in your React component

handleClick = api => e => {
    .then(response => response.json())
    .then(json => this.setState({ msg: json.msg }))

render() {
  const { msg } = this.state
  return (
      <button onClick={this.handleClick()}></button>

export default App

Serverless price calculation via Stripe

Probably you want to evolve from writing a hello-world function to an effective Stripe function which handles your calculation on the server side. In the code below we upgraded the ES5 syntax to ES6 syntax which uses async await.
First create your function in the required Node.js format with an exports handler. In the first lines we'll import our env variables to access our Stripe secret key. This way the API key will never has to live in your source control even though the function runs on the server. In our event (imagine clicking on buy, after selecting several products) we can access our selected items via the event body.
Next we'll create a Payment Intent. In short, in Europe the regulation changed concerning authenticating online payments and when doing online payment in Europe, this rule should be followed. The intent needs two parameters
  1. A total order amount
  2. Currency
By providing these 2 parameters we should obtain a client secret from Stripe which we can pass back to our front-end React application. This secret can then be used to complete the checkout/payment flow by using Stripe Elements.
  path: `.env.${process.env.NODE_ENV}`,

const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY)

exports.handler = async (event, context, callback) => {
  const data = JSON.parse(event.body)
  const items = data.items
  //items [ { sku: 'black-medium-shirt', quantity: 1 } ]
  const paymentIntent = await stripe.paymentIntents.create({
    amount: 100,
    currency: "eur",

  return {
    statusCode: 200,
    headers: {
      "Access-Control-Allow-Origin": "*",
      "Access-Control-Allow-Headers": "Content-Type",
    body: JSON.stringify({
      clientSecret: paymentIntent.client_secret,
But both parameters are hard coded for the moment. For the currency, this is ok for now. But for the price calculation we prefer a server side calculation. We'll change this using async await code to trigger the Stripe API.
const calculateOrderAmount = async items => {
  let total = 0

  for (let index = 0; index < items.length; index++) {
    const sku = await stripe.skus.retrieve(items[index].sku)
    total += sku.price * items[index].quantity
  return total

exports.handler = async (event, context, callback) => {
  const data = JSON.parse(event.body)
  const items = data.items
  //items [ { sku: 'black-medium-shirt', quantity: 1 } ]
  const paymentIntent = await stripe.paymentIntents.create({
    amount: await calculateOrderAmount(items),
    currency: "eur",
We're introducing a asynchronous methods which uses the Stripe API to calculate the total price. When the Stripe API calls are resolved and a total is calculated, we'll return a callback to the Intent creation to start the payment process with the server-side calculated price.

Next steps

Now you can browse back to your Stripe dashboard an check the created intent. Only after paying the actual amount, the intent changes to resolved.
Boom, you successfully created a payment intent. Now you can start worrying about making a beautiful payment form using Stripe Elements to complete your checkout.
Eli Colpaert

By Eli Colpaert

Working at delaware, professional SAP C/4 enthusiast and quite passionate about new Open-Source technology.