paymentsgate 1.4.9__py3-none-any.whl → 1.5.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
paymentsgate/models.py CHANGED
@@ -1,233 +1,289 @@
1
1
  from __future__ import annotations
2
- from dataclasses import dataclass
3
2
  import datetime
3
+ from decimal import Decimal
4
4
  import json
5
5
  from typing import Optional, List
6
+ from pydantic import BaseModel, ConfigDict, Field
7
+
6
8
 
7
9
  from paymentsgate.enums import (
8
- Currencies,
9
- InvoiceTypes,
10
- Languages,
11
- Statuses,
12
- TTLUnits,
13
- CurrencyTypes,
14
- FeesStrategy,
15
- InvoiceDirection,
16
- CredentialsTypes
10
+ Currencies,
11
+ InvoiceTypes,
12
+ Languages,
13
+ Statuses,
14
+ TTLUnits,
15
+ CurrencyTypes,
16
+ FeesStrategy,
17
+ InvoiceDirection,
18
+ CredentialsTypes,
17
19
  )
18
20
 
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
21
 
22
+ class BaseRequestModel(BaseModel):
23
+ model_config = ConfigDict(extra="forbid")
24
+
25
+
26
+ class BaseResponseModel(BaseModel):
27
+ model_config = ConfigDict(extra="ignore")
28
+
29
+
30
+ class Credentials(BaseModel):
31
+ account_id: str
32
+ public_key: str
33
+ private_key: Optional[str] = Field(default=None)
34
+ merchant_id: Optional[str] = Field(default=None)
35
+ project_id: Optional[str] = Field(default=None)
36
+
37
+ @classmethod
38
+ def fromFile(cls, filename):
39
+ data = json.load(open(filename))
40
+ return cls(**data)
41
+
42
+ model_config = ConfigDict(extra="ignore")
43
+
44
+
45
+ class PayInFingerprintBrowserModel(BaseRequestModel):
46
+ acceptHeader: str
47
+ colorDepth: int
48
+ language: str
49
+ screenHeight: int
50
+ screenWidth: int
51
+ timezone: str
52
+ userAgent: str
53
+ javaEnabled: bool
54
+ windowHeight: int
55
+ windowWidth: int
56
+
57
+
58
+ class PayInFingerprintModel(BaseRequestModel):
59
+ fingerprint: str
60
+ ip: str
61
+ country: str
62
+ city: str
63
+ state: str
64
+ zip: str
65
+ browser: Optional[PayInFingerprintBrowserModel]
66
+
67
+
68
+ class PayInModel(BaseRequestModel):
69
+ amount: float # decimals: 2
70
+ currency: Currencies
71
+ country: Optional[str] # Country iso code
72
+ invoiceId: Optional[str] # idempotent key
73
+ clientId: Optional[str] # uniq client ref
74
+ type: InvoiceTypes # Invoice subtype, see documentation
75
+ bankId: Optional[str] # ID from bank list or NSPK id
76
+ trusted: Optional[bool]
77
+ successUrl: Optional[str]
78
+ failUrl: Optional[str]
79
+ backUrl: Optional[str]
80
+ clientCard: Optional[str]
81
+ clientName: Optional[str]
82
+ fingerprint: Optional[PayInFingerprintModel]
83
+ lang: Optional[Languages]
84
+ sync: Optional[bool] # sync h2h scheme, see documentation
85
+ multiWidgetOptions: Optional[PayInMultiWidgetOptions]
86
+ theme: Optional[str] # personalized widget theme
87
+
88
+
89
+ class PayInResponseModel(BaseResponseModel):
90
+ id: str
91
+ status: Statuses
92
+ type: InvoiceTypes
93
+ url: Optional[str]
94
+ deeplink: Optional[str]
95
+ m10: Optional[str]
96
+ cardholder: Optional[str]
97
+ account: Optional[str]
98
+ bankId: Optional[str]
99
+ accountSubType: Optional[str]
100
+
101
+
102
+ class PayOutRecipientModel(BaseRequestModel):
103
+ account_number: str | None = None # IBAN, Phone, Card, local bank account number, wallet number, etc'
104
+ account_owner: str | None = None # FirstName LastName or FirstName MiddleName LastName
105
+ account_iban: str | None = None # use only cases where iban is't primary account id
106
+ account_swift: str | None = None # for swift transfers only
107
+ account_phone: str | None = None # additional recipient phone number, use only cases where phone is't primary account id
108
+ account_bic: str | None = None # recipient bank id
109
+ account_ewallet_name: str | None = None # additional recipient wallet provider info
110
+ account_email: str | None = None # additional recipient email, use only cases where email is't primary account id
111
+ account_bank_id: str | None = None # recipient bankId (from API banks or RU NSPK id)
112
+ account_internal_client_number: str | None = None # Bank internal identifier used for method banktransferphp (Philippines)
113
+ type: CredentialsTypes | None = None # primary credential type
114
+
115
+
116
+ class PayOutModel(BaseRequestModel):
117
+ currency: Currencies | None = None # currency from, by default = usdt
118
+ currencyTo: Currencies | None = None # currency to, fiat only, if use quoteId - not required
119
+ amount: Decimal | None = None # decimals: 2, if use quoteId - not required
120
+ invoiceId: str | None = None # idempotent key
121
+ clientId: str | None = None # uniq client ref
122
+ ttl: int | None = None
123
+ ttl_unit: TTLUnits | None = None
124
+ finalAmount: Decimal | None = None # Optional, for pre-charge rate lock
125
+ sender_name: str | None = None # sender personal short data
126
+ sender_personal: PayOutSenderModel | None = None
127
+ baseCurrency: CurrencyTypes | None = None
128
+ feesStrategy: FeesStrategy | None = None
129
+ recipient: PayOutRecipientModel
130
+ quoteId: str | None = None
131
+ src_amount: str | None = None # Optional, source amount in local currency for 2phase payout
132
+ type: InvoiceTypes | None = None # payout transaction scheme hint
133
+
134
+
135
+ class PayOutResponseModel(BaseResponseModel):
136
+ id: str
137
+ status: str
138
+
139
+
140
+ class GetQuoteModel(BaseRequestModel):
141
+ currency_from: Currencies
142
+ currency_to: Currencies
143
+ amount: Decimal
144
+ subtype: InvoiceTypes | None = None
145
+ currency_original: Currencies | None = None
146
+
147
+
148
+ class QuoteEntity(BaseResponseModel):
149
+ currencyFrom: Currencies
150
+ currencyTo: Currencies
151
+ pair: str
152
+ rate: float
153
+
154
+
155
+ class GetQuoteResponseModel(BaseResponseModel):
156
+ id: str
157
+ finalAmount: Decimal
158
+ direction: InvoiceDirection
159
+ fullRate: Decimal
160
+ fullRateReverse: Decimal
161
+ fees: Decimal
162
+ fees_percent: Decimal
163
+ quotes: List[QuoteEntity]
164
+ expiredAt: Optional[datetime.datetime] | None = None
165
+
166
+ # deprecated
167
+ currency_from: Optional[CurrencyModel] = Field(default=None)
168
+ currency_to: Optional[CurrencyModel] = Field(default=None)
169
+ currency_middle: Optional[CurrencyModel] = Field(default=None)
170
+ rate1: Optional[float] = Field(default=None)
171
+ rate2: Optional[float] = Field(default=None)
172
+ rate3: Optional[float] = Field(default=None)
173
+ net_amount: Optional[float] = Field(default=None)
174
+ metadata: Optional[object] = Field(default=None)
175
+
176
+
177
+ class DepositAddressResponseModel(BaseResponseModel):
178
+ currency: Currencies
179
+ address: str
180
+ expiredAt: datetime.datetime
181
+
182
+
183
+ class CurrencyModel(BaseResponseModel):
184
+ _id: str
185
+ type: CurrencyTypes
186
+ code: Currencies
187
+ symbol: str
188
+ label: Optional[str] = Field(default=None)
189
+ decimal: int
190
+ countryCode: Optional[str] = Field(default=None)
191
+ countryName: Optional[str] = Field(default=None)
192
+
193
+
194
+ class BankModel(BaseResponseModel):
195
+ name: str
196
+ title: str
197
+ currency: Currencies
198
+ fpsId: str
199
+
200
+
201
+ class InvoiceStatusModel(BaseResponseModel):
202
+ name: Statuses
203
+ createdAt: datetime.datetime
204
+ updatedAt: datetime.datetime
205
+
206
+
207
+ class InvoiceAmountModel(BaseResponseModel):
208
+ crypto: float
209
+ fiat: float
210
+ fiat_net: float
211
+
212
+
213
+ class InvoiceMetadataModel(BaseResponseModel):
214
+ invoiceId: Optional[str]
215
+ clientId: Optional[str]
216
+ fiatAmount: Optional[float]
217
+
218
+
219
+ class InvoiceModel(BaseResponseModel):
220
+ id: str | None = Field(..., alias='_id')
221
+ orderId: str
222
+ projectId: str
223
+ currencyFrom: CurrencyModel
224
+ currencyTo: CurrencyModel
225
+ direction: InvoiceDirection
226
+ amount: float
227
+ status: InvoiceStatusModel
228
+ amounts: InvoiceAmountModel
229
+ metadata: InvoiceMetadataModel
230
+ receiptUrls: List[str]
231
+ isExpired: bool
232
+ createdAt: datetime.datetime | None = None
233
+ updatedAt: datetime.datetime | None = None
234
+ expiredAt: datetime.datetime | None = None
235
+
236
+
237
+ class AssetsAccountModel(BaseResponseModel):
238
+ currency: CurrencyModel
239
+ total: float
240
+ pending: float
241
+ available: float
242
+
243
+
244
+ class AssetsResponseModel(BaseResponseModel):
245
+ assets: List[AssetsAccountModel]
246
+
247
+
248
+ class PayInMultiWidgetOptions(BaseRequestModel):
249
+ offerAmount: Optional[bool] # show amount select from best offers
250
+ elqrBanks: Optional[str] # elqr bank list
251
+
252
+
253
+ class PayOutSenderModel(BaseRequestModel):
254
+ name: Optional[str]
255
+ birthday: Optional[str]
256
+ phone: Optional[str]
257
+ passport: Optional[str]
258
+
259
+
260
+ class PayOutTlvRequestModel(BaseRequestModel):
261
+ quoteId: str # ID from /fx/tlv response
262
+ invoiceId: Optional[str]
263
+ clientId: Optional[str]
264
+ sender_personal: Optional[PayOutSenderModel]
265
+
266
+
267
+ class GetQuoteTlv(BaseRequestModel):
268
+ data: str
269
+
270
+
271
+ class QuoteTlvResponse(BaseResponseModel):
272
+ id: str
273
+ amount: float # fiat local amount
274
+ amountCrypto: float # total crypto amount inc. fees
275
+ currencyCode: Currencies # local currency
276
+ feeInCrypto: float # total fee in crypto
277
+ feePercent: float # fee percent
278
+ qrVersion: int # qr code version, 1 - nspk, 2 - tlv encoded, 3 - tlv plain
279
+ rate: float # exchange rate
280
+ merchant: Optional[str] = Field(default=None) # merchant title
281
+ logo: Optional[str] = Field(default=None) # merchant logo
282
+
283
+
284
+ class PayOutTlvRequest(BaseRequestModel):
285
+ quoteId: str # quote.id ref
286
+ invoiceId: Optional[str] = Field(default=None)
287
+ clientId: Optional[str] = Field(default=None)
288
+ src_amount: Optional[float] = Field(default=None)
289
+ sender_personal: Optional[PayOutSenderModel] = Field(default=None)
@@ -0,0 +1,91 @@
1
+ import enum
2
+ import base64
3
+ import hashlib
4
+ from cryptography.hazmat.primitives.asymmetric import padding
5
+ from cryptography.hazmat.primitives import hashes, serialization
6
+
7
+
8
+ class SignatureCheckMode(enum.StrEnum):
9
+ none = enum.auto()
10
+ decrypt_only = enum.auto()
11
+ full = enum.auto()
12
+
13
+
14
+ def flatten_stringify(d: dict, count: int = 1) -> dict:
15
+ def inner(d, count):
16
+ res = {}
17
+ for k, v in d.items():
18
+ if type(v) is list:
19
+ count += 1
20
+ elif type(v) is dict:
21
+ new_d, count = inner(v, count)
22
+ res = res | new_d
23
+ else:
24
+ res[(k.lower(), count)] = str(v).lower() if type(v) is bool else str(v)
25
+ count += 1
26
+ return res, count
27
+
28
+ (d, _) = inner(d, count)
29
+ return d
30
+
31
+
32
+ class SignatureHelper:
33
+ def __init__(
34
+ self,
35
+ private_key_data: str,
36
+ password: str = None,
37
+ mode: SignatureCheckMode = SignatureCheckMode.full,
38
+ ) -> None:
39
+ decoded_key = base64.decodebytes(private_key_data.encode("utf-8"))
40
+ self.private_key = serialization.load_pem_private_key(
41
+ decoded_key, password=password
42
+ )
43
+ self.mode = mode
44
+
45
+ def check(self, api_signature: str, json_value: dict) -> bool:
46
+ try:
47
+ self._check_impl(api_signature, json_value)
48
+ except ValueError:
49
+ return False
50
+ except:
51
+ raise
52
+ return True
53
+
54
+ def sign(self, json_value: dict) -> str:
55
+ digest = self.encode(json_value)
56
+ return base64.encodebytes(
57
+ self.private_key.sign(
58
+ digest,
59
+ padding.PSS(
60
+ mgf=padding.MGF1(hashes.SHA256()),
61
+ salt_length=padding.PSS.MAX_LENGTH,
62
+ ),
63
+ hashes.SHA256(),
64
+ )
65
+ ).decode("utf-8")
66
+
67
+ def encode(self, json_value: dict) -> bytes:
68
+ d = flatten_stringify(json_value)
69
+ val = "".join(d[k] for k in sorted(d.keys()))
70
+ return hashlib.sha256(val.encode("utf-8")).hexdigest().encode("utf-8")
71
+
72
+ def check_raise(self, api_signatre: str, json_value: dict) -> None:
73
+ self._check_impl(api_signatre, json_value)
74
+
75
+ def _check_impl(self, api_signature: str, json_value: dict) -> None:
76
+ if self.mode == SignatureCheckMode.none:
77
+ return
78
+ if len(api_signature) < 256:
79
+ raise ValueError(
80
+ "wrong api_signature length, check Webhook version.in project settings, should be 3!"
81
+ )
82
+ decoded_signature = base64.decodebytes(api_signature.encode("utf-8"))
83
+ alg = hashes.SHA256()
84
+ digest = self.private_key.decrypt(
85
+ decoded_signature,
86
+ padding.OAEP(mgf=padding.MGF1(algorithm=alg), algorithm=alg, label=None),
87
+ )
88
+ if self.mode == SignatureCheckMode.decrypt_only:
89
+ return
90
+ if self.encode(json_value) != digest:
91
+ raise ValueError("Error while checking signature")
paymentsgate/tokens.py CHANGED
@@ -2,6 +2,7 @@ from dataclasses import dataclass
2
2
  from jwt import JWT
3
3
  import time
4
4
 
5
+
5
6
  @dataclass
6
7
  class AccessToken:
7
8
  token: str
@@ -11,16 +12,18 @@ class AccessToken:
11
12
  self.token = token
12
13
  jwdInstance = JWT()
13
14
  parsed = jwdInstance.decode(token, do_verify=False, do_time_check=False)
14
- self.expiredAt = int(parsed['exp'])
15
+ self.expiredAt = int(parsed["exp"])
16
+
15
17
  @property
16
18
  def is_expired(self):
17
19
  if self.expiredAt:
18
- return int(time.time()) >= self.expiredAt;
20
+ return int(time.time()) >= self.expiredAt
19
21
  return True
20
22
 
21
23
  def __str__(self) -> str:
22
24
  return self.token
23
25
 
26
+
24
27
  @dataclass
25
28
  class RefreshToken:
26
29
  token: str
@@ -29,12 +32,12 @@ class RefreshToken:
29
32
  def __init__(self, token, expiredAt):
30
33
  self.token = token
31
34
  self.expiredAt = expiredAt
35
+
32
36
  @property
33
37
  def is_expired(self):
34
38
  if self.expiredAt:
35
- return int(time.time()) >= self.expiredAt;
39
+ return int(time.time()) >= self.expiredAt
36
40
  return True
37
41
 
38
42
  def __str__(self) -> str:
39
43
  return self.token
40
-
paymentsgate/transport.py CHANGED
@@ -1,22 +1,20 @@
1
- from dataclasses import dataclass
2
1
  from pydantic import BaseModel
3
2
 
4
- @dataclass
5
- class Request:
3
+
4
+ class Request(BaseModel):
6
5
  method: str
7
6
  path: str
8
- content_type: str = 'application/json'
7
+ content_type: str = "application/json"
9
8
  headers: dict[str, str] | None = None
10
9
  body: dict | None = None
11
10
  noAuth: bool | None = False
12
11
  signature: bool | None = False
13
12
 
14
13
 
15
- @dataclass
16
- class Response:
14
+ class Response(BaseModel):
17
15
  raw_body: bytes
18
- json: dict
19
16
  status_code: int
17
+ json_body: dict | None = None
20
18
 
21
19
  @property
22
20
  def success(self) -> bool:
@@ -24,8 +22,13 @@ class Response:
24
22
 
25
23
  def cast(self, model: BaseModel, error: dict):
26
24
  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
-
25
+ return model(**self.json_body)
26
+ return error(
27
+ self.json_body.get("error"),
28
+ self.json_body.get("message"),
29
+ self.json_body.get("data"),
30
+ self.json_body.get("status"),
31
+ )
32
+
30
33
  def __str__(self) -> str:
31
- return self.raw_body.decode("utf-8")
34
+ return self.raw_body.decode("utf-8")
paymentsgate/types.py ADDED
@@ -0,0 +1,7 @@
1
+ from pydantic import BaseModel
2
+
3
+
4
+ class TokenResponse(BaseModel):
5
+ access_token: str
6
+ refresh_token: str
7
+ expires_in: int