paymentsgate 1.5.0__py3-none-any.whl → 1.5.2__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.

Potentially problematic release.


This version of paymentsgate might be problematic. Click here for more details.

paymentsgate/__init__.py CHANGED
@@ -1,17 +1,17 @@
1
1
  from paymentsgate.client import ApiClient
2
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
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
16
  )
17
- from paymentsgate.models import Credentials
17
+ from paymentsgate.models import Credentials
paymentsgate/cache.py CHANGED
@@ -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
- AccessToken,
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(default_factory=dict, init=False)
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
 
paymentsgate/client.py CHANGED
@@ -1,26 +1,20 @@
1
1
  from __future__ import annotations
2
2
  import logging
3
- # from dataclasses import dataclass, is_dataclass, field, asdict
3
+
4
4
  import json
5
5
  from urllib.parse import urlencode
6
6
  from pydantic import Field, BaseModel
7
7
 
8
8
  from .types import TokenResponse
9
- from .tokens import (
10
- AccessToken,
11
- RefreshToken
12
- )
13
- from .exceptions import (
14
- APIResponseError,
15
- APIAuthenticationError
16
- )
9
+ from .tokens import AccessToken, RefreshToken
10
+ from .exceptions import APIResponseError, APIAuthenticationError
17
11
  from .models import (
18
- Credentials,
19
- GetQuoteModel,
20
- GetQuoteResponseModel,
21
- PayInModel,
22
- PayInResponseModel,
23
- PayOutModel,
12
+ Credentials,
13
+ GetQuoteModel,
14
+ GetQuoteResponseModel,
15
+ PayInModel,
16
+ PayInResponseModel,
17
+ PayOutModel,
24
18
  PayOutResponseModel,
25
19
  InvoiceModel,
26
20
  GetQuoteTlv,
@@ -28,32 +22,240 @@ from .models import (
28
22
  QuoteTlvResponse,
29
23
  )
30
24
  from .enums import ApiPaths
31
- from .transport import (
32
- Request,
33
- Response
34
- )
25
+ from .transport import Request, Response
35
26
  from .logger import Logger
36
- from .cache import (
37
- AbstractCache,
38
- DefaultCache
39
- )
27
+ from .cache import AbstractCache, DefaultCache
28
+
29
+ import httpx
40
30
 
41
- import requests
42
31
 
32
+ class BaseClient:
43
33
 
44
- class ApiClient:
45
- def __init__(self, config: Credentials, baseUrl: str, debug: bool=False):
34
+ def __init__(self, config: Credentials, baseUrl: str, timeout: int = 20, debug: bool=False):
46
35
  self.config = config
47
36
  self.cache = DefaultCache()
48
37
  self.baseUrl = baseUrl
49
-
50
- self.REQUEST_DEBUG = False
51
- self.RESPONSE_DEBUG = False
52
- self.timeout = 180
53
-
38
+ self.timeout = timeout
54
39
  if debug:
55
40
  logging.basicConfig(level=logging.DEBUG)
56
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
+
57
259
  def PayIn(self, request: PayInModel) -> PayInResponseModel:
58
260
  # Prepare request
59
261
  request = Request(
@@ -61,12 +263,11 @@ class ApiClient:
61
263
  path=ApiPaths.invoices_payin,
62
264
  content_type='application/json',
63
265
  noAuth=False,
64
- body=request,
266
+ body=request.model_dump(exclude_none=True),
65
267
  )
66
268
 
67
269
  # Handle response
68
270
  response = self._send_request(request)
69
- self.log(request, response)
70
271
  if (response.success):
71
272
  return response.cast(PayInResponseModel, APIResponseError)
72
273
  else:
@@ -80,12 +281,11 @@ class ApiClient:
80
281
  content_type='application/json',
81
282
  noAuth=False,
82
283
  signature=True,
83
- body=request
284
+ body=request.model_dump(exclude_none=True)
84
285
  )
85
286
 
86
287
  # Handle response
87
288
  response = self._send_request(request)
88
- self.log(request, response)
89
289
  if (response.success):
90
290
  return response.cast(PayOutResponseModel, APIResponseError)
91
291
  else:
@@ -95,52 +295,48 @@ class ApiClient:
95
295
  request = Request(
96
296
  method="post",
97
297
  path=ApiPaths.invoices_payout_tlv,
98
- content_type='application/json',
298
+ content_type="application/json",
99
299
  noAuth=False,
100
300
  signature=False,
101
- body=request
301
+ body=request.model_dump(exclude_none=True),
102
302
  )
103
303
 
104
304
  # Handle response
105
305
  response = self._send_request(request)
106
- self.log(request, response)
107
306
  if not response.success:
108
307
  raise APIResponseError(response)
109
-
110
308
  return response.cast(PayOutResponseModel, APIResponseError)
111
309
 
112
310
  def Quote(self, params: GetQuoteModel) -> GetQuoteResponseModel:
113
311
  # Prepare request
114
312
  request = Request(
115
- method="post",
313
+ method="get",
116
314
  path=ApiPaths.fx_quote,
117
315
  content_type='application/json',
118
316
  noAuth=False,
119
317
  signature=False,
120
- body=params
318
+ body=params.model_dump(exclude_none=True)
121
319
  )
122
320
 
123
321
  # Handle response
124
322
  response = self._send_request(request)
125
- self.log(request, response)
126
323
  if not response.success:
127
324
  raise APIResponseError(response)
128
325
 
129
326
  return response.cast(GetQuoteResponseModel, APIResponseError)
130
-
327
+
131
328
  def QuoteQr(self, params: GetQuoteTlv) -> QuoteTlvResponse:
132
329
  request = Request(
133
330
  method="post",
134
331
  path=ApiPaths.fx_quote_tlv,
135
- content_type='application/json',
332
+ content_type="application/json",
136
333
  noAuth=False,
137
334
  signature=False,
138
- body=params
335
+ body=params.model_dump(exclude_none=True),
139
336
  )
140
337
 
141
338
  # Handle response
142
339
  response = self._send_request(request)
143
- self.log(request, response)
144
340
  if not response.success:
145
341
  raise APIResponseError(response)
146
342
 
@@ -158,41 +354,37 @@ class ApiClient:
158
354
 
159
355
  # Handle response
160
356
  response = self._send_request(request)
161
- self.log(request, response)
162
357
  if not response.success:
163
358
  raise APIResponseError(response)
164
359
 
165
360
  return response.cast(InvoiceModel, APIResponseError)
166
361
 
167
- @property
168
- def token(self) -> AccessToken | None:
362
+ def get_token(self) -> AccessToken | None:
169
363
  # First check if valid token is cached
170
- token = self.cache.get_token('AccessToken')
171
- refresh = self.cache.get_token('RefreshToken')
172
-
364
+ token = self.cache.get_token('access')
365
+ refresh = self.cache.get_token('refresh')
173
366
  if token is not None and not token.is_expired:
174
367
  return token
175
368
  else:
176
369
  # try to refresh token
177
370
  if refresh is not None and not refresh.is_expired:
178
- refreshed = self._refresh_token(token, refresh)
371
+ refreshed = self._refresh_token()
179
372
 
180
373
  if (refreshed.success):
181
374
  access = AccessToken(
182
- refreshed.json_body["access_token"]
375
+ response.json_body["access_token"]
183
376
  )
184
377
  refresh = RefreshToken(
185
- refreshed.json_body["refresh_token"],
186
- int(refreshed.json_body["expires_in"]),
378
+ response.json_body["refresh_token"],
379
+ int(response.json_body["expires_in"]),
187
380
  )
188
-
189
381
  self.cache.set_token(access)
190
382
  self.cache.set_token(refresh)
191
383
 
192
384
  return access
193
385
 
194
- # try to issue token
195
- response = self._get_token()
386
+ # try to issue token
387
+ response = self._fetch_token()
196
388
  if response.success:
197
389
 
198
390
  access = AccessToken(
@@ -202,7 +394,6 @@ class ApiClient:
202
394
  response.json_body["refresh_token"],
203
395
  int(response.json_body["expires_in"]),
204
396
  )
205
-
206
397
  self.cache.set_token(access)
207
398
  self.cache.set_token(refresh)
208
399
 
@@ -214,60 +405,40 @@ class ApiClient:
214
405
  """
215
406
  Send a specified Request to the GoPay REST API and process the response
216
407
  """
217
- # body = asdict(request.body) if is_dataclass(request.body) else request.body
218
- body = request.body
219
408
  # Add Bearer authentication to headers if needed
220
409
  headers = request.headers or {}
221
410
  if not request.noAuth:
222
- auth = self.token
411
+ auth = self.get_token()
223
412
  if auth is not None:
224
413
  headers["Authorization"] = f"Bearer {auth.token}"
225
414
 
226
415
  if (request.method == 'get'):
227
416
  params = urlencode(body)
228
- try:
229
- r = requests.request(
230
- method=request.method,
231
- url=f"{self.baseUrl}{request.path}?{params}",
232
- headers=headers,
233
- timeout=self.timeout
234
- )
235
- except:
236
- print('Error')
237
- pass
417
+ r = httpx.request(
418
+ method=request.method,
419
+ url=f"{self.baseUrl}{request.path}?{params}",
420
+ headers=headers,
421
+ timeout=self.timeout
422
+ )
238
423
  else:
239
- try:
240
- r = requests.request(
241
- method=request.method,
242
- url=f"{self.baseUrl}{request.path}",
243
- headers=headers,
244
- json=body,
245
- timeout=self.timeout
246
- )
247
- except KeyError:
248
- print('Error')
249
- pass
250
-
251
- # if r == None:
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
+ )
252
431
 
253
432
  # Build Response instance, try to decode body as JSON
254
- response = Response(raw_body=r.content, json_body={}, status_code=r.status_code)
433
+ response = Response(raw_body=r.content, json={}, status_code=r.status_code)
255
434
 
256
- if (self.REQUEST_DEBUG):
257
- print(f"{request.method} => {self.baseUrl}{request.path} => {response.status_code}")
258
-
259
435
  try:
260
436
  response.json_body = r.json()
261
437
  except json.JSONDecodeError:
262
438
  pass
263
-
264
- self.log(request, response)
265
439
  return response
266
440
 
267
- def log(self, request: Request, response: Response):
268
- Logger(self, request, response)
269
-
270
- def _get_token(self) -> Response:
441
+ def _fetch_token(self) -> Response:
271
442
  # Prepare request
272
443
  request = Request(
273
444
  method="post",
@@ -278,27 +449,16 @@ class ApiClient:
278
449
  )
279
450
  # Handle response
280
451
  response = self._send_request(request)
281
- self.log(request, response)
282
452
  return response
283
-
284
- def _refresh_token(self, access: AccessToken, refresh: RefreshToken) -> Response:
453
+
454
+ def _refresh_token(self) -> Response:
285
455
  # Prepare request
286
456
  request = Request(
287
457
  method="post",
288
458
  path=ApiPaths.token_refresh,
289
459
  content_type='application/json',
290
- noAuth=True,
291
- headers={"Authorization": f"Bearer {access.token}" },
292
- body={"refresh_token": refresh.token},
460
+ body={"refresh_token": self.refreshToken},
293
461
  )
294
462
  # Handle response
295
463
  response = self._send_request(request)
296
- self.log(request, response)
297
464
  return response
298
-
299
- def loadToken(self, params: TokenResponse):
300
- access = AccessToken(params.access_token)
301
- refresh = RefreshToken(params.refresh_token, int(params.expires_in))
302
- self.cache.set_token(access)
303
- self.cache.set_token(refresh)
304
-