# app/modules/payments/services/gateway_service.py """ Gateway service for managing payment gateway configurations. This service handles: - Gateway configuration and credentials - Gateway health checks - Gateway-specific operations Each gateway has its own implementation that conforms to the gateway protocol. Usage: from app.modules.payments.services import GatewayService gateway_service = GatewayService() # Get available gateways gateways = gateway_service.get_available_gateways() # Check gateway status status = await gateway_service.check_gateway_health("stripe") """ import logging from abc import ABC, abstractmethod from dataclasses import dataclass from enum import Enum from typing import Any, Protocol logger = logging.getLogger(__name__) class GatewayStatus(str, Enum): """Gateway operational status.""" ACTIVE = "active" INACTIVE = "inactive" ERROR = "error" MAINTENANCE = "maintenance" @dataclass class GatewayInfo: """Information about a payment gateway.""" code: str name: str status: GatewayStatus enabled: bool supports_refunds: bool = True supports_recurring: bool = False supported_currencies: list[str] | None = None config: dict[str, Any] | None = None class GatewayProtocol(Protocol): """Protocol that all gateway implementations must follow.""" @property def code(self) -> str: """Gateway code identifier.""" ... @property def name(self) -> str: """Gateway display name.""" ... async def process_payment( self, amount: int, currency: str, payment_method: str, **kwargs: Any, ) -> dict[str, Any]: """Process a payment.""" ... async def refund( self, transaction_id: str, amount: int | None = None, ) -> dict[str, Any]: """Issue a refund.""" ... async def health_check(self) -> bool: """Check if gateway is operational.""" ... class BaseGateway(ABC): """Base class for gateway implementations.""" @property @abstractmethod def code(self) -> str: """Gateway code identifier.""" @property @abstractmethod def name(self) -> str: """Gateway display name.""" @abstractmethod async def process_payment( self, amount: int, currency: str, payment_method: str, **kwargs: Any, ) -> dict[str, Any]: """Process a payment.""" @abstractmethod async def refund( self, transaction_id: str, amount: int | None = None, ) -> dict[str, Any]: """Issue a refund.""" async def health_check(self) -> bool: """Check if gateway is operational.""" return True class StripeGateway(BaseGateway): """Stripe payment gateway implementation.""" @property def code(self) -> str: return "stripe" @property def name(self) -> str: return "Stripe" async def process_payment( self, amount: int, currency: str, payment_method: str, **kwargs: Any, ) -> dict[str, Any]: """Process a payment through Stripe.""" # TODO: Implement Stripe payment processing logger.info(f"Processing Stripe payment: {amount} {currency}") return { "success": True, "transaction_id": f"pi_mock_{amount}", "gateway": self.code, } async def refund( self, transaction_id: str, amount: int | None = None, ) -> dict[str, Any]: """Issue a refund through Stripe.""" # TODO: Implement Stripe refund logger.info(f"Processing Stripe refund for {transaction_id}") return { "success": True, "refund_id": f"re_mock_{transaction_id}", } class PayPalGateway(BaseGateway): """PayPal payment gateway implementation.""" @property def code(self) -> str: return "paypal" @property def name(self) -> str: return "PayPal" async def process_payment( self, amount: int, currency: str, payment_method: str, **kwargs: Any, ) -> dict[str, Any]: """Process a payment through PayPal.""" # TODO: Implement PayPal payment processing logger.info(f"Processing PayPal payment: {amount} {currency}") return { "success": True, "transaction_id": f"paypal_mock_{amount}", "gateway": self.code, } async def refund( self, transaction_id: str, amount: int | None = None, ) -> dict[str, Any]: """Issue a refund through PayPal.""" # TODO: Implement PayPal refund logger.info(f"Processing PayPal refund for {transaction_id}") return { "success": True, "refund_id": f"paypal_refund_{transaction_id}", } class BankTransferGateway(BaseGateway): """Bank transfer gateway implementation.""" @property def code(self) -> str: return "bank_transfer" @property def name(self) -> str: return "Bank Transfer" async def process_payment( self, amount: int, currency: str, payment_method: str, **kwargs: Any, ) -> dict[str, Any]: """Record a bank transfer payment (manual verification).""" logger.info(f"Recording bank transfer: {amount} {currency}") return { "success": True, "transaction_id": f"bt_mock_{amount}", "gateway": self.code, "status": "pending_verification", } async def refund( self, transaction_id: str, amount: int | None = None, ) -> dict[str, Any]: """Record a bank transfer refund (manual process).""" logger.info(f"Recording bank transfer refund for {transaction_id}") return { "success": True, "refund_id": f"bt_refund_{transaction_id}", "status": "pending_manual", } class GatewayService: """ Service for managing payment gateway configurations. Provides a registry of available gateways and methods for gateway operations. """ def __init__(self) -> None: self._gateways: dict[str, BaseGateway] = { "stripe": StripeGateway(), "paypal": PayPalGateway(), "bank_transfer": BankTransferGateway(), } self._enabled_gateways: set[str] = {"stripe", "bank_transfer"} def get_gateway(self, code: str) -> BaseGateway | None: """Get a gateway by code.""" return self._gateways.get(code) def get_available_gateways(self) -> list[GatewayInfo]: """Get list of all available gateways with their status.""" result = [] for code, gateway in self._gateways.items(): result.append( GatewayInfo( code=code, name=gateway.name, status=GatewayStatus.ACTIVE if code in self._enabled_gateways else GatewayStatus.INACTIVE, enabled=code in self._enabled_gateways, supports_refunds=True, supports_recurring=code == "stripe", supported_currencies=["EUR", "USD", "GBP"] if code != "bank_transfer" else ["EUR"], ) ) return result def enable_gateway(self, code: str) -> bool: """Enable a gateway.""" if code in self._gateways: self._enabled_gateways.add(code) logger.info(f"Enabled gateway: {code}") return True return False def disable_gateway(self, code: str) -> bool: """Disable a gateway.""" if code in self._enabled_gateways: self._enabled_gateways.remove(code) logger.info(f"Disabled gateway: {code}") return True return False async def check_gateway_health(self, code: str) -> dict[str, Any]: """Check the health of a specific gateway.""" gateway = self._gateways.get(code) if not gateway: return {"status": "unknown", "message": f"Gateway {code} not found"} try: is_healthy = await gateway.health_check() return { "status": "healthy" if is_healthy else "unhealthy", "gateway": code, } except Exception as e: # noqa: EXC003 logger.exception(f"Gateway health check failed: {code}") return { "status": "error", "gateway": code, "message": str(e), } async def check_all_gateways(self) -> list[dict[str, Any]]: """Check health of all enabled gateways.""" results = [] for code in self._enabled_gateways: result = await self.check_gateway_health(code) results.append(result) return results # Singleton instance gateway_service = GatewayService() __all__ = [ "GatewayService", "GatewayStatus", "GatewayInfo", "GatewayProtocol", "BaseGateway", "StripeGateway", "PayPalGateway", "BankTransferGateway", "gateway_service", ]