Mastering Stripe Subscription Custom Checkout Flow: Server Side and Client Side Implementation
Development
Nikin Shan Faizal
|
January 23, 2024
|
6 min read
This article will explain how to use Stripe to create a custom subscription checkout flow. It will cover both client-side (React js) and server-side (Node js) components.
Stripe Subscriptions is a sophisticated service provided by Stripe, a popular online payment processing platform. It lets businesses to effortlessly manage and bill their consumers on a recurring basis, generally for subscription-based products or services. Let's break down some key concepts:
Click on the "Sign Up" or "Create Account" button.
Fill in the required information, including your email address and password.
Follow the on-screen instructions to complete the account setup process.
2. Navigate to the Dashboard:
Once your account is set up, log in to the Stripe Dashboard.
3. Activate Your Account:
Stripe may require you to activate your account by providing additional information, such as business details and banking information.
4. Enable Test Mode:
In the Dashboard, toggle to the "View test data" switch to enable Test Mode. This allows you to test transactions without processing real payments.
5. Set Up Products and Plans:
In the Dashboard, go to the "Products" section.
Products:
Click on "Add product."
Fill in product details, including name, description, and pricing information.
note : you should have to add a price to save the product
Save the product.
Pricing :
Click on "Products" and select the product you created.
Under "Pricing," click on "Add another price."
Configure price details, such as billing interval, pricing, and currency.
Save the price.
6. Integrate Stripe with Your Website or App:
Follow the integration guide based on your platform to seamlessly incorporate Stripe's payment functionality.
Stripe provides Publishable and Secret key after activation of the account which is then used to configure stripe in frontend and backend code will be later discussed below.
Plan and pricing model will be created in stripe account and will be displayed in client side with the help of priceId as shown below. After you create the prices, record the price IDs so you can use them in other steps. Price IDs look like this: price_1ORsMKDXxvvhRkJtLHm0O2yT.
Each price id represents different price plan, Its recommended to create priceId for free plan too with zero value so that you can provide free trial for 3 months or one month as per your convenience.
3. Create Customer in Stripe
Client Side
Stripe needs a customer for each subscription. In your application frontend, collect any necessary information from your users and pass it to the backend. Customers can be created in initial stage while registering to an account or can be created while choosing a plan based on your use case.
Server Side
On the server, create the Stripe customer object with the necessary information passed from the frontend.
Then the customer would be added into the stripe account as show below.
4. Create an Incomplete Subscription while Customer reaches Custom Checkout form.
By a choosing a plan from step-2, customer will be directed to a checkout form where they have to enter their credit or debit card details for purchasing a subscription. Stripe often provide prebuilt checkout form but here we are discussing about how to collect payment from a custom checkout form of our design or brand.
Client Side
By clicking on the choose plan option, send the priceId selected by the user to backend(server side)
Create the subscription with status incomplete using payment_behaviour=default_incomplete. Then return the client_secret from the subscription’s first payment intent to the front end to complete payment.
Set save_default_payment_method to on_subscription to save the payment method as the default for a subscription when a payment succeeds. Saving a default payment method increases the success rate of future subscription payments.
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
const stripe = require('stripe')('sk_test_###################');
app.post('/create-subscription', async (req, res) => {
const customerId = req.cookies['customer'];
const priceId = req.body.priceId;
try {
// Create the subscription. Note we're expanding the Subscription's
// latest invoice and that invoice's payment_intent
// so we can pass it to the front end to confirm the payment
const subscription = await stripe.subscriptions.create({
customer: customerId,
items: [{
price: priceId,
}],
payment_behavior: 'default_incomplete',
payment_settings: { save_default_payment_method: 'on_subscription' },
expand: ['latest_invoice.payment_intent'],
});
res.send({
subscriptionId: subscription.id,
clientSecret: subscription.latest_invoice.payment_intent.client_secret,
});
} catch (error) {
return res.status(400).send({ error: { message: error.message } });
}
});
Here we are sending back paymentIntent-client secret and subscriptionId to frontend for completing the payment, this client secret acts as a connection to the subscription which the user has selected. At this point subscription will be inactive and will be active only once the payment is confirmed from the client side.
5. Collect Payment Information - Client Side
For collecting payment informations like credit or debit card details, cvv, expiry are collected using Stripe Elements. They securely collect all payment informations and also you can customize Elements to match the look-and-feel of your application.
Add Payment Element to your Checkout Form
Load Stripe by using loadStripe from "@stripe/stripe-js" and wrap your whole checkout form with Element provided by "@stripe/react-stripe-js" as shown below.
import {Elements} from '@stripe/react-stripe-js';
import {loadStripe} from '@stripe/stripe-js';
// Make sure to call 'loadStripe' outside of a component's render to avoid
// recreating the 'Stripe' object on every render
const stripePromise = loadStripe( pk_test_###################)
export default function App(){
const options = {
// passing the client secret obtained from the server
clientSecret: '{{CLIENT_SECRET}}'
};
return (
<Elements stripe={stripePromise} options={options}>
<CheckoutForm/>
</Elements>
)}
Use PaymentElement provided by '@stripe/react-stripe-js' to display checkout form
note : If you want to customize each field like card number , Expiry or CVV, stripe provides:
CardElement : flexible single-line input that collects all necessary card details.
CardExpiryElement : Collects the card‘s expiration date.
To pass confidential information collected from users to stripe, the best practice is to access the elements and use it with stripe.ConfirmPayment as shown below. Here stripeHooke provides a reference to the Stripe instance passed to the Elements provider.
import {useStripe, useElements, PaymentElement} from '@stripe/react-stripe-js';
const CheckoutForm = () => {
const stripe = useStripe();
const elements = useElements () ;
const handleSubmit = async (event) => {
// We don't want to let default form submission happen here,
// which would refresh the page.
event.preventDefault () ;
if (!stripe || !elements) {
// Stripe.js hasn't yet loaded.
// Make sure to disable form submission until Stripe.js has loaded.
return;
}
const result = await stripe.confirmPayment ({
//'Elements' instance that was used to create the Payment Element
elements,
confirmParams: {
return_url: "https://example. com/order/123/complete",
},
});
stripe.confirmPayment confirms the payment and once the payment succeeds, it redirects users to the return URL
6. Listen for Webhooks
Webhook Configuration
Navigate to Webhooks:
In the Dashboard, find the "Developers" section in the left-hand sidebar.
Click on "Webhooks" under the "Developers" section.
Create a New Endpoint:
Click the "Add Endpoint" button.
In the "Endpoint URL" field, enter the URL of the server or service that will handle the incoming webhook events. This should be an HTTPS URL.
Choose the events you want to be notified about by selecting them from the "Events to send" section.
By clicking on Add endpoint button, Stripe would provide you with a webhook secret key which is used to connect webhook with your backend code
You will be able to add HTTP URL in local event listener, such that you will be able to test working of webhook in local.
Server Side
Add this middleware to handle incoming requests. If the original URL is "/webhook," it allows the request to pass through without parsing the JSON body. Otherwise, it uses the bodyParser middleware to parse the JSON body of the request.
// Use JSON parser for all non-webhook routes
app.use((req, res, next) => {
if (req.originalUrl === "/webhook"){
next();
} else {
bodyParser.json()(req, res, next);
}
})
The variable endpointSecret holds the secret key associated with your webhook endpoint. This key is used to verify the authenticity of incoming webhook events.
// Set your secret key. Remember to switch to your live secret key in production!
// See your keys here: https://dashboard.stripe.com/apikeys
const Stripe = require('stripe');
const stripe = Stripe('sk_test_**************');
// If you are testing your webhook locally with the Stripe CLI you
// can find the endpoint's secret by running stripe listen'
// Otherwise, find your endpoint's secret in your webhook settings in the Developer Dashboard
const endpointSecret = 'whsec_...';
app.post('/webhook', bodyParser.raw({type: 'application/json'}),(request, response) => {
const sig = request.headers['stripe-signature'];
let event;
// Verify webhook signature and extract the event.
// See https://stripe.com/docs/webhooks#verify-events for more information.
try {
event = stripe.webhooks.constructEvent(request.body, sig, endpointSecret);
} catch (err) {
return response.status(400).send('Webhook Error: ${err.message}');
}
return res.sendStatus(400);
})
This part of the code is responsible for verifying the Stripe webhook signature and extracting the event.
In our specific context, we have already initiated the subscription process on Stripe, but it remains partial or incomplete. To finalize and update the subscription details upon payment confirmation, we rely on webhook events provided by Stripe. In particular, we are interested in the “payment_intent.succeeded” event.
If the event called “payment_intent.succeeded” is triggered, then we could update our local database as well and this could complete your whole subscription payout journey.
// This example uses Express to receive webhooks
const express = require('express');
const app = express () ;
// Match the raw body to content type application/json
// If you are using Express v4 - v4.16 you need to use body-parser, not express
app.post ('/webhook', express.json({type: 'application/json'}), (request, response) => {
const event = request.body;
// Handle the event
switch(event.type) {
case 'payment_intent.succeeded':
const paymentIntent = event.data.object;
// Then define and call a method to handle the successful payment intent.
// handlePaymentIntentSucceeded(paymentIntent);
break;
case 'payment_method.attached':
const paymentMethod = event.data.object;
// Then define and call a method to handle the successful attachment of a PaymentMethod.
// handlePaymentMethodAttached(paymentMethod);
break;
// ... handle other event types
default:
console.log('Unhandled event type ${event.type}');
}
// Return a response to acknowledge receipt of the event
response.json({received: true}) ;
});
app.listen(8000, () => console.log('Running on port 8000'));
Conclusion
We discussed whole stripe subscription checkout journey from initiation to the completion using stripe webhook. The initialization of the Stripe library is a crucial step, and it's important to replace the test secret key with the live secret key in a production environment. This ensures that real transactions are securely processed, maintaining the integrity and confidentiality of sensitive information. You can also use the help of test clock in stripe so that you could analyze the working of your subscription by providing future date in the test clock, thus make your subscription integration flawless.