Files
breakpilot-core/billing-service/internal/models/models.go
Benjamin Boenisch ad111d5e69 Initial commit: breakpilot-core - Shared Infrastructure
Docker Compose with 24+ services:
- PostgreSQL (PostGIS), Valkey, MinIO, Qdrant
- Vault (PKI/TLS), Nginx (Reverse Proxy)
- Backend Core API, Consent Service, Billing Service
- RAG Service, Embedding Service
- Gitea, Woodpecker CI/CD
- Night Scheduler, Health Aggregator
- Jitsi (Web/XMPP/JVB/Jicofo), Mailpit

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 23:47:13 +01:00

373 lines
14 KiB
Go

package models
import (
"time"
"github.com/google/uuid"
)
// SubscriptionStatus represents the status of a subscription
type SubscriptionStatus string
const (
StatusTrialing SubscriptionStatus = "trialing"
StatusActive SubscriptionStatus = "active"
StatusPastDue SubscriptionStatus = "past_due"
StatusCanceled SubscriptionStatus = "canceled"
StatusExpired SubscriptionStatus = "expired"
)
// PlanID represents the available plan IDs
type PlanID string
const (
PlanBasic PlanID = "basic"
PlanStandard PlanID = "standard"
PlanPremium PlanID = "premium"
)
// TaskType represents the type of task
type TaskType string
const (
TaskTypeCorrection TaskType = "correction"
TaskTypeLetter TaskType = "letter"
TaskTypeMeeting TaskType = "meeting"
TaskTypeBatch TaskType = "batch"
TaskTypeOther TaskType = "other"
)
// CarryoverMonthsCap is the maximum number of months tasks can accumulate
const CarryoverMonthsCap = 5
// Subscription represents a user's subscription
type Subscription struct {
ID uuid.UUID `json:"id"`
UserID uuid.UUID `json:"user_id"`
StripeCustomerID string `json:"stripe_customer_id"`
StripeSubscriptionID string `json:"stripe_subscription_id"`
PlanID PlanID `json:"plan_id"`
Status SubscriptionStatus `json:"status"`
TrialEnd *time.Time `json:"trial_end,omitempty"`
CurrentPeriodEnd *time.Time `json:"current_period_end,omitempty"`
CancelAtPeriodEnd bool `json:"cancel_at_period_end"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// BillingPlan represents a billing plan with its features and limits
type BillingPlan struct {
ID PlanID `json:"id"`
StripePriceID string `json:"stripe_price_id"`
Name string `json:"name"`
Description string `json:"description"`
PriceCents int `json:"price_cents"` // Price in cents (990 = 9.90 EUR)
Currency string `json:"currency"`
Interval string `json:"interval"` // "month" or "year"
Features PlanFeatures `json:"features"`
IsActive bool `json:"is_active"`
SortOrder int `json:"sort_order"`
}
// PlanFeatures represents the features and limits of a plan
type PlanFeatures struct {
// Task-based limits (primary billing unit)
MonthlyTaskAllowance int `json:"monthly_task_allowance"` // Tasks per month
MaxTaskBalance int `json:"max_task_balance"` // Max accumulated tasks (allowance * CarryoverMonthsCap)
// Legacy fields for backward compatibility (deprecated, use task-based limits)
AIRequestsLimit int `json:"ai_requests_limit,omitempty"`
DocumentsLimit int `json:"documents_limit,omitempty"`
// Feature flags
FeatureFlags []string `json:"feature_flags"`
MaxTeamMembers int `json:"max_team_members,omitempty"`
PrioritySupport bool `json:"priority_support"`
CustomBranding bool `json:"custom_branding"`
BatchProcessing bool `json:"batch_processing"`
CustomTemplates bool `json:"custom_templates"`
// Premium: Fair Use (no visible limit)
FairUseMode bool `json:"fair_use_mode"`
}
// Task represents a single task that consumes 1 unit from the balance
type Task struct {
ID uuid.UUID `json:"id"`
AccountID uuid.UUID `json:"account_id"`
TaskType TaskType `json:"task_type"`
CreatedAt time.Time `json:"created_at"`
Consumed bool `json:"consumed"` // Always true when created
// Internal metrics (not shown to user)
PageCount int `json:"-"`
TokenCount int `json:"-"`
ProcessTime int `json:"-"` // in seconds
}
// AccountUsage represents the task-based usage for an account
type AccountUsage struct {
ID uuid.UUID `json:"id"`
AccountID uuid.UUID `json:"account_id"`
PlanID PlanID `json:"plan"`
MonthlyTaskAllowance int `json:"monthly_task_allowance"`
CarryoverMonthsCap int `json:"carryover_months_cap"` // Always 5
MaxTaskBalance int `json:"max_task_balance"` // allowance * cap
TaskBalance int `json:"task_balance"` // Current available tasks
LastRenewalAt time.Time `json:"last_renewal_at"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// UsageSummary tracks usage for a specific period (internal metrics)
type UsageSummary struct {
ID uuid.UUID `json:"id"`
UserID uuid.UUID `json:"user_id"`
UsageType string `json:"usage_type"` // "task", "page", "token"
PeriodStart time.Time `json:"period_start"`
TotalCount int `json:"total_count"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// UserEntitlements represents cached entitlements for a user
type UserEntitlements struct {
ID uuid.UUID `json:"id"`
UserID uuid.UUID `json:"user_id"`
PlanID PlanID `json:"plan_id"`
TaskBalance int `json:"task_balance"`
MaxBalance int `json:"max_balance"`
Features PlanFeatures `json:"features"`
UpdatedAt time.Time `json:"updated_at"`
// Legacy fields for backward compatibility with old entitlement service
AIRequestsLimit int `json:"ai_requests_limit"`
AIRequestsUsed int `json:"ai_requests_used"`
DocumentsLimit int `json:"documents_limit"`
DocumentsUsed int `json:"documents_used"`
}
// StripeWebhookEvent tracks processed webhook events for idempotency
type StripeWebhookEvent struct {
StripeEventID string `json:"stripe_event_id"`
EventType string `json:"event_type"`
Processed bool `json:"processed"`
ProcessedAt time.Time `json:"processed_at"`
CreatedAt time.Time `json:"created_at"`
}
// BillingStatusResponse is the response for the billing status endpoint
type BillingStatusResponse struct {
HasSubscription bool `json:"has_subscription"`
Subscription *SubscriptionInfo `json:"subscription,omitempty"`
TaskUsage *TaskUsageInfo `json:"task_usage,omitempty"`
Entitlements *EntitlementInfo `json:"entitlements,omitempty"`
AvailablePlans []BillingPlan `json:"available_plans,omitempty"`
}
// SubscriptionInfo contains subscription details for the response
type SubscriptionInfo struct {
PlanID PlanID `json:"plan_id"`
PlanName string `json:"plan_name"`
Status SubscriptionStatus `json:"status"`
IsTrialing bool `json:"is_trialing"`
TrialDaysLeft int `json:"trial_days_left,omitempty"`
CurrentPeriodEnd *time.Time `json:"current_period_end,omitempty"`
CancelAtPeriodEnd bool `json:"cancel_at_period_end"`
PriceCents int `json:"price_cents"`
Currency string `json:"currency"`
}
// TaskUsageInfo contains current task usage information
// This is the ONLY usage info shown to users
type TaskUsageInfo struct {
TasksAvailable int `json:"tasks_available"` // Current balance
MaxTasks int `json:"max_tasks"` // Max possible balance
InfoText string `json:"info_text"` // "Aufgaben verfuegbar: X von max. Y"
TooltipText string `json:"tooltip_text"` // "Aufgaben koennen sich bis zu 5 Monate ansammeln."
}
// EntitlementInfo contains feature entitlements
type EntitlementInfo struct {
Features []string `json:"features"`
MaxTeamMembers int `json:"max_team_members,omitempty"`
PrioritySupport bool `json:"priority_support"`
CustomBranding bool `json:"custom_branding"`
BatchProcessing bool `json:"batch_processing"`
CustomTemplates bool `json:"custom_templates"`
FairUseMode bool `json:"fair_use_mode"` // Premium only
}
// StartTrialRequest is the request to start a trial
type StartTrialRequest struct {
PlanID PlanID `json:"plan_id" binding:"required"`
}
// StartTrialResponse is the response after starting a trial
type StartTrialResponse struct {
CheckoutURL string `json:"checkout_url"`
SessionID string `json:"session_id"`
}
// ChangePlanRequest is the request to change plans
type ChangePlanRequest struct {
NewPlanID PlanID `json:"new_plan_id" binding:"required"`
}
// ChangePlanResponse is the response after changing plans
type ChangePlanResponse struct {
Success bool `json:"success"`
Message string `json:"message"`
EffectiveDate string `json:"effective_date,omitempty"`
}
// CancelSubscriptionResponse is the response after canceling
type CancelSubscriptionResponse struct {
Success bool `json:"success"`
Message string `json:"message"`
CancelDate string `json:"cancel_date"`
ActiveUntil string `json:"active_until"`
}
// CustomerPortalResponse contains the portal URL
type CustomerPortalResponse struct {
PortalURL string `json:"portal_url"`
}
// ConsumeTaskRequest is the request to consume a task (internal)
type ConsumeTaskRequest struct {
UserID string `json:"user_id" binding:"required"`
TaskType TaskType `json:"task_type" binding:"required"`
}
// ConsumeTaskResponse is the response after consuming a task
type ConsumeTaskResponse struct {
Success bool `json:"success"`
TaskID string `json:"task_id,omitempty"`
TasksRemaining int `json:"tasks_remaining"`
Message string `json:"message,omitempty"`
}
// CheckTaskAllowedResponse is the response for task limit checks
type CheckTaskAllowedResponse struct {
Allowed bool `json:"allowed"`
TasksAvailable int `json:"tasks_available"`
MaxTasks int `json:"max_tasks"`
PlanID PlanID `json:"plan_id"`
Message string `json:"message,omitempty"`
}
// EntitlementCheckResponse is the response for entitlement checks (internal)
type EntitlementCheckResponse struct {
HasEntitlement bool `json:"has_entitlement"`
PlanID PlanID `json:"plan_id,omitempty"`
Message string `json:"message,omitempty"`
}
// TaskLimitError represents the error when task limit is reached
type TaskLimitError struct {
Error string `json:"error"`
CurrentBalance int `json:"current_balance"`
Plan PlanID `json:"plan"`
}
// UsageInfo represents current usage information (legacy, prefer TaskUsageInfo)
type UsageInfo struct {
AIRequestsUsed int `json:"ai_requests_used"`
AIRequestsLimit int `json:"ai_requests_limit"`
AIRequestsPercent float64 `json:"ai_requests_percent"`
DocumentsUsed int `json:"documents_used"`
DocumentsLimit int `json:"documents_limit"`
DocumentsPercent float64 `json:"documents_percent"`
PeriodStart string `json:"period_start"`
PeriodEnd string `json:"period_end"`
}
// CheckUsageResponse is the response for legacy usage checks
type CheckUsageResponse struct {
Allowed bool `json:"allowed"`
CurrentUsage int `json:"current_usage"`
Limit int `json:"limit"`
Remaining int `json:"remaining"`
Message string `json:"message,omitempty"`
}
// TrackUsageRequest is the request to track usage (internal)
type TrackUsageRequest struct {
UserID string `json:"user_id" binding:"required"`
UsageType string `json:"usage_type" binding:"required"`
Quantity int `json:"quantity"`
}
// GetDefaultPlans returns the default billing plans with task-based limits
func GetDefaultPlans() []BillingPlan {
return []BillingPlan{
{
ID: PlanBasic,
Name: "Basic",
Description: "Perfekt fuer den Einstieg - Gelegentliche Nutzung",
PriceCents: 990, // 9.90 EUR
Currency: "eur",
Interval: "month",
Features: PlanFeatures{
MonthlyTaskAllowance: 30, // 30 tasks/month
MaxTaskBalance: 30 * CarryoverMonthsCap, // 150 max
FeatureFlags: []string{"basic_ai", "basic_documents"},
MaxTeamMembers: 1,
PrioritySupport: false,
CustomBranding: false,
BatchProcessing: false,
CustomTemplates: false,
FairUseMode: false,
},
IsActive: true,
SortOrder: 1,
},
{
ID: PlanStandard,
Name: "Standard",
Description: "Fuer regelmaessige Nutzer - Mehrere Klassen und regelmaessige Korrekturen",
PriceCents: 1990, // 19.90 EUR
Currency: "eur",
Interval: "month",
Features: PlanFeatures{
MonthlyTaskAllowance: 100, // 100 tasks/month
MaxTaskBalance: 100 * CarryoverMonthsCap, // 500 max
FeatureFlags: []string{"basic_ai", "basic_documents", "templates", "batch_processing"},
MaxTeamMembers: 3,
PrioritySupport: false,
CustomBranding: false,
BatchProcessing: true,
CustomTemplates: true,
FairUseMode: false,
},
IsActive: true,
SortOrder: 2,
},
{
ID: PlanPremium,
Name: "Premium",
Description: "Sorglos-Tarif - Vielnutzer, Teams, schulischer Kontext",
PriceCents: 3990, // 39.90 EUR
Currency: "eur",
Interval: "month",
Features: PlanFeatures{
MonthlyTaskAllowance: 1000, // Very high (Fair Use)
MaxTaskBalance: 1000 * CarryoverMonthsCap, // 5000 max (not shown to user)
FeatureFlags: []string{"basic_ai", "basic_documents", "templates", "batch_processing", "team_features", "admin_panel", "audit_log", "api_access"},
MaxTeamMembers: 10,
PrioritySupport: true,
CustomBranding: true,
BatchProcessing: true,
CustomTemplates: true,
FairUseMode: true, // No visible limit
},
IsActive: true,
SortOrder: 3,
},
}
}
// CalculateMaxTaskBalance calculates max task balance from monthly allowance
func CalculateMaxTaskBalance(monthlyAllowance int) int {
return monthlyAllowance * CarryoverMonthsCap
}