Using the new Stripe Checkout in Gatsby + AWS Amplify: Payment confirmation (webhook)
Tags: gatsby, stripe, awsamplify, serverless
Published: 2020-06-19 17:21:13

This is the final post of the *Using the new Stripe Checkout in Gatsby + AWS Amplify* series. Previously, you completed the *server-side* setup by writing an AWS Lambda function that returned a new Stripe session for the checkout and you wrote the *client-side* `CheckoutButton` component that redirects your customer to Stripe Checkout. In this post, you'll implement a *webhook* that listens out for confirmation from Stripe that the customer's payment has been completed.

Take a look at the code from this blog post on [Github](https://github.com/beezeebly/gatsby-stripe-checkout-amplify-example).

## Step 1. Update your API to include a stripeHook
If you have been following along with these posts, you will already have AWS Amplify setup within your Gatsby project. Running `amplify status` from the terminal will show you the status of your AWS services. Your project should include a `stripeCheckout` function and a `stripeAPI`.

![Amplify table of services](https://dev-to-uploads.s3.amazonaws.com/i/m1tcs40z2zq4dtm1osas.png)
To update the `stripeAPI` run:
```bash
amplify api update
```
The Amplify CLI will walk you through a series of questions:
```bash
Please select from one of the below mentioned services:
```
You want to update your `REST` API.
```bash
Please select the REST API you would want to update
```
Select `stripeAPI`.
```bash
What would you like to do
```
You want to `add a new path` to the stripeAPI.
```bash
Provide a path (e.g., /book/{isbn}):
```
Enter `/webhook`.
```bash
Choose a Lambda source
```
Select `Create a new Lambda function`.
```bash
Provide a friendly name for your resource to be used as a label for this category in the project:
```
Enter `stripeHook`.
```bash
Provide the AWS Lambda function name:
```
Press Enter to select the default option of `stripeHook`.
```bash
Choose the function runtime that you want to use:
```
Select `NodeJS`
```bash
Choose the function template that you want to use:
```
Select `Serverless ExpressJS function (Integration with API Gateway)`
```bash
Do you want to access other resources created in this project from your Lambda function?
```
Enter `n`.
```bash
Do you want to invoke this function on a recurring schedule?
```
Enter `n`.
```bash
Do you want to edit the local lambda function now?
```
Enter `n`.
```bash
Restrict API access
```
Enter `n`.
```bash
Do you want to add another path?
```
Enter `n`.

If you run `amplify status` again you'll see that the new `stripeHook` function has been created and the `stripeAPI` has been updated.

![Updated Amplify status](https://dev-to-uploads.s3.amazonaws.com/i/urelieq09wikrjqjg7s6.png)

## Step 2. Install Stripe
Now the Amplify configuration has been completed, in your terminal navigate to the `amplify/backend/function/stripeHook/src` folder within your Gatsby project and install Stripe:
```bash
cd amplify/backend/function/stripeHook/src
npm install stripe
```
## Step 3. Add your Stripe secret key and endpoint secret
Open the app.js folder within the `/amplify/backend/functions/stripeHook/src/` directory. 

To the top of this file add constants for your Stripe secret key and endpoint secret:
```javascript
// Stripe parameters
const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY)
const endpointSecret = process.env.STRIPE_ENDPOINT_SECRET
```
You will add the STRIPE_SECRET_KEY and STRIPE_ENDPOINT_SECRET environment variables later via your AWS Lambda console.

## Step 4. Ensure Stripe has access to the raw request body
When Stripe calls your webhook it runs identity related security checks. It expects the *raw* request body for the signature to match the expected signature for the payload. By default, our Lambda automatically `stringify`s the request body so you need to update the `bodyParser` to include the raw body.

Comment out the call to `bodyParser.json()`:
```javascript
// app.use(bodyParser.json()) 
```
Replace this line with the following code in order to include the `rawBody` within the request params:
```javascript
// Add raw body to req params for Stripe signature check
app.use(
  bodyParser.json({
    verify: function(req, res, buf) {
      req.rawBody = buf.toString()
    },
  })
)
```
## Step 4. Write the stripeHook function
Delete the example `get`, `post`, `put` and `delete` code and replace it with the following empty `async` post function:
```javascript
app.post("/webhook", async function (req, res) {})
```
Within this function you want Stripe to authenticate the request and return the event:
```javascript
app.post("/webhook", async function (req, res) {
  // Check Stripe signature
  const sig = req.headers['stripe-signature']
  let event
  try {
    event = stripe.webhooks.constructEvent(req.rawBody, sig, endpointSecret)
  } catch (err) {
    return res.status(400).send(`Webhook Error: ${err.message}`)
  }
})
```
Next, you want to handle the event. Modify the POST function as follows:
```javascript
app.post("/webhook", async function (req, res) {
  // Check Stripe signature
  const sig = req.headers['stripe-signature']
  let event
  try {
    event = stripe.webhooks.constructEvent(req.rawBody, sig, endpointSecret)
  } catch (err) {
    return res.status(400).send(`Webhook Error: ${err.message}`)
  }

  switch (event.type) {
    case 'checkout.session.completed':
      console.log(`Payment checkout session for ${req.body.data.object.client_reference_id} was successful!`)

      break
    default:
      // Unexpected event type
      return res.status(400).end()
  }
})
```
You are checking for the `checkout.session.completed` event and you are accessing the unique `client_reference_id` you sent to Stripe when creating the original Stripe session via your `stripeCheckout` lambda function in Part 1. Having access to this allows you to match up payments to unique customer checkouts.

To complete the `stripeHook` function, Stripe requires that you acknowledge receipt of the event. Add `res.json({ received: true })` to the end of the function.

Your POST function should look like:
```javascript
app.post("/webhook", async function (req, res) {
  // Check Stripe signature
  const sig = req.headers['stripe-signature']
  let event
  try {
    event = stripe.webhooks.constructEvent(req.rawBody, sig, endpointSecret)
  } catch (err) {
    return res.status(400).send(`Webhook Error: ${err.message}`)
  }

  switch (event.type) {
    case 'checkout.session.completed':
      console.log(`Payment checkout session for ${req.body.data.object.client_reference_id} was successful!`)

      break
    default:
      // Unexpected event type
      return res.status(400).end()
  }

  // Return a response to acknowledge receipt of the event
  res.json({ received: true })
})
```
## Step 5. Publish your updates to your AWS account
From the terminal, run:

```shell
amplify push
```
It will show you the following table:
![Amplify push table](https://dev-to-uploads.s3.amazonaws.com/i/42jsjmnyig044zuyrlyz.png)
and it will ask:
```shell
Are you sure you want to continue? (Y/n)
```
Enter `y`.

The `amplify push` command takes a few minutes to complete.
## Step 6. Add the webhook to Stripe
In order for Stripe to send out the event you need to add the URL the webhook must call within the Stripe dashboard.

1. To find the URL of the `stripeHook` lambda you created, log into your AWS Console and navigate to the API Gateway page. From here, select the `stripeAPI`. From the left-hand navigation panel, select `Stages`. Finally, click on `dev` > `/webhook` > `POST`. The Invoke URL will be shown in a blue panel at the top of the main area. Copy the URL. It will look something like `https://4ssahjnp1d3.execute-api.eu-west-2.amazonaws.com/dev/webhook`
2. Log in to your Stripe account, click on the `Developers` link and [navigate to Webhooks](https://dashboard.stripe.com/test/webhooks).
3. Click on `+ Add endpoint` and paste in your endpoint URL.
4. Within `Events to send` search for and select the `checkout.session.completed` event. Click `Add endpoint`.
5. Reveal, copy and save the signing secret which you will add as an AWS Lambda environment variable in the next step. 

## Step 7. Add your Stripe secret key and endpoint secret as environment variables to your Lambda

1. Remain in your Stripe account and [navigate to your API keys](https://dashboard.stripe.com/account/apikeys). Reveal and copy the secret key token, it will look something like `sk_test_LE4KxozC6O8d3krb3FEjbBp00erufO2Bm`.

2. Log in to your AWS console and navigate to your Lambda page.

3. Find your `stripeHook-dev` function on the Lambda > Functions list (If you can't find it, check you are in the correct region within the AWS console)

4. On the stripeHook function page within the AWS console, scroll down to the Environment Variables area and click `Edit`.

5. Add `STRIPE_SECRET_KEY` and `STRIPE_ENDPOINT_SECRET` variables with the values copied from the Stripe dashboard earlier. Click `Save`.

![AWS Lambda environment variables](https://dev-to-uploads.s3.amazonaws.com/i/2dk1a0ion4m0eyb3m30k.png)

You've completed setting up the new Stripe Checkout in Gatsby using AWS Amplify.

## Extra info.
When testing the Stripe Checkout use the card number `4242 4242 4242 4242` with an expiry date in the future and any 3 digits for the CVC.

To see that the webhook has been sent after payment, log into Stripe, navigate to your webhook and scroll down to Webhook attempts. You can resend webhooks from here too.

To observe that the Lambda function fired on receipt of the webhook, log into AWS Console and navigate to the `stripeHook` lambda function. Click on the `monitoring` tab and click `View logs on Cloudwatch`. Scroll down to `Log events` and you'll find the `console.log` we wrote earlier that logged `Payment checkout session for UniqueString was successful!`.

To delete the resources pushed to AWS, run `amplify delete` from terminal.