Now let’s get started with creating an API to handle billing. It’s going to take a Stripe token and the number of notes the user wants to store.

Add a Billing Lambda

Start by installing the Stripe npm package.

Change indicator Run the following in the packages/functions/ directory of our project.

$ npm install stripe

Change indicator Create a new file in packages/functions/src/billing.ts with the following.

import Stripe from "stripe";
import { Resource } from "sst";
import { Util } from "@notes/core/util";
import { Billing } from "@notes/core/billing";

export const main = Util.handler(async (event) => {
  const { storage, source } = JSON.parse(event.body || "{}");
  const amount = Billing.compute(storage);
  const description = "Scratch charge";

  const stripe = new Stripe(
    // Load our secret key
    Resource.StripeSecretKey.value,
    { apiVersion: "2024-06-20" }
  );

  await stripe.charges.create({
    source,
    amount,
    description,
    currency: "usd",
  });

  return JSON.stringify({ status: true });
});

Most of this is fairly straightforward but let’s go over it quickly:

  • We get the storage and source from the request body. The storage variable is the number of notes the user would like to store in his account. And source is the Stripe token for the card that we are going to charge.

  • We are using a Billing.compute(storage) function, that we are going to add soon; to figure out how much to charge a user based on the number of notes that are going to be stored.

  • We create a new Stripe object using our Stripe Secret key. We are getting this from the secret that we configured in the previous chapter. At the time of writing, we are using apiVersion 2024-06-20 but you can check the Stripe docs for the latest version.

  • Finally, we use the stripe.charges.create() function to charge the user and respond to the request if everything went through successfully.

Add the Business Logic

Now let’s implement our Billing.compute function. This is primarily the business logic in our app.

Change indicator Create a packages/core/src/billing/index.ts and add the following.

export module Billing {
  export function compute(storage: number) {
    const rate = storage <= 10 ? 4 : storage <= 100 ? 2 : 1;
    return rate * storage * 100;
  }
}

A module is a good way to organize our business logic. You want to create modules for the various domains in your app. This follows some basic principles of Domain-driven design.

The compute function is basically saying that if a user wants to store 10 or fewer notes, we’ll charge them $4 per note. For 11 to 100 notes, we’ll charge $2 and any more than 100 is $1 per note. Since Stripe expects us to provide the amount in pennies (the currency’s smallest unit) we multiply the result by 100.

Clearly, our serverless infrastructure might be cheap but our service isn’t!

Add the Route

Let’s add a new route for our billing API.

Change indicator Add the following below the DELETE /notes/{id} route in infra/api.ts.

api.route("POST /billing", "packages/functions/src/billing.main");

Deploy Our Changes

If you switch over to your terminal, you will notice that your changes are being deployed.

You should see that the new API stack has been deployed.

+  Complete
   Api: https://5bv7x0iuga.execute-api.us-east-1.amazonaws.com
   ...

Test the Billing API

Now that we have our billing API all set up, let’s do a quick test in our local environment.

We’ll be using the same CLI from a few chapters ago.

Change indicator Run the following in your terminal.

$ npx aws-api-gateway-cli-test \
--username='admin@example.com' \
--password='Passw0rd!' \
--user-pool-id='<USER_POOL_ID>' \
--app-client-id='<USER_POOL_CLIENT_ID>' \
--cognito-region='<COGNITO_REGION>' \
--identity-pool-id='<IDENTITY_POOL_ID>' \
--invoke-url='<API_ENDPOINT>' \
--api-gateway-region='<API_REGION>' \
--path-template='/billing' \
--method='POST' \
--body='{"source":"tok_visa","storage":21}'

Here we are testing with a Stripe test token called tok_visa and with 21 as the number of notes we want to store. You can read more about the Stripe test cards and tokens in the Stripe API Docs here.

If the command is successful, the response will look similar to this.

Authenticating with User Pool
Getting temporary credentials
Making API request
{ status: 200, statusText: 'OK', data: { status: true } }

Commit the Changes

Change indicator Let’s commit and push our changes to GitHub.

$ git add .
$ git commit -m "Adding a billing API"
$ git push

Now that we have our new billing API ready. Let’s look at how to setup unit tests in serverless. We’ll be using that to ensure that our infrastructure and business logic has been configured correctly.