paymentsgate 1.5.0__tar.gz → 1.5.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.
Potentially problematic release.
This version of paymentsgate might be problematic. Click here for more details.
- {paymentsgate-1.5.0 → paymentsgate-1.5.1}/PKG-INFO +5 -4
- paymentsgate-1.5.1/paymentsgate/__init__.py +17 -0
- {paymentsgate-1.5.0 → paymentsgate-1.5.1}/paymentsgate/cache.py +9 -7
- paymentsgate-1.5.1/paymentsgate/client.py +464 -0
- {paymentsgate-1.5.0 → paymentsgate-1.5.1}/paymentsgate/enums.py +60 -59
- paymentsgate-1.5.1/paymentsgate/exceptions.py +55 -0
- {paymentsgate-1.5.0 → paymentsgate-1.5.1}/paymentsgate/logger.py +0 -1
- {paymentsgate-1.5.0 → paymentsgate-1.5.1}/paymentsgate/mappers.py +0 -1
- paymentsgate-1.5.1/paymentsgate/models.py +289 -0
- paymentsgate-1.5.1/paymentsgate/signature.py +91 -0
- {paymentsgate-1.5.0 → paymentsgate-1.5.1}/paymentsgate/tokens.py +7 -4
- {paymentsgate-1.5.0 → paymentsgate-1.5.1}/paymentsgate/transport.py +12 -6
- paymentsgate-1.5.1/paymentsgate/types.py +7 -0
- {paymentsgate-1.5.0 → paymentsgate-1.5.1}/pyproject.toml +5 -3
- {paymentsgate-1.5.0 → paymentsgate-1.5.1}/setup.py +6 -4
- paymentsgate-1.5.0/paymentsgate/__init__.py +0 -17
- paymentsgate-1.5.0/paymentsgate/client.py +0 -304
- paymentsgate-1.5.0/paymentsgate/exceptions.py +0 -38
- paymentsgate-1.5.0/paymentsgate/models.py +0 -264
- paymentsgate-1.5.0/paymentsgate/types.py +0 -9
- {paymentsgate-1.5.0 → paymentsgate-1.5.1}/LICENSE +0 -0
- {paymentsgate-1.5.0 → paymentsgate-1.5.1}/README.md +0 -0
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: paymentsgate
|
|
3
|
-
Version: 1.5.
|
|
3
|
+
Version: 1.5.1
|
|
4
4
|
Summary: PaymentsGate's Python SDK for REST API
|
|
5
5
|
Home-page: https://github.com/paymentsgate/python-secure-api
|
|
6
6
|
License: MIT
|
|
7
7
|
Keywords: paymentsgate,payments,sdk,api
|
|
8
8
|
Author: PaymentsGate
|
|
9
|
-
Requires-Python: >=3.
|
|
9
|
+
Requires-Python: >=3.10,<4.0
|
|
10
10
|
Classifier: Development Status :: 5 - Production/Stable
|
|
11
11
|
Classifier: Intended Audience :: Developers
|
|
12
12
|
Classifier: License :: OSI Approved :: MIT License
|
|
13
13
|
Classifier: Programming Language :: Python :: 3
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
15
14
|
Classifier: Programming Language :: Python :: 3.10
|
|
16
15
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
16
|
+
Requires-Dist: cryptography (>=44.0.2,<45.0.0)
|
|
17
|
+
Requires-Dist: httpx (>=0.28.1,<0.29.0)
|
|
17
18
|
Requires-Dist: jwt (>=1.3.1,<2.0.0)
|
|
18
19
|
Requires-Dist: pydantic (>=2.8.2,<3.0.0)
|
|
19
|
-
Requires-Dist:
|
|
20
|
+
Requires-Dist: ruff (>=0.11.7,<0.12.0)
|
|
20
21
|
Requires-Dist: tomli (>=2.0.1,<3.0.0)
|
|
21
22
|
Project-URL: Documentation, https://github.com/paymentsgate/python-secure-api
|
|
22
23
|
Project-URL: Repository, https://github.com/paymentsgate/python-secure-api
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from paymentsgate.client import ApiClient
|
|
2
|
+
from paymentsgate.enums import (
|
|
3
|
+
AuthenticationRealms,
|
|
4
|
+
ApiPaths,
|
|
5
|
+
Currencies,
|
|
6
|
+
Languages,
|
|
7
|
+
Statuses,
|
|
8
|
+
CurrencyTypes,
|
|
9
|
+
InvoiceTypes,
|
|
10
|
+
CredentialsTypes,
|
|
11
|
+
RiskScoreLevels,
|
|
12
|
+
CancellationReason,
|
|
13
|
+
FeesStrategy,
|
|
14
|
+
InvoiceDirection,
|
|
15
|
+
TTLUnits,
|
|
16
|
+
)
|
|
17
|
+
from paymentsgate.models import Credentials
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
2
|
from dataclasses import dataclass, field
|
|
3
3
|
|
|
4
|
-
from paymentsgate.tokens import
|
|
5
|
-
|
|
6
|
-
RefreshToken
|
|
7
|
-
)
|
|
4
|
+
from paymentsgate.tokens import AccessToken, RefreshToken
|
|
5
|
+
|
|
8
6
|
|
|
9
7
|
class AbstractCache(ABC):
|
|
10
8
|
"""
|
|
@@ -19,15 +17,19 @@ class AbstractCache(ABC):
|
|
|
19
17
|
...
|
|
20
18
|
|
|
21
19
|
@abstractmethod
|
|
22
|
-
def set_token(self,token: AccessToken | RefreshToken) -> None:
|
|
20
|
+
def set_token(self, token: AccessToken | RefreshToken) -> None:
|
|
23
21
|
"""
|
|
24
22
|
Save the token to the cache under the specified key
|
|
25
23
|
"""
|
|
26
24
|
...
|
|
27
|
-
|
|
25
|
+
|
|
26
|
+
|
|
28
27
|
@dataclass
|
|
29
28
|
class DefaultCache(AbstractCache):
|
|
30
|
-
tokens: dict[str, AccessToken | RefreshToken] = field(
|
|
29
|
+
tokens: dict[str, AccessToken | RefreshToken] = field(
|
|
30
|
+
default_factory=dict, init=False
|
|
31
|
+
)
|
|
32
|
+
|
|
31
33
|
def get_token(self, key: str) -> AccessToken | RefreshToken | None:
|
|
32
34
|
return self.tokens.get(key)
|
|
33
35
|
|
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
from urllib.parse import urlencode
|
|
6
|
+
from pydantic import Field, BaseModel
|
|
7
|
+
|
|
8
|
+
from .types import TokenResponse
|
|
9
|
+
from .tokens import AccessToken, RefreshToken
|
|
10
|
+
from .exceptions import APIResponseError, APIAuthenticationError
|
|
11
|
+
from .models import (
|
|
12
|
+
Credentials,
|
|
13
|
+
GetQuoteModel,
|
|
14
|
+
GetQuoteResponseModel,
|
|
15
|
+
PayInModel,
|
|
16
|
+
PayInResponseModel,
|
|
17
|
+
PayOutModel,
|
|
18
|
+
PayOutResponseModel,
|
|
19
|
+
InvoiceModel,
|
|
20
|
+
GetQuoteTlv,
|
|
21
|
+
PayOutTlvRequest,
|
|
22
|
+
QuoteTlvResponse,
|
|
23
|
+
)
|
|
24
|
+
from .enums import ApiPaths
|
|
25
|
+
from .transport import Request, Response
|
|
26
|
+
from .logger import Logger
|
|
27
|
+
from .cache import AbstractCache, DefaultCache
|
|
28
|
+
|
|
29
|
+
import httpx
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class BaseClient:
|
|
33
|
+
|
|
34
|
+
def __init__(self, config: Credentials, baseUrl: str, timeout: int = 20, debug: bool=False):
|
|
35
|
+
self.config = config
|
|
36
|
+
self.cache = DefaultCache()
|
|
37
|
+
self.baseUrl = baseUrl
|
|
38
|
+
self.timeout = timeout
|
|
39
|
+
if debug:
|
|
40
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ApiAsyncClient(BaseClient):
|
|
44
|
+
async def PayIn(self, request: PayInModel) -> PayInResponseModel:
|
|
45
|
+
# Prepare request
|
|
46
|
+
request = Request(
|
|
47
|
+
method="post",
|
|
48
|
+
path=ApiPaths.invoices_payin,
|
|
49
|
+
content_type='application/json',
|
|
50
|
+
noAuth=False,
|
|
51
|
+
body=request.model_dump(exclude_none=True),
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Handle response
|
|
55
|
+
response = await self._send_request(request)
|
|
56
|
+
if (response.success):
|
|
57
|
+
return response.cast(PayInResponseModel, APIResponseError)
|
|
58
|
+
else:
|
|
59
|
+
raise APIResponseError(response)
|
|
60
|
+
|
|
61
|
+
async def PayOut(self, request: PayOutModel) -> PayOutResponseModel:
|
|
62
|
+
# Prepare request
|
|
63
|
+
request = Request(
|
|
64
|
+
method="post",
|
|
65
|
+
path=ApiPaths.invoices_payout,
|
|
66
|
+
content_type='application/json',
|
|
67
|
+
noAuth=False,
|
|
68
|
+
signature=True,
|
|
69
|
+
body=request.model_dump(exclude_none=True)
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Handle response
|
|
73
|
+
response = await self._send_request(request)
|
|
74
|
+
if (response.success):
|
|
75
|
+
return response.cast(PayOutResponseModel, APIResponseError)
|
|
76
|
+
else:
|
|
77
|
+
raise APIResponseError(response)
|
|
78
|
+
|
|
79
|
+
async def PayOutTlv(self, request: PayOutTlvRequest) -> PayOutResponseModel:
|
|
80
|
+
request = Request(
|
|
81
|
+
method="post",
|
|
82
|
+
path=ApiPaths.invoices_payout_tlv,
|
|
83
|
+
content_type="application/json",
|
|
84
|
+
noAuth=False,
|
|
85
|
+
signature=False,
|
|
86
|
+
body=request.model_dump(exclude_none=True),
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# Handle response
|
|
90
|
+
response = await self._send_request(request)
|
|
91
|
+
if not response.success:
|
|
92
|
+
raise APIResponseError(response)
|
|
93
|
+
|
|
94
|
+
return response.cast(PayOutResponseModel, APIResponseError)
|
|
95
|
+
|
|
96
|
+
async def Quote(self, params: GetQuoteModel) -> GetQuoteResponseModel:
|
|
97
|
+
# Prepare request
|
|
98
|
+
request = Request(
|
|
99
|
+
method="get",
|
|
100
|
+
path=ApiPaths.fx_quote,
|
|
101
|
+
content_type='application/json',
|
|
102
|
+
noAuth=False,
|
|
103
|
+
signature=False,
|
|
104
|
+
body=params.model_dump(exclude_none=True)
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# Handle response
|
|
108
|
+
response = await self._send_request(request)
|
|
109
|
+
if not response.success:
|
|
110
|
+
raise APIResponseError(response)
|
|
111
|
+
|
|
112
|
+
return response.cast(GetQuoteResponseModel, APIResponseError)
|
|
113
|
+
|
|
114
|
+
async def QuoteQr(self, params: GetQuoteTlv) -> QuoteTlvResponse:
|
|
115
|
+
request = Request(
|
|
116
|
+
method="post",
|
|
117
|
+
path=ApiPaths.fx_quote_tlv,
|
|
118
|
+
content_type="application/json",
|
|
119
|
+
noAuth=False,
|
|
120
|
+
signature=False,
|
|
121
|
+
body=params.model_dump(exclude_none=True),
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
# Handle response
|
|
125
|
+
response = await self._send_request(request)
|
|
126
|
+
if not response.success:
|
|
127
|
+
raise APIResponseError(response)
|
|
128
|
+
|
|
129
|
+
return response.cast(QuoteTlvResponse, APIResponseError)
|
|
130
|
+
|
|
131
|
+
async def Status(self, id: str) -> InvoiceModel:
|
|
132
|
+
# Prepare request
|
|
133
|
+
request = Request(
|
|
134
|
+
method="get",
|
|
135
|
+
path=ApiPaths.invoices_info.replace(':id', id),
|
|
136
|
+
content_type='application/json',
|
|
137
|
+
noAuth=False,
|
|
138
|
+
signature=False,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# Handle response
|
|
142
|
+
response = await self._send_request(request)
|
|
143
|
+
if not response.success:
|
|
144
|
+
raise APIResponseError(response)
|
|
145
|
+
return response.cast(InvoiceModel, APIResponseError)
|
|
146
|
+
|
|
147
|
+
async def get_token(self) -> AccessToken | None:
|
|
148
|
+
# First check if valid token is cached
|
|
149
|
+
token = self.cache.get_token("AccessToken")
|
|
150
|
+
refresh = self.cache.get_token("RefreshToken")
|
|
151
|
+
|
|
152
|
+
if token is not None and not token.is_expired:
|
|
153
|
+
return token
|
|
154
|
+
else:
|
|
155
|
+
# try to refresh token
|
|
156
|
+
if refresh is not None and not refresh.is_expired:
|
|
157
|
+
refreshed = await self._refresh_token(token, refresh)
|
|
158
|
+
|
|
159
|
+
if refreshed.success:
|
|
160
|
+
access = AccessToken(refreshed.json_body["access_token"])
|
|
161
|
+
refresh = RefreshToken(
|
|
162
|
+
refreshed.json_body["refresh_token"],
|
|
163
|
+
int(refreshed.json_body["expires_in"]),
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
self.cache.set_token(access)
|
|
167
|
+
self.cache.set_token(refresh)
|
|
168
|
+
|
|
169
|
+
return access
|
|
170
|
+
|
|
171
|
+
# try to issue token
|
|
172
|
+
response = await self._fetch_token()
|
|
173
|
+
if response.success:
|
|
174
|
+
access = AccessToken(response.json_body["access_token"])
|
|
175
|
+
refresh = RefreshToken(
|
|
176
|
+
response.json_body["refresh_token"],
|
|
177
|
+
int(response.json_body["expires_in"]),
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
self.cache.set_token(access)
|
|
181
|
+
self.cache.set_token(refresh)
|
|
182
|
+
|
|
183
|
+
return access
|
|
184
|
+
else:
|
|
185
|
+
raise APIAuthenticationError(response)
|
|
186
|
+
|
|
187
|
+
async def _send_request(self, request: Request) -> Response:
|
|
188
|
+
"""
|
|
189
|
+
Send a specified Request to the GoPay REST API and process the response
|
|
190
|
+
"""
|
|
191
|
+
dict_factory = lambda l: {k: v for k, v in l if v is not None}
|
|
192
|
+
body = request.body
|
|
193
|
+
# Add Bearer authentication to headers if needed
|
|
194
|
+
headers = request.headers or {}
|
|
195
|
+
if not request.noAuth:
|
|
196
|
+
auth = await self.get_token()
|
|
197
|
+
if auth is not None:
|
|
198
|
+
headers["Authorization"] = f"Bearer {auth.token}"
|
|
199
|
+
|
|
200
|
+
client = httpx.AsyncClient(timeout=self.timeout)
|
|
201
|
+
if request.method == 'get':
|
|
202
|
+
url = f'{self.baseUrl}{request.path}'
|
|
203
|
+
if body:
|
|
204
|
+
params = urlencode(body)
|
|
205
|
+
url = f'{url}?{params}'
|
|
206
|
+
r = await client.request(
|
|
207
|
+
method=request.method,
|
|
208
|
+
url=url,
|
|
209
|
+
headers=headers,
|
|
210
|
+
timeout=self.timeout
|
|
211
|
+
)
|
|
212
|
+
else:
|
|
213
|
+
r = await client.request(
|
|
214
|
+
method=request.method,
|
|
215
|
+
url=f"{self.baseUrl}{request.path}",
|
|
216
|
+
headers=headers,
|
|
217
|
+
json=body,
|
|
218
|
+
timeout=self.timeout
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
# Build Response instance, try to decode body as JSON
|
|
222
|
+
response = Response(raw_body=r.content, json={}, status_code=r.status_code)
|
|
223
|
+
|
|
224
|
+
try:
|
|
225
|
+
response.json_body = r.json()
|
|
226
|
+
except json.JSONDecodeError:
|
|
227
|
+
pass
|
|
228
|
+
|
|
229
|
+
return response
|
|
230
|
+
|
|
231
|
+
async def _fetch_token(self) -> Response:
|
|
232
|
+
# Prepare request
|
|
233
|
+
request = Request(
|
|
234
|
+
method="post",
|
|
235
|
+
path=ApiPaths.token_issue,
|
|
236
|
+
content_type='application/json',
|
|
237
|
+
noAuth=True,
|
|
238
|
+
body={"account_id": self.config.account_id, "public_key": self.config.public_key},
|
|
239
|
+
)
|
|
240
|
+
# Handle response
|
|
241
|
+
response = await self._send_request(request)
|
|
242
|
+
return response
|
|
243
|
+
|
|
244
|
+
async def _refresh_token(self) -> Response:
|
|
245
|
+
# Prepare request
|
|
246
|
+
request = Request(
|
|
247
|
+
method="post",
|
|
248
|
+
path=ApiPaths.token_refresh,
|
|
249
|
+
content_type='application/json',
|
|
250
|
+
body={"refresh_token": self.refreshToken},
|
|
251
|
+
)
|
|
252
|
+
# Handle response
|
|
253
|
+
response = await self._send_request(request)
|
|
254
|
+
return response
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
class ApiClient(BaseClient):
|
|
258
|
+
|
|
259
|
+
def PayIn(self, request: PayInModel) -> PayInResponseModel:
|
|
260
|
+
# Prepare request
|
|
261
|
+
request = Request(
|
|
262
|
+
method="post",
|
|
263
|
+
path=ApiPaths.invoices_payin,
|
|
264
|
+
content_type='application/json',
|
|
265
|
+
noAuth=False,
|
|
266
|
+
body=request.model_dump(exclude_none=True),
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
# Handle response
|
|
270
|
+
response = self._send_request(request)
|
|
271
|
+
if (response.success):
|
|
272
|
+
return response.cast(PayInResponseModel, APIResponseError)
|
|
273
|
+
else:
|
|
274
|
+
raise APIResponseError(response)
|
|
275
|
+
|
|
276
|
+
def PayOut(self, request: PayOutModel) -> PayOutResponseModel:
|
|
277
|
+
# Prepare request
|
|
278
|
+
request = Request(
|
|
279
|
+
method="post",
|
|
280
|
+
path=ApiPaths.invoices_payout,
|
|
281
|
+
content_type='application/json',
|
|
282
|
+
noAuth=False,
|
|
283
|
+
signature=True,
|
|
284
|
+
body=request.model_dump(exclude_none=True)
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
# Handle response
|
|
288
|
+
response = self._send_request(request)
|
|
289
|
+
if (response.success):
|
|
290
|
+
return response.cast(PayOutResponseModel, APIResponseError)
|
|
291
|
+
else:
|
|
292
|
+
raise APIResponseError(response)
|
|
293
|
+
|
|
294
|
+
def PayOutTlv(self, request: PayOutTlvRequest) -> PayOutResponseModel:
|
|
295
|
+
request = Request(
|
|
296
|
+
method="post",
|
|
297
|
+
path=ApiPaths.invoices_payout_tlv,
|
|
298
|
+
content_type="application/json",
|
|
299
|
+
noAuth=False,
|
|
300
|
+
signature=False,
|
|
301
|
+
body=request.model_dump(exclude_none=True),
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
# Handle response
|
|
305
|
+
response = self._send_request(request)
|
|
306
|
+
if not response.success:
|
|
307
|
+
raise APIResponseError(response)
|
|
308
|
+
return response.cast(PayOutResponseModel, APIResponseError)
|
|
309
|
+
|
|
310
|
+
def Quote(self, params: GetQuoteModel) -> GetQuoteResponseModel:
|
|
311
|
+
# Prepare request
|
|
312
|
+
request = Request(
|
|
313
|
+
method="get",
|
|
314
|
+
path=ApiPaths.fx_quote,
|
|
315
|
+
content_type='application/json',
|
|
316
|
+
noAuth=False,
|
|
317
|
+
signature=False,
|
|
318
|
+
body=params.model_dump(exclude_none=True)
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
# Handle response
|
|
322
|
+
response = self._send_request(request)
|
|
323
|
+
if not response.success:
|
|
324
|
+
raise APIResponseError(response)
|
|
325
|
+
|
|
326
|
+
return response.cast(GetQuoteResponseModel, APIResponseError)
|
|
327
|
+
|
|
328
|
+
def QuoteQr(self, params: GetQuoteTlv) -> QuoteTlvResponse:
|
|
329
|
+
request = Request(
|
|
330
|
+
method="post",
|
|
331
|
+
path=ApiPaths.fx_quote_tlv,
|
|
332
|
+
content_type="application/json",
|
|
333
|
+
noAuth=False,
|
|
334
|
+
signature=False,
|
|
335
|
+
body=params.model_dump(exclude_none=True),
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
# Handle response
|
|
339
|
+
response = self._send_request(request)
|
|
340
|
+
if not response.success:
|
|
341
|
+
raise APIResponseError(response)
|
|
342
|
+
|
|
343
|
+
return response.cast(QuoteTlvResponse, APIResponseError)
|
|
344
|
+
|
|
345
|
+
def Status(self, id: str) -> InvoiceModel:
|
|
346
|
+
# Prepare request
|
|
347
|
+
request = Request(
|
|
348
|
+
method="get",
|
|
349
|
+
path=ApiPaths.invoices_info.replace(':id', id),
|
|
350
|
+
content_type='application/json',
|
|
351
|
+
noAuth=False,
|
|
352
|
+
signature=False,
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
# Handle response
|
|
356
|
+
response = self._send_request(request)
|
|
357
|
+
if not response.success:
|
|
358
|
+
raise APIResponseError(response)
|
|
359
|
+
|
|
360
|
+
return response.cast(InvoiceModel, APIResponseError)
|
|
361
|
+
|
|
362
|
+
def get_token(self) -> AccessToken | None:
|
|
363
|
+
# First check if valid token is cached
|
|
364
|
+
token = self.cache.get_token('access')
|
|
365
|
+
refresh = self.cache.get_token('refresh')
|
|
366
|
+
if token is not None and not token.is_expired:
|
|
367
|
+
return token
|
|
368
|
+
else:
|
|
369
|
+
# try to refresh token
|
|
370
|
+
if refresh is not None and not refresh.is_expired:
|
|
371
|
+
refreshed = self._refresh_token()
|
|
372
|
+
|
|
373
|
+
if (refreshed.success):
|
|
374
|
+
access = AccessToken(
|
|
375
|
+
response.json_body["access_token"]
|
|
376
|
+
)
|
|
377
|
+
refresh = RefreshToken(
|
|
378
|
+
response.json_body["refresh_token"],
|
|
379
|
+
int(response.json_body["expires_in"]),
|
|
380
|
+
)
|
|
381
|
+
self.cache.set_token(access)
|
|
382
|
+
self.cache.set_token(refresh)
|
|
383
|
+
|
|
384
|
+
return access
|
|
385
|
+
|
|
386
|
+
# try to issue token
|
|
387
|
+
response = self._fetch_token()
|
|
388
|
+
if response.success:
|
|
389
|
+
|
|
390
|
+
access = AccessToken(
|
|
391
|
+
response.json_body["access_token"]
|
|
392
|
+
)
|
|
393
|
+
refresh = RefreshToken(
|
|
394
|
+
response.json_body["refresh_token"],
|
|
395
|
+
int(response.json_body["expires_in"]),
|
|
396
|
+
)
|
|
397
|
+
self.cache.set_token(access)
|
|
398
|
+
self.cache.set_token(refresh)
|
|
399
|
+
|
|
400
|
+
return access
|
|
401
|
+
else:
|
|
402
|
+
raise APIAuthenticationError(response)
|
|
403
|
+
|
|
404
|
+
def _send_request(self, request: Request) -> Response:
|
|
405
|
+
"""
|
|
406
|
+
Send a specified Request to the GoPay REST API and process the response
|
|
407
|
+
"""
|
|
408
|
+
# Add Bearer authentication to headers if needed
|
|
409
|
+
headers = request.headers or {}
|
|
410
|
+
if not request.noAuth:
|
|
411
|
+
auth = self.get_token()
|
|
412
|
+
if auth is not None:
|
|
413
|
+
headers["Authorization"] = f"Bearer {auth.token}"
|
|
414
|
+
|
|
415
|
+
if (request.method == 'get'):
|
|
416
|
+
params = urlencode(body)
|
|
417
|
+
r = httpx.request(
|
|
418
|
+
method=request.method,
|
|
419
|
+
url=f"{self.baseUrl}{request.path}?{params}",
|
|
420
|
+
headers=headers,
|
|
421
|
+
timeout=self.timeout
|
|
422
|
+
)
|
|
423
|
+
else:
|
|
424
|
+
r = httpx.request(
|
|
425
|
+
method=request.method,
|
|
426
|
+
url=f"{self.baseUrl}{request.path}",
|
|
427
|
+
headers=headers,
|
|
428
|
+
json=body,
|
|
429
|
+
timeout=self.timeout
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
# Build Response instance, try to decode body as JSON
|
|
433
|
+
response = Response(raw_body=r.content, json={}, status_code=r.status_code)
|
|
434
|
+
|
|
435
|
+
try:
|
|
436
|
+
response.json_body = r.json()
|
|
437
|
+
except json.JSONDecodeError:
|
|
438
|
+
pass
|
|
439
|
+
return response
|
|
440
|
+
|
|
441
|
+
def _fetch_token(self) -> Response:
|
|
442
|
+
# Prepare request
|
|
443
|
+
request = Request(
|
|
444
|
+
method="post",
|
|
445
|
+
path=ApiPaths.token_issue,
|
|
446
|
+
content_type='application/json',
|
|
447
|
+
noAuth=True,
|
|
448
|
+
body={"account_id": self.config.account_id, "public_key": self.config.public_key},
|
|
449
|
+
)
|
|
450
|
+
# Handle response
|
|
451
|
+
response = self._send_request(request)
|
|
452
|
+
return response
|
|
453
|
+
|
|
454
|
+
def _refresh_token(self) -> Response:
|
|
455
|
+
# Prepare request
|
|
456
|
+
request = Request(
|
|
457
|
+
method="post",
|
|
458
|
+
path=ApiPaths.token_refresh,
|
|
459
|
+
content_type='application/json',
|
|
460
|
+
body={"refresh_token": self.refreshToken},
|
|
461
|
+
)
|
|
462
|
+
# Handle response
|
|
463
|
+
response = self._send_request(request)
|
|
464
|
+
return response
|