"""Monetization models: Subscription, Transaction, Boost. Stripe is source of truth for subscriptions. Local rows mirror state and are reconciled via webhooks + nightly job. Money stored as integer cents throughout. """ from datetime import datetime from sqlalchemy import JSON from app.extensions import db class Subscription(db.Model): __tablename__ = "subscriptions" id = db.Column(db.BigInteger().with_variant(db.Integer, "sqlite"), primary_key=True, autoincrement=True) user_id = db.Column(db.BigInteger().with_variant(db.Integer, "sqlite"), db.ForeignKey("users.id"), nullable=False, unique=True, index=True) plan_id = db.Column(db.BigInteger().with_variant(db.Integer, "sqlite"), db.ForeignKey("plans.id"), nullable=False) stripe_customer_id = db.Column(db.String(64), nullable=True, index=True) stripe_sub_id = db.Column(db.String(64), nullable=True, unique=True, index=True) status = db.Column( db.Enum("active", "past_due", "canceled", "trialing", name="sub_status"), nullable=False, default="active") current_period_end = db.Column(db.DateTime, nullable=True) cancel_at_period_end = db.Column(db.Boolean, nullable=False, default=False) created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False) updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False) user = db.relationship("User", backref=db.backref("subscription", uselist=False)) plan = db.relationship("Plan", backref=db.backref("subscriptions", lazy="dynamic")) @property def is_active(self): return self.status in ("active", "trialing") def __repr__(self): return f"" class Transaction(db.Model): __tablename__ = "transactions" id = db.Column(db.BigInteger().with_variant(db.Integer, "sqlite"), primary_key=True, autoincrement=True) user_id = db.Column(db.BigInteger().with_variant(db.Integer, "sqlite"), db.ForeignKey("users.id"), nullable=False, index=True) type = db.Column( db.Enum("subscription", "boost", "refund", name="txn_type"), nullable=False) amount_cents = db.Column(db.Integer, nullable=False, default=0) currency = db.Column(db.String(3), nullable=False, default="usd") stripe_object_id = db.Column(db.String(64), nullable=True, index=True) status = db.Column(db.String(20), nullable=False, default="succeeded") meta = db.Column(JSON, nullable=True) created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False) user = db.relationship("User", backref=db.backref("transactions", lazy="dynamic")) def __repr__(self): return f"" class Boost(db.Model): __tablename__ = "boosts" # Boost types and their default durations in days TYPES = { "featured": {"label": "Featured listing", "days": 7, "cents": 499}, "bump": {"label": "Bump to top", "days": 3, "cents": 199}, "highlight":{"label": "Highlight", "days": 7, "cents": 299}, "urgent": {"label": "Urgent tag", "days": 7, "cents": 199}, } id = db.Column(db.BigInteger().with_variant(db.Integer, "sqlite"), primary_key=True, autoincrement=True) listing_id = db.Column(db.BigInteger().with_variant(db.Integer, "sqlite"), db.ForeignKey("listings.id"), nullable=False, index=True) user_id = db.Column(db.BigInteger().with_variant(db.Integer, "sqlite"), db.ForeignKey("users.id"), nullable=False) type = db.Column( db.Enum("featured", "bump", "highlight", "urgent", name="boost_type"), nullable=False) expires_at = db.Column(db.DateTime, nullable=False, index=True) transaction_id = db.Column( db.BigInteger().with_variant(db.Integer, "sqlite"), db.ForeignKey("transactions.id"), nullable=True) created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False) listing = db.relationship("Listing", backref=db.backref("boosts", lazy="selectin")) user = db.relationship("User", backref=db.backref("boosts", lazy="dynamic")) transaction = db.relationship("Transaction") @property def is_active(self): return self.expires_at > datetime.utcnow() def __repr__(self): return f""