# /opt/docker/dev/service_finder/backend/app/api/v1/endpoints/billing.py from fastapi import APIRouter, Depends, HTTPException, status, Request, Header from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from typing import Optional, Dict, Any import logging from app.api.deps import get_db, get_current_user from app.models.identity import User, Wallet, UserRole from app.models import FinancialLedger, WalletType from app.models.marketplace.payment import PaymentIntent, PaymentIntentStatus from app.services.config_service import config from app.services.payment_router import PaymentRouter from app.services.stripe_adapter import stripe_adapter from app.services.billing_engine import upgrade_subscription, get_user_balance router = APIRouter() logger = logging.getLogger(__name__) @router.post("/upgrade") async def upgrade_account(target_package: str, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user)): """ Univerzális csomagváltó a Billing Engine segítségével. Kezeli az 5+ csomagot, a Rank-ugrást és a különleges 'Service Coin' bónuszokat. """ try: result = await upgrade_subscription(db, current_user.id, target_package) return { "status": "success", "package": target_package, "price_paid": result.get("price_paid", 0.0), "new_plan": result.get("new_plan"), "expires_at": result.get("expires_at"), "transaction": result.get("transaction") } except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) except Exception as e: logger.error(f"Upgrade error: {e}") raise HTTPException(status_code=500, detail=f"Belső hiba: {str(e)}") @router.post("/payment-intent/create") async def create_payment_intent( request: Dict[str, Any], db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user) ): """ PaymentIntent létrehozása (Prior Intent - Kettős Lakat 1. lépés). Body: - net_amount: float (kötelező) - handling_fee: float (alapértelmezett: 0) - target_wallet_type: string (EARNED, PURCHASED, SERVICE_COINS, VOUCHER) - beneficiary_id: int (opcionális) - currency: string (alapértelmezett: "EUR") - metadata: dict (opcionális) """ try: # Adatok kinyerése net_amount = request.get("net_amount") handling_fee = request.get("handling_fee", 0.0) target_wallet_type_str = request.get("target_wallet_type") beneficiary_id = request.get("beneficiary_id") currency = request.get("currency", "EUR") metadata = request.get("metadata", {}) # Validáció if net_amount is None or net_amount <= 0: raise HTTPException(status_code=400, detail="net_amount pozitív szám kell legyen") if handling_fee < 0: raise HTTPException(status_code=400, detail="handling_fee nem lehet negatív") try: target_wallet_type = WalletType(target_wallet_type_str) except ValueError: raise HTTPException( status_code=400, detail=f"Érvénytelen target_wallet_type: {target_wallet_type_str}. Használd: {[wt.value for wt in WalletType]}" ) # PaymentIntent létrehozása payment_intent = await PaymentRouter.create_payment_intent( db=db, payer_id=current_user.id, net_amount=net_amount, handling_fee=handling_fee, target_wallet_type=target_wallet_type, beneficiary_id=beneficiary_id, currency=currency, metadata=metadata ) return { "success": True, "payment_intent_id": payment_intent.id, "intent_token": str(payment_intent.intent_token), "net_amount": float(payment_intent.net_amount), "handling_fee": float(payment_intent.handling_fee), "gross_amount": float(payment_intent.gross_amount), "currency": payment_intent.currency, "status": payment_intent.status.value, "expires_at": payment_intent.expires_at.isoformat() if payment_intent.expires_at else None, } except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) except Exception as e: logger.error(f"PaymentIntent létrehozási hiba: {e}") raise HTTPException(status_code=500, detail=f"Belső hiba: {str(e)}") @router.post("/payment-intent/{payment_intent_id}/stripe-checkout") async def initiate_stripe_checkout( payment_intent_id: int, request: Dict[str, Any], db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user) ): """ Stripe Checkout Session indítása PaymentIntent alapján. Body: - success_url: string (kötelező) - cancel_url: string (kötelező) """ try: success_url = request.get("success_url") cancel_url = request.get("cancel_url") if not success_url or not cancel_url: raise HTTPException(status_code=400, detail="success_url és cancel_url kötelező") # Ellenőrizzük, hogy a PaymentIntent a felhasználóhoz tartozik-e stmt = select(PaymentIntent).where( PaymentIntent.id == payment_intent_id, PaymentIntent.payer_id == current_user.id ) result = await db.execute(stmt) payment_intent = result.scalar_one_or_none() if not payment_intent: raise HTTPException(status_code=404, detail="PaymentIntent nem található vagy nincs hozzáférésed") # Stripe Checkout indítása session_data = await PaymentRouter.initiate_stripe_payment( db=db, payment_intent_id=payment_intent_id, success_url=success_url, cancel_url=cancel_url ) return { "success": True, "checkout_url": session_data["checkout_url"], "stripe_session_id": session_data["stripe_session_id"], "expires_at": session_data["expires_at"], } except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) except Exception as e: logger.error(f"Stripe Checkout indítási hiba: {e}") raise HTTPException(status_code=500, detail=f"Belső hiba: {str(e)}") @router.post("/payment-intent/{payment_intent_id}/process-internal") async def process_internal_payment( payment_intent_id: int, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user) ): """ Belső ajándékozás feldolgozása (SmartDeduction használatával). Csak akkor engedélyezett, ha a PaymentIntent PENDING státuszú és a felhasználó a payer. """ try: # Ellenőrizzük, hogy a PaymentIntent a felhasználóhoz tartozik-e stmt = select(PaymentIntent).where( PaymentIntent.id == payment_intent_id, PaymentIntent.payer_id == current_user.id, PaymentIntent.status == PaymentIntentStatus.PENDING ) result = await db.execute(stmt) payment_intent = result.scalar_one_or_none() if not payment_intent: raise HTTPException( status_code=404, detail="PaymentIntent nem található, nincs hozzáférésed, vagy nem PENDING státuszú" ) # Belső fizetés feldolgozása result = await PaymentRouter.process_internal_payment(db, payment_intent_id) if not result["success"]: raise HTTPException(status_code=400, detail=result.get("error", "Ismeretlen hiba")) return { "success": True, "transaction_id": result.get("transaction_id"), "used_amounts": result.get("used_amounts"), "beneficiary_credited": result.get("beneficiary_credited", False), } except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) except Exception as e: logger.error(f"Belső fizetés feldolgozási hiba: {e}") raise HTTPException(status_code=500, detail=f"Belső hiba: {str(e)}") @router.post("/stripe-webhook") async def stripe_webhook( request: Request, stripe_signature: Optional[str] = Header(None), db: AsyncSession = Depends(get_db) ): """ Stripe webhook végpont a Kettős Lakat validációval. Stripe a következő header-t küldi: Stripe-Signature """ if not stripe_signature: raise HTTPException(status_code=400, detail="Missing Stripe-Signature header") try: # Request body kiolvasása payload = await request.body() # Webhook feldolgozása result = await PaymentRouter.process_stripe_webhook( db=db, payload=payload, signature=stripe_signature ) if not result.get("success", False): error_msg = result.get("error", "Unknown error") logger.error(f"Stripe webhook feldolgozás sikertelen: {error_msg}") raise HTTPException(status_code=400, detail=error_msg) return result except HTTPException: raise except Exception as e: logger.error(f"Stripe webhook végpont hiba: {e}") raise HTTPException(status_code=500, detail=f"Belső hiba: {str(e)}") @router.get("/payment-intent/{payment_intent_id}/status") async def get_payment_intent_status( payment_intent_id: int, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user) ): """ PaymentIntent státusz lekérdezése. """ try: # Ellenőrizzük, hogy a PaymentIntent a felhasználóhoz tartozik-e stmt = select(PaymentIntent).where( PaymentIntent.id == payment_intent_id, PaymentIntent.payer_id == current_user.id ) result = await db.execute(stmt) payment_intent = result.scalar_one_or_none() if not payment_intent: raise HTTPException(status_code=404, detail="PaymentIntent nem található vagy nincs hozzáférésed") return { "id": payment_intent.id, "intent_token": str(payment_intent.intent_token), "net_amount": float(payment_intent.net_amount), "handling_fee": float(payment_intent.handling_fee), "gross_amount": float(payment_intent.gross_amount), "currency": payment_intent.currency, "status": payment_intent.status.value, "target_wallet_type": payment_intent.target_wallet_type.value, "beneficiary_id": payment_intent.beneficiary_id, "stripe_session_id": payment_intent.stripe_session_id, "transaction_id": str(payment_intent.transaction_id) if payment_intent.transaction_id else None, "created_at": payment_intent.created_at.isoformat(), "updated_at": payment_intent.updated_at.isoformat(), "completed_at": payment_intent.completed_at.isoformat() if payment_intent.completed_at else None, "expires_at": payment_intent.expires_at.isoformat() if payment_intent.expires_at else None, } except Exception as e: logger.error(f"PaymentIntent státusz lekérdezési hiba: {e}") raise HTTPException(status_code=500, detail=f"Belső hiba: {str(e)}") @router.get("/wallet/balance") async def get_wallet_balance( db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user) ): """ Felhasználó pénztárca egyenlegének lekérdezése a Billing Engine segítségével. """ try: balances = await get_user_balance(db, current_user.id) return balances except ValueError as e: raise HTTPException(status_code=404, detail=str(e)) except Exception as e: logger.error(f"Pénztárca egyenleg lekérdezési hiba: {e}") raise HTTPException(status_code=500, detail=f"Belső hiba: {str(e)}") raise HTTPException(status_code=500, detail=f"Belső hiba: {str(e)}")