Billing System Documentation #
This document describes the billing and subscription management system in BonsAI, which integrates Stripe for payment processing and Clerk for free trial management.
Architecture Overview #
The billing system consists of several components working together:
- Stripe Integration: Handles payment processing, subscription management, and billing events
- Clerk Integration: Manages organizations and user authentication, also free trial limitations
- Billing API: Provides endpoints for retrieving billing information
- Cache Layer: Redis-based caching for performance optimization
- Webhook Handlers: Process real-time updates from Stripe and Clerk
Billing Flow Diagram #
graph TB
subgraph "User Actions"
A[User Creates Organization]
B[User Manages Subscription]
end
subgraph "Clerk"
C[Organization Created Event]
D[Organization Metadata]
end
subgraph "BonsAPI"
E[Clerk Webhook Handler]
F[Stripe Webhook Handler]
G[Billing Service]
H[Organization Repository]
I[Billing API Endpoints]
end
subgraph "Stripe"
J[Customer Created]
K[Subscription Events]
L[Customer Portal]
end
subgraph "Cache Layer"
M[Redis Cache]
end
subgraph "Frontend"
N[Billing Page]
O[Billing Alerts]
end
A --> C
C --> E
E --> J
E --> D
J --> K
K --> F
F --> M
B --> L
I --> G
G --> H
G --> M
G --> J
N --> I
O --> I
M --> I
style A fill:#e1f5fe
style B fill:#e1f5fe
style N fill:#e1f5fe
style O fill:#e1f5fe
style M fill:#fff3e0
style J fill:#e8f5e9
style K fill:#e8f5e9
style L fill:#e8f5e9
Core Components #
1. Stripe Client (libs/rust/bonsai-integration/src/billing/stripe/)
#
The Stripe client provides methods for:
- Creating customers
- Managing subscriptions
- Handling webhooks
- Retrieving billing information
2. Billing Service (apps/bonsapi/src/service/billing/)
#
The billing service layer handles:
- Reading billing information
- Managing trial periods
- Checking subscription status
- Calculating usage limits
3. Organization Management #
Organizations are the billable entities in BonsAI:
- Each organization has a
stripe_customer_idfield - Organizations are created via Clerk webhooks
- Stripe customers are automatically created for new organizations
Database Schema #
Organization Table Extension #
ALTER TABLE organization
ADD COLUMN stripe_customer_id VARCHAR(255);
CREATE INDEX organization_stripe_customer_id_idx
ON organization(stripe_customer_id);
Webhook Handling #
Stripe Webhooks #
Endpoint: POST /webhook/stripe
Handled Events:
customer.subscription.created- Handles cache invalidation and immediate chargingcustomer.subscription.deleted- Invalidates billing cachecustomer.subscription.resumed- Invalidates billing cachecustomer.subscription.updated- Invalidates billing cache
Security:
- Webhook signatures are verified using
STRIPE_WEBHOOK_SIGNING_SECRET - Uses
stripe::Webhook::construct_event()for cryptographic verification - Falls back to manual parsing for API version mismatches (with signature verification)
- Rejects any unsigned or tampered requests
Processing Flow:
- Verify webhook signature
- Extract customer ID from subscription event
- Look up organization by
stripe_customer_id - Invalidate billing cache for the organization
Clerk Webhooks #
Endpoint: POST /webhook/clerk
Handled Events:
organization.created: Creates Stripe customer and sets trial metadataorganization.updated: Upserts Stripe customer and invalidates cache
Trial System #
New organizations receive a 14-day free trial with:
- Trial Duration: 14 days from organization creation
- Trial Limits:
- Max entities: 2
- Max invoices per month: 100
Trial metadata is stored in Clerk organization metadata:
{
"trial_expiry_date": "2024-01-15",
"trial_max_entities": 2,
"trial_max_invoices_per_month": 100
}
Immediate Charging for Mid-Month Subscriptions #
When a subscription is created after the 1st of the month, BonsAI handles immediate charging to ensure customers pay for the current month’s usage:
How It Works #
- Billing Cycle Anchor: All subscriptions are anchored to the 1st of each month
- Immediate Charge: When subscribing after the 1st, an immediate invoice is created for the current month
- Metadata Requirements: Subscriptions must include:
charge_current_month: "true"- Enables immediate chargingprice_id: "price_xxx"- The price to charge for the current month
Processing Flow #
graph LR
A[Subscription Created] --> B{Is it the 1st?}
B -->|Yes| C[No Immediate Charge]
B -->|No| D{charge_current_month = true?}
D -->|No| E[No Immediate Charge]
D -->|Yes| F[Create Immediate Invoice]
F --> G[Charge Customer]
G --> H[Regular Billing Continues on 1st]
Implementation Details #
- Handled in
customer.subscription.createdwebhook - Creates a one-time invoice for the current month
- Charges immediately using the default payment method
- Regular subscription billing continues on the 1st of each month
Error Handling #
- If immediate charge fails, the invoice remains open for collection
- Customer receives standard Stripe payment retry emails
- Subscription remains active during retry period
Caching Strategy #
The billing system uses Redis caching to minimize API calls to Stripe:
Cache Keys #
- Pattern:
billing:plan:{organization_id} - TTL: 5 minutes
- Structure:
{
"subscription_status": "active",
"current_plan": "starter",
"billing_cycle_end": "2024-02-01T00:00:00Z",
"max_invoices_per_month": 100,
"max_entities": 10
}
Cache Invalidation #
Cache is invalidated when:
- Stripe subscription events are received
- Organization is updated via Clerk
- After 5 minutes
Frontend Integration #
The webapp displays billing information and alerts:
Billing Alert Component #
Shows warnings when:
- Free trial is expiring (< 3 days)
- Subscription is inactive
- Usage limits are approached
Billing Page #
Located at /settings/organization/billing:
- Shows current plan details
- Provides link to Stripe customer portal
- Displays usage statistics
- Shows trial information
State Management #
Billing state is managed via:
useBillinghook for fetching billing datauseOrganizationhook for organization details- Automatic refetch on focus and authentication changes
Environment Variables #
Required environment variables:
# Stripe Configuration
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SIGNING_SECRET=whsec_...
# Clerk Configuration
CLERK_SECRET_KEY=sk_test_...
CLERK_WEBHOOK_SIGNING_SECRET=whsec_...
# API Configuration
BONSAPI_EXTERNAL_HOST=https://api.bonsai.com
Error Handling #
Common Error Scenarios #
-
No Stripe Customer: Organization exists but no Stripe customer
- Automatically creates customer on first billing request
- Logs warning for monitoring
-
Cache Miss: Billing information not in cache
- Falls back to Stripe API
- Repopulates cache with fresh data
-
Webhook Failures: Failed to process webhook
- Logs error with details
- Returns 200 OK to prevent retries (for non-critical events)
- Critical events may return error for retry
Monitoring and Debugging #
Key Log Points #
- Webhook receipt and processing
- Cache hits/misses
- Stripe API calls
- Customer creation events
- Subscription changes
Metrics to Monitor #
- Webhook processing time
- Cache hit rate
- Stripe API latency
- Failed webhook attempts
- Trial conversion rate
Development and Testing #
Local Development #
- Spin up ngrok
- Set up Stripe test account
- Configure webhook endpoint using ngrok’s host
- Use update BONSAPI_EXTERNAL_HOST and STRIPE_WEBHOOK_SIGNING_SECRET in /.env
Security Considerations #
- Webhook Verification: All webhooks are cryptographically verified
- Authorization: Billing endpoints require valid JWT tokens
- Rate Limiting: Consider implementing rate limits for billing endpoints
- Audit Logging: All billing changes are logged for compliance
- PCI Compliance: No payment information is stored locally
Future Enhancements #
- Usage Tracking: Real-time usage monitoring
- Billing Alerts: Email notifications for billing events
- Multiple Plans: Support for different subscription tiers
- Metered Billing: Usage-based pricing options
- Invoice Management: Direct invoice access and management