All checks were successful
- Add centralized _is_noqa_suppressed() to BaseValidator with normalization (accepts both SEC001 and SEC-001 formats for ruff compatibility) - Wire noqa support into all 21 security and 18 performance check functions - Add ruff external config for SEC/PERF/MOD/EXC codes in pyproject.toml - Convert all 280 Python noqa comments to dashless format (ruff-compatible) - Add site/ to IGNORE_PATTERNS (excludes mkdocs build output) - Suppress 152 false positive findings (test passwords, seed data, validator self-references, Apple Wallet SHA1, etc.) - Security: 79 errors → 0, 60 warnings → 0 - Performance: 80 warnings → 77 (3 test script suppressions) - Add proposal doc with noqa inventory and remaining findings recommendations Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
348 lines
9.1 KiB
Python
348 lines
9.1 KiB
Python
# 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",
|
|
]
|