python-redsys 1.2.2__py3-none-any.whl → 2.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,122 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: python-redsys
3
- Version: 1.2.2
4
- Summary: A simple, lightweight client to handle payments through the RedSys platform.
5
- License: MIT
6
- License-File: LICENSE
7
- Author: David Díaz
8
- Author-email: d.diazp@gmail.com
9
- Requires-Python: >=3.8,<4.0
10
- Classifier: Intended Audience :: Developers
11
- Classifier: License :: OSI Approved :: MIT License
12
- Classifier: Operating System :: OS Independent
13
- Classifier: Programming Language :: Python :: 3
14
- Classifier: Programming Language :: Python :: 3.8
15
- Classifier: Programming Language :: Python :: 3.9
16
- Classifier: Programming Language :: Python :: 3.10
17
- Classifier: Programming Language :: Python :: 3.11
18
- Classifier: Programming Language :: Python :: 3.12
19
- Classifier: Programming Language :: Python :: 3.13
20
- Classifier: Programming Language :: Python :: 3.14
21
- Classifier: Programming Language :: Python :: 3.15
22
- Requires-Dist: pycryptodome (>=3.19.1,<4.0.0)
23
- Project-URL: Homepage, https://github.com/ddiazpinto/python-redsys
24
- Description-Content-Type: text/markdown
25
-
26
- # Welcome to python-redsys!
27
-
28
- [![CI](https://github.com/ddiazpinto/python-redsys/actions/workflows/python-app.yml/badge.svg?branch=release/1.x)](https://github.com/ddiazpinto/python-redsys/actions/workflows/python-app.yml?query=branch%3Arelease%2F1.x) [![PyPI](https://img.shields.io/pypi/v/python-redsys.svg)](https://pypi.org/project/python-redsys/) ![Python 3.8+](https://img.shields.io/badge/python-3.8%2B-blue.svg) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/ddiazpinto/python-redsys/blob/release/1.x/LICENSE)
29
-
30
- A simple, lightweight client to handle payments through the RedSys platform, using its *redirect connection* (browser form POST).
31
-
32
- The library only builds the signed request and verifies/parses the response. It makes no network or browser calls of its own: the payment form is submitted from the cardholder's browser.
33
-
34
- ## Installation
35
-
36
- ```bash
37
- pip install "python-redsys>=1.2,<2"
38
-
39
- # or with Poetry
40
- poetry add "python-redsys@^1.2"
41
- ```
42
-
43
- Requires Python 3.8+. Runtime dependency: `pycryptodome`.
44
-
45
- ## Usage
46
-
47
- The client prepares a request from a dict of parameters and verifies the response. Parameter values are validated on construction (`ValueError` on an unknown parameter or an invalid value, `TypeError` when `amount` is not a `Decimal`); `order` has 12 characters (4 digits followed by 8 alphanumerics), and `amount` is a `Decimal`, serialized to integer minor units on the wire. The allowed values for `currency`, `transaction_type` and `consumer_language` are in `redsys.constants`.
48
-
49
- ## Using *redirect connection*
50
-
51
- ### 1. Build a signed request
52
-
53
- ```python
54
- from decimal import Decimal, ROUND_HALF_UP
55
- from redsys.client import RedirectClient
56
- from redsys.constants import EUR, STANDARD_PAYMENT
57
-
58
- client = RedirectClient(secret_key="sq7HjrUOBfKmC576ILgskD5srU870gJ7")
59
-
60
- post_parameters = client.prepare_request({
61
- "merchant_code": "999008881",
62
- "terminal": "1",
63
- "transaction_type": STANDARD_PAYMENT,
64
- "currency": EUR,
65
- "order": "000000000001",
66
- # The amount is a Decimal; pre-quantize to two decimals.
67
- "amount": Decimal("10.56489").quantize(Decimal(".01"), ROUND_HALF_UP),
68
- "merchant_url": "https://example.com/redsys/response",
69
- "url_ok": "https://example.com/ok",
70
- "url_ko": "https://example.com/ko",
71
- "merchant_name": "Example Commerce",
72
- "product_description": "Products of Example Commerce",
73
- }) # Ds_SignatureVersion / Ds_MerchantParameters / Ds_Signature
74
- ```
75
-
76
- The secret key, merchant code `999008881` and terminal `1` are the RedSys public test merchant's. `Ds_MerchantParameters` and `Ds_Signature` are returned as `bytes`; decode them before rendering them in an HTML form.
77
-
78
- Bizum: add `"payment_method": "z"` (and optionally `"mobile_number"`) to the parameters.
79
-
80
- ### 2. Communication step
81
-
82
- Auto-submit an HTML form (`method="POST"`) with the three `post_parameters` fields to the RedSys endpoint:
83
-
84
- - Test: `https://sis-t.redsys.es:25443/sis/realizarPago`
85
- - Production: `https://sis.redsys.es/sis/realizarPago`
86
-
87
- RedSys notifies the result both as a server-to-server POST to `merchant_url` and as a browser redirect to `url_ok` / `url_ko`.
88
-
89
- ### 3. Verify and read the response
90
-
91
- `create_response()` takes the received `Ds_Signature` and `Ds_MerchantParameters` and raises `ValueError` on signature mismatch.
92
-
93
- ```python
94
- response = client.create_response(signature, merchant_parameters) # as RedSys posted them back
95
-
96
- if response.is_paid:
97
- ... # actions after a successful payment
98
- else:
99
- raise Exception(f"declined with code {response.code}")
100
- ```
101
-
102
- Response predicates (per the RedSys documentation):
103
-
104
- - `response.is_paid`: response code between 0 and 99.
105
- - `response.is_canceled`: response code 400.
106
- - `response.is_refunded`: response code 900.
107
- - `response.is_authorized`: paid, refunded or canceled.
108
-
109
- `response.code` is the RedSys code as an `int`, and `response.message` its Spanish description (`None` for a code without one). Decoded fields (`response.order`, `response.amount` as a `Decimal`, ...) read as attributes, `None` when absent; `response.parameters` holds every decoded parameter: listed ones under their attribute name (`amount`), unlisted ones under their original `Ds_*` key.
110
-
111
- ## Signature version
112
-
113
- The 1.x line signs with `HMAC_SHA256_V1` (3DES + HMAC-SHA256), the legacy scheme still accepted by RedSys. `HMAC_SHA512_V1` requires 2.x.
114
-
115
- ## Support
116
-
117
- The 1.x line receives fixes until 2026-12-31.
118
-
119
- ## Contributing
120
-
121
- Contributions that keep the *lightweight* philosophy are welcome.
122
-
@@ -1,13 +0,0 @@
1
- redsys/__init__.py,sha256=k234cuEigYkeydQvG5NyAl4pPAyCQSjL65UvOkeTak8,138
2
- redsys/client.py,sha256=ervLX4FbzDfWjh_SO2SFyY9IT0PZ5aIcVZdYy4mUO7Q,3937
3
- redsys/constants.py,sha256=lJYgbmikxh0b9iVM4M5XFWBWVvdFY27OkcXSLluHSsQ,2497
4
- redsys/request.py,sha256=IEzlRi34JgDBRRyaBHtIrAZ38fADL977RdBFaeWDdbY,5304
5
- redsys/response.py,sha256=YDihLSIE388Ib53qr9PUtGsWqLqj1zFdjbgn9x_i-8A,5585
6
- redsys/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- redsys/tests/test_client.py,sha256=oND8W9y6O6MkDLNxnBpd8UDShwKWoAW3SgKmWDOXHrw,3858
8
- redsys/tests/test_request.py,sha256=FONigjMNdaug2DwillYKCLPK-gzIYZSVUZmcm9QkCms,3110
9
- redsys/tests/test_response.py,sha256=K1QP7eRyBum88LXG6IOQtno8FZNxoVHUv-GJU83k5OI,1777
10
- python_redsys-1.2.2.dist-info/METADATA,sha256=PXqNlioj4tNFrfUXhiPZ1P77pgIJne39Q_oD4sXto4U,5517
11
- python_redsys-1.2.2.dist-info/WHEEL,sha256=L-WvLdSBvJWHXv5zrQxDvNRnIgoBzLUpcbuiw19KCuQ,88
12
- python_redsys-1.2.2.dist-info/licenses/LICENSE,sha256=Ku6mJcUOcHd2aZ4o2Jh4Tjkx1oZT-iT0VCyUNvm2IXU,1257
13
- python_redsys-1.2.2.dist-info/RECORD,,
@@ -1,25 +0,0 @@
1
- MIT License
2
-
3
- The original development of python-redsys is copyrighted to David Díaz.
4
- Copyright (c) 2016 David Díaz
5
-
6
- Extensions to python-redsys are copyrighted to Andrés Reverón Molina.
7
- Copyright (c) 2021 Andrés Reverón Molina
8
-
9
- Permission is hereby granted, free of charge, to any person obtaining a copy
10
- of this software and associated documentation files (the "Software"), to deal
11
- in the Software without restriction, including without limitation the rights
12
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
- copies of the Software, and to permit persons to whom the Software is
14
- furnished to do so, subject to the following conditions:
15
-
16
- The above copyright notice and this permission notice shall be included in all
17
- copies or substantial portions of the Software.
18
-
19
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
- SOFTWARE.
redsys/client.py DELETED
@@ -1,105 +0,0 @@
1
- import base64
2
- import hashlib
3
- import hmac
4
- import json
5
- import re
6
- from abc import ABC
7
- from abc import abstractmethod
8
- from typing import Any
9
- from typing import Dict
10
-
11
- from Crypto.Cipher import DES3
12
-
13
- from redsys.request import Request
14
- from redsys.response import Response
15
-
16
- # Calculated parameters
17
- SIGNATURE_VERSION = "Ds_SignatureVersion"
18
- MERCHANT_PARAMETERS = "Ds_MerchantParameters"
19
- SIGNATURE = "Ds_Signature"
20
- DEFAULT_SIGNATURE_VERSION = "HMAC_SHA256_V1"
21
-
22
-
23
- class Client(ABC):
24
- """
25
- Abstract class from which RedirectClient inherits.
26
- It implements methods that may be used in future clients(i.e. rest).
27
- """
28
-
29
- def __init__(self, secret_key: str):
30
- self.secret_key: bytes = secret_key.encode()
31
-
32
- @abstractmethod
33
- def create_response(self, signature, parameters):
34
- raise NotImplementedError
35
-
36
- @abstractmethod
37
- def prepare_request(self, request):
38
- raise NotImplementedError
39
-
40
- @staticmethod
41
- def encode_parameters(parameters: Dict[str, Any]) -> bytes:
42
- """Encodes the merchant parameters in base64"""
43
- return base64.b64encode(json.dumps(parameters).encode())
44
-
45
- @staticmethod
46
- def decode_parameters(parameters: bytes) -> Dict[str, Any]:
47
- """Decodes the merchant parameters from base64"""
48
- return json.loads(base64.b64decode(parameters).decode())
49
-
50
- @staticmethod
51
- def sign_hmac256(encrypted_order: bytes, merchant_parameters: bytes) -> bytes:
52
- """
53
- Generates the encrypted signature using the 3DES-encrypted order
54
- and base64-encoded merchant parameters
55
- """
56
- signature = hmac.new(encrypted_order, merchant_parameters, hashlib.sha256).digest()
57
- return base64.b64encode(signature)
58
-
59
- def encrypt_3DES(self, order: str) -> bytes:
60
- """Encrypts(3DES algorithm) the payment order using the secret key"""
61
- cipher = DES3.new(base64.b64decode(self.secret_key), DES3.MODE_CBC, IV=b"\0\0\0\0\0\0\0\0")
62
- # the cipher needs to be passed 16 bytes,
63
- # so "order" must be 16 bytes long,
64
- # therefore we left-justify adding ceros
65
- return cipher.encrypt(order.encode().ljust(16, b"\0"))
66
-
67
- def generate_signature(self, order: str, merchant_parameters: bytes) -> bytes:
68
- return self.sign_hmac256(self.encrypt_3DES(order), merchant_parameters)
69
-
70
-
71
- class RedirectClient(Client):
72
- def prepare_request(self, parameters: Dict[str, Any]) -> Dict[str, Any]:
73
- """Takes the merchant parameters and returns the necessary parameters
74
- to make the POST request to Redsys"""
75
- request = Request(parameters)
76
- merchant_parameters = self.encode_parameters(request.prepare_parameters())
77
- signature = self.generate_signature(request.order, merchant_parameters)
78
- return {
79
- SIGNATURE_VERSION: DEFAULT_SIGNATURE_VERSION,
80
- MERCHANT_PARAMETERS: merchant_parameters,
81
- SIGNATURE: signature,
82
- }
83
-
84
- def create_response(
85
- self,
86
- signature: str,
87
- merchant_parameters: str,
88
- ) -> Response:
89
- """
90
- Decodes the Redsys response to check for validity.
91
- Checks if the received signature corresponds to the sent signature.
92
-
93
- Both the `signature` and `merchant parameters` are plain strings, not bytes.
94
- """
95
- decoded_parameters = self.decode_parameters(merchant_parameters.encode())
96
- response = Response(decoded_parameters)
97
- calculated_signature = self.generate_signature(response.order, merchant_parameters.encode())
98
- # Remove any non-alphanumeric characters from the signature
99
- not_alphanumeric = re.compile("[^a-zA-Z0-9]")
100
- safe_signature = re.sub(not_alphanumeric, "", signature)
101
-
102
- safe_calculated_signature = re.sub(not_alphanumeric, "", calculated_signature.decode())
103
- if safe_signature != safe_calculated_signature:
104
- raise ValueError("The provided signature is not valid.")
105
- return response
redsys/constants.py DELETED
@@ -1,113 +0,0 @@
1
- # Currencies
2
- """ Euro """
3
- EUR = 978
4
- """ United States dollar """
5
- USD = 840
6
- """ Pound sterling """
7
- GBP = 826
8
- """ Japanese yen """
9
- JPY = 392
10
- """ Argentine peso """
11
- ARS = 32
12
- """ Canadian dollar """
13
- CAD = 124
14
- """ Chilean Peso """
15
- CLP = 152
16
- """ Colombian peso """
17
- COP = 170
18
- """ Indian rupee """
19
- INR = 356
20
- """ Mexican peso """
21
- MXN = 484
22
- """ Peruvian Sol """
23
- PEN = 604
24
- """ Swiss franc """
25
- CHF = 756
26
- """ Brazilian real """
27
- BRL = 986
28
- """ Venezuelan bolivar """
29
- VEF = 937
30
- """ Turkish lira """
31
- TRY = 949
32
-
33
- CURRENCIES = [EUR, USD, GBP, JPY, ARS, CAD, CLP, COP, INR, MXN, PEN, CHF, BRL, VEF, TRY]
34
-
35
- # Languages
36
- CUSTOMER = "000"
37
- SPANISH = "001"
38
- ENGLISH = "002"
39
- CATALAN = "003"
40
- FRENCH = "004"
41
- GERMAN = "005"
42
- DUTCH = "006"
43
- ITALIAN = "007"
44
- SWEDISH = "008"
45
- PORTUGUESE = "009"
46
- VALENCIAN = "010"
47
- POLISH = "011"
48
- GALICIAN = "012"
49
- EUSKERA = "013"
50
-
51
- LANGUAGES = [
52
- CUSTOMER,
53
- SPANISH,
54
- ENGLISH,
55
- CATALAN,
56
- FRENCH,
57
- GERMAN,
58
- DUTCH,
59
- ITALIAN,
60
- SWEDISH,
61
- PORTUGUESE,
62
- VALENCIAN,
63
- POLISH,
64
- GALICIAN,
65
- EUSKERA,
66
- ]
67
-
68
- # Transaction types
69
- """ Standard payment """
70
- STANDARD_PAYMENT = "0"
71
- """ Pre-authorization """
72
- PREAUTHORIZATION = "1"
73
- """ Confirmation of pre-authorization """
74
- PREAUTHORIZATION_CONFIRMATION = "2"
75
- """ Partial or total refund """
76
- REFUND = "3"
77
- """ Recurring transaction """
78
- RECURRING_TRANSACTION = "5"
79
- """ Successive transaction """
80
- SUCCESSIVE_TRANSACTION = "6"
81
- """ Authentication """
82
- AUTHENTICATION = "7"
83
- """ Confirmation of authentication """
84
- AUTHENTICATION_CONFIRMATION = "8"
85
- """ Cancellation of pre-authorization """
86
- PREAUTHORIZATION_CANCELATION = "9"
87
- """ Deferred pre-authorization """
88
- DEFERRED_PREAUTHORIZATION = "O"
89
- """ Confirmation of deferred pre-authorization """
90
- DEFERRED_PREAUTHORIZATION_CONFIRMATION = "P"
91
- """ Cancelation of deferred pre-authorization """
92
- DEFERRED_PREAUTHORIZATION_CANCELATION = "Q"
93
- """ Recurring deferred pre-authorization """
94
- RECURRING_DEFERRED_PREAUTHORIZATION = "R"
95
- """ Confirmation of recurring deferred pre-authorization and successive transaction """
96
- SUCCESSIVE_RECURRING_TRANSACTION = "S"
97
-
98
- TRANSACTIONS = [
99
- STANDARD_PAYMENT,
100
- PREAUTHORIZATION,
101
- PREAUTHORIZATION_CONFIRMATION,
102
- REFUND,
103
- RECURRING_TRANSACTION,
104
- SUCCESSIVE_TRANSACTION,
105
- AUTHENTICATION,
106
- AUTHENTICATION_CONFIRMATION,
107
- PREAUTHORIZATION_CANCELATION,
108
- DEFERRED_PREAUTHORIZATION,
109
- DEFERRED_PREAUTHORIZATION_CONFIRMATION,
110
- DEFERRED_PREAUTHORIZATION_CANCELATION,
111
- RECURRING_DEFERRED_PREAUTHORIZATION,
112
- SUCCESSIVE_RECURRING_TRANSACTION,
113
- ]
redsys/response.py DELETED
@@ -1,149 +0,0 @@
1
- from decimal import Decimal
2
- from typing import Any
3
- from typing import Dict
4
-
5
- RESPONSE = "Ds_Response"
6
- DATE = "Ds_Date"
7
- HOUR = "Ds_Hour"
8
- MERCHANT_CODE = "Ds_MerchantCode"
9
- TERMINAL = "Ds_Terminal"
10
- TRANSACTION_TYPE = "Ds_TransactionType"
11
- ORDER = "Ds_Order"
12
- CURRENCY = "Ds_Currency"
13
- AMOUNT = "Ds_Amount"
14
- MERCHANT_DATA = "Ds_MerchantData"
15
- CONSUMER_LANGUAGE = "Ds_ConsumerLanguage"
16
- CARD_COUNTRY = "Ds_Card_Country"
17
- CARD_TYPE = "Ds_Card_Type"
18
- CARD_BRAND = "Ds_Card_Brand"
19
- MERCHANT_COF_TXNID = "Ds_Merchant_Cof_Txnid"
20
- SECURE_PAYMENT = "Ds_SecurePayment"
21
- AUTHORIZATION_CODE = "Ds_AuthorisationCode"
22
- ERROR_CODE = "Ds_ErrorCode"
23
- AMOUNT_EURO = "Ds_Amount_Euro"
24
- PROCESSED_PAY_METHOD = "Ds_ProcessedPayMethod"
25
-
26
- RESPONSE_MAP = {
27
- "0000": "Transacción autorizada para pagos y preautorizaciones",
28
- "900": "Transacción autorizada para devoluciones y confirmaciones",
29
- "400": "Transacción autorizada para anulaciones",
30
- "101": "Tarjeta caducada",
31
- "102": "Tarjeta en excepción transitoria o bajo sospecha de fraude",
32
- "106": "Intentos de PIN excedidos",
33
- "125": "Tarjeta no efectiva",
34
- "129": "Código de seguridad (CVV2/CVC2) incorrecto",
35
- "180": "Tarjeta ajena al servicio",
36
- "184": "Error en la autenticación del titular",
37
- "190": "Denegación del emisor sin especificar motivo",
38
- "191": "Fecha de caducidad errónea",
39
- "202": "Tarjeta en excepción transitoria o bajo sospecha de fraude con retirada de tarjeta",
40
- "904": "Comercio no registrado en FUC",
41
- "909": "Error de sistema",
42
- "912": "Emisor no disponible",
43
- "913": "Pedido repetido",
44
- "944": "Sesión Incorrecta",
45
- "950": "Operación de devolución no permitida",
46
- "9912": "Emisor no disponible",
47
- "9064": "Número de posiciones de la tarjeta incorrecto",
48
- "9078": "Tipo de operación no permitida para esa tarjeta",
49
- "9093": "Tarjeta no existente",
50
- "9094": "Rechazo servidores internacionales",
51
- "9104": "Comercio con “titular seguro” y titular sin clave de compra segura",
52
- "9218": "El comercio no permite op. seguras por entrada /operaciones",
53
- "9253": "Tarjeta no cumple el check-digit",
54
- "9256": "El comercio no puede realizar preautorizaciones",
55
- "9257": "Esta tarjeta no permite operativa de preautorizaciones",
56
- "9261": "Operación detenida por superar el control de restricciones en la entrada al SIS",
57
- "9913": "Error en la confirmación que el comercio envía al TPV Virtual",
58
- "9914": "Confirmación “KO” del comercio",
59
- "9915": "A petición del usuario se ha cancelado el pago",
60
- "9928": "Anulación de autorización en diferido realizada por el SIS (proceso batch)",
61
- "9929": "Anulación de autorización en diferido realizada por el comercio",
62
- "9997": "Se está procesando otra transacción en SIS con la misma tarjeta",
63
- "9998": "Operación en proceso de solicitud de datos de tarjeta",
64
- "9999": "Operación que ha sido redirigida al emisor a autenticar",
65
- }
66
-
67
- MERCHANT_PARAMETERS_MAP = {
68
- "response": RESPONSE,
69
- "date": DATE,
70
- "hour": HOUR,
71
- "merchant_code": MERCHANT_CODE,
72
- "terminal": TERMINAL,
73
- "transaction_type": TRANSACTION_TYPE,
74
- "order": ORDER,
75
- "currency": CURRENCY,
76
- "amount": AMOUNT,
77
- "merchant_data": MERCHANT_DATA,
78
- "consumer_language": CONSUMER_LANGUAGE,
79
- "card_country": CARD_COUNTRY,
80
- "card_type": CARD_TYPE,
81
- "card_brand": CARD_BRAND,
82
- "merchant_cof_txnid": MERCHANT_COF_TXNID,
83
- "secure_payment": SECURE_PAYMENT,
84
- "authorization_code": AUTHORIZATION_CODE,
85
- "error_code": ERROR_CODE,
86
- "amount_euro": AMOUNT_EURO,
87
- "processed_pay_method": PROCESSED_PAY_METHOD,
88
- }
89
-
90
-
91
- class Response:
92
- """
93
- Defines a response
94
- """
95
-
96
- _parameters: Dict[str, Any]
97
-
98
- def __init__(self, parameters: Dict[str, Any]):
99
- # __setattr__ only handles merchant parameters, so the per-instance store is set directly.
100
- object.__setattr__(self, "_parameters", {})
101
- MERCHANT_PARAMETERS_MAP_REVERSE = {value: key for key, value in MERCHANT_PARAMETERS_MAP.items()}
102
- for key, value in parameters.items():
103
- reversed_parameter = MERCHANT_PARAMETERS_MAP_REVERSE.get(key, key)
104
- clean = getattr(self, "clean_%s" % reversed_parameter, None)
105
- self._parameters[reversed_parameter] = clean(value) if clean else value
106
-
107
- def __getattr__(self, item: str) -> Any:
108
- if item in MERCHANT_PARAMETERS_MAP:
109
- return self._parameters.get(item)
110
-
111
- def __setattr__(self, key: str, value: Any):
112
- if key in MERCHANT_PARAMETERS_MAP:
113
- clean = getattr(self, "clean_%s" % MERCHANT_PARAMETERS_MAP[key], None)
114
- self._parameters[MERCHANT_PARAMETERS_MAP[key]] = clean(value) if clean else value
115
-
116
- @property
117
- def is_authorized(self):
118
- return (0 <= self.code <= 99) or self.code == 900 or self.code == 400
119
-
120
- @property
121
- def is_paid(self):
122
- return 0 <= self.code <= 99
123
-
124
- @property
125
- def is_refunded(self):
126
- return self.code == 900
127
-
128
- @property
129
- def is_canceled(self):
130
- return self.code == 400
131
-
132
- @property
133
- def code(self):
134
- return int(self.response)
135
-
136
- @property
137
- def message(self):
138
- if self.is_paid:
139
- return RESPONSE_MAP["0000"]
140
- # RedSys sends 4-digit codes ("0190"); the map keys drop the leading zeros ("190").
141
- return RESPONSE_MAP.get(self.response) or RESPONSE_MAP.get(str(self.code))
142
-
143
- @property
144
- def parameters(self):
145
- return self._parameters
146
-
147
- @staticmethod
148
- def clean_amount(value):
149
- return Decimal(f"{str(value)[:-2]}.{str(value)[-2:]}")
redsys/tests/__init__.py DELETED
File without changes
@@ -1,89 +0,0 @@
1
- from decimal import ROUND_HALF_UP
2
- from decimal import Decimal as D
3
-
4
- import pytest
5
-
6
- from redsys.client import RedirectClient
7
- from redsys.constants import EUR
8
- from redsys.constants import STANDARD_PAYMENT
9
- from redsys.request import Request
10
-
11
-
12
- class TestRedirectClient:
13
- @pytest.fixture(autouse=True)
14
- def set_up(self):
15
- secret_key = "sq7HjrUOBfKmC576ILgskD5srU870gJ7"
16
- self.client = RedirectClient(secret_key)
17
- self.parameters = {
18
- "merchant_code": "100000001",
19
- "terminal": "1",
20
- "transaction_type": STANDARD_PAYMENT,
21
- "currency": EUR,
22
- "order": "000000000001",
23
- "amount": D("10.56489").quantize(D(".01"), ROUND_HALF_UP),
24
- "merchant_data": "test merchant data",
25
- "merchant_name": "Example Commerce",
26
- "titular": "Example Ltd.",
27
- "product_description": "Products of Example Commerce",
28
- "merchant_url": "https://example.com/redsys/response",
29
- }
30
- self.request = Request(self.parameters)
31
- self.merchant_params = self.request.prepare_parameters()
32
-
33
- def test_encode_parameters(self):
34
- encoded_params = self.client.encode_parameters(self.merchant_params)
35
- encoded_params_str = encoded_params.decode("UTF-8")
36
- assert encoded_params_str.endswith("ZWRzeXMvcmVzcG9uc2UifQ==")
37
- assert len(encoded_params_str) == 628
38
-
39
- def test_decode_parameters(self):
40
- encoded_params = self.client.encode_parameters(self.merchant_params)
41
- decoded_params = self.client.decode_parameters(encoded_params)
42
- assert decoded_params == self.merchant_params
43
-
44
- def test_encrypt_3DES(self):
45
- encrypted_order = self.client.encrypt_3DES(self.request.order)
46
- assert len(encrypted_order) == 16
47
- assert str(encrypted_order) == "b'/\\x17\\\\6\\n\\x1aY\\xf3\\xd7\\xa5\\x97\\xb9\\xb6\\xe0\\x8dH'"
48
-
49
- def test_sign_hmac256(self):
50
- encrypted_order = self.client.encrypt_3DES(self.request.order)
51
- encoded_params = self.client.encode_parameters(self.merchant_params)
52
- signature = self.client.sign_hmac256(encrypted_order, encoded_params)
53
- assert signature == b"a2F05DwuH5aXu5vD1IdjQ+NxaR0lPsRwksW1nr2Nzuw="
54
-
55
- def test_prepare_request(self):
56
- prepared_request = self.client.prepare_request(self.parameters)
57
- assert len(prepared_request.keys()) == 3
58
- assert prepared_request.get("Ds_Signature") == b"a2F05DwuH5aXu5vD1IdjQ+NxaR0lPsRwksW1nr2Nzuw="
59
- pass
60
-
61
- def test_create_response_valid(self):
62
- merchant_parameters = {
63
- "Ds_MerchantCode": "100000001",
64
- "Ds_Terminal": "1",
65
- "Ds_TransactionType": "0",
66
- "Ds_Currency": 978,
67
- "Ds_Order": "000000000001",
68
- "Ds_Amount": 1056,
69
- "Ds_MerchantData": "test merchant data",
70
- }
71
- encoded_params = self.client.encode_parameters(merchant_parameters)
72
- signature = self.client.generate_signature(self.request.order, encoded_params)
73
- response = self.client.create_response(signature.decode(), encoded_params.decode())
74
- assert response._parameters.get("order") == merchant_parameters.get("Ds_Order")
75
-
76
- def test_create_response_invalid(self):
77
- merchant_parameters = {
78
- "Ds_MerchantCode": "100000001",
79
- "Ds_Terminal": "1",
80
- "Ds_TransactionType": "0",
81
- "Ds_Currency": 978,
82
- "Ds_Order": "000000000001",
83
- "Ds_Amount": 1056,
84
- "Ds_MerchantData": "test merchant data",
85
- }
86
- encoded_params = self.client.encode_parameters(merchant_parameters)
87
- bad_signature = b"sadfe2r3q2fdssaf3"
88
- with pytest.raises(ValueError, match="The provided signature is not valid"):
89
- self.client.create_response(bad_signature.decode(), encoded_params.decode())