blindpay 0.1.0__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.
- blindpay/__init__.py +54 -0
- blindpay/_internal/__init__.py +4 -0
- blindpay/_internal/api_client.py +54 -0
- blindpay/_internal/exceptions.py +6 -0
- blindpay/client.py +536 -0
- blindpay/resources/__init__.py +29 -0
- blindpay/resources/api_keys/__init__.py +21 -0
- blindpay/resources/api_keys/api_keys.py +82 -0
- blindpay/resources/available/__init__.py +25 -0
- blindpay/resources/available/available.py +113 -0
- blindpay/resources/bank_accounts/__init__.py +69 -0
- blindpay/resources/bank_accounts/bank_accounts.py +473 -0
- blindpay/resources/instances/__init__.py +23 -0
- blindpay/resources/instances/instances.py +82 -0
- blindpay/resources/partner_fees/__init__.py +21 -0
- blindpay/resources/partner_fees/partner_fees.py +100 -0
- blindpay/resources/payins/__init__.py +69 -0
- blindpay/resources/payins/payins.py +246 -0
- blindpay/resources/payins/quotes.py +100 -0
- blindpay/resources/payouts/__init__.py +35 -0
- blindpay/resources/payouts/payouts.py +241 -0
- blindpay/resources/quotes/__init__.py +25 -0
- blindpay/resources/quotes/quotes.py +114 -0
- blindpay/resources/receivers/__init__.py +66 -0
- blindpay/resources/receivers/receivers.py +452 -0
- blindpay/resources/virtual_accounts/__init__.py +31 -0
- blindpay/resources/virtual_accounts/virtual_accounts.py +106 -0
- blindpay/resources/wallets/__init__.py +63 -0
- blindpay/resources/wallets/blockchain.py +167 -0
- blindpay/resources/wallets/offramp.py +117 -0
- blindpay/resources/webhooks/__init__.py +27 -0
- blindpay/resources/webhooks/webhooks.py +103 -0
- blindpay/types.py +361 -0
- blindpay-0.1.0.dist-info/METADATA +94 -0
- blindpay-0.1.0.dist-info/RECORD +37 -0
- blindpay-0.1.0.dist-info/WHEEL +4 -0
- blindpay-0.1.0.dist-info/licenses/LICENSE +21 -0
blindpay/__init__.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
2
|
+
|
|
3
|
+
from ._internal.exceptions import BlindPayError
|
|
4
|
+
from .client import BlindPay, BlindPaySync
|
|
5
|
+
from .types import (
|
|
6
|
+
AccountClass,
|
|
7
|
+
BankAccountType,
|
|
8
|
+
BlindpayApiResponse,
|
|
9
|
+
BlindpayErrorResponse,
|
|
10
|
+
BlindpaySuccessResponse,
|
|
11
|
+
Country,
|
|
12
|
+
Currency,
|
|
13
|
+
CurrencyType,
|
|
14
|
+
ErrorResponse,
|
|
15
|
+
Network,
|
|
16
|
+
PaginationMetadata,
|
|
17
|
+
PaginationParams,
|
|
18
|
+
Rail,
|
|
19
|
+
StablecoinToken,
|
|
20
|
+
TrackingComplete,
|
|
21
|
+
TrackingLiquidity,
|
|
22
|
+
TrackingPartnerFee,
|
|
23
|
+
TrackingPayment,
|
|
24
|
+
TrackingTransaction,
|
|
25
|
+
TransactionDocumentType,
|
|
26
|
+
TransactionStatus,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"BlindPay",
|
|
31
|
+
"BlindPaySync",
|
|
32
|
+
"BlindPayError",
|
|
33
|
+
"AccountClass",
|
|
34
|
+
"BankAccountType",
|
|
35
|
+
"Country",
|
|
36
|
+
"Currency",
|
|
37
|
+
"CurrencyType",
|
|
38
|
+
"Network",
|
|
39
|
+
"Rail",
|
|
40
|
+
"StablecoinToken",
|
|
41
|
+
"TransactionDocumentType",
|
|
42
|
+
"TransactionStatus",
|
|
43
|
+
"BlindpayApiResponse",
|
|
44
|
+
"BlindpayErrorResponse",
|
|
45
|
+
"BlindpaySuccessResponse",
|
|
46
|
+
"ErrorResponse",
|
|
47
|
+
"PaginationMetadata",
|
|
48
|
+
"PaginationParams",
|
|
49
|
+
"TrackingComplete",
|
|
50
|
+
"TrackingLiquidity",
|
|
51
|
+
"TrackingPartnerFee",
|
|
52
|
+
"TrackingPayment",
|
|
53
|
+
"TrackingTransaction",
|
|
54
|
+
]
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from typing import Any, Mapping, Optional, Protocol
|
|
2
|
+
|
|
3
|
+
from ..types import BlindpayApiResponse
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class InternalApiClient(Protocol):
|
|
7
|
+
"""
|
|
8
|
+
Internal API client interface that resources use to make HTTP requests.
|
|
9
|
+
This interface is not exposed to SDK users.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
async def get(self, path: str) -> BlindpayApiResponse[Any]:
|
|
13
|
+
"""Make a GET request"""
|
|
14
|
+
...
|
|
15
|
+
|
|
16
|
+
async def post(self, path: str, body: Mapping[str, Any]) -> BlindpayApiResponse[Any]:
|
|
17
|
+
"""Make a POST request"""
|
|
18
|
+
...
|
|
19
|
+
|
|
20
|
+
async def put(self, path: str, body: Mapping[str, Any]) -> BlindpayApiResponse[Any]:
|
|
21
|
+
"""Make a PUT request"""
|
|
22
|
+
...
|
|
23
|
+
|
|
24
|
+
async def patch(self, path: str, body: Mapping[str, Any]) -> BlindpayApiResponse[Any]:
|
|
25
|
+
"""Make a PATCH request"""
|
|
26
|
+
...
|
|
27
|
+
|
|
28
|
+
async def delete(self, path: str, body: Optional[Mapping[str, Any]] = None) -> BlindpayApiResponse[Any]:
|
|
29
|
+
"""Make a DELETE request"""
|
|
30
|
+
...
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class InternalApiClientSync(Protocol):
|
|
34
|
+
"""Synchronous version of InternalApiClient"""
|
|
35
|
+
|
|
36
|
+
def get(self, path: str) -> BlindpayApiResponse[Any]:
|
|
37
|
+
"""Make a GET request"""
|
|
38
|
+
...
|
|
39
|
+
|
|
40
|
+
def post(self, path: str, body: Mapping[str, Any]) -> BlindpayApiResponse[Any]:
|
|
41
|
+
"""Make a POST request"""
|
|
42
|
+
...
|
|
43
|
+
|
|
44
|
+
def put(self, path: str, body: Mapping[str, Any]) -> BlindpayApiResponse[Any]:
|
|
45
|
+
"""Make a PUT request"""
|
|
46
|
+
...
|
|
47
|
+
|
|
48
|
+
def patch(self, path: str, body: Mapping[str, Any]) -> BlindpayApiResponse[Any]:
|
|
49
|
+
"""Make a PATCH request"""
|
|
50
|
+
...
|
|
51
|
+
|
|
52
|
+
def delete(self, path: str, body: Optional[Mapping[str, Any]] = None) -> BlindpayApiResponse[Any]:
|
|
53
|
+
"""Make a DELETE request"""
|
|
54
|
+
...
|
blindpay/client.py
ADDED
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import hashlib
|
|
3
|
+
import hmac
|
|
4
|
+
from functools import cached_property
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Dict, Literal, Mapping, Optional, TypeVar
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
from ._internal.exceptions import BlindPayError
|
|
10
|
+
from .types import BlindpayApiResponse
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from blindpay.resources.api_keys.api_keys import ApiKeysResource, ApiKeysResourceSync
|
|
14
|
+
from blindpay.resources.available.available import AvailableResource, AvailableResourceSync
|
|
15
|
+
from blindpay.resources.bank_accounts.bank_accounts import BankAccountsResource, BankAccountsResourceSync
|
|
16
|
+
from blindpay.resources.instances.instances import InstancesResource, InstancesResourceSync
|
|
17
|
+
from blindpay.resources.partner_fees.partner_fees import PartnerFeesResource, PartnerFeesResourceSync
|
|
18
|
+
from blindpay.resources.payins.payins import PayinsResource, PayinsResourceSync
|
|
19
|
+
from blindpay.resources.payins.quotes import PayinQuotesResource, PayinQuotesResourceSync
|
|
20
|
+
from blindpay.resources.payouts.payouts import PayoutsResource, PayoutsResourceSync
|
|
21
|
+
from blindpay.resources.quotes.quotes import QuotesResource, QuotesResourceSync
|
|
22
|
+
from blindpay.resources.receivers.receivers import ReceiversResource, ReceiversResourceSync
|
|
23
|
+
from blindpay.resources.virtual_accounts.virtual_accounts import (
|
|
24
|
+
VirtualAccountsResource,
|
|
25
|
+
VirtualAccountsResourceSync,
|
|
26
|
+
)
|
|
27
|
+
from blindpay.resources.wallets.blockchain import BlockchainWalletsResource, BlockchainWalletsResourceSync
|
|
28
|
+
from blindpay.resources.wallets.offramp import OfframpWalletsResource, OfframpWalletsResourceSync
|
|
29
|
+
from blindpay.resources.webhooks.webhooks import WebhookEndpointsResource, WebhookEndpointsResourceSync
|
|
30
|
+
|
|
31
|
+
__version__ = "1.0.0"
|
|
32
|
+
|
|
33
|
+
T = TypeVar("T")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ApiClientImpl:
|
|
37
|
+
def __init__(self, base_url: str, headers: Dict[str, str]):
|
|
38
|
+
self.base_url = base_url
|
|
39
|
+
self.headers = headers
|
|
40
|
+
|
|
41
|
+
async def get(self, path: str) -> BlindpayApiResponse[T]:
|
|
42
|
+
return await self._request("GET", path)
|
|
43
|
+
|
|
44
|
+
async def post(self, path: str, body: Mapping[str, Any]) -> BlindpayApiResponse[T]:
|
|
45
|
+
return await self._request("POST", path, body)
|
|
46
|
+
|
|
47
|
+
async def put(self, path: str, body: Mapping[str, Any]) -> BlindpayApiResponse[T]:
|
|
48
|
+
return await self._request("PUT", path, body)
|
|
49
|
+
|
|
50
|
+
async def patch(self, path: str, body: Mapping[str, Any]) -> BlindpayApiResponse[T]:
|
|
51
|
+
return await self._request("PATCH", path, body)
|
|
52
|
+
|
|
53
|
+
async def delete(self, path: str, body: Optional[Mapping[str, Any]] = None) -> BlindpayApiResponse[T]:
|
|
54
|
+
return await self._request("DELETE", path, body)
|
|
55
|
+
|
|
56
|
+
async def _request(
|
|
57
|
+
self,
|
|
58
|
+
method: Literal["GET", "POST", "PUT", "DELETE", "PATCH"],
|
|
59
|
+
path: str,
|
|
60
|
+
body: Optional[Mapping[str, Any]] = None,
|
|
61
|
+
) -> BlindpayApiResponse[T]:
|
|
62
|
+
url = f"{self.base_url}{path}"
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
async with httpx.AsyncClient() as client:
|
|
66
|
+
if body is not None:
|
|
67
|
+
response = await client.request(method=method, url=url, headers=self.headers, json=body)
|
|
68
|
+
else:
|
|
69
|
+
response = await client.request(method=method, url=url, headers=self.headers)
|
|
70
|
+
|
|
71
|
+
if response.status_code >= 400:
|
|
72
|
+
try:
|
|
73
|
+
error_data = response.json()
|
|
74
|
+
error_message = error_data.get("message", "Unknown error")
|
|
75
|
+
except Exception:
|
|
76
|
+
error_message = "Unknown error"
|
|
77
|
+
|
|
78
|
+
return {"data": None, "error": {"message": error_message}}
|
|
79
|
+
|
|
80
|
+
data = response.json()
|
|
81
|
+
return {"data": data, "error": None}
|
|
82
|
+
|
|
83
|
+
except Exception as e:
|
|
84
|
+
error_message = str(e) if str(e) else "Unknown error"
|
|
85
|
+
return {"data": None, "error": {"message": error_message}}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class ApiClientImplSync:
|
|
89
|
+
def __init__(self, base_url: str, headers: Dict[str, str]):
|
|
90
|
+
self.base_url = base_url
|
|
91
|
+
self.headers = headers
|
|
92
|
+
self.client = httpx.Client(base_url=base_url, headers=headers)
|
|
93
|
+
|
|
94
|
+
def get(self, path: str) -> BlindpayApiResponse[T]:
|
|
95
|
+
return self._request("GET", path)
|
|
96
|
+
|
|
97
|
+
def post(self, path: str, body: Mapping[str, Any]) -> BlindpayApiResponse[T]:
|
|
98
|
+
return self._request("POST", path, body)
|
|
99
|
+
|
|
100
|
+
def put(self, path: str, body: Mapping[str, Any]) -> BlindpayApiResponse[T]:
|
|
101
|
+
return self._request("PUT", path, body)
|
|
102
|
+
|
|
103
|
+
def patch(self, path: str, body: Mapping[str, Any]) -> BlindpayApiResponse[T]:
|
|
104
|
+
return self._request("PATCH", path, body)
|
|
105
|
+
|
|
106
|
+
def delete(self, path: str, body: Optional[Mapping[str, Any]] = None) -> BlindpayApiResponse[T]:
|
|
107
|
+
return self._request("DELETE", path, body)
|
|
108
|
+
|
|
109
|
+
def _request(
|
|
110
|
+
self,
|
|
111
|
+
method: Literal["GET", "POST", "PUT", "DELETE", "PATCH"],
|
|
112
|
+
path: str,
|
|
113
|
+
body: Optional[Mapping[str, Any]] = None,
|
|
114
|
+
) -> BlindpayApiResponse[T]:
|
|
115
|
+
try:
|
|
116
|
+
if body is not None:
|
|
117
|
+
response = self.client.request(method=method, url=path, json=body)
|
|
118
|
+
else:
|
|
119
|
+
response = self.client.request(method=method, url=path)
|
|
120
|
+
|
|
121
|
+
if response.status_code >= 400:
|
|
122
|
+
try:
|
|
123
|
+
error_data = response.json()
|
|
124
|
+
error_message = error_data.get("message", "Unknown error")
|
|
125
|
+
except Exception:
|
|
126
|
+
error_message = "Unknown error"
|
|
127
|
+
|
|
128
|
+
return {"data": None, "error": {"message": error_message}}
|
|
129
|
+
|
|
130
|
+
data = response.json()
|
|
131
|
+
return {"data": data, "error": None}
|
|
132
|
+
|
|
133
|
+
except Exception as e:
|
|
134
|
+
error_message = str(e) if str(e) else "Unknown error"
|
|
135
|
+
return {"data": None, "error": {"message": error_message}}
|
|
136
|
+
|
|
137
|
+
def close(self) -> None:
|
|
138
|
+
self.client.close()
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class _InstancesNamespace:
|
|
142
|
+
def __init__(self, instance_id: str, api_client: ApiClientImpl) -> None:
|
|
143
|
+
self._instance_id = instance_id
|
|
144
|
+
self._api = api_client
|
|
145
|
+
|
|
146
|
+
@cached_property
|
|
147
|
+
def _base(self) -> "InstancesResource":
|
|
148
|
+
from blindpay.resources.instances.instances import create_instances_resource
|
|
149
|
+
|
|
150
|
+
return create_instances_resource(self._instance_id, self._api)
|
|
151
|
+
|
|
152
|
+
@cached_property
|
|
153
|
+
def api_keys(self) -> "ApiKeysResource":
|
|
154
|
+
from blindpay.resources.api_keys.api_keys import create_api_keys_resource
|
|
155
|
+
|
|
156
|
+
return create_api_keys_resource(self._instance_id, self._api)
|
|
157
|
+
|
|
158
|
+
@cached_property
|
|
159
|
+
def webhook_endpoints(self) -> "WebhookEndpointsResource":
|
|
160
|
+
from blindpay.resources.webhooks.webhooks import create_webhook_endpoints_resource
|
|
161
|
+
|
|
162
|
+
return create_webhook_endpoints_resource(self._instance_id, self._api)
|
|
163
|
+
|
|
164
|
+
def __getattr__(self, name: str) -> Any:
|
|
165
|
+
return getattr(self._base, name)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class _PayinsNamespace:
|
|
169
|
+
def __init__(self, instance_id: str, api_client: ApiClientImpl) -> None:
|
|
170
|
+
self._instance_id = instance_id
|
|
171
|
+
self._api = api_client
|
|
172
|
+
|
|
173
|
+
@cached_property
|
|
174
|
+
def _base(self) -> "PayinsResource":
|
|
175
|
+
from blindpay.resources.payins.payins import create_payins_resource
|
|
176
|
+
|
|
177
|
+
return create_payins_resource(self._instance_id, self._api)
|
|
178
|
+
|
|
179
|
+
@cached_property
|
|
180
|
+
def quotes(self) -> "PayinQuotesResource":
|
|
181
|
+
from blindpay.resources.payins.quotes import create_payin_quotes_resource
|
|
182
|
+
|
|
183
|
+
return create_payin_quotes_resource(self._instance_id, self._api)
|
|
184
|
+
|
|
185
|
+
def __getattr__(self, name: str) -> Any:
|
|
186
|
+
return getattr(self._base, name)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
class _ReceiversNamespace:
|
|
190
|
+
def __init__(self, instance_id: str, api_client: ApiClientImpl) -> None:
|
|
191
|
+
self._instance_id = instance_id
|
|
192
|
+
self._api = api_client
|
|
193
|
+
|
|
194
|
+
@cached_property
|
|
195
|
+
def _base(self) -> "ReceiversResource":
|
|
196
|
+
from blindpay.resources.receivers.receivers import create_receivers_resource
|
|
197
|
+
|
|
198
|
+
return create_receivers_resource(self._instance_id, self._api)
|
|
199
|
+
|
|
200
|
+
@cached_property
|
|
201
|
+
def bank_accounts(self) -> "BankAccountsResource":
|
|
202
|
+
from blindpay.resources.bank_accounts.bank_accounts import create_bank_accounts_resource
|
|
203
|
+
|
|
204
|
+
return create_bank_accounts_resource(self._instance_id, self._api)
|
|
205
|
+
|
|
206
|
+
def __getattr__(self, name: str) -> Any:
|
|
207
|
+
return getattr(self._base, name)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class _WalletsNamespace:
|
|
211
|
+
def __init__(self, instance_id: str, api_client: ApiClientImpl) -> None:
|
|
212
|
+
self._instance_id = instance_id
|
|
213
|
+
self._api = api_client
|
|
214
|
+
|
|
215
|
+
@cached_property
|
|
216
|
+
def blockchain(self) -> "BlockchainWalletsResource":
|
|
217
|
+
from blindpay.resources.wallets.blockchain import create_blockchain_wallets_resource
|
|
218
|
+
|
|
219
|
+
return create_blockchain_wallets_resource(self._instance_id, self._api)
|
|
220
|
+
|
|
221
|
+
@cached_property
|
|
222
|
+
def offramp(self) -> "OfframpWalletsResource":
|
|
223
|
+
from blindpay.resources.wallets.offramp import create_offramp_wallets_resource
|
|
224
|
+
|
|
225
|
+
return create_offramp_wallets_resource(self._instance_id, self._api)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
class BlindPay:
|
|
229
|
+
_api_key: str
|
|
230
|
+
_instance_id: str
|
|
231
|
+
_base_url: str
|
|
232
|
+
|
|
233
|
+
def __init__(self, *, api_key: str, instance_id: str):
|
|
234
|
+
if not api_key:
|
|
235
|
+
raise BlindPayError("Api key not provided, get your api key on blindpay dashboard")
|
|
236
|
+
|
|
237
|
+
if not instance_id:
|
|
238
|
+
raise BlindPayError("Instance id not provided, get your instance id on blindpay dashboard")
|
|
239
|
+
|
|
240
|
+
self._api_key = api_key
|
|
241
|
+
self._instance_id = instance_id
|
|
242
|
+
self._base_url = "https://api.blindpay.com/v1"
|
|
243
|
+
|
|
244
|
+
self._headers = {
|
|
245
|
+
"Content-Type": "application/json",
|
|
246
|
+
"Accept": "application/json",
|
|
247
|
+
"User-Agent": f"blindpay-python/{__version__}",
|
|
248
|
+
"Authorization": f"Bearer {self._api_key}",
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
self._api = ApiClientImpl(self._base_url, self._headers)
|
|
252
|
+
|
|
253
|
+
@cached_property
|
|
254
|
+
def available(self) -> "AvailableResource":
|
|
255
|
+
from blindpay.resources.available import create_available_resource
|
|
256
|
+
|
|
257
|
+
return create_available_resource(self._api)
|
|
258
|
+
|
|
259
|
+
@cached_property
|
|
260
|
+
def instances(self) -> _InstancesNamespace:
|
|
261
|
+
return _InstancesNamespace(self._instance_id, self._api)
|
|
262
|
+
|
|
263
|
+
@cached_property
|
|
264
|
+
def partner_fees(self) -> "PartnerFeesResource":
|
|
265
|
+
from blindpay.resources.partner_fees import create_partner_fees_resource
|
|
266
|
+
|
|
267
|
+
return create_partner_fees_resource(self._instance_id, self._api)
|
|
268
|
+
|
|
269
|
+
@cached_property
|
|
270
|
+
def payins(self) -> _PayinsNamespace:
|
|
271
|
+
return _PayinsNamespace(self._instance_id, self._api)
|
|
272
|
+
|
|
273
|
+
@cached_property
|
|
274
|
+
def quotes(self) -> "QuotesResource":
|
|
275
|
+
from blindpay.resources.quotes import create_quotes_resource
|
|
276
|
+
|
|
277
|
+
return create_quotes_resource(self._instance_id, self._api)
|
|
278
|
+
|
|
279
|
+
@cached_property
|
|
280
|
+
def payouts(self) -> "PayoutsResource":
|
|
281
|
+
from blindpay.resources.payouts import create_payouts_resource
|
|
282
|
+
|
|
283
|
+
return create_payouts_resource(self._instance_id, self._api)
|
|
284
|
+
|
|
285
|
+
@cached_property
|
|
286
|
+
def receivers(self) -> _ReceiversNamespace:
|
|
287
|
+
return _ReceiversNamespace(self._instance_id, self._api)
|
|
288
|
+
|
|
289
|
+
@cached_property
|
|
290
|
+
def virtual_accounts(self) -> "VirtualAccountsResource":
|
|
291
|
+
from blindpay.resources.virtual_accounts import create_virtual_accounts_resource
|
|
292
|
+
|
|
293
|
+
return create_virtual_accounts_resource(self._instance_id, self._api)
|
|
294
|
+
|
|
295
|
+
@cached_property
|
|
296
|
+
def wallets(self) -> _WalletsNamespace:
|
|
297
|
+
return _WalletsNamespace(self._instance_id, self._api)
|
|
298
|
+
|
|
299
|
+
def verify_webhook_signature(
|
|
300
|
+
self, *, secret: str, id: str, timestamp: str, payload: str, svix_signature: str
|
|
301
|
+
) -> bool:
|
|
302
|
+
"""
|
|
303
|
+
Verifies the BlindPay webhook signature
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
secret: The webhook secret from BlindPay dashboard
|
|
307
|
+
id: The value of the `svix-id` header
|
|
308
|
+
timestamp: The value of the `svix-timestamp` header
|
|
309
|
+
payload: The raw request body
|
|
310
|
+
svix_signature: The value of the `svix-signature` header
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
True if the signature is valid, False otherwise
|
|
314
|
+
"""
|
|
315
|
+
signed_content = f"{id}.{timestamp}.{payload}"
|
|
316
|
+
|
|
317
|
+
secret_parts = secret.split("_")
|
|
318
|
+
if len(secret_parts) < 2:
|
|
319
|
+
return False
|
|
320
|
+
|
|
321
|
+
try:
|
|
322
|
+
secret_bytes = base64.b64decode(secret_parts[1])
|
|
323
|
+
except Exception:
|
|
324
|
+
return False
|
|
325
|
+
|
|
326
|
+
expected_signature = base64.b64encode(
|
|
327
|
+
hmac.new(secret_bytes, signed_content.encode(), hashlib.sha256).digest()
|
|
328
|
+
).decode()
|
|
329
|
+
|
|
330
|
+
return len(svix_signature) == len(expected_signature) and hmac.compare_digest(
|
|
331
|
+
svix_signature, expected_signature
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
class _InstancesNamespaceSync:
|
|
336
|
+
def __init__(self, instance_id: str, api_client: ApiClientImplSync) -> None:
|
|
337
|
+
self._instance_id = instance_id
|
|
338
|
+
self._api = api_client
|
|
339
|
+
|
|
340
|
+
@cached_property
|
|
341
|
+
def _base(self) -> "InstancesResourceSync":
|
|
342
|
+
from blindpay.resources.instances.instances import create_instances_resource_sync
|
|
343
|
+
|
|
344
|
+
return create_instances_resource_sync(self._instance_id, self._api)
|
|
345
|
+
|
|
346
|
+
@cached_property
|
|
347
|
+
def api_keys(self) -> "ApiKeysResourceSync":
|
|
348
|
+
from blindpay.resources.api_keys.api_keys import create_api_keys_resource_sync
|
|
349
|
+
|
|
350
|
+
return create_api_keys_resource_sync(self._instance_id, self._api)
|
|
351
|
+
|
|
352
|
+
@cached_property
|
|
353
|
+
def webhook_endpoints(self) -> "WebhookEndpointsResourceSync":
|
|
354
|
+
from blindpay.resources.webhooks.webhooks import create_webhook_endpoints_resource_sync
|
|
355
|
+
|
|
356
|
+
return create_webhook_endpoints_resource_sync(self._instance_id, self._api)
|
|
357
|
+
|
|
358
|
+
def __getattr__(self, name: str) -> Any:
|
|
359
|
+
return getattr(self._base, name)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
class _PayinsNamespaceSync:
|
|
363
|
+
def __init__(self, instance_id: str, api_client: ApiClientImplSync) -> None:
|
|
364
|
+
self._instance_id = instance_id
|
|
365
|
+
self._api = api_client
|
|
366
|
+
|
|
367
|
+
@cached_property
|
|
368
|
+
def _base(self) -> "PayinsResourceSync":
|
|
369
|
+
from blindpay.resources.payins.payins import create_payins_resource_sync
|
|
370
|
+
|
|
371
|
+
return create_payins_resource_sync(self._instance_id, self._api)
|
|
372
|
+
|
|
373
|
+
@cached_property
|
|
374
|
+
def quotes(self) -> "PayinQuotesResourceSync":
|
|
375
|
+
from blindpay.resources.payins.quotes import create_payin_quotes_resource_sync
|
|
376
|
+
|
|
377
|
+
return create_payin_quotes_resource_sync(self._instance_id, self._api)
|
|
378
|
+
|
|
379
|
+
def __getattr__(self, name: str) -> Any:
|
|
380
|
+
return getattr(self._base, name)
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
class _ReceiversNamespaceSync:
|
|
384
|
+
def __init__(self, instance_id: str, api_client: ApiClientImplSync) -> None:
|
|
385
|
+
self._instance_id = instance_id
|
|
386
|
+
self._api = api_client
|
|
387
|
+
|
|
388
|
+
@cached_property
|
|
389
|
+
def _base(self) -> "ReceiversResourceSync":
|
|
390
|
+
from blindpay.resources.receivers.receivers import create_receivers_resource_sync
|
|
391
|
+
|
|
392
|
+
return create_receivers_resource_sync(self._instance_id, self._api)
|
|
393
|
+
|
|
394
|
+
@cached_property
|
|
395
|
+
def bank_accounts(self) -> "BankAccountsResourceSync":
|
|
396
|
+
from blindpay.resources.bank_accounts.bank_accounts import create_bank_accounts_resource_sync
|
|
397
|
+
|
|
398
|
+
return create_bank_accounts_resource_sync(self._instance_id, self._api)
|
|
399
|
+
|
|
400
|
+
def __getattr__(self, name: str) -> Any:
|
|
401
|
+
return getattr(self._base, name)
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
class _WalletsNamespaceSync:
|
|
405
|
+
def __init__(self, instance_id: str, api_client: ApiClientImplSync) -> None:
|
|
406
|
+
self._instance_id = instance_id
|
|
407
|
+
self._api = api_client
|
|
408
|
+
|
|
409
|
+
@cached_property
|
|
410
|
+
def blockchain(self) -> "BlockchainWalletsResourceSync":
|
|
411
|
+
from blindpay.resources.wallets.blockchain import create_blockchain_wallets_resource_sync
|
|
412
|
+
|
|
413
|
+
return create_blockchain_wallets_resource_sync(self._instance_id, self._api)
|
|
414
|
+
|
|
415
|
+
@cached_property
|
|
416
|
+
def offramp(self) -> "OfframpWalletsResourceSync":
|
|
417
|
+
from blindpay.resources.wallets.offramp import create_offramp_wallets_resource_sync
|
|
418
|
+
|
|
419
|
+
return create_offramp_wallets_resource_sync(self._instance_id, self._api)
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
class BlindPaySync:
|
|
423
|
+
_api_key: str
|
|
424
|
+
_instance_id: str
|
|
425
|
+
_base_url: str
|
|
426
|
+
|
|
427
|
+
def __init__(self, *, api_key: str, instance_id: str):
|
|
428
|
+
if not api_key:
|
|
429
|
+
raise BlindPayError("Api key not provided, get your api key on blindpay dashboard")
|
|
430
|
+
|
|
431
|
+
if not instance_id:
|
|
432
|
+
raise BlindPayError("Instance id not provided, get your instance id on blindpay dashboard")
|
|
433
|
+
|
|
434
|
+
self._api_key = api_key
|
|
435
|
+
self._instance_id = instance_id
|
|
436
|
+
self._base_url = "https://api.blindpay.com/v1"
|
|
437
|
+
|
|
438
|
+
self._headers = {
|
|
439
|
+
"Content-Type": "application/json",
|
|
440
|
+
"Accept": "application/json",
|
|
441
|
+
"User-Agent": f"blindpay-python/{__version__}",
|
|
442
|
+
"Authorization": f"Bearer {self._api_key}",
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
self._api = ApiClientImplSync(self._base_url, self._headers)
|
|
446
|
+
|
|
447
|
+
@cached_property
|
|
448
|
+
def available(self) -> "AvailableResourceSync":
|
|
449
|
+
from blindpay.resources.available import create_available_resource_sync
|
|
450
|
+
|
|
451
|
+
return create_available_resource_sync(self._api)
|
|
452
|
+
|
|
453
|
+
@cached_property
|
|
454
|
+
def instances(self) -> _InstancesNamespaceSync:
|
|
455
|
+
return _InstancesNamespaceSync(self._instance_id, self._api)
|
|
456
|
+
|
|
457
|
+
@cached_property
|
|
458
|
+
def partner_fees(self) -> "PartnerFeesResourceSync":
|
|
459
|
+
from blindpay.resources.partner_fees import create_partner_fees_resource_sync
|
|
460
|
+
|
|
461
|
+
return create_partner_fees_resource_sync(self._instance_id, self._api)
|
|
462
|
+
|
|
463
|
+
@cached_property
|
|
464
|
+
def payins(self) -> _PayinsNamespaceSync:
|
|
465
|
+
return _PayinsNamespaceSync(self._instance_id, self._api)
|
|
466
|
+
|
|
467
|
+
@cached_property
|
|
468
|
+
def quotes(self) -> "QuotesResourceSync":
|
|
469
|
+
from blindpay.resources.quotes import create_quotes_resource_sync
|
|
470
|
+
|
|
471
|
+
return create_quotes_resource_sync(self._instance_id, self._api)
|
|
472
|
+
|
|
473
|
+
@cached_property
|
|
474
|
+
def payouts(self) -> "PayoutsResourceSync":
|
|
475
|
+
from blindpay.resources.payouts import create_payouts_resource_sync
|
|
476
|
+
|
|
477
|
+
return create_payouts_resource_sync(self._instance_id, self._api)
|
|
478
|
+
|
|
479
|
+
@cached_property
|
|
480
|
+
def receivers(self) -> _ReceiversNamespaceSync:
|
|
481
|
+
return _ReceiversNamespaceSync(self._instance_id, self._api)
|
|
482
|
+
|
|
483
|
+
@cached_property
|
|
484
|
+
def virtual_accounts(self) -> "VirtualAccountsResourceSync":
|
|
485
|
+
from blindpay.resources.virtual_accounts import create_virtual_accounts_resource_sync
|
|
486
|
+
|
|
487
|
+
return create_virtual_accounts_resource_sync(self._instance_id, self._api)
|
|
488
|
+
|
|
489
|
+
@cached_property
|
|
490
|
+
def wallets(self) -> _WalletsNamespaceSync:
|
|
491
|
+
return _WalletsNamespaceSync(self._instance_id, self._api)
|
|
492
|
+
|
|
493
|
+
def verify_webhook_signature(
|
|
494
|
+
self, *, secret: str, id: str, timestamp: str, payload: str, svix_signature: str
|
|
495
|
+
) -> bool:
|
|
496
|
+
"""
|
|
497
|
+
Verifies the BlindPay webhook signature
|
|
498
|
+
|
|
499
|
+
Args:
|
|
500
|
+
secret: The webhook secret from BlindPay dashboard
|
|
501
|
+
id: The value of the `svix-id` header
|
|
502
|
+
timestamp: The value of the `svix-timestamp` header
|
|
503
|
+
payload: The raw request body
|
|
504
|
+
svix_signature: The value of the `svix-signature` header
|
|
505
|
+
|
|
506
|
+
Returns:
|
|
507
|
+
True if the signature is valid, False otherwise
|
|
508
|
+
"""
|
|
509
|
+
signed_content = f"{id}.{timestamp}.{payload}"
|
|
510
|
+
|
|
511
|
+
secret_parts = secret.split("_")
|
|
512
|
+
if len(secret_parts) < 2:
|
|
513
|
+
return False
|
|
514
|
+
|
|
515
|
+
try:
|
|
516
|
+
secret_bytes = base64.b64decode(secret_parts[1])
|
|
517
|
+
except Exception:
|
|
518
|
+
return False
|
|
519
|
+
|
|
520
|
+
expected_signature = base64.b64encode(
|
|
521
|
+
hmac.new(secret_bytes, signed_content.encode(), hashlib.sha256).digest()
|
|
522
|
+
).decode()
|
|
523
|
+
|
|
524
|
+
return len(svix_signature) == len(expected_signature) and hmac.compare_digest(
|
|
525
|
+
svix_signature, expected_signature
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
def close(self) -> None:
|
|
529
|
+
if hasattr(self, "_api"):
|
|
530
|
+
self._api.close()
|
|
531
|
+
|
|
532
|
+
def __enter__(self) -> "BlindPaySync":
|
|
533
|
+
return self
|
|
534
|
+
|
|
535
|
+
def __exit__(self, exc_type: Optional[type], exc_val: Optional[BaseException], exc_tb: Optional[Any]) -> None:
|
|
536
|
+
self.close()
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from .api_keys import create_api_keys_resource
|
|
2
|
+
from .available import create_available_resource
|
|
3
|
+
from .bank_accounts import create_bank_accounts_resource
|
|
4
|
+
from .instances import create_instances_resource
|
|
5
|
+
from .partner_fees import create_partner_fees_resource
|
|
6
|
+
from .payins import create_payin_quotes_resource, create_payins_resource
|
|
7
|
+
from .payouts import create_payouts_resource
|
|
8
|
+
from .quotes import create_quotes_resource
|
|
9
|
+
from .receivers import create_receivers_resource
|
|
10
|
+
from .virtual_accounts import create_virtual_accounts_resource
|
|
11
|
+
from .wallets import create_blockchain_wallets_resource, create_offramp_wallets_resource
|
|
12
|
+
from .webhooks import create_webhook_endpoints_resource
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"create_api_keys_resource",
|
|
16
|
+
"create_available_resource",
|
|
17
|
+
"create_bank_accounts_resource",
|
|
18
|
+
"create_instances_resource",
|
|
19
|
+
"create_partner_fees_resource",
|
|
20
|
+
"create_payins_resource",
|
|
21
|
+
"create_payin_quotes_resource",
|
|
22
|
+
"create_payouts_resource",
|
|
23
|
+
"create_quotes_resource",
|
|
24
|
+
"create_receivers_resource",
|
|
25
|
+
"create_virtual_accounts_resource",
|
|
26
|
+
"create_blockchain_wallets_resource",
|
|
27
|
+
"create_offramp_wallets_resource",
|
|
28
|
+
"create_webhook_endpoints_resource",
|
|
29
|
+
]
|