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: .
The important things to note are:
- The server is sending a session ID to the client
- 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!