282 lines
7.9 KiB
Python
282 lines
7.9 KiB
Python
"""API routes for MercadoPago (MOCK) vein - Mock MercadoPago API for testing."""
|
|
|
|
import asyncio
|
|
import random
|
|
from fastapi import APIRouter, HTTPException, Query, Header
|
|
from fastapi.responses import JSONResponse
|
|
from typing import Optional, List, Dict, Any
|
|
from pydantic import BaseModel
|
|
|
|
# Import datagen from ward/tools
|
|
import sys
|
|
from pathlib import Path
|
|
ward_tools_path = Path(__file__).parent.parent.parent.parent.parent / "ward" / "tools"
|
|
sys.path.insert(0, str(ward_tools_path))
|
|
|
|
from datagen.mercadopago import MercadoPagoDataGenerator
|
|
|
|
from ..core.config import settings
|
|
|
|
router = APIRouter()
|
|
|
|
# In-memory storage for mock data
|
|
MOCK_DB = {
|
|
"preferences": {},
|
|
"payments": {},
|
|
"merchant_orders": {},
|
|
"tokens": {},
|
|
}
|
|
|
|
|
|
# Request/Response Models
|
|
class PreferenceItem(BaseModel):
|
|
title: str
|
|
quantity: int
|
|
unit_price: float
|
|
currency_id: str = "ARS"
|
|
|
|
|
|
class CreatePreferenceRequest(BaseModel):
|
|
items: List[PreferenceItem]
|
|
external_reference: Optional[str] = None
|
|
back_urls: Optional[Dict[str, str]] = None
|
|
notification_url: Optional[str] = None
|
|
auto_return: str = "approved"
|
|
|
|
|
|
class CreatePaymentRequest(BaseModel):
|
|
transaction_amount: float
|
|
description: str
|
|
payment_method_id: str
|
|
payer: Dict[str, Any]
|
|
application_fee: Optional[float] = None
|
|
token: Optional[str] = None
|
|
installments: Optional[int] = 1
|
|
issuer_id: Optional[str] = None
|
|
|
|
|
|
async def _mock_delay():
|
|
"""Add realistic delay if enabled."""
|
|
if settings.enable_random_delays:
|
|
delay_ms = random.randint(settings.min_delay_ms, settings.max_delay_ms)
|
|
await asyncio.sleep(delay_ms / 1000)
|
|
|
|
|
|
def _maybe_error():
|
|
"""Randomly raise an error based on error_rate."""
|
|
if random.random() < settings.error_rate:
|
|
raise HTTPException(500, "Mock error: Simulated MercadoPago failure")
|
|
|
|
|
|
@router.get("/health")
|
|
async def health():
|
|
"""Health check endpoint."""
|
|
return {
|
|
"status": "ok",
|
|
"vein": "MercadoPago\n(MOCK)",
|
|
"message": "Mock MercadoPago API for testing",
|
|
"_mock": "MercadoPago",
|
|
}
|
|
|
|
|
|
@router.post("/v1/preferences")
|
|
async def create_preference(request: CreatePreferenceRequest):
|
|
"""Create a Checkout Pro preference (payment link)."""
|
|
await _mock_delay()
|
|
_maybe_error()
|
|
|
|
# Calculate total from items
|
|
total = sum(item.unit_price * item.quantity for item in request.items)
|
|
|
|
# Generate preference
|
|
preference = MercadoPagoDataGenerator.preference(
|
|
description=request.items[0].title if request.items else "Payment",
|
|
total=total,
|
|
external_reference=request.external_reference,
|
|
)
|
|
|
|
# Store in mock DB
|
|
MOCK_DB["preferences"][preference["id"]] = preference
|
|
|
|
return preference
|
|
|
|
|
|
@router.get("/v1/preferences/{preference_id}")
|
|
async def get_preference(preference_id: str):
|
|
"""Get preference by ID."""
|
|
await _mock_delay()
|
|
_maybe_error()
|
|
|
|
preference = MOCK_DB["preferences"].get(preference_id)
|
|
if not preference:
|
|
raise HTTPException(404, {"message": "Preference not found", "error": "not_found", "status": 404})
|
|
|
|
return preference
|
|
|
|
|
|
@router.post("/v1/payments")
|
|
async def create_payment(
|
|
request: CreatePaymentRequest,
|
|
x_idempotency_key: Optional[str] = Header(None),
|
|
):
|
|
"""Create a payment (Checkout API/Bricks)."""
|
|
await _mock_delay()
|
|
_maybe_error()
|
|
|
|
# Use configured default status or random
|
|
status = settings.default_payment_status
|
|
if status not in ["approved", "pending", "rejected"]:
|
|
status = random.choice(["approved", "pending", "rejected"])
|
|
|
|
# Generate payment
|
|
payment = MercadoPagoDataGenerator.payment(
|
|
transaction_amount=request.transaction_amount,
|
|
description=request.description,
|
|
status=status,
|
|
application_fee=request.application_fee,
|
|
)
|
|
|
|
# Override with request payment method
|
|
payment["payment_method_id"] = request.payment_method_id
|
|
payment["payer"] = request.payer
|
|
|
|
# Store in mock DB
|
|
MOCK_DB["payments"][payment["id"]] = payment
|
|
|
|
return payment
|
|
|
|
|
|
@router.get("/v1/payments/{payment_id}")
|
|
async def get_payment(payment_id: int):
|
|
"""Get payment details."""
|
|
await _mock_delay()
|
|
_maybe_error()
|
|
|
|
payment = MOCK_DB["payments"].get(payment_id)
|
|
if not payment:
|
|
raise HTTPException(404, {"message": "Payment not found", "error": "not_found", "status": 404})
|
|
|
|
return payment
|
|
|
|
|
|
@router.get("/v1/merchant_orders/{order_id}")
|
|
async def get_merchant_order(order_id: int):
|
|
"""Get merchant order details."""
|
|
await _mock_delay()
|
|
_maybe_error()
|
|
|
|
order = MOCK_DB["merchant_orders"].get(order_id)
|
|
if not order:
|
|
# Generate on-the-fly if not found
|
|
order = MercadoPagoDataGenerator.merchant_order(
|
|
preference_id=f"pref_{order_id}",
|
|
total=100000,
|
|
paid_amount=100000,
|
|
)
|
|
order["id"] = order_id
|
|
MOCK_DB["merchant_orders"][order_id] = order
|
|
|
|
return order
|
|
|
|
|
|
@router.post("/oauth/token")
|
|
async def oauth_token(
|
|
grant_type: str = "refresh_token",
|
|
client_id: str = None,
|
|
client_secret: str = None,
|
|
refresh_token: str = None,
|
|
code: str = None,
|
|
):
|
|
"""OAuth token exchange/refresh."""
|
|
await _mock_delay()
|
|
_maybe_error()
|
|
|
|
if grant_type == "refresh_token":
|
|
if not refresh_token:
|
|
raise HTTPException(400, {"error": "invalid_request", "error_description": "refresh_token is required"})
|
|
|
|
# Generate new tokens
|
|
token_data = MercadoPagoDataGenerator.oauth_token()
|
|
MOCK_DB["tokens"][token_data["access_token"]] = token_data
|
|
return token_data
|
|
|
|
elif grant_type == "authorization_code":
|
|
if not code:
|
|
raise HTTPException(400, {"error": "invalid_request", "error_description": "code is required"})
|
|
|
|
# Generate tokens from code
|
|
token_data = MercadoPagoDataGenerator.oauth_token()
|
|
MOCK_DB["tokens"][token_data["access_token"]] = token_data
|
|
return token_data
|
|
|
|
else:
|
|
raise HTTPException(400, {"error": "unsupported_grant_type"})
|
|
|
|
|
|
@router.post("/mock/webhook")
|
|
async def simulate_webhook(
|
|
topic: str = "payment",
|
|
resource_id: str = None,
|
|
):
|
|
"""Simulate a webhook notification (for testing)."""
|
|
await _mock_delay()
|
|
|
|
if not resource_id:
|
|
raise HTTPException(400, "resource_id is required")
|
|
|
|
notification = MercadoPagoDataGenerator.webhook_notification(
|
|
topic=topic,
|
|
resource_id=resource_id,
|
|
)
|
|
|
|
return notification
|
|
|
|
|
|
@router.get("/mock/reset")
|
|
async def reset_mock_db():
|
|
"""Reset the mock database."""
|
|
MOCK_DB["preferences"].clear()
|
|
MOCK_DB["payments"].clear()
|
|
MOCK_DB["merchant_orders"].clear()
|
|
MOCK_DB["tokens"].clear()
|
|
|
|
return {
|
|
"message": "Mock database reset",
|
|
"_mock": "MercadoPago",
|
|
}
|
|
|
|
|
|
@router.get("/mock/stats")
|
|
async def mock_stats():
|
|
"""Get mock database statistics."""
|
|
return {
|
|
"preferences": len(MOCK_DB["preferences"]),
|
|
"payments": len(MOCK_DB["payments"]),
|
|
"merchant_orders": len(MOCK_DB["merchant_orders"]),
|
|
"tokens": len(MOCK_DB["tokens"]),
|
|
"_mock": "MercadoPago",
|
|
}
|
|
|
|
|
|
@router.post("/mock/config")
|
|
async def update_mock_config(
|
|
default_payment_status: Optional[str] = None,
|
|
error_rate: Optional[float] = None,
|
|
):
|
|
"""Update mock configuration (for testing different scenarios)."""
|
|
if default_payment_status:
|
|
if default_payment_status not in ["approved", "pending", "rejected", "in_process", "cancelled"]:
|
|
raise HTTPException(400, "Invalid payment status")
|
|
settings.default_payment_status = default_payment_status
|
|
|
|
if error_rate is not None:
|
|
if not (0 <= error_rate <= 1):
|
|
raise HTTPException(400, "error_rate must be between 0 and 1")
|
|
settings.error_rate = error_rate
|
|
|
|
return {
|
|
"default_payment_status": settings.default_payment_status,
|
|
"error_rate": settings.error_rate,
|
|
"_mock": "MercadoPago",
|
|
}
|