paymentsgate 1.4.7__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.4.7/LICENSE +21 -0
- paymentsgate-1.4.7/PKG-INFO +148 -0
- paymentsgate-1.4.7/README.md +124 -0
- paymentsgate-1.4.7/paymentsgate/__init__.py +17 -0
- paymentsgate-1.4.7/paymentsgate/cache.py +35 -0
- paymentsgate-1.4.7/paymentsgate/client.py +223 -0
- paymentsgate-1.4.7/paymentsgate/enums.py +161 -0
- paymentsgate-1.4.7/paymentsgate/exceptions.py +38 -0
- paymentsgate-1.4.7/paymentsgate/logger.py +8 -0
- paymentsgate-1.4.7/paymentsgate/models.py +228 -0
- paymentsgate-1.4.7/paymentsgate/tokens.py +40 -0
- paymentsgate-1.4.7/paymentsgate/transport.py +31 -0
- paymentsgate-1.4.7/pyproject.toml +48 -0
- paymentsgate-1.4.7/setup.py +33 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2015 GoPay.com
|
|
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.
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: paymentsgate
|
|
3
|
+
Version: 1.4.7
|
|
4
|
+
Summary: PaymentsGate's Python SDK for REST API
|
|
5
|
+
Home-page: https://github.com/paymentsgate/python-secure-api
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: paymentsgate,payments,sdk,api
|
|
8
|
+
Author: PaymentsGate
|
|
9
|
+
Requires-Python: >=3.9,<4.0
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
|
+
Requires-Dist: jwt (>=1.3.1,<2.0.0)
|
|
18
|
+
Requires-Dist: pydantic (>=2.8.2,<3.0.0)
|
|
19
|
+
Requires-Dist: requests (>=2.31.0,<3.0.0)
|
|
20
|
+
Requires-Dist: tomli (>=2.0.1,<3.0.0)
|
|
21
|
+
Project-URL: Documentation, https://github.com/paymentsgate/python-secure-api
|
|
22
|
+
Project-URL: Repository, https://github.com/paymentsgate/python-secure-api
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# Paymentsgate Python SDK for Payments REST API
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
## Requirements
|
|
30
|
+
|
|
31
|
+
- Python >= 3.8.1
|
|
32
|
+
- dependencies:
|
|
33
|
+
- [`requests`](https://github.com/kennethreitz/requests)
|
|
34
|
+
- [`pydantic`](https://docs.pydantic.dev/latest/)
|
|
35
|
+
- [`jwt`](https://pyjwt.readthedocs.io/en/stable/)
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
The simplest way to install SDK is to use [PIP](https://docs.python.org/3/installing/):
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install paymentsgate
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Basic usage
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from paymentsgate import ApiClient, Credentials, Currencies
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# minimal configuration
|
|
52
|
+
config = Credentials().fromFile('/path/to/credentials.json');
|
|
53
|
+
|
|
54
|
+
# create ApiClient
|
|
55
|
+
client = ApiClient(config, baseUrl='https://api.example.com');
|
|
56
|
+
|
|
57
|
+
# request quote
|
|
58
|
+
res = cli.Quote(
|
|
59
|
+
{
|
|
60
|
+
"amount": 10.10,
|
|
61
|
+
"currency_from": Currencies.EUR,
|
|
62
|
+
"currency_to": Currencies.AZN,
|
|
63
|
+
}
|
|
64
|
+
)
|
|
65
|
+
print(res);
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The `credentials.json` file is used to connect to the client and contains all necessary data to use the API. This file can be obtained in your personal cabinet, in the service accounts section. Follow the instructions in the documentation to issue new keys. If you already have keys, but you don't feel comfortable storing them in a file, you can use client initialization via variables. In this case, the key data can be stored in external storage instead of on the file system:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from paymentsgate import ApiClient, Credentials
|
|
72
|
+
|
|
73
|
+
config = Credentials(
|
|
74
|
+
account_id="00000000-4000-4000-0000-00000000000a"
|
|
75
|
+
public_key="LS0tLS1CRUdJTiBSU0EgUFJJVkFUNSUlFb3dJQk..."
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
client = ApiClient(config, baseUrl='https://api.example.com');
|
|
79
|
+
|
|
80
|
+
...
|
|
81
|
+
```
|
|
82
|
+
*It is important to note that the data format for key transfer is base46.
|
|
83
|
+
|
|
84
|
+
## Examples
|
|
85
|
+
|
|
86
|
+
### create PayIn
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
res = cli.PayIn(
|
|
90
|
+
{
|
|
91
|
+
"amount": 10.10,
|
|
92
|
+
"currency": Currencies.AZN,
|
|
93
|
+
"invoiceId": "INVOICE-112123124",
|
|
94
|
+
"clientId": "",
|
|
95
|
+
"successUrl": "https://example.com/success",
|
|
96
|
+
"failUrl": "https://example.com/fail",
|
|
97
|
+
"type": InvoiceTypes.m10
|
|
98
|
+
}
|
|
99
|
+
)
|
|
100
|
+
print(res);
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### create PayOut
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
res = cli.PayOut(
|
|
107
|
+
{
|
|
108
|
+
"amount": 5.12,
|
|
109
|
+
"currencyTo": Currencies.EUR,
|
|
110
|
+
"invoiceId": "INVOICE-112123124",
|
|
111
|
+
"clientId": "CLIENT-003010023004",
|
|
112
|
+
"baseCurrency": CurrencyTypes.fiat,
|
|
113
|
+
"feesStrategy": FeesStrategy.add,
|
|
114
|
+
"recipient": {
|
|
115
|
+
"account_number": "4000000000000012",
|
|
116
|
+
"account_owner": "CARD HOLDER",
|
|
117
|
+
"type": CredentialsTypes.card
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
)
|
|
121
|
+
print(res);
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Error handling
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
try:
|
|
128
|
+
res = cli.PayOut(
|
|
129
|
+
{
|
|
130
|
+
"amount": 5.12,
|
|
131
|
+
"currencyTo": Currencies.EUR,
|
|
132
|
+
"invoiceId": "INVOICE-112123124",
|
|
133
|
+
"clientId": "CLIENT-003010023004",
|
|
134
|
+
"baseCurrency": CurrencyTypes.fiat,
|
|
135
|
+
"feesStrategy": FeesStrategy.add,
|
|
136
|
+
"recipient": {
|
|
137
|
+
"account_number": "4000000000000012",
|
|
138
|
+
"account_owner": "CARD HOLDER",
|
|
139
|
+
"type": CredentialsTypes.card
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
)
|
|
143
|
+
print(res);
|
|
144
|
+
except APIAuthenticationError as err:
|
|
145
|
+
print(f"Authentication fail: {err.message}")
|
|
146
|
+
except APIResponseError as err:
|
|
147
|
+
print(f"Exception: {err.error}; Message: {err.message}")
|
|
148
|
+
```
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
|
|
2
|
+
# Paymentsgate Python SDK for Payments REST API
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- Python >= 3.8.1
|
|
8
|
+
- dependencies:
|
|
9
|
+
- [`requests`](https://github.com/kennethreitz/requests)
|
|
10
|
+
- [`pydantic`](https://docs.pydantic.dev/latest/)
|
|
11
|
+
- [`jwt`](https://pyjwt.readthedocs.io/en/stable/)
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
The simplest way to install SDK is to use [PIP](https://docs.python.org/3/installing/):
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install paymentsgate
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Basic usage
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
from paymentsgate import ApiClient, Credentials, Currencies
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# minimal configuration
|
|
28
|
+
config = Credentials().fromFile('/path/to/credentials.json');
|
|
29
|
+
|
|
30
|
+
# create ApiClient
|
|
31
|
+
client = ApiClient(config, baseUrl='https://api.example.com');
|
|
32
|
+
|
|
33
|
+
# request quote
|
|
34
|
+
res = cli.Quote(
|
|
35
|
+
{
|
|
36
|
+
"amount": 10.10,
|
|
37
|
+
"currency_from": Currencies.EUR,
|
|
38
|
+
"currency_to": Currencies.AZN,
|
|
39
|
+
}
|
|
40
|
+
)
|
|
41
|
+
print(res);
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The `credentials.json` file is used to connect to the client and contains all necessary data to use the API. This file can be obtained in your personal cabinet, in the service accounts section. Follow the instructions in the documentation to issue new keys. If you already have keys, but you don't feel comfortable storing them in a file, you can use client initialization via variables. In this case, the key data can be stored in external storage instead of on the file system:
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from paymentsgate import ApiClient, Credentials
|
|
48
|
+
|
|
49
|
+
config = Credentials(
|
|
50
|
+
account_id="00000000-4000-4000-0000-00000000000a"
|
|
51
|
+
public_key="LS0tLS1CRUdJTiBSU0EgUFJJVkFUNSUlFb3dJQk..."
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
client = ApiClient(config, baseUrl='https://api.example.com');
|
|
55
|
+
|
|
56
|
+
...
|
|
57
|
+
```
|
|
58
|
+
*It is important to note that the data format for key transfer is base46.
|
|
59
|
+
|
|
60
|
+
## Examples
|
|
61
|
+
|
|
62
|
+
### create PayIn
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
res = cli.PayIn(
|
|
66
|
+
{
|
|
67
|
+
"amount": 10.10,
|
|
68
|
+
"currency": Currencies.AZN,
|
|
69
|
+
"invoiceId": "INVOICE-112123124",
|
|
70
|
+
"clientId": "",
|
|
71
|
+
"successUrl": "https://example.com/success",
|
|
72
|
+
"failUrl": "https://example.com/fail",
|
|
73
|
+
"type": InvoiceTypes.m10
|
|
74
|
+
}
|
|
75
|
+
)
|
|
76
|
+
print(res);
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### create PayOut
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
res = cli.PayOut(
|
|
83
|
+
{
|
|
84
|
+
"amount": 5.12,
|
|
85
|
+
"currencyTo": Currencies.EUR,
|
|
86
|
+
"invoiceId": "INVOICE-112123124",
|
|
87
|
+
"clientId": "CLIENT-003010023004",
|
|
88
|
+
"baseCurrency": CurrencyTypes.fiat,
|
|
89
|
+
"feesStrategy": FeesStrategy.add,
|
|
90
|
+
"recipient": {
|
|
91
|
+
"account_number": "4000000000000012",
|
|
92
|
+
"account_owner": "CARD HOLDER",
|
|
93
|
+
"type": CredentialsTypes.card
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
)
|
|
97
|
+
print(res);
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Error handling
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
try:
|
|
104
|
+
res = cli.PayOut(
|
|
105
|
+
{
|
|
106
|
+
"amount": 5.12,
|
|
107
|
+
"currencyTo": Currencies.EUR,
|
|
108
|
+
"invoiceId": "INVOICE-112123124",
|
|
109
|
+
"clientId": "CLIENT-003010023004",
|
|
110
|
+
"baseCurrency": CurrencyTypes.fiat,
|
|
111
|
+
"feesStrategy": FeesStrategy.add,
|
|
112
|
+
"recipient": {
|
|
113
|
+
"account_number": "4000000000000012",
|
|
114
|
+
"account_owner": "CARD HOLDER",
|
|
115
|
+
"type": CredentialsTypes.card
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
)
|
|
119
|
+
print(res);
|
|
120
|
+
except APIAuthenticationError as err:
|
|
121
|
+
print(f"Authentication fail: {err.message}")
|
|
122
|
+
except APIResponseError as err:
|
|
123
|
+
print(f"Exception: {err.error}; Message: {err.message}")
|
|
124
|
+
```
|
|
@@ -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
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
|
|
4
|
+
from paymentsgate.tokens import (
|
|
5
|
+
AccessToken,
|
|
6
|
+
RefreshToken
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
class AbstractCache(ABC):
|
|
10
|
+
"""
|
|
11
|
+
Abstract class for implementing custom caches used to cache the token
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
@abstractmethod
|
|
15
|
+
def get_token(self, key: str) -> AccessToken | RefreshToken:
|
|
16
|
+
"""
|
|
17
|
+
Fetch a token with the specified key from the cache
|
|
18
|
+
"""
|
|
19
|
+
...
|
|
20
|
+
|
|
21
|
+
@abstractmethod
|
|
22
|
+
def set_token(self,token: AccessToken | RefreshToken) -> None:
|
|
23
|
+
"""
|
|
24
|
+
Save the token to the cache under the specified key
|
|
25
|
+
"""
|
|
26
|
+
...
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class DefaultCache(AbstractCache):
|
|
30
|
+
tokens: dict[str, AccessToken | RefreshToken] = field(default_factory=dict, init=False)
|
|
31
|
+
def get_token(self, key: str) -> AccessToken | RefreshToken | None:
|
|
32
|
+
return self.tokens.get(key)
|
|
33
|
+
|
|
34
|
+
def set_token(self, token: AccessToken | RefreshToken) -> None:
|
|
35
|
+
self.tokens[token.__class__.__name__] = token
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import logging
|
|
3
|
+
from dataclasses import dataclass, is_dataclass, field, asdict
|
|
4
|
+
import json
|
|
5
|
+
from urllib.parse import urlencode
|
|
6
|
+
|
|
7
|
+
from paymentsgate.tokens import (
|
|
8
|
+
AccessToken,
|
|
9
|
+
RefreshToken
|
|
10
|
+
)
|
|
11
|
+
from paymentsgate.exceptions import (
|
|
12
|
+
APIResponseError,
|
|
13
|
+
APIAuthenticationError
|
|
14
|
+
)
|
|
15
|
+
from paymentsgate.models import (
|
|
16
|
+
Credentials,
|
|
17
|
+
GetQuoteModel,
|
|
18
|
+
GetQuoteResponseModel,
|
|
19
|
+
PayInModel,
|
|
20
|
+
PayInResponseModel,
|
|
21
|
+
PayOutModel,
|
|
22
|
+
PayOutResponseModel
|
|
23
|
+
)
|
|
24
|
+
from paymentsgate.enums import ApiPaths
|
|
25
|
+
from paymentsgate.transport import (
|
|
26
|
+
Request,
|
|
27
|
+
Response
|
|
28
|
+
)
|
|
29
|
+
from paymentsgate.logger import Logger
|
|
30
|
+
from paymentsgate.cache import (
|
|
31
|
+
AbstractCache,
|
|
32
|
+
DefaultCache
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
import requests
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class ApiClient:
|
|
40
|
+
baseUrl: str = field(default="", init=False)
|
|
41
|
+
timeout: int = field(default=180, init=True)
|
|
42
|
+
logger: Logger = Logger
|
|
43
|
+
cache: AbstractCache = field(default_factory=DefaultCache)
|
|
44
|
+
config: Credentials = field(default_factory=dict, init=False)
|
|
45
|
+
|
|
46
|
+
REQUEST_DEBUG: bool = False
|
|
47
|
+
RESPONSE_DEBUG: bool = False
|
|
48
|
+
|
|
49
|
+
def __init__(self, config: Credentials, baseUrl: str, debug: bool=False):
|
|
50
|
+
self.config = config
|
|
51
|
+
self.cache = DefaultCache()
|
|
52
|
+
self.baseUrl = baseUrl
|
|
53
|
+
if debug:
|
|
54
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
55
|
+
|
|
56
|
+
def PayIn(self, request: PayInModel) -> PayInResponseModel:
|
|
57
|
+
# Prepare request
|
|
58
|
+
request = Request(
|
|
59
|
+
method="post",
|
|
60
|
+
path=ApiPaths.invoices_payin,
|
|
61
|
+
content_type='application/json',
|
|
62
|
+
noAuth=False,
|
|
63
|
+
body=request,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Handle response
|
|
67
|
+
response = self._send_request(request)
|
|
68
|
+
self.logger(request, response)
|
|
69
|
+
if (response.success):
|
|
70
|
+
return response.cast(PayInResponseModel, APIResponseError)
|
|
71
|
+
else:
|
|
72
|
+
raise APIResponseError(response)
|
|
73
|
+
|
|
74
|
+
def PayOut(self, request: PayOutModel) -> PayOutResponseModel:
|
|
75
|
+
# Prepare request
|
|
76
|
+
request = Request(
|
|
77
|
+
method="post",
|
|
78
|
+
path=ApiPaths.invoices_payout,
|
|
79
|
+
content_type='application/json',
|
|
80
|
+
noAuth=False,
|
|
81
|
+
signature=True,
|
|
82
|
+
body=request
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# Handle response
|
|
86
|
+
response = self._send_request(request)
|
|
87
|
+
self.logger(request, response)
|
|
88
|
+
if (response.success):
|
|
89
|
+
return response.cast(PayOutResponseModel, APIResponseError)
|
|
90
|
+
else:
|
|
91
|
+
raise APIResponseError(response)
|
|
92
|
+
|
|
93
|
+
def Quote(self, request: GetQuoteModel) -> GetQuoteResponseModel:
|
|
94
|
+
# Prepare request
|
|
95
|
+
request = Request(
|
|
96
|
+
method="get",
|
|
97
|
+
path=ApiPaths.fx_quote,
|
|
98
|
+
content_type='application/json',
|
|
99
|
+
noAuth=False,
|
|
100
|
+
signature=False,
|
|
101
|
+
body=request
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# Handle response
|
|
105
|
+
response = self._send_request(request)
|
|
106
|
+
self.logger(request, response)
|
|
107
|
+
if not response.success:
|
|
108
|
+
raise APIResponseError(response)
|
|
109
|
+
|
|
110
|
+
return response.cast(GetQuoteResponseModel, APIResponseError)
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def token(self) -> AccessToken | None:
|
|
114
|
+
# First check if valid token is cached
|
|
115
|
+
token = self.cache.get_token('access')
|
|
116
|
+
refresh = self.cache.get_token('refresh')
|
|
117
|
+
if token is not None and not token.is_expired:
|
|
118
|
+
return token
|
|
119
|
+
else:
|
|
120
|
+
# try to refresh token
|
|
121
|
+
if refresh is not None and not refresh.is_expired:
|
|
122
|
+
refreshed = self._refresh_token()
|
|
123
|
+
|
|
124
|
+
if (refreshed.success):
|
|
125
|
+
access = AccessToken(
|
|
126
|
+
response.json["access_token"]
|
|
127
|
+
)
|
|
128
|
+
refresh = RefreshToken(
|
|
129
|
+
response.json["refresh_token"],
|
|
130
|
+
int(response.json["expires_in"]),
|
|
131
|
+
)
|
|
132
|
+
self.cache.set_token(access)
|
|
133
|
+
self.cache.set_token(refresh)
|
|
134
|
+
|
|
135
|
+
return access
|
|
136
|
+
|
|
137
|
+
# try to issue token
|
|
138
|
+
response = self._get_token()
|
|
139
|
+
if response.success:
|
|
140
|
+
|
|
141
|
+
access = AccessToken(
|
|
142
|
+
response.json["access_token"]
|
|
143
|
+
)
|
|
144
|
+
refresh = RefreshToken(
|
|
145
|
+
response.json["refresh_token"],
|
|
146
|
+
int(response.json["expires_in"]),
|
|
147
|
+
)
|
|
148
|
+
self.cache.set_token(access)
|
|
149
|
+
self.cache.set_token(refresh)
|
|
150
|
+
|
|
151
|
+
return access
|
|
152
|
+
else:
|
|
153
|
+
raise APIAuthenticationError(response)
|
|
154
|
+
|
|
155
|
+
def _send_request(self, request: Request) -> Response:
|
|
156
|
+
"""
|
|
157
|
+
Send a specified Request to the GoPay REST API and process the response
|
|
158
|
+
"""
|
|
159
|
+
body = asdict(request.body) if is_dataclass(request.body) else request.body
|
|
160
|
+
# Add Bearer authentication to headers if needed
|
|
161
|
+
headers = request.headers or {}
|
|
162
|
+
if not request.noAuth:
|
|
163
|
+
auth = self.token
|
|
164
|
+
if auth is not None:
|
|
165
|
+
headers["Authorization"] = f"Bearer {auth.token}"
|
|
166
|
+
|
|
167
|
+
if (request.method == 'get'):
|
|
168
|
+
params = urlencode(body)
|
|
169
|
+
r = requests.request(
|
|
170
|
+
method=request.method,
|
|
171
|
+
url=f"{self.baseUrl}{request.path}?{params}",
|
|
172
|
+
headers=headers,
|
|
173
|
+
timeout=self.timeout
|
|
174
|
+
)
|
|
175
|
+
else:
|
|
176
|
+
r = requests.request(
|
|
177
|
+
method=request.method,
|
|
178
|
+
url=f"{self.baseUrl}{request.path}",
|
|
179
|
+
headers=headers,
|
|
180
|
+
json=body,
|
|
181
|
+
timeout=self.timeout
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
# Build Response instance, try to decode body as JSON
|
|
185
|
+
response = Response(raw_body=r.content, json={}, status_code=r.status_code)
|
|
186
|
+
|
|
187
|
+
if (self.REQUEST_DEBUG):
|
|
188
|
+
print(f"{request.method} => {self.baseUrl}{request.path} => {response.status_code}")
|
|
189
|
+
|
|
190
|
+
try:
|
|
191
|
+
response.json = r.json()
|
|
192
|
+
except json.JSONDecodeError:
|
|
193
|
+
pass
|
|
194
|
+
|
|
195
|
+
self.logger(request, response)
|
|
196
|
+
return response
|
|
197
|
+
|
|
198
|
+
def _get_token(self) -> Response:
|
|
199
|
+
# Prepare request
|
|
200
|
+
request = Request(
|
|
201
|
+
method="post",
|
|
202
|
+
path=ApiPaths.token_issue,
|
|
203
|
+
content_type='application/json',
|
|
204
|
+
noAuth=True,
|
|
205
|
+
body={"account_id": self.config.account_id, "public_key": self.config.public_key},
|
|
206
|
+
)
|
|
207
|
+
# Handle response
|
|
208
|
+
response = self._send_request(request)
|
|
209
|
+
self.logger(request, response)
|
|
210
|
+
return response
|
|
211
|
+
|
|
212
|
+
def _refresh_token(self) -> Response:
|
|
213
|
+
# Prepare request
|
|
214
|
+
request = Request(
|
|
215
|
+
method="post",
|
|
216
|
+
path=ApiPaths.token_refresh,
|
|
217
|
+
content_type='application/json',
|
|
218
|
+
body={"refresh_token": self.refreshToken},
|
|
219
|
+
)
|
|
220
|
+
# Handle response
|
|
221
|
+
response = self._send_request(request)
|
|
222
|
+
self.logger(request, response)
|
|
223
|
+
return response
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class StrEnum(str, Enum):
|
|
5
|
+
def __str__(self) -> str:
|
|
6
|
+
return self.value
|
|
7
|
+
|
|
8
|
+
class AuthenticationRealms(StrEnum):
|
|
9
|
+
production = "production"
|
|
10
|
+
sandbox = "sandbox"
|
|
11
|
+
|
|
12
|
+
class ApiPaths(StrEnum):
|
|
13
|
+
token_issue = "/auth/token"
|
|
14
|
+
token_refresh = "/auth/token/refresh"
|
|
15
|
+
token_revoke = "/auth/token/revoke"
|
|
16
|
+
token_validate = "/auth/token/validate"
|
|
17
|
+
invoices_payin = "/deals/payin"
|
|
18
|
+
invoices_payout = "/deals/payout"
|
|
19
|
+
invoices_info = "/deals/:id"
|
|
20
|
+
invoices_credentials = "/deals/:id/credentials"
|
|
21
|
+
assets_list = "/wallet"
|
|
22
|
+
assets_deposit = "/wallet/deposit"
|
|
23
|
+
banks_list = "/banks/find"
|
|
24
|
+
appel_create = "/support/create"
|
|
25
|
+
appel_list = "/support/list"
|
|
26
|
+
appel_stat = "/support/statistic"
|
|
27
|
+
fx_quote = "/fx/calculatenew"
|
|
28
|
+
|
|
29
|
+
class Currencies(StrEnum):
|
|
30
|
+
USDT = "USDT"
|
|
31
|
+
EUR = "EUR"
|
|
32
|
+
USD = "USD"
|
|
33
|
+
TRY = "TRY"
|
|
34
|
+
CNY = "CNY"
|
|
35
|
+
JPY = "JPY"
|
|
36
|
+
GEL = "GEL"
|
|
37
|
+
AZN = "AZN"
|
|
38
|
+
INR = "INR"
|
|
39
|
+
AED = "AED"
|
|
40
|
+
KZT = "KZT"
|
|
41
|
+
UZS = "UZS"
|
|
42
|
+
TJS = "TJS"
|
|
43
|
+
EGP = "EGP"
|
|
44
|
+
PKR = "PKR"
|
|
45
|
+
IDR = "IDR"
|
|
46
|
+
BDT = "BDT"
|
|
47
|
+
GBP = "GBP"
|
|
48
|
+
RUB = "RUB"
|
|
49
|
+
THB = "THB"
|
|
50
|
+
KGS = "KGS"
|
|
51
|
+
PHP = "PHP"
|
|
52
|
+
ZAR = "ZAR"
|
|
53
|
+
ARS = "ARS"
|
|
54
|
+
GHS = "GHS"
|
|
55
|
+
KES = "KES"
|
|
56
|
+
NGN = "NGN"
|
|
57
|
+
AMD = "AMD"
|
|
58
|
+
|
|
59
|
+
class Languages(StrEnum):
|
|
60
|
+
EN = "EN"
|
|
61
|
+
IN = "IN"
|
|
62
|
+
AE = "AE"
|
|
63
|
+
TR = "TR"
|
|
64
|
+
GE = "GE"
|
|
65
|
+
RU = "RU"
|
|
66
|
+
UZ = "UZ"
|
|
67
|
+
AZ = "AZ"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class Statuses(StrEnum):
|
|
71
|
+
queued = "queued"
|
|
72
|
+
new = "new"
|
|
73
|
+
pending = "pending"
|
|
74
|
+
paid = "paid"
|
|
75
|
+
completed = "completed"
|
|
76
|
+
disputed = "disputed"
|
|
77
|
+
canceled = "canceled"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class CurrencyTypes(StrEnum):
|
|
81
|
+
fiat = "fiat"
|
|
82
|
+
crypto = "crypto"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class InvoiceTypes(StrEnum):
|
|
86
|
+
p2p = "p2p"
|
|
87
|
+
ecom = "ecom"
|
|
88
|
+
c2c = "c2c"
|
|
89
|
+
m10 = "m10"
|
|
90
|
+
mpay = "mpay"
|
|
91
|
+
sbp = "sbp"
|
|
92
|
+
sbpqr = "sbpqr"
|
|
93
|
+
iban = "iban"
|
|
94
|
+
upi = "upi"
|
|
95
|
+
imps = "imps"
|
|
96
|
+
spei = "spei"
|
|
97
|
+
pix = "pix"
|
|
98
|
+
rps = "rps"
|
|
99
|
+
ibps = "ibps"
|
|
100
|
+
bizum = "bizum"
|
|
101
|
+
rkgs = "rkgs"
|
|
102
|
+
kgsphone = "kgsphone"
|
|
103
|
+
krungthainext = "krungthainext"
|
|
104
|
+
sber = "sber"
|
|
105
|
+
kztphone = "kztphone"
|
|
106
|
+
accountbdt = "accountbdt"
|
|
107
|
+
alipay = "alipay"
|
|
108
|
+
accountegp = "accountegp"
|
|
109
|
+
accountphp = "accountphp"
|
|
110
|
+
sberqr = "sberqr"
|
|
111
|
+
maya = "maya"
|
|
112
|
+
gcash = "gcash"
|
|
113
|
+
banktransferphp = "banktransferphp"
|
|
114
|
+
banktransferars = "banktransferars"
|
|
115
|
+
phonepe = "phonepe"
|
|
116
|
+
freecharge = "freecharge"
|
|
117
|
+
instapay = "instapay"
|
|
118
|
+
vodafonecash = "vodafonecash"
|
|
119
|
+
razn = "razn"
|
|
120
|
+
rtjs = "rtjs"
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class CredentialsTypes(StrEnum):
|
|
124
|
+
iban = "iban"
|
|
125
|
+
phone = "phone"
|
|
126
|
+
card = "card"
|
|
127
|
+
fps = "fps"
|
|
128
|
+
account = "account"
|
|
129
|
+
custom = "custom"
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class RiskScoreLevels(StrEnum):
|
|
133
|
+
unclassified = "unclassified"
|
|
134
|
+
hr = "hr" # highest risk
|
|
135
|
+
ftd = "ftd" # high risk
|
|
136
|
+
trusted = "trusted" # low risk
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class CancellationReason(StrEnum):
|
|
140
|
+
NO_MONEY = "NO_MONEY"
|
|
141
|
+
CREDENTIALS_INVALID = "CREDENTIALS_INVALID"
|
|
142
|
+
EXPIRED = "EXPIRED"
|
|
143
|
+
PRECHARGE_GAP_UPPER_LIMIT = "PRECHARGE_GAP_UPPER_LIMIT"
|
|
144
|
+
CROSS_BANK_TFF_LESS_THAN_3K = "CROSS_BANK_TFF_LESS_THAN_3K"
|
|
145
|
+
CROSS_BANK_UNSUPPORTED = "CROSS_BANK_UNSUPPORTED"
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class FeesStrategy(StrEnum):
|
|
149
|
+
add = "add"
|
|
150
|
+
sub = "sub"
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class InvoiceDirection(StrEnum):
|
|
154
|
+
F2C = "F2C"
|
|
155
|
+
C2F = "C2F"
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class TTLUnits(StrEnum):
|
|
159
|
+
sec = "sec"
|
|
160
|
+
min = "min"
|
|
161
|
+
hour = "hour"
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
|
|
2
|
+
from paymentsgate.transport import Response
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class PaymentsgateError(Exception):
|
|
6
|
+
pass
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class APIError(PaymentsgateError):
|
|
10
|
+
error: str
|
|
11
|
+
message: str
|
|
12
|
+
code: int
|
|
13
|
+
data: object | None
|
|
14
|
+
details: object | None
|
|
15
|
+
|
|
16
|
+
def __init__(self, error: str, message: str, data: object | None, details: object | None, status: int) -> None:
|
|
17
|
+
super().__init__(f"[{error}] {message} (status: {status})")
|
|
18
|
+
self.error = error
|
|
19
|
+
self.message = message
|
|
20
|
+
self.data = data
|
|
21
|
+
self.code = status;
|
|
22
|
+
self.details = details;
|
|
23
|
+
|
|
24
|
+
if (details is not None):
|
|
25
|
+
print('Error details:', self.details)
|
|
26
|
+
if (data is not None):
|
|
27
|
+
print(self.data)
|
|
28
|
+
# print(f"{self.error}: {self.message} code: {self.code} details: {self.details}")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class APIResponseError(APIError):
|
|
33
|
+
def __init__(self, response: Response) -> None:
|
|
34
|
+
super().__init__(response.json.get('error'), response.json.get('message'), response.json.get('data'), response.json.get('details'), response.status_code)
|
|
35
|
+
|
|
36
|
+
class APIAuthenticationError(APIError):
|
|
37
|
+
def __init__(self, response: Response) -> None:
|
|
38
|
+
super().__init__(response.json.get('error'), response.json.get('message'), response.json.get('data'), response.json.get('details'), response.status_code)
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
import datetime
|
|
4
|
+
import json
|
|
5
|
+
from typing import Optional, List
|
|
6
|
+
|
|
7
|
+
from paymentsgate.enums import (
|
|
8
|
+
Currencies,
|
|
9
|
+
InvoiceTypes,
|
|
10
|
+
Languages,
|
|
11
|
+
Statuses,
|
|
12
|
+
TTLUnits,
|
|
13
|
+
CurrencyTypes,
|
|
14
|
+
FeesStrategy,
|
|
15
|
+
InvoiceDirection,
|
|
16
|
+
CredentialsTypes
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
from pydantic import BaseModel, ConfigDict
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class Credentials:
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
account_id: str,
|
|
26
|
+
merchant_id: str,
|
|
27
|
+
project_id: str,
|
|
28
|
+
private_key: str,
|
|
29
|
+
public_key: str
|
|
30
|
+
):
|
|
31
|
+
self.account_id = account_id
|
|
32
|
+
self.merchant_id = merchant_id
|
|
33
|
+
self.project_id = project_id
|
|
34
|
+
self.private_key = private_key
|
|
35
|
+
self.public_key = public_key
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def fromFile(cls, filename):
|
|
39
|
+
data = json.load(open(filename))
|
|
40
|
+
return cls(data.get('account_id'),
|
|
41
|
+
data.get('merchant_id'),
|
|
42
|
+
data.get('project_id'),
|
|
43
|
+
data.get('private_key'),
|
|
44
|
+
data.get('public_key'))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class PayInFingerprintBrowserModel:
|
|
49
|
+
acceptHeader: str
|
|
50
|
+
colorDepth: int
|
|
51
|
+
language: str
|
|
52
|
+
screenHeight: int
|
|
53
|
+
screenWidth: int
|
|
54
|
+
timezone: str
|
|
55
|
+
userAgent: str
|
|
56
|
+
javaEnabled: bool
|
|
57
|
+
windowHeight: int
|
|
58
|
+
windowWidth: int
|
|
59
|
+
|
|
60
|
+
@dataclass
|
|
61
|
+
class PayInFingerprintModel:
|
|
62
|
+
fingerprint: str
|
|
63
|
+
ip: str
|
|
64
|
+
country: str
|
|
65
|
+
city: str
|
|
66
|
+
state: str
|
|
67
|
+
zip: str
|
|
68
|
+
browser: Optional[PayInFingerprintBrowserModel]
|
|
69
|
+
|
|
70
|
+
@dataclass
|
|
71
|
+
class PayInModel:
|
|
72
|
+
amount: float
|
|
73
|
+
currency: Currencies
|
|
74
|
+
invoiceId: Optional[str] # idempotent key
|
|
75
|
+
clientId: Optional[str]
|
|
76
|
+
type: InvoiceTypes
|
|
77
|
+
bankId: Optional[str]
|
|
78
|
+
trusted: Optional[bool]
|
|
79
|
+
successUrl: Optional[str]
|
|
80
|
+
failUrl: Optional[str]
|
|
81
|
+
backUrl: Optional[str]
|
|
82
|
+
clientCard: Optional[str]
|
|
83
|
+
fingerprint: Optional[PayInFingerprintModel]
|
|
84
|
+
lang: Optional[Languages]
|
|
85
|
+
|
|
86
|
+
@dataclass
|
|
87
|
+
class PayInResponseModel:
|
|
88
|
+
id: str
|
|
89
|
+
status: Statuses
|
|
90
|
+
type: InvoiceTypes
|
|
91
|
+
url: str
|
|
92
|
+
|
|
93
|
+
@dataclass
|
|
94
|
+
class PayOutRecipientModel:
|
|
95
|
+
account_number: str
|
|
96
|
+
account_owner: Optional[str]
|
|
97
|
+
account_iban: Optional[str]
|
|
98
|
+
account_swift: Optional[str]
|
|
99
|
+
account_phone: Optional[str]
|
|
100
|
+
account_bic: Optional[str]
|
|
101
|
+
account_ewallet_name: Optional[str]
|
|
102
|
+
account_email: Optional[str]
|
|
103
|
+
account_bank_id: Optional[str]
|
|
104
|
+
type: Optional[CredentialsTypes]
|
|
105
|
+
|
|
106
|
+
@dataclass
|
|
107
|
+
class PayOutModel:
|
|
108
|
+
currency: Optional[Currencies] # currency from, by default = usdt
|
|
109
|
+
currencyTo:Currencies
|
|
110
|
+
amount: float
|
|
111
|
+
invoiceId: Optional[str] # idempotent key
|
|
112
|
+
clientId: Optional[str]
|
|
113
|
+
ttl: Optional[int]
|
|
114
|
+
ttl_unit: Optional[TTLUnits]
|
|
115
|
+
finalAmount: Optional[float]
|
|
116
|
+
sender_name: Optional[str]
|
|
117
|
+
baseCurrency: Optional[CurrencyTypes]
|
|
118
|
+
feesStrategy: Optional[FeesStrategy]
|
|
119
|
+
recipient: PayOutRecipientModel
|
|
120
|
+
quoteId: Optional[str]
|
|
121
|
+
|
|
122
|
+
@dataclass
|
|
123
|
+
class PayOutResponseModel:
|
|
124
|
+
id: str
|
|
125
|
+
status: Statuses
|
|
126
|
+
|
|
127
|
+
@dataclass
|
|
128
|
+
class GetQuoteModel:
|
|
129
|
+
currency_from: Currencies
|
|
130
|
+
currency_to: Currencies
|
|
131
|
+
amount: float
|
|
132
|
+
subtype: InvoiceTypes
|
|
133
|
+
|
|
134
|
+
@dataclass
|
|
135
|
+
class QuoteEntity:
|
|
136
|
+
currency_from: CurrencyModel
|
|
137
|
+
currency_to: CurrencyModel
|
|
138
|
+
pair: str
|
|
139
|
+
rate: float
|
|
140
|
+
|
|
141
|
+
@dataclass
|
|
142
|
+
class GetQuoteResponseModel:
|
|
143
|
+
id: Optional[str] = None
|
|
144
|
+
finalAmount: Optional[float] = None
|
|
145
|
+
direction: Optional[InvoiceDirection] = None
|
|
146
|
+
fullRate: Optional[float] = None
|
|
147
|
+
fullRateReverse: Optional[float] = None
|
|
148
|
+
fees: Optional[float] = None
|
|
149
|
+
fees_percent: Optional[float] = None
|
|
150
|
+
quotes: Optional[List[QuoteEntity]] = None
|
|
151
|
+
expiredAt: Optional[datetime.datetime] = None
|
|
152
|
+
|
|
153
|
+
#deprecated
|
|
154
|
+
currency_from: Optional[CurrencyModel] = None
|
|
155
|
+
currency_to: Optional[CurrencyModel] = None
|
|
156
|
+
currency_middle: Optional[CurrencyModel] = None
|
|
157
|
+
rate1: Optional[float] = None
|
|
158
|
+
rate2: Optional[float] = None
|
|
159
|
+
rate3: Optional[float] = None
|
|
160
|
+
net_amount: Optional[float] = None
|
|
161
|
+
metadata: Optional[object] = None
|
|
162
|
+
|
|
163
|
+
@dataclass
|
|
164
|
+
class DepositAddressResponseModel:
|
|
165
|
+
currency: Currencies
|
|
166
|
+
address: str
|
|
167
|
+
expiredAt: datetime
|
|
168
|
+
|
|
169
|
+
@dataclass
|
|
170
|
+
class CurrencyModel:
|
|
171
|
+
_id: str
|
|
172
|
+
type: CurrencyTypes
|
|
173
|
+
code: Currencies
|
|
174
|
+
symbol: str
|
|
175
|
+
label: Optional[str]
|
|
176
|
+
decimal: int
|
|
177
|
+
countryCode: Optional[str]
|
|
178
|
+
countryName: Optional[str]
|
|
179
|
+
|
|
180
|
+
@dataclass
|
|
181
|
+
class BankModel:
|
|
182
|
+
name: str
|
|
183
|
+
title: str
|
|
184
|
+
currency: Currencies
|
|
185
|
+
fpsId: str
|
|
186
|
+
|
|
187
|
+
@dataclass
|
|
188
|
+
class InvoiceStatusModel:
|
|
189
|
+
name: Statuses
|
|
190
|
+
createdAt: datetime
|
|
191
|
+
updatedAt: datetime
|
|
192
|
+
|
|
193
|
+
@dataclass
|
|
194
|
+
class InvoiceAmountModel:
|
|
195
|
+
crypto: float
|
|
196
|
+
fiat: float
|
|
197
|
+
fiat_net: float
|
|
198
|
+
|
|
199
|
+
@dataclass
|
|
200
|
+
class InvoiceMetadataModel:
|
|
201
|
+
invoiceId: Optional[str]
|
|
202
|
+
clientId: Optional[str]
|
|
203
|
+
|
|
204
|
+
@dataclass
|
|
205
|
+
class InvoiceModel:
|
|
206
|
+
orderId: str
|
|
207
|
+
projectId: str
|
|
208
|
+
currencyFrom: CurrencyModel
|
|
209
|
+
currencyTo: CurrencyModel
|
|
210
|
+
direction: InvoiceDirection
|
|
211
|
+
amount: float
|
|
212
|
+
status: InvoiceStatusModel
|
|
213
|
+
amounts: InvoiceAmountModel
|
|
214
|
+
metadata: InvoiceMetadataModel
|
|
215
|
+
createdAt: datetime
|
|
216
|
+
updatedAt: datetime
|
|
217
|
+
expiredAt: datetime
|
|
218
|
+
|
|
219
|
+
@dataclass
|
|
220
|
+
class AssetsAccountModel:
|
|
221
|
+
currency: CurrencyModel;
|
|
222
|
+
total: float
|
|
223
|
+
pending: float
|
|
224
|
+
available: float
|
|
225
|
+
|
|
226
|
+
@dataclass
|
|
227
|
+
class AssetsResponseModel:
|
|
228
|
+
assets: List[AssetsAccountModel]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from jwt import JWT
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
@dataclass
|
|
6
|
+
class AccessToken:
|
|
7
|
+
token: str
|
|
8
|
+
expiredAt: int
|
|
9
|
+
|
|
10
|
+
def __init__(self, token):
|
|
11
|
+
self.token = token
|
|
12
|
+
jwdInstance = JWT()
|
|
13
|
+
parsed = jwdInstance.decode(token, do_verify=False, do_time_check=False)
|
|
14
|
+
self.expiredAt = int(parsed['exp'])
|
|
15
|
+
@property
|
|
16
|
+
def is_expired(self):
|
|
17
|
+
if self.expiredAt:
|
|
18
|
+
return int(time.time()) >= self.expiredAt;
|
|
19
|
+
return True
|
|
20
|
+
|
|
21
|
+
def __str__(self) -> str:
|
|
22
|
+
return self.token
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class RefreshToken:
|
|
26
|
+
token: str
|
|
27
|
+
expiredAt: int
|
|
28
|
+
|
|
29
|
+
def __init__(self, token, expiredAt):
|
|
30
|
+
self.token = token
|
|
31
|
+
self.expiredAt = expiredAt
|
|
32
|
+
@property
|
|
33
|
+
def is_expired(self):
|
|
34
|
+
if self.expiredAt:
|
|
35
|
+
return int(time.time()) >= self.expiredAt;
|
|
36
|
+
return True
|
|
37
|
+
|
|
38
|
+
def __str__(self) -> str:
|
|
39
|
+
return self.token
|
|
40
|
+
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
|
|
4
|
+
@dataclass
|
|
5
|
+
class Request:
|
|
6
|
+
method: str
|
|
7
|
+
path: str
|
|
8
|
+
content_type: str = 'application/json'
|
|
9
|
+
headers: dict[str, str] | None = None
|
|
10
|
+
body: dict | None = None
|
|
11
|
+
noAuth: bool | None = False
|
|
12
|
+
signature: bool | None = False
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class Response:
|
|
17
|
+
raw_body: bytes
|
|
18
|
+
json: dict
|
|
19
|
+
status_code: int
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def success(self) -> bool:
|
|
23
|
+
return self.status_code < 400
|
|
24
|
+
|
|
25
|
+
def cast(self, model: BaseModel, error: dict):
|
|
26
|
+
if self.success:
|
|
27
|
+
return model(**self.json)
|
|
28
|
+
return error(self.json.get('error'), self.json.get('message'), self.json.get('data'), self.json.get('status'));
|
|
29
|
+
|
|
30
|
+
def __str__(self) -> str:
|
|
31
|
+
return self.raw_body.decode("utf-8")
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
authors = ["PaymentsGate"]
|
|
3
|
+
classifiers = [
|
|
4
|
+
"Development Status :: 5 - Production/Stable",
|
|
5
|
+
"Intended Audience :: Developers",
|
|
6
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
7
|
+
]
|
|
8
|
+
description = "PaymentsGate's Python SDK for REST API"
|
|
9
|
+
documentation = "https://github.com/paymentsgate/python-secure-api"
|
|
10
|
+
homepage = "https://github.com/paymentsgate/python-secure-api"
|
|
11
|
+
keywords = ["paymentsgate", "payments", "sdk", "api"]
|
|
12
|
+
license = "MIT"
|
|
13
|
+
name = "paymentsgate"
|
|
14
|
+
packages = [{include = "paymentsgate"}]
|
|
15
|
+
readme = "README.md"
|
|
16
|
+
repository = "https://github.com/paymentsgate/python-secure-api"
|
|
17
|
+
version = "1.4.7"
|
|
18
|
+
|
|
19
|
+
[tool.poetry.dependencies]
|
|
20
|
+
pydantic = "^2.8.2"
|
|
21
|
+
python = "^3.9"
|
|
22
|
+
requests = "^2.31.0"
|
|
23
|
+
tomli = "^2.0.1"
|
|
24
|
+
jwt = "^1.3.1"
|
|
25
|
+
|
|
26
|
+
[tool.poetry.group.dev.dependencies]
|
|
27
|
+
black = ">=23.3,<25.0"
|
|
28
|
+
flake8 = "^7.1.1"
|
|
29
|
+
isort = "^5.12.0"
|
|
30
|
+
pytest = "^8.3.3"
|
|
31
|
+
pytest-coverage = "^0.0"
|
|
32
|
+
pytest-dotenv = "^0.5.2"
|
|
33
|
+
|
|
34
|
+
[tool.pytest.ini_options]
|
|
35
|
+
addopts = [
|
|
36
|
+
"--log-cli-level=INFO",
|
|
37
|
+
"--cov=paymentsgate",
|
|
38
|
+
]
|
|
39
|
+
env_files = [
|
|
40
|
+
"tests/.sandbox.env",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[tool.isort]
|
|
44
|
+
profile = "black"
|
|
45
|
+
|
|
46
|
+
[build-system]
|
|
47
|
+
build-backend = "poetry.core.masonry.api"
|
|
48
|
+
requires = ["poetry-core"]
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from setuptools import setup
|
|
3
|
+
|
|
4
|
+
packages = \
|
|
5
|
+
['paymentsgate']
|
|
6
|
+
|
|
7
|
+
package_data = \
|
|
8
|
+
{'': ['*']}
|
|
9
|
+
|
|
10
|
+
install_requires = \
|
|
11
|
+
['jwt>=1.3.1,<2.0.0',
|
|
12
|
+
'pydantic>=2.8.2,<3.0.0',
|
|
13
|
+
'requests>=2.31.0,<3.0.0',
|
|
14
|
+
'tomli>=2.0.1,<3.0.0']
|
|
15
|
+
|
|
16
|
+
setup_kwargs = {
|
|
17
|
+
'name': 'paymentsgate',
|
|
18
|
+
'version': '1.4.7',
|
|
19
|
+
'description': "PaymentsGate's Python SDK for REST API",
|
|
20
|
+
'long_description': '\n# Paymentsgate Python SDK for Payments REST API\n\n\n## Requirements\n\n- Python >= 3.8.1\n- dependencies:\n - [`requests`](https://github.com/kennethreitz/requests)\n - [`pydantic`](https://docs.pydantic.dev/latest/)\n - [`jwt`](https://pyjwt.readthedocs.io/en/stable/)\n \n## Installation\n\nThe simplest way to install SDK is to use [PIP](https://docs.python.org/3/installing/):\n\n```bash\npip install paymentsgate\n```\n\n## Basic usage\n\n```python\nfrom paymentsgate import ApiClient, Credentials, Currencies\n\n\n# minimal configuration\nconfig = Credentials().fromFile(\'/path/to/credentials.json\');\n\n# create ApiClient\nclient = ApiClient(config, baseUrl=\'https://api.example.com\');\n\n# request quote\nres = cli.Quote(\n {\n "amount": 10.10,\n "currency_from": Currencies.EUR,\n "currency_to": Currencies.AZN,\n }\n)\nprint(res);\n```\n\nThe `credentials.json` file is used to connect to the client and contains all necessary data to use the API. This file can be obtained in your personal cabinet, in the service accounts section. Follow the instructions in the documentation to issue new keys. If you already have keys, but you don\'t feel comfortable storing them in a file, you can use client initialization via variables. In this case, the key data can be stored in external storage instead of on the file system:\n\n```python\nfrom paymentsgate import ApiClient, Credentials\n\nconfig = Credentials(\n account_id="00000000-4000-4000-0000-00000000000a" \n public_key="LS0tLS1CRUdJTiBSU0EgUFJJVkFUNSUlFb3dJQk..."\n)\n\nclient = ApiClient(config, baseUrl=\'https://api.example.com\');\n\n...\n```\n*It is important to note that the data format for key transfer is base46.\n\n## Examples\n\n### create PayIn\n\n```python\nres = cli.PayIn(\n {\n "amount": 10.10,\n "currency": Currencies.AZN,\n "invoiceId": "INVOICE-112123124",\n "clientId": "",\n "successUrl": "https://example.com/success",\n "failUrl": "https://example.com/fail",\n "type": InvoiceTypes.m10\n }\n)\nprint(res);\n```\n\n### create PayOut\n\n```python\nres = cli.PayOut(\n {\n "amount": 5.12,\n "currencyTo": Currencies.EUR,\n "invoiceId": "INVOICE-112123124",\n "clientId": "CLIENT-003010023004",\n "baseCurrency": CurrencyTypes.fiat,\n "feesStrategy": FeesStrategy.add,\n "recipient": {\n "account_number": "4000000000000012",\n "account_owner": "CARD HOLDER",\n "type": CredentialsTypes.card\n }\n }\n)\nprint(res);\n```\n\n### Error handling\n\n```python\ntry:\n res = cli.PayOut(\n {\n "amount": 5.12,\n "currencyTo": Currencies.EUR,\n "invoiceId": "INVOICE-112123124",\n "clientId": "CLIENT-003010023004",\n "baseCurrency": CurrencyTypes.fiat,\n "feesStrategy": FeesStrategy.add,\n "recipient": {\n "account_number": "4000000000000012",\n "account_owner": "CARD HOLDER",\n "type": CredentialsTypes.card\n }\n }\n )\n print(res);\nexcept APIAuthenticationError as err:\n print(f"Authentication fail: {err.message}")\nexcept APIResponseError as err:\n print(f"Exception: {err.error}; Message: {err.message}")\n```',
|
|
21
|
+
'author': 'PaymentsGate',
|
|
22
|
+
'author_email': 'None',
|
|
23
|
+
'maintainer': 'None',
|
|
24
|
+
'maintainer_email': 'None',
|
|
25
|
+
'url': 'https://github.com/paymentsgate/python-secure-api',
|
|
26
|
+
'packages': packages,
|
|
27
|
+
'package_data': package_data,
|
|
28
|
+
'install_requires': install_requires,
|
|
29
|
+
'python_requires': '>=3.9,<4.0',
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
setup(**setup_kwargs)
|