Creating a SaaS solution is fun, but creating a payment system can be a minefield. In this article, I demonstrate how to create a serverless SaaS on AWS. It uses the Stripe API and Vue-Checkout library to perform payments and manages users with Cognito, building on the architecture from part 1. Check out the live demo 💽.
Payments systems are a complex but necessary part of running a SaaS. Turning to a payment service side-steps most of the complexity and responsibility, but usually costs a percentage or flat fee for each transaction. Stripe offers payment system solution and charges 1.4%+20p for European cards and 2.9%+20p for for non-European cards per transaction. Stripe also has other useful features such as:
- customer management, including storing payment details
- manage products and prices
- create payments and subscriptions
- dashboards and insights
In this article, I will demonstrate integrating Stripe’s payment solution and how to store user details, make subscriptions and instant payments. In a previous article, I demonstrated how to create a SaaS user management application which allows users to manage their API key and monitor usage.
🚫 Users Only: Quickstart for creating a SaaS pt. 1 — User Management
Managing users and API keys is a necessary task for creating a Software as a Service (SaaS). In this article, I…
I want to limit the number of requests a user can make per month and scale that with an amount of money that would make the SaaS profitable. I have chosen to use credits instead of number of requests to open up the possibility of having multiple services which might be more costly to run. This also allows you to charge multiple credits.
I have divided plans up into three tiers which can be subscribed to or as pay-as-you-go:
- Free — 50 Credits per week
- Basic —1,000 Credits per month
- Power — 100,000 Credits per month
In a future article, I hope to investigate the cost of the architecture per user to determine a fair price for the service.
Stripe offers both the ability to make instant payments and subscriptions. Your products and prices must be stored on the Stripe service and when a purchase is successfully completed, Stripe will notify you using a web hook that you define. This web hook is called on subscription payment being successful too.
The architecture extends upon the previous User Manager App with another DynamoDB table to store products, a checkout function on the User API, a Stripe web hook API, and a State machine to manage users’ credit.
Let’s Build! 🔩
User credit is managed using a State Machine. The Cognito post-confirmation hook trigger a Lambda to create a user and begin an execution of the State Machine with the Free plan.
State Machines offer the ability to wait a specific amount of time and this does not cost. This allows us to periodically add credits to users on the Free plan. This is a requirement as this plan is not purchased and therefore cannot use the Stripe web hook as a trigger to add credits (see later).
Using a State Machines to construct flows means that extensions are easily added, such as sending email alerts. The cost of a standard State Machine is $0.025 per thousand state transitions. In the above State Machine, a free user would cost $0.025/1000 * 3 = $0.000075 per User per week — not including the Lambda fees. There is also a limit of 25,000 executions at a time.
Stripe allows you to create products which can have multiple prices. This is particularly useful for SaaS where you may offer the same product as an instant buy or recurring subscription.
I have defined the products in a JSON file and attempted to sync this details with both Stripe and a DynamoDB table using the following script:
The previous User Management system from part 1 has:
/generate-key — this regenerates the User’s API key
/user — returns details like API key, last Update time, and subscription Plan
This is extended with:
The Stripe-checkout frontend component requires a session-Id to initiate the payment sequence.
When the user selects a product on the frontend, a request is sent to the
/create-session endpoint where following function is run:
When creating a session, you can also attach
meta-data. This data is accessible on the resultant success object of the payment sent by Stripe to your web hook.
Users should be able to cancel their subscription to the SaaS at any time. In this example, selecting the Free plan in the frontend cancels all other subscriptions. This is achieved simply by referencing a customer id on a cancel call to Stipe.
Stripe sends transaction events to a web hook which you must create and define using the Stripe API:
Once you have defined the web hook, the secret is used in the following Lambda function which processes the successful charge events:
Again, I have added open CORS whilst developing. For security, these should been locked down so that only Stripe can call this API.
The test service API still uses the user’s API key for a query on a GSI of the User table to get the username and credit count. The credit field is decremented every time a request is made to the service.
The frontend is created using Vue JS with the AWS Amplify package and components — this is described in the pevious User Manager App from part 1. Here, we add the ability to make payments.
Stripe Checkout is a payment page hosted by Stripe that users are redirected to in order to complete their purchase. Vue-Stripe-Checkout is a plugin that means Stripe Checkout can be added in just a few lines:
<v-btn @click="$refs.sessionRef.redirectToCheckout()" v- show="!isLoadingSession">Pay</v-btn>
The pricing page for this site looks like this:
When the user clicks pay they are redirected to the Stripe page which looks like this:
This Architecture is a basis for creating your own SaaS. It would be great to extend this SaaS to cover:
- processing unsuccessful subscription payments
- connecting to social identity providers
Thanks for reading