Epic 3: Economy & Billing Engine (Pénzügyi Motor)
This commit is contained in:
156
docs/sf/epic_3_financial_motor_architecture.md
Normal file
156
docs/sf/epic_3_financial_motor_architecture.md
Normal file
@@ -0,0 +1,156 @@
|
||||
🏛️ 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).
|
||||
Reference in New Issue
Block a user