Backend: - User authentication with JWT tokens (auth_service.py) - Subscription plans: Free, Starter (), Pro (), Business (), Enterprise - Stripe integration for payments (payment_service.py) - Usage tracking and quotas - Credit packages for pay-per-use - Plan-based provider restrictions Frontend: - Landing page with hero, features, pricing preview (landing-sections.tsx) - Pricing page with all plans and credit packages (/pricing) - User dashboard with usage stats (/dashboard) - Login/Register pages with validation (/auth/login, /auth/register) - Ollama self-hosting setup guide (/ollama-setup) - Updated sidebar with user section and plan badge Monetization strategy: - Freemium: 3 docs/day, Ollama only - Starter: 50 docs/month, Google Translate - Pro: 200 docs/month, all providers, API access - Business: 1000 docs/month, team management - Enterprise: Custom pricing, SLA Self-hosted option: - Free unlimited usage with own Ollama server - Complete privacy (data never leaves machine) - Step-by-step setup guide included
251 lines
7.5 KiB
Python
251 lines
7.5 KiB
Python
"""
|
|
Subscription and User models for the monetization system
|
|
"""
|
|
from pydantic import BaseModel, EmailStr, Field
|
|
from typing import Optional, List, Dict, Any
|
|
from datetime import datetime
|
|
from enum import Enum
|
|
|
|
|
|
class PlanType(str, Enum):
|
|
FREE = "free"
|
|
STARTER = "starter"
|
|
PRO = "pro"
|
|
BUSINESS = "business"
|
|
ENTERPRISE = "enterprise"
|
|
|
|
|
|
class SubscriptionStatus(str, Enum):
|
|
ACTIVE = "active"
|
|
CANCELED = "canceled"
|
|
PAST_DUE = "past_due"
|
|
TRIALING = "trialing"
|
|
PAUSED = "paused"
|
|
|
|
|
|
# Plan definitions with limits
|
|
PLANS = {
|
|
PlanType.FREE: {
|
|
"name": "Free",
|
|
"price_monthly": 0,
|
|
"price_yearly": 0,
|
|
"docs_per_month": 3,
|
|
"max_pages_per_doc": 10,
|
|
"max_file_size_mb": 5,
|
|
"providers": ["ollama"], # Only self-hosted
|
|
"features": [
|
|
"3 documents per day",
|
|
"Up to 10 pages per document",
|
|
"Ollama (self-hosted) only",
|
|
"Basic support via community",
|
|
],
|
|
"api_access": False,
|
|
"priority_processing": False,
|
|
"stripe_price_id_monthly": None,
|
|
"stripe_price_id_yearly": None,
|
|
},
|
|
PlanType.STARTER: {
|
|
"name": "Starter",
|
|
"price_monthly": 9,
|
|
"price_yearly": 90, # 2 months free
|
|
"docs_per_month": 50,
|
|
"max_pages_per_doc": 50,
|
|
"max_file_size_mb": 25,
|
|
"providers": ["ollama", "google", "libre"],
|
|
"features": [
|
|
"50 documents per month",
|
|
"Up to 50 pages per document",
|
|
"Google Translate included",
|
|
"LibreTranslate included",
|
|
"Email support",
|
|
],
|
|
"api_access": False,
|
|
"priority_processing": False,
|
|
"stripe_price_id_monthly": "price_starter_monthly",
|
|
"stripe_price_id_yearly": "price_starter_yearly",
|
|
},
|
|
PlanType.PRO: {
|
|
"name": "Pro",
|
|
"price_monthly": 29,
|
|
"price_yearly": 290, # 2 months free
|
|
"docs_per_month": 200,
|
|
"max_pages_per_doc": 200,
|
|
"max_file_size_mb": 100,
|
|
"providers": ["ollama", "google", "deepl", "openai", "libre"],
|
|
"features": [
|
|
"200 documents per month",
|
|
"Up to 200 pages per document",
|
|
"All translation providers",
|
|
"DeepL & OpenAI included",
|
|
"API access (1000 calls/month)",
|
|
"Priority email support",
|
|
],
|
|
"api_access": True,
|
|
"api_calls_per_month": 1000,
|
|
"priority_processing": True,
|
|
"stripe_price_id_monthly": "price_pro_monthly",
|
|
"stripe_price_id_yearly": "price_pro_yearly",
|
|
},
|
|
PlanType.BUSINESS: {
|
|
"name": "Business",
|
|
"price_monthly": 79,
|
|
"price_yearly": 790, # 2 months free
|
|
"docs_per_month": 1000,
|
|
"max_pages_per_doc": 500,
|
|
"max_file_size_mb": 250,
|
|
"providers": ["ollama", "google", "deepl", "openai", "libre", "azure"],
|
|
"features": [
|
|
"1000 documents per month",
|
|
"Up to 500 pages per document",
|
|
"All translation providers",
|
|
"Azure Translator included",
|
|
"Unlimited API access",
|
|
"Priority processing queue",
|
|
"Dedicated support",
|
|
"Team management (up to 5 users)",
|
|
],
|
|
"api_access": True,
|
|
"api_calls_per_month": -1, # Unlimited
|
|
"priority_processing": True,
|
|
"team_seats": 5,
|
|
"stripe_price_id_monthly": "price_business_monthly",
|
|
"stripe_price_id_yearly": "price_business_yearly",
|
|
},
|
|
PlanType.ENTERPRISE: {
|
|
"name": "Enterprise",
|
|
"price_monthly": -1, # Custom
|
|
"price_yearly": -1,
|
|
"docs_per_month": -1, # Unlimited
|
|
"max_pages_per_doc": -1,
|
|
"max_file_size_mb": -1,
|
|
"providers": ["ollama", "google", "deepl", "openai", "libre", "azure", "custom"],
|
|
"features": [
|
|
"Unlimited documents",
|
|
"Unlimited pages",
|
|
"Custom integrations",
|
|
"On-premise deployment",
|
|
"SLA guarantee",
|
|
"24/7 dedicated support",
|
|
"Custom AI models",
|
|
"White-label option",
|
|
],
|
|
"api_access": True,
|
|
"api_calls_per_month": -1,
|
|
"priority_processing": True,
|
|
"team_seats": -1, # Unlimited
|
|
"stripe_price_id_monthly": None, # Contact sales
|
|
"stripe_price_id_yearly": None,
|
|
},
|
|
}
|
|
|
|
|
|
class User(BaseModel):
|
|
id: str
|
|
email: EmailStr
|
|
name: str
|
|
password_hash: str
|
|
created_at: datetime = Field(default_factory=datetime.utcnow)
|
|
updated_at: datetime = Field(default_factory=datetime.utcnow)
|
|
email_verified: bool = False
|
|
avatar_url: Optional[str] = None
|
|
|
|
# Subscription info
|
|
plan: PlanType = PlanType.FREE
|
|
subscription_status: SubscriptionStatus = SubscriptionStatus.ACTIVE
|
|
stripe_customer_id: Optional[str] = None
|
|
stripe_subscription_id: Optional[str] = None
|
|
subscription_ends_at: Optional[datetime] = None
|
|
|
|
# Usage tracking
|
|
docs_translated_this_month: int = 0
|
|
pages_translated_this_month: int = 0
|
|
api_calls_this_month: int = 0
|
|
usage_reset_date: datetime = Field(default_factory=datetime.utcnow)
|
|
|
|
# Extra credits (purchased separately)
|
|
extra_credits: int = 0 # Each credit = 1 page
|
|
|
|
# Settings
|
|
default_source_lang: str = "auto"
|
|
default_target_lang: str = "en"
|
|
default_provider: str = "google"
|
|
|
|
# Ollama self-hosted config
|
|
ollama_endpoint: Optional[str] = None
|
|
ollama_model: Optional[str] = None
|
|
|
|
|
|
class UserCreate(BaseModel):
|
|
email: EmailStr
|
|
name: str
|
|
password: str
|
|
|
|
|
|
class UserLogin(BaseModel):
|
|
email: EmailStr
|
|
password: str
|
|
|
|
|
|
class UserResponse(BaseModel):
|
|
id: str
|
|
email: EmailStr
|
|
name: str
|
|
avatar_url: Optional[str] = None
|
|
plan: PlanType
|
|
subscription_status: SubscriptionStatus
|
|
docs_translated_this_month: int
|
|
pages_translated_this_month: int
|
|
api_calls_this_month: int
|
|
extra_credits: int
|
|
created_at: datetime
|
|
|
|
# Plan limits for display
|
|
plan_limits: Dict[str, Any] = {}
|
|
|
|
|
|
class Subscription(BaseModel):
|
|
id: str
|
|
user_id: str
|
|
plan: PlanType
|
|
status: SubscriptionStatus
|
|
stripe_subscription_id: Optional[str] = None
|
|
stripe_customer_id: Optional[str] = None
|
|
current_period_start: datetime
|
|
current_period_end: datetime
|
|
cancel_at_period_end: bool = False
|
|
created_at: datetime = Field(default_factory=datetime.utcnow)
|
|
|
|
|
|
class UsageRecord(BaseModel):
|
|
id: str
|
|
user_id: str
|
|
document_name: str
|
|
document_type: str # excel, word, pptx
|
|
pages_count: int
|
|
source_lang: str
|
|
target_lang: str
|
|
provider: str
|
|
processing_time_seconds: float
|
|
credits_used: int
|
|
created_at: datetime = Field(default_factory=datetime.utcnow)
|
|
|
|
|
|
class CreditPurchase(BaseModel):
|
|
"""For buying extra credits (pay-per-use)"""
|
|
id: str
|
|
user_id: str
|
|
credits_amount: int
|
|
price_paid: float # in cents
|
|
stripe_payment_id: str
|
|
created_at: datetime = Field(default_factory=datetime.utcnow)
|
|
|
|
|
|
# Credit packages for purchase
|
|
CREDIT_PACKAGES = [
|
|
{"credits": 50, "price": 5.00, "price_per_credit": 0.10, "stripe_price_id": "price_credits_50"},
|
|
{"credits": 100, "price": 9.00, "price_per_credit": 0.09, "stripe_price_id": "price_credits_100", "popular": True},
|
|
{"credits": 250, "price": 20.00, "price_per_credit": 0.08, "stripe_price_id": "price_credits_250"},
|
|
{"credits": 500, "price": 35.00, "price_per_credit": 0.07, "stripe_price_id": "price_credits_500"},
|
|
{"credits": 1000, "price": 60.00, "price_per_credit": 0.06, "stripe_price_id": "price_credits_1000"},
|
|
]
|