Skip to main content

Flask-Headless-Payments

Modern, headless payment integration for Flask APIs. Stripe subscriptions + webhooks + plan management in one line.

PyPI version Python 3.8+ License: MIT

✨ 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

  1. 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
  2. In Your App:

    app.config['STRIPE_WEBHOOK_SECRET'] = 'whsec_...'
    
  3. 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

  1. Always use HTTPS in production
  2. Keep your Stripe keys secure (use environment variables)
  3. Verify webhook signatures (handled automatically)
  4. Use JWT authentication with flask-headless-auth
  5. 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 data
  • Payment - Payment transaction records
  • WebhookEvent - Webhook event log
  • UsageRecord - Metered billing usage

Managers

  • SubscriptionManager - Subscription CRUD operations
  • CheckoutManager - Checkout & portal sessions
  • WebhookManager - Webhook event processing
  • PlanManager - 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.27

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for flask-headless-payments 0.2.27
File Size Uploaded
flask_headless_payments-0.2.27.tar.gz 51.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for flask-headless-payments 0.2.27
File Interpreter ABI Platform
flask_headless_payments-0.2.27-py3-none-any.whl Python 3 none any Details

Total release size: 109.7 kB

Release files / flask_headless_payments-0.2.27.tar.gz

Download URL flask_headless_payments-0.2.27.tar.gz
Size 51.0 kB
Tags Source
SHA-256 checksum
How to use checksums
0e9633dc700136ea225e692ea5e24a69c93b518ab6dd351d63d3b5863552c93c
BLAKE2b-256 checksum
How to use checksums
d1e054ebd559b09d0f2058fa78d86843300883cb223b6e9c6db81c190dc6ee41
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.27-py3-none-any.whl

Download URL flask_headless_payments-0.2.27-py3-none-any.whl
Size 58.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
64813242c892ccb6d3bc4ed75dd96b0b30cc3f2c8502a486503b1066b56652dd
BLAKE2b-256 checksum
How to use checksums
2b1458c6e0ab16fa67b1723d8a48de7a9e12050994b689e5a9e027b7e1a7f146
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.5

Release history Release notifications | RSS feed

0.2.29

2 release files

0.2.28

2 release files

This release

0.2.27 This release

2 release files

0.2.26

2 release files

0.2.25

2 release files

0.2.24

2 release files

0.2.23

2 release files

0.2.22

2 release files

0.2.11

2 release files

0.2.6

2 release files

0.2.4

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page