Serverless functions allow us to build secure e-commerce flows into our sites, even without servers! Learn how to use Netlify Functions to securely create Stripe Checkout sessions, which allows us to quickly set up full-featured e-commerce on Jamstack sites.
Jason Lengstorf: [0:00] Now that our site's mostly set up, we're ready to start processing orders.
[0:04] We're going to go in to index.html, and we're going to bring in the Stripe JS library. After that, we can create a new file in JS, called Stripe purchase, and this is where we're going to add our handling logic for sending someone to Stripe.
[0:25] We're going to export an async function, called handleFormSubmission, and that is going to take an event, and this'll be an event handler. The first that we want to do, is prevent the default, so we're not going to handle the default submission at all.
[0:44] Then, we're going to grab our form data, which we'll do using the FormData API. Props to Suz Hinton for teaching me about this one.
[0:57] Then, I'm going to get that data as an object. I just want the sku, which is the form get skew, and the quantity. That is form get quantity.
[1:16] Now that I've got this data, I'm going to send it somewhere. We haven't built this yet, but we're going to build it shortly. I'm going to send this, using the fetch API, to netlify functions create checkout. We still need to build that, but for now, we're going to act as though it exists, and then we'll build it here in a second.
[1:40] We're going to use the POST method to send it. We're going to send some headers, and the headers we want to send, we just want to let the function know that we're sending in json, so application json. Then, we're going to stringify the body -- the data specifically -- so that we can send that. Then, whatever comes back we're going to then res, and run res.json.
[2:13] What we want this function to send back, is both a session ID from Stripe, and the Stripe publishable key. We're going to get first, a new Stripe object, and that's going to come out of the Stripe js. We're going to be able to create a new Stripe object -- this global gets added by Stripe js -- and we're going to pass in that publishableKey. That's how we authenticate with Stripe publishableKey.
[2:46] Then, we're going to see if we get an error out of running await stripe.redirectToCheckout. Stripe checkout is their hosted checkout flow, which is really, really convenient for not having to do a whole bunch of security compliance, and other nightmares around making this purchase flow possible on a personal website.
[3:16] If we get an error, we're going to run console.error, and log that out. That's about all there is to it on the client side.
[3:25] The last thing that we need to do is update load products, and attach that form handler. We're going to import, up at the top...We'll import handleFormSubmission from stripe purchase.js. Then, we're going to grab that, and at the bottom here, we'll just add another line that says const form equals product.querySelector form, because we want all products to handle their own submission.
[4:02] Then we'll do form.addEventListener, and on submit, we're going to run handleFormSubmission. Now that we've done that, we need to actually handle this function. This function is going to require a package, which means that we need to have a package json in it.
[4:25] Let's move in to functions, and run yarn init y. I'm using the yarn init over the npm init, because I like the way that these packages look a little bit better. Then, I'm going to install stripe, so npm install Stripe. OK, so there's Stripe. We're all good.
[4:46] Then, I need to tell netlify to install our dependencies in the functions directory. I'm going to update the command to be change in to the functions directory. Then, we're going to install. Then, we're just going to change back, to make sure we don't confuse the build bot.
[5:07] OK, now that we've done that, we can come in and actually create our function. We're going to create a new file, and it's going to be called create-checkout.js. This is where we're going to talk to stripe, so we need to include that Stripe library that we just created, so require Stripe. Then remember we are importing our environment variable, so we'll get process and stripe secret key.
[5:36] The publishable key can be seen. People can see that. It doesn't give them any privileged access. The secret key, on the other hand, does, and so by putting it into a serverless function, we make sure it never shows up on the client. There is no way to get at this from the Internet. You can only call this function, which won't expose this value.
[5:56] Then we're going to pull in our product inventory. We're going to get that from data⁄products. The reason that we do that is that we don't want to pass around a checkout where the client was able to set prices and things. Instead, we just pass the sku and the quantity, and now we're going to lookup the product to get pricing.
[6:18] Inside of our handler, we're going to export.handler, and that's an async, and it gets to the event, the actual function event. Let's pull out the SKU and the quantity. Those were sent in as JSON, so we're going to JSON parse the event.body. That's what was sent in by our function here, stripe-purchase. We're sending in the stringified body, which is the sku and quantity. In our function, we're just extracting that back out by parsing the JSON string and grabbing sku and quantity.
[6:54] Then we're going to get our product by doing an inventory lookup, so inventory.find, and we will look at the product that has a sku that matches our sku. Now we've got our product.
[7:42] Now that we have the product and the details that we need to make the order, we need to create a session with Stripe. The way that we do that is we run const session = await stripe.checkout.sessions.create, and then we're going to pass in some details here. We can set the payment method types. These are the ways we're going to let people pay. In our case, we're only going to allow credit card payments.
[8:10] We also want to decide whether or not we're going to ask for billing address. We only want to collect that if we have to, so we're going to set that to auto. If stripe decides that we need the billing address, it will ask for it. If not, it won't.
[8:23] We also need to get shipping addresses, because these are physical products. We're going to get the shipping address collection. Inside here, we need to decide where we're going to ship to. Stripe will allow you to set any countries you want, but you have to explicitly set each country that you're going to ship to. For our example, we are going to ship to the US and Canada.
[8:44] Next, we need to decide what's going to happen when things go right, so for our success URL, we're going to create a new page, doesn't exist yet, but we'll send to process.m.url⁄success.html. This process.m.url is set by Netlify as part of the build. What's nice about using this versus hard-coding something is that we can now use this locally or in production, and this environment variable will be set properly.
[9:12] It's going to redirect to localhost when we develop locally, and then when we deploy, it'll actually set to whatever our production URL is.
[9:20] For cancel, we actually just want to go back to our store page, so we'll just set that to be our home page in this example, because our index.html is the store.
[9:33] Now that we've got all the details about how the session is going to work, we need to tell stripe what we're buying. We're going set the line items. Our line items are an array, and it's an array of objects. The object that we want to buy is our product, so the name is product.name.
[9:51] We also want to set a description, rates, and then we're going to set the images. Stripe will let you set multiples, but we've only got one, so we're going to pass that in. Stripe will set the product.image.
[10:05] Then we're going to set the amount. This is where this is really important. We are using the amount set by our database. We have pulled the inventory out of our JSON and then looked that product up, and then we're setting the amount from there. The reason is because, again, somebody could edit our site and submit a lower price or a different...whatever, something that's out of stock, or something that's supposed to be limited access.
[10:35] This is us basically doing validation in the server side even though we're in a serverless function. This gives us the added security and confidence that what we're selling is what we intended to be selling at the price that it's intended to be sold at.
[10:50] Next, we're going to set our quantity. Remember, we have this validated quantity, so we are going to use that. At this point, you could, if you wanted, add another item for, say, flat-rate shipping, but at this point, we're not going to do that. We're going to assume that shipping is free.
[11:06] We will have a status code sent back of 200, we're OK. Then we're going to send back a JSON stringified object of first, the sessionID. That's going to come out of our session here. We'll use session.id. We also want to send back the publishableKey.
[11:28] The reason that we're sending back the publishableKey is because it's in an environment variable, Stripe publishable key, and we can't get at this in the HTML, but we need this in this Stripe purchase. The client side, rather than adding a build step to get environment variables out, we're just going to send it back from our function, because we're already calling it.
[11:52] That allows us to avoid a build step and also avoid publishing this publishableKey as a hard-coded value. It's not a problem to hard-code this value, but if we ever want to reuse this code or if roll our keys, we'd have to come back and remember to edit this, which would probably result in bugs. Sending around an environment variable instead just saves us some extra work and maintenance in the future.
[12:18] Next up, let's create our success page. I'm just going to copy⁄paste the index.html, rename it to success.html. Then I'm going to delete all of the template and script stuff, and we're going to change that to say, "Purchase successful," and we'll say something like, "Your test transaction went through. Technology is neat."
[12:53] We've got that. Let's update the title as well to let people where they are. We'll say, "Purchase successful." We can save that, and here, let's save this as well.
[13:04] Now that we've got our success page ready and our functions ready, we can actually start serving, give this a try. Let's run Netlify Dev, and we can click, "Buy Now." This should let us buy. Let's change the quantity. Let's buy two of these mugs.
[13:20] All right. Now we are on the Stripe checkout page. It's showing the name of our mug. It's also showing our credit. It's got the price calculated properly. There's two mugs, and that is at $15 apiece. It shows an image for us, and then it lets us set some detail. Let's say this is going to be email@example.com, and then it's going to be Someone Cool, you live at 123 Address Street, and that is in Portland, and let's see, that'll be at 97111, that's an Oregon zip code somewhere.
[14:02] Stripe, in testing mode, gives us a credit card number that we can use to check out. That's 4242 all the way across. Then we can set any future expiration date and any CVC that we want. We're going to keep this box checked for billing same as shipping. If we wanted to, we could change the shipping info so that we could set a different zip, but we're going to keep that the same, and then I'm going to pay.
[14:31] Once that payment succeeds, it redirects us to our success page. That is a fully-managed, serverless Stripe checkout flow using Netlify functions.