Modern, headless payment integration for Flask APIs. Stripe subscriptions + webhooks + plan management in one line.
Project description
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. 💳
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file flask_headless_payments-0.2.1.tar.gz.
File metadata
- Download URL: flask_headless_payments-0.2.1.tar.gz
- Upload date:
- Size: 44.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a30e10252ef65342e4e13d9ac0dcd4fdca1859bfdbe62fa49afa664f85026194
|
|
| MD5 |
40333a90a0f8eebee104dfb7641723fc
|
|
| BLAKE2b-256 |
d255c36676a8aaaeb9164fa963153d01d1660dcb8c6b97f2e59167593bc2dd4d
|
File details
Details for the file flask_headless_payments-0.2.1-py3-none-any.whl.
File metadata
- Download URL: flask_headless_payments-0.2.1-py3-none-any.whl
- Upload date:
- Size: 59.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7711570d3737bf77705b50604ca7c18e8f3806258eabef1b60659298500ac8ff
|
|
| MD5 |
c141d9e25f67b7ff02783772ca79eb78
|
|
| BLAKE2b-256 |
481677878f8670281e810cc1e709174cf890f88e1d49a86165e39e077de470cb
|