import base64 import json from decimal import Decimal from unittest.mock import patch import pytest from django.contrib.auth import get_user_model from django.urls import reverse from django.utils import timezone from rest_framework import status from rest_framework.test import APITestCase from config.tests.mixins import Response from shop.exceptions import RedsysPaymentException from shop.models import CustomerAddress, Order, ShopSettings, Tax from shop.redsys import RedsysClient from shop.tests.mixins import CreateProductsMixin from shop.utils import create_order, create_order_line_for_product, validate_expiry_date User = get_user_model() def redsys_response_ok(*args, **kwargs): response = { "Ds_SignatureVersion": "HMAC_SHA256_V1", "Ds_MerchantParameters": "eyJEc19BbW91bnQiOiIxNDUiLCJEc19DdXJyZW5jeSI6Ijk3OCIsIkRzX09yZGVyIjoiMTQ0NjA2ODU4MSIsIkRzX01lcmNoYW50Q29kZSI6Ijk5OTAwODg4MSIsIkRzX1Rlcm1pbmFsIjoiMSIsIkRzX1Jlc3BvbnNlIjoiMDAwMCIsIkRzX0F1dGhvcmlzYXRpb25Db2RlIjoiNTAxNjAyIiwiRHNfVHJhbnNhY3Rpb25UeXBlIjoiMCIsIkRzX1NlY3VyZVBheW1lbnQiOiIwIiwiRHNfTGFuZ3VhZ2UiOiIxIiwiRHNfQ2FyZE51bWJlciI6IjQ1NDg4MSoqKioqKioqMDQiLCJEc19NZXJjaGFudERhdGEiOiIiLCJEc19DYXJkX0NvdW50cnkiOiI3MjQiLCJEc19DYXJkX0JyYW5kIjoiMSJ9", "Ds_Signature": "QVxoXwwp919v7XYjyBjhr1VXozESRosHPb3PDW-rcME=", } return Response(response, status_code=200) def redsys_response_error(*args, **kwargs): response = {"errorCode": "SIS00001"} return Response(response, status_code=200) class TestRedsysTPV(APITestCase, CreateProductsMixin): def setUp(self): self.tax = Tax.objects.create( code="IVA", value=21, ) self.customer = get_user_model().objects.create_user( username="11111111H", first_name="Darth", last_name="Maul", email="darth@maul.com", password="dathomir", ) self.customer_shipping_address = CustomerAddress.objects.create( user=self.customer, address="Dathomir", address_town="Dathomir", address_zip="00001", address_state="Dathomir", address_phone="900000000", address_type=CustomerAddress.Types.SHIPPING, ) self.customer_billing_address = CustomerAddress.objects.create( user=self.customer, address="Dathomir", address_town="Dathomir", address_zip="00001", address_state="Dathomir", address_phone="900000000", address_type=CustomerAddress.Types.BILLING, ) self.product = self.create_product() self.order = self.create_order() self.order.calculate_total_from_lines() self.settings = ShopSettings.load() self.settings.merchant_code = "999008881" self.settings.shared_secret = "sq7HjrUOBfKmC576ILgskD5srU870gJ7" # Debug secret self.settings.save() def create_order(self): order = create_order( customer=self.customer, billing_address=self.customer_billing_address.address, billing_city=self.customer_billing_address.address_town, billing_state=self.customer_billing_address.address_state, billing_zip=self.customer_billing_address.address_zip, billing_country=self.customer_billing_address.address_country, ) l1 = create_order_line_for_product( self.product, quantity=Decimal("1.0"), order=order, ) return order def test_redsys_client(self): amount_to_pay = Decimal("12.10") client = RedsysClient() merchant_parameters = client._get_merchant_parameters_for_order( self.order, ) assert merchant_parameters.get("DS_MERCHANT_ORDER") == self.order.code assert merchant_parameters.get("DS_MERCHANT_AMOUNT") == str( int(amount_to_pay * 100) ) assert merchant_parameters.get("DS_MERCHANT_TERMINAL") == self.settings.terminal assert ( merchant_parameters.get("DS_MERCHANT_MERCHANTCODE") == self.settings.merchant_code ) client.get_body_for_order(self.order) def test_redsys_webhook(self): redsys_response_data = { "Ds_MerchantCode": "999008881", "Ds_Terminal": "001", "Ds_Order": self.order.code, "Ds_Amount": str(self.order.total * 100), "Ds_Currency": "978", "Ds_Date": "01/01/2024", "Ds_Hour": "00:00", "Ds_SecurePayment": "1", "Ds_Card_Number": "454881******1156", "Ds_Card_Country": "724", "Ds_Response": "0000", "Ds_MerchantData": "", "Ds_TransactionType": "0", "Ds_ConsumerLanguage": "1", "Ds_AuthorisationCode": "182670", "Ds_Card_Brand": "1", "Ds_ProcessedPayMethod": "80", "Ds_ECI": "05", "Ds_Response_Description": "OPERACION AUTORIZADA", } redsys_response_data_str = json.dumps(redsys_response_data) b64_merchant_params = base64.b64encode( redsys_response_data_str.encode() ).decode() response = self.client.post( reverse("shop:webhook", kwargs={"uuid": self.order.uuid}), data={ "Ds_MerchantParameters": b64_merchant_params, "Ds_Signature": self.settings.shared_secret, "Ds_SignatureVersion": "HMAC_SHA256_V1", }, ) self.order.refresh_from_db() assert response.status_code == status.HTTP_200_OK assert self.order.status == Order.Statuses.STATUS_PAID def test_redsys_webhook_payment_error(self): redsys_response_data = { "Ds_MerchantCode": "999008881", "Ds_Terminal": "001", "Ds_Order": self.order.code, "Ds_Amount": "1000", "Ds_Currency": "978", "Ds_Date": "01/01/2024", "Ds_Hour": "00:00", "Ds_SecurePayment": "1", "Ds_Card_Number": "454881******1156", "Ds_Card_Country": "724", "Ds_Response": "0184", "Ds_MerchantData": "", "Ds_TransactionType": "0", "Ds_ConsumerLanguage": "1", "Ds_AuthorisationCode": "182670", "Ds_Card_Brand": "1", "Ds_ProcessedPayMethod": "80", "Ds_ECI": "05", "Ds_Response_Description": "ERROR", } redsys_response_data_str = json.dumps(redsys_response_data) b64_merchant_params = base64.b64encode( redsys_response_data_str.encode() ).decode() response = self.client.post( reverse("shop:webhook", kwargs={"uuid": self.order.uuid}), data={ "Ds_MerchantParameters": b64_merchant_params, "Ds_Signature": self.settings.shared_secret, "Ds_SignatureVersion": "HMAC_SHA256_V1", }, ) assert response.status_code == status.HTTP_409_CONFLICT self.order.refresh_from_db() assert self.order.status == Order.Statuses.STATUS_ERROR def test_expiry_date(self): now = timezone.now() previous_year = str(now.year - 1).rjust(2, "0") current_month = str(now.month).rjust(2, "0") current_year = str(now.year)[-2:] assert not validate_expiry_date("042024") assert not validate_expiry_date(f"{previous_year}{current_month}") assert validate_expiry_date(f"{current_year}{current_month}") @patch("requests.post", redsys_response_ok) def test_redsys_rest_client(self): client = RedsysClient() params = client.pay_order_rest( self.order, pan="4548810000000003", expiry_date="122049", cvv2="285", ) assert params.get("Ds_Order") == "1446068581" assert params.get("Ds_MerchantCode") == self.settings.merchant_code @patch("requests.post", redsys_response_error) def test_redsys_rest_client_error(self): client = RedsysClient() with pytest.raises(RedsysPaymentException): client.pay_order_rest( self.order, pan="4548810000000003", expiry_date="122049", cvv2="285", )