Building an API MVP from the Ground Up: Part 2 - Billing

Now that we have a working authentication system, it's time to start making some cash.

PS - remember to follow along with the scaffolding code if you want to see how these ideas are being implemented.

Stripe Setup

The first thing that we are going to want to do is set up Stripe as our payment gateway. We'll be using Stripe to process all of our payments, and automatically keep track of subscribed customers. Once you go through the sign up flow, you'll need to create a product to sell.

To do so, go to the Products tab, and create a new product. This not only creates the product, but also creates a separate pricing object. We'll need to use this pricing ID later when we are configuring our checkout endpoint, so make note of this now.

Checkout Webhook

Next, we'll want to configure our webhook endpoint so we can start making application updates after checkout. For those curious about what's going on, once Stripe processes a payment on our behalf, it will make a REST call to an endpoint that we have configured. At this point, the only change we need to make is to activate their API key.

This is what my webhook endpoint looks like:

router.post('/webhook', bodyParser.raw({type: 'application/json'}), async (request, response) => {
    const sig = request.headers['stripe-signature'];
  
    let event;
  
    try {
      // this is doing some extra magic to make sure the webhook event
      // is actually sent by Stripe
      event = stripe.webhooks.constructEvent(request.body, sig, endpointSecret);
    } catch (err) {
      console.warn(`Webhook Error: ${err.message}`);
      return response.status(400).send(`Webhook Error: ${err.message}`);
    }
  
    // Handle the checkout.session.completed event
    if (event.type === 'checkout.session.completed') {
      const session = event.data.object;
  
      // activate the user's API key and update billing info
      await BillingService.handleCheckoutSession(session);
    }
  
    // Return a response to acknowledge receipt of the event
    response.json({received: true});
});

One thing to note here is that the endpoint has to be encoded as raw input. So not only will you need the body-parser line here, but you'll need to make sure that is ignored in any preview body-parser middleware. What's happening in handleCheckoutSession is pretty minimal - you just need to make sure that the user's API key is activated.

Ok, now that we've got our webhook code up and running, we'll need to wire it together with a simple input form to start taking advantage of Stripe Checkout.

Stripe Checkout

If you are using my API scaffold, you should be able to run the server and hit the /landing endpoint. Just make sure to configure all the necessary environment variables in .env.

If you are going it on your own, you'll need to configure two separates pieces - one is a backend endpiont to create a Stripe session, and the other is the frontend form page which will redirect to the checkout page.

The session is Stripe's way of tracking what is being purchased, as well as where the user should be redirected after the purchase is completed. Once that is created, you'll need your frontend to pass that session ID to the checkout page. Here's a diagram of the flow that's taking place: Stripe Checkout diagram.

The important things to note are:

  1. The server is sending a session ID to the client
  2. The client is calling redirectToCheckout with the session ID

Your frontend code needs to use Stripe's client package to call that redirectToCheckout method. My example landing page is just an email input that hits my /billing/checkout endpoint, and then another view that calls redirectToCheckout.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <form method="POST" action="http://localhost:3000/billing/checkout" enctype="multipart/form-data">
        <input name="email" type="email" />
        <button type="submit">Submit</button>
    </form>
</body>
</html>

and

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Redirect Page</title>
    <script src="https://js.stripe.com/v3/"></script>
</head>
<body>
    <script>
    var stripe = Stripe('{{stripeKey}}');
    const sessionId = "{{sessionId}}"
    
    window.onload = function() {
        stripe.redirectToCheckout({
            sessionId: sessionId,
        });
    }
</script>
</body>
</html>

In my case, sessionId is being set by express when it renders the view. You could alternatively have one landing page that makes the AJAX request to your endpoint, and also redirects to the checkout.

Once all this is properly tied together, you should see the Stripe checkout page in all its glory.

Testing it Out

To test, we'll want to run through the flow end-to-end. This will entail submitting a new email, filling out the checkout info, and then verifying the user has been created.

First, we'll need to configure a simple endpoint to be redirected to on successful checkout. This can be event something as simple as:

router.get('/success', (req, res) => res.send('You did it!'))

We'll want to hook up our /success endpoint up to our /billing/checkout endpoint, so Stripe knows where to go.

Next, we'll want to install the Stripe CLI so we can start testing our payments. To do so, follow the instructions here. After that's set up, you can run a simple stripe listen to start receiving webhook events. Go ahead and add that secret to the .env file, since we'll need it in the webhook endpoint.

Finally, we should be able to go through our billing flow from start to finish. After entering in a test credit card number and submitting, we should be greeted with You did it!. More importantly, we should see a request to our /webhook endpoint with a corresponding checkout.session.completed.

Congratulations, you are now able to process payments!

As a final nice feature, you can enable email receipts for your users by going to https://dashboard.stripe.com/settings/billing/automatic, and ticking Email finalized invoices to customers. It's probably also a good idea to toggle Send email about upcoming renewals, since people like to know when they are about to be billed.


Next, we'll be creating an enticing landing page to draw future customers in. If you've got any questions or suggestions, send them my way at stephen.huffnagle@gmail.com. Thanks for reading!

Show Comments