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:
.
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:
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.