105 lines
2.9 KiB
Python
105 lines
2.9 KiB
Python
"""Wallet and Transaction ledger models for Credits virtual currency.
|
|
|
|
docs/planning/economy.md US-1.
|
|
"""
|
|
|
|
from datetime import datetime, timezone
|
|
import enum
|
|
|
|
from sqlalchemy import (
|
|
BigInteger,
|
|
CheckConstraint,
|
|
Column,
|
|
DateTime,
|
|
Enum,
|
|
ForeignKey,
|
|
Integer,
|
|
String,
|
|
func,
|
|
)
|
|
from sqlalchemy.dialects.postgresql import JSONB
|
|
from sqlalchemy.types import JSON
|
|
from sqlalchemy.orm import relationship
|
|
|
|
from app.database import Base
|
|
|
|
|
|
class TransactionType(str, enum.Enum):
|
|
credit_purchase = "credit_purchase"
|
|
marketplace_purchase = "marketplace_purchase"
|
|
marketplace_sale = "marketplace_sale"
|
|
escrow_release = "escrow_release"
|
|
payout = "payout"
|
|
refund = "refund"
|
|
admin_adjustment = "admin_adjustment"
|
|
|
|
|
|
class Wallet(Base):
|
|
__tablename__ = "wallets"
|
|
|
|
id = Column(BigInteger, primary_key=True, autoincrement=True)
|
|
user_id = Column(
|
|
Integer,
|
|
ForeignKey("users.id", ondelete="CASCADE"),
|
|
unique=True,
|
|
nullable=False,
|
|
index=True,
|
|
)
|
|
balance = Column(BigInteger, nullable=False, default=0, server_default="0")
|
|
created_at = Column(
|
|
DateTime(timezone=True),
|
|
nullable=False,
|
|
default=lambda: datetime.now(timezone.utc),
|
|
server_default=func.now(),
|
|
)
|
|
updated_at = Column(
|
|
DateTime(timezone=True),
|
|
nullable=False,
|
|
default=lambda: datetime.now(timezone.utc),
|
|
onupdate=lambda: datetime.now(timezone.utc),
|
|
server_default=func.now(),
|
|
)
|
|
|
|
__table_args__ = (
|
|
CheckConstraint("balance >= 0", name="check_wallet_balance_non_negative"),
|
|
)
|
|
|
|
user = relationship("User", backref="wallet", uselist=False)
|
|
transactions = relationship(
|
|
"Transaction",
|
|
back_populates="wallet",
|
|
cascade="all, delete-orphan",
|
|
order_by="Transaction.created_at.desc()",
|
|
)
|
|
|
|
|
|
class Transaction(Base):
|
|
__tablename__ = "transactions"
|
|
|
|
id = Column(BigInteger, primary_key=True, autoincrement=True)
|
|
wallet_id = Column(
|
|
BigInteger,
|
|
ForeignKey("wallets.id", ondelete="CASCADE"),
|
|
nullable=False,
|
|
index=True,
|
|
)
|
|
type = Column(
|
|
Enum(TransactionType, name="transaction_type"),
|
|
nullable=False,
|
|
)
|
|
amount = Column(BigInteger, nullable=False) # signed integer: positive = credit, negative = debit
|
|
balance_after = Column(BigInteger, nullable=False)
|
|
reference_type = Column(String, nullable=True) # e.g. "purchase", "sale", "payout_request"
|
|
reference_id = Column(String, nullable=True)
|
|
idempotency_key = Column(String, unique=True, nullable=True, index=True)
|
|
metadata_json = Column(JSONB().with_variant(JSON, "sqlite"), nullable=True)
|
|
created_at = Column(
|
|
DateTime(timezone=True),
|
|
nullable=False,
|
|
default=lambda: datetime.now(timezone.utc),
|
|
server_default=func.now(),
|
|
index=True,
|
|
)
|
|
|
|
wallet = relationship("Wallet", back_populates="transactions")
|