7.0 KiB
🏛️ 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).