pénzügyi modul továbbfejlesztése (csomagkezelés)
This commit is contained in:
@@ -33,37 +33,44 @@ class MockPaymentGateway(BasePaymentGateway):
|
||||
Mock payment gateway for development and testing.
|
||||
|
||||
Modes:
|
||||
- auto_approve (default): All payments succeed immediately.
|
||||
- simulate_redirect (NEW default): Returns a checkout URL for paid tiers.
|
||||
The frontend redirects to the mock checkout page; after user clicks "Pay Now",
|
||||
a webhook callback marks the PaymentIntent as COMPLETED.
|
||||
- auto_approve: All payments succeed immediately (legacy).
|
||||
- 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")
|
||||
gateway = MockPaymentGateway(mode="simulate_redirect")
|
||||
result = await gateway.create_intent(Decimal("100.00"), "EUR")
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
mode: str = "auto_approve",
|
||||
mode: str = "simulate_redirect",
|
||||
failure_message: str = "Mock payment declined (simulated failure)",
|
||||
timeout_seconds: int = 5,
|
||||
base_url: str = "http://localhost:8000",
|
||||
):
|
||||
"""
|
||||
Initialize the mock gateway.
|
||||
|
||||
Args:
|
||||
mode: Operation mode — "auto_approve", "simulate_failure", or "simulate_timeout".
|
||||
mode: Operation mode — "simulate_redirect" (default), "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.
|
||||
base_url: Base URL for generating mock checkout URLs.
|
||||
"""
|
||||
self.mode = mode
|
||||
self.failure_message = failure_message
|
||||
self.timeout_seconds = timeout_seconds
|
||||
self.base_url = base_url
|
||||
self._processed_intents: Dict[str, Dict[str, Any]] = {}
|
||||
|
||||
logger.info(
|
||||
"MockPaymentGateway initialized: mode=%s, timeout=%ds",
|
||||
self.mode, self.timeout_seconds,
|
||||
"MockPaymentGateway initialized: mode=%s, timeout=%ds, base_url=%s",
|
||||
self.mode, self.timeout_seconds, self.base_url,
|
||||
)
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────
|
||||
@@ -80,28 +87,46 @@ class MockPaymentGateway(BasePaymentGateway):
|
||||
"""
|
||||
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".
|
||||
Modes:
|
||||
- simulate_redirect (default): For paid amounts (>0), returns a checkout URL
|
||||
with status "requires_action". The frontend redirects the user to this URL.
|
||||
For $0 amounts, falls through to auto_approve.
|
||||
- auto_approve: Immediately returns a "completed" intent.
|
||||
- simulate_failure: Raises PaymentGatewayError.
|
||||
- simulate_timeout: Logs a warning and returns "processing".
|
||||
|
||||
THOUGHT PROCESS:
|
||||
The simulate_redirect mode bridges the gap between "instant success"
|
||||
and a realistic payment flow. The returned checkout_url points to a
|
||||
mock page served by our own backend. When the user clicks "Pay Now",
|
||||
the mock page POSTs to a webhook callback endpoint that finalizes
|
||||
the PaymentIntent. This allows frontend testing of the full redirect
|
||||
→ callback → success lifecycle without Stripe.
|
||||
|
||||
Args:
|
||||
amount: The payment amount.
|
||||
currency: ISO 4217 currency code (default: EUR).
|
||||
metadata: Optional metadata dict.
|
||||
**kwargs: Additional parameters (ignored in mock).
|
||||
**kwargs: Supports 'base_url' override for the checkout URL base.
|
||||
|
||||
Returns:
|
||||
Dict with mock payment intent details.
|
||||
Dict with mock payment intent details:
|
||||
- simulate_redirect: {id, status: "requires_action", checkout_url, ...}
|
||||
- auto_approve: {id, status: "completed", ...}
|
||||
|
||||
Raises:
|
||||
PaymentGatewayError: In simulate_failure mode.
|
||||
"""
|
||||
intent_id = f"mock_intent_{uuid.uuid4().hex[:12]}"
|
||||
|
||||
# ── Strict console log for audit trail ──────────────────────────────
|
||||
logger.info(
|
||||
"Mock create_intent: id=%s amount=%s %s mode=%s",
|
||||
intent_id, amount, currency, self.mode,
|
||||
"[MOCK_PAYMENT_REQUEST] Initiating transaction with external gateway "
|
||||
"for amount: %s %s, mode=%s, intent_id=%s",
|
||||
amount, currency, self.mode, intent_id,
|
||||
)
|
||||
|
||||
# ── simulate_failure: Always fail ───────────────────────────────────
|
||||
if self.mode == "simulate_failure":
|
||||
logger.warning(
|
||||
"Mock payment FAILURE: intent=%s reason='%s'",
|
||||
@@ -109,6 +134,7 @@ class MockPaymentGateway(BasePaymentGateway):
|
||||
)
|
||||
raise PaymentGatewayError(self.failure_message)
|
||||
|
||||
# ── simulate_timeout: Pretend to hang then return "processing" ──────
|
||||
if self.mode == "simulate_timeout":
|
||||
logger.warning(
|
||||
"Mock payment TIMEOUT simulation: intent=%s delay=%ds",
|
||||
@@ -117,19 +143,59 @@ class MockPaymentGateway(BasePaymentGateway):
|
||||
# In a real scenario we'd sleep; here we just return "processing"
|
||||
# so the caller can handle the pending state.
|
||||
|
||||
# ── simulate_redirect: Return a checkout URL for paid tiers ─────────
|
||||
# For $0 amounts, fall through to auto_approve behaviour.
|
||||
if self.mode == "simulate_redirect" and amount > 0:
|
||||
# Use the base_url from kwargs if provided, otherwise use self.base_url
|
||||
base_url = kwargs.get("base_url", self.base_url)
|
||||
|
||||
# Generate the mock checkout page URL — the user will be redirected
|
||||
# here to complete the payment. After clicking "Pay Now", the mock
|
||||
# page POSTs to /billing/mock-payment/callback which updates the
|
||||
# PaymentIntent from PENDING → COMPLETED.
|
||||
checkout_url = f"{base_url}/api/v1/billing/mock-payment/checkout/{intent_id}"
|
||||
|
||||
intent_data = {
|
||||
"id": intent_id,
|
||||
"status": "requires_action",
|
||||
"amount": float(amount),
|
||||
"currency": currency,
|
||||
"checkout_url": checkout_url,
|
||||
"method": "GET", # Frontend opens this URL in a new tab/window
|
||||
"created_at": datetime.utcnow().isoformat(),
|
||||
"completed_at": None,
|
||||
"metadata": metadata or {},
|
||||
"gateway": "mock",
|
||||
}
|
||||
|
||||
self._processed_intents[intent_id] = intent_data
|
||||
|
||||
logger.info(
|
||||
"[MOCK_PAYMENT_REDIRECT] Checkout URL generated: intent=%s "
|
||||
"checkout_url=%s amount=%s %s",
|
||||
intent_id, checkout_url, amount, currency,
|
||||
)
|
||||
return intent_data
|
||||
|
||||
# ── auto_approve OR $0 amount: Immediate completion ─────────────────
|
||||
now = datetime.utcnow()
|
||||
intent_data = {
|
||||
"id": intent_id,
|
||||
"status": "completed" if self.mode == "auto_approve" else "processing",
|
||||
"status": "completed",
|
||||
"amount": float(amount),
|
||||
"currency": currency,
|
||||
"created_at": now.isoformat(),
|
||||
"completed_at": now.isoformat() if self.mode == "auto_approve" else None,
|
||||
"completed_at": now.isoformat(),
|
||||
"metadata": metadata or {},
|
||||
"gateway": "mock",
|
||||
}
|
||||
|
||||
self._processed_intents[intent_id] = intent_data
|
||||
|
||||
logger.info(
|
||||
"Mock payment AUTO-APPROVED: intent=%s amount=%s %s",
|
||||
intent_id, amount, currency,
|
||||
)
|
||||
return intent_data
|
||||
|
||||
async def verify_payment(
|
||||
@@ -228,9 +294,10 @@ class MockPaymentGateway(BasePaymentGateway):
|
||||
Change the gateway's operating mode at runtime.
|
||||
|
||||
Args:
|
||||
mode: "auto_approve", "simulate_failure", or "simulate_timeout".
|
||||
mode: "simulate_redirect", "auto_approve", "simulate_failure",
|
||||
or "simulate_timeout".
|
||||
"""
|
||||
valid_modes = {"auto_approve", "simulate_failure", "simulate_timeout"}
|
||||
valid_modes = {"simulate_redirect", "auto_approve", "simulate_failure", "simulate_timeout"}
|
||||
if mode not in valid_modes:
|
||||
raise ValueError(
|
||||
f"Invalid mock mode '{mode}'. Valid modes: {valid_modes}"
|
||||
|
||||
Reference in New Issue
Block a user