paymentsgate 1.4.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.

@@ -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.1
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,218 @@
1
+ from __future__ import annotations
2
+ from dataclasses import dataclass, field
3
+ import json
4
+ from urllib.parse import urlencode
5
+
6
+ from paymentsgate.tokens import (
7
+ AccessToken,
8
+ RefreshToken
9
+ )
10
+ from paymentsgate.exceptions import (
11
+ APIResponseError,
12
+ APIAuthenticationError
13
+ )
14
+ from paymentsgate.models import (
15
+ Credentials,
16
+ GetQuoteModel,
17
+ GetQuoteResponseModel,
18
+ PayInModel,
19
+ PayInResponseModel,
20
+ PayOutModel,
21
+ PayOutResponseModel
22
+ )
23
+ from paymentsgate.enums import ApiPaths
24
+ from paymentsgate.transport import (
25
+ Request,
26
+ Response
27
+ )
28
+ from paymentsgate.logger import Logger
29
+ from paymentsgate.cache import (
30
+ AbstractCache,
31
+ DefaultCache
32
+ )
33
+
34
+ import requests
35
+
36
+ @dataclass
37
+ class ApiClient:
38
+ baseUrl: str = field(default="", init=False)
39
+ timeout: int = field(default=180, init=True)
40
+ logger: Logger = Logger
41
+ cache: AbstractCache = field(default_factory=DefaultCache)
42
+ config: Credentials = field(default_factory=dict, init=False)
43
+
44
+ REQUEST_DEBUG: bool = False
45
+ RESPONSE_DEBUG: bool = False
46
+
47
+ def __init__(self, config: Credentials, baseUrl: str):
48
+ self.config = config
49
+ self.cache = DefaultCache()
50
+ self.baseUrl = baseUrl;
51
+
52
+ def PayIn(self, request: PayInModel) -> PayInResponseModel:
53
+ # Prepare request
54
+ request = Request(
55
+ method="post",
56
+ path=ApiPaths.invoices_payin,
57
+ content_type='application/json',
58
+ noAuth=False,
59
+ body=request,
60
+ )
61
+
62
+ # Handle response
63
+ response = self._send_request(request)
64
+ self.logger(request, response)
65
+ if (response.success):
66
+ return response.cast(PayInResponseModel, APIResponseError)
67
+ else:
68
+ raise APIResponseError(response)
69
+
70
+ def PayOut(self, request: PayOutModel) -> PayOutResponseModel:
71
+ # Prepare request
72
+ request = Request(
73
+ method="post",
74
+ path=ApiPaths.invoices_payout,
75
+ content_type='application/json',
76
+ noAuth=False,
77
+ signature=True,
78
+ body=request
79
+ )
80
+
81
+ # Handle response
82
+ response = self._send_request(request)
83
+ self.logger(request, response)
84
+ if (response.success):
85
+ return response.cast(PayOutResponseModel, APIResponseError)
86
+ else:
87
+ raise APIResponseError(response)
88
+
89
+ def Quote(self, request: GetQuoteModel) -> GetQuoteResponseModel:
90
+ # Prepare request
91
+ request = Request(
92
+ method="get",
93
+ path=ApiPaths.fx_quote,
94
+ content_type='application/json',
95
+ noAuth=False,
96
+ signature=False,
97
+ body=request
98
+ )
99
+
100
+ # Handle response
101
+ response = self._send_request(request)
102
+ self.logger(request, response)
103
+ if (response.success):
104
+ return response.cast(GetQuoteResponseModel, APIResponseError)
105
+ else:
106
+ raise APIResponseError(response)
107
+
108
+ @property
109
+ def token(self) -> AccessToken | None:
110
+ # First check if valid token is cached
111
+ token = self.cache.get_token('access')
112
+ refresh = self.cache.get_token('refresh')
113
+ if token is not None and not token.is_expired:
114
+ return token
115
+ else:
116
+ # try to refresh token
117
+ if refresh is not None and not refresh.is_expired:
118
+ refreshed = self._refresh_token()
119
+
120
+ if (refreshed.success):
121
+ access = AccessToken(
122
+ response.json["access_token"]
123
+ )
124
+ refresh = RefreshToken(
125
+ response.json["refresh_token"],
126
+ int(response.json["expires_in"]),
127
+ )
128
+ self.cache.set_token(access)
129
+ self.cache.set_token(refresh)
130
+
131
+ return access
132
+
133
+ # try to issue token
134
+ response = self._get_token()
135
+ if response.success:
136
+
137
+ access = AccessToken(
138
+ response.json["access_token"]
139
+ )
140
+ refresh = RefreshToken(
141
+ response.json["refresh_token"],
142
+ int(response.json["expires_in"]),
143
+ )
144
+ self.cache.set_token(access)
145
+ self.cache.set_token(refresh)
146
+
147
+ return access
148
+ else:
149
+ raise APIAuthenticationError(response)
150
+
151
+ def _send_request(self, request: Request) -> Response:
152
+ """
153
+ Send a specified Request to the GoPay REST API and process the response
154
+ """
155
+
156
+ # Add Bearer authentication to headers if needed
157
+ headers = request.headers or {}
158
+ if not request.noAuth:
159
+ auth = self.token
160
+ if auth is not None:
161
+ headers["Authorization"] = f"Bearer {auth.token}"
162
+
163
+ if (request.method == 'get'):
164
+ r = requests.request(
165
+ method=request.method,
166
+ url=f"{self.baseUrl}{request.path}?{urlencode(request.body)}",
167
+ headers=headers,
168
+ timeout=self.timeout
169
+ )
170
+ else:
171
+ r = requests.request(
172
+ method=request.method,
173
+ url=f"{self.baseUrl}{request.path}",
174
+ headers=headers,
175
+ json=request.body,
176
+ timeout=self.timeout
177
+ )
178
+
179
+ # Build Response instance, try to decode body as JSON
180
+ response = Response(raw_body=r.content, json={}, status_code=r.status_code)
181
+
182
+ if (self.REQUEST_DEBUG):
183
+ print(f"{request.method} => {self.baseUrl}{request.path} => {response.status_code}")
184
+
185
+ try:
186
+ response.json = r.json()
187
+ except json.JSONDecodeError:
188
+ pass
189
+
190
+ self.logger(request, response)
191
+ return response
192
+
193
+ def _get_token(self) -> Response:
194
+ # Prepare request
195
+ request = Request(
196
+ method="post",
197
+ path=ApiPaths.token_issue,
198
+ content_type='application/json',
199
+ noAuth=True,
200
+ body={"account_id": self.config.account_id, "public_key": self.config.public_key},
201
+ )
202
+ # Handle response
203
+ response = self._send_request(request)
204
+ self.logger(request, response)
205
+ return response
206
+
207
+ def _refresh_token(self) -> Response:
208
+ # Prepare request
209
+ request = Request(
210
+ method="post",
211
+ path=ApiPaths.token_refresh,
212
+ content_type='application/json',
213
+ body={"refresh_token": self.refreshToken},
214
+ )
215
+ # Handle response
216
+ response = self._send_request(request)
217
+ self.logger(request, response)
218
+ 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/calculation"
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,8 @@
1
+ import logging
2
+ from paymentsgate.transport import Request, Response
3
+
4
+
5
+ def Logger(self, request: Request, response: Response):
6
+ logging.debug(f"HTTP Request: {request}")
7
+ logging.debug(f"HTTP Response: {response}")
8
+
@@ -0,0 +1,225 @@
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
+ )
17
+
18
+ from pydantic import BaseModel, ConfigDict
19
+
20
+ @dataclass
21
+ class Credentials:
22
+ def __init__(
23
+ self,
24
+ account_id='',
25
+ merchant_id='',
26
+ project_id='',
27
+ private_key='',
28
+ public_key=''
29
+ ):
30
+ self.account_id = account_id
31
+ self.merchant_id = merchant_id
32
+ self.project_id = project_id
33
+ self.private_key = private_key
34
+ self.public_key = public_key
35
+
36
+ def fromFile(cls, filename):
37
+ data = json.load(open(filename))
38
+ cls.account_id = data.get('account_id')
39
+ cls.merchant_id = data.get('merchant_id')
40
+ cls.project_id = data.get('project_id')
41
+ cls.private_key = data.get('private_key')
42
+ cls.public_key = data.get('public_key')
43
+ return cls
44
+
45
+
46
+ @dataclass
47
+ class PayInFingerprintBrowserModel:
48
+ acceptHeader: str
49
+ colorDepth: int
50
+ language: str
51
+ screenHeight: int
52
+ screenWidth: int
53
+ timezone: str
54
+ userAgent: str
55
+ javaEnabled: bool
56
+ windowHeight: int
57
+ windowWidth: int
58
+
59
+ @dataclass
60
+ class PayInFingerprintModel:
61
+ fingerprint: str
62
+ ip: str
63
+ country: str
64
+ city: str
65
+ state: str
66
+ zip: str
67
+ browser: Optional[PayInFingerprintBrowserModel]
68
+
69
+ @dataclass
70
+ class PayInModel:
71
+ amount: float
72
+ currency: Currencies
73
+ invoiceId: Optional[str] # idempotent key
74
+ clientId: Optional[str]
75
+ type: InvoiceTypes
76
+ bankId: Optional[str]
77
+ trusted: Optional[bool]
78
+ successUrl: Optional[str]
79
+ failUrl: Optional[str]
80
+ backUrl: Optional[str]
81
+ clientCard: Optional[str]
82
+ fingerprint: Optional[PayInFingerprintModel]
83
+ lang: Optional[Languages]
84
+
85
+ @dataclass
86
+ class PayInResponseModel:
87
+ id: str
88
+ status: Statuses
89
+ type: InvoiceTypes
90
+ url: str
91
+
92
+ @dataclass
93
+ class PayOutRecipientModel:
94
+ account_number: str
95
+ account_owner: Optional[str]
96
+ account_iban: Optional[str]
97
+ account_swift: Optional[str]
98
+ account_phone: Optional[str]
99
+ account_bic: Optional[str]
100
+ account_ewallet_name: Optional[str]
101
+ account_email: Optional[str]
102
+ account_bank_id: Optional[str]
103
+ type: Optional[CredentialsTypes]
104
+
105
+ @dataclass
106
+ class PayOutModel:
107
+ currency: Optional[Currencies] # currency from, by default = usdt
108
+ currencyTo:Currencies
109
+ amount: float
110
+ invoiceId: Optional[str] # idempotent key
111
+ clientId: Optional[str]
112
+ ttl: Optional[int]
113
+ ttl_unit: Optional[TTLUnits]
114
+ finalAmount: Optional[float]
115
+ sender_name: Optional[str]
116
+ baseCurrency: Optional[CurrencyTypes]
117
+ feesStrategy: Optional[FeesStrategy]
118
+ recipient: PayOutRecipientModel
119
+ quoteId: Optional[str]
120
+
121
+ @dataclass
122
+ class PayOutResponseModel:
123
+ id: str
124
+ status: Statuses
125
+
126
+ @dataclass
127
+ class GetQuoteModel:
128
+ currency_from: Currencies
129
+ currency_to: Currencies
130
+ amount: float
131
+
132
+ @dataclass
133
+ class QuoteEntity:
134
+ currency_from: CurrencyModel
135
+ currency_to: CurrencyModel
136
+ pair: str
137
+ rate: float
138
+
139
+ @dataclass
140
+ class GetQuoteResponseModel:
141
+ id: Optional[str] = None
142
+ finalAmount: Optional[float] = None
143
+ direction: Optional[InvoiceDirection] = None
144
+ fullRate: Optional[float] = None
145
+ fullRateReverse: Optional[float] = None
146
+ fees: Optional[float] = None
147
+ fees_percent: Optional[float] = None
148
+ quotes: Optional[List[QuoteEntity]] = None
149
+
150
+ #deprecated
151
+ currency_from: Optional[CurrencyModel] = None
152
+ currency_to: Optional[CurrencyModel] = None
153
+ currency_middle: Optional[CurrencyModel] = None
154
+ rate1: Optional[float] = None
155
+ rate2: Optional[float] = None
156
+ rate3: Optional[float] = None
157
+ net_amount: Optional[float] = None
158
+ metadata: Optional[object] = None
159
+
160
+ @dataclass
161
+ class DepositAddressResponseModel:
162
+ currency: Currencies
163
+ address: str
164
+ expiredAt: datetime
165
+
166
+ @dataclass
167
+ class CurrencyModel:
168
+ _id: str
169
+ type: CurrencyTypes
170
+ code: Currencies
171
+ symbol: str
172
+ label: Optional[str]
173
+ decimal: int
174
+ countryCode: Optional[str]
175
+ countryName: Optional[str]
176
+
177
+ @dataclass
178
+ class BankModel:
179
+ name: str
180
+ title: str
181
+ currency: Currencies
182
+ fpsId: str
183
+
184
+ @dataclass
185
+ class InvoiceStatusModel:
186
+ name: Statuses
187
+ createdAt: datetime
188
+ updatedAt: datetime
189
+
190
+ @dataclass
191
+ class InvoiceAmountModel:
192
+ crypto: float
193
+ fiat: float
194
+ fiat_net: float
195
+
196
+ @dataclass
197
+ class InvoiceMetadataModel:
198
+ invoiceId: Optional[str]
199
+ clientId: Optional[str]
200
+
201
+ @dataclass
202
+ class InvoiceModel:
203
+ orderId: str
204
+ projectId: str
205
+ currencyFrom: CurrencyModel
206
+ currencyTo: CurrencyModel
207
+ direction: InvoiceDirection
208
+ amount: float
209
+ status: InvoiceStatusModel
210
+ amounts: InvoiceAmountModel
211
+ metadata: InvoiceMetadataModel
212
+ createdAt: datetime
213
+ updatedAt: datetime
214
+ expiredAt: datetime
215
+
216
+ @dataclass
217
+ class AssetsAccountModel:
218
+ currency: CurrencyModel;
219
+ total: float
220
+ pending: float
221
+ available: float
222
+
223
+ @dataclass
224
+ class AssetsResponseModel:
225
+ 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.1"
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.1',
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)