feat: improved order payment lifecycle

This commit is contained in:
2025-01-16 18:17:53 +01:00
parent ebb6d5a5a5
commit 6c60df2da0
7 changed files with 108 additions and 13 deletions
@@ -0,0 +1,44 @@
# Generated by Django 5.1.4 on 2025-01-16 17:00
import django.db.models.deletion
from decimal import Decimal
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("shop", "0004_shopsettings_tpv_domain"),
]
operations = [
migrations.AlterModelOptions(
name="order",
options={
"ordering": ("creation_date",),
"verbose_name": "pedido",
"verbose_name_plural": "pedidos",
},
),
migrations.AddField(
model_name="order",
name="amount_paid",
field=models.DecimalField(
decimal_places=2,
default=Decimal("0.00"),
max_digits=11,
verbose_name="cantidad pagada",
),
),
migrations.AddField(
model_name="order",
name="from_cart",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="shop.cart",
verbose_name="carrito de origen de pedido",
),
),
]
+15
View File
@@ -429,6 +429,13 @@ class Order(TimestampedModel):
max_length=32, blank=True, verbose_name=_("teléfono de contacto") max_length=32, blank=True, verbose_name=_("teléfono de contacto")
) )
amount_paid = models.DecimalField(
default=Decimal("0.00"),
max_digits=11,
decimal_places=2,
verbose_name=_("cantidad pagada"),
)
shipping_method = models.ForeignKey( shipping_method = models.ForeignKey(
"shop.ShippingMethod", "shop.ShippingMethod",
blank=True, blank=True,
@@ -437,6 +444,14 @@ class Order(TimestampedModel):
verbose_name=_("método de envío"), verbose_name=_("método de envío"),
) )
from_cart = models.ForeignKey(
"shop.Cart",
on_delete=models.SET_NULL,
blank=True,
null=True,
verbose_name=_("carrito de origen de pedido"),
)
def calculate_total_from_lines(self): def calculate_total_from_lines(self):
self.base_total = self.lines.aggregate(base_total=models.Sum("base_total")).get( self.base_total = self.lines.aggregate(base_total=models.Sum("base_total")).get(
"base_total" "base_total"
+29 -6
View File
@@ -7,6 +7,7 @@ from decimal import Decimal
import pyDes import pyDes
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.db import transaction from django.db import transaction
from django.db.models import Sum
from django.utils import timezone from django.utils import timezone
from django.utils.text import gettext_lazy as _ from django.utils.text import gettext_lazy as _
@@ -20,8 +21,10 @@ from shop.models import (
ProductBatch, ProductBatch,
ProductPrice, ProductPrice,
ShippingMethod, ShippingMethod,
CartItem,
) )
from shop.settings import ERROR_CODES from shop.settings import ERROR_CODES
from shop.signals import clear_cart
User = get_user_model() User = get_user_model()
@@ -114,6 +117,7 @@ def create_order_from_cart(
shipping_method=shipping_method, shipping_method=shipping_method,
user=cart.user, user=cart.user,
email=email, email=email,
from_cart=cart,
) )
for item in cart.items.all(): for item in cart.items.all():
@@ -261,15 +265,34 @@ def validate_expiry_date(expiry_date: str):
return True return True
def pay_order(order: Order, amount_paid: Decimal): def update_order_payment_status(order: Order):
with transaction.atomic() as tx: if order.amount_paid >= order.total:
order.status = Order.Statuses.STATUS_PAID
elif order.status == Order.Statuses.STATUS_PAID and order.amount_paid == Decimal(
"0.00"
):
order.status = Order.Statuses.STATUS_RETURNED
else:
order.status = Order.Statuses.STATUS_PENDING
order.save()
def delete_cart_items_from_order(order):
if order.status == Order.Statuses.STATUS_PAID and order.from_cart:
CartItem.objects.filter(cart=order.from_cart).delete()
def add_payment_to_order(order: Order, amount):
with transaction.atomic():
payment = Payment.objects.create( payment = Payment.objects.create(
amount=amount_paid, amount=amount,
order=order, order=order,
user=order.user, user=order.user,
method=Payment.MethodChoices.REDSYS, method=Payment.MethodChoices.REDSYS,
) )
order.amount_paid += payment.amount
if amount_paid >= order.total: update_order_payment_status(order)
order.status = Order.Statuses.STATUS_PAID
order.save() delete_cart_items_from_order(order)
+2 -2
View File
@@ -3,7 +3,7 @@ from django.shortcuts import get_object_or_404
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from shop.models import Order from shop.models import Order
from shop.utils import pay_order, validate_payment_for_order from shop.utils import validate_payment_for_order, add_payment_to_order
@csrf_exempt @csrf_exempt
@@ -12,7 +12,7 @@ def webhook(request, uuid):
try: try:
amount_paid = validate_payment_for_order(request, order) amount_paid = validate_payment_for_order(request, order)
pay_order(order, amount_paid) add_payment_to_order(order, amount_paid)
return HttpResponse(status=200) return HttpResponse(status=200)
except Exception as e: except Exception as e:
+1
View File
@@ -8,6 +8,7 @@ class CartItemForm(forms.Form):
<label for="quantity">{% translate 'Cantidad' %}</label> <label for="quantity">{% translate 'Cantidad' %}</label>
<input id="quantity" type="number" name="quantity" value="1"> <input id="quantity" type="number" name="quantity" value="1">
""" """
product = forms.IntegerField(widget=widgets.HiddenInput) product = forms.IntegerField(widget=widgets.HiddenInput)
quantity = forms.IntegerField() quantity = forms.IntegerField()
+6 -3
View File
@@ -11,10 +11,13 @@ class TestManifest(TestCase):
def setUp(self): def setUp(self):
self.settings = WebSettings.load() self.settings = WebSettings.load()
with open(os.path.join(settings.BASE_DIR, 'web', 'tests', 'images', 'trolley.png'), 'rb') as f: with open(
file_128 = SimpleUploadedFile('logo_128.png', f.read()) os.path.join(settings.BASE_DIR, "web", "tests", "images", "trolley.png"),
"rb",
) as f:
file_128 = SimpleUploadedFile("logo_128.png", f.read())
self.settings.logo_128 = file_128 self.settings.logo_128 = file_128
file_240 = SimpleUploadedFile('logo_240.png', f.read()) file_240 = SimpleUploadedFile("logo_240.png", f.read())
self.settings.logo_240 = file_240 self.settings.logo_240 = file_240
self.settings.logo = file_240 self.settings.logo = file_240
self.settings.save() self.settings.save()
+11 -2
View File
@@ -4,14 +4,23 @@ from django.contrib.auth.models import User
from django.test import TestCase from django.test import TestCase
from django.urls import reverse from django.urls import reverse
from shop.models import Cart, CartItem, Order, CustomerAddress, ShippingMethod, ShopSettings from shop.models import (
Cart,
CartItem,
Order,
CustomerAddress,
ShippingMethod,
ShopSettings,
)
from shop.tests.mixins import CreateProductsMixin from shop.tests.mixins import CreateProductsMixin
class TestOrders(TestCase, CreateProductsMixin): class TestOrders(TestCase, CreateProductsMixin):
def setUp(self): def setUp(self):
self.product = self.create_product() self.product = self.create_product()
self.user = User.objects.create_user("anakin", "anakin@skywalker.com", "ihatesand") self.user = User.objects.create_user(
"anakin", "anakin@skywalker.com", "ihatesand"
)
self.cart_for_user = self.create_cart(user=self.user) self.cart_for_user = self.create_cart(user=self.user)
self.anonymous_cart = self.create_cart() self.anonymous_cart = self.create_cart()
self.address = CustomerAddress.objects.create( self.address = CustomerAddress.objects.create(