Billing & Subscriptions
The OEC.SH platform offers flexible subscription plans with transparent pricing and comprehensive billing management. Every organization has a subscription that determines available features, resource quotas, and support levels.
Overview
The billing system manages:
- Subscription Plans: Five tiers from Free to Enterprise with different features and limits
- Billing Intervals: Monthly or annual billing (20% discount on annual)
- Stripe Integration: PCI-compliant payment processing via Stripe Checkout and Customer Portal
- Plan Changes: Instant upgrades, scheduled downgrades with validation
- Subscription Lifecycle: Trial periods, pausing, resuming, and cancellation
- Billable Items: Additional charges for BYOS servers, platform environments, and add-ons
- Usage Tracking: Real-time monitoring of resource consumption
- Invoices: Automated invoice generation with PDF downloads
All billing operations require appropriate permissions (org.billing.view or org.billing.manage).
Subscription Plans
Plan Overview
OEC.SH offers five subscription plans designed for different organization sizes and needs:
| Plan | Monthly Price | Annual Price | Annual Savings |
|---|---|---|---|
| Free | $0 | $0 | - |
| Starter | $29 | $278.40 | 20% ($69.60) |
| Professional | $79 | $758.40 | 20% ($189.60) |
| Business | $199 | $1,910.40 | 20% ($477.60) |
| Enterprise | Custom | Custom | Negotiable |
Note: All annual plans include a 20% discount compared to monthly billing.
Free Plan
Perfect for individuals and small teams getting started with Odoo deployment.
Pricing: $0/month (forever free)
Included Resources:
- 1 BYOS server included
- 0 platform environments included
- Maximum 2 projects
- Maximum 5 environments
- Maximum 2 team members
Per-Environment Limits:
- CPU: 2.0 cores max
- RAM: 4,096 MB (4 GB) max
- Disk: 40 GB max
Features:
- Daily backups (7-day retention)
- Basic monitoring (Netdata)
- Community support
- Standard backup frequency
- No cloning/staging environments
- No custom domains
- No SSO authentication
- No SLA guarantee
Best For: Personal projects, testing, proof-of-concept deployments
Starter Plan
Ideal for small businesses running a few Odoo instances.
Pricing:
- Monthly: $29/month
- Annual: 23.20/month equivalent)
Included Resources:
- 1 BYOS server included
- 0 platform environments included
- Maximum 10 projects
- Maximum 10 environments
- Maximum 5 team members
Per-Environment Limits:
- CPU: 4.0 cores max
- RAM: 8,192 MB (8 GB) max
- Disk: 80 GB max
Features:
- Daily backups (30-day retention)
- Full monitoring (Netdata)
- Email support (48-hour response)
- Clone/staging environments enabled
- Custom domains and SSL
- No SSO authentication
- No SLA guarantee
Best For: Small businesses, development agencies with 3-5 clients
Professional Plan
Designed for growing teams managing multiple production Odoo deployments.
Pricing:
- Monthly: $79/month
- Annual: 63.20/month equivalent)
Included Resources:
- 3 BYOS servers included
- 1 platform environment included (Small size)
- Maximum 25 projects
- Maximum 50 environments
- Maximum 15 team members
Per-Environment Limits:
- CPU: 8.0 cores max
- RAM: 16,384 MB (16 GB) max
- Disk: 160 GB max
Features:
- Hourly backups (90-day retention)
- Advanced monitoring (Netdata with alerting)
- Priority email support (24-hour response)
- Clone/staging environments enabled
- Custom domains and SSL
- PostgreSQL read replicas (Odoo 18/19 only)
- SSO authentication (SAML, OAuth)
- 99.5% uptime SLA
Best For: Development agencies, mid-size businesses with 10-20 deployments
Business Plan
For larger organizations with extensive Odoo infrastructure.
Pricing:
- Monthly: $199/month
- Annual: 159.20/month equivalent)
Included Resources:
- 5 BYOS servers included
- 2 platform environments included (Small size)
- Maximum 100 projects
- Maximum 200 environments
- Maximum 50 team members
Per-Environment Limits:
- CPU: 16.0 cores max
- RAM: 32,768 MB (32 GB) max
- Disk: 320 GB max
Features:
- Continuous backups (1-year retention)
- Enterprise monitoring (Netdata + custom dashboards)
- Priority phone support (12-hour response)
- Clone/staging environments enabled
- Custom domains and SSL
- PostgreSQL read replicas (Odoo 18/19 only)
- SSO authentication (SAML, OAuth)
- 99.9% uptime SLA
- Dedicated account manager
Best For: Large agencies, enterprises with 50+ deployments
Enterprise Plan
Fully customizable plan for organizations with specific requirements.
Pricing: Custom (contact sales)
Included Resources:
- Unlimited BYOS servers
- Unlimited platform environments
- Unlimited projects
- Unlimited team members
- Custom resource limits per environment
Features:
- Custom backup retention (multi-year)
- White-label monitoring options
- Dedicated support (4-hour SLA)
- All Professional/Business features
- Custom integrations
- On-premise deployment options
- 99.95% uptime SLA
- Dedicated infrastructure
- Custom contract terms
Best For: Large enterprises, ISVs, hosting providers
Contact: sales@oec.sh for custom pricing
Plan Features Matrix
Comprehensive comparison of features across all plans:
| Feature | Free | Starter | Professional | Business | Enterprise |
|---|---|---|---|---|---|
| Limits | |||||
| Max Projects | 2 | 10 | 25 | 100 | Unlimited |
| Max Environments | 5 | 10 | 50 | 200 | Unlimited |
| Max Servers | 1 | 1 | 3 | 5 | Unlimited |
| Max Team Members | 2 | 5 | 15 | 50 | Unlimited |
| Included BYOS Servers | 1 | 1 | 3 | 5 | Custom |
| Included Platform Envs | 0 | 0 | 1 | 2 | Custom |
| Per-Environment Resources | |||||
| Max CPU Cores | 2.0 | 4.0 | 8.0 | 16.0 | Custom |
| Max RAM (MB) | 4,096 | 8,192 | 16,384 | 32,768 | Custom |
| Max Disk (GB) | 40 | 80 | 160 | 320 | Custom |
| Backup & Monitoring | |||||
| Backup Frequency | Daily | Daily | Hourly | Continuous | Custom |
| Backup Retention | 7 days | 30 days | 90 days | 1 year | Custom |
| Monitoring (Netdata) | ✅ Basic | ✅ Full | ✅ Advanced | ✅ Enterprise | ✅ White-label |
| Real-time Alerts | ❌ | ✅ | ✅ | ✅ | ✅ |
| Advanced Features | |||||
| Clone/Staging Envs | ❌ | ✅ | ✅ | ✅ | ✅ |
| Custom Domains/SSL | ❌ | ✅ | ✅ | ✅ | ✅ |
| PostgreSQL Replicas | ❌ | ❌ | ✅ | ✅ | ✅ |
| SSO Authentication | ❌ | ❌ | ✅ | ✅ | ✅ |
| Multi-cloud Backup | ✅ | ✅ | ✅ | ✅ | ✅ |
| Support & SLA | |||||
| Support Level | Community | Priority Email | Phone | Dedicated | |
| Response Time | - | 48 hours | 24 hours | 12 hours | 4 hours |
| Uptime SLA | - | - | 99.5% | 99.9% | 99.95% |
| Account Manager | ❌ | ❌ | ❌ | ✅ | ✅ |
Viewing Current Plan
Billing Overview Page
Access your organization's billing information:
Navigation: Dashboard → Settings → Billing
Permission Required: org.billing.view
The billing overview displays:
-
Current Plan Details
- Plan name and tier
- Billing interval (monthly/annual)
- Current status (trial, active, past_due, etc.)
- Trial end date (if applicable)
- Next billing date
-
Usage Metrics
- Projects: X used / Y limit
- Environments: X used / Y limit
- Servers: X used / Y limit
- Team members: X used / Y limit
- CPU allocation: X cores used / Y cores limit
- RAM allocation: X GB used / Y GB limit
- Disk allocation: X GB used / Y GB limit
-
Cost Breakdown
- Base plan cost: $X/month
- Additional items cost: $Y/month
- Total monthly cost: $Z/month
- Annual savings (if applicable): $A/year
-
Billable Items
- List of BYOS servers with pricing
- List of platform environments with pricing
- List of add-ons with pricing
API Endpoint
GET /api/v1/organizations/{org_id}/billing/summaryAuthentication: Required (JWT token)
Authorization: org.billing.view permission
Response: 200 OK
{
"subscription": {
"id": "uuid",
"plan": "professional",
"status": "active",
"billing_interval": "annual",
"trial_ends_at": null,
"current_period_end": "2025-01-15T00:00:00Z",
"pending_plan": null,
"can_pause": true
},
"plan_limits": {
"max_servers": 3,
"max_environments": 50,
"max_projects": 25,
"max_members": 15,
"included_byos_servers": 3,
"included_platform_envs": 1
},
"usage": {
"servers": 2,
"environments": 12,
"projects": 5,
"members": 8
},
"billing": {
"base_plan_cost": 79.00,
"items_cost": 19.00,
"total_monthly": 98.00,
"currency": "USD"
},
"items": [
{
"id": "uuid",
"type": "byos_server",
"name": "Production Server",
"tier": "standard",
"price": 19.00,
"quantity": 1
}
]
}Upgrading Plan
Upgrade Process
Plan upgrades take effect immediately with prorated billing.
Steps:
- Navigate to: Dashboard → Settings → Billing
- Click "Upgrade Plan"
- Select higher tier plan
- Choose billing interval (monthly/annual)
- Review proration calculation
- Confirm upgrade
- Stripe processes payment adjustment
- Quotas updated immediately
- New features enabled instantly
Permission Required: org.billing.manage
Restrictions:
- Can only upgrade to higher-tier plans
- Cannot downgrade using upgrade endpoint
- Must be organization owner or admin
Proration Calculation
When upgrading mid-cycle, Stripe automatically calculates proration:
Example: Upgrading from Starter (79/mo) on day 15 of 30-day cycle:
- Unused Starter time: 15 days × (14.50 credit
- Professional cost for remaining period: 15 days × (39.50
- Immediate charge: 14.50 = $25.00
Result: You pay 79 on next billing date.
API Endpoint
POST /api/v1/organizations/{org_id}/billing/subscription/upgradeAuthentication: Required (JWT token)
Authorization: org.billing.manage permission
Request Body:
{
"new_plan": "professional",
"billing_interval": "annual"
}Response: 200 OK
{
"id": "uuid",
"organization_id": "uuid",
"base_plan": "professional",
"status": "active",
"billing_interval": "annual",
"monthly_price": 79.00,
"annual_price": 758.40,
"is_annual": true,
"current_period_start": "2024-12-15T00:00:00Z",
"current_period_end": "2025-12-15T00:00:00Z",
"stripe_customer_id": "cus_XXX",
"stripe_subscription_id": "sub_XXX",
"pending_plan": null
}Error Responses:
400 Bad Request: Invalid plan, already on higher plan, or validation error403 Forbidden: Missingorg.billing.managepermission502 Bad Gateway: Stripe API error
Downgrading Plan
Downgrade Process
Plan downgrades are scheduled for the end of the current billing period to avoid service disruption.
Steps:
- Navigate to: Dashboard → Settings → Billing
- Click "Change Plan"
- Select lower tier plan
- Review usage validation
- Confirm resource requirements met
- Schedule downgrade for period end
- Continue using current plan until then
- Downgrade applies automatically at renewal
Permission Required: org.billing.manage
Important: Current usage must fit within new plan limits.
Downgrade Validation
Before downgrading, the system validates:
- Projects: Current count ≤ new plan limit
- Environments: Current count ≤ new plan limit
- Servers: Current count ≤ new plan limit
- Team Members: Current count ≤ new plan limit
If validation fails, you must remove resources before downgrading:
Error: You have 15 environments but Starter allows 10.
Please remove 5 environments first.Scheduled Downgrade
After scheduling:
- Current plan remains active until period end
pending_planfield shows scheduled planpending_plan_effective_atshows when it takes effect- Full access to current plan features until then
- No immediate billing changes
Example Timeline:
- December 15: Request downgrade Professional → Starter
- December 15-31: Continue using Professional features
- December 31: Downgrade automatically applied
- January 1: Billed at Starter rate ($29/month)
API Endpoint
POST /api/v1/organizations/{org_id}/billing/subscription/downgradeAuthentication: Required (JWT token)
Authorization: org.billing.manage permission
Request Body:
{
"new_plan": "starter",
"billing_interval": "monthly"
}Response: 200 OK
{
"id": "uuid",
"organization_id": "uuid",
"base_plan": "professional",
"status": "active",
"billing_interval": "monthly",
"monthly_price": 79.00,
"pending_plan": "starter",
"pending_plan_effective_at": "2024-12-31T23:59:59Z",
"current_period_end": "2024-12-31T23:59:59Z"
}Error Responses:
400 Bad Request: Usage exceeds new plan limits, invalid plan, or already on lower plan403 Forbidden: Missingorg.billing.managepermission
Cancelling Subscription
Cancellation Options
Two cancellation modes:
- End of Period (Recommended): Cancel at billing cycle end, retain access until then
- Immediate: Cancel now, lose access immediately (not recommended)
Permission Required: org.billing.manage
End-of-Period Cancellation
Process:
- Navigate to: Dashboard → Settings → Billing
- Click "Cancel Subscription"
- Select "Cancel at period end"
- Provide cancellation reason (optional)
- Confirm cancellation
- Continue using service until period end
- Subscription expires at renewal date
- Data retained for 30 days
Timeline:
- Cancellation date: Immediately marked as "cancelling"
- Service access: Until
current_period_end - Final billing: No future charges
- Data retention: 30 days after expiration
- Data deletion: Automatic after retention period
Immediate Cancellation
Process:
- Navigate to: Dashboard → Settings → Billing
- Click "Cancel Subscription"
- Select "Cancel immediately"
- Acknowledge loss of access
- Confirm cancellation
- Subscription terminated instantly
- No refunds for unused time
- Data retained for 30 days
Warning: Immediate cancellation:
- Stops all environments immediately
- No refund for remaining billing period
- Lose access to all features instantly
- Not recommended for production use
Reactivation
To reactivate a cancelled subscription:
- Must be within 30-day retention period
- Navigate to: Dashboard → Settings → Billing
- Click "Reactivate Subscription"
- Select new plan
- Add payment method
- Subscription reactivates immediately
After 30 days, all data is permanently deleted and cannot be recovered.
API Endpoint
POST /api/v1/organizations/{org_id}/billing/subscription/cancelAuthentication: Required (JWT token)
Authorization: org.billing.manage permission
Request Body:
{
"immediate": false,
"reason": "Migrating to on-premise solution"
}Response: 200 OK
{
"id": "uuid",
"organization_id": "uuid",
"base_plan": "professional",
"status": "cancelling",
"cancel_at_period_end": true,
"cancelled_at": "2024-12-15T10:30:00Z",
"cancel_at": "2024-12-31T23:59:59Z",
"current_period_end": "2024-12-31T23:59:59Z",
"data_deletion_at": null
}Immediate Cancellation Response:
{
"status": "expired",
"cancel_at_period_end": false,
"cancelled_at": "2024-12-15T10:30:00Z",
"data_deletion_at": "2025-01-14T10:30:00Z"
}Payment Methods
Stripe Customer Portal
OEC.SH uses Stripe Customer Portal for PCI-compliant payment management.
Features:
- Add/remove credit/debit cards
- Update default payment method
- View payment history
- Download invoice PDFs
- Update billing information
- Manage subscription directly
Security:
- PCI-DSS Level 1 compliant
- No card data stored on OEC.SH servers
- Stripe handles all payment processing
- HMAC signature verification on webhooks
Accessing Customer Portal
From Dashboard:
- Navigate to: Dashboard → Settings → Billing
- Click "Manage Payment Methods"
- Redirected to Stripe Customer Portal
- Make changes in Stripe interface
- Click "Return to OEC.SH" when done
Permission Required: org.billing.manage
Managing Payment Methods
In Stripe Customer Portal:
-
Add Payment Method:
- Click "Add payment method"
- Enter card details
- Set as default (optional)
- Save changes
-
Update Default Method:
- Select existing card
- Click "Set as default"
- Future charges use this card
-
Remove Payment Method:
- Select card to remove
- Click "Remove"
- Cannot remove if only payment method
-
Update Billing Address:
- Click "Update information"
- Edit billing details
- Save changes
API Endpoint
POST /api/v1/organizations/{org_id}/billing/portalAuthentication: Required (JWT token)
Authorization: org.billing.manage permission
Request Body:
{
"return_url": "https://app.oec.sh/dashboard/settings/billing"
}Response: 200 OK
{
"portal_url": "https://billing.stripe.com/session/XXX"
}Usage:
// Frontend code
const response = await api.post(
`/organizations/${orgId}/billing/portal`,
{ return_url: window.location.href }
);
// Redirect to Stripe
window.location.href = response.portal_url;Billing History
Invoice Management
All invoices are automatically generated by Stripe and stored in OEC.SH.
Invoice Information:
- Invoice number (unique identifier)
- Billing period (start and end dates)
- Line items (base plan, billable items, add-ons)
- Subtotal, tax, and total amounts
- Payment status (paid, pending, failed)
- Payment date (if paid)
- PDF download link
Viewing Invoices
In Dashboard:
- Navigate to: Dashboard → Settings → Billing
- Scroll to "Billing History" section
- View list of all invoices
- Click "Download PDF" for any invoice
Permission Required: org.billing.view or org.billing.invoices
Invoice Statuses
| Status | Description |
|---|---|
draft | Invoice created but not finalized |
pending | Awaiting payment |
paid | Successfully paid |
failed | Payment attempt failed |
refunded | Payment refunded |
void | Invoice cancelled |
Failed Payment Handling
When a payment fails:
-
Immediate Actions:
- Subscription status:
active→past_due - Email notification sent
- Stripe retries payment automatically
- Subscription status:
-
Retry Schedule (Stripe default):
- Retry 1: 3 days after failure
- Retry 2: 5 days after failure
- Retry 3: 7 days after failure
- Retry 4: 9 days after failure
-
After All Retries Fail:
- Subscription status:
past_due→suspended - All environments stopped
- Data preserved for 30 days
- Email notification sent
- Subscription status:
-
Resolution:
- Update payment method in Customer Portal
- Stripe automatically retries with new card
- Subscription reactivates on successful payment
- Environments restart automatically
Accessing Stripe Customer Portal
From the Customer Portal you can:
- View complete invoice history
- Download PDF invoices
- See upcoming charges
- View payment receipts
- Export invoice data
Invoice Records in Database
Invoices are synchronized from Stripe via webhooks:
Webhook Events:
invoice.paid: Invoice successfully paidinvoice.payment_failed: Payment failedinvoice.finalized: Invoice finalized and sent
Invoice Fields:
- Stripe invoice ID (reference to Stripe)
- Organization and subscription references
- Invoice number (human-readable)
- Period start and end dates
- Amount, currency, and payment status
- Line items (JSONB)
- PDF URL (hosted by Stripe)
- Payment timestamps
Usage Tracking
Current Usage Overview
The platform tracks resource consumption in real-time for billing and quota enforcement.
Tracked Metrics:
-
Entity Counts:
- Active projects
- Active environments
- Active servers (VMs)
- Active team members
-
Resource Allocation:
- Total CPU cores allocated
- Total RAM allocated (MB)
- Total disk allocated (GB)
-
Usage Percentages:
- Projects: X / Y limit (Z%)
- Environments: X / Y limit (Z%)
- CPU: X / Y cores (Z%)
- RAM: X / Y GB (Z%)
- Disk: X / Y GB (Z%)
Usage Dashboard
Location: Dashboard → Settings → Billing → Usage
Visual Indicators:
- Green (0-70%): Normal usage
- Yellow (70-90%): Approaching limit
- Red (90-100%): At or over limit
Warning Thresholds:
- 80% usage: Warning email sent
- 95% usage: Critical alert email
- 100% usage: Quota exceeded, creation blocked
Usage by Resource Type
Projects Usage:
Active Projects: 8 / 25 (32%)
Status: Normal
Action: Can create 17 more projectsEnvironments Usage:
Active Environments: 45 / 50 (90%)
Status: Critical
Action: Approaching limit, consider upgradingResource Allocation:
CPU: 24.5 cores / 50 cores (49%)
RAM: 92 GB / 120 GB (77%)
Disk: 850 GB / 1,000 GB (85%)
Status: NormalUsage Breakdown by Project
View resource usage per project:
| Project | Environments | CPU | RAM | Disk |
|---|---|---|---|---|
| Production | 5 envs | 12 cores | 48 GB | 400 GB |
| Staging | 3 envs | 6 cores | 24 GB | 200 GB |
| Development | 2 envs | 4 cores | 16 GB | 150 GB |
| Total | 10 envs | 22 cores | 88 GB | 750 GB |
API Endpoint
GET /api/v1/organizations/{org_id}/billing/usageAuthentication: Required (JWT token)
Authorization: org.billing.view permission
Response: 200 OK
{
"usage": {
"projects": {
"used": 8,
"limit": 25,
"percentage": 32
},
"environments": {
"used": 45,
"limit": 50,
"percentage": 90
},
"servers": {
"used": 3,
"limit": 3,
"percentage": 100
},
"members": {
"used": 12,
"limit": 15,
"percentage": 80
},
"cpu": {
"used": 24.5,
"limit": 50.0,
"percentage": 49,
"unit": "cores"
},
"ram": {
"used": 94208,
"limit": 122880,
"percentage": 77,
"unit": "MB"
},
"disk": {
"used": 850,
"limit": 1000,
"percentage": 85,
"unit": "GB"
}
},
"warnings": [
{
"type": "environments",
"message": "Approaching environment limit (90%). Consider upgrading plan.",
"severity": "warning"
},
{
"type": "servers",
"message": "Server limit reached (100%). Upgrade plan to add more servers.",
"severity": "critical"
}
]
}Quota Enforcement
Hard Limits vs Soft Limits
Hard Limits (Cannot exceed):
- Maximum projects per plan
- Maximum environments per plan
- Maximum servers per plan
- Maximum team members per plan
- Per-environment CPU limit
- Per-environment RAM limit
- Per-environment disk limit
Soft Limits (Can temporarily exceed):
- None - all limits are hard enforced
Quota Exceeded Behavior
When attempting to create resources that exceed quota:
Example: Creating Project Beyond Limit
POST /api/v1/organizations/{org_id}/projectsResponse: 400 Bad Request
{
"detail": "Project limit reached (25). Please upgrade your plan or contact support.",
"error_code": "QUOTA_EXCEEDED",
"current_usage": 25,
"limit": 25,
"resource_type": "projects"
}Example: Creating Environment with Excessive Resources
POST /api/v1/projects/{project_id}/environments
{
"cpu_cores": 10.0,
"ram_mb": 20480,
"disk_gb": 200
}Response: 400 Bad Request
{
"detail": "Requested CPU (10.0 cores) exceeds per-environment limit (8.0 cores) for Professional plan.",
"error_code": "RESOURCE_LIMIT_EXCEEDED",
"requested": 10.0,
"limit": 8.0,
"resource_type": "cpu_cores"
}Warnings Before Hitting Limits
Warning Emails:
-
80% Usage Warning:
- Subject: "Approaching resource limit for [Resource Type]"
- Content: Current usage, limit, and upgrade recommendation
- Frequency: Once when threshold crossed
-
95% Critical Alert:
- Subject: "Critical: Resource limit almost reached"
- Content: Urgent upgrade recommendation
- Frequency: Daily until resolved
-
100% Quota Exceeded:
- Subject: "Resource limit reached - Upgrades blocked"
- Content: Cannot create more resources, must upgrade
- Frequency: Per failed creation attempt
Quota Integration with QuotaService
All resource creation endpoints call QuotaService before proceeding:
# Example quota check before creating environment
quota_service = QuotaService(db)
quota_check = await quota_service.can_create_environment(
organization_id=org_id,
cpu_cores=requested_cpu,
ram_mb=requested_ram,
disk_gb=requested_disk
)
if not quota_check.allowed:
raise HTTPException(
status_code=400,
detail=quota_check.reason
)
# Proceed with environment creationQuota Validation Steps:
- Count Check: Verify entity count (projects, environments, etc.) under limit
- Resource Check: Verify requested resources don't exceed per-environment limits
- Total Check: Verify organization's total allocated resources under quota
- Replica Check: If creating replica, count 30% CPU/RAM, 100% disk towards quota
Stripe Webhooks
Webhook Endpoint
OEC.SH receives real-time updates from Stripe via webhooks.
Endpoint: POST /api/v1/webhooks/stripe
Authentication: HMAC signature verification
Webhook Secret: Configured in Stripe Dashboard and STRIPE_WEBHOOK_SECRET environment variable
Event Types Handled
The platform processes these Stripe webhook events:
1. checkout.session.completed
Trigger: Customer completes Stripe Checkout
Actions:
- Link Stripe customer ID to organization
- Link Stripe subscription ID to subscription
- Activate subscription (status:
trial→active) - Update plan details from checkout metadata
- Send welcome email
2. customer.subscription.updated
Trigger: Subscription modified in Stripe
Actions:
- Update subscription status (active, past_due, canceled, etc.)
- Update billing period dates (
current_period_start,current_period_end) - Update cancellation flags (
cancel_at_period_end,cancel_at) - Sync plan changes
- Sync billing interval changes
3. customer.subscription.deleted
Trigger: Subscription cancelled in Stripe
Actions:
- Set subscription status to
expired - Record cancellation timestamp
- Schedule data deletion (30 days)
- Stop all environments
- Send cancellation confirmation email
4. invoice.paid
Trigger: Invoice successfully paid
Actions:
- Create invoice record in database
- Link to subscription
- Store invoice PDF URL
- Clear
past_duestatus if set - Record payment timestamp
- Send payment receipt email
5. invoice.payment_failed
Trigger: Payment attempt failed
Actions:
- Set subscription status to
past_due - Create failed invoice record
- Send payment failure notification
- Stripe automatically retries per retry schedule
6. customer.subscription.paused
Trigger: Subscription paused (via pause API)
Actions:
- Set subscription status to
paused - Record pause timestamp
- Extract resume date from webhook
- Stop all environments
- Send pause confirmation email
7. customer.subscription.resumed
Trigger: Subscription resumed after pause
Actions:
- Set subscription status to
active - Clear pause timestamps
- Restart environments
- Send resume confirmation email
Webhook Security
Signature Verification:
import stripe
# Verify webhook signature
try:
event = stripe.Webhook.construct_event(
payload,
stripe_signature_header,
STRIPE_WEBHOOK_SECRET
)
except stripe.error.SignatureVerificationError:
# Invalid signature - reject webhook
return 401Security Measures:
- HMAC-SHA256 signature verification
- Reject webhooks with invalid/missing signatures
- Timestamp validation (reject old webhooks)
- Idempotency handling (ignore duplicate events)
Webhook Retry Logic
Stripe Retry Behavior:
- Retry failed webhooks automatically
- Exponential backoff (1 hour, 2 hours, 4 hours, etc.)
- Max retries: 3 attempts over 3 days
- After 3 days: Webhook marked as failed in Stripe Dashboard
OEC.SH Handling:
- Return
200 OKeven on processing errors - Log errors for manual investigation
- Don't raise exceptions (prevents infinite retries)
- Use database transactions to ensure consistency
Monitoring Webhooks
In Stripe Dashboard:
- View all webhook delivery attempts
- See success/failure status
- Retry failed webhooks manually
- View webhook payload and response
In OEC.SH Logs:
INFO: Received Stripe webhook
INFO: Processing customer.subscription.updated (event_id: evt_XXX)
INFO: Subscription updated (subscription_id: uuid, status: active)Annual Billing
Annual Discount
Discount: 20% off compared to monthly billing
Calculation:
- Monthly billing: Plan price × 12 months
- Annual billing: Plan price × 12 months × 0.8
- Savings: Plan price × 12 months × 0.2
Annual vs Monthly Comparison
| Plan | Monthly | Annual (Monthly × 12) | Annual Price | Savings |
|---|---|---|---|---|
| Starter | $29/mo | $348/year | $278.40/year | $69.60 (20%) |
| Professional | $79/mo | $948/year | $758.40/year | $189.60 (20%) |
| Business | $199/mo | $2,388/year | $1,910.40/year | $477.60 (20%) |
Example: Professional plan annual savings:
Monthly billing: $79 × 12 = $948/year
Annual billing: $758.40/year
Savings: $189.60/year (equivalent to 2.4 months free)Switching to Annual Billing
From Monthly to Annual:
- Navigate to: Dashboard → Settings → Billing
- Click "Switch to Annual Billing"
- Review savings calculation
- Confirm switch
- Stripe creates prorated invoice
- Annual billing starts immediately
Proration: When switching mid-cycle, you receive credit for unused monthly time:
Current date: December 15
Monthly billing: $79/month, next due December 31
Days remaining: 16 days
Credit for unused monthly: 16 days × ($79/30) = $42.13
Annual billing cost: $758.40
Immediate charge: $758.40 - $42.13 = $716.27
Next billing: December 15, 2025 ($758.40)Annual Commitment Terms
Commitment:
- Billed upfront for full year
- Cannot downgrade during annual term
- Can upgrade anytime (prorated)
- Cancellation: No refund for unused time
Refund Policy:
- No refunds after 14-day trial
- Cancel before annual renewal to avoid next charge
- Cancellation takes effect at term end
- Access maintained until term expiration
Auto-Renewal:
- Annual subscriptions auto-renew yearly
- Email reminder sent 7 days before renewal
- Can disable auto-renewal in Stripe Customer Portal
- If disabled, subscription expires at term end
Enterprise Plan
Custom Pricing
Enterprise plans are fully customized to organization needs.
Pricing Factors:
- Number of environments
- Resource requirements (CPU, RAM, disk)
- Number of team members
- Support SLA requirements
- Backup retention requirements
- Custom feature needs
- Contract term length (1-3 years)
- Volume discounts
Typical Enterprise Pricing: 5,000/month depending on scale
Volume Discounts
| Environments | Discount |
|---|---|
| 200-500 | 10% off |
| 500-1,000 | 15% off |
| 1,000-2,000 | 20% off |
| 2,000+ | 25% off |
Dedicated Support
Enterprise Support Includes:
- Dedicated Account Manager: Single point of contact for all needs
- 24/7 Phone Support: Direct phone line with 4-hour response SLA
- Quarterly Business Reviews: Strategic planning and optimization sessions
- Custom Onboarding: Dedicated onboarding team and migration assistance
- Training Sessions: Live training for administrators and developers
- Architecture Review: Review deployment architecture and provide recommendations
- Priority Feature Requests: Influence product roadmap
SLA Guarantees
Uptime SLA: 99.95% uptime guarantee
Calculation:
99.95% uptime = 0.05% downtime per year
= 4.38 hours downtime per year
= 21.9 minutes downtime per monthSLA Credits:
| Uptime % | Credit |
|---|---|
| < 99.95% | 10% monthly fee |
| < 99.90% | 25% monthly fee |
| < 99.50% | 50% monthly fee |
| < 99.00% | 100% monthly fee |
Exclusions:
- Scheduled maintenance (with 7-day notice)
- Customer-caused issues
- Third-party service failures
- Force majeure events
Contact Sales Workflow
To Request Enterprise Plan:
- Visit: https://oec.sh/enterprise (opens in a new tab)
- Fill out enterprise contact form:
- Organization name
- Number of Odoo instances
- Number of users
- Current hosting setup
- Timeline for migration
- Special requirements
- Sales team responds within 1 business day
- Schedule discovery call (30 minutes)
- Receive custom proposal within 1 week
- Negotiate terms and finalize contract
- Sign SOW (Statement of Work)
- Begin onboarding process
Contact: sales@oec.sh or +1 (555) 123-4567
Permissions
Billing Permissions
Access to billing features is controlled by organization permissions.
org.billing.view
Description: View billing information and invoices
Allows:
- View current plan and subscription details
- View usage metrics and quota limits
- View billable items list
- View billing summary
- View invoice history
- Download invoice PDFs
Does NOT Allow:
- Changing plans
- Managing payment methods
- Cancelling subscription
- Pausing subscription
Default Roles:
org_owner: ✅ Has permissionorg_admin: ✅ Has permissionorg_member: ❌ No permissionproject_admin: ❌ No permissionproject_member: ❌ No permission
org.billing.manage
Description: Manage subscriptions and payment methods
Allows:
- All permissions from
org.billing.view - Upgrade plan
- Downgrade plan
- Start trial
- Cancel subscription
- Pause subscription
- Resume subscription
- Create checkout sessions (initiate payment)
- Access Stripe Customer Portal
- Update payment methods
- Update billing information
Does NOT Allow:
- Viewing other organizations' billing
Default Roles:
org_owner: ✅ Has permissionorg_admin: ✅ Has permission (can be removed if desired)org_member: ❌ No permissionproject_admin: ❌ No permissionproject_member: ❌ No permission
org.billing.invoices
Description: Download and export invoices (accounting teams)
Allows:
- View invoice history
- Download invoice PDFs
- Export invoice data
- View payment receipts
Does NOT Allow:
- Managing subscriptions
- Changing payment methods
Default Roles:
- Typically granted to accounting/finance team members
- Not included by default (must be explicitly granted)
Permission Hierarchy
org.billing.manage (full billing control)
├─ org.billing.view (read-only access)
└─ org.billing.invoices (invoice access)Checking Permissions
In Frontend:
import { useAbilities } from '@/contexts/AbilityContext';
function BillingPage() {
const { can } = useAbilities({ organizationId });
// Check view permission
if (!can('org.billing.view')) {
return <AccessDenied />;
}
// Check manage permission for actions
const canManage = can('org.billing.manage');
return (
<div>
<BillingSummary />
{canManage && (
<div>
<UpgradeButton />
<CancelButton />
</div>
)}
</div>
);
}In Backend:
from core.permissions import check_permission
# Check view permission
has_permission = await check_permission(
db=db,
user=current_user,
permission_code="org.billing.view",
organization_id=organization_id
)
if not has_permission:
raise HTTPException(
status_code=403,
detail="You don't have permission to view billing."
)API Reference
Complete Endpoint Documentation
All billing API endpoints with full details.
1. List Plans
GET /api/v1/billing/plansDescription: Get list of all available subscription plans
Authentication: None (public endpoint)
Response: 200 OK
{
"plans": [
{
"plan": "free",
"name": "Free",
"description": "Perfect for getting started",
"monthly_price": 0.00,
"annual_price": 0.00,
"max_servers": 1,
"max_environments": 5,
"max_projects": 2,
"max_members": 2,
"included_byos_servers": 1,
"included_platform_envs": 0,
"backup_frequency": "daily",
"clone_enabled": false,
"monitoring_enabled": true,
"priority_support": false,
"custom_domains": false,
"sso_enabled": false,
"is_active": true
}
]
}2. Get Pricing
GET /api/v1/billing/pricesDescription: Get complete pricing information for all products
Authentication: None (public endpoint)
Response: 200 OK
{
"base_plans": [...],
"byos_tiers": [
{
"tier": "standard",
"name": "Standard",
"price": 19,
"max_environments": 5
}
],
"environment_sizes": [
{
"size": "small",
"name": "Small",
"price": 39,
"cpu": 1.0,
"ram_mb": 2048,
"disk_gb": 20
}
],
"addons": [
{
"addon": "storage_gb",
"price": 0.10,
"unit": "monthly"
}
]
}3. Get Subscription
GET /api/v1/organizations/{org_id}/billing/subscriptionDescription: Get subscription details for organization
Authentication: Required (JWT)
Authorization: org.billing.view
Response: 200 OK (see "Viewing Current Plan" section)
4. Get Billing Summary
GET /api/v1/organizations/{org_id}/billing/summaryDescription: Get complete billing summary with usage and costs
Authentication: Required (JWT)
Authorization: org.billing.view
Response: 200 OK (see "Viewing Current Plan" section)
5. Start Trial
POST /api/v1/organizations/{org_id}/billing/subscription/trialDescription: Start 14-day trial with Professional features
Authentication: Required (JWT)
Authorization: org.billing.manage
Response: 200 OK (subscription object)
6. Upgrade Plan
POST /api/v1/organizations/{org_id}/billing/subscription/upgradeDescription: Upgrade to higher plan (immediate)
Authentication: Required (JWT)
Authorization: org.billing.manage
Request Body: (see "Upgrading Plan" section)
Response: 200 OK (subscription object)
7. Downgrade Plan
POST /api/v1/organizations/{org_id}/billing/subscription/downgradeDescription: Downgrade to lower plan (end of period)
Authentication: Required (JWT)
Authorization: org.billing.manage
Request Body: (see "Downgrading Plan" section)
Response: 200 OK (subscription object)
8. Pause Subscription
POST /api/v1/organizations/{org_id}/billing/subscription/pauseDescription: Pause subscription for 1-3 months
Authentication: Required (JWT)
Authorization: org.billing.manage
Request Body:
{
"duration_months": 1
}Response: 200 OK (subscription object with paused_at and resumes_at)
9. Resume Subscription
POST /api/v1/organizations/{org_id}/billing/subscription/resumeDescription: Resume paused subscription
Authentication: Required (JWT)
Authorization: org.billing.manage
Response: 200 OK (subscription object with status: "active")
10. Cancel Subscription
POST /api/v1/organizations/{org_id}/billing/subscription/cancelDescription: Cancel subscription (immediate or end of period)
Authentication: Required (JWT)
Authorization: org.billing.manage
Request Body: (see "Cancelling Subscription" section)
Response: 200 OK (subscription object)
11. Create Checkout Session
POST /api/v1/organizations/{org_id}/billing/checkoutDescription: Create Stripe checkout session for payment
Authentication: Required (JWT)
Authorization: org.billing.manage
Request Body:
{
"plan": "professional",
"billing_interval": "annual",
"success_url": "https://app.oec.sh/dashboard/settings/billing?success=true",
"cancel_url": "https://app.oec.sh/dashboard/settings/billing?cancelled=true"
}Response: 200 OK
{
"checkout_url": "https://checkout.stripe.com/c/pay/cs_XXX",
"session_id": "cs_XXX"
}Usage: Redirect user to checkout_url to complete payment
12. Create Customer Portal Session
POST /api/v1/organizations/{org_id}/billing/portalDescription: Create Stripe Customer Portal session
Authentication: Required (JWT)
Authorization: org.billing.manage
Request Body: (see "Payment Methods" section)
Response: 200 OK (see "Payment Methods" section)
13. List Billable Items
GET /api/v1/organizations/{org_id}/billing/items?active_only=trueDescription: List all billable items (servers, environments, add-ons)
Authentication: Required (JWT)
Authorization: org.billing.view
Query Parameters:
active_only(boolean): Filter to active items only (default: true)
Response: 200 OK
[
{
"id": "uuid",
"organization_id": "uuid",
"subscription_id": "uuid",
"item_type": "byos_server",
"reference_id": "uuid",
"reference_name": "Production Server",
"tier": "standard",
"quantity": 1,
"unit_price": 19.00,
"status": "active",
"stripe_subscription_item_id": "si_XXX",
"started_at": "2024-01-01T00:00:00Z",
"cancelled_at": null,
"created_at": "2024-01-01T00:00:00Z"
}
]Best Practices
Monitor Usage Proactively
-
Check Usage Weekly:
- Review Dashboard → Settings → Billing → Usage
- Look for resources approaching limits
- Plan upgrades before hitting quotas
-
Set Up Alerts:
- Enable email notifications for quota warnings
- Monitor 80% and 95% threshold alerts
- Act on warnings before reaching 100%
-
Track Trends:
- Monitor usage growth over time
- Predict when upgrades will be needed
- Budget for plan changes
Set Up Billing Alerts
Recommended Alerts:
- 80% Quota Usage: Warning notification
- 95% Quota Usage: Critical notification
- 100% Quota Usage: Blocking notification
- Payment Failed: Immediate action required
- Trial Ending: 3-day advance notice
- Subscription Renewal: 7-day advance notice
Email Preferences:
- Navigate to: Dashboard → Settings → Notifications
- Enable billing alerts
- Configure alert recipients (multiple email addresses)
- Set alert frequency (immediate, daily digest)
Review Plan Periodically
Quarterly Review Checklist:
-
Usage Assessment:
- ✅ Review current resource usage
- ✅ Compare against plan limits
- ✅ Identify underutilized capacity
- ✅ Identify overages or constraints
-
Cost Optimization:
- ✅ Calculate total monthly cost
- ✅ Compare against actual usage
- ✅ Identify cost-saving opportunities
- ✅ Consider annual billing for savings
-
Plan Alignment:
- ✅ Current plan matches needs?
- ✅ Should upgrade for more capacity?
- ✅ Should downgrade to save costs?
- ✅ Enterprise plan needed?
-
Feature Utilization:
- ✅ Using all paid features?
- ✅ Need features from higher tiers?
- ✅ Can eliminate unused features?
Annual Billing for Savings
When to Choose Annual:
- ✅ Stable resource needs for next 12 months
- ✅ Want to save 20% on subscription cost
- ✅ Predictable budget planning
- ✅ Committed to platform long-term
When to Choose Monthly:
- ❌ Uncertain resource needs
- ❌ Testing platform or plan fit
- ❌ May need to scale up/down frequently
- ❌ Prefer payment flexibility
Switching Strategy:
- Start with monthly billing during onboarding (first 3 months)
- Switch to annual after confirming plan fits needs
- Maximize savings once usage stabilizes
Right-Sizing Resources
Environment Sizing:
- Start with smaller environments
- Monitor performance metrics
- Upgrade when consistently over 80% CPU/RAM
- Don't over-provision "just in case"
Plan Selection:
- Choose plan based on actual usage + 20-30% buffer
- Don't jump to highest tier prematurely
- Upgrade when consistently at 85-90% of limits
- Review annually for optimization
Troubleshooting
Payment Failed
Symptoms:
- Email: "Payment failed for your OEC.SH subscription"
- Subscription status:
past_due - Dashboard shows payment warning banner
Causes:
- Expired credit card
- Insufficient funds
- Card declined by bank
- Incorrect billing address
- Bank fraud protection triggered
Resolution Steps:
-
Update Payment Method:
- Go to: Dashboard → Settings → Billing
- Click "Manage Payment Methods"
- Add new payment method or update existing
- Set as default payment method
-
Contact Your Bank:
- Verify card has sufficient funds
- Check for fraud alerts or blocks
- Confirm international payments allowed (Stripe processes in US)
-
Retry Payment:
- Stripe automatically retries with updated payment method
- Or click "Retry Payment" in Customer Portal
-
Contact Support:
- If payment still fails after updating card
- Email: billing@oec.sh
- Provide organization ID and error details
Timeline:
- Day 0: Payment fails, subscription status →
past_due - Day 3: Automatic retry #1
- Day 5: Automatic retry #2
- Day 7: Automatic retry #3
- Day 9: Automatic retry #4
- Day 10: All retries exhausted, subscription →
suspended - Day 40: Data deletion if not resolved
Quota Exceeded After Downgrade
Symptoms:
- Scheduled downgrade fails to apply
- Error: "Current usage exceeds new plan limits"
- Subscription stays on current plan
- Email notification about downgrade failure
Causes:
- Resources added after scheduling downgrade
- Forgot to remove resources before downgrade
- Didn't account for all resource types
Resolution Steps:
-
Check Current Usage:
- Go to: Dashboard → Settings → Billing → Usage
- Compare usage vs new plan limits
- Identify resources to remove
-
Remove Excess Resources:
- Environments: Delete or deactivate environments
- Projects: Archive or delete unused projects
- Servers: Remove excess BYOS servers
- Members: Remove inactive team members
-
Reschedule Downgrade:
- After removing resources, downgrade will apply automatically
- Or manually retry downgrade via API/UI
Example:
Target Plan: Starter (10 environments max)
Current Usage: 15 environments
Action Required: Remove 5 environments
Steps:
1. Navigate to: Dashboard → Projects
2. Identify 5 least-used environments
3. Stop and delete each environment
4. Verify usage now shows 10 environments
5. Downgrade will apply at period endWebhook Delivery Failures
Symptoms:
- Stripe shows webhook delivery failures
- Subscription status out of sync
- Invoices missing from OEC.SH
- Payment status not updated
Causes:
- OEC.SH server temporarily unavailable
- Network connectivity issues
- Webhook secret misconfigured
- Firewall blocking Stripe IPs
Resolution Steps:
-
Check Webhook Status in Stripe:
- Log in to Stripe Dashboard
- Navigate to: Developers → Webhooks
- View webhook endpoint status
- Check recent delivery attempts
-
Retry Failed Webhooks:
- In Stripe Dashboard, click on failed webhook
- Click "Resend" button
- Verify successful delivery
-
Verify Webhook Configuration:
- Webhook URL:
https://api.oec.sh/api/v1/webhooks/stripe - Events to send: All subscription and invoice events
- Webhook secret matches
STRIPE_WEBHOOK_SECRETenv var
- Webhook URL:
-
Check Server Logs:
- Look for webhook processing errors
- Verify HMAC signature validation passing
- Check for database transaction errors
-
Contact Support:
- If webhooks continue failing
- Email: support@oec.sh
- Provide webhook event IDs and timestamps
Portal Session Expired
Symptoms:
- Clicking "Manage Payment Methods" shows error
- Stripe Customer Portal link doesn't work
- Error: "This session has expired"
Causes:
- Portal session expires after 24 hours
- Browser cached old session URL
- Session created but not used immediately
Resolution Steps:
-
Generate New Session:
- Go back to: Dashboard → Settings → Billing
- Click "Manage Payment Methods" again
- New session created with fresh 24-hour expiration
-
Use Session Immediately:
- Click link and complete changes within 24 hours
- Don't bookmark portal URLs (they expire)
Note: Portal sessions are single-use and time-limited for security.
Related Documentation
- Quota Management - Resource quota system and limits
- Resource Allocation - Environment resource configuration
- Organization Settings - General organization management
- Permission System - Access control and roles
- Backup Management - Backup retention and plan limits
- PostgreSQL Replicas - Read replica billing and quotas
Support
For billing and payment assistance:
- Email: billing@oec.sh
- Documentation: https://docs.oec.sh (opens in a new tab)
- Status: https://status.oec.sh (opens in a new tab)
- Enterprise Sales: sales@oec.sh
Response Times:
- Free: Community support only
- Starter: 48 hours (email)
- Professional: 24 hours (email)
- Business: 12 hours (phone + email)
- Enterprise: 4 hours (dedicated support)