Implement a payment flow with Stripe and webhooks

This post is a part of a multistep tutorial about building a full-stack epic games store clone with Next.js, Drizzle and NeonDB serverless database. To get to the full tutorial with the full code of the app click the button below.

Implement a payment flow with Stripe and webhooks

This post is a part of a multistep tutorial about building a full-stack epic games store clone with Next.js, Drizzle and NeonDB serverless database. To get to the full tutorial with the full code of the app, click the button below.

In this post, we will look into the inner workings of syncing your stripe payments with your database. While Stripe will hold all the data for your products, prices and discounts. You will need to store data from stripe into your database.

The primary way to sync Stripe and your backend is to make use of the power of webhooks. Stripe allows us to create webhooks that listen to the events we want to listen to (e.g.: checkout.successful : meaning when a user completes a purchase).

Creating a webhook

To create a webhook with stripe, we simply navigate to the developers page and select the webhooks tab.

Then we need to create a new endpoint and choose the events we like to listen to from the list of events.

Once you create an event, stripe will ask you to provide an endpoint URL to send the webhook to. If you choose to test the webhooks locally, you can use tools like Ngrok to do that.

Creating a webhook with stripe dashboard

The final step is to copy the webhook secret to your .env file so that we use it to create our endpoint.

Creating the webhook endpoint

Listening to the webhooks is as simple as creating an ending that accepts a post request, we will also need the stripe package to make the processing of the event easier. In the Next.js, the code is as follows :

export async function POST(req: Request) {
  const payload = await req.text()
  const signature = req.headers.get('stripe-signature')
  return NextResponse.json({ received: true })
}

The first step is to process the webhook event using the webhook secret

    const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!

    const event = stripe.webhooks.constructEvent(
      payload,
      signature!,
      webhookSecret,
    )

Finally, we switch the event type and handle each event separately

    switch (event.type) {
      case 'checkout.session.completed':
        // handle payment_intent.succeded
        if (event.type === 'checkout.session.completed') {
        // Handle the event here
        }
        break
      default:
        // other events that we don't handle
        break
    }

The final endpoint code I use the data from the event to update from record in my database taking to register a new order.

// Stripe will give you a webhook secret when setting up webhooks.
// well get this later and add it to the .env.local file when testing
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!

export async function POST(req: Request) {
  const payload = await req.text()
  const signature = req.headers.get('stripe-signature')

  try {
    const event = stripe.webhooks.constructEvent(
      payload,
      signature!,
      webhookSecret,
    )
    switch (event.type) {
      case 'checkout.session.completed':
        // handle payment_intent.succeded
        if (event.type === 'checkout.session.completed') {
          try {
            const { client_reference_id } = event.data.object
            const { stripeId, authId } = JSON.parse(client_reference_id)

            if (stripeId && authId) {
              const game = await db.query.games.findFirst({
                where: eq(games.stripeId, stripeId),
                columns: {
                  id: true,
                },
              })
              const user = await db.query.users.findFirst({
                where: eq(users.authId, authId),
                columns: {
                  id: true,
                },
              })
              await db.insert(libraryItems).values({
                gameId: game?.id,
                userId: user?.id,
              })
              return NextResponse.json({ message: 'Registered successfully ' })
            }
          } catch (e) {
            console.log(e)
          }
        }
        break
      default:
        // other events that we don't handle
        break
    }
  } catch (err) {
    if (err instanceof Error) {
      console.error(err.message)
      return NextResponse.json({ message: err.message }, { status: 400 })
    }
  }
  return NextResponse.json({ received: true })
}

In there you have it, I this post I explained how I used stripe webhooks to load orders into my database