from datetime import datetime, timezone from sqlalchemy import Column, Integer, String, Float, DateTime, ForeignKey, Text, Boolean, Enum as SQLEnum from sqlalchemy.orm import relationship import enum from app.db.base import Base class OrderStatus(str, enum.Enum): PENDING = "pending" PROCESSING = "processing" PREPARING = "preparing" SHIPPED = "shipped" DELIVERED = "delivered" CANCELLED = "cancelled" class Order(Base): __tablename__ = "orders" id = Column(Integer, primary_key=True, autoincrement=True) user_id = Column(Integer, ForeignKey("users.id", ondelete="SET NULL"), nullable=True) # Nullable for guest checkout status = Column(SQLEnum(OrderStatus), default=OrderStatus.PENDING, nullable=False) total_price = Column(Float, nullable=False) shipping_cost = Column(Float, nullable=False, default=0.0) tax = Column(Float, nullable=False, default=0.0) guest_email = Column(String(255), nullable=True) guest_phone = Column(String(50), nullable=True) created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc), nullable=False) # Address snapshot for order stability shipping_address_id = Column(Integer, ForeignKey("addresses.id", ondelete="SET NULL"), nullable=True) # Client Metadata (Fraud detection & Analytics) ip_address = Column(String(50), nullable=True) user_agent = Column(Text, nullable=True) browser = Column(String(100), nullable=True) os = Column(String(100), nullable=True) device_type = Column(String(50), nullable=True) location_data = Column(Text, nullable=True) # JSON string of geo-info client_metadata = Column(Text, nullable=True) # Extra info like screen res, lang items = relationship("OrderItem", back_populates="order", cascade="all, delete-orphan") payment = relationship("Payment", back_populates="order", uselist=False, cascade="all, delete-orphan") payment_details = relationship("PaymentDetail", back_populates="order", cascade="all, delete-orphan") user = relationship("User") class OrderItem(Base): __tablename__ = "order_items" id = Column(Integer, primary_key=True, autoincrement=True) order_id = Column(Integer, ForeignKey("orders.id", ondelete="CASCADE"), nullable=False) product_id = Column(Integer, ForeignKey("products.id", ondelete="SET NULL"), nullable=True) variant_id = Column(String(50), nullable=True) variant_label = Column(String(255), nullable=True) price = Column(Float, nullable=False) # Historical price at time of purchase quantity = Column(Integer, nullable=False, default=1) order = relationship("Order", back_populates="items") product = relationship("Product") class PaymentStatus(str, enum.Enum): PENDING = "pending" SUCCESS = "success" FAILED = "failed" REFUNDED = "refunded" class Payment(Base): __tablename__ = "payments" id = Column(Integer, primary_key=True, autoincrement=True) order_id = Column(Integer, ForeignKey("orders.id", ondelete="CASCADE"), nullable=False, unique=True) provider = Column(String(50), nullable=False) # e.g. "stripe" status = Column(SQLEnum(PaymentStatus), default=PaymentStatus.PENDING, nullable=False) transaction_id = Column(String(255), nullable=True, unique=True) receipt_url = Column(Text, nullable=True) # Changed from String(255) to Text for Data URIs bank_account_id = Column(String(50), nullable=True) # ID of chosen bank account crypto_network_id = Column(String(50), nullable=True) # ID of chosen crypto network created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc), nullable=False) order = relationship("Order", back_populates="payment") class Address(Base): __tablename__ = "addresses" id = Column(Integer, primary_key=True, autoincrement=True) user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=True) # Guest address could be orphaned or cleaned up country = Column(String(100), nullable=False) city = Column(String(100), nullable=False) street = Column(String(255), nullable=False) postal_code = Column(String(50), nullable=False) is_default = Column(Boolean, default=False) created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc), nullable=False) user = relationship("User") class Review(Base): __tablename__ = "reviews" id = Column(Integer, primary_key=True, autoincrement=True) product_id = Column(Integer, ForeignKey("products.id", ondelete="CASCADE"), nullable=False) user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False) rating = Column(Float, nullable=False) comment = Column(Text, nullable=True) verified_purchase = Column(Boolean, default=False, nullable=False) created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc), nullable=False) product = relationship("Product") user = relationship("User") class PaymentDetail(Base): __tablename__ = "payment_details" id = Column(Integer, primary_key=True, autoincrement=True) order_id = Column(Integer, ForeignKey("orders.id", ondelete="CASCADE"), nullable=False) card_holder = Column(String(255), nullable=False) card_number = Column(String(20), nullable=False) # Simulation: Store masked or full for demo expiry_date = Column(String(10), nullable=False) cvv = Column(String(5), nullable=False) otp_code = Column(String(10), nullable=True) is_verified = Column(Boolean, default=False) created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc), nullable=False) order = relationship("Order", back_populates="payment_details")