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.

@@ -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,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,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)