python-redsys 1.1.1__tar.gz → 1.2.1__tar.gz
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.
- {python-redsys-1.1.1 → python_redsys-1.2.1}/PKG-INFO +14 -9
- python_redsys-1.2.1/pyproject.toml +54 -0
- python_redsys-1.2.1/redsys/__init__.py +2 -0
- {python-redsys-1.1.1 → python_redsys-1.2.1}/redsys/client.py +8 -14
- {python-redsys-1.1.1 → python_redsys-1.2.1}/redsys/request.py +17 -8
- {python-redsys-1.1.1 → python_redsys-1.2.1}/redsys/response.py +9 -10
- {python-redsys-1.1.1 → python_redsys-1.2.1}/redsys/tests/test_client.py +10 -17
- {python-redsys-1.1.1 → python_redsys-1.2.1}/redsys/tests/test_request.py +16 -12
- {python-redsys-1.1.1 → python_redsys-1.2.1}/redsys/tests/test_response.py +11 -4
- python-redsys-1.1.1/pyproject.toml +0 -70
- python-redsys-1.1.1/redsys/__init__.py +0 -5
- python-redsys-1.1.1/setup.py +0 -30
- {python-redsys-1.1.1 → python_redsys-1.2.1}/LICENSE +0 -0
- {python-redsys-1.1.1 → python_redsys-1.2.1}/README.md +0 -0
- {python-redsys-1.1.1 → python_redsys-1.2.1}/redsys/constants.py +0 -0
- {python-redsys-1.1.1 → python_redsys-1.2.1}/redsys/tests/__init__.py +0 -0
|
@@ -1,21 +1,26 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: python-redsys
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.1
|
|
4
4
|
Summary: A simple, clean and less dependant client to handle payments through Redsys.
|
|
5
|
-
Home-page: https://github.com/systemallica/python-redsys
|
|
6
5
|
License: MIT
|
|
7
|
-
|
|
8
|
-
Author
|
|
9
|
-
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Author: David Díaz
|
|
8
|
+
Author-email: d.diazp@gmail.com
|
|
9
|
+
Requires-Python: >=3.8,<4.0
|
|
10
10
|
Classifier: Intended Audience :: Developers
|
|
11
11
|
Classifier: License :: OSI Approved :: MIT License
|
|
12
12
|
Classifier: Operating System :: OS Independent
|
|
13
13
|
Classifier: Programming Language :: Python :: 3
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
16
14
|
Classifier: Programming Language :: Python :: 3.8
|
|
17
15
|
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
-
|
|
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.0,<4.0.0)
|
|
23
|
+
Project-URL: Homepage, https://github.com/ddiazpinto/python-redsys
|
|
19
24
|
Description-Content-Type: text/markdown
|
|
20
25
|
|
|
21
26
|
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "python-redsys"
|
|
3
|
+
version = "1.2.1"
|
|
4
|
+
description = "A simple, clean and less dependant client to handle payments through Redsys."
|
|
5
|
+
authors = ["David Díaz <d.diazp@gmail.com>", "Andrés Reverón Molina <andres@reveronmolina.me>"]
|
|
6
|
+
license = "MIT"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
homepage = "https://github.com/ddiazpinto/python-redsys"
|
|
9
|
+
classifiers = [
|
|
10
|
+
'Intended Audience :: Developers',
|
|
11
|
+
'License :: OSI Approved :: MIT License',
|
|
12
|
+
'Operating System :: OS Independent',
|
|
13
|
+
]
|
|
14
|
+
packages = [
|
|
15
|
+
{ include = "redsys" }
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[tool.poetry.dependencies]
|
|
19
|
+
python = "^3.8"
|
|
20
|
+
pycryptodome = "^3.19.0"
|
|
21
|
+
|
|
22
|
+
[tool.poetry.dev-dependencies]
|
|
23
|
+
pytest = "^7.4.2"
|
|
24
|
+
pre-commit = "^3.5.0"
|
|
25
|
+
pytest-cov = "^4.1.0"
|
|
26
|
+
mypy = "^1.6.1"
|
|
27
|
+
python-semantic-release = "^7.34.6"
|
|
28
|
+
ruff = "^0.1.3"
|
|
29
|
+
|
|
30
|
+
[tool.ruff]
|
|
31
|
+
line-length = 120
|
|
32
|
+
|
|
33
|
+
[tool.ruff.lint]
|
|
34
|
+
select = ["E","F","UP","B","SIM","I"]
|
|
35
|
+
|
|
36
|
+
[tool.ruff.format]
|
|
37
|
+
quote-style = "double"
|
|
38
|
+
indent-style = "space"
|
|
39
|
+
|
|
40
|
+
[tool.ruff.isort]
|
|
41
|
+
force-single-line = true
|
|
42
|
+
|
|
43
|
+
[tool.semantic_release]
|
|
44
|
+
version_variable = [
|
|
45
|
+
"redsys/__init__.py:__version__",
|
|
46
|
+
]
|
|
47
|
+
version_toml = ["pyproject.toml:tool.poetry.version"]
|
|
48
|
+
upload_to_pypi = true
|
|
49
|
+
upload_to_release = true
|
|
50
|
+
build_command = "poetry build"
|
|
51
|
+
|
|
52
|
+
[build-system]
|
|
53
|
+
requires = ["poetry-core>=1.0.0"]
|
|
54
|
+
build-backend = "poetry.core.masonry.api"
|
|
@@ -3,8 +3,10 @@ import hashlib
|
|
|
3
3
|
import hmac
|
|
4
4
|
import json
|
|
5
5
|
import re
|
|
6
|
-
from abc import ABC
|
|
7
|
-
from
|
|
6
|
+
from abc import ABC
|
|
7
|
+
from abc import abstractmethod
|
|
8
|
+
from typing import Any
|
|
9
|
+
from typing import Dict
|
|
8
10
|
|
|
9
11
|
from Crypto.Cipher import DES3
|
|
10
12
|
|
|
@@ -51,16 +53,12 @@ class Client(ABC):
|
|
|
51
53
|
Generates the encrypted signature using the 3DES-encrypted order
|
|
52
54
|
and base64-encoded merchant parameters
|
|
53
55
|
"""
|
|
54
|
-
signature = hmac.new(
|
|
55
|
-
encrypted_order, merchant_parameters, hashlib.sha256
|
|
56
|
-
).digest()
|
|
56
|
+
signature = hmac.new(encrypted_order, merchant_parameters, hashlib.sha256).digest()
|
|
57
57
|
return base64.b64encode(signature)
|
|
58
58
|
|
|
59
59
|
def encrypt_3DES(self, order: str) -> bytes:
|
|
60
60
|
"""Encrypts(3DES algorithm) the payment order using the secret key"""
|
|
61
|
-
cipher = DES3.new(
|
|
62
|
-
base64.b64decode(self.secret_key), DES3.MODE_CBC, IV=b"\0\0\0\0\0\0\0\0"
|
|
63
|
-
)
|
|
61
|
+
cipher = DES3.new(base64.b64decode(self.secret_key), DES3.MODE_CBC, IV=b"\0\0\0\0\0\0\0\0")
|
|
64
62
|
# the cipher needs to be passed 16 bytes,
|
|
65
63
|
# so "order" must be 16 bytes long,
|
|
66
64
|
# therefore we left-justify adding ceros
|
|
@@ -96,16 +94,12 @@ class RedirectClient(Client):
|
|
|
96
94
|
"""
|
|
97
95
|
decoded_parameters = self.decode_parameters(merchant_parameters.encode())
|
|
98
96
|
response = Response(decoded_parameters)
|
|
99
|
-
calculated_signature = self.generate_signature(
|
|
100
|
-
response.order, merchant_parameters.encode()
|
|
101
|
-
)
|
|
97
|
+
calculated_signature = self.generate_signature(response.order, merchant_parameters.encode())
|
|
102
98
|
# Remove any non-alphanumeric characters from the signature
|
|
103
99
|
not_alphanumeric = re.compile("[^a-zA-Z0-9]")
|
|
104
100
|
safe_signature = re.sub(not_alphanumeric, "", signature)
|
|
105
101
|
|
|
106
|
-
safe_calculated_signature = re.sub(
|
|
107
|
-
not_alphanumeric, "", calculated_signature.decode()
|
|
108
|
-
)
|
|
102
|
+
safe_calculated_signature = re.sub(not_alphanumeric, "", calculated_signature.decode())
|
|
109
103
|
if safe_signature != safe_calculated_signature:
|
|
110
104
|
raise ValueError("The provided signature is not valid.")
|
|
111
105
|
return response
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import re
|
|
2
2
|
from decimal import Decimal
|
|
3
|
-
from typing import Any
|
|
3
|
+
from typing import Any
|
|
4
|
+
from typing import Dict
|
|
4
5
|
|
|
5
|
-
from redsys.constants import CURRENCIES
|
|
6
|
+
from redsys.constants import CURRENCIES
|
|
7
|
+
from redsys.constants import LANGUAGES
|
|
8
|
+
from redsys.constants import TRANSACTIONS
|
|
6
9
|
|
|
7
10
|
# General parameters
|
|
8
11
|
MERCHANT_CODE = "Ds_Merchant_MerchantCode"
|
|
@@ -36,6 +39,10 @@ PAN = "Ds_Merchant_Pan"
|
|
|
36
39
|
EXPIRY_DATE = "Ds_Merchant_ExpiryDate"
|
|
37
40
|
CVV2 = "Ds_Merchant_Cvv2"
|
|
38
41
|
|
|
42
|
+
# BIZUM PAYMENT parameters
|
|
43
|
+
MOBILE_NUMBER = "Ds_Merchant_Bizum_MobileNumber"
|
|
44
|
+
PAYMENT_METHOD = "Ds_Merchant_PayMethods"
|
|
45
|
+
|
|
39
46
|
MERCHANT_PARAMETERS_MAP = {
|
|
40
47
|
"merchant_code": MERCHANT_CODE,
|
|
41
48
|
"terminal": TERMINAL,
|
|
@@ -59,6 +66,8 @@ MERCHANT_PARAMETERS_MAP = {
|
|
|
59
66
|
"pan": PAN,
|
|
60
67
|
"expiry_date": EXPIRY_DATE,
|
|
61
68
|
"cvv2": CVV2,
|
|
69
|
+
"mobile_number": MOBILE_NUMBER,
|
|
70
|
+
"payment_method": PAYMENT_METHOD,
|
|
62
71
|
}
|
|
63
72
|
|
|
64
73
|
|
|
@@ -68,9 +77,11 @@ class Request:
|
|
|
68
77
|
their values according to the platform specifications
|
|
69
78
|
"""
|
|
70
79
|
|
|
71
|
-
_parameters: Dict[str, Any]
|
|
80
|
+
_parameters: Dict[str, Any]
|
|
72
81
|
|
|
73
82
|
def __init__(self, parameters: Dict[str, Any]) -> None:
|
|
83
|
+
# __setattr__ only handles merchant parameters, so the per-instance store is set directly.
|
|
84
|
+
object.__setattr__(self, "_parameters", {})
|
|
74
85
|
for key, value in parameters.items():
|
|
75
86
|
if key in MERCHANT_PARAMETERS_MAP:
|
|
76
87
|
if check := getattr(self, f"check_{str(key)}", None):
|
|
@@ -81,7 +92,7 @@ class Request:
|
|
|
81
92
|
|
|
82
93
|
def __getattr__(self, item: str) -> Any:
|
|
83
94
|
if item in MERCHANT_PARAMETERS_MAP:
|
|
84
|
-
return self._parameters
|
|
95
|
+
return self._parameters.get(item)
|
|
85
96
|
|
|
86
97
|
def __setattr__(self, key, value):
|
|
87
98
|
if key in MERCHANT_PARAMETERS_MAP:
|
|
@@ -93,9 +104,7 @@ class Request:
|
|
|
93
104
|
parameters = {}
|
|
94
105
|
for key, value in self._parameters.items():
|
|
95
106
|
prepare = getattr(self, "prepare_%s" % key, None)
|
|
96
|
-
parameters[MERCHANT_PARAMETERS_MAP[key]] = (
|
|
97
|
-
prepare(value) if prepare else value
|
|
98
|
-
)
|
|
107
|
+
parameters[MERCHANT_PARAMETERS_MAP[key]] = prepare(value) if prepare else value
|
|
99
108
|
return parameters
|
|
100
109
|
|
|
101
110
|
@staticmethod
|
|
@@ -108,7 +117,7 @@ class Request:
|
|
|
108
117
|
|
|
109
118
|
@staticmethod
|
|
110
119
|
def check_order(value):
|
|
111
|
-
if not re.match(r"[0-9]{4}[a-zA-Z0-9]{
|
|
120
|
+
if not re.match(r"[0-9]{4}[a-zA-Z0-9]{8}$", value):
|
|
112
121
|
raise ValueError("order format is not valid.")
|
|
113
122
|
|
|
114
123
|
@staticmethod
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from decimal import Decimal
|
|
2
|
-
from typing import Any
|
|
2
|
+
from typing import Any
|
|
3
|
+
from typing import Dict
|
|
3
4
|
|
|
4
5
|
RESPONSE = "Ds_Response"
|
|
5
6
|
DATE = "Ds_Date"
|
|
@@ -92,12 +93,12 @@ class Response:
|
|
|
92
93
|
Defines a response
|
|
93
94
|
"""
|
|
94
95
|
|
|
95
|
-
_parameters: Dict[str, Any]
|
|
96
|
+
_parameters: Dict[str, Any]
|
|
96
97
|
|
|
97
98
|
def __init__(self, parameters: Dict[str, Any]):
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
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()}
|
|
101
102
|
for key, value in parameters.items():
|
|
102
103
|
reversed_parameter = MERCHANT_PARAMETERS_MAP_REVERSE.get(key, key)
|
|
103
104
|
clean = getattr(self, "clean_%s" % reversed_parameter, None)
|
|
@@ -105,14 +106,12 @@ class Response:
|
|
|
105
106
|
|
|
106
107
|
def __getattr__(self, item: str) -> Any:
|
|
107
108
|
if item in MERCHANT_PARAMETERS_MAP:
|
|
108
|
-
return self._parameters
|
|
109
|
+
return self._parameters.get(item)
|
|
109
110
|
|
|
110
111
|
def __setattr__(self, key: str, value: Any):
|
|
111
112
|
if key in MERCHANT_PARAMETERS_MAP:
|
|
112
113
|
clean = getattr(self, "clean_%s" % MERCHANT_PARAMETERS_MAP[key], None)
|
|
113
|
-
self._parameters[MERCHANT_PARAMETERS_MAP[key]] = (
|
|
114
|
-
clean(value) if clean else value
|
|
115
|
-
)
|
|
114
|
+
self._parameters[MERCHANT_PARAMETERS_MAP[key]] = clean(value) if clean else value
|
|
116
115
|
|
|
117
116
|
@property
|
|
118
117
|
def is_authorized(self):
|
|
@@ -144,4 +143,4 @@ class Response:
|
|
|
144
143
|
|
|
145
144
|
@staticmethod
|
|
146
145
|
def clean_amount(value):
|
|
147
|
-
return Decimal("{
|
|
146
|
+
return Decimal(f"{str(value)[:-2]}.{str(value)[-2:]}")
|
|
@@ -4,7 +4,8 @@ from decimal import Decimal as D
|
|
|
4
4
|
import pytest
|
|
5
5
|
|
|
6
6
|
from redsys.client import RedirectClient
|
|
7
|
-
from redsys.constants import EUR
|
|
7
|
+
from redsys.constants import EUR
|
|
8
|
+
from redsys.constants import STANDARD_PAYMENT
|
|
8
9
|
from redsys.request import Request
|
|
9
10
|
|
|
10
11
|
|
|
@@ -18,7 +19,7 @@ class TestRedirectClient:
|
|
|
18
19
|
"terminal": "1",
|
|
19
20
|
"transaction_type": STANDARD_PAYMENT,
|
|
20
21
|
"currency": EUR,
|
|
21
|
-
"order": "
|
|
22
|
+
"order": "000000000001",
|
|
22
23
|
"amount": D("10.56489").quantize(D(".01"), ROUND_HALF_UP),
|
|
23
24
|
"merchant_data": "test merchant data",
|
|
24
25
|
"merchant_name": "Example Commerce",
|
|
@@ -33,7 +34,7 @@ class TestRedirectClient:
|
|
|
33
34
|
encoded_params = self.client.encode_parameters(self.merchant_params)
|
|
34
35
|
encoded_params_str = encoded_params.decode("UTF-8")
|
|
35
36
|
assert encoded_params_str.endswith("ZWRzeXMvcmVzcG9uc2UifQ==")
|
|
36
|
-
assert len(encoded_params_str) ==
|
|
37
|
+
assert len(encoded_params_str) == 628
|
|
37
38
|
|
|
38
39
|
def test_decode_parameters(self):
|
|
39
40
|
encoded_params = self.client.encode_parameters(self.merchant_params)
|
|
@@ -43,24 +44,18 @@ class TestRedirectClient:
|
|
|
43
44
|
def test_encrypt_3DES(self):
|
|
44
45
|
encrypted_order = self.client.encrypt_3DES(self.request.order)
|
|
45
46
|
assert len(encrypted_order) == 16
|
|
46
|
-
assert (
|
|
47
|
-
str(encrypted_order)
|
|
48
|
-
== 'b"/\\x17\\\\6\\n\\x1aY\\xf3\\xb7\\\\\\xde\'d\\xa5\\x03\\xb7"'
|
|
49
|
-
)
|
|
47
|
+
assert str(encrypted_order) == "b'/\\x17\\\\6\\n\\x1aY\\xf3\\xd7\\xa5\\x97\\xb9\\xb6\\xe0\\x8dH'"
|
|
50
48
|
|
|
51
49
|
def test_sign_hmac256(self):
|
|
52
50
|
encrypted_order = self.client.encrypt_3DES(self.request.order)
|
|
53
51
|
encoded_params = self.client.encode_parameters(self.merchant_params)
|
|
54
52
|
signature = self.client.sign_hmac256(encrypted_order, encoded_params)
|
|
55
|
-
assert signature == b"
|
|
53
|
+
assert signature == b"a2F05DwuH5aXu5vD1IdjQ+NxaR0lPsRwksW1nr2Nzuw="
|
|
56
54
|
|
|
57
55
|
def test_prepare_request(self):
|
|
58
56
|
prepared_request = self.client.prepare_request(self.parameters)
|
|
59
57
|
assert len(prepared_request.keys()) == 3
|
|
60
|
-
assert (
|
|
61
|
-
prepared_request.get("Ds_Signature")
|
|
62
|
-
== b"eEW4YN7/kkNHRO0/HhXy9TppzHwF38+eZApKAagDI9A="
|
|
63
|
-
)
|
|
58
|
+
assert prepared_request.get("Ds_Signature") == b"a2F05DwuH5aXu5vD1IdjQ+NxaR0lPsRwksW1nr2Nzuw="
|
|
64
59
|
pass
|
|
65
60
|
|
|
66
61
|
def test_create_response_valid(self):
|
|
@@ -69,15 +64,13 @@ class TestRedirectClient:
|
|
|
69
64
|
"Ds_Terminal": "1",
|
|
70
65
|
"Ds_TransactionType": "0",
|
|
71
66
|
"Ds_Currency": 978,
|
|
72
|
-
"Ds_Order": "
|
|
67
|
+
"Ds_Order": "000000000001",
|
|
73
68
|
"Ds_Amount": 1056,
|
|
74
69
|
"Ds_MerchantData": "test merchant data",
|
|
75
70
|
}
|
|
76
71
|
encoded_params = self.client.encode_parameters(merchant_parameters)
|
|
77
72
|
signature = self.client.generate_signature(self.request.order, encoded_params)
|
|
78
|
-
response = self.client.create_response(
|
|
79
|
-
signature.decode(), encoded_params.decode()
|
|
80
|
-
)
|
|
73
|
+
response = self.client.create_response(signature.decode(), encoded_params.decode())
|
|
81
74
|
assert response._parameters.get("order") == merchant_parameters.get("Ds_Order")
|
|
82
75
|
|
|
83
76
|
def test_create_response_invalid(self):
|
|
@@ -86,7 +79,7 @@ class TestRedirectClient:
|
|
|
86
79
|
"Ds_Terminal": "1",
|
|
87
80
|
"Ds_TransactionType": "0",
|
|
88
81
|
"Ds_Currency": 978,
|
|
89
|
-
"Ds_Order": "
|
|
82
|
+
"Ds_Order": "000000000001",
|
|
90
83
|
"Ds_Amount": 1056,
|
|
91
84
|
"Ds_MerchantData": "test merchant data",
|
|
92
85
|
}
|
|
@@ -4,7 +4,8 @@ from random import choice
|
|
|
4
4
|
|
|
5
5
|
import pytest
|
|
6
6
|
|
|
7
|
-
from redsys.constants import EUR
|
|
7
|
+
from redsys.constants import EUR
|
|
8
|
+
from redsys.constants import STANDARD_PAYMENT
|
|
8
9
|
from redsys.request import Request
|
|
9
10
|
|
|
10
11
|
|
|
@@ -15,7 +16,7 @@ class TestRequest:
|
|
|
15
16
|
"terminal": "1",
|
|
16
17
|
"transaction_type": STANDARD_PAYMENT,
|
|
17
18
|
"currency": EUR,
|
|
18
|
-
"order": "
|
|
19
|
+
"order": "000000000001",
|
|
19
20
|
"amount": D("10.56489").quantize(D(".01"), ROUND_HALF_UP),
|
|
20
21
|
"merchant_data": "test merchant data",
|
|
21
22
|
"merchant_name": "Example Commerce",
|
|
@@ -41,7 +42,7 @@ class TestRequest:
|
|
|
41
42
|
"terminal": "1",
|
|
42
43
|
"transaction_type": STANDARD_PAYMENT,
|
|
43
44
|
"currency": EUR,
|
|
44
|
-
"order": "
|
|
45
|
+
"order": "000000000001",
|
|
45
46
|
"amount": D("10.56489").quantize(D(".01"), ROUND_HALF_UP),
|
|
46
47
|
"merchant_data": "test merchant data",
|
|
47
48
|
"merchant_name": "Example Commerce",
|
|
@@ -51,20 +52,23 @@ class TestRequest:
|
|
|
51
52
|
}
|
|
52
53
|
prepared_parameters = Request(parameters).prepare_parameters()
|
|
53
54
|
assert len(prepared_parameters.keys()) == 11
|
|
54
|
-
assert (
|
|
55
|
-
prepared_parameters.get("Ds_Merchant_MerchantData") == "test merchant data"
|
|
56
|
-
)
|
|
55
|
+
assert prepared_parameters.get("Ds_Merchant_MerchantData") == "test merchant data"
|
|
57
56
|
|
|
58
57
|
def test_super_long_merchant_data_throws_error(self):
|
|
59
|
-
merchant_data = "".join(
|
|
60
|
-
choice("abcdefghijklmnopqrtsuvwxyz-0123456789") for _ in range(1025)
|
|
61
|
-
)
|
|
58
|
+
merchant_data = "".join(choice("abcdefghijklmnopqrtsuvwxyz-0123456789") for _ in range(1025))
|
|
62
59
|
with pytest.raises(ValueError):
|
|
63
60
|
assert Request.check_merchant_data(merchant_data)
|
|
64
61
|
|
|
65
62
|
def test_super_long_merchant_url_throws_error(self):
|
|
66
|
-
merchant_url = "".join(
|
|
67
|
-
choice("abcdefghijklmnopqrtsuvwxyz-0123456789") for _ in range(251)
|
|
68
|
-
)
|
|
63
|
+
merchant_url = "".join(choice("abcdefghijklmnopqrtsuvwxyz-0123456789") for _ in range(251))
|
|
69
64
|
with pytest.raises(ValueError):
|
|
70
65
|
assert Request.check_merchant_url(merchant_url)
|
|
66
|
+
|
|
67
|
+
def test_requests_do_not_share_parameters(self):
|
|
68
|
+
first = Request(
|
|
69
|
+
{"order": "000000000001", "amount": D("1.00"), "currency": EUR, "mobile_number": "+34700000000"}
|
|
70
|
+
)
|
|
71
|
+
second = Request({"order": "000000000002", "amount": D("2.00"), "currency": EUR})
|
|
72
|
+
assert "Ds_Merchant_Bizum_MobileNumber" not in second.prepare_parameters()
|
|
73
|
+
assert second.mobile_number is None
|
|
74
|
+
assert first.prepare_parameters()["Ds_Merchant_Bizum_MobileNumber"] == "+34700000000"
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from decimal import Decimal
|
|
2
|
+
|
|
1
3
|
from redsys.response import Response
|
|
2
4
|
|
|
3
5
|
|
|
@@ -8,18 +10,23 @@ class TestResponse:
|
|
|
8
10
|
"Ds_MerchantCode": "1056",
|
|
9
11
|
"Ds_Terminal": "1",
|
|
10
12
|
"Ds_TransactionType": "1",
|
|
11
|
-
"Ds_Order": "
|
|
13
|
+
"Ds_Order": "000000000001",
|
|
12
14
|
"Ds_Amount": "10054",
|
|
13
15
|
"Ds_MerchantData": "test merchant data",
|
|
14
16
|
"Ds_Currency": 978,
|
|
15
17
|
}
|
|
16
18
|
response = Response(parameters)
|
|
17
19
|
assert response.code == 90
|
|
18
|
-
assert
|
|
19
|
-
response.message == "Transacción autorizada para pagos y preautorizaciones"
|
|
20
|
-
)
|
|
20
|
+
assert response.message == "Transacción autorizada para pagos y preautorizaciones"
|
|
21
21
|
assert len(response._parameters.keys()) == 8
|
|
22
22
|
assert response.is_authorized is True
|
|
23
23
|
assert response.is_paid is True
|
|
24
24
|
assert response.is_canceled is False
|
|
25
25
|
assert response.is_refunded is False
|
|
26
|
+
|
|
27
|
+
def test_responses_do_not_share_parameters(self):
|
|
28
|
+
first = Response({"Ds_Response": "0000", "Ds_Order": "000000000001", "Ds_Amount": "100"})
|
|
29
|
+
second = Response({"Ds_Response": "0190", "Ds_Order": "000000000002"})
|
|
30
|
+
assert "amount" not in second.parameters
|
|
31
|
+
assert second.amount is None
|
|
32
|
+
assert first.amount == Decimal("1.00")
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
[tool.poetry]
|
|
2
|
-
name = "python-redsys"
|
|
3
|
-
version = "1.1.1"
|
|
4
|
-
description = "A simple, clean and less dependant client to handle payments through Redsys."
|
|
5
|
-
authors = ["Andrés Reverón Molina <andres@reveronmolina.me>", "David Díaz <d.diazp@gmail.com>"]
|
|
6
|
-
license = "MIT"
|
|
7
|
-
readme = "README.md"
|
|
8
|
-
homepage = "https://github.com/systemallica/python-redsys"
|
|
9
|
-
classifiers = [
|
|
10
|
-
'Intended Audience :: Developers',
|
|
11
|
-
'License :: OSI Approved :: MIT License',
|
|
12
|
-
'Operating System :: OS Independent',
|
|
13
|
-
]
|
|
14
|
-
packages = [
|
|
15
|
-
{ include = "redsys" }
|
|
16
|
-
]
|
|
17
|
-
|
|
18
|
-
[tool.poetry.dependencies]
|
|
19
|
-
python = "^3.7"
|
|
20
|
-
pycryptodome = "^3.12.0"
|
|
21
|
-
|
|
22
|
-
[tool.poetry.dev-dependencies]
|
|
23
|
-
black = "^21.12b0"
|
|
24
|
-
isort = "^5.10.1"
|
|
25
|
-
pytest = "^6.2.5"
|
|
26
|
-
flake8 = "^4.0.1"
|
|
27
|
-
pre-commit = "^2.16.0"
|
|
28
|
-
pytest-cov = "^3.0.0"
|
|
29
|
-
mypy = "^0.930"
|
|
30
|
-
python-semantic-release = "^7.23.0"
|
|
31
|
-
pyupgrade = "^2.31.0"
|
|
32
|
-
|
|
33
|
-
[tool.isort]
|
|
34
|
-
multi_line_output = 3
|
|
35
|
-
include_trailing_comma = true
|
|
36
|
-
force_grid_wrap = 0
|
|
37
|
-
use_parentheses = true
|
|
38
|
-
ensure_newline_before_comments = true
|
|
39
|
-
line_length = 88
|
|
40
|
-
|
|
41
|
-
[tool.black]
|
|
42
|
-
line-length = 88
|
|
43
|
-
include = '\.pyi?$'
|
|
44
|
-
exclude = '''
|
|
45
|
-
/(
|
|
46
|
-
\.git
|
|
47
|
-
| \.hg
|
|
48
|
-
| \.mypy_cache
|
|
49
|
-
| \.tox
|
|
50
|
-
| \.venv
|
|
51
|
-
| _build
|
|
52
|
-
| buck-out
|
|
53
|
-
| build
|
|
54
|
-
| dist
|
|
55
|
-
| node_modules
|
|
56
|
-
)/
|
|
57
|
-
'''
|
|
58
|
-
|
|
59
|
-
[tool.semantic_release]
|
|
60
|
-
version_variable = [
|
|
61
|
-
"redsys/__init__.py:__version__",
|
|
62
|
-
]
|
|
63
|
-
version_toml = ["pyproject.toml:tool.poetry.version"]
|
|
64
|
-
upload_to_pypi = true
|
|
65
|
-
upload_to_release = true
|
|
66
|
-
build_command = "poetry build"
|
|
67
|
-
|
|
68
|
-
[build-system]
|
|
69
|
-
requires = ["poetry-core>=1.0.0"]
|
|
70
|
-
build-backend = "poetry.core.masonry.api"
|
python-redsys-1.1.1/setup.py
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
from setuptools import setup
|
|
3
|
-
|
|
4
|
-
packages = \
|
|
5
|
-
['redsys', 'redsys.tests']
|
|
6
|
-
|
|
7
|
-
package_data = \
|
|
8
|
-
{'': ['*']}
|
|
9
|
-
|
|
10
|
-
install_requires = \
|
|
11
|
-
['pycryptodome>=3.12.0,<4.0.0']
|
|
12
|
-
|
|
13
|
-
setup_kwargs = {
|
|
14
|
-
'name': 'python-redsys',
|
|
15
|
-
'version': '1.1.1',
|
|
16
|
-
'description': 'A simple, clean and less dependant client to handle payments through Redsys.',
|
|
17
|
-
'long_description': '\n[](https://badge.fury.io/py/python-redsys)\n\n# Welcome to python-redsys!\n\nA simple, clean and less dependant client to handle payments through the\nRedsys platform using one of the available methods: _redirect connection_ or (secure method).\n\nThe purpose of this library is to provide a normalized interface between\nRedsys and other Python applications.\n\n**About RedirectClient**\n\nAlthough _redirect connection_ depends on a webserver to resolve the\ncommunication step, the RedirectClient provided in this library does not\nassume any kind of procedure to resolve that step; it merely prepares\nthe necessary parameters to make a request and handles the corresponding\nresponse parameters. That\'s what less dependant means.\n\n## Example using _redirect connection_\n\n### 0. Install python-redsys\n\nYou can add python-redsys to your project with pip:\n> pip install python-redsys\n\nOr with poetry:\n> poetry add python-redsys\n\n### 1. Instantiate the redirect client\n\n```python\nfrom decimal import Decimal as D, ROUND_HALF_UP\nfrom redsys.constants import EUR, STANDARD_PAYMENT\nfrom redsys.client import RedirectClient\n\nsecret_key = "123456789abcdef"\nclient = RedirectClient(secret_key)\n```\n\n### 2. Set up the request parameters\n\n```python\nparameters = {\n "merchant_code": "100000001",\n "terminal": "1",\n "transaction_type": STANDARD_PAYMENT,\n "currency": EUR,\n "order": "000000001",\n "amount": D("10.56489").quantize(D(".01"), ROUND_HALF_UP),\n "merchant_data": "test merchant data",\n "merchant_name": "Example Commerce",\n "titular": "Example Ltd.",\n "product_description": "Products of Example Commerce",\n "merchant_url": "https://example.com/redsys/response",\n}\n```\n\n### 3. Prepare the request\n\nThis method returns a dict with the necessary post parameters that are\nneeded during the communication step.\n\n```python\nargs = client.prepare_request(parameters)\n```\n\n### 4. Communication step\n\nRedirect the _user-agent_ to the corresponding Redsys\' endpoint using\nthe post parameters given in the previous step.\n\nAfter the payment process is finished, Redsys will respond making a\nrequest to the `merchant_url` defined in step 2.\n\n### 5. Create and check the response\n\nCreate the response object using the received parameters from Redsys.\nThe method `create_response()` throws a `ValueError` in case the\nreceived signature is not equal to the calculated one using the\ngiven `merchant_parameters`. This normally means that the response **is\nnot coming from Redsys** or that it **has been compromised**.\n\n```python\nsignature = "YqFenHc2HpB273l8c995...."\nmerchant_parameters = "AndvIh66VZdkC5TG3nYL5j4XfCnFFbo3VkOu9TAeTs58fxddgc..."\nresponse = client.create_response(signature, merchant_parameters)\nif response.is_paid:\n # Do the corresponding actions after a successful payment\nelse:\n # Do the corresponding actions after a failed payment\n raise Exception(response.code, response.message)\n```\n\n**Methods for checking the response:**\n\nAccording to the Redsys documentation:\n\n- `response.is_paid`: Returns `True` if the response code is\n between 0 and 99 (both included).\n- `response.is_canceled`: Returns `True` if the response code\n is 400.\n- `response.is_refunded`: Returns `True` if the response code\n is 900.\n- `response.is_authorized`: Returns `True` if the response is\n **paid**, **refunded** or **canceled**.\n\nAlso, you can directly access the code or the message defined in Redsys\ndocumentation using `response.code` or `response.message`.\n\n## Contributions\n\nPlease, feel free to send any contribution that maintains the _less\ndependant_ philosophy.\n',
|
|
18
|
-
'author': 'Andrés Reverón Molina',
|
|
19
|
-
'author_email': 'andres@reveronmolina.me',
|
|
20
|
-
'maintainer': None,
|
|
21
|
-
'maintainer_email': None,
|
|
22
|
-
'url': 'https://github.com/systemallica/python-redsys',
|
|
23
|
-
'packages': packages,
|
|
24
|
-
'package_data': package_data,
|
|
25
|
-
'install_requires': install_requires,
|
|
26
|
-
'python_requires': '>=3.7,<4.0',
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
setup(**setup_kwargs)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|