csobpg 0.0.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.
Files changed (46) hide show
  1. csobpg-0.0.1/LICENSE +21 -0
  2. csobpg-0.0.1/PKG-INFO +160 -0
  3. csobpg-0.0.1/README.md +129 -0
  4. csobpg-0.0.1/csobpg/__init__.py +1 -0
  5. csobpg-0.0.1/csobpg/http/__init__.py +17 -0
  6. csobpg-0.0.1/csobpg/http/base.py +104 -0
  7. csobpg-0.0.1/csobpg/http/requests_client.py +46 -0
  8. csobpg-0.0.1/csobpg/http/urllib_client.py +62 -0
  9. csobpg-0.0.1/csobpg/v19/__init__.py +18 -0
  10. csobpg-0.0.1/csobpg/v19/api.py +209 -0
  11. csobpg-0.0.1/csobpg/v19/errors.py +225 -0
  12. csobpg-0.0.1/csobpg/v19/key.py +36 -0
  13. csobpg-0.0.1/csobpg/v19/request/__init__.py +9 -0
  14. csobpg-0.0.1/csobpg/v19/request/base.py +39 -0
  15. csobpg-0.0.1/csobpg/v19/request/dttm.py +30 -0
  16. csobpg-0.0.1/csobpg/v19/request/echo.py +16 -0
  17. csobpg-0.0.1/csobpg/v19/request/fields.py +64 -0
  18. csobpg-0.0.1/csobpg/v19/request/payment_close.py +26 -0
  19. csobpg-0.0.1/csobpg/v19/request/payment_init/__init__.py +143 -0
  20. csobpg-0.0.1/csobpg/v19/request/payment_init/cart.py +66 -0
  21. csobpg-0.0.1/csobpg/v19/request/payment_init/currency.py +17 -0
  22. csobpg-0.0.1/csobpg/v19/request/payment_init/customer/__init__.py +13 -0
  23. csobpg-0.0.1/csobpg/v19/request/payment_init/customer/account.py +69 -0
  24. csobpg-0.0.1/csobpg/v19/request/payment_init/customer/data.py +75 -0
  25. csobpg-0.0.1/csobpg/v19/request/payment_init/customer/login.py +47 -0
  26. csobpg-0.0.1/csobpg/v19/request/payment_init/order/__init__.py +16 -0
  27. csobpg-0.0.1/csobpg/v19/request/payment_init/order/address.py +58 -0
  28. csobpg-0.0.1/csobpg/v19/request/payment_init/order/data.py +142 -0
  29. csobpg-0.0.1/csobpg/v19/request/payment_init/order/delivery.py +38 -0
  30. csobpg-0.0.1/csobpg/v19/request/payment_init/payment.py +18 -0
  31. csobpg-0.0.1/csobpg/v19/request/payment_init/webpage.py +41 -0
  32. csobpg-0.0.1/csobpg/v19/request/payment_process.py +25 -0
  33. csobpg-0.0.1/csobpg/v19/request/payment_refund.py +26 -0
  34. csobpg-0.0.1/csobpg/v19/request/payment_reverse.py +19 -0
  35. csobpg-0.0.1/csobpg/v19/request/payment_status.py +25 -0
  36. csobpg-0.0.1/csobpg/v19/request/url.py +9 -0
  37. csobpg-0.0.1/csobpg/v19/response/__init__.py +19 -0
  38. csobpg-0.0.1/csobpg/v19/response/base.py +93 -0
  39. csobpg-0.0.1/csobpg/v19/response/payment_close.py +66 -0
  40. csobpg-0.0.1/csobpg/v19/response/payment_init.py +66 -0
  41. csobpg-0.0.1/csobpg/v19/response/payment_process.py +76 -0
  42. csobpg-0.0.1/csobpg/v19/response/payment_refund.py +13 -0
  43. csobpg-0.0.1/csobpg/v19/response/payment_reverse.py +61 -0
  44. csobpg-0.0.1/csobpg/v19/response/payment_status.py +319 -0
  45. csobpg-0.0.1/csobpg/v19/signature.py +72 -0
  46. csobpg-0.0.1/pyproject.toml +45 -0
csobpg-0.0.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Andrii Nechaiev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
csobpg-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,160 @@
1
+ Metadata-Version: 2.1
2
+ Name: csobpg
3
+ Version: 0.0.1
4
+ Summary: Library for communication with ČSOB API
5
+ Home-page: https://github.com/litteratum/csobpg
6
+ License: MIT
7
+ Author: Andrii Nechaiev
8
+ Author-email: andrewnech@gmail.com
9
+ Requires-Python: >=3.7,<3.12
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: POSIX :: Linux
14
+ Classifier: Programming Language :: Python
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.7
17
+ Classifier: Programming Language :: Python :: 3.8
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3 :: Only
22
+ Classifier: Programming Language :: Python :: Implementation :: CPython
23
+ Classifier: Topic :: Software Development :: Libraries
24
+ Classifier: Topic :: Utilities
25
+ Provides-Extra: requests-client
26
+ Requires-Dist: pycryptodome (>=3.17,<4.0)
27
+ Requires-Dist: requests (>=2.31.0,<3.0.0) ; extra == "requests-client"
28
+ Project-URL: Repository, https://github.com/litteratum/csobpg
29
+ Description-Content-Type: text/markdown
30
+
31
+ # CSOB client
32
+ Python library for communicating with ČSOB (<https://platbakartou.csob.cz/>) payment gateway API. The API is described here: <https://github.com/csob/paymentgateway>.
33
+
34
+ The library currently implements API v.1.9 only.
35
+
36
+
37
+ ## Installation
38
+ ```bash
39
+ pip install csobpg
40
+ ```
41
+
42
+ ## Basic usage
43
+ ### API client initialization
44
+ ```python
45
+ from csobpg.v19 import APIClient
46
+
47
+ client = APIClient("merchantId", "merch_private.key", "csob.pub", base_url=..., http_client=...)
48
+ ```
49
+
50
+ ### HTTP client
51
+ The library uses the `HTTPClient` interface for making HTTP requests.
52
+
53
+ It provides the following HTTP clients:
54
+ * `csobpg.http.urllib_client.UrllibHTTPClient` - the default one using the `urllib` under the hood
55
+ * `csobpg.http.requests_client.RequestsHTTPClient` - an extra HTTP client using the `requests` under the hood. The library must be installed with the `requests-client` extra to be able to use this client
56
+
57
+ It is also possible to implement the `HTTPClient` interface to use your custom HTTP client.
58
+
59
+ The HTTP client is set as the following:
60
+
61
+ ```python
62
+ from csobpg.v19 import APIClient
63
+ from csobpg.http import HTTPClient
64
+
65
+ class CustomHTTPClient(HTTPClient):
66
+ ...
67
+
68
+ client = APIClient(..., http_client=CustomHTTPClient(...))
69
+ ```
70
+
71
+ ### Payment initialization
72
+ ```python
73
+ from csobpg.v19 import Cart, CartItem
74
+
75
+ response = client.init_payment(
76
+ order_no="2233823251",
77
+ total_amount=100,
78
+ return_url="http://127.0.0.1:5000/",
79
+ cart=Cart([CartItem("Apples", 1, 100)]),
80
+ merchant_data=b"Hello, World!",
81
+ )
82
+ ```
83
+
84
+ ### Get payment URL
85
+ ```python
86
+ url = client.get_payment_process_url(pay_id)
87
+ ```
88
+
89
+ ### Process the gateway redirect
90
+ ```python
91
+ response = client.process_gateway_return(data_dict)
92
+ ```
93
+
94
+ ### Get payment status
95
+ ```python
96
+ response = client.get_payment_status(pay_id)
97
+ ```
98
+
99
+ ### Reverse payment
100
+ ```python
101
+ response = client.reverse_payment(pay_id)
102
+ ```
103
+
104
+ ### Refund payment
105
+ ```python
106
+ response = client.refund_payment(pay_id, amount=100)
107
+ ```
108
+
109
+ ### Exceptions handling
110
+ ```python
111
+ from csobpg.v19.errors import APIError, APIClientError
112
+ from csobpg.http import HTTPRequestError
113
+
114
+ try:
115
+ response = client.<operation>(...)
116
+ except APIError as exc:
117
+ # handle API error
118
+ # it is raised on any API error. You may also catch the specific API error
119
+ except APIClientError as exc:
120
+ # handle API client error
121
+ # it is raised when API returns unexpected response (e.g. invalid JSON, invalid signature)
122
+ except HTTPRequestError as exc:
123
+ # handle HTTP error
124
+ # it is raised on any HTTP error
125
+ except ValueError as exc:
126
+ # handle value error
127
+ # it is raised on any library's misuse (e.g. passing invalid parameters)
128
+ # it always means developer's mistake
129
+ ```
130
+
131
+ ### RSA keys management
132
+ The simples way to pass RSA keys is to pass their file paths:
133
+
134
+ ```python
135
+ from csobpg.v19 import APIClient
136
+
137
+ client = APIClient(..., "merch_private.key", "csob.pub")
138
+ ```
139
+
140
+ The library will read the private key from the file when needed. The public key will be cached into the RAM.
141
+
142
+ If you want to change it, use special classes:
143
+
144
+ ```python
145
+ from csobpg.v19 import APIClient, FileRSAKey, CachedRSAKey
146
+
147
+ client = APIClient(..., FileRSAKey("merch_private.key"), FileRSAKey("csob.pub"))
148
+ ```
149
+
150
+ You may also override the base RSAKey class to define your own key access strategy:
151
+
152
+ ```python
153
+ from csobpg.v19 import RSAKey
154
+
155
+ class MyRSAKey(RSAKey):
156
+
157
+ def __str__(self) -> str:
158
+ return "my key"
159
+ ```
160
+
csobpg-0.0.1/README.md ADDED
@@ -0,0 +1,129 @@
1
+ # CSOB client
2
+ Python library for communicating with ČSOB (<https://platbakartou.csob.cz/>) payment gateway API. The API is described here: <https://github.com/csob/paymentgateway>.
3
+
4
+ The library currently implements API v.1.9 only.
5
+
6
+
7
+ ## Installation
8
+ ```bash
9
+ pip install csobpg
10
+ ```
11
+
12
+ ## Basic usage
13
+ ### API client initialization
14
+ ```python
15
+ from csobpg.v19 import APIClient
16
+
17
+ client = APIClient("merchantId", "merch_private.key", "csob.pub", base_url=..., http_client=...)
18
+ ```
19
+
20
+ ### HTTP client
21
+ The library uses the `HTTPClient` interface for making HTTP requests.
22
+
23
+ It provides the following HTTP clients:
24
+ * `csobpg.http.urllib_client.UrllibHTTPClient` - the default one using the `urllib` under the hood
25
+ * `csobpg.http.requests_client.RequestsHTTPClient` - an extra HTTP client using the `requests` under the hood. The library must be installed with the `requests-client` extra to be able to use this client
26
+
27
+ It is also possible to implement the `HTTPClient` interface to use your custom HTTP client.
28
+
29
+ The HTTP client is set as the following:
30
+
31
+ ```python
32
+ from csobpg.v19 import APIClient
33
+ from csobpg.http import HTTPClient
34
+
35
+ class CustomHTTPClient(HTTPClient):
36
+ ...
37
+
38
+ client = APIClient(..., http_client=CustomHTTPClient(...))
39
+ ```
40
+
41
+ ### Payment initialization
42
+ ```python
43
+ from csobpg.v19 import Cart, CartItem
44
+
45
+ response = client.init_payment(
46
+ order_no="2233823251",
47
+ total_amount=100,
48
+ return_url="http://127.0.0.1:5000/",
49
+ cart=Cart([CartItem("Apples", 1, 100)]),
50
+ merchant_data=b"Hello, World!",
51
+ )
52
+ ```
53
+
54
+ ### Get payment URL
55
+ ```python
56
+ url = client.get_payment_process_url(pay_id)
57
+ ```
58
+
59
+ ### Process the gateway redirect
60
+ ```python
61
+ response = client.process_gateway_return(data_dict)
62
+ ```
63
+
64
+ ### Get payment status
65
+ ```python
66
+ response = client.get_payment_status(pay_id)
67
+ ```
68
+
69
+ ### Reverse payment
70
+ ```python
71
+ response = client.reverse_payment(pay_id)
72
+ ```
73
+
74
+ ### Refund payment
75
+ ```python
76
+ response = client.refund_payment(pay_id, amount=100)
77
+ ```
78
+
79
+ ### Exceptions handling
80
+ ```python
81
+ from csobpg.v19.errors import APIError, APIClientError
82
+ from csobpg.http import HTTPRequestError
83
+
84
+ try:
85
+ response = client.<operation>(...)
86
+ except APIError as exc:
87
+ # handle API error
88
+ # it is raised on any API error. You may also catch the specific API error
89
+ except APIClientError as exc:
90
+ # handle API client error
91
+ # it is raised when API returns unexpected response (e.g. invalid JSON, invalid signature)
92
+ except HTTPRequestError as exc:
93
+ # handle HTTP error
94
+ # it is raised on any HTTP error
95
+ except ValueError as exc:
96
+ # handle value error
97
+ # it is raised on any library's misuse (e.g. passing invalid parameters)
98
+ # it always means developer's mistake
99
+ ```
100
+
101
+ ### RSA keys management
102
+ The simples way to pass RSA keys is to pass their file paths:
103
+
104
+ ```python
105
+ from csobpg.v19 import APIClient
106
+
107
+ client = APIClient(..., "merch_private.key", "csob.pub")
108
+ ```
109
+
110
+ The library will read the private key from the file when needed. The public key will be cached into the RAM.
111
+
112
+ If you want to change it, use special classes:
113
+
114
+ ```python
115
+ from csobpg.v19 import APIClient, FileRSAKey, CachedRSAKey
116
+
117
+ client = APIClient(..., FileRSAKey("merch_private.key"), FileRSAKey("csob.pub"))
118
+ ```
119
+
120
+ You may also override the base RSAKey class to define your own key access strategy:
121
+
122
+ ```python
123
+ from csobpg.v19 import RSAKey
124
+
125
+ class MyRSAKey(RSAKey):
126
+
127
+ def __str__(self) -> str:
128
+ return "my key"
129
+ ```
@@ -0,0 +1 @@
1
+ """The library package."""
@@ -0,0 +1,17 @@
1
+ """Package for dealing with HTTP."""
2
+
3
+ from .base import (
4
+ HTTPClient,
5
+ HTTPConnectionError,
6
+ HTTPRequestError,
7
+ HTTPResponse,
8
+ HTTPTimeoutError,
9
+ )
10
+
11
+ __all__ = [
12
+ "HTTPClient",
13
+ "HTTPConnectionError",
14
+ "HTTPRequestError",
15
+ "HTTPResponse",
16
+ "HTTPTimeoutError",
17
+ ]
@@ -0,0 +1,104 @@
1
+ """Base client."""
2
+
3
+ import json as jsonlib
4
+ from abc import ABC, abstractmethod
5
+ from typing import Optional
6
+
7
+
8
+ class HTTPRequestError(Exception):
9
+ """Base HTTP request error."""
10
+
11
+
12
+ class HTTPConnectionError(HTTPRequestError):
13
+ """Any error related to connection."""
14
+
15
+
16
+ class HTTPTimeoutError(HTTPRequestError):
17
+ """HTTP request timed out."""
18
+
19
+
20
+ class HTTPInvalidResponseError(HTTPRequestError):
21
+ """HTTP response is invalid."""
22
+
23
+
24
+ class HTTPResponse:
25
+ """HTTP response wrapper."""
26
+
27
+ def __init__(
28
+ self, status_code: int, body: Optional[bytes], headers: dict
29
+ ) -> None:
30
+ self.status_code = status_code
31
+ self.body = body or b""
32
+ self._headers = headers
33
+
34
+ self._json = None
35
+
36
+ @property
37
+ # TODO: return dict
38
+ def json(self) -> Optional[dict]:
39
+ """Return body as JSON."""
40
+ if self._json is not None:
41
+ return self._json
42
+
43
+ headers = {key.lower(): val for key, val in self._headers.items()}
44
+ if "application/json" in headers.get("content-type", ""):
45
+ try:
46
+ self._json = jsonlib.loads(self.body)
47
+ except Exception as exc:
48
+ raise HTTPInvalidResponseError(
49
+ f"Invalid JSON in response: {exc}"
50
+ ) from exc
51
+
52
+ return self._json
53
+
54
+ def __str__(self) -> str:
55
+ return f"{self.__class__.__name__}(status={self.status_code})"
56
+
57
+ def __repr__(self) -> str:
58
+ return (
59
+ f"{self.__class__.__name__}("
60
+ f"status={self.status_code}, "
61
+ f"body={self.body}, "
62
+ f"headers={self._headers}"
63
+ ")"
64
+ )
65
+
66
+
67
+ class HTTPClient(ABC):
68
+ """Base HTTP client."""
69
+
70
+ def __init__(self, request_timeout: float = 5) -> None:
71
+ self.request_timeout = request_timeout
72
+
73
+ # pylint: disable=too-many-arguments
74
+ @abstractmethod
75
+ def _request(
76
+ self,
77
+ method: str,
78
+ url: str,
79
+ json: Optional[dict] = None,
80
+ headers: Optional[dict] = None,
81
+ ) -> HTTPResponse:
82
+ """Perform request.
83
+
84
+ This method must handle all possible HTTP exceptions and raise them
85
+ as `HTTPRequestError`.
86
+
87
+ Headers may be extended if necessary.
88
+ """
89
+
90
+ def request(
91
+ self,
92
+ method: str,
93
+ url: str,
94
+ json: Optional[dict] = None,
95
+ headers: Optional[dict] = None,
96
+ ) -> HTTPResponse:
97
+ """Perform HTTP request with a given HTTP method.
98
+
99
+ :param method: HTTP method to use
100
+ :param url: API URL
101
+ :param json: JSON data to post
102
+ :param headers: headers
103
+ """
104
+ return self._request(method, url, json, headers=headers)
@@ -0,0 +1,46 @@
1
+ """HTTP client which uses `requests` under the hood."""
2
+
3
+ from typing import Optional
4
+
5
+ import requests
6
+
7
+ from .base import (
8
+ HTTPClient,
9
+ HTTPConnectionError,
10
+ HTTPRequestError,
11
+ HTTPResponse,
12
+ HTTPTimeoutError,
13
+ )
14
+
15
+
16
+ class RequestsHTTPClient(HTTPClient):
17
+ """`requests` HTTP client."""
18
+
19
+ def __init__(self, request_timeout: float = 5) -> None:
20
+ super().__init__(request_timeout)
21
+ self._session = requests.Session()
22
+
23
+ def _request(
24
+ self,
25
+ method: str,
26
+ url: str,
27
+ json: Optional[dict] = None,
28
+ headers: Optional[dict] = None,
29
+ ) -> HTTPResponse:
30
+ try:
31
+ response: requests.Response = getattr(
32
+ self._session, method.lower()
33
+ )(url, json=json, timeout=self.request_timeout, headers=headers)
34
+ except ConnectionError as exc:
35
+ raise HTTPConnectionError(exc) from exc
36
+ except requests.Timeout as exc:
37
+ raise HTTPTimeoutError(exc) from exc
38
+ except requests.RequestException as exc:
39
+ raise HTTPRequestError(exc) from exc
40
+
41
+ return HTTPResponse(
42
+ response.status_code, response.content, dict(response.headers)
43
+ )
44
+
45
+ def __str__(self) -> str:
46
+ return self.__class__.__name__
@@ -0,0 +1,62 @@
1
+ """HTTP client which uses `urllib` under the hood."""
2
+
3
+ import json as _jsonlib
4
+ import urllib
5
+ import urllib.error
6
+ import urllib.parse
7
+ import urllib.request
8
+ from typing import Optional
9
+
10
+ from .base import (
11
+ HTTPClient,
12
+ HTTPConnectionError,
13
+ HTTPRequestError,
14
+ HTTPResponse,
15
+ HTTPTimeoutError,
16
+ )
17
+
18
+
19
+ class UrllibHTTPClient(HTTPClient):
20
+ """`urllib` HTTP client."""
21
+
22
+ # pylint: disable=too-many-arguments
23
+ def _request(
24
+ self,
25
+ method: str,
26
+ url: str,
27
+ json: Optional[dict] = None,
28
+ headers: Optional[dict] = None,
29
+ ) -> HTTPResponse:
30
+ headers = headers or {}
31
+ data = None
32
+
33
+ if json:
34
+ headers["Content-Type"] = "application/json"
35
+ data = _jsonlib.dumps(json).encode()
36
+
37
+ try:
38
+ with urllib.request.urlopen(
39
+ urllib.request.Request(
40
+ url,
41
+ data=data,
42
+ headers=headers,
43
+ method=method.upper(),
44
+ ),
45
+ timeout=self.request_timeout,
46
+ ) as response:
47
+ return HTTPResponse(
48
+ response.status, response.read(), dict(response.headers)
49
+ )
50
+ except ConnectionError as exc:
51
+ raise HTTPConnectionError(exc) from exc
52
+ except TimeoutError as exc:
53
+ raise HTTPTimeoutError(exc) from exc
54
+ except urllib.error.HTTPError as exc:
55
+ return HTTPResponse(
56
+ exc.status or 500, exc.read(), dict(exc.headers)
57
+ )
58
+ except urllib.error.URLError as exc:
59
+ raise HTTPRequestError(exc) from exc
60
+
61
+ def __str__(self) -> str:
62
+ return self.__class__.__name__
@@ -0,0 +1,18 @@
1
+ """Client for API v.1.9."""
2
+
3
+ from .api import APIClient
4
+ from .key import FileRSAKey, RAMRSAKey, RSAKey
5
+ from .request.payment_init.cart import Cart, CartItem
6
+ from .request.payment_init.currency import Currency
7
+ from .response import PaymentStatus
8
+
9
+ __all__ = (
10
+ "APIClient",
11
+ "Cart",
12
+ "CartItem",
13
+ "Currency",
14
+ "RAMRSAKey",
15
+ "FileRSAKey",
16
+ "RSAKey",
17
+ "PaymentStatus",
18
+ )