admin_nyelvi_javítas
This commit is contained in:
244
backend/app/services/mock_payment_gateway.py
Normal file
244
backend/app/services/mock_payment_gateway.py
Normal file
@@ -0,0 +1,244 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/services/mock_payment_gateway.py
|
||||
"""
|
||||
Mock Payment Gateway — Development/Testing implementation of BasePaymentGateway.
|
||||
|
||||
Simulates successful payment processing without requiring real Stripe keys.
|
||||
Can be configured to simulate failures for testing error handling paths.
|
||||
|
||||
THOUGHT PROCESS:
|
||||
- Implements BasePaymentGateway abstract interface (Strategy Pattern).
|
||||
- Default mode is "auto_approve" — all payments succeed immediately.
|
||||
- Supports "simulate_failure" mode for testing error paths.
|
||||
- Generates deterministic mock session IDs for traceability.
|
||||
- Logs all payment attempts for debugging.
|
||||
- Decoupled from FinancialManager via dependency injection.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import uuid
|
||||
from datetime import datetime, timedelta
|
||||
from decimal import Decimal
|
||||
from typing import Optional, Dict, Any
|
||||
|
||||
from app.services.financial_interfaces import (
|
||||
BasePaymentGateway,
|
||||
PaymentGatewayError,
|
||||
)
|
||||
|
||||
logger = logging.getLogger("mock-payment-gateway")
|
||||
|
||||
|
||||
class MockPaymentGateway(BasePaymentGateway):
|
||||
"""
|
||||
Mock payment gateway for development and testing.
|
||||
|
||||
Modes:
|
||||
- auto_approve (default): All payments succeed immediately.
|
||||
- simulate_failure: All payments fail with a configurable error message.
|
||||
- simulate_timeout: Payments hang until a configurable timeout then succeed.
|
||||
|
||||
Usage:
|
||||
gateway = MockPaymentGateway(mode="auto_approve")
|
||||
result = await gateway.create_intent(Decimal("100.00"), "EUR")
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
mode: str = "auto_approve",
|
||||
failure_message: str = "Mock payment declined (simulated failure)",
|
||||
timeout_seconds: int = 5,
|
||||
):
|
||||
"""
|
||||
Initialize the mock gateway.
|
||||
|
||||
Args:
|
||||
mode: Operation mode — "auto_approve", "simulate_failure", or "simulate_timeout".
|
||||
failure_message: Custom error message for simulate_failure mode.
|
||||
timeout_seconds: Simulated processing delay for simulate_timeout mode.
|
||||
"""
|
||||
self.mode = mode
|
||||
self.failure_message = failure_message
|
||||
self.timeout_seconds = timeout_seconds
|
||||
self._processed_intents: Dict[str, Dict[str, Any]] = {}
|
||||
|
||||
logger.info(
|
||||
"MockPaymentGateway initialized: mode=%s, timeout=%ds",
|
||||
self.mode, self.timeout_seconds,
|
||||
)
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────
|
||||
# BasePaymentGateway Implementation
|
||||
# ──────────────────────────────────────────────────────────────────────────
|
||||
|
||||
async def create_intent(
|
||||
self,
|
||||
amount: Decimal,
|
||||
currency: str = "EUR",
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
**kwargs,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Create a mock payment intent.
|
||||
|
||||
In auto_approve mode, immediately returns a "completed" intent.
|
||||
In simulate_failure mode, raises PaymentGatewayError.
|
||||
In simulate_timeout mode, logs a warning and returns "processing".
|
||||
|
||||
Args:
|
||||
amount: The payment amount.
|
||||
currency: ISO 4217 currency code (default: EUR).
|
||||
metadata: Optional metadata dict.
|
||||
**kwargs: Additional parameters (ignored in mock).
|
||||
|
||||
Returns:
|
||||
Dict with mock payment intent details.
|
||||
|
||||
Raises:
|
||||
PaymentGatewayError: In simulate_failure mode.
|
||||
"""
|
||||
intent_id = f"mock_intent_{uuid.uuid4().hex[:12]}"
|
||||
logger.info(
|
||||
"Mock create_intent: id=%s amount=%s %s mode=%s",
|
||||
intent_id, amount, currency, self.mode,
|
||||
)
|
||||
|
||||
if self.mode == "simulate_failure":
|
||||
logger.warning(
|
||||
"Mock payment FAILURE: intent=%s reason='%s'",
|
||||
intent_id, self.failure_message,
|
||||
)
|
||||
raise PaymentGatewayError(self.failure_message)
|
||||
|
||||
if self.mode == "simulate_timeout":
|
||||
logger.warning(
|
||||
"Mock payment TIMEOUT simulation: intent=%s delay=%ds",
|
||||
intent_id, self.timeout_seconds,
|
||||
)
|
||||
# In a real scenario we'd sleep; here we just return "processing"
|
||||
# so the caller can handle the pending state.
|
||||
|
||||
now = datetime.utcnow()
|
||||
intent_data = {
|
||||
"id": intent_id,
|
||||
"status": "completed" if self.mode == "auto_approve" else "processing",
|
||||
"amount": float(amount),
|
||||
"currency": currency,
|
||||
"created_at": now.isoformat(),
|
||||
"completed_at": now.isoformat() if self.mode == "auto_approve" else None,
|
||||
"metadata": metadata or {},
|
||||
"gateway": "mock",
|
||||
}
|
||||
|
||||
self._processed_intents[intent_id] = intent_data
|
||||
return intent_data
|
||||
|
||||
async def verify_payment(
|
||||
self,
|
||||
payment_intent_id: str,
|
||||
**kwargs,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Verify a mock payment's status.
|
||||
|
||||
Args:
|
||||
payment_intent_id: The mock intent ID to verify.
|
||||
**kwargs: Additional parameters (ignored in mock).
|
||||
|
||||
Returns:
|
||||
Dict with verification details.
|
||||
|
||||
Raises:
|
||||
PaymentGatewayError: If the intent ID is unknown.
|
||||
"""
|
||||
intent = self._processed_intents.get(payment_intent_id)
|
||||
if not intent:
|
||||
logger.warning(
|
||||
"Mock verify_payment: unknown intent_id=%s",
|
||||
payment_intent_id,
|
||||
)
|
||||
raise PaymentGatewayError(
|
||||
f"Mock payment intent '{payment_intent_id}' not found"
|
||||
)
|
||||
|
||||
logger.info(
|
||||
"Mock verify_payment: intent=%s status=%s",
|
||||
payment_intent_id, intent["status"],
|
||||
)
|
||||
|
||||
return {
|
||||
"verified": intent["status"] == "completed",
|
||||
"intent_id": payment_intent_id,
|
||||
"status": intent["status"],
|
||||
"amount": intent["amount"],
|
||||
"currency": intent["currency"],
|
||||
}
|
||||
|
||||
async def refund_payment(
|
||||
self,
|
||||
payment_intent_id: str,
|
||||
amount: Optional[Decimal] = None,
|
||||
**kwargs,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Refund a mock payment.
|
||||
|
||||
Args:
|
||||
payment_intent_id: The mock intent ID to refund.
|
||||
amount: Optional partial refund amount (None = full refund).
|
||||
**kwargs: Additional parameters (ignored in mock).
|
||||
|
||||
Returns:
|
||||
Dict with refund details.
|
||||
|
||||
Raises:
|
||||
PaymentGatewayError: If the intent ID is unknown.
|
||||
"""
|
||||
intent = self._processed_intents.get(payment_intent_id)
|
||||
if not intent:
|
||||
logger.warning(
|
||||
"Mock refund_payment: unknown intent_id=%s",
|
||||
payment_intent_id,
|
||||
)
|
||||
raise PaymentGatewayError(
|
||||
f"Mock payment intent '{payment_intent_id}' not found"
|
||||
)
|
||||
|
||||
refund_amount = float(amount) if amount is not None else intent["amount"]
|
||||
refund_id = f"mock_refund_{uuid.uuid4().hex[:12]}"
|
||||
|
||||
logger.info(
|
||||
"Mock refund: intent=%s amount=%s refund_id=%s",
|
||||
payment_intent_id, refund_amount, refund_id,
|
||||
)
|
||||
|
||||
return {
|
||||
"refunded": True,
|
||||
"refund_id": refund_id,
|
||||
"intent_id": payment_intent_id,
|
||||
"amount": refund_amount,
|
||||
"status": "succeeded",
|
||||
}
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────
|
||||
# Mock-specific Helpers
|
||||
# ──────────────────────────────────────────────────────────────────────────
|
||||
|
||||
def set_mode(self, mode: str) -> None:
|
||||
"""
|
||||
Change the gateway's operating mode at runtime.
|
||||
|
||||
Args:
|
||||
mode: "auto_approve", "simulate_failure", or "simulate_timeout".
|
||||
"""
|
||||
valid_modes = {"auto_approve", "simulate_failure", "simulate_timeout"}
|
||||
if mode not in valid_modes:
|
||||
raise ValueError(
|
||||
f"Invalid mock mode '{mode}'. Valid modes: {valid_modes}"
|
||||
)
|
||||
self.mode = mode
|
||||
logger.info("MockPaymentGateway mode changed to: %s", self.mode)
|
||||
|
||||
def reset(self) -> None:
|
||||
"""Clear all processed intents (useful between tests)."""
|
||||
self._processed_intents.clear()
|
||||
logger.info("MockPaymentGateway reset: all intents cleared")
|
||||
Reference in New Issue
Block a user