b3184fbdfe
El módulo monolítico (~3200 líneas) se convierte en un paquete cuyo __init__.py re-exporta todas las vistas, de modo que `views.X`, `from home.views import X` y la cadena 'home.views.not_found' (handler404) siguen funcionando sin tocar urls.py. Módulos por sección: _base (imports comunes + require_paid_stripe), auth, pages, account, recruit, characters, store, points, guild, history, players y payments. Cada submódulo hereda los imports con `from ._base import *`; las dependencias entre secciones se resuelven con imports explícitos (guild->auth, recruit/history->account). Se eliminan dos definiciones duplicadas que quedaban muertas al cargar: - get_character_image (primera copia, idéntica a la efectiva) - store_novawow_success_view (stub sin SOAP, pisado por la versión real) Validado con `manage.py check` (0 issues), resolución de las 100 rutas y un chequeo estático de nombres no definidos por módulo. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
133 lines
5.5 KiB
Python
133 lines
5.5 KiB
Python
from ._base import *
|
|
|
|
|
|
@csrf_exempt
|
|
def stripe_webhook(request):
|
|
payload = request.body
|
|
sig_header = request.META.get("HTTP_STRIPE_SIGNATURE")
|
|
|
|
try:
|
|
event = stripe.Webhook.construct_event(payload, sig_header, settings.STRIPE_WEBHOOK_SECRET)
|
|
|
|
if event["type"] == "checkout.session.completed":
|
|
session = event["data"]["object"]
|
|
session_id = session["id"]
|
|
stripe_ip = request.META.get("REMOTE_ADDR") # IP detectada
|
|
|
|
# 🔹 Actualizar la IP de Stripe en el log
|
|
StripeLog.objects.filter(session_id=session_id).update(stripe_ip=stripe_ip, timestamp=now())
|
|
|
|
return JsonResponse({"success": True})
|
|
|
|
except stripe.error.SignatureVerificationError:
|
|
return JsonResponse({"success": False, "message": "Firma de webhook no válida."}, status=400)
|
|
def get_pending_battlepay_orders(account_id):
|
|
"""Órdenes Battlepay PENDING de una cuenta de juego."""
|
|
with connections['acore_auth'].cursor() as cursor:
|
|
cursor.execute(
|
|
"SELECT id, reference, product_id, product_name, price_eur, status, created_at "
|
|
"FROM battlepay_orders WHERE account_id = %s AND status = 'PENDING' "
|
|
"ORDER BY created_at DESC",
|
|
[account_id],
|
|
)
|
|
cols = [c[0] for c in cursor.description]
|
|
return [dict(zip(cols, row)) for row in cursor.fetchall()]
|
|
def _mark_order_paid_by_reference(reference):
|
|
"""Marca una orden como PAID solo si estaba PENDING. Devuelve filas afectadas."""
|
|
with connections['acore_auth'].cursor() as cursor:
|
|
cursor.execute(
|
|
"UPDATE battlepay_orders SET status = 'PAID', paid_at = UNIX_TIMESTAMP() "
|
|
"WHERE reference = %s AND status = 'PENDING'",
|
|
[reference],
|
|
)
|
|
return cursor.rowcount
|
|
def battlepay_view(request):
|
|
"""Lista las órdenes Battlepay pendientes de la cuenta de juego seleccionada."""
|
|
account_id = request.session.get('account_id')
|
|
if not account_id:
|
|
if request.session.get('bnet_id'):
|
|
return redirect('select-account')
|
|
return redirect('login')
|
|
|
|
orders = get_pending_battlepay_orders(account_id)
|
|
return render(request, 'account/battlepay.html', {
|
|
'orders': orders,
|
|
'username': request.session.get('username'),
|
|
})
|
|
def battlepay_pay_view(request):
|
|
"""Crea un checkout de SumUp para pagar una orden Battlepay pendiente."""
|
|
account_id = request.session.get('account_id')
|
|
if not account_id:
|
|
return redirect('login')
|
|
if request.method != 'POST':
|
|
return redirect('battlepay')
|
|
|
|
reference = request.POST.get('reference', '').strip()
|
|
|
|
# Validar que la orden pertenece a la cuenta y sigue pendiente
|
|
with connections['acore_auth'].cursor() as cursor:
|
|
cursor.execute(
|
|
"SELECT product_name, price_eur FROM battlepay_orders "
|
|
"WHERE reference = %s AND account_id = %s AND status = 'PENDING'",
|
|
[reference, account_id],
|
|
)
|
|
row = cursor.fetchone()
|
|
|
|
if not row:
|
|
return render(request, 'account/pago_sumup.html', {'error': 'Orden no encontrada o ya procesada.'})
|
|
|
|
product_name, price_eur = row
|
|
try:
|
|
checkout = crear_checkout_battlepay(reference, price_eur, product_name)
|
|
except Exception as e:
|
|
return render(request, 'account/pago_sumup.html', {'error': str(e)})
|
|
|
|
return render(request, 'account/pago_sumup.html', {
|
|
'checkout_id': checkout.get('id'),
|
|
'reference': reference,
|
|
'product_name': product_name,
|
|
'price_eur': price_eur,
|
|
})
|
|
def pagar_sumup(request):
|
|
"""Alias de compatibilidad: envía al listado de órdenes Battlepay."""
|
|
return redirect('battlepay')
|
|
@csrf_exempt
|
|
def sumup_webhook(request):
|
|
"""Webhook de SumUp: marca la orden Battlepay como PAID al completarse el pago."""
|
|
if request.method != "POST":
|
|
return JsonResponse({"error": "Método no permitido"}, status=405)
|
|
|
|
try:
|
|
data = json.loads(request.body or b'{}')
|
|
except json.JSONDecodeError:
|
|
return JsonResponse({"error": "JSON inválido"}, status=400)
|
|
|
|
payload = data.get('payload') or data.get('data') or {}
|
|
reference = data.get('checkout_reference') or payload.get('checkout_reference')
|
|
status = (data.get('status') or payload.get('status') or '').upper()
|
|
checkout_id = data.get('id') or payload.get('checkout_id') or payload.get('id')
|
|
event_type = (data.get('event_type') or '').lower()
|
|
|
|
# Si falta la referencia o el estado, consultarlo a la API de SumUp
|
|
if (not reference or status not in ('PAID', 'SUCCESSFUL')) and checkout_id:
|
|
try:
|
|
checkout = obtener_checkout(checkout_id)
|
|
reference = reference or checkout.get('checkout_reference')
|
|
status = (checkout.get('status') or status).upper()
|
|
except Exception as e:
|
|
logger.error("SumUp webhook: no se pudo consultar el checkout %s: %s", checkout_id, e)
|
|
|
|
pagado = status in ('PAID', 'SUCCESSFUL') or event_type == 'transaction.successful'
|
|
|
|
if reference and pagado:
|
|
try:
|
|
afectadas = _mark_order_paid_by_reference(reference)
|
|
except Exception as e:
|
|
logger.error("SumUp webhook: error actualizando la orden %s: %s", reference, e)
|
|
return JsonResponse({"error": "Error interno"}, status=500)
|
|
if afectadas:
|
|
return JsonResponse({"message": "Orden marcada como pagada"}, status=200)
|
|
return JsonResponse({"message": "Orden no pendiente o inexistente"}, status=200)
|
|
|
|
return JsonResponse({"message": "Evento ignorado"}, status=200)
|