Files
gatehouse-api/gatehouse_app/models/billing/subscription.py
T

100 lines
3.2 KiB
Python

"""Subscription model for organization billing."""
import logging
from datetime import datetime, timezone
from sqlalchemy import Column, String, Integer, Boolean, DateTime, ForeignKey, Enum
from gatehouse_app.models.base import BaseModel
import enum
logger = logging.getLogger(__name__)
class SubscriptionStatus(enum.Enum):
"""Subscription status values."""
TRIAL = "trial"
ACTIVE = "active"
PAST_DUE = "past_due"
CANCELLED = "cancelled"
SUSPENDED = "suspended"
class BillingCycle(enum.Enum):
"""Billing cycle values."""
MONTHLY = "monthly"
YEARLY = "yearly"
class Subscription(BaseModel):
"""Organization subscription record.
Links an organization to a plan and tracks billing state.
"""
__tablename__ = "subscriptions"
# Organization relation
organization_id = Column(
String(36),
ForeignKey("organizations.id", ondelete="CASCADE"),
unique=True,
nullable=False
)
# Plan relation
plan_id = Column(
String(36),
ForeignKey("plans.id", ondelete="SET NULL"),
nullable=True
)
# Status
status = Column(
Enum(SubscriptionStatus, name="subscription_status"),
nullable=False,
default=SubscriptionStatus.TRIAL
)
# Billing
billing_cycle = Column(
Enum(BillingCycle, name="billing_cycle"),
nullable=False,
default=BillingCycle.MONTHLY
)
# Period dates
current_period_start = Column(DateTime, nullable=True)
current_period_end = Column(DateTime, nullable=True)
# Trial
trial_ends_at = Column(DateTime, nullable=True)
# Stripe
stripe_subscription_id = Column(String(100), nullable=True)
# Overage
overage_enabled = Column(Boolean, nullable=False, default=True)
# Cancellation
cancelled_at = Column(DateTime, nullable=True)
cancel_at_period_end = Column(Boolean, nullable=False, default=False)
def __repr__(self):
return f"<Subscription org={self.organization_id} status={self.status.value}>"
def to_dict(self):
"""Convert subscription to dictionary."""
return {
"id": self.id,
"organization_id": self.organization_id,
"plan_id": self.plan_id,
"status": self.status.value if self.status else None,
"billing_cycle": self.billing_cycle.value if self.billing_cycle else None,
"current_period_start": self.current_period_start.isoformat() + "Z" if self.current_period_start else None,
"current_period_end": self.current_period_end.isoformat() + "Z" if self.current_period_end else None,
"trial_ends_at": self.trial_ends_at.isoformat() + "Z" if self.trial_ends_at else None,
"stripe_subscription_id": self.stripe_subscription_id,
"overage_enabled": self.overage_enabled,
"cancelled_at": self.cancelled_at.isoformat() + "Z" if self.cancelled_at else None,
"cancel_at_period_end": self.cancel_at_period_end,
"created_at": self.created_at.isoformat() + "Z" if self.created_at else None,
"updated_at": self.updated_at.isoformat() + "Z" if self.updated_at else None,
}