paymentsgate 1.4.9__tar.gz → 1.5.0__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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: paymentsgate
3
- Version: 1.4.9
3
+ Version: 1.5.0
4
4
  Summary: PaymentsGate's Python SDK for REST API
5
5
  Home-page: https://github.com/paymentsgate/python-secure-api
6
6
  License: MIT
@@ -1,18 +1,20 @@
1
1
  from __future__ import annotations
2
2
  import logging
3
- from dataclasses import dataclass, is_dataclass, field, asdict
3
+ # from dataclasses import dataclass, is_dataclass, field, asdict
4
4
  import json
5
5
  from urllib.parse import urlencode
6
+ from pydantic import Field, BaseModel
6
7
 
7
- from paymentsgate.tokens import (
8
+ from .types import TokenResponse
9
+ from .tokens import (
8
10
  AccessToken,
9
11
  RefreshToken
10
12
  )
11
- from paymentsgate.exceptions import (
13
+ from .exceptions import (
12
14
  APIResponseError,
13
15
  APIAuthenticationError
14
16
  )
15
- from paymentsgate.models import (
17
+ from .models import (
16
18
  Credentials,
17
19
  GetQuoteModel,
18
20
  GetQuoteResponseModel,
@@ -20,36 +22,35 @@ from paymentsgate.models import (
20
22
  PayInResponseModel,
21
23
  PayOutModel,
22
24
  PayOutResponseModel,
23
- InvoiceModel
25
+ InvoiceModel,
26
+ GetQuoteTlv,
27
+ PayOutTlvRequest,
28
+ QuoteTlvResponse,
24
29
  )
25
- from paymentsgate.enums import ApiPaths
26
- from paymentsgate.transport import (
30
+ from .enums import ApiPaths
31
+ from .transport import (
27
32
  Request,
28
33
  Response
29
34
  )
30
- from paymentsgate.logger import Logger
31
- from paymentsgate.cache import (
35
+ from .logger import Logger
36
+ from .cache import (
32
37
  AbstractCache,
33
38
  DefaultCache
34
39
  )
35
40
 
36
41
  import requests
37
42
 
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
43
 
44
+ class ApiClient:
49
45
  def __init__(self, config: Credentials, baseUrl: str, debug: bool=False):
50
46
  self.config = config
51
47
  self.cache = DefaultCache()
52
48
  self.baseUrl = baseUrl
49
+
50
+ self.REQUEST_DEBUG = False
51
+ self.RESPONSE_DEBUG = False
52
+ self.timeout = 180
53
+
53
54
  if debug:
54
55
  logging.basicConfig(level=logging.DEBUG)
55
56
 
@@ -65,7 +66,7 @@ class ApiClient:
65
66
 
66
67
  # Handle response
67
68
  response = self._send_request(request)
68
- self.logger(request, response)
69
+ self.log(request, response)
69
70
  if (response.success):
70
71
  return response.cast(PayInResponseModel, APIResponseError)
71
72
  else:
@@ -84,16 +85,34 @@ class ApiClient:
84
85
 
85
86
  # Handle response
86
87
  response = self._send_request(request)
87
- self.logger(request, response)
88
+ self.log(request, response)
88
89
  if (response.success):
89
90
  return response.cast(PayOutResponseModel, APIResponseError)
90
91
  else:
91
92
  raise APIResponseError(response)
92
93
 
94
+ def PayOutTlv(self, request: PayOutTlvRequest) -> PayOutResponseModel:
95
+ request = Request(
96
+ method="post",
97
+ path=ApiPaths.invoices_payout_tlv,
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.log(request, response)
107
+ if not response.success:
108
+ raise APIResponseError(response)
109
+
110
+ return response.cast(PayOutResponseModel, APIResponseError)
111
+
93
112
  def Quote(self, params: GetQuoteModel) -> GetQuoteResponseModel:
94
113
  # Prepare request
95
114
  request = Request(
96
- method="get",
115
+ method="post",
97
116
  path=ApiPaths.fx_quote,
98
117
  content_type='application/json',
99
118
  noAuth=False,
@@ -103,12 +122,30 @@ class ApiClient:
103
122
 
104
123
  # Handle response
105
124
  response = self._send_request(request)
106
- self.logger(request, response)
125
+ self.log(request, response)
107
126
  if not response.success:
108
127
  raise APIResponseError(response)
109
128
 
110
129
  return response.cast(GetQuoteResponseModel, APIResponseError)
111
130
 
131
+ def QuoteQr(self, params: GetQuoteTlv) -> QuoteTlvResponse:
132
+ request = Request(
133
+ method="post",
134
+ path=ApiPaths.fx_quote_tlv,
135
+ content_type='application/json',
136
+ noAuth=False,
137
+ signature=False,
138
+ body=params
139
+ )
140
+
141
+ # Handle response
142
+ response = self._send_request(request)
143
+ self.log(request, response)
144
+ if not response.success:
145
+ raise APIResponseError(response)
146
+
147
+ return response.cast(QuoteTlvResponse, APIResponseError)
148
+
112
149
  def Status(self, id: str) -> InvoiceModel:
113
150
  # Prepare request
114
151
  request = Request(
@@ -121,7 +158,7 @@ class ApiClient:
121
158
 
122
159
  # Handle response
123
160
  response = self._send_request(request)
124
- self.logger(request, response)
161
+ self.log(request, response)
125
162
  if not response.success:
126
163
  raise APIResponseError(response)
127
164
 
@@ -130,23 +167,25 @@ class ApiClient:
130
167
  @property
131
168
  def token(self) -> AccessToken | None:
132
169
  # First check if valid token is cached
133
- token = self.cache.get_token('access')
134
- refresh = self.cache.get_token('refresh')
170
+ token = self.cache.get_token('AccessToken')
171
+ refresh = self.cache.get_token('RefreshToken')
172
+
135
173
  if token is not None and not token.is_expired:
136
174
  return token
137
175
  else:
138
176
  # try to refresh token
139
177
  if refresh is not None and not refresh.is_expired:
140
- refreshed = self._refresh_token()
178
+ refreshed = self._refresh_token(token, refresh)
141
179
 
142
180
  if (refreshed.success):
143
181
  access = AccessToken(
144
- response.json["access_token"]
182
+ refreshed.json_body["access_token"]
145
183
  )
146
184
  refresh = RefreshToken(
147
- response.json["refresh_token"],
148
- int(response.json["expires_in"]),
185
+ refreshed.json_body["refresh_token"],
186
+ int(refreshed.json_body["expires_in"]),
149
187
  )
188
+
150
189
  self.cache.set_token(access)
151
190
  self.cache.set_token(refresh)
152
191
 
@@ -157,12 +196,13 @@ class ApiClient:
157
196
  if response.success:
158
197
 
159
198
  access = AccessToken(
160
- response.json["access_token"]
199
+ response.json_body["access_token"]
161
200
  )
162
201
  refresh = RefreshToken(
163
- response.json["refresh_token"],
164
- int(response.json["expires_in"]),
202
+ response.json_body["refresh_token"],
203
+ int(response.json_body["expires_in"]),
165
204
  )
205
+
166
206
  self.cache.set_token(access)
167
207
  self.cache.set_token(refresh)
168
208
 
@@ -174,7 +214,8 @@ class ApiClient:
174
214
  """
175
215
  Send a specified Request to the GoPay REST API and process the response
176
216
  """
177
- body = asdict(request.body) if is_dataclass(request.body) else request.body
217
+ # body = asdict(request.body) if is_dataclass(request.body) else request.body
218
+ body = request.body
178
219
  # Add Bearer authentication to headers if needed
179
220
  headers = request.headers or {}
180
221
  if not request.noAuth:
@@ -184,35 +225,48 @@ class ApiClient:
184
225
 
185
226
  if (request.method == 'get'):
186
227
  params = urlencode(body)
187
- r = requests.request(
188
- method=request.method,
189
- url=f"{self.baseUrl}{request.path}?{params}",
190
- headers=headers,
191
- timeout=self.timeout
192
- )
228
+ try:
229
+ r = requests.request(
230
+ method=request.method,
231
+ url=f"{self.baseUrl}{request.path}?{params}",
232
+ headers=headers,
233
+ timeout=self.timeout
234
+ )
235
+ except:
236
+ print('Error')
237
+ pass
193
238
  else:
194
- r = requests.request(
195
- method=request.method,
196
- url=f"{self.baseUrl}{request.path}",
197
- headers=headers,
198
- json=body,
199
- timeout=self.timeout
200
- )
239
+ try:
240
+ r = requests.request(
241
+ method=request.method,
242
+ url=f"{self.baseUrl}{request.path}",
243
+ headers=headers,
244
+ json=body,
245
+ timeout=self.timeout
246
+ )
247
+ except KeyError:
248
+ print('Error')
249
+ pass
250
+
251
+ # if r == None:
201
252
 
202
253
  # Build Response instance, try to decode body as JSON
203
- response = Response(raw_body=r.content, json={}, status_code=r.status_code)
254
+ response = Response(raw_body=r.content, json_body={}, status_code=r.status_code)
204
255
 
205
256
  if (self.REQUEST_DEBUG):
206
257
  print(f"{request.method} => {self.baseUrl}{request.path} => {response.status_code}")
207
258
 
208
259
  try:
209
- response.json = r.json()
260
+ response.json_body = r.json()
210
261
  except json.JSONDecodeError:
211
262
  pass
212
263
 
213
- self.logger(request, response)
264
+ self.log(request, response)
214
265
  return response
215
266
 
267
+ def log(self, request: Request, response: Response):
268
+ Logger(self, request, response)
269
+
216
270
  def _get_token(self) -> Response:
217
271
  # Prepare request
218
272
  request = Request(
@@ -224,18 +278,27 @@ class ApiClient:
224
278
  )
225
279
  # Handle response
226
280
  response = self._send_request(request)
227
- self.logger(request, response)
281
+ self.log(request, response)
228
282
  return response
229
283
 
230
- def _refresh_token(self) -> Response:
284
+ def _refresh_token(self, access: AccessToken, refresh: RefreshToken) -> Response:
231
285
  # Prepare request
232
286
  request = Request(
233
287
  method="post",
234
288
  path=ApiPaths.token_refresh,
235
289
  content_type='application/json',
236
- body={"refresh_token": self.refreshToken},
290
+ noAuth=True,
291
+ headers={"Authorization": f"Bearer {access.token}" },
292
+ body={"refresh_token": refresh.token},
237
293
  )
238
294
  # Handle response
239
295
  response = self._send_request(request)
240
- self.logger(request, response)
296
+ self.log(request, response)
241
297
  return response
298
+
299
+ def loadToken(self, params: TokenResponse):
300
+ access = AccessToken(params.access_token)
301
+ refresh = RefreshToken(params.refresh_token, int(params.expires_in))
302
+ self.cache.set_token(access)
303
+ self.cache.set_token(refresh)
304
+
@@ -16,6 +16,7 @@ class ApiPaths(StrEnum):
16
16
  token_validate = "/auth/token/validate"
17
17
  invoices_payin = "/deals/payin"
18
18
  invoices_payout = "/deals/payout"
19
+ invoices_payout_tlv = "/deals/tlv"
19
20
  invoices_info = "/deals/:id"
20
21
  invoices_credentials = "/deals/:id/credentials"
21
22
  assets_list = "/wallet"
@@ -25,6 +26,7 @@ class ApiPaths(StrEnum):
25
26
  appel_list = "/support/list"
26
27
  appel_stat = "/support/statistic"
27
28
  fx_quote = "/fx/calculatenew"
29
+ fx_quote_tlv = "/fx/tlv"
28
30
 
29
31
  class Currencies(StrEnum):
30
32
  USDT = "USDT"
@@ -57,14 +59,16 @@ class Currencies(StrEnum):
57
59
  AMD = "AMD"
58
60
 
59
61
  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"
62
+ EN = "EN",
63
+ AZ = "AZ",
64
+ UZ = "UZ",
65
+ GE = "GE",
66
+ TR = "TR",
67
+ AE = "AE",
68
+ RU = "RU",
69
+ IN = "IN",
70
+ AR = "AR",
71
+ KG = "KG"
68
72
 
69
73
 
70
74
  class Statuses(StrEnum):
@@ -118,14 +122,42 @@ class InvoiceTypes(StrEnum):
118
122
  vodafonecash = "vodafonecash"
119
123
  razn = "razn"
120
124
  rtjs = "rtjs"
121
-
125
+ sberpay = "sberpay",
126
+ tpay = "tpay",
127
+ opay = "opay",
128
+ moniepoint = "moniepoint",
129
+ palmpay = "palmpay",
130
+ wave = "wave",
131
+ orangemoney = "orangemoney",
132
+ moovmoney = "moovmoney",
133
+ rtjscard = "rtjscard",
134
+ ruzs = "ruzs",
135
+ amobile = "amobile",
136
+ payid = "payid",
137
+ baridi = "baridi",
138
+ multiwidget = "multiwidget",
139
+ attijari = "attijari",
140
+ cih = "cih",
141
+ cashplus = "cashplus",
142
+ elqr = "elqr",
143
+ odengi = "odengi"
144
+
145
+ class EELQRBankALias(StrEnum):
146
+ bakai = 'bakai',
147
+ mbank = 'mbank',
148
+ optima = 'optima',
149
+ kicb = 'kicb',
150
+ odengi = 'odengi',
151
+ demir = 'demir',
152
+ megapay = 'megapay',
122
153
 
123
154
  class CredentialsTypes(StrEnum):
124
- iban = "iban"
125
- phone = "phone"
126
- card = "card"
127
- fps = "fps"
128
- account = "account"
155
+ iban = "iban",
156
+ phone = "phone",
157
+ card = "card",
158
+ fps = "fps",
159
+ qr = "qr",
160
+ account = "account",
129
161
  custom = "custom"
130
162
 
131
163
 
@@ -137,12 +169,13 @@ class RiskScoreLevels(StrEnum):
137
169
 
138
170
 
139
171
  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"
172
+ NO_MONEY = "NO_MONEY",
173
+ CREDENTIALS_INVALID = "CREDENTIALS_INVALID",
174
+ EXPIRED = "EXPIRED",
175
+ PRECHARGE_GAP_UPPER_LIMIT = "PRECHARGE_GAP_UPPER_LIMIT",
176
+ CROSS_BANK_TFF_LESS_THAN_3K = "CROSS_BANK_TFF_LESS_THAN_3K",
177
+ CROSS_BANK_UNSUPPORTED = "CROSS_BANK_UNSUPPORTED",
178
+ ACCOUNT_NUMBER_BLACKLISTED = "ACCOUNT_NUMBER_BLACKLISTED"
146
179
 
147
180
 
148
181
  class FeesStrategy(StrEnum):
@@ -151,8 +184,10 @@ class FeesStrategy(StrEnum):
151
184
 
152
185
 
153
186
  class InvoiceDirection(StrEnum):
154
- F2C = "F2C"
155
- C2F = "C2F"
187
+ F2C = "F2C",
188
+ C2F = "C2F",
189
+ FIAT_IN = "FIAT_IN",
190
+ FIAT_OUT = "FIAT_OUT"
156
191
 
157
192
 
158
193
  class TTLUnits(StrEnum):
@@ -31,8 +31,8 @@ class APIError(PaymentsgateError):
31
31
 
32
32
  class APIResponseError(APIError):
33
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)
34
+ super().__init__(response.json_body.get('error'), response.json_body.get('message'), response.json_body.get('data'), response.json_body.get('details'), response.status_code)
35
35
 
36
36
  class APIAuthenticationError(APIError):
37
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)
38
+ super().__init__(response.json_body.get('error'), response.json_body.get('message'), response.json_body.get('data'), response.json_body.get('details'), response.status_code)
@@ -0,0 +1,4 @@
1
+
2
+ # def QuoteResponseMapper(self, request: , response: Response):
3
+ # logging.debug(f"HTTP Request: {request}")
4
+ # logging.debug(f"HTTP Response: {response}")
@@ -0,0 +1,264 @@
1
+ from __future__ import annotations
2
+ import datetime
3
+ import json
4
+ from typing import Optional, List
5
+ from pydantic import BaseModel, ConfigDict, Field
6
+
7
+
8
+ from paymentsgate.enums import (
9
+ Currencies,
10
+ InvoiceTypes,
11
+ Languages,
12
+ Statuses,
13
+ TTLUnits,
14
+ CurrencyTypes,
15
+ FeesStrategy,
16
+ InvoiceDirection,
17
+ CredentialsTypes
18
+ )
19
+
20
+ class BaseRequestModel(BaseModel):
21
+ model_config = ConfigDict(extra='forbid')
22
+
23
+ class BaseResponseModel(BaseModel):
24
+ model_config = ConfigDict(extra='ignore')
25
+
26
+
27
+ class Credentials(BaseModel):
28
+ account_id: str
29
+ public_key: str
30
+ private_key: Optional[str] = Field(default=None)
31
+ merchant_id: Optional[str] = Field(default=None)
32
+ project_id: Optional[str] = Field(default=None)
33
+
34
+ @classmethod
35
+ def fromFile(cls, filename):
36
+ data = json.load(open(filename))
37
+ return cls(**data)
38
+
39
+ model_config = ConfigDict(extra='ignore')
40
+
41
+
42
+ class PayInFingerprintBrowserModel(BaseRequestModel):
43
+ acceptHeader: str
44
+ colorDepth: int
45
+ language: str
46
+ screenHeight: int
47
+ screenWidth: int
48
+ timezone: str
49
+ userAgent: str
50
+ javaEnabled: bool
51
+ windowHeight: int
52
+ windowWidth: int
53
+
54
+ class PayInFingerprintModel(BaseRequestModel):
55
+ fingerprint: str
56
+ ip: str
57
+ country: str
58
+ city: str
59
+ state: str
60
+ zip: str
61
+ browser: Optional[PayInFingerprintBrowserModel]
62
+
63
+ class PayInModel(BaseRequestModel):
64
+ amount: float # decimals: 2
65
+ currency: Currencies
66
+ country: Optional[str] # Country iso code
67
+ invoiceId: Optional[str] # idempotent key
68
+ clientId: Optional[str] # uniq client ref
69
+ type: InvoiceTypes # Invoice subtype, see documentation
70
+ bankId: Optional[str] # ID from bank list or NSPK id
71
+ trusted: Optional[bool]
72
+ successUrl: Optional[str]
73
+ failUrl: Optional[str]
74
+ backUrl: Optional[str]
75
+ clientCard: Optional[str]
76
+ clientName: Optional[str]
77
+ fingerprint: Optional[PayInFingerprintModel]
78
+ lang: Optional[Languages]
79
+ sync: Optional[bool] # sync h2h scheme, see documentation
80
+ multiWidgetOptions: Optional[PayInMultiWidgetOptions]
81
+ theme: Optional[str] # personalized widget theme
82
+
83
+ class PayInResponseModel(BaseResponseModel):
84
+ id: str
85
+ status: Statuses
86
+ type: InvoiceTypes
87
+ url: Optional[str]
88
+ deeplink: Optional[str]
89
+ m10: Optional[str]
90
+ cardholder: Optional[str]
91
+ account: Optional[str]
92
+ bankId: Optional[str]
93
+ accountSubType: Optional[str]
94
+
95
+ class PayOutRecipientModel(BaseRequestModel):
96
+ account_number: str # IBAN, Phone, Card, local bank account number, wallet number, etc'
97
+ account_owner: Optional[str] # FirstName LastName or FirstName MiddleName LastName
98
+ account_iban: Optional[str] # use only cases where iban is't primary account id
99
+ account_swift: Optional[str] # for swift transfers only
100
+ account_phone: Optional[str] # additional recipient phone number, use only cases where phone is't primary account id
101
+ account_bic: Optional[str] # recipient bank id
102
+ account_ewallet_name: Optional[str] # additional recipient wallet provider info
103
+ account_email: Optional[str] # additional recipient email, use only cases where email is't primary account id
104
+ account_bank_id: Optional[str] # recipient bankId (from API banks or RU NSPK id)
105
+ account_internal_client_number: Optional[str] # Bank internal identifier used for method banktransferphp (Philippines)
106
+ type: Optional[CredentialsTypes] # primary credential type
107
+
108
+ class PayOutModel(BaseRequestModel):
109
+ currency: Optional[Currencies] # currency from, by default = usdt
110
+ currencyTo: Optional[Currencies] # currency to, fiat only, if use quoteId - not required
111
+ amount: Optional[float] # decimals: 2, if use quoteId - not required
112
+ invoiceId: Optional[str] # idempotent key
113
+ clientId: Optional[str] # uniq client ref
114
+ ttl: Optional[int]
115
+ ttl_unit: Optional[TTLUnits]
116
+ finalAmount: Optional[float] # Optional, for pre-charge rate lock
117
+ sender_name: Optional[str] # sender personal short data
118
+ sender_personal: Optional[PayOutSenderModel]
119
+ baseCurrency: Optional[CurrencyTypes]
120
+ feesStrategy: Optional[FeesStrategy]
121
+ recipient: PayOutRecipientModel
122
+ quoteId: Optional[str]
123
+ src_amount: Optional[str] # Optional, source amount in local currency for 2phase payout
124
+ type: Optional[InvoiceTypes] # payout transaction scheme hint
125
+
126
+ class PayOutResponseModel(BaseResponseModel):
127
+ id: str
128
+ status: Statuses
129
+
130
+ class GetQuoteModel(BaseRequestModel):
131
+ currency_from: Currencies
132
+ currency_to: Currencies
133
+ amount: float
134
+ subtype: Optional[InvoiceTypes]
135
+ currency_original: Optional[Currencies]
136
+
137
+ class QuoteEntity(BaseResponseModel):
138
+ currencyFrom: Currencies
139
+ currencyTo: Currencies
140
+ pair: str
141
+ rate: float
142
+
143
+ class GetQuoteResponseModel(BaseResponseModel):
144
+ id: str
145
+ finalAmount: float
146
+ direction: InvoiceDirection
147
+ fullRate: float
148
+ fullRateReverse: float
149
+ fees: float
150
+ fees_percent: float
151
+ quotes: List[QuoteEntity]
152
+ expiredAt: Optional[datetime.datetime] = Field(default=None)
153
+
154
+ #deprecated
155
+ currency_from: Optional[CurrencyModel] = Field(default=None)
156
+ currency_to: Optional[CurrencyModel] = Field(default=None)
157
+ currency_middle: Optional[CurrencyModel] = Field(default=None)
158
+ rate1: Optional[float] = Field(default=None)
159
+ rate2: Optional[float] = Field(default=None)
160
+ rate3: Optional[float] = Field(default=None)
161
+ net_amount: Optional[float] = Field(default=None)
162
+ metadata: Optional[object] = Field(default=None)
163
+
164
+
165
+ class DepositAddressResponseModel(BaseResponseModel):
166
+ currency: Currencies
167
+ address: str
168
+ expiredAt: datetime.datetime
169
+
170
+
171
+ class CurrencyModel(BaseResponseModel):
172
+ _id: str
173
+ type: CurrencyTypes
174
+ code: Currencies
175
+ symbol: str
176
+ label: Optional[str] = Field(default=None)
177
+ decimal: int
178
+ countryCode: Optional[str] = Field(default=None)
179
+ countryName: Optional[str] = Field(default=None)
180
+
181
+ class BankModel(BaseResponseModel):
182
+ name: str
183
+ title: str
184
+ currency: Currencies
185
+ fpsId: str
186
+
187
+ class InvoiceStatusModel(BaseResponseModel):
188
+ name: Statuses
189
+ createdAt: datetime.datetime
190
+ updatedAt: datetime.datetime
191
+
192
+ class InvoiceAmountModel(BaseResponseModel):
193
+ crypto: float
194
+ fiat: float
195
+ fiat_net: float
196
+
197
+ class InvoiceMetadataModel(BaseResponseModel):
198
+ invoiceId: Optional[str]
199
+ clientId: Optional[str]
200
+ fiatAmount: Optional[float]
201
+
202
+ class InvoiceModel(BaseResponseModel):
203
+ _id: str
204
+ orderId: str
205
+ projectId: str
206
+ currencyFrom: CurrencyModel
207
+ currencyTo: CurrencyModel
208
+ direction: InvoiceDirection
209
+ amount: float
210
+ status: InvoiceStatusModel
211
+ amounts: InvoiceAmountModel
212
+ metadata: InvoiceMetadataModel
213
+ receiptUrls: List[str]
214
+ isExpired: bool
215
+ createdAt: datetime.datetime
216
+ updatedAt: datetime.datetime
217
+ expiredAt: datetime.datetime
218
+
219
+ class AssetsAccountModel(BaseResponseModel):
220
+ currency: CurrencyModel;
221
+ total: float
222
+ pending: float
223
+ available: float
224
+
225
+ class AssetsResponseModel(BaseResponseModel):
226
+ assets: List[AssetsAccountModel]
227
+
228
+ class PayInMultiWidgetOptions(BaseRequestModel):
229
+ offerAmount: Optional[bool] # show amount select from best offers
230
+ elqrBanks: Optional[str] # elqr bank list
231
+
232
+ class PayOutSenderModel(BaseRequestModel):
233
+ name: Optional[str]
234
+ birthday: Optional[str]
235
+ phone: Optional[str]
236
+ passport: Optional[str]
237
+
238
+ class PayOutTlvRequestModel(BaseRequestModel):
239
+ quoteId: str # ID from /fx/tlv response
240
+ invoiceId: Optional[str]
241
+ clientId: Optional[str]
242
+ sender_personal: Optional[PayOutSenderModel]
243
+
244
+ class GetQuoteTlv(BaseRequestModel):
245
+ data: str
246
+
247
+ class QuoteTlvResponse(BaseResponseModel):
248
+ id: str
249
+ amount: float # fiat local amount
250
+ amountCrypto: float # total crypto amount inc. fees
251
+ currencyCode: Currencies # local currency
252
+ feeInCrypto: float # total fee in crypto
253
+ feePercent: float # fee percent
254
+ qrVersion: int # qr code version, 1 - nspk, 2 - tlv encoded, 3 - tlv plain
255
+ rate: float # exchange rate
256
+ merchant: Optional[str] = Field(default=None) # merchant title
257
+ logo: Optional[str] = Field(default=None) # merchant logo
258
+
259
+ class PayOutTlvRequest(BaseRequestModel):
260
+ quoteId: str # quote.id ref
261
+ invoiceId: Optional[str] = Field(default=None)
262
+ clientId: Optional[str] = Field(default=None)
263
+ src_amount: Optional[float] = Field(default=None)
264
+ sender_personal: Optional[PayOutSenderModel] = Field(default=None)
@@ -1,8 +1,7 @@
1
1
  from dataclasses import dataclass
2
2
  from pydantic import BaseModel
3
3
 
4
- @dataclass
5
- class Request:
4
+ class Request(BaseModel):
6
5
  method: str
7
6
  path: str
8
7
  content_type: str = 'application/json'
@@ -11,11 +10,9 @@ class Request:
11
10
  noAuth: bool | None = False
12
11
  signature: bool | None = False
13
12
 
14
-
15
- @dataclass
16
- class Response:
13
+ class Response(BaseModel):
17
14
  raw_body: bytes
18
- json: dict
15
+ json_body: dict
19
16
  status_code: int
20
17
 
21
18
  @property
@@ -24,8 +21,8 @@ class Response:
24
21
 
25
22
  def cast(self, model: BaseModel, error: dict):
26
23
  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'));
24
+ return model(**self.json_body)
25
+ return error(self.json_body.get('error'), self.json_body.get('message'), self.json_body.get('data'), self.json_body.get('status'));
29
26
 
30
27
  def __str__(self) -> str:
31
28
  return self.raw_body.decode("utf-8")
@@ -0,0 +1,9 @@
1
+ from pydantic import BaseModel
2
+
3
+ from .tokens import AccessToken, RefreshToken
4
+
5
+ class TokenResponse(BaseModel):
6
+ access_token: str
7
+ refresh_token: str
8
+ expires_in: int
9
+
@@ -14,7 +14,7 @@ name = "paymentsgate"
14
14
  packages = [{include = "paymentsgate"}]
15
15
  readme = "README.md"
16
16
  repository = "https://github.com/paymentsgate/python-secure-api"
17
- version = "1.4.9"
17
+ version = "1.5.0"
18
18
 
19
19
  [tool.poetry.dependencies]
20
20
  pydantic = "^2.8.2"
@@ -15,7 +15,7 @@ install_requires = \
15
15
 
16
16
  setup_kwargs = {
17
17
  'name': 'paymentsgate',
18
- 'version': '1.4.9',
18
+ 'version': '1.5.0',
19
19
  'description': "PaymentsGate's Python SDK for REST API",
20
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
21
  'author': 'PaymentsGate',
@@ -1,233 +0,0 @@
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
- fiatAmount: Optional[float]
204
-
205
- @dataclass
206
- class InvoiceModel:
207
- _id: str
208
- orderId: str
209
- projectId: str
210
- currencyFrom: CurrencyModel
211
- currencyTo: CurrencyModel
212
- direction: InvoiceDirection
213
- amount: float
214
- status: InvoiceStatusModel
215
- amounts: InvoiceAmountModel
216
- metadata: InvoiceMetadataModel
217
- receiptUrls: List[str]
218
- isExpired: bool
219
- createdAt: datetime
220
- updatedAt: datetime
221
- expiredAt: datetime
222
-
223
- @dataclass
224
- class AssetsAccountModel:
225
- currency: CurrencyModel;
226
- total: float
227
- pending: float
228
- available: float
229
-
230
- @dataclass
231
- class AssetsResponseModel:
232
- assets: List[AssetsAccountModel]
233
-
File without changes
File without changes