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/__init__.py +14 -14
- paymentsgate/cache.py +9 -7
- paymentsgate/client.py +293 -70
- paymentsgate/enums.py +67 -31
- paymentsgate/exceptions.py +29 -12
- paymentsgate/logger.py +0 -1
- paymentsgate/mappers.py +3 -0
- paymentsgate/models.py +280 -224
- paymentsgate/signature.py +91 -0
- paymentsgate/tokens.py +7 -4
- paymentsgate/transport.py +14 -11
- paymentsgate/types.py +7 -0
- {paymentsgate-1.4.9.dist-info → paymentsgate-1.5.1.dist-info}/METADATA +5 -4
- paymentsgate-1.5.1.dist-info/RECORD +16 -0
- paymentsgate-1.4.9.dist-info/RECORD +0 -13
- {paymentsgate-1.4.9.dist-info → paymentsgate-1.5.1.dist-info}/LICENSE +0 -0
- {paymentsgate-1.4.9.dist-info → paymentsgate-1.5.1.dist-info}/WHEEL +0 -0
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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[
|
|
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
|
-
|
|
5
|
-
class Request:
|
|
3
|
+
|
|
4
|
+
class Request(BaseModel):
|
|
6
5
|
method: str
|
|
7
6
|
path: str
|
|
8
|
-
content_type: str =
|
|
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
|
-
|
|
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.
|
|
28
|
-
return error(
|
|
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
|
-
|
|
34
|
+
return self.raw_body.decode("utf-8")
|