🏛️ EPIC 3: Pénzügyi Motor és Főkönyv (Financial Motor Architecture)

Státusz: READY / AUDITED
Dátum: 2026-03-08
Hatáskör: Backend (FastAPI, SQLAlchemy 2.0, PostgreSQL)
Vezetői Összefoglaló (Executive Summary)

A rendszer pénzügyi magja egy szigorú Kettős Könyvvitel (Double-Entry Ledger) elvű, atomi tranzakciókra épülő motor. Célja a "Zero-Sum" (zéró összegű) játékelmélet biztosítása: a rendszerben minden pénz- és kreditmozgásnak (debit/credit) tökéletesen egyeznie kell a felhasználói pénztárcák (Walletek) egyenlegével. Minden tranzakció visszavonhatatlan és auditálható.
📋 Implementált Feladatok és Kártyák
💳 #15 Epic 3 Audit: Pénzügyi Motor és Főkönyv

A pénzügyi alapok lefektetése. A többzsebes pénztárca (Quadruple Wallet) és a megmásíthatatlan főkönyv (Financial Ledger) adatbázis sémájának és modelljeinek elkészítése.

Architekturális Döntések:

    Wallet felépítése: Négy különálló zseb (purchased_credits, earned_credits, service_coins, és a FIFO elvű ActiveVouchers). Szigorúan az identity sémában.

    Főkönyv (Ledger): Az audit sémában tárolva. Nincs description oszlop, helyette egy rugalmas details (JSON) mezőt használunk a metaadatokhoz, és egy transaction_type oszlopot az azonosításhoz.

Letisztított Kódstruktúra (Modellek):
Python

# models/audit.py - Financial Ledger
class FinancialLedger(Base):
    __tablename__ = "financial_ledger"
    __table_args__ = {"schema": "audit"}

    id: Mapped[int] = mapped_column(BigInteger, primary_key=True)
    user_id: Mapped[int] = mapped_column(Integer, ForeignKey("identity.users.id"))
    amount: Mapped[float] = mapped_column(Numeric(18, 4), nullable=False)
    entry_type: Mapped[LedgerEntryType] = mapped_column(PG_ENUM(LedgerEntryType, name="ledger_entry_type", schema="audit"))
    wallet_type: Mapped[WalletType] = mapped_column(PG_ENUM(WalletType, name="wallet_type", schema="audit"))
    transaction_type: Mapped[str] = mapped_column(String(50))
    details: Mapped[Any] = mapped_column(JSON, server_default=text("'{}'::jsonb"))
    balance_after: Mapped[float] = mapped_column(Numeric(18, 4))
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())

🛡️ #16 Fejlesztés: Stripe Webhook implementálása

A külső fizetések (Stripe) integrációja a rendszerbe a Kettős Lakat (Double Lock) biztonsági protokollal.

A Kettős Lakat folyamata:

    HMAC Validáció: A Stripe Stripe-Signature ellenőrzése.

    Intent Egyeztetés: A webhookban kapott azonosító összevetése az adatbázisban lévő PaymentIntent PENDING státuszú rekordjával.

    Összeg Validáció: A Stripe által küldött összeg cent-pontos egyeztetése a mi gross_amount értékünkkel.

    Atomi Könyvelés: Csak ha az előző 3 lépés sikeres, akkor kerül meghívásra az AtomicTransactionManager.

⚙️ #17 Fejlesztés: Billing Engine Service létrehozása

Az okos levonási logika (Smart Deduction) és a belső fizetések/ajándékozások kezelése.

Levonási Prioritás (A "Vízesés" modell):
Amikor egy felhasználó fizet a rendszeren belül, a motor a következő sorrendben terheli meg a zsebeket:

    ActiveVoucher (FIFO elv szerint, a legkorábban lejáró fogy el először)

    service_coins

    purchased_credits

    earned_credits

Letisztított Kódstruktúra (Vízesés logika):
Python

# services/billing_engine.py - Smart Deduction
@classmethod
async def deduct_from_wallets(cls, db: AsyncSession, user_id: int, amount: float) -> dict:
    stmt = select(Wallet).where(Wallet.user_id == user_id)
    wallet = (await db.execute(stmt)).scalar_one()
    remaining = Decimal(str(amount))
    used = {"vouchers": 0, "service_coins": 0, "purchased": 0, "earned": 0}

    # 1. Voucherek (Kihagyva a rövidség kedvéért, FIFO feldolgozás)
    # 2. Service Coins
    if remaining > 0 and wallet.service_coins > 0:
        deduction = min(wallet.service_coins, remaining)
        wallet.service_coins -= deduction
        remaining -= deduction
        used["service_coins"] = float(deduction)

    # 3. Purchased Credits
    if remaining > 0 and wallet.purchased_credits > 0:
        deduction = min(wallet.purchased_credits, remaining)
        wallet.purchased_credits -= deduction
        remaining -= deduction
        used["purchased"] = float(deduction)

    if remaining > 0:
        raise ValueError("Insufficient funds across all wallets.")
    
    await db.flush() # Perzisztáljuk az állapotot a fő tranzakció lezárása nélkül!
    return used

⛓️ #18 Fejlesztés: Atomi tranzakciók bevezetése

A rendszer legkritikusabb pontja. A Nested Transactions (egymásba ágyazott tranzakciók) elkerülése SQLAlchemy 2.0 alatt, biztosítva az adatintegritást.

Architekturális Szabály:
Soha nem használunk db.commit()-ot a szerviz réteg (Service Layer) belső ciklusaiban vagy async with db.begin(): blokkok belsejében. Ehelyett in_transaction() ellenőrzést és flush()-t alkalmazunk.

Letisztított Kódstruktúra (Atomic Manager):
Python

# services/billing_engine.py - AtomicTransactionManager
@classmethod
async def atomic_billing_transaction(cls, db: AsyncSession, user_id: int, amount: float, transaction_type: str, details: dict):
    # Ellenőrizzük, hogy van-e már nyitott tranzakció
    owns_transaction = False
    if not db.in_transaction():
        await db.begin()
        owns_transaction = True

    try:
        # 1. Pénz levonása (Smart Deduction hívása)
        # 2. Főkönyvi (FinancialLedger) bejegyzések létrehozása Debit/Credit párban
        
        await db.flush() # Adatok leírása az adatbázisba, ID-k generálása
        
        if owns_transaction:
            await db.commit() # Csak az zárja le, aki megnyitotta!
            
        return {"transaction_id": details.get("transaction_id")}
        
    except Exception as e:
        if owns_transaction:
            await db.rollback()
        raise e

⏱️ #19 Fejlesztés: Cron-job ütemező beállítása

A lejárt voucherek automatikus feldolgozása, valamint a Network Fee (Rendszerhasználati díj) beszedése.

Üzleti logika (Voucher Expiration):
Ha egy ActiveVoucher lejár (expires_at < now()), a rendszer:

    Átmozgatja a fennmaradó összeget a felhasználó purchased_credits zsebébe.

    Levon 10% "Network Fee"-t a tranzakcióból (a rendszer bevételeként könyvelve).

    Törli/Inaktiválja az ActiveVoucher rekordot.

Ezt a folyamatot egy háttérben futó worker (System-Robot-3) ütemezve hívja meg.
♾️ #20 Fejlesztés: Előfizetés életciklus kezelése

B2B és Prémium felhasználók havidíjas/éves előfizetéseinek menedzselése.

    Renewals (Megújítás): A Cron-job naponta ellenőrzi a subscription_expires_at dátumokat.

    Grace Period (Türelmi idő): Lejárat után 3 napig a profil még publikus, de a Wallet zárolásra kerül.

    Downgrade: Sikertelen levonás esetén a rendszer automatikusan visszasorolja a felhasználót a FREE tier-be, és alkalmazza az ehhez tartozó funkcionális korlátozásokat (Quota management).