{"id":292959,"date":"2019-08-20T07:41:40","date_gmt":"2019-08-20T14:41:40","guid":{"rendered":"https:\/\/css-tricks.com\/?p=292959"},"modified":"2019-08-21T09:41:55","modified_gmt":"2019-08-21T16:41:55","slug":"lets-build-a-jamstack-e-commerce-store-with-netlify-functions","status":"publish","type":"post","link":"https:\/\/css-tricks.com\/lets-build-a-jamstack-e-commerce-store-with-netlify-functions\/","title":{"rendered":"Let’s Build a JAMstack E-Commerce Store with Netlify Functions"},"content":{"rendered":"

A lot of people are confused about what JAMstack<\/a> is. The acronym stands for JavaScript, APIs, and Markup, but truly, JAMstack doesn\u2019t have to include all three. What defines JAMstack is that it\u2019s served without web servers. If you consider the history of computing, this type of abstraction isn\u2019t unnatural; rather it\u2019s the inevitable progression this industry has been moving towards.<\/p>\n

So, if JAMstack tends to be static by definition, it can\u2019t have dynamic functionality, server-side events, or use a JavaScript framework, right? Thankfully, not so. In this tutorial, we\u2019ll set up a JAMstack e-commerce app and add some serverless functionality with Netlify Functions<\/a> (which abstract AWS Lambda, and are super dope in my opinion).<\/p>\n

I’ll show more directly how the Nuxt\/Vue part was set up in a follow-up post, but for now we’re going to focus on the Stripe serverless function. I’ll show you how I set this one up, and we’ll even talk about how to connect to other static site generators such as Gatsby. Full disclosure, I work for Netlify, and am using their tools for this, it is possible to connect to Stripe with other services. I chose to work for Netlify in part because I enjoy some of the nice abstractions that their services offer.<\/p>\n

This site and repo should get you started if you\u2019d like to set something like this up yourself:<\/p>\n

<\/p>\n

\n Demo Site<\/a><\/p>\n

GitHub Repo<\/a>\n<\/div>\n

Scaffold our app<\/h3>\n

The very first step is to set up our app. This one is built with Nuxt to create a Vue app, but you can replace these commands with your tech stack of choice:<\/p>\n

yarn create nuxt-app\r\n\r\nhub create\r\n\r\ngit add -A\r\ngit commit -m \u201cinitial commit\u201d\r\n\r\ngit push -u origin master<\/code><\/pre>\n

I am using yarn<\/a>, hub<\/a> (which allows me to create repos from the command line) and Nuxt<\/a>. You may need to install these tools locally or globally before proceeding.<\/p>\n

With these few commands, following the prompts, we can set up an entirely new Nuxt project as well as the repo.<\/p>\n

If we log into Netlify<\/a> and authenticate, it will ask us to pick a repo:<\/p>\n

\"choose<\/figure>\n

I’ll use yarn generate<\/code> to create the project. With that, I can add in the site settings for Nuxt in the dist<\/code> directory and hit feploy! That’s all it takes to set up CI<\/abbr>\/CD<\/abbr> and deploy the site! Now every time I push to the master<\/code> branch, not only will I deploy, but I’ll be given a unique link for that particular deploy as well. So awesome.<\/p>\n

A basic serverless function with Netlify<\/h3>\n

So here’s the exciting part, because the setup for this kind of functionality is so quick! If you\u2019re unfamiliar with Serverless<\/a>, you can think of it like the same JavaScript functions you know and love, but executed on the server. Serverless functions are event-driven logic and their pricing is extremely low (not just on Netlify, but industry-wide) and scales with your usage. And yes, we have to add the qualifier here: serverless still uses servers<\/a>, but babysitting them is no longer your job. Let\u2019s get started.<\/p>\n

Our very basic function looks like this. I stored mine in a folder named functions, and just called it index.js<\/code>. You can truly call the folder and function what you want.<\/p>\n

\/\/ functions\/index.js\r\nexports.handler = async (event, context) => {\r\n  return {\r\n    statusCode: 200,\r\n    body: JSON.stringify({\r\n      message: \"Hi there Tacos\",\r\n      event\r\n    })\r\n  }\r\n}<\/code><\/pre>\n

We\u2019ll also need to create a netlify.toml<\/code> file at the root of the project and let it know which directory to find the function in, which in this case, is “functions.”<\/p>\n

\/\/ netlify.toml\r\n[build]\r\n  functions = \"functions\"<\/code><\/pre>\n

If we push to master<\/code> and go into the dashboard, you can see it pick up the function! <\/p>\n

\"netlify<\/figure>\n

If you look at the endpoint listed above it\u2019s stored here:
\nhttps:\/\/ecommerce-netlify.netlify.com\/.netlify\/functions\/index<\/code><\/p>\n

Really, for any site you give it, the URL will follow this pattern:
\nhttps:\/<yoursiteurlhere>\/.netlify\/functions\/<functionname><\/code><\/p>\n

When we hit that endpoint, it provides us with the message we passed in, as well as all the event data we logged as well: <\/p>\n

\"the<\/figure>\n

I love how few steps that is! This small bit of code gives us infinite power and capabilities for rich, dynamic functionality on our sites.<\/p>\n

Hook up Stripe<\/h3>\n

Pairing with Stripe<\/a> is extremely fun because it’s easy to use, sophisticated, has great docs, and works well with serverless functions. I have other tutorials<\/a> where I used Stripe because I enjoy using their service so much.<\/p>\n

Here\u2019s a bird\u2019s eye view of the app we\u2019ll be building:<\/p>\n

\"\"<\/figure>\n

First we\u2019ll go to the Stripe dashboard and get our keys. For anyone totally scandalized right now, it’s OK, these are testing keys. You can use them, too, but you\u2019ll learn more if you set them up on your own. (It\u2019s two clicks and I promise it\u2019s not hard to follow along from here.)<\/p>\n

\"testing<\/figure>\n

We\u2019ll install a package called dotenv which will help us store our key and test it locally. Then, we\u2019ll store our Stripe secret key to a Stripe variable. (You can call it anything, but here I\u2019ve called it STRIPE_SECRET_KEY<\/code>, and that\u2019s pretty much industry standard.)<\/p>\n

yarn add dotenv<\/code><\/pre>\n
require(\"dotenv\").config()\r\n\r\nconst stripe = require(\"stripe\")(process.env.STRIPE_SECRET_KEY)<\/code><\/pre>\n

In the Netlify dashboard, we\u2019ll go to “Build & deploy,” then “Environment” to add in Environment variables, where the STRIPE_SECRET_KEY<\/code> is key, and the value will be the key that starts with sk<\/code>.<\/p>\n

\"\"<\/figure>\n

We\u2019ll also add in some headers so we don\u2019t run into CORS issues. We\u2019ll use these headers throughout the function we\u2019re going to build.<\/p>\n

const headers = {\r\n  \"Access-Control-Allow-Origin\": \"*\",\r\n  \"Access-Control-Allow-Headers\": \"Content-Type\"\r\n}<\/code><\/pre>\n

So, now we\u2019ll create the functionality for talking to Stripe. We\u2019ll make sure we\u2019ll handle the cases that it\u2019s not the HTTP<\/abbr> method we\u2019re expecting, and also that we\u2019re getting the information we expect. <\/p>\n

You can already see here, what data we\u2019re going to be needing to send to stripe by what we check for. We\u2019ll need the token, the total amount, and an idempotency key<\/strong>. <\/p>\n

If you’re unfamiliar with idempotency keys, they are unique values that are generated by a client and sent to an API<\/abbr> along with a request in case the connection is disrupted. If the server receives a call that it realizes is a duplicate, it ignores the request and responds with a successful status code. Oh, and it’s an impossible word to pronounce.<\/p>\n

exports.handler = async (event, context) => {\r\n  if (!event.body || event.httpMethod !== \"POST\") {\r\n    return {\r\n      statusCode: 400,\r\n      headers,\r\n      body: JSON.stringify({\r\n        status: \"invalid http method\"\r\n      })\r\n    }\r\n  }\r\n\r\n  const data = JSON.parse(event.body)\r\n\r\n  if (!data.stripeToken || !data.stripeAmt || !data.stripeIdempotency) {\r\n    console.error(\"Required information is missing.\")\r\n\r\n    return {\r\n      statusCode: 400,\r\n      headers,\r\n      body: JSON.stringify({\r\n        status: \"missing information\"\r\n      })\r\n    }\r\n  }<\/code><\/pre>\n

Now, we\u2019ll kick off the Stripe payment processing! We\u2019ll create a Stripe customer using the email and token, do a little logging, and then create the Stripe charge. We\u2019ll specify the currency, amount, email, customer ID<\/abbr>, and give a description while we’re at it. Finally, we\u2019ll provide the idempotency key (pronounced eye-dem-po-ten-see<\/i>), and log that it was successful. <\/p>\n

(While it’s not shown here, we\u2019ll also do some error handling.)<\/p>\n

\/\/ stripe payment processing begins here\r\ntry {\r\n  await stripe.customers\r\n    .create({\r\n      email: data.stripeEmail,\r\n      source: data.stripeToken\r\n    })\r\n    .then(customer => {\r\n      console.log(\r\n        `starting the charges, amt: ${data.stripeAmt}, email: ${data.stripeEmail}`\r\n      )\r\n      return stripe.charges\r\n        .create(\r\n          {\r\n            currency: \"usd\",\r\n            amount: data.stripeAmt,\r\n            receipt_email: data.stripeEmail,\r\n            customer: customer.id,\r\n            description: \"Sample Charge\"\r\n          },\r\n          {\r\n            idempotency_key: data.stripeIdempotency\r\n          }\r\n        )\r\n        .then(result => {\r\n          console.log(`Charge created: ${result}`)\r\n        })\r\n    })<\/code><\/pre>\n

Hook it up to Nuxt<\/h3>\n
\"\"<\/figure>\n

If we look back at our application, you can see we have pages and components that live inside the pages. The Vuex store<\/a> is like the brain of our application. It will hold the state of the app, and that’s what will communicate with Stripe. However, we still need to communicate with our user via the client. We’ll collect the credit card data in a component called AppCard.vue<\/code> that will live on the cart page.<\/p>\n

First, we’ll install a package called vue-stripe-elements-plus<\/a>, that gives us some Stripe form elements that allow us to collect credit card data, and even sets us up with a pay method that allows us to create tokens for stripe payment processing. We’ll also add a library called uuid<\/abbr> that will allow us to generate unique keys, which we’ll use for the idempotency key.<\/p>\n

yarn add vue-stripe-elements-plus uuid<\/code><\/pre>\n

The default setup they give us to work with vue-stripe-elements-plus looks like this:<\/p>\n

<template>\r\n  <div id='app'>\r\n    <h1>Please give us your payment details:<\/h1>\r\n    <card class='stripe-card'\r\n      :class='{ complete }'\r\n      stripe='pk_test_XXXXXXXXXXXXXXXXXXXXXXXX'\r\n      :options='stripeOptions'\r\n      @change='complete = $event.complete'\r\n    \/>\r\n    <button class='pay-with-stripe' @click='pay' :disabled='!complete'>Pay with credit card<\/button>\r\n  <\/div>\r\n<\/template><\/code><\/pre>\n
<script>\r\nimport { stripeKey, stripeOptions } from '.\/stripeConfig.json'\r\nimport { Card, createToken } from 'vue-stripe-elements-plus'\r\n\r\nexport default {\r\n  data () {\r\n    return {\r\n      complete: false,\r\n      stripeOptions: {\r\n        \/\/ see https:\/\/stripe.com\/docs\/stripe.js#element-options for details\r\n      }\r\n    }\r\n  },\r\n\r\n  components: { Card },\r\n\r\n  methods: {\r\n    pay () {\r\n      \/\/ createToken returns a Promise which resolves in a result object with\r\n      \/\/ either a token or an error key.\r\n      \/\/ See https:\/\/stripe.com\/docs\/api#tokens for the token object.\r\n      \/\/ See https:\/\/stripe.com\/docs\/api#errors for the error object.\r\n      \/\/ More general https:\/\/stripe.com\/docs\/stripe.js#stripe-create-token.\r\n      createToken().then(data => console.log(data.token))\r\n    }\r\n  }\r\n}\r\n<\/script><\/code><\/pre>\n

So here’s what we’re going to do. We’re going to update the form to store the customer email, and update the pay method to send that and the token or error key to the Vuex store. We’ll dispatch an action to do so.<\/p>\n

data() {\r\n    return {\r\n      ...\r\n      stripeEmail: \"\"\r\n    };\r\n  },\r\n  methods: {\r\n    pay() {\r\n      createToken().then(data => {\r\n        const stripeData = { data, stripeEmail: this.stripeEmail };\r\n        this.$store.dispatch(\"postStripeFunction\", stripeData);\r\n      });\r\n    },\r\n ...<\/code><\/pre>\n

That postStripeFunction<\/abbr> action we dispatched looks like this:<\/p>\n

\/\/ Vuex store\r\nexport const actions = {\r\n  async postStripeFunction({ getters, commit }, payload) {\r\n    commit(\"updateCartUI\", \"loading\")\r\n\r\n    try {\r\n      await axios\r\n        .post(\r\n          \"https:\/\/ecommerce-netlify.netlify.com\/.netlify\/functions\/index\",\r\n          {\r\n            stripeEmail: payload.stripeEmail,\r\n            stripeAmt: Math.floor(getters.cartTotal * 100), \/\/it expects the price in cents                        \r\n            stripeToken: \"tok_visa\", \/\/testing token, later we would use payload.data.token\r\n            stripeIdempotency: uuidv1() \/\/we use this library to create a unique id\r\n          },\r\n          {\r\n            headers: {\r\n              \"Content-Type\": \"application\/json\"\r\n            }\r\n          }\r\n        )\r\n        .then(res => {\r\n          if (res.status === 200) {\r\n            commit(\"updateCartUI\", \"success\")\r\n            setTimeout(() => commit(\"clearCart\"), 3000)\r\n            \u2026<\/code><\/pre>\n

We’re going to update the UI<\/abbr> state to loading while we’re processing. Then we’ll use axios to post to our function endpoint (the URL<\/abbr> you saw earlier in the post when we set up our function). We’ll send over the email, the amt, the token and the unique key that we built the function to expect.<\/p>\n

Then if it was successful, we’ll update the UI<\/abbr> state to reflect that.<\/p>\n

One last note I’ll give is that I store the UI<\/abbr> state in a string, rather than a boolean. I usually start it with something like “idle” and, in this case, I’ll also have “loading,” “success,” and “failure.” I don’t use boolean states because I’ve rarely encountered a situation where UI<\/abbr> state only has two states. When you work with booleans for this purpose, you tend to need to break it out into more and more states, and checking for all of them can get increasingly complicated.<\/p>\n

As it stands, I can reflect changes in the UI<\/abbr> on the cart page with legible conditionals, like this:<\/p>\n

<section v-if=\"cartUIStatus === 'idle'\">\r\n  <app-cart-display \/>\r\n<\/section>\r\n\r\n<section v-else-if=\"cartUIStatus === 'loading'\" class=\"loader\">\r\n  <app-loader \/>\r\n<\/section>\r\n\r\n<section v-else-if=\"cartUIStatus === 'success'\" class=\"success\">\r\n  <h2>Success!<\/h2>\r\n  <p>Thank you for your purchase. You'll be receiving your items in 4 business days.<\/p>\r\n  <p>Forgot something?<\/p>\r\n\r\n  <button class=\"pay-with-stripe\">\r\n    <nuxt-link exact to=\"\/\">Back to Home<\/nuxt-link>\r\n  <\/button>\r\n<\/section>\r\n\r\n<section v-else-if=\"cartUIStatus === 'failure'\">\r\n  <p>Oops, something went wrong. Redirecting you to your cart to try again.<\/p>\r\n<\/section><\/code><\/pre>\n

And there you have it! We\u2019re all set up and running to accept payments with stripe on a Nuxt, Vue site with a Netlify function<\/a>, and it wasn\u2019t even that complicated to set up! <\/p>\n

Gatsby Applications<\/h3>\n

We used Nuxt in this instance but if you wanted to set up the same kind of functionality with something that uses React such as Gatsby, there’s a plugin for that<\/a>. (Everything is plugin in Gatsby. ☺️)<\/p>\n

You would install it with this command:<\/p>\n

yarn add gatsby-plugin-netlify-functions<\/code><\/pre>\n

…and add the plugin to your application like this:<\/p>\n

plugins: [\r\n  {\r\n    resolve: `gatsby-plugin-netlify-functions`,\r\n    options: {\r\n      functionsSrc: `${__dirname}\/src\/functions`,\r\n      functionsOutput: `${__dirname}\/functions`,\r\n    },\r\n  },\r\n]<\/code><\/pre>\n

The serverless function used in this demo is straight up JavaScript, so it’s also portable to React applications.<\/strong> There’s a plugin to add the Stripe script<\/a> to your Gatsby app (again, everything is a plugin). Fair warning: this adds the script to every page on the site. To collect the credit card information on the client, you would use React Stripe Elements<\/a>, which is similar to the Vue one we used above. <\/p>\n

Just make sure that you’re collecting from the client and passing all the information the function is expecting:<\/p>\n