Bonaventure OgetoBy Bonaventure Ogeto|

Accept Payments with Paystack in Flask

To accept Paystack payments in Flask, create a route that initializes a transaction with the Paystack API using the requests library, redirects the customer to Paystack checkout, and verifies the payment in a callback route before granting value.

Project Setup

pip install flask requests python-dotenv
# app.py
import os
import uuid
import requests as http_requests
from flask import Flask, request, redirect, render_template, jsonify
from dotenv import load_dotenv

load_dotenv()

app = Flask(__name__)
PAYSTACK_SECRET = os.environ.get('PAYSTACK_SECRET_KEY')
BASE_URL = os.environ.get('BASE_URL', 'http://localhost:5000')

Initialize a Transaction

@app.route('/pay', methods=['POST'])
def initialize_payment():
    email = request.form.get('email')
    amount = float(request.form.get('amount', 5000))
    reference = 'order_' + uuid.uuid4().hex[:12]

    response = http_requests.post(
        'https://api.paystack.co/transaction/initialize',
        json={
            'email': email,
            'amount': int(amount * 100),
            'reference': reference,
            'callback_url': BASE_URL + '/payment/callback',
        },
        headers={
            'Authorization': 'Bearer ' + PAYSTACK_SECRET,
            'Content-Type': 'application/json',
        },
    )

    data = response.json()

    if data.get('status'):
        # TODO: Save order to database
        return redirect(data['data']['authorization_url'])

    return 'Initialization failed: ' + data.get('message', 'Unknown error'), 400

Handle the Callback

@app.route('/payment/callback')
def payment_callback():
    reference = request.args.get('reference') or request.args.get('trxref')

    if not reference:
        return 'Missing reference', 400

    response = http_requests.get(
        'https://api.paystack.co/transaction/verify/' + reference,
        headers={'Authorization': 'Bearer ' + PAYSTACK_SECRET},
    )

    data = response.json()

    if data.get('status') and data['data']['status'] == 'success':
        amount = data['data']['amount'] / 100
        currency = data['data']['currency']

        # TODO: Verify amount matches database, mark order as paid
        return 'Payment successful! ' + currency + ' ' + str(amount)

    return 'Payment not successful', 400

JSON API for SPA Frontends

If your frontend is a separate SPA (React, Vue, etc.), return JSON instead of redirecting:

@app.route('/api/pay', methods=['POST'])
def api_initialize():
    data = request.get_json()
    email = data.get('email')
    amount = data.get('amount', 5000)
    reference = 'order_' + uuid.uuid4().hex[:12]

    response = http_requests.post(
        'https://api.paystack.co/transaction/initialize',
        json={
            'email': email,
            'amount': int(amount * 100),
            'reference': reference,
        },
        headers={
            'Authorization': 'Bearer ' + PAYSTACK_SECRET,
            'Content-Type': 'application/json',
        },
    )

    result = response.json()

    if result.get('status'):
        return jsonify({
            'authorization_url': result['data']['authorization_url'],
            'access_code': result['data']['access_code'],
            'reference': result['data']['reference'],
        })

    return jsonify({'error': result.get('message')}), 400

@app.route('/api/verify')
def api_verify():
    reference = request.args.get('reference')
    if not reference:
        return jsonify({'error': 'Missing reference'}), 400

    response = http_requests.get(
        'https://api.paystack.co/transaction/verify/' + reference,
        headers={'Authorization': 'Bearer ' + PAYSTACK_SECRET},
    )

    data = response.json()

    if data.get('status') and data['data']['status'] == 'success':
        return jsonify({
            'success': True,
            'amount': data['data']['amount'] / 100,
            'currency': data['data']['currency'],
        })

    return jsonify({'success': False}), 400

Production Checklist

  1. Use gunicorn. Do not run Flask's development server in production. Use gunicorn behind nginx.
  2. HTTPS required. Paystack requires HTTPS for callback and webhook URLs.
  3. CORS. Install flask-cors if your frontend is on a different domain.
  4. Set up webhooks. See Handle Paystack Webhooks in Flask.
  5. Validate amounts. Check amounts against your database, not client input.
  6. Switch keys. Use sk_live_ in production.

Ship Payments Faster

Key Takeaways

  • Flask routes handle the Paystack API calls server-side. Use the requests library for HTTP calls.
  • Store your Paystack secret key in environment variables. Access it with os.environ or Flask config.
  • Amounts are in kobo (NGN), pesewas (GHS), or cents. Multiply by 100 before sending to Paystack.
  • Always verify the transaction server-side after the redirect callback.
  • Flask is minimal by design. You bring your own database, ORM, and templating as needed.
  • For production, use a WSGI server like gunicorn behind nginx with HTTPS.

Frequently Asked Questions

Is Flask too minimal for payment processing?
No. Flask is a web framework. The Paystack integration is HTTP calls with the requests library. Flask handles routing and request/response. What matters is your server-side verification logic, which is the same regardless of framework.
Do I need Flask-SQLAlchemy for payments?
You need some way to store orders and track payment status. Flask-SQLAlchemy is a popular choice. You could also use plain SQLite, MongoDB, or any other database.
Can I use Flask async with Paystack?
Flask 2.0+ supports async views with async def. Use httpx instead of requests for async HTTP calls. The Paystack API endpoints and logic are the same.

Ready to build real-world apps?

Join the McTaba Labs full-stack marathon (4 months full-time · 6 months part-time). Learn M-Pesa, USSD, and WhatsApp engineering while shipping 8 production apps.

Apply to the McTaba Marathon