feat: payments working
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
# Generated by Django 5.1.4 on 2025-01-12 22:15
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("shop", "0002_shopsettings_order_code_order_shipping_status_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="order",
|
||||
name="shipping_method",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to="shop.shippingmethod",
|
||||
verbose_name="método de envío",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="payment",
|
||||
name="order",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name="payments",
|
||||
to="shop.order",
|
||||
verbose_name="pedido",
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -429,6 +429,14 @@ class Order(TimestampedModel):
|
||||
max_length=32, blank=True, verbose_name=_("teléfono de contacto")
|
||||
)
|
||||
|
||||
shipping_method = models.ForeignKey(
|
||||
"shop.ShippingMethod",
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=models.SET_NULL,
|
||||
verbose_name=_("método de envío"),
|
||||
)
|
||||
|
||||
def calculate_total_from_lines(self):
|
||||
self.base_total = self.lines.aggregate(base_total=models.Sum("base_total")).get(
|
||||
"base_total"
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
from django.core.signals import Signal
|
||||
|
||||
|
||||
clear_cart = Signal()
|
||||
send_order_email = Signal()
|
||||
@@ -111,6 +111,7 @@ def create_order_from_cart(
|
||||
shipping_state=shipping_address_state,
|
||||
shipping_country=shipping_address_country,
|
||||
shipping_zip=shipping_address_zip,
|
||||
shipping_method=shipping_method,
|
||||
user=cart.user,
|
||||
email=email,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
from django.dispatch import receiver
|
||||
|
||||
from shop.models import Cart, CartItem
|
||||
from shop.signals import clear_cart, send_order_email
|
||||
|
||||
|
||||
@receiver(clear_cart, dispatch_uid="clear_cart")
|
||||
def create_payment_from_redsys(sender, **kwargs):
|
||||
cart = Cart.objects.get(hash=kwargs.get("cart_id"))
|
||||
CartItem.objects.filter(cart=cart).delete()
|
||||
|
||||
|
||||
@receiver(send_order_email, dispatch_uid="send_order_email")
|
||||
def create_payment_from_redsys(sender, **kwargs):
|
||||
pass
|
||||
@@ -1,6 +1,5 @@
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{{ page }}
|
||||
|
||||
<ul class="mb-4 grid gap-4 sm:grid-cols-2 md:mb-8 lg:grid-cols-3 xl:grid-cols-4">
|
||||
{% for product in page %}
|
||||
|
||||
+172
-165
@@ -2,183 +2,190 @@
|
||||
{% load static i18n %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'components/generic/navbar.html' %}
|
||||
{% load i18n %}
|
||||
<section class="bg-white py-2 antialiased dark:bg-gray-900 md:py-8">
|
||||
<form action="{% url 'web:cart_detail' %}" method="post">
|
||||
{% csrf_token %}
|
||||
<div class="mx-auto max-w-screen-xl px-4 2xl:px-0">
|
||||
{% include 'components/generic/navbar.html' %}
|
||||
{% load i18n %}
|
||||
<section class="bg-white py-2 antialiased dark:bg-gray-900 md:py-8">
|
||||
<div class="mx-auto max-w-screen-xl px-4 2xl:px-0">
|
||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white sm:text-2xl">
|
||||
{% translate 'Resumen del pedido' %}
|
||||
{% translate 'Resumen del pedido' %}
|
||||
|
||||
{% if order.status == 'PAI' %}
|
||||
<span class="bg-green-100 text-green-800 text-sm font-medium me-2 px-2.5 py-0.5 rounded dark:bg-green-900 dark:text-green-300 ml-3">{% trans 'Pagado' %}</span>
|
||||
{% endif %}
|
||||
</h2>
|
||||
|
||||
<div class="mt-4 sm:mt-6 md:gap-6 lg:flex lg:items-start xl:gap-8">
|
||||
<div class="mx-auto w-full flex-none lg:max-w-2xl xl:max-w-4xl">
|
||||
<div class="space-y-6">
|
||||
<div class="mx-auto w-full flex-none lg:max-w-2xl xl:max-w-4xl">
|
||||
<div class="space-y-6">
|
||||
|
||||
{% for item in items %}
|
||||
{% for item in items %}
|
||||
<div
|
||||
class="rounded-lg border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-700 dark:bg-gray-800 md:p-6">
|
||||
<div class="space-y-4 md:flex md:items-center md:justify-between md:gap-6 md:space-y-0">
|
||||
<a href="#" class="shrink-0 md:order-1">
|
||||
<img class="h-20 w-20 dark:hidden" src="{{ item.product.images.all|first }}"
|
||||
alt="{{ item.product.name }}"/>
|
||||
<img class="hidden h-20 w-20 dark:block" src="{{ item.product.images.all|first }}"
|
||||
alt="{{ item.product.name }}"/>
|
||||
</a>
|
||||
|
||||
<div class="flex items-center justify-between md:order-3 md:justify-end">
|
||||
<div class="flex items-center">
|
||||
<span
|
||||
class="w-10 shrink-0 border-0 bg-transparent text-center text-sm font-medium text-gray-900 focus:outline-none focus:ring-0 dark:text-white">
|
||||
{{ item.quantity }}x
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-end md:order-4 md:w-32">
|
||||
<p
|
||||
class="text-base font-bold text-gray-900 dark:text-white">
|
||||
{{ item.product.price.price_with_tax }}€
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-full min-w-0 flex-1 space-y-4 md:order-2 md:max-w-md">
|
||||
<a href="{% url 'web:product_detail' pk=item.product.pk slug=item.product.slug %}"
|
||||
class="text-base font-medium text-gray-900 hover:underline dark:text-white"
|
||||
>
|
||||
{{ item.product.name }}
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% if items.count == 0 %}
|
||||
<p class="text-base font-normal text-gray-500 dark:text-gray-400">
|
||||
{% translate 'No hay nada en el carrito...' %}
|
||||
</p>
|
||||
<p>
|
||||
<a href="{% url 'web:index' %}" title=""
|
||||
class="inline-flex items-center gap-2 text-sm font-medium text-primary-700 underline hover:no-underline dark:text-primary-500">
|
||||
{% translate 'Volver a inicio' %}
|
||||
</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<section>
|
||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white sm:text-2xl">
|
||||
{% translate 'Dirección de envío' %}
|
||||
</h2>
|
||||
|
||||
<div>
|
||||
<p>{{ order.shipping_address }}</p>
|
||||
<p>{{ order.shipping_city }}</p>
|
||||
<p>{{ order.shipping_state }}</p>
|
||||
<p>{{ order.shipping_country }}</p>
|
||||
<p>{{ order.shipping_zip }}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white sm:text-2xl">
|
||||
{% translate 'Dirección de facturación' %}
|
||||
</h2>
|
||||
|
||||
<div>
|
||||
<p>{{ order.billing_address }}</p>
|
||||
<p>{{ order.billing_city }}</p>
|
||||
<p>{{ order.billing_state }}</p>
|
||||
<p>{{ order.billing_country }}</p>
|
||||
<p>{{ order.billing_zip }}</p>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white sm:text-2xl">
|
||||
{% translate 'Método de envío' %}
|
||||
</h2>
|
||||
|
||||
<section>
|
||||
{{ order.shipping_method.name }}
|
||||
</section>
|
||||
|
||||
</section>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Resumen del pedido -->
|
||||
<div class="mx-auto mt-6 max-w-4xl flex-1 space-y-6 lg:mt-0 lg:w-full">
|
||||
<div
|
||||
class="rounded-lg border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-700 dark:bg-gray-800 md:p-6">
|
||||
<div class="space-y-4 md:flex md:items-center md:justify-between md:gap-6 md:space-y-0">
|
||||
<a href="#" class="shrink-0 md:order-1">
|
||||
<img class="h-20 w-20 dark:hidden" src="{{ item.product.images.all|first }}"
|
||||
alt="{{ item.product.name }}"/>
|
||||
<img class="hidden h-20 w-20 dark:block" src="{{ item.product.images.all|first }}"
|
||||
alt="{{ item.product.name }}"/>
|
||||
</a>
|
||||
class="space-y-4 rounded-lg border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-700 dark:bg-gray-800 sm:p-6">
|
||||
<p class="text-xl font-semibold text-gray-900 dark:text-white">
|
||||
{% translate 'Resumen del pedido' %}</p>
|
||||
|
||||
<label for="counter-input" class="sr-only">{% translate 'Cantidad' %}</label>
|
||||
<div class="flex items-center justify-between md:order-3 md:justify-end">
|
||||
<div class="flex items-center">
|
||||
<span
|
||||
class="w-10 shrink-0 border-0 bg-transparent text-center text-sm font-medium text-gray-900 focus:outline-none focus:ring-0 dark:text-white">
|
||||
{{ item.quantity }}x
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-end md:order-4 md:w-32">
|
||||
<p
|
||||
class="text-base font-bold text-gray-900 dark:text-white">{{ item.product.price.price_with_tax }}
|
||||
€</p>
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<dl class="flex items-center justify-between gap-4">
|
||||
<dt
|
||||
class="text-base font-normal text-gray-500 dark:text-gray-400">
|
||||
{% translate 'Base imponible' %}
|
||||
</dt>
|
||||
<dd class="text-base font-medium text-gray-900 dark:text-white">{{ base_total }} €
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="flex items-center justify-between gap-4">
|
||||
<dt class="text-base font-normal text-gray-500 dark:text-gray-400">
|
||||
{% translate 'Descuentos' %}
|
||||
</dt>
|
||||
<dd class="text-base font-medium text-green-600">-{{ savings }} €</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="flex items-center justify-between gap-4">
|
||||
<dt
|
||||
class="text-base font-normal text-gray-500 dark:text-gray-400">
|
||||
{% translate 'Gastos de envío' %}
|
||||
</dt>
|
||||
<dd class="text-base font-medium text-gray-900 dark:text-white">{{ shipping_cost }}
|
||||
€
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="flex items-center justify-between gap-4">
|
||||
<dt class="text-base font-normal text-gray-500 dark:text-gray-400">
|
||||
{% translate 'Impuestos' %}
|
||||
</dt>
|
||||
<dd class="text-base font-medium text-gray-900 dark:text-white">{{ tax_total }} €
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<dl class="flex items-center justify-between gap-4 border-t border-gray-200 pt-2 dark:border-gray-700">
|
||||
<dt class="text-base font-bold text-gray-900 dark:text-white">{% translate 'Total' %}
|
||||
</dt>
|
||||
<dd class="text-base font-bold text-gray-900 dark:text-white">{{ total }} €</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div class="w-full min-w-0 flex-1 space-y-4 md:order-2 md:max-w-md">
|
||||
<a href="{% url 'web:product_detail' pk=item.product.pk slug=item.product.slug %}"
|
||||
class="text-base font-medium text-gray-900 hover:underline dark:text-white"
|
||||
>
|
||||
{{ item.product.name }}
|
||||
</a>
|
||||
{% if order.status == 'PEN' %}
|
||||
<form name="from" action="{{ redsys_target_url }}" method="POST">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="Ds_SignatureVersion" value="{{ signature_version }}"/>
|
||||
<input type="hidden" name="Ds_MerchantParameters" value="{{ merchant_parameters }}"/>
|
||||
<input type="hidden" name="Ds_Signature" value="{{ signature }}"/>
|
||||
|
||||
<div class="flex items-center gap-4">
|
||||
<form class="flex" hx-post="{% url 'web:delete_cart_item' pk=item.pk %}"
|
||||
hx-swap="none">
|
||||
{% csrf_token %}
|
||||
<button
|
||||
type="submit"
|
||||
class="inline-flex items-center text-sm font-medium text-red-600 hover:underline dark:text-red-500"
|
||||
>
|
||||
{% include 'components/icons/x.svg' %}
|
||||
{% translate 'Quitar' %}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
<button class="flex w-full items-center justify-center rounded-lg bg-primary-700 px-5 py-2.5 text-sm font-medium text-white hover:bg-primary-800 focus:outline-none focus:ring-4 focus:ring-primary-300 dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800">
|
||||
{% translate 'Pagar' %}
|
||||
</button>
|
||||
</form>
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<span class="text-sm font-normal text-gray-500 dark:text-gray-400"> {% translate 'o' %} </span>
|
||||
<a href="{% url 'web:index' %}" title=""
|
||||
class="inline-flex items-center gap-2 text-sm font-medium text-primary-700 underline hover:no-underline dark:text-primary-500">
|
||||
{% translate 'Seguir comprando' %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% if items.count == 0 %}
|
||||
<p class="text-base font-normal text-gray-500 dark:text-gray-400">
|
||||
{% translate 'No hay nada en el carrito...' %}
|
||||
</p>
|
||||
<p>
|
||||
<a href="{% url 'web:index' %}" title=""
|
||||
class="inline-flex items-center gap-2 text-sm font-medium text-primary-700 underline hover:no-underline dark:text-primary-500">
|
||||
{% translate 'Volver a inicio' %}
|
||||
</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<section>
|
||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white sm:text-2xl">
|
||||
{% translate 'Dirección de envío' %}
|
||||
</h2>
|
||||
|
||||
{% for address in shipping_addresses %}
|
||||
<input type="radio" id="shipping_address_{{ address.pk }}" name="shipping_address"
|
||||
value="{{ address.pk }}">
|
||||
<label for="shipping_address_{{ address.pk }}">
|
||||
{{ address }}
|
||||
</label>
|
||||
{% endfor %}
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white sm:text-2xl">
|
||||
{% translate 'Dirección de facturación' %}
|
||||
</h2>
|
||||
|
||||
{% for address in billing_addresses %}
|
||||
<input type="radio" id="billing_address_{{ address.pk }}" name="billing_address"
|
||||
value="{{ address.pk }}">
|
||||
<label for="billing_address_{{ address.pk }}">
|
||||
{{ address }}
|
||||
</label>
|
||||
{% endfor %}
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white sm:text-2xl">
|
||||
{% translate 'Opciones de envío' %}
|
||||
</h2>
|
||||
|
||||
{% for shipping_method in shipping_methods %}
|
||||
<input type="radio" id="shipping_method_{{ shipping_method.pk }}" name="shipping_method"
|
||||
value="{{ shipping_method.pk }}">
|
||||
<label for="shipping_method_{{ shipping_method.pk }}">
|
||||
{{ shipping_method.name }} - {{ shipping_method.shipping_product.price.price_with_tax }}€
|
||||
</label>
|
||||
{% endfor %}
|
||||
</section>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Resumen del pedido -->
|
||||
<div class="mx-auto mt-6 max-w-4xl flex-1 space-y-6 lg:mt-0 lg:w-full">
|
||||
<div
|
||||
class="space-y-4 rounded-lg border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-700 dark:bg-gray-800 sm:p-6">
|
||||
<p class="text-xl font-semibold text-gray-900 dark:text-white">{% translate 'Resumen del pedido' %}</p>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<dl class="flex items-center justify-between gap-4">
|
||||
<dt
|
||||
class="text-base font-normal text-gray-500 dark:text-gray-400">{% translate 'Base imponible' %}</dt>
|
||||
<dd class="text-base font-medium text-gray-900 dark:text-white">{{ base_total }} €</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="flex items-center justify-between gap-4">
|
||||
<dt class="text-base font-normal text-gray-500 dark:text-gray-400">{% translate 'Descuentos' %}</dt>
|
||||
<dd class="text-base font-medium text-green-600">-{{ savings }} €</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="flex items-center justify-between gap-4">
|
||||
<dt
|
||||
class="text-base font-normal text-gray-500 dark:text-gray-400">{% translate 'Gastos de envío' %}</dt>
|
||||
<dd class="text-base font-medium text-gray-900 dark:text-white">{{ shipping_cost }} €</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="flex items-center justify-between gap-4">
|
||||
<dt class="text-base font-normal text-gray-500 dark:text-gray-400">{% translate 'Impuestos' %}</dt>
|
||||
<dd class="text-base font-medium text-gray-900 dark:text-white">{{ tax_total }} €</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<dl class="flex items-center justify-between gap-4 border-t border-gray-200 pt-2 dark:border-gray-700">
|
||||
<dt class="text-base font-bold text-gray-900 dark:text-white">{% translate 'Total' %}</dt>
|
||||
<dd class="text-base font-bold text-gray-900 dark:text-white">{{ total }} €</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="flex w-full items-center justify-center rounded-lg bg-primary-700 px-5 py-2.5 text-sm font-medium text-white hover:bg-primary-800 focus:outline-none focus:ring-4 focus:ring-primary-300 dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800">
|
||||
{% translate 'Pagar' %}
|
||||
</button>
|
||||
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<span class="text-sm font-normal text-gray-500 dark:text-gray-400"> {% translate 'o' %} </span>
|
||||
<a href="{% url 'web:index' %}" title=""
|
||||
class="inline-flex items-center gap-2 text-sm font-medium text-primary-700 underline hover:no-underline dark:text-primary-500">
|
||||
{% translate 'Seguir comprando' %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
+27
-2
@@ -1,3 +1,4 @@
|
||||
from django.db.models import Sum
|
||||
from django.http.response import HttpResponse, JsonResponse
|
||||
from django.shortcuts import get_object_or_404, redirect, reverse
|
||||
from django.utils.text import gettext_lazy as _
|
||||
@@ -6,7 +7,6 @@ from django.views.generic import CreateView, TemplateView
|
||||
|
||||
from shop.models import (
|
||||
CartItem,
|
||||
CustomerAddress,
|
||||
Order,
|
||||
OrderLine,
|
||||
Product,
|
||||
@@ -14,6 +14,7 @@ from shop.models import (
|
||||
ShippingMethod,
|
||||
WishlistedProduct,
|
||||
)
|
||||
from shop.redsys import RedsysClient
|
||||
from shop.utils import create_order_from_cart
|
||||
from web.forms import CreateOrderForm
|
||||
from web.models import WebSettings
|
||||
@@ -137,10 +138,34 @@ class OrderDetail(TemplateView):
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
order = get_object_or_404(Order, uuid=kwargs.get("uuid"))
|
||||
items = OrderLine.objects.filter(order=order)
|
||||
|
||||
redsys_client = RedsysClient()
|
||||
parameters = redsys_client.get_body_for_order(order)
|
||||
|
||||
items = OrderLine.objects.select_related("product").filter(
|
||||
order=order, product__is_shipping_method=False
|
||||
)
|
||||
shipping_cost = (
|
||||
OrderLine.objects.select_related("product")
|
||||
.filter(
|
||||
order=order,
|
||||
product__is_shipping_method=True,
|
||||
)
|
||||
.aggregate(amount=Sum("total"))
|
||||
.get("amount")
|
||||
)
|
||||
|
||||
return {
|
||||
"order": order,
|
||||
"items": items,
|
||||
"signature_version": parameters.get("Ds_SignatureVersion"),
|
||||
"merchant_parameters": parameters.get("Ds_MerchantParameters"),
|
||||
"signature": parameters.get("Ds_Signature"),
|
||||
"redsys_target_url": redsys_client.get_target_url(),
|
||||
"total": order.total,
|
||||
"base_total": order.base_total,
|
||||
"tax_total": order.total - order.base_total,
|
||||
"shipping_cost": round(shipping_cost, 2),
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user