Accept Payments with Paystack in FastAPI
To accept Paystack payments in FastAPI, create async endpoints that call the Paystack API using httpx. Use Pydantic models for input validation. Initialize a transaction, return the authorization URL to your frontend, and verify the payment in a callback endpoint.
Project Setup
pip install fastapi uvicorn httpx python-decouple
Create a .env file:
PAYSTACK_SECRET_KEY=sk_test_xxxxxxxxxxxxxxxxxxxxx
BASE_URL=http://localhost:8000
# main.py
from fastapi import FastAPI
from decouple import config
app = FastAPI()
PAYSTACK_SECRET = config('PAYSTACK_SECRET_KEY')
BASE_URL = config('BASE_URL')
Pydantic Models
# schemas.py
from pydantic import BaseModel, EmailStr
class PaymentRequest(BaseModel):
email: EmailStr
amount: float
class PaymentResponse(BaseModel):
authorization_url: str
access_code: str
reference: str
class VerifyResponse(BaseModel):
success: bool
amount: float = 0
currency: str = ''
email: str = ''
Initialize a Transaction
# main.py
import httpx
import uuid
@app.post('/api/pay', response_model=PaymentResponse)
async def initialize_payment(payment: PaymentRequest):
reference = 'order_' + uuid.uuid4().hex[:12]
async with httpx.AsyncClient() as client:
response = await client.post(
'https://api.paystack.co/transaction/initialize',
json={
'email': payment.email,
'amount': int(payment.amount * 100),
'reference': reference,
'callback_url': BASE_URL + '/api/payment/callback',
},
headers={
'Authorization': 'Bearer ' + PAYSTACK_SECRET,
'Content-Type': 'application/json',
},
)
data = response.json()
if not data.get('status'):
raise HTTPException(status_code=400, detail=data.get('message', 'Failed'))
# TODO: Save order to database
return PaymentResponse(
authorization_url=data['data']['authorization_url'],
access_code=data['data']['access_code'],
reference=data['data']['reference'],
)
Handle the Callback
# main.py
from fastapi import Query
from fastapi.responses import RedirectResponse, HTMLResponse
@app.get('/api/payment/callback')
async def payment_callback(reference: str = Query(None), trxref: str = Query(None)):
ref = reference or trxref
if not ref:
return HTMLResponse('Missing reference', status_code=400)
async with httpx.AsyncClient() as client:
response = await client.get(
'https://api.paystack.co/transaction/verify/' + ref,
headers={'Authorization': 'Bearer ' + PAYSTACK_SECRET},
)
data = response.json()
if data.get('status') and data['data']['status'] == 'success':
amount = data['data']['amount'] / 100
# TODO: Verify amount against database, mark order as paid
return HTMLResponse(
'Payment successful! Amount: ' + str(amount) + ' ' + data['data']['currency']
)
return HTMLResponse('Payment not successful', status_code=400)
Verify Endpoint for Inline Popup
@app.get('/api/verify', response_model=VerifyResponse)
async def verify_payment(reference: str):
async with httpx.AsyncClient() as client:
response = await client.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 VerifyResponse(
success=True,
amount=data['data']['amount'] / 100,
currency=data['data']['currency'],
email=data['data']['customer']['email'],
)
return VerifyResponse(success=False)
Production Checklist
- Use httpx connection pooling. Create a single AsyncClient instance and reuse it across requests instead of creating a new one per request.
- Add CORS middleware. Use FastAPI's CORSMiddleware to allow requests from your frontend domain.
- Set up webhooks. See Handle Paystack Webhooks in FastAPI.
- Use HTTPS. Required for callback and webhook URLs in production.
- Validate amounts. Look up the price from your database. Never trust client-submitted amounts.
Key Takeaways
- ✓FastAPI is async by default. Use httpx for non-blocking HTTP calls to the Paystack API.
- ✓Pydantic models validate payment input automatically. FastAPI returns 422 for invalid data.
- ✓Store your Paystack secret key in environment variables. Use pydantic-settings or python-decouple to load them.
- ✓Amounts are in kobo (NGN), pesewas (GHS), or cents (ZAR/KES/USD). Validate and convert on the server.
- ✓Always verify the transaction server-side. The redirect callback alone is not proof of payment.
- ✓FastAPI auto-generates OpenAPI docs for your payment endpoints, which helps frontend developers integrate.
Frequently Asked Questions
- Why use httpx instead of requests?
- The requests library is synchronous. FastAPI is async. Using requests blocks the event loop. httpx provides an async API (AsyncClient) that works naturally with FastAPI async endpoints.
- Can I use the requests library with FastAPI?
- Yes, but only in synchronous (non-async) endpoints using def instead of async def. FastAPI runs synchronous endpoints in a thread pool. For best performance, use httpx with async def.
- Does FastAPI auto-generate docs for my payment API?
- Yes. Visit /docs for Swagger UI or /redoc for ReDoc. FastAPI generates these from your Pydantic models and type annotations. This helps your frontend team understand the payment API without separate documentation.
- How do I add authentication to payment endpoints?
- Use FastAPI Depends with an authentication dependency. For JWT auth, decode the token in a dependency and inject the current user into your endpoint. Only authenticated users should be able to initialize payments.
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