Skip to content

Commit bb7ff62

Browse files
committed
cleanup + typing
1 parent 7ccd353 commit bb7ff62

File tree

3 files changed

+48
-64
lines changed

3 files changed

+48
-64
lines changed

redsys/client.py

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
import hmac
55
import json
66
import re
7-
from abc import ABCMeta, abstractmethod
7+
from abc import ABC, abstractmethod
8+
from typing import Any, Dict
89

910
from Crypto.Cipher import DES3
1011

@@ -18,10 +19,13 @@
1819
DEFAULT_SIGNATURE_VERSION = "HMAC_SHA256_V1"
1920

2021

21-
class Client(object):
22-
__metaclass__ = ABCMeta
22+
class Client(ABC):
23+
"""
24+
Abstract class from which RedirectClient inherits.
25+
It implements methods that may be used in future clients(i.e. rest).
26+
"""
2327

24-
def __init__(self, secret_key=None):
28+
def __init__(self, secret_key: str = None):
2529
self.secret_key = secret_key
2630

2731
@abstractmethod
@@ -34,15 +38,28 @@ def create_response(
3438
def prepare_request(self, request):
3539
raise NotImplementedError
3640

37-
def encode_parameters(self, parameters):
41+
@staticmethod
42+
def encode_parameters(parameters: Dict[str, Any]) -> bytes:
3843
"""Encodes the merchant parameters in base64"""
3944
return base64.b64encode(json.dumps(parameters).encode())
4045

41-
def decode_parameters(self, parameters):
46+
@staticmethod
47+
def decode_parameters(parameters: bytes) -> Dict[str, Any]:
4248
"""Decodes the merchant parameters from base64"""
4349
return json.loads(base64.b64decode(parameters).decode())
4450

45-
def encrypt_3DES(self, order):
51+
@staticmethod
52+
def sign_hmac256(encrypted_order: bytes, merchant_parameters: bytes) -> bytes:
53+
"""
54+
Generates the encrypted signature using the 3DES-encrypted order
55+
and base64-encoded merchant parameters
56+
"""
57+
signature = hmac.new(
58+
encrypted_order, merchant_parameters, hashlib.sha256
59+
).digest()
60+
return base64.b64encode(signature)
61+
62+
def encrypt_3DES(self, order: str) -> bytes:
4663
"""Encrypts(3DES algorithm) the payment order using the secret key"""
4764
cipher = DES3.new(
4865
base64.b64decode(self.secret_key), DES3.MODE_CBC, IV=b"\0\0\0\0\0\0\0\0"
@@ -52,22 +69,12 @@ def encrypt_3DES(self, order):
5269
# therefore we left-justify adding ceros
5370
return cipher.encrypt(order.encode().ljust(16, b"\0"))
5471

55-
def sign_hmac256(self, encrypted_order, merchant_parameters):
56-
"""
57-
Generates the encrypted signature using the encrypted order
58-
and merchant parameters
59-
"""
60-
signature = hmac.new(
61-
encrypted_order, merchant_parameters, hashlib.sha256
62-
).digest()
63-
return base64.b64encode(signature)
64-
65-
def generate_signature(self, order, merchant_parameters):
72+
def generate_signature(self, order: str, merchant_parameters: bytes) -> bytes:
6673
return self.sign_hmac256(self.encrypt_3DES(order), merchant_parameters)
6774

6875

6976
class RedirectClient(Client):
70-
def prepare_request(self, parameters):
77+
def prepare_request(self, parameters: Dict[str, Any]) -> Dict[str, Any]:
7178
"""Takes the merchant parameters and returns the necessary parameters
7279
to make the POST request to Redsys"""
7380
request = Request(parameters)
@@ -81,14 +88,14 @@ def prepare_request(self, parameters):
8188

8289
def create_response(
8390
self,
84-
signature,
85-
merchant_parameters,
86-
):
91+
signature: str,
92+
merchant_parameters: str,
93+
) -> Response:
8794
"""
8895
Decodes the Redsys response to check for validity.
8996
Checks if the received signature corresponds to the sent signature.
9097
91-
Both the signature and merchant parameters are plain strings, not bytes.
98+
Both the `signature` and `merchant parameters` are plain strings, not bytes.
9299
"""
93100
decoded_parameters = self.decode_parameters(merchant_parameters)
94101
response = Response(decoded_parameters)

redsys/response.py

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# -*- coding: utf-8 -*-
22
from decimal import Decimal
3+
from typing import Any, Dict
34

45
RESPONSE = "Ds_Response"
56
DATE = "Ds_Date"
@@ -85,14 +86,14 @@
8586
}
8687

8788

88-
class Response(object):
89+
class Response:
8990
"""
9091
Defines a response
9192
"""
9293

9394
_parameters = {}
9495

95-
def __init__(self, parameters):
96+
def __init__(self, parameters: Dict[str, Any]):
9697
MERCHANT_PARAMETERS_MAP_REVERSE = {
9798
value: key for key, value in MERCHANT_PARAMETERS_MAP.items()
9899
}
@@ -104,37 +105,34 @@ def __init__(self, parameters):
104105
clean(value) if clean else value
105106
)
106107

107-
def __getattr__(self, item):
108+
def __getattr__(self, item: str) -> Any:
108109
if item in MERCHANT_PARAMETERS_MAP:
109110
return self._parameters[item]
110111

111-
def __setattr__(self, key, value):
112+
def __setattr__(self, key: str, value: Any):
112113
if key in MERCHANT_PARAMETERS_MAP:
113114
self._parameters[key] = value
114115

115116
def is_authorized(self):
116-
return (
117-
(0 <= self.response_code <= 99)
118-
or self.response_code == 900
119-
or self.response_code == 400
120-
)
117+
return (0 <= self.code <= 99) or self.code == 900 or self.code == 400
121118

122119
def is_paid(self):
123-
return 0 <= self.response_code <= 99
120+
return 0 <= self.code <= 99
124121

125122
def is_refunded(self):
126-
return self.response_code == 900
123+
return self.code == 900
127124

128125
def is_canceled(self):
129-
return self.response_code == 400
126+
return self.code == 400
130127

131128
@property
132-
def response_code(self):
129+
def code(self):
133130
return int(self.response)
134131

135132
@property
136-
def response_message(self):
133+
def message(self):
137134
return RESPONSE_MAP["0000"] if self.is_paid() else RESPONSE_MAP[self.response]
138135

139-
def clean_amount(self, value):
136+
@staticmethod
137+
def clean_amount(value):
140138
return Decimal("%s.%s" % (str(value)[:-2], str(value)[-2:]))

redsys/tests/test_client.py

Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,17 @@
33

44
import pytest
55

6-
from redsys.client import Client, RedirectClient
6+
from redsys.client import RedirectClient
77
from redsys.constants import EUR, STANDARD_PAYMENT
88
from redsys.request import Request
99

1010

11-
class TestClient:
11+
class TestRedirectClient:
1212
@pytest.fixture(autouse=True)
1313
def set_up(self):
1414
secret_key = "sq7HjrUOBfKmC576ILgskD5srU870gJ7"
15-
self.client = Client(secret_key)
16-
parameters = {
15+
self.client = RedirectClient(secret_key)
16+
self.parameters = {
1717
"merchant_code": "100000001",
1818
"terminal": "1",
1919
"transaction_type": STANDARD_PAYMENT,
@@ -26,7 +26,7 @@ def set_up(self):
2626
"product_description": "Products of Example Commerce",
2727
"merchant_url": "https://example.com/redsys/response",
2828
}
29-
self.request = Request(parameters)
29+
self.request = Request(self.parameters)
3030
self.merchant_params = self.request.prepare_parameters()
3131

3232
def test_encode_parameters(self):
@@ -54,27 +54,6 @@ def test_sign_hmac256(self):
5454
signature = self.client.sign_hmac256(encrypted_order, encoded_params)
5555
assert signature == b"eEW4YN7/kkNHRO0/HhXy9TppzHwF38+eZApKAagDI9A="
5656

57-
58-
class TestRedirectClient:
59-
@pytest.fixture(autouse=True)
60-
def set_up(self):
61-
secret_key = "sq7HjrUOBfKmC576ILgskD5srU870gJ7"
62-
self.client = RedirectClient(secret_key)
63-
self.parameters = {
64-
"merchant_code": "100000001",
65-
"terminal": "1",
66-
"transaction_type": STANDARD_PAYMENT,
67-
"currency": EUR,
68-
"order": "000000001",
69-
"amount": D("10.56489").quantize(D(".01"), ROUND_HALF_UP),
70-
"merchant_data": "test merchant data",
71-
"merchant_name": "Example Commerce",
72-
"titular": "Example Ltd.",
73-
"product_description": "Products of Example Commerce",
74-
"merchant_url": "https://example.com/redsys/response",
75-
}
76-
self.request = Request(self.parameters)
77-
7857
def test_prepare_request(self):
7958
prepared_request = self.client.prepare_request(self.parameters)
8059
assert len(prepared_request.keys()) == 3

0 commit comments

Comments
 (0)