Integrate Stripe With Your Next Big Idea

Integrate Stripe With Your Next Big Idea

ยท

5 min read

If you're here, I'm assuming that you already know some programming language for Backend and API stuff. Now you want to integrate stripe and make some paid features or in Layman's terms, "putting some API endpoints behind the paywall which only paid user can access". By the end of this article, You'll be able to start that cool projects with free & paid users modals fearlessly. So Let's get started!

Understanding Stripe's Payment Flow

  1. Stripe's flow starts with initiating a checkout session i.e. customer asking to pay and you send them a means to pay specific amount.
  2. Customer pays the amount successfully or cancel the payment.
  3. If payment is successful, an event will trigger and stripe will notify your backend. Now you can take actions as you wish i.e. giving paid feature access to user.

And that's it!

stripe-payment-flow.png

Start Editing Your Source Code

First, the Language I'm going to use for backend will be Javascript (NodeJs RE). But you can use Rust, Ruby or Python etc, whatever suits you.
Second, I'm Not going to copy-paste whole source code here (Because remember I'm assuming you already have simple backend with Authentication and your precious logic ready)
Last but not least, Register to stripe.com, get your API secret and save it as STRIPE_PRIVATE_KEY in your .env file.

Update User Schema

  • stripeId: Register your every new user to stripe so if they make any payment, you can distinguish them.
  • isPro: Identifier that will decide if user has paid or not!
const UserSchema = new mongoose.Schema({
    stripeId: {
        type: String,
        required: true
    },
    isPro: {
        type: Boolean,
        default: false
    },
    // Usual stuff like username, email & password etc...   
});

Update Signup Controller

Before moving forward, do npm i stripe --save

While Register User into your Database, also register him/her to Stripe.

const stripe = require('stripe')(process.env.STRIPE_PRIVATE_KEY);

// @route POST /api/signup
// @access Public
exports.register = async (req, res) => {
        const { username, email, password } = req.body;
        let user = await User.findOne({email});
        if (user)
            res.status(400).json({ message: "Email already registered" })
        // register user to stripe
        const stripeUser = await stripe.customers.create({
            email
        });
        const newUser = new User({
            stripeId: stripeUser.id,
            username,
            email,
            password
        });
        await newUser.save();
        res.status(201).json({ message: "User created" });
}

NOTE: Do not trust user and always validate all user inputs, I'm lazy so am not doing it here!

Initiate The Payment [create endpoint]

Let's create an authenticated endpoint which lets users to initiate payment if they're interested in becoming a PRO member!

const stripe = require('stripe')(process.env.STRIPE_PRIVATE_KEY);

// @route POST /api/purchase
// @access Protected
exports.purchase = async (req, res) => {
        if (req.user.isPro)
            return res.status(400).json({ message: "you're already a pro member" });
        const session = await stripe.checkout.sessions.create({
            customer: req.user.stripeId,
            payment_method_types: ['card'],
            line_items: [{
                name: 'pro membership',
                description: 'with this membership, You will be able to use all those paid feature of this app!',
                amount: 1000 * 100,   //  Rs 1000
                currency: 'inr',
                quantity: 1,
            }],
            success_url: `${process.env.HOST}/success`,
            cancel_url: `${process.env.HOST}/cancel`,
        });
        res.json({
            url: session.url
        });
}

When your frontend receives the URL as response, You should redirect them to the url using javascript. Or just redirect from server-side using 303 See Other redirect.

Stripe Webhook manager [event handler]

Using previous section's URL, user will pay the specified amount. Now How your backend will know if payment was actually completed or not? That's where stripe webhook comes into play.
We'll setup an endpoint (webhook) that will listen to stripe. If any payment related activity happens stripe will send a event to this webhook in form of POST request.

Now there are so many event but we are only interested in payment completion.
What we want is, when payment happens by our user, stripe will inform us and we'll make that user a PRO member.

Before using below code, do sudo apt get install stripe & stripe login then finally, stripe listen --forward-to http://localhost:1337/api/webhook when you run this last command, it'll give you a webhook secret, save it as STRIPE_WEBHOOK_SECRET in your .env file and let this command running. Also make sure you're running development server in another tab!

// @route POST /api/webhook
// @access public
exports.webhook = (req, res) => {
        const sig = req.headers['stripe-signature'];
        const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET;
        let event = req.body;
        event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret);
        // Handle the event
        switch (event.type) {
            case 'charge.succeeded':
                const { customer } = event.data.object
                console.log('๐Ÿ’ฐ payment succeeded');
                //  now make user a pro member
                const user = await getUserByStripeId(customer);
                user.isPro = true;
                await user.save();
                //  send congratulations email to user [optional]
                sendProMemberConfirmationMail(user.email);
                break;
            default:
                console.log(`Unhandled event type ${event.type}`);
                return res.status(400).send(`Unhandled event type ${event.type}`);
        }

        // Return a response to acknowledge receipt of the event
        res.json({received: true});
}

NOTE: This above webhook doesn't like request body in json format, instead it like raw buffer as request body. So to do that, let's make a little change to our main file index.js of the server!

const express = require("express");
const bodyParser = require("body-parser");

const app = express();

app.use('/api/webhook', bodyParser.raw({type: "*/*"}));
// continue file

Now everything from stripe is in place. All that's left is protecting your precious features from non-pro members & that we can do using middleware.

//  Middleware: check if user is pro member
function isPro(req, res, next) {
    if (req.user.isPro) {
        next();
    }  else {
        res.status(403).json({
            message: 'User is not PRO member'
        });
    }
}

And This is sample of protected route that's using above middleware.

const router = require("express").Router();
const { isAdmin, isPro } = require('../middlewares/auth');
const { getProjectById, downloadProject } = require('../controllers/project');

// Get project data 
router.get('/:id', getProjectById);

// Download project if you're a PRO member
router.get('/:id/download', isPro, downloadProject);

module.exports = router;

Finally, All things are set. Just re-run the server and check if something is broken ๐Ÿฅฒ (i hope not)


Bonus

  • Code Snippets shown above are taken from one of my projects, it's full source code is available on Github and Project is hosted on Heroku ๐Ÿ˜Š

  • I've created 3 projects using Stripe while learning and later tweeted about it, they're also deployed and ofc code is preset at Github. I hope it'll help as reference or just for code review maybe.

Conclusion

In this article, we discussed payment flow of Stripe and then implemented Stripe in our Backend. If you followed along & there's still any doubts or error that you can't figure out, feel free to contact me at karansh491 thru twitter.

PS: I had lots of fun while learning and playing around with Stripe & I hope you'll too!

cya โœŒ๐Ÿป

ย