Payments
Complete Stripe-based payment system with subscriptions, one-time payments, and billing management
All payment routes are prefixed with /payments
and tagged as payments
in
the API documentation. Authentication is required for all endpoints.
Features
Stripe Integration
Full Stripe payment processing with secure checkout sessions
Subscription Management
Recurring subscriptions with automatic billing and cancellation
One-time Payments
Single purchase transactions for products and services
Billing Portal
Customer self-service portal for managing subscriptions
Webhook Handling
Automated processing of Stripe events and status updates
Product Management
Get Products
GET /payments/products/
Fetch available products filtered by type.
Query Parameters:
product_type
(required): Product type filter -one_time
orsubscription
Response:
[
{
"id": "prod_xxxxxx",
"name": "Premium Plan",
"description": "Full access to all features",
"metadata": {},
"prices": [
{
"id": "price_xxxxxx",
"unit_amount": 2999,
"currency": "usd",
"recurring": {
"interval": "month"
}
}
]
}
]
Returns 400 if Stripe API error occurs or invalid product_type provided.
Get Available Plans
GET /payments/get-plans
Retrieve all available subscription plans from the database.
Headers:
Authorization: Bearer {access_token}
Response:
[
{
"id": 1,
"name": "Basic Plan",
"stripe_price_id": "price_xxxxxx",
"price": 999,
"interval": "month",
"features": ["Feature 1", "Feature 2"],
"is_active": true
}
]
Subscription Management
Get User Subscription
GET /payments/get-user-subscription
Get the current user's subscription details and status.
Headers:
Authorization: Bearer {access_token}
Response:
{
"subscription": {
"id": "sub_xxxxxx",
"status": "active",
"current_period_start": "2024-01-01T00:00:00Z",
"current_period_end": "2024-02-01T00:00:00Z",
"cancel_at_period_end": false,
"plan": {
"id": "price_xxxxxx",
"nickname": "Premium Plan",
"amount": 2999,
"currency": "usd",
"interval": "month"
}
}
}
{
"subscription": null,
"message": "No active subscription"
}
Cancel Subscription
DELETE /payments/cancel-subscription
Cancel the user's active subscription at the end of the current billing period.
Headers:
Authorization: Bearer {access_token}
Response:
{
"message": "Subscription canceled successfully",
"subscription": {
"id": "sub_xxxxxx",
"status": "active",
"cancel_at_period_end": true,
"current_period_end": "2024-02-01T00:00:00Z"
}
}
Returns 404 if no active subscription found or 400 if Stripe API error occurs.
Checkout Sessions
Create Subscription Checkout
POST /payments/create-checkout-session
Create a Stripe checkout session for subscription payments.
Headers:
Authorization: Bearer {access_token}
Request Body:
{
"price_id": "price_xxxxxx"
}
Response:
{
"checkout_url": "https://checkout.stripe.com/pay/cs_xxxxxx",
"session_id": "cs_xxxxxx"
}
Session Creation: Creates Stripe checkout session with price ID
Returns 400 if user already has an active subscription. Cancel existing subscription first.
Create One-time Checkout
POST /payments/create-checkout-session-onetime
Create a Stripe checkout session for one-time payments.
Headers:
Authorization: Bearer {access_token}
Request Body:
{
"price_id": "price_xxxxxx",
"product_id": "prod_xxxxxx"
}
Response:
{
"checkout_url": "https://checkout.stripe.com/pay/cs_xxxxxx",
"session_id": "cs_xxxxxx"
}
One-time payments don't require subscription status checks and can be purchased multiple times.
Billing Management
Create Billing Portal Session
GET /payments/create-billing-portal-session
Create a Stripe billing portal session for subscription management.
Headers:
Authorization: Bearer {access_token}
Response:
{
"portal_url": "https://billing.stripe.com/session/xxxxxx"
}
Returns 400 if user has no customer_id (no active plan history).
The billing portal allows customers to:
- View billing history and invoices
- Update payment methods
- Cancel or modify subscriptions
- Download receipts
Webhook Handling
Stripe Webhook Endpoint
POST /payments/webhook
Handle Stripe webhook events for automated payment processing.
Headers:
stripe-signature: {webhook_signature}
Supported Events:
Event Type | Description |
---|---|
checkout.session.completed | Payment successful, activate subscription/product |
invoice.paid | Subscription renewal successful |
invoice.payment_failed | Subscription payment failed |
Signature Verification: Validates webhook signature using Stripe secret
Event Processing: Routes event to appropriate handler function
Returns 400 for invalid payload/signature or 500 for processing errors.
Security Features
Webhook Security
- Signature Verification: All webhooks verified using Stripe signature
- Event Validation: Validates event structure and required fields
- Idempotency: Prevents duplicate event processing
Payment Security
- Stripe Hosted: All payment processing handled by Stripe
- No Card Storage: No sensitive payment data stored locally
- PCI Compliance: Inherits Stripe's PCI DSS compliance
User Authorization
- JWT Required: All endpoints require valid access token
- User Scoping: Users can only access their own subscription data
- Role Validation: Ensures user permissions for payment operations
Error Handling
Common HTTP status codes returned by payment endpoints:
Status Code | Description |
---|---|
200 | Success |
400 | Bad request (validation error, Stripe error) |
401 | Unauthorized (invalid token) |
404 | Resource not found (no subscription) |
500 | Internal server error (webhook processing) |
Configuration
Payment system configuration through environment variables:
STRIPE_SECRET_KEY=sk_live_xxxxxx
STRIPE_PUBLISHABLE_KEY=pk_live_xxxxxx
WEBHOOK_SECRET=whsec_xxxxxx
FRONTEND_URL=https://yourdomain.com
Use live keys in production and test keys in development. Never expose secret keys in frontend code.
Integration Examples
Frontend Checkout Flow
import { useState } from 'react';
function CheckoutButton({ priceId }) {
const [loading, setLoading] = useState(false);
const handleCheckout = async () => {
setLoading(true);
try {
const response = await fetch('/payments/create-checkout-session', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('access_token')}`
},
body: JSON.stringify({ price_id: priceId })
});
const data = await response.json();
window.location.href = data.checkout_url;
} catch (error) {
console.error('Checkout error:', error);
} finally {
setLoading(false);
}
};
return (
<button onClick={handleCheckout} disabled={loading}>
{loading ? 'Processing...' : 'Subscribe Now'}
</button>
);
}
async function createSubscriptionCheckout(priceId) {
const response = await fetch('/payments/create-checkout-session', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('access_token')}`
},
body: JSON.stringify({ price_id: priceId })
});
const data = await response.json();
if (response.ok) {
window.location.href = data.checkout_url;
} else {
throw new Error(data.detail);
}
}
import requests
def create_checkout_session(price_id, access_token):
response = requests.post(
'/payments/create-checkout-session',
headers={
'Authorization': f'Bearer {access_token}',
'Content-Type': 'application/json'
},
json={'price_id': price_id}
)
if response.status_code == 200:
return response.json()['checkout_url']
else:
raise Exception(response.json()['detail'])
Subscription Management
import { useEffect, useState } from 'react';
function SubscriptionManager() {
const [subscription, setSubscription] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchSubscription();
}, []);
const fetchSubscription = async () => {
try {
const response = await fetch('/payments/get-user-subscription', {
headers: {
'Authorization': `Bearer ${localStorage.getItem('access_token')}`
}
});
const data = await response.json();
setSubscription(data.subscription);
} catch (error) {
console.error('Error fetching subscription:', error);
} finally {
setLoading(false);
}
};
const cancelSubscription = async () => {
try {
await fetch('/payments/cancel-subscription', {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${localStorage.getItem('access_token')}`
}
});
fetchSubscription(); // Refresh subscription data
} catch (error) {
console.error('Error canceling subscription:', error);
}
};
const openBillingPortal = async () => {
try {
const response = await fetch('/payments/create-billing-portal-session', {
headers: {
'Authorization': `Bearer ${localStorage.getItem('access_token')}`
}
});
const data = await response.json();
window.location.href = data.portal_url;
} catch (error) {
console.error('Error opening billing portal:', error);
}
};
if (loading) return <div>Loading...</div>;
return (
<div>
{subscription ? (
<div>
<h3>Current Subscription</h3>
<p>Plan: {subscription.plan.nickname}</p>
<p>Status: {subscription.status}</p>
<p>Next billing: {subscription.current_period_end}</p>
<button onClick={cancelSubscription}>
Cancel Subscription
</button>
<button onClick={openBillingPortal}>
Manage Billing
</button>
</div>
) : (
<div>
<p>No active subscription</p>
<button onClick={() => window.location.href = '/pricing'}>
View Plans
</button>
</div>
)}
</div>
);
}
class SubscriptionManager {
constructor(accessToken) {
this.accessToken = accessToken;
}
async getSubscription() {
const response = await fetch('/payments/get-user-subscription', {
headers: {
'Authorization': `Bearer ${this.accessToken}`
}
});
return response.json();
}
async cancelSubscription() {
const response = await fetch('/payments/cancel-subscription', {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${this.accessToken}`
}
});
return response.json();
}
async openBillingPortal() {
const response = await fetch('/payments/create-billing-portal-session', {
headers: {
'Authorization': `Bearer ${this.accessToken}`
}
});
const data = await response.json();
window.location.href = data.portal_url;
}
}
Best Practices
Frontend Integration
- Error Handling: Always handle payment errors gracefully
- Loading States: Show loading indicators during checkout
- Success Pages: Redirect to success page after payment
- Token Management: Ensure valid access tokens for API calls
Webhook Security
- Signature Verification: Always verify webhook signatures
- Idempotency: Handle duplicate webhook events
- Error Logging: Log webhook processing errors
- Retry Logic: Implement retry for failed webhook processing
User Experience
- Clear Pricing: Display pricing clearly before checkout
- Billing Transparency: Provide easy access to billing portal
- Cancellation Flow: Make subscription cancellation straightforward
- Payment History: Show transaction history and receipts
Security Considerations
- Environment Variables: Secure API keys in environment variables
- HTTPS Only: Always use HTTPS for payment endpoints
- Token Validation: Validate JWT tokens on all endpoints
- Rate Limiting: Implement rate limiting on payment endpoints