refactor: separate standalone and managed room configs
- veins → shunts rename - add cfg/standalone/ and cfg/<room>/ structure - remove old data/*.json (moved to cfg/<room>/data/) - update build.py and ctrl scripts
This commit is contained in:
281
artery/shunts/mercadopago/api/routes.py
Normal file
281
artery/shunts/mercadopago/api/routes.py
Normal file
@@ -0,0 +1,281 @@
|
||||
"""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",
|
||||
}
|
||||
Reference in New Issue
Block a user