Files
service-finder/backend/app/services/stripe_adapter.py

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()