236 lines
8.7 KiB
Python
236 lines
8.7 KiB
Python
# /opt/docker/dev/service_finder/backend/app/services/stripe_adapter.py
|
|
"""
|
|
Stripe integrációs adapter a Payment Router számára.
|
|
Kezeli a Stripe Checkout Session létrehozását és a webhook validációt.
|
|
"""
|
|
|
|
import logging
|
|
from typing import Dict, Any, Optional, Tuple
|
|
from datetime import datetime, timedelta
|
|
from decimal import Decimal
|
|
|
|
from app.core.config import settings
|
|
from app.models.payment import PaymentIntent, PaymentIntentStatus
|
|
from app.models.audit import WalletType
|
|
|
|
logger = logging.getLogger("stripe-adapter")
|
|
|
|
# Try to import stripe, but handle the case when it's not installed
|
|
try:
|
|
import stripe
|
|
STRIPE_AVAILABLE = True
|
|
except ImportError:
|
|
stripe = None
|
|
STRIPE_AVAILABLE = False
|
|
logger.warning("Stripe module not installed. Stripe functionality will be disabled.")
|
|
|
|
|
|
class StripeAdapter:
|
|
"""Stripe API adapter a fizetési gateway integrációhoz."""
|
|
|
|
def __init__(self):
|
|
"""Inicializálja a Stripe klienst a konfigurációból."""
|
|
# Use getattr with defaults for missing settings
|
|
self.stripe_api_key = getattr(settings, 'STRIPE_SECRET_KEY', None)
|
|
self.webhook_secret = getattr(settings, 'STRIPE_WEBHOOK_SECRET', None)
|
|
self.currency = getattr(settings, 'STRIPE_CURRENCY', "EUR")
|
|
|
|
# Check if stripe module is available
|
|
if not STRIPE_AVAILABLE:
|
|
logger.warning("Stripe Python module not installed. Stripe adapter disabled.")
|
|
self.stripe_available = False
|
|
elif not self.stripe_api_key:
|
|
logger.warning("STRIPE_SECRET_KEY nincs beállítva, Stripe adapter nem működik")
|
|
self.stripe_available = False
|
|
else:
|
|
stripe.api_key = self.stripe_api_key
|
|
self.stripe_available = True
|
|
logger.info(f"Stripe adapter inicializálva currency={self.currency}")
|
|
|
|
async def create_checkout_session(
|
|
self,
|
|
payment_intent: PaymentIntent,
|
|
success_url: str,
|
|
cancel_url: str,
|
|
metadata: Optional[Dict[str, Any]] = None
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
Stripe Checkout Session létrehozása a PaymentIntent alapján.
|
|
|
|
Args:
|
|
payment_intent: A PaymentIntent objektum
|
|
success_url: Sikeres fizetés után átirányítási URL
|
|
cancel_url: Megszakított fizetés után átirányítási URL
|
|
metadata: Extra metadata a Stripe számára
|
|
|
|
Returns:
|
|
Dict: Stripe Checkout Session adatai
|
|
"""
|
|
if not self.stripe_available:
|
|
raise ValueError("Stripe nem elérhető, STRIPE_SECRET_KEY hiányzik")
|
|
|
|
if payment_intent.status != PaymentIntentStatus.PENDING:
|
|
raise ValueError(f"PaymentIntent nem PENDING státuszú: {payment_intent.status}")
|
|
|
|
# Alap metadata (kötelező: intent_token)
|
|
base_metadata = {
|
|
"intent_token": str(payment_intent.intent_token),
|
|
"payment_intent_id": payment_intent.id,
|
|
"payer_id": payment_intent.payer_id,
|
|
"target_wallet_type": payment_intent.target_wallet_type.value,
|
|
}
|
|
|
|
if payment_intent.beneficiary_id:
|
|
base_metadata["beneficiary_id"] = payment_intent.beneficiary_id
|
|
|
|
# Egyesített metadata
|
|
final_metadata = {**base_metadata, **(metadata or {})}
|
|
|
|
try:
|
|
# Stripe Checkout Session létrehozása
|
|
session = stripe.checkout.Session.create(
|
|
payment_method_types=["card"],
|
|
line_items=[
|
|
{
|
|
"price_data": {
|
|
"currency": self.currency.lower(),
|
|
"product_data": {
|
|
"name": f"Service Finder - {payment_intent.target_wallet_type.value} feltöltés",
|
|
"description": f"Net: {payment_intent.net_amount} {self.currency}, Fee: {payment_intent.handling_fee} {self.currency}",
|
|
},
|
|
"unit_amount": int(payment_intent.gross_amount * 100), # Stripe centben várja
|
|
},
|
|
"quantity": 1,
|
|
}
|
|
],
|
|
mode="payment",
|
|
success_url=success_url,
|
|
cancel_url=cancel_url,
|
|
client_reference_id=str(payment_intent.id),
|
|
metadata=final_metadata,
|
|
expires_at=int((datetime.utcnow() + timedelta(hours=24)).timestamp()),
|
|
)
|
|
|
|
logger.info(
|
|
f"Stripe Checkout Session létrehozva: {session.id}, "
|
|
f"amount={payment_intent.gross_amount}{self.currency}, "
|
|
f"intent_token={payment_intent.intent_token}"
|
|
)
|
|
|
|
return {
|
|
"session_id": session.id,
|
|
"url": session.url,
|
|
"payment_intent_id": session.payment_intent,
|
|
"expires_at": datetime.fromtimestamp(session.expires_at),
|
|
"metadata": final_metadata,
|
|
}
|
|
|
|
except stripe.error.StripeError as e:
|
|
logger.error(f"Stripe hiba Checkout Session létrehozásakor: {e}")
|
|
raise ValueError(f"Stripe hiba: {e.user_message if hasattr(e, 'user_message') else str(e)}")
|
|
|
|
async def verify_webhook_signature(
|
|
self,
|
|
payload: bytes,
|
|
signature: str
|
|
) -> Tuple[bool, Optional[Dict[str, Any]]]:
|
|
"""
|
|
Stripe webhook aláírás validálása (Kettős Lakat - 1. lépés).
|
|
|
|
Args:
|
|
payload: A nyers HTTP request body
|
|
signature: A Stripe-Signature header értéke
|
|
|
|
Returns:
|
|
Tuple: (sikeres validáció, event adatok vagy None)
|
|
"""
|
|
if not self.webhook_secret:
|
|
logger.error("STRIPE_WEBHOOK_SECRET nincs beállítva, webhook validáció sikertelen")
|
|
return False, None
|
|
|
|
try:
|
|
event = stripe.Webhook.construct_event(
|
|
payload, signature, self.webhook_secret
|
|
)
|
|
logger.info(f"Stripe webhook validálva: {event.type} (id: {event.id})")
|
|
return True, event
|
|
|
|
except stripe.error.SignatureVerificationError as e:
|
|
logger.error(f"Stripe webhook aláírás érvénytelen: {e}")
|
|
return False, None
|
|
except Exception as e:
|
|
logger.error(f"Stripe webhook feldolgozási hiba: {e}")
|
|
return False, None
|
|
|
|
async def handle_checkout_completed(
|
|
self,
|
|
event: Dict[str, Any]
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
checkout.session.completed esemény feldolgozása.
|
|
|
|
Args:
|
|
event: Stripe webhook event
|
|
|
|
Returns:
|
|
Dict: Feldolgozási eredmény
|
|
"""
|
|
session = event["data"]["object"]
|
|
|
|
# Metadata kinyerése
|
|
metadata = session.get("metadata", {})
|
|
intent_token = metadata.get("intent_token")
|
|
|
|
if not intent_token:
|
|
logger.error("Stripe session metadata nem tartalmaz intent_token-t")
|
|
return {"success": False, "error": "Missing intent_token in metadata"}
|
|
|
|
# Összeg ellenőrzése (cent -> valuta)
|
|
amount_total = session.get("amount_total", 0) / 100.0 # Centből valuta
|
|
|
|
logger.info(
|
|
f"Stripe checkout completed: session={session['id']}, "
|
|
f"amount={amount_total}, intent_token={intent_token}"
|
|
)
|
|
|
|
return {
|
|
"success": True,
|
|
"session_id": session["id"],
|
|
"payment_intent_id": session.get("payment_intent"),
|
|
"amount_total": amount_total,
|
|
"currency": session.get("currency", "eur").upper(),
|
|
"metadata": metadata,
|
|
"intent_token": intent_token,
|
|
}
|
|
|
|
async def handle_payment_intent_succeeded(
|
|
self,
|
|
event: Dict[str, Any]
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
payment_intent.succeeded esemény feldolgozása.
|
|
|
|
Args:
|
|
event: Stripe webhook event
|
|
|
|
Returns:
|
|
Dict: Feldolgozási eredmény
|
|
"""
|
|
payment_intent = event["data"]["object"]
|
|
|
|
logger.info(
|
|
f"Stripe payment intent succeeded: {payment_intent['id']}, "
|
|
f"amount={payment_intent['amount']/100}"
|
|
)
|
|
|
|
return {
|
|
"success": True,
|
|
"payment_intent_id": payment_intent["id"],
|
|
"amount": payment_intent["amount"] / 100.0,
|
|
"currency": payment_intent.get("currency", "eur").upper(),
|
|
"status": payment_intent.get("status"),
|
|
}
|
|
|
|
|
|
# Globális példány
|
|
stripe_adapter = StripeAdapter() |