From 41c3c6ae91cf32900975979ee6cb912db655629e Mon Sep 17 00:00:00 2001 From: adevopg <133045315+adevopg@users.noreply.github.com> Date: Sat, 15 Feb 2025 23:36:48 +0100 Subject: [PATCH] Implementacion Inicial de la Tienda --- ..._storecategory_parent_category_and_more.py | 49 +++++ home/models.py | 31 ++-- .../partials/store/store_novawow.html | 149 ++++++++++++++++ .../partials/store/store_novawow_cancel.html | 0 .../partials/store/store_novawow_success.html | 0 home/templates/partials/store_novawow.html | 34 ---- .../{account => store}/store_novawow.html | 2 +- .../templates/store/store_novawow_cancel.html | 9 + .../store/store_novawow_success.html | 9 + home/urls.py | 3 + home/views.py | 168 ++++++++++-------- .../nw-js-handlers/store_novawow_response.js | 91 ++++++++++ 12 files changed, 420 insertions(+), 125 deletions(-) create mode 100644 home/migrations/0008_category_remove_storecategory_parent_category_and_more.py create mode 100644 home/templates/partials/store/store_novawow.html create mode 100644 home/templates/partials/store/store_novawow_cancel.html create mode 100644 home/templates/partials/store/store_novawow_success.html delete mode 100644 home/templates/partials/store_novawow.html rename home/templates/{account => store}/store_novawow.html (82%) create mode 100644 home/templates/store/store_novawow_cancel.html create mode 100644 home/templates/store/store_novawow_success.html create mode 100644 static/nw-themes/nw-ryu/nw-js-handlers/store_novawow_response.js diff --git a/home/migrations/0008_category_remove_storecategory_parent_category_and_more.py b/home/migrations/0008_category_remove_storecategory_parent_category_and_more.py new file mode 100644 index 0000000..397991b --- /dev/null +++ b/home/migrations/0008_category_remove_storecategory_parent_category_and_more.py @@ -0,0 +1,49 @@ +# Generated by Django 5.1.4 on 2025-02-15 18:04 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('home', '0007_maintenancemode'), + ] + + operations = [ + migrations.CreateModel( + name='Category', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100, unique=True)), + ], + ), + migrations.RemoveField( + model_name='storecategory', + name='parent_category', + ), + migrations.RemoveField( + model_name='storeitem', + name='category', + ), + migrations.CreateModel( + name='Item', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=200)), + ('price', models.DecimalField(decimal_places=2, max_digits=10)), + ('item_id', models.IntegerField(unique=True)), + ('image_url', models.URLField()), + ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='home.category')), + ], + ), + migrations.DeleteModel( + name='Cart', + ), + migrations.DeleteModel( + name='StoreCategory', + ), + migrations.DeleteModel( + name='StoreItem', + ), + ] diff --git a/home/models.py b/home/models.py index 75f7890..91adf0e 100644 --- a/home/models.py +++ b/home/models.py @@ -62,34 +62,25 @@ class TransferPrice(models.Model): def __str__(self): return f"Precio de transferencia: {self.price} EUR" - - -class StoreCategory(models.Model): - name = models.CharField(max_length=100) - parent_category = models.ForeignKey('self', null=True, blank=True, on_delete=models.CASCADE) + + +class Category(models.Model): + name = models.CharField(max_length=100, unique=True) def __str__(self): return self.name -class StoreItem(models.Model): + +class Item(models.Model): + category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name="items") name = models.CharField(max_length=200) - item_id = models.IntegerField() - category = models.ForeignKey(StoreCategory, on_delete=models.CASCADE) - image_url = models.URLField() - price = models.DecimalField(max_digits=6, decimal_places=2) # Precio en euros + price = models.DecimalField(max_digits=10, decimal_places=2) # Precio en euros + item_id = models.IntegerField(unique=True) # ID del ítem en el juego + image_url = models.URLField() # URL de la imagen del ítem def __str__(self): return self.name - -class Cart(models.Model): - username = models.CharField(max_length=150) # Para almacenar el nombre de usuario - item = models.ForeignKey(StoreItem, on_delete=models.CASCADE) - added_at = models.DateTimeField(auto_now_add=True) - - def total_price(self): - return self.item.price - - + class RenamePrice(models.Model): price = models.DecimalField(max_digits=5, decimal_places=2, default=2.00) diff --git a/home/templates/partials/store/store_novawow.html b/home/templates/partials/store/store_novawow.html new file mode 100644 index 0000000..403c275 --- /dev/null +++ b/home/templates/partials/store/store_novawow.html @@ -0,0 +1,149 @@ + +
+
+
+

Tienda de {{ NOMBRE_SERVIDOR }}

+
+ +
+ + + +
+ + +
+ + +
+ + +
+ + +
+

Carrito de Compras

+
+
+
    +

    Total: 0 monedas

    +

    Total con IVA (21%): 0 monedas

    + +
    +
    +
    +
    +
    + + diff --git a/home/templates/partials/store/store_novawow_cancel.html b/home/templates/partials/store/store_novawow_cancel.html new file mode 100644 index 0000000..e69de29 diff --git a/home/templates/partials/store/store_novawow_success.html b/home/templates/partials/store/store_novawow_success.html new file mode 100644 index 0000000..e69de29 diff --git a/home/templates/partials/store_novawow.html b/home/templates/partials/store_novawow.html deleted file mode 100644 index e99921d..0000000 --- a/home/templates/partials/store_novawow.html +++ /dev/null @@ -1,34 +0,0 @@ -
    -
    - {% for category in categories %} -

    {{ category.name }}

    - - {% endfor %} -
    - -
    -

    Carrito

    - -

    Total: {{ total_price }} €

    - - - -
    -
    diff --git a/home/templates/account/store_novawow.html b/home/templates/store/store_novawow.html similarity index 82% rename from home/templates/account/store_novawow.html rename to home/templates/store/store_novawow.html index e0d6ad6..80c6f69 100644 --- a/home/templates/account/store_novawow.html +++ b/home/templates/store/store_novawow.html @@ -3,7 +3,7 @@ {% include 'partials/head.html' %} {% include 'partials/header.html' %} {% include 'partials/video.html' %} - {% include 'partials/store_novawow.html' %} + {% include 'partials/store/store_novawow.html' %} {% include 'partials/social.html' %} {% include 'partials/footer.html' %} {% include 'partials/final.html' %} \ No newline at end of file diff --git a/home/templates/store/store_novawow_cancel.html b/home/templates/store/store_novawow_cancel.html new file mode 100644 index 0000000..094b5ec --- /dev/null +++ b/home/templates/store/store_novawow_cancel.html @@ -0,0 +1,9 @@ + + + {% include 'partials/head.html' %} + {% include 'partials/header.html' %} + {% include 'partials/video.html' %} + {% include 'partials/store/store_novawow_cancel.html' %} + {% include 'partials/social.html' %} + {% include 'partials/footer.html' %} + {% include 'partials/final.html' %} \ No newline at end of file diff --git a/home/templates/store/store_novawow_success.html b/home/templates/store/store_novawow_success.html new file mode 100644 index 0000000..914d1ea --- /dev/null +++ b/home/templates/store/store_novawow_success.html @@ -0,0 +1,9 @@ + + + {% include 'partials/head.html' %} + {% include 'partials/header.html' %} + {% include 'partials/video.html' %} + {% include 'partials/store/store_novawow_success.html' %} + {% include 'partials/social.html' %} + {% include 'partials/footer.html' %} + {% include 'partials/final.html' %} \ No newline at end of file diff --git a/home/urls.py b/home/urls.py index fb6ad81..adb5726 100644 --- a/home/urls.py +++ b/home/urls.py @@ -63,7 +63,10 @@ urlpatterns = [ path('transfer-character/' , views.transfer_character_view, name='transfer-character'), path('transfer-success/' , views.transfer_success_view, name='transfer-success'), path('transfer-cancel/' , views.transfer_cancel_view, name='transfer-cancel'), + path('store-novawow/buscar/categoria//', views.store_novawow_buscar_categoria_view, name='store_novawow/buscar/categoria/'), path('store-novawow/' , views.store_novawow_view, name='store-novawow'), + path('store-novawow/success', views.store_novawow_success_view, name='store-novawow/success'), + path('store-novawow/cancel', views.store_novawow_cancel_view, name='store-novawow/cancel'), path('maintenance/', views.maintenance_view, name='maintenance'), diff --git a/home/views.py b/home/views.py index c6c2bbb..b032fec 100644 --- a/home/views.py +++ b/home/views.py @@ -4,6 +4,7 @@ import gmpy2 import binascii import os import re +import json from django.shortcuts import render, redirect from django.urls import reverse from django.http import HttpResponse, JsonResponse @@ -12,7 +13,7 @@ from django.contrib import messages from django.contrib.auth import logout from django import forms from .pagos_stripe import create_checkout_session -from .models import Noticia, ClienteCategoria, ServerSelection, RecruitAFriend, DownloadClientPage, ContentCreator, RecruitReward, ClaimedReward, AccountActivation, SecurityToken, GuildRenameSettings, VoteSite, VoteLog, HomeApiPoints, UnstuckHistory, ReviveHistory, RenamePrice, CustomizePrice, ChangeRacePrice, ChangeFactionPrice, LevelUpPrice, GoldPrice, TransferPrice, StoreCategory, StoreItem, Cart +from .models import Noticia, ClienteCategoria, ServerSelection, RecruitAFriend, DownloadClientPage, ContentCreator, RecruitReward, ClaimedReward, AccountActivation, SecurityToken, GuildRenameSettings, VoteSite, VoteLog, HomeApiPoints, UnstuckHistory, ReviveHistory, RenamePrice, CustomizePrice, ChangeRacePrice, ChangeFactionPrice, LevelUpPrice, GoldPrice, TransferPrice, Category, Item from django.views.decorators.csrf import csrf_exempt import logging from datetime import datetime, timedelta @@ -1164,7 +1165,7 @@ def rename_character_view(request): price_obj = RenamePrice.objects.first() price = price_obj.price if price_obj else 2.00 - # Ejecutar el comando SOAP + # Ejecutar el comando SOAP De prueba haber si el servidor funciona antes de proceder al pago command = f".server info" response = execute_soap_command(command) @@ -1723,6 +1724,8 @@ def transfer_character_view(request): return render(request, 'account/transfer_character.html', {'characters': characters, 'price': price}) + + def transfer_success_view(request): transfer_data = request.session.get('transfer_data') if not transfer_data: @@ -1745,88 +1748,113 @@ def transfer_success_view(request): def transfer_cancel_view(request): return render(request, 'account/transfer_cancel.html') - - + + + +from decimal import Decimal + +from django.http import JsonResponse + +def store_novawow_success_view(request): + return render(request, 'store/store_novawow_success.html') + +def store_novawow_cancel_view(request): + return render(request, 'store/store_novawow_cancel.html') + + def store_novawow_view(request): username = request.session.get('username') if not username: return redirect('login') + + category_name = request.GET.get("category") - categories = StoreCategory.objects.filter(parent_category=None) - items = StoreItem.objects.all() - cart_items = Cart.objects.filter(username=username) + # Obtener personajes del usuario desde la base de datos acore_characters + with connections['acore_characters'].cursor() as cursor: + cursor.execute(""" + SELECT name + FROM characters + WHERE account = ( + SELECT id + FROM acore_auth.account + WHERE username = %s + ) + """, [username]) + characters = [row[0] for row in cursor.fetchall()] - total_price = sum([item.total_price() for item in cart_items]) + # Obtener categorías e ítems + categories = Category.objects.prefetch_related("items").all() + store_data = {category.name: list(category.items.values()) for category in categories} + + # Si se está solicitando una categoría específica, devolver solo los ítems de esa categoría + if category_name: + items_in_category = Category.objects.get(name=category_name).items.values() + return JsonResponse({'success': True, 'items': list(items_in_category)}) - return render(request, 'account/store_novawow.html', { + # Si el carrito está en la sesión + carrito = request.session.get('carrito', []) + total_price = sum(item['price'] for item in carrito) * Decimal('1.21') # Incluir IVA + + if request.method == 'POST': + # Obtener datos del body + data = json.loads(request.body) + character_name = data.get('character') + + if not character_name: + return JsonResponse({'success': False, 'message': 'Por favor, selecciona un personaje.'}) + + # Verificar si el personaje pertenece al usuario + if character_name not in characters: + return JsonResponse({'success': False, 'message': 'No tienes permiso para renombrar este personaje.'}) + + # Crear sesión de pago con Stripe + success_url = request.build_absolute_uri(reverse('store-novawow/success')) + cancel_url = request.build_absolute_uri(reverse('store-novawow/cancel')) + stripe_response = create_checkout_session(total_price, success_url=success_url, cancel_url=cancel_url) + + if stripe_response["success"]: + request.session['store_novawow'] = character_name + return JsonResponse({ + 'success': True, + 'session_id': stripe_response["session_id"], + 'stripe_public_key': settings.STRIPE_PUBLIC_KEY + }) + else: + return JsonResponse({'success': False, 'message': f'Error al iniciar el pago: {stripe_response["error"]}'}) + + return render(request, 'store/store_novawow.html', { + 'characters': characters, + 'store_data': store_data, 'categories': categories, - 'items': items, - 'cart_items': cart_items, - 'total_price': total_price, - }) - -def add_to_cart(request): - username = request.session.get('username') - if not username: - return redirect('login') - - if request.method == 'POST': - item_id = request.POST.get('item_id') - item = StoreItem.objects.get(id=item_id) - Cart.objects.create(username=username, item=item) - return JsonResponse({'success': True, 'message': f'{item.name} añadido al carrito.'}) - - return JsonResponse({'success': False, 'message': 'Error al añadir al carrito.'}) - -def remove_from_cart(request): - username = request.session.get('username') - if not username: - return redirect('login') - - if request.method == 'POST': - cart_id = request.POST.get('cart_id') - Cart.objects.filter(id=cart_id, username=username).delete() - return JsonResponse({'success': True, 'message': 'Artículo eliminado del carrito.'}) - - return JsonResponse({'success': False, 'message': 'Error al eliminar del carrito.'}) - -def checkout_view(request): - username = request.session.get('username') - if not username: - return redirect('login') - - cart_items = Cart.objects.filter(username=username) - total_price = sum([item.total_price() for item in cart_items]) - - success_url = request.build_absolute_uri(reverse('checkout_success')) - cancel_url = request.build_absolute_uri(reverse('checkout_cancel')) - stripe_response = create_checkout_session(total_price, success_url, cancel_url) - - if stripe_response["success"]: - return JsonResponse({'success': True, 'session_id': stripe_response["session_id"]}) + 'total_price': total_price + }) - return JsonResponse({'success': False, 'message': 'Error al procesar el pago.'}) +def store_novawow_success_view(request): + # Procesar el comando SOAP tras el pago exitoso + character_name = request.session.get('store_novawow') + if not character_name: + return redirect('store-novawow') -def checkout_success(request): - username = request.session.get('username') - if not username: - return redirect('login') + command = f".send item {character_name}" + response = execute_soap_command(command) - cart_items = Cart.objects.filter(username=username) + if response is None or response.strip() == "": + message = f"El personaje {character_name} se a anadido los items de la compra." + request.session.pop('store_novawow', None) # Limpiar la sesión + return render(request, 'store/store_success.html', {'message': message}) + else: + return render(request, 'store/store_success.html', {'message': f"Error: {response}"}) - for cart_item in cart_items: - command = f".send item {cart_item.item.item_id} {cart_item.item.name} {username}" - execute_soap_command(command) - cart_item.delete() - return render(request, 'account/checkout_success.html', {'message': 'Compra procesada con éxito.'}) +def store_novawow_buscar_categoria_view(request, categoria): + try: + # Buscar la categoría desde el parámetro de la URL + category = Category.objects.get(name=categoria) + items = category.items.all().values("id", "name", "price", "image_url") + return JsonResponse({"success": True, "items": list(items)}) + except Category.DoesNotExist: + return JsonResponse({"success": False, "message": "Categoría no encontrada"}, status=404) -def checkout_cancel(request): - username = request.session.get('username') - if not username: - return redirect('login') - - return render(request, 'account/checkout_cancel.html', {'message': 'Compra cancelada.'}) def revive_character_view(request): username = request.session.get('username') diff --git a/static/nw-themes/nw-ryu/nw-js-handlers/store_novawow_response.js b/static/nw-themes/nw-ryu/nw-js-handlers/store_novawow_response.js new file mode 100644 index 0000000..ced2756 --- /dev/null +++ b/static/nw-themes/nw-ryu/nw-js-handlers/store_novawow_response.js @@ -0,0 +1,91 @@ +$(document).ready(function () { + $("#comprar").on("click", async function (e) { + e.preventDefault(); + $("#store-response").empty(); + + let button = $(this); + let originalText = button.html(); + button.html("Procesando pago..."); + button.prop("disabled", true); + + let personaje = $("#personaje").val(); + if (!personaje) { + $("#store-response").append('Selecciona un personaje antes de comprar.').hide().slideDown(); + button.html(originalText); + button.prop("disabled", false); + return; + } + + let carrito = []; + $("#carrito li").each(function () { + let itemData = $(this).data("item"); + carrito.push({ + item_id: itemData.item_id, + name: itemData.name, + price: itemData.price + }); + }); + + if (carrito.length === 0) { + $("#store-response").append('El carrito está vacío.').hide().slideDown(); + button.html(originalText); + button.prop("disabled", false); + return; + } + + // Realizar la solicitud para crear la sesión de pago en el backend + try { + const response = await fetch("{% url 'create_checkout_session' %}", { + method: "POST", + body: new URLSearchParams({ + personaje: personaje, + carrito: JSON.stringify(carrito) + }) + }); + + const data = await response.json(); + if (data.success) { + window.location.href = data.stripe_url; // Redirigir a la página de pago de Stripe + } else { + $("#store-response").append(`${data.message}`).hide().slideDown(); + } + } catch (error) { + console.error("Error al procesar el pago:", error); + $("#store-response").append('Error al procesar el pago.').hide().slideDown(); + } + + button.html(originalText); + button.prop("disabled", false); + }); +}); +$("#categoria").on("change", function () { + let categoriaSeleccionada = $(this).val(); + if (categoriaSeleccionada) { + $.ajax({ + url: "{% url 'get_items_by_category' %}", + method: "GET", + data: { category: categoriaSeleccionada }, + success: function (data) { + $("#items-container").empty(); + if (data.items.length > 0) { + data.items.forEach(function (item) { + let itemHtml = ` +
    + ${item.name} +

    ${item.name}

    +

    Price: ${item.price} EUR

    + +
    + `; + $("#items-container").append(itemHtml); + }); + } else { + $("#items-container").append('

    No items found in this category.

    '); + } + }, + error: function () { + alert("Error loading items."); + } + }); + } +});