Integra Battlepay con SumUp (órdenes battlepay_orders)
- pagos_sumup.py: crear_checkout_battlepay(reference,...) y obtener_checkout() - battlepay_view: lista las órdenes PENDING de la cuenta de juego - battlepay_pay_view: crea checkout SumUp para una orden concreta (checkout_reference=reference) - sumup_webhook: marca la orden PAID (valida estado contra la API de SumUp si falta) - plantillas account/battlepay.html y partials/pago_sumup.html - sql/battlepay_sumup.sql (tablas battlepay_orders/battlepay_price) y docs actualizados Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+120
-32
@@ -5,7 +5,7 @@ from django.http import HttpResponse, JsonResponse
|
||||
from django.db import connections
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth import logout
|
||||
from .pagos_sumup import crear_checkout
|
||||
from .pagos_sumup import crear_checkout, crear_checkout_battlepay, obtener_checkout
|
||||
from django import forms
|
||||
import stripe
|
||||
from .pagos_stripe import create_checkout_session
|
||||
@@ -2979,44 +2979,132 @@ def novawow_players_view(request):
|
||||
})
|
||||
|
||||
|
||||
def pagar_sumup(request):
|
||||
"""Genera un pago de prueba y lo muestra en el widget"""
|
||||
# ---------------------------------------------------------------------------
|
||||
# Battlepay (tienda nativa del cliente) pagado vía SumUp
|
||||
#
|
||||
# Flujo: el worldserver crea una orden PENDING en `battlepay_orders` cuando el
|
||||
# jugador pulsa "Comprar" en la tienda del cliente y el producto tiene precio en
|
||||
# `battlepay_price`. El portal muestra esas órdenes, cobra por SumUp y las marca
|
||||
# PAID; el worldserver las entrega y las marca DELIVERED.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
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_id = crear_checkout(10.00) # Pago de 10 EUR
|
||||
pedido = Pedido.objects.create(email_cliente="cliente@example.com", monto=10.00)
|
||||
return render(request, "account/pago_sumup.html", {"checkout_id": checkout_id, "pedido": pedido})
|
||||
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', {'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):
|
||||
"""Recibe el webhook de SumUp y actualiza la base de datos"""
|
||||
if request.method == "POST":
|
||||
"""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:
|
||||
data = json.loads(request.body)
|
||||
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)
|
||||
|
||||
# Verificar si el evento es un pago exitoso
|
||||
if data.get("event_type") == "transaction.successful":
|
||||
transaction_id = data.get("data", {}).get("transaction_id")
|
||||
amount = data.get("data", {}).get("amount")
|
||||
email_cliente = data.get("data", {}).get("customer_email")
|
||||
pagado = status in ('PAID', 'SUCCESSFUL') or event_type == 'transaction.successful'
|
||||
|
||||
# Buscar el pedido y actualizarlo como pagado
|
||||
pedido = Pedido.objects.filter(email_cliente=email_cliente, monto=amount).first()
|
||||
if pedido:
|
||||
pedido.transaction_id = transaction_id
|
||||
pedido.estado = "Pagado"
|
||||
pedido.save()
|
||||
return JsonResponse({"message": "Pago actualizado"}, status=200)
|
||||
else:
|
||||
return JsonResponse({"error": "Pedido no encontrado"}, status=404)
|
||||
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({"error": "Evento no reconocido"}, status=400)
|
||||
|
||||
except json.JSONDecodeError:
|
||||
return JsonResponse({"error": "JSON inválido"}, status=400)
|
||||
|
||||
return JsonResponse({"error": "Método no permitido"}, status=405)
|
||||
return JsonResponse({"message": "Evento ignorado"}, status=200)
|
||||
|
||||
Reference in New Issue
Block a user