July 9, 2020

Accepting payments in Flask with Stripe

Introduction

In this article you’ll learn how to use Stripe Checkout to accept one time payments in Flask application. THe example will be a webshop, that has a single page for selling 5$ T-shirts.

Main page

Create a Flask route that serves the webshop page.

The page loads some JavaScript as well:

  • a Stripe JS
  • jQuery for AJAX call
  • some custom JavaScript
@app.route('/')
def webshop():
    return """<html>
                <head></head>
                <body>
                    <a href="#" id="checkout">Buy T-shirt for 5$</a>

                    <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
                    <script src="https://js.stripe.com/v3/"></script>
                    <script src="static/checkout.js"></script>

                </body>
            </html>"""

Starting a payment session

Now in the checkout.js you need to do a couple of things:

  • bind an onclick event to the button
  • instantiate stripe by providing your public Stripe key
  • retrieve a payment session id by calling /start_payment_session in your backend Flask
  • redirect to Stripe checkout providing a session_id created in the backend
$('#checkout').click(function() {
    var stripe = Stripe('pk_test_XXX');
    $.ajax({
        url: '/start_payment_session',
        success: function(data) {
            payment_session_id = data['session_id'];
            stripe.redirectToCheckout({
                sessionId: payment_session_id
            }).then(function (result) {
                console.log(result.error.message);
            });
        }
    });
});         

Put that file to static folder in your Flask project. But eventually you might want to serve static files with nginx directly without any Flask involvement.

Creating a session and redirecting to Stripe

Your frontend now calls a /start_payment_session when you a user hits a link on the page. This route should call stripe to request a session payment id. Then it return this to frontend.

First, install a stripe Python SDK with

pip install stripe

Then, create a new route in Flask:

import stripe
from flask import jsonify


@app.route('/start_payment_session')
def start_payment_session():
    stripe.api_key = 'sk_test_XXX' # put your Stripe secret key here
    session = stripe.checkout.Session.create(
        payment_method_types=['card'],
        line_items=[{
            'name': 'T-shirt',
            'description': 'Some cool T',
            'amount': 500,
            'currency': 'usd',
            'quantity': 1,
        }],
        success_url='https://123.ngrok.io/success?session_id={CHECKOUT_SESSION_ID}',
        cancel_url='https://123.ngrok.io',
    )
    return jsonify({'session_id': session["id"]})

Couple of gotchas:

  • amount should be integer
  • amount is in cents ($5 is 500)
  • amount can’t be less than 50ct
  • don’t hardcode stripe secret key - get it from environment variables or from a secrets store like HashiCorp Vault

When you create a payment session with Stripe you need to provide a success_url - this is where your users will be redirected once payments succeeds. You need to create a Flask route for that as well. Also, if the payment fails users will be redirected to cancel_url

I’m using ngrok to serve my Flask - as you can see from the urls - other methods to deploy Flask here.

Getting callback from Stripe

Now, when the payment succeeds your user is redirected to a success_url. But nothing prevents them to just go to this url directly and acquire the purchase. One way to solve that is to configure stripe servers to call a specific url in your backend notifying you that payment was successful.

First, you need to create a webhook in Stripe dashboard:

callback.

You provide a URL and en event type here. checkout.session.completed should be enough.

In this page you will find a Signing Secret - you need to use that in callback route code.

Then, you create a new route in Flask:

@app.route('/callback', methods=['POST'])
def callback():
    payload = request.get_data()
    sig_header = request.headers.get('Stripe-Signature')
    event = None
    endpoint_secret = 'whsec_XXX' # put Signing Secret here
    try:
        event = stripe.Webhook.construct_event(
        payload, sig_header, endpoint_secret
        )
    except (stripe.error.SignatureVerificationError,ValueError) as e:
        print(e)
        abort(400)

    # Handle the checkout.session.completed event
    if event['type'] == 'checkout.session.completed':
        session = event['data']['object']
        print(session)
        # save somewhere in database
        # that this session is completed
    return 'ok'

In this route you can save the details of the payment, so you can check them later once the use reached success_url to verify that payment is in a good order.

Finalizing purchase

The last step is when your user is redirected to success_url. Use this route to notify a user that payment was successful, send confirmation email and start shipments and so on.

@app.route('/success', methods=['get'])
def success():
    payment_session_id = request.args.get('session_id')
    # check in your DB that this particular 
    # session is completed
    return 'payment ok, shipping your T'

Demo

Here is a little demo on how it looks from a user perspective. In the bottom you also see Flask access logs:

gif

Further thoughts

This example showed how to do one time payments in Flask with Stripe. But it misses a couple of things:

  • no recurrent payments
  • if somethings goes wrong and user cannot reach success_url, there is no way for him to know whether the payment was ok

Accepting recurrent payment is almost identical, but require some extra step to set up. As for the second edge case - you may collect user email beforehand and notify her via it.

© Alexey Smirnov 2023