Flask-Headless-Payments
Modern, headless payment integration for Flask APIs. Stripe subscriptions + webhooks + plan management in one line.
✨ Features
- ✅ Drop-in Integration - Add payments to any Flask app in minutes
- ✅ Stripe Checkout - Pre-built checkout sessions with trial support
- ✅ Subscription Management - Upgrade, downgrade, cancel subscriptions
- ✅ Customer Portal - Self-service billing management
- ✅ Webhook Handling - Automatic event processing with extensibility
- ✅ Plan-Based Access Control - Decorators to protect routes by plan
- ✅ Usage Tracking - Built-in metered billing support
- ✅ Headless Architecture - Perfect for SPAs, mobile apps, and APIs
- ✅ Works with flask-headless-auth - Seamless integration with authentication
🚀 Quick Start
Installation
pip install flask-headless-payments
Basic Setup (5 minutes)
from flask import Flask
from flask_headless_auth import AuthSvc
from flask_headless_payments import PaymentSvc
app = Flask(__name__)
# Configure
app.config['SECRET_KEY'] = 'your-secret-key'
app.config['JWT_SECRET_KEY'] = 'your-jwt-secret'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db'
# Stripe configuration
app.config['STRIPE_API_KEY'] = 'sk_test_...'
app.config['STRIPE_WEBHOOK_SECRET'] = 'whsec_...'
# Initialize auth (required)
auth = AuthSvc(app)
# Initialize payments
payments = PaymentSvc(
app,
plans={
'free': {
'name': 'Free',
'price_id': None,
'features': ['basic_features'],
'limits': {'api_calls': 100}
},
'pro': {
'name': 'Pro',
'price_id': 'price_1234567890', # From Stripe Dashboard
'features': ['basic_features', 'advanced_features'],
'limits': {'api_calls': 1000}
},
'enterprise': {
'name': 'Enterprise',
'price_id': 'price_0987654321',
'features': ['basic_features', 'advanced_features', 'premium_features'],
'limits': {'api_calls': 10000}
}
}
)
if __name__ == '__main__':
app.run()
Your API now has payment endpoints ready at /api/payments/* 🎉
📚 API Endpoints
All endpoints are automatically registered:
| Endpoint | Method | Auth | Description |
|---|---|---|---|
/api/payments/plans |
GET | No | Get all available plans |
/api/payments/subscription |
GET | Yes | Get current user's subscription |
/api/payments/checkout |
POST | Yes | Create checkout session |
/api/payments/portal |
POST | Yes | Create customer portal session |
/api/payments/cancel |
POST | Yes | Cancel subscription |
/api/payments/upgrade |
POST | Yes | Upgrade/downgrade plan |
/api/payments/webhook |
POST | No | Stripe webhook handler |
🎯 Usage Examples
1. Protect Routes by Plan
from flask_jwt_extended import jwt_required
from flask_headless_payments import requires_plan
@app.route('/api/premium-feature')
@jwt_required()
@requires_plan('pro', 'enterprise')
def premium_feature():
return {'message': 'Premium content'}
2. Require Any Active Subscription
from flask_headless_payments import requires_active_subscription
@app.route('/api/subscriber-only')
@jwt_required()
@requires_active_subscription
def subscriber_feature():
return {'message': 'Subscriber content'}
3. Check for Specific Features
from flask_headless_payments import requires_feature
@app.route('/api/advanced-feature')
@jwt_required()
@requires_feature('advanced_features')
def advanced_feature():
return {'message': 'Advanced feature'}
4. Track Usage (Metered Billing)
from flask_headless_payments import track_usage
@app.route('/api/convert-pdf')
@jwt_required()
@track_usage('pdf_conversion', quantity=1)
def convert_pdf():
return {'message': 'PDF converted'}
🔧 Configuration
Required Settings
# Stripe credentials
app.config['STRIPE_API_KEY'] = 'sk_test_...'
app.config['STRIPE_WEBHOOK_SECRET'] = 'whsec_...'
Optional Settings
# URL prefix for payment routes (default: '/api/payments')
app.config['PAYMENTSVC_URL_PREFIX'] = '/api/payments'
# Frontend URLs for redirects
app.config['PAYMENTSVC_SUCCESS_URL'] = 'http://localhost:3000/success'
app.config['PAYMENTSVC_CANCEL_URL'] = 'http://localhost:3000/cancel'
app.config['PAYMENTSVC_RETURN_URL'] = 'http://localhost:3000/account'
# Default trial period (days)
app.config['PAYMENTSVC_DEFAULT_TRIAL_DAYS'] = 14
# Default currency
app.config['PAYMENTSVC_DEFAULT_CURRENCY'] = 'usd'
# Feature flags
app.config['PAYMENTSVC_ENABLE_TRIALS'] = True
app.config['PAYMENTSVC_ENABLE_USAGE_TRACKING'] = True
# CORS origins
app.config['PAYMENTSVC_CORS_ORIGINS'] = ['http://localhost:3000']
💡 Frontend Integration
Create Checkout Session
// JavaScript/React example
async function subscribe(planName) {
const response = await fetch('/api/payments/checkout', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${accessToken}`
},
body: JSON.stringify({ plan: planName })
});
const { url } = await response.json();
window.location.href = url; // Redirect to Stripe Checkout
}
Open Customer Portal
async function manageBilling() {
const response = await fetch('/api/payments/portal', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`
}
});
const { url } = await response.json();
window.location.href = url; // Redirect to Stripe Portal
}
Get Current Subscription
async function getSubscription() {
const response = await fetch('/api/payments/subscription', {
headers: {
'Authorization': `Bearer ${accessToken}`
}
});
const { subscription } = await response.json();
console.log(subscription);
// {
// plan_name: 'pro',
// plan_status: 'active',
// is_subscribed: true,
// days_until_renewal: 25,
// ...
// }
}
🔌 Advanced: Custom Models
Extend Your User Model
from flask_sqlalchemy import SQLAlchemy
from flask_headless_payments import SubscriptionMixin
db = SQLAlchemy()
class User(db.Model, SubscriptionMixin):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(255), unique=True, nullable=False)
password_hash = db.Column(db.String(1024))
# SubscriptionMixin adds:
# - stripe_customer_id
# - stripe_subscription_id
# - plan_name, plan_status
# - current_period_start, current_period_end
# - trial_start, trial_end
# - is_subscribed(), has_plan(), etc.
# Initialize with custom model
payments = PaymentSvc(app, user_model=User, plans={...})
🪝 Webhooks
Setting Up Webhooks
-
In Stripe Dashboard:
- Go to Developers → Webhooks
- Add endpoint:
https://yourdomain.com/api/payments/webhook - Select events to listen to (or select all)
- Copy the webhook signing secret
-
In Your App:
app.config['STRIPE_WEBHOOK_SECRET'] = 'whsec_...'
-
Test Locally with Stripe CLI:
stripe listen --forward-to localhost:5000/api/payments/webhook
Custom Webhook Handlers
def custom_invoice_handler(event_data, db, user_model):
"""Custom handler for invoice.paid events."""
invoice_id = event_data['id']
customer_id = event_data['customer']
# Your custom logic
print(f"Invoice {invoice_id} paid by {customer_id}")
# Register custom handler
payments.register_webhook_handler('invoice.paid', custom_invoice_handler)
🎨 Plan Configuration
Define Your Plans
plans = {
'free': {
'name': 'Free',
'price_id': None, # Free tier has no Stripe price
'features': ['basic_pdf', 'compress'],
'limits': {
'pdf_conversions': 10,
'storage_mb': 100
}
},
'pro': {
'name': 'Pro',
'price_id': 'price_1234567890', # Monthly price from Stripe
'features': ['basic_pdf', 'compress', 'merge', 'split'],
'limits': {
'pdf_conversions': 100,
'storage_mb': 1000
}
},
'enterprise': {
'name': 'Enterprise',
'price_id': 'price_0987654321',
'features': ['basic_pdf', 'compress', 'merge', 'split', 'api_access', 'priority_support'],
'limits': {
'pdf_conversions': -1, # Unlimited
'storage_mb': -1
}
}
}
payments = PaymentSvc(app, plans=plans)
Check Features in Code
from flask import current_app
@app.route('/api/check-limit')
@jwt_required()
def check_limit():
user_id = get_jwt_identity()
user = User.query.get(user_id)
payment_svc = current_app.extensions['paymentsvc']
limit = payment_svc.plan_manager.get_plan_limit(user.plan_name, 'pdf_conversions')
return {'limit': limit}
🔒 Security Best Practices
- Always use HTTPS in production
- Keep your Stripe keys secure (use environment variables)
- Verify webhook signatures (handled automatically)
- Use JWT authentication with flask-headless-auth
- Validate plan changes server-side
🤝 Integration with flask-headless-auth
Flask-Headless-Payments is designed to work seamlessly with flask-headless-auth:
from flask_headless_auth import AuthSvc
from flask_headless_payments import PaymentSvc
# Initialize auth first
auth = AuthSvc(app)
# Payments automatically detects and uses auth's User model
payments = PaymentSvc(app, plans={...})
# Now protect routes with both auth AND plan requirements
@app.route('/api/premium')
@jwt_required()
@requires_plan('pro')
def premium():
return {'message': 'Premium feature'}
📦 What's Included
Models (with Mixins)
Customer- Stripe customer dataPayment- Payment transaction recordsWebhookEvent- Webhook event logUsageRecord- Metered billing usage
Managers
SubscriptionManager- Subscription CRUD operationsCheckoutManager- Checkout & portal sessionsWebhookManager- Webhook event processingPlanManager- Plan configuration & access control
Decorators
@requires_plan()- Require specific plan(s)@requires_active_subscription- Require any active subscription@requires_feature()- Require specific feature@track_usage()- Track usage for metered billing
🎓 Examples
Check out the examples/ directory for complete working examples:
- Basic SaaS - Simple subscription app
- With flask-headless-auth - Full auth + payments
- Metered Billing - Usage-based pricing
- Custom Webhooks - Advanced webhook handling
🐛 Troubleshooting
Issue: "User model does not support subscriptions"
Solution: Add SubscriptionMixin to your User model:
from flask_headless_payments import SubscriptionMixin
class User(db.Model, SubscriptionMixin):
# ... your fields
Issue: "STRIPE_WEBHOOK_SECRET not configured"
Solution: Set the webhook secret in your config:
app.config['STRIPE_WEBHOOK_SECRET'] = 'whsec_...'
Get it from: Stripe Dashboard → Developers → Webhooks
Issue: "No active subscription found"
Solution: Ensure user has completed checkout and webhook has been processed.
📄 License
MIT License - See LICENSE file for details.
🙏 Credits
Developed with ❤️ by Dhruv Agnihotri
Built with Flask, Stripe Python SDK, and Flask-SQLAlchemy.
Companion to flask-headless-auth.
Made for modern SaaS applications. Deploy with confidence. 💳
Release files for flask-headless-payments 0.2.19
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| flask_headless_payments-0.2.19.tar.gz | 51.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| flask_headless_payments-0.2.19-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 118.0 kB
Release files / flask_headless_payments-0.2.19.tar.gz
| Download URL | flask_headless_payments-0.2.19.tar.gz |
|---|---|
| Size | 51.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0eb03573c02eb6d1256f6e3485576caf520104493d912781fe6e809b6def8dad
|
|
BLAKE2b-256 checksum How to use checksums |
fb00b68dded0c27cadde07c090646f295869e4446d89560b1f2c9ff8da3d610d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.5
|
Release files / flask_headless_payments-0.2.19-py3-none-any.whl
| Download URL | flask_headless_payments-0.2.19-py3-none-any.whl |
|---|---|
| Size | 66.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
687ff7d15b19bed982ba4986cf384389d64e6d523363cff033a32eea8f634066
|
|
BLAKE2b-256 checksum How to use checksums |
96317ab26c5fd22b3a6ced2ff594359f20504cbaba51c02265ba91daa12929e9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.5
|