chore: docs and added new field for tpv domain

This commit is contained in:
2025-01-13 18:08:57 +01:00
parent 475b53a7e3
commit 2ff2d08565
12 changed files with 101 additions and 43 deletions
+25 -5
View File
@@ -95,8 +95,28 @@ Puedes crear un archivo `.env` con las siguientes variables de entorno.
| AWS_SECRET_ACCESS_KEY | | Clave secreta de S3 | | AWS_SECRET_ACCESS_KEY | | Clave secreta de S3 |
| AWS_STORAGE_BUCKET_NAME | | Nombre del bucket de S3 | | AWS_STORAGE_BUCKET_NAME | | Nombre del bucket de S3 |
| AWS_S3_ENDPOINT_URL | | Endpoint de S3 | | AWS_S3_ENDPOINT_URL | | Endpoint de S3 |
| REDSYS_SHARED_SECRET | sq7HjrUOBfKmC576ILgskD5srU870gJ7 | Clave privada de Redsys |
| REDSYS_MERCHANT_CODE | 999008881 | Código de negocio de Redsys |
| REDSYS_TERMINAL | 001 | Terminal de Redsys | ## Depurar pagos TPV con Redsys
| REDSYS_CURRENCY_CODE | 978 | Código de moneda de Redsys |
| REDSYS_TPV_DOMAIN | | Dominio del TPV para redirección de Redsys | Para poder usar el TPV en la máquina local necesitaremos instalar y configurar [`ngrok`](https://ngrok.com/),
siguiendo los pasos que nos indican en la web al crear nuestra cuenta
Arrancaremos ngrok con el siguiente comando:
```bash
ngrok http http://localhost:8000
```
Ngrok nos dará una URL accesible desde fuera, que tendremos que guardar en los `Ajustes de la tienda`
como `Dominio del TPV`
Tendremos que establecer también los siguientes ajustes de la cuenta con estos valores para desarrollo.
| Variable | Valor para desarrollo | Descripción |
|------------------------|----------------------------------|------------------------------------------------------------------------------|
| `redsys_shared_secret` | sq7HjrUOBfKmC576ILgskD5srU870gJ7 | Clave privada de Redsys |
| `merchant_code` | 999008881 | Código de negocio de Redsys |
| `terminal` | 001 | Terminal de Redsys |
| `currency_code` | 978 | Código de moneda de Redsys |
| `tpv_domain` | <variable> | Dominio del TPV para redirección de Redsys (en local lo obtenemos por ngrok) |
@@ -0,0 +1,20 @@
# Generated by Django 5.1.4 on 2025-01-13 08:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("shop", "0003_order_shipping_method_alter_payment_order"),
]
operations = [
migrations.AddField(
model_name="shopsettings",
name="tpv_domain",
field=models.URLField(
blank=True, max_length=128, null=True, verbose_name="dominio del tpv"
),
),
]
+3
View File
@@ -631,6 +631,9 @@ class ShopSettings(SingletonModel):
) # 978 == EURO ) # 978 == EURO
terminal = models.CharField(max_length=8, verbose_name=_("Terminal")) terminal = models.CharField(max_length=8, verbose_name=_("Terminal"))
shared_secret = models.CharField(max_length=100, verbose_name=_("Clave de Redsys")) shared_secret = models.CharField(max_length=100, verbose_name=_("Clave de Redsys"))
tpv_domain = models.URLField(
max_length=128, blank=True, null=True, verbose_name=_("dominio del tpv")
)
class Meta: class Meta:
verbose_name = _("ajustes de la tienda") verbose_name = _("ajustes de la tienda")
+11 -8
View File
@@ -7,7 +7,7 @@ from django.conf import settings
from django.urls import reverse from django.urls import reverse
from shop.exceptions import RedsysPaymentException from shop.exceptions import RedsysPaymentException
from shop.models import Order from shop.models import Order, ShopSettings
from shop.settings import TransactionTypes from shop.settings import TransactionTypes
from shop.utils import compute_signature, decode_b64_dict from shop.utils import compute_signature, decode_b64_dict
@@ -21,6 +21,9 @@ class RedsysClient:
) )
REST_PROD_ENVIRONMENT_URL = "https://sis.redsys.es/sis/rest/trataPeticionREST" REST_PROD_ENVIRONMENT_URL = "https://sis.redsys.es/sis/rest/trataPeticionREST"
def __init__(self):
self.settings = ShopSettings.load()
def get_target_url(self): def get_target_url(self):
if settings.DEBUG: if settings.DEBUG:
return self.DEBUG_ENVIRONMENT_URL return self.DEBUG_ENVIRONMENT_URL
@@ -32,31 +35,31 @@ class RedsysClient:
return self.REST_PROD_ENVIRONMENT_URL return self.REST_PROD_ENVIRONMENT_URL
def get_merchant_code(self) -> str: def get_merchant_code(self) -> str:
return settings.REDSYS_MERCHANT_CODE return self.settings.merchant_code
def get_terminal(self) -> str: def get_terminal(self) -> str:
return settings.REDSYS_TERMINAL return self.settings.terminal
def get_currency_code(self) -> str: def get_currency_code(self) -> str:
return settings.REDSYS_CURRENCY_CODE return self.settings.currency_code
def get_merchant_url_ok_for_order(self, order: Order) -> str: def get_merchant_url_ok_for_order(self, order: Order) -> str:
path = reverse("web:order", kwargs={"uuid": order.uuid}) path = reverse("web:order", kwargs={"uuid": order.uuid})
return f"{settings.REDSYS_TPV_DOMAIN}{path}" return f"{self.settings.tpv_domain}{path}"
def to_integer(self, value: Decimal): def to_integer(self, value: Decimal):
return int(value * 100) return int(value * 100)
def get_merchant_url_ko_for_order(self, order: Order) -> str: def get_merchant_url_ko_for_order(self, order: Order) -> str:
path = reverse("web:order", kwargs={"uuid": order.uuid}) path = reverse("web:order", kwargs={"uuid": order.uuid})
return f"{settings.REDSYS_TPV_DOMAIN}{path}" return f"{self.settings.tpv_domain}{path}"
def get_webhook_url_for_order(self, order: Order) -> str: def get_webhook_url_for_order(self, order: Order) -> str:
path = reverse("shop:webhook", kwargs={"uuid": order.uuid}) path = reverse("shop:webhook", kwargs={"uuid": order.uuid})
return f"{settings.REDSYS_TPV_DOMAIN}{path}" return f"{self.settings.tpv_domain}{path}"
def get_shared_secret(self) -> str: def get_shared_secret(self) -> str:
return settings.REDSYS_SHARED_SECRET return self.settings.shared_secret
def get_signature(self, hash, payload: str): def get_signature(self, hash, payload: str):
key = self.get_shared_secret() key = self.get_shared_secret()
-1
View File
@@ -1,5 +1,4 @@
from django.core.signals import Signal from django.core.signals import Signal
clear_cart = Signal() clear_cart = Signal()
send_order_email = Signal() send_order_email = Signal()
+9 -9
View File
@@ -2,14 +2,13 @@ import base64
import json import json
from decimal import Decimal from decimal import Decimal
from django.conf import settings
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from rest_framework import status from rest_framework import status
from rest_framework.test import APITestCase from rest_framework.test import APITestCase
from shop.models import CustomerAddress, Order, Tax from shop.models import CustomerAddress, Order, ShopSettings, Tax
from shop.redsys import RedsysClient from shop.redsys import RedsysClient
from shop.tests.mixins import CreateProductsMixin from shop.tests.mixins import CreateProductsMixin
from shop.utils import create_order, create_order_line_for_product, validate_expiry_date from shop.utils import create_order, create_order_line_for_product, validate_expiry_date
@@ -26,7 +25,7 @@ class TestRedsysTPV(APITestCase, CreateProductsMixin):
self.customer = get_user_model().objects.create_user( self.customer = get_user_model().objects.create_user(
username="11111111H", username="11111111H",
first_name="Darth", first_name="Darth",
last_name="Maull", last_name="Maul",
email="darth@maul.com", email="darth@maul.com",
password="dathomir", password="dathomir",
) )
@@ -53,6 +52,9 @@ class TestRedsysTPV(APITestCase, CreateProductsMixin):
self.product = self.create_product() self.product = self.create_product()
self.order = self.create_order() self.order = self.create_order()
self.order.calculate_total_from_lines() self.order.calculate_total_from_lines()
self.settings = ShopSettings.load()
self.settings.shared_secret = "sq7HjrUOBfKmC576ILgskD5srU870gJ7" # Debug secret
self.settings.save()
def create_order(self): def create_order(self):
order = create_order( order = create_order(
@@ -83,12 +85,10 @@ class TestRedsysTPV(APITestCase, CreateProductsMixin):
assert merchant_parameters.get("DS_MERCHANT_AMOUNT") == str( assert merchant_parameters.get("DS_MERCHANT_AMOUNT") == str(
int(amount_to_pay * 100) int(amount_to_pay * 100)
) )
assert ( assert merchant_parameters.get("DS_MERCHANT_TERMINAL") == self.settings.terminal
merchant_parameters.get("DS_MERCHANT_TERMINAL") == settings.REDSYS_TERMINAL
)
assert ( assert (
merchant_parameters.get("DS_MERCHANT_MERCHANTCODE") merchant_parameters.get("DS_MERCHANT_MERCHANTCODE")
== settings.REDSYS_MERCHANT_CODE == self.settings.merchant_code
) )
client.get_body_for_order(self.order) client.get_body_for_order(self.order)
@@ -124,7 +124,7 @@ class TestRedsysTPV(APITestCase, CreateProductsMixin):
reverse("shop:webhook", kwargs={"uuid": self.order.uuid}), reverse("shop:webhook", kwargs={"uuid": self.order.uuid}),
data={ data={
"Ds_MerchantParameters": b64_merchant_params, "Ds_MerchantParameters": b64_merchant_params,
"Ds_Signature": settings.REDSYS_SHARED_SECRET, "Ds_Signature": self.settings.shared_secret,
"Ds_SignatureVersion": "HMAC_SHA256_V1", "Ds_SignatureVersion": "HMAC_SHA256_V1",
}, },
) )
@@ -164,7 +164,7 @@ class TestRedsysTPV(APITestCase, CreateProductsMixin):
reverse("shop:webhook", kwargs={"uuid": self.order.uuid}), reverse("shop:webhook", kwargs={"uuid": self.order.uuid}),
data={ data={
"Ds_MerchantParameters": b64_merchant_params, "Ds_MerchantParameters": b64_merchant_params,
"Ds_Signature": settings.REDSYS_SHARED_SECRET, "Ds_Signature": self.settings.shared_secret,
"Ds_SignatureVersion": "HMAC_SHA256_V1", "Ds_SignatureVersion": "HMAC_SHA256_V1",
}, },
) )
+1 -1
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 validate_payment_for_order, pay_order from shop.utils import pay_order, validate_payment_for_order
@csrf_exempt @csrf_exempt
+1 -2
View File
@@ -20,7 +20,6 @@
{% endblock %} {% endblock %}
</main> </main>
</body>
<script src="{% static 'theme/js/theme.js' %}"></script> <script src="{% static 'theme/js/theme.js' %}"></script>
</body>
</html> </html>
+3 -4
View File
@@ -1,5 +1,4 @@
<svg class="h-4 w-4 text-yellow-400" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" <svg class="h-4 w-4" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"
fill="currentColor" viewBox="0 0 24 24"> fill="#CFCA00" viewBox="0 0 24 24">
<path <path d="M13.8 4.2a2 2 0 0 0-3.6 0L8.4 8.4l-4.6.3a2 2 0 0 0-1.1 3.5l3.5 3-1 4.4c-.5 1.7 1.4 3 2.9 2.1l3.9-2.3 3.9 2.3c1.5 1 3.4-.4 3-2.1l-1-4.4 3.4-3a2 2 0 0 0-1.1-3.5l-4.6-.3-1.8-4.2Z" />
d="M13.8 4.2a2 2 0 0 0-3.6 0L8.4 8.4l-4.6.3a2 2 0 0 0-1.1 3.5l3.5 3-1 4.4c-.5 1.7 1.4 3 2.9 2.1l3.9-2.3 3.9 2.3c1.5 1 3.4-.4 3-2.1l-1-4.4 3.4-3a2 2 0 0 0-1.1-3.5l-4.6-.3-1.8-4.2Z"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 347 B

After

Width:  |  Height:  |  Size: 315 B

@@ -0,0 +1,5 @@
<svg class="h-4 w-4" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"
fill="#101010" viewBox="0 0 24 24">
<path
d="M13.8 4.2a2 2 0 0 0-3.6 0L8.4 8.4l-4.6.3a2 2 0 0 0-1.1 3.5l3.5 3-1 4.4c-.5 1.7 1.4 3 2.9 2.1l3.9-2.3 3.9 2.3c1.5 1 3.4-.4 3-2.1l-1-4.4 3.4-3a2 2 0 0 0-1.1-3.5l-4.6-.3-1.8-4.2Z"/>
</svg>

After

Width:  |  Height:  |  Size: 326 B

+2
View File
@@ -80,7 +80,9 @@ class MyAccountView(TemplateView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
addresses = CustomerAddress.objects.filter(user=self.request.user) addresses = CustomerAddress.objects.filter(user=self.request.user)
settings = WebSettings.load()
return { return {
"web_title": settings.web_title,
"customer_addresses": addresses, "customer_addresses": addresses,
} }
+21 -13
View File
@@ -1,3 +1,5 @@
from decimal import Decimal
from django.db.models import Sum from django.db.models import Sum
from django.http.response import HttpResponse, JsonResponse from django.http.response import HttpResponse, JsonResponse
from django.shortcuts import get_object_or_404, redirect, reverse from django.shortcuts import get_object_or_404, redirect, reverse
@@ -79,7 +81,7 @@ class CartDetail(TemplateView, CreateView):
return { return {
"cart": cart, "cart": cart,
"items": cart_items, "items": cart_items,
"title": web_settings.web_title, "web_title": web_settings.web_title,
"description": _("resumen del carrito"), "description": _("resumen del carrito"),
} }
@@ -148,15 +150,21 @@ class OrderDetail(TemplateView):
items = OrderLine.objects.select_related("product").filter( items = OrderLine.objects.select_related("product").filter(
order=order, product__is_shipping_method=False order=order, product__is_shipping_method=False
) )
shipping_cost = (
OrderLine.objects.select_related("product") shipping_cost = OrderLine.objects.select_related("product").filter(
.filter( order=order,
order=order, product__is_shipping_method=True,
product__is_shipping_method=True, ).aggregate(amount=Sum("total")).get("amount") or Decimal("0")
)
.aggregate(amount=Sum("total")) base_total = OrderLine.objects.select_related("product").filter(
.get("amount") order=order,
) product__is_shipping_method=False,
).aggregate(amount=Sum("base_total")).get("amount") or Decimal("0")
tax_total = OrderLine.objects.select_related("product").filter(
order=order,
product__is_shipping_method=False,
).aggregate(amount=Sum("taxes")).get("amount") or Decimal("0")
return { return {
"order": order, "order": order,
@@ -165,9 +173,9 @@ class OrderDetail(TemplateView):
"merchant_parameters": parameters.get("Ds_MerchantParameters"), "merchant_parameters": parameters.get("Ds_MerchantParameters"),
"signature": parameters.get("Ds_Signature"), "signature": parameters.get("Ds_Signature"),
"redsys_target_url": redsys_client.get_target_url(), "redsys_target_url": redsys_client.get_target_url(),
"total": order.total, "total": round(order.total, 2),
"base_total": order.base_total, "base_total": round(base_total, 2),
"tax_total": order.total - order.base_total, "tax_total": round(tax_total, 2),
"shipping_cost": round(shipping_cost, 2), "shipping_cost": round(shipping_cost, 2),
} }