chore: docs and added new field for tpv domain
This commit is contained in:
@@ -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"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -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
@@ -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,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()
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 |
@@ -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
@@ -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),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user