aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTolmachev Igor <me@igorek.dev>2025-07-17 15:18:44 +0900
committerTolmachev Igor <me@igorek.dev>2025-07-17 15:31:54 +0900
commit166b2faed2ee31970ca77f03d0c2095b3482ad26 (patch)
tree51cc5ea76ef9c64672ff5b231890b9512c8e21e4 /src
parent69296e9899d28a6a5e64708cbcdc57a59a54fa41 (diff)
downloadasync_crypto_pay_api-166b2faed2ee31970ca77f03d0c2095b3482ad26.tar.gz
async_crypto_pay_api-166b2faed2ee31970ca77f03d0c2095b3482ad26.zip
Release v.1.0.0
Diffstat (limited to 'src')
-rw-r--r--src/async_crypto_pay_api/__init__.py16
-rw-r--r--src/async_crypto_pay_api/__meta__.py2
-rw-r--r--src/async_crypto_pay_api/client.py371
-rw-r--r--src/async_crypto_pay_api/exceptions.py26
-rw-r--r--src/async_crypto_pay_api/models/__init__.py52
-rw-r--r--src/async_crypto_pay_api/models/app_info.py14
-rw-r--r--src/async_crypto_pay_api/models/app_stats.py19
-rw-r--r--src/async_crypto_pay_api/models/assets.py73
-rw-r--r--src/async_crypto_pay_api/models/balance.py16
-rw-r--r--src/async_crypto_pay_api/models/check.py26
-rw-r--r--src/async_crypto_pay_api/models/currency.py18
-rw-r--r--src/async_crypto_pay_api/models/exchange_rate.py19
-rw-r--r--src/async_crypto_pay_api/models/invoice.py82
-rw-r--r--src/async_crypto_pay_api/models/items.py14
-rw-r--r--src/async_crypto_pay_api/models/response.py22
-rw-r--r--src/async_crypto_pay_api/models/transfer.py23
16 files changed, 793 insertions, 0 deletions
diff --git a/src/async_crypto_pay_api/__init__.py b/src/async_crypto_pay_api/__init__.py
new file mode 100644
index 0000000..8689f7f
--- /dev/null
+++ b/src/async_crypto_pay_api/__init__.py
@@ -0,0 +1,16 @@
1# TODO: Add doc strings
2# TODO: Add webhooks
3
4# isort: off
5from . import models
6from . import exceptions
7from . import client
8
9from .client import CryptoPayApi
10
11__all__ = [
12 "models",
13 "exceptions",
14 "client",
15 "CryptoPayApi",
16]
diff --git a/src/async_crypto_pay_api/__meta__.py b/src/async_crypto_pay_api/__meta__.py
new file mode 100644
index 0000000..d53578f
--- /dev/null
+++ b/src/async_crypto_pay_api/__meta__.py
@@ -0,0 +1,2 @@
1__version__ = "1.0.0"
2__api_version__ = "1.5.1"
diff --git a/src/async_crypto_pay_api/client.py b/src/async_crypto_pay_api/client.py
new file mode 100644
index 0000000..93551fd
--- /dev/null
+++ b/src/async_crypto_pay_api/client.py
@@ -0,0 +1,371 @@
1from datetime import datetime, timedelta
2from decimal import Decimal
3from enum import Enum
4from secrets import token_hex
5from typing import Any, Literal, TypeVar, overload
6
7from aiohttp import ClientSession
8from pydantic import ValidationError
9
10from async_crypto_pay_api import models as m
11from async_crypto_pay_api.exceptions import InvalidResponseError, RequestError
12
13
14def serialize_value(value: Any) -> str:
15 if isinstance(value, str):
16 return value
17 elif isinstance(value, (int, float, Decimal)):
18 return str(value)
19 elif isinstance(value, bool):
20 return "true" if value else "false"
21 elif isinstance(value, list):
22 return ",".join(map(serialize_value, value))
23 elif isinstance(value, datetime):
24 return value.isoformat()
25 elif isinstance(value, Enum):
26 return value.value
27 else:
28 raise ValueError(f"unsupported type for serialization: '{type(value)}'")
29
30
31def serialize_body(body: dict[str, Any]) -> dict[str, Any]:
32 return {k: serialize_value(v) for k, v in body.items() if v is not None}
33
34
35R = TypeVar("R")
36
37
38class CryptoPayApi:
39 __token: str
40 __is_test_backend: bool
41 __session: ClientSession | None
42
43 def __init__(self, token: str, test_backend: bool = False) -> None:
44 self.__token = token
45 self.__is_test_backend = test_backend
46 self.__session = None
47
48 async def __aenter__(self) -> "CryptoPayApi":
49 self.__get_session()
50 return self
51
52 async def __aexit__(self, *_) -> bool:
53 await self.close()
54 return False
55
56 def __get_session(self) -> ClientSession:
57 if self.__session is None:
58 self.__session = ClientSession(
59 base_url=(
60 "https://testnet-pay.crypt.bot/api/"
61 if self.__is_test_backend
62 else "https://pay.crypt.bot/api/"
63 ),
64 headers={"Crypto-Pay-API-Token": self.__token},
65 )
66
67 return self.__session
68
69 async def __process_request(
70 self,
71 method_name: str,
72 result_model: type[R],
73 body: dict[str, Any] | None = None,
74 ) -> R:
75 async with (
76 self.__get_session().post(
77 method_name,
78 json={} if body is None else body,
79 ) as http_response,
80 ):
81 json = await http_response.json()
82 try:
83 response = m.Response[result_model].model_validate(json)
84 except ValidationError:
85 raise InvalidResponseError("invalid response body")
86
87 if response.ok:
88 if response.result is None:
89 raise InvalidResponseError(
90 "response.result is empty while response.ok = True"
91 )
92
93 return response.result
94 else:
95 if response.error is None:
96 raise InvalidResponseError(
97 "response.error is empty while response.ok = False"
98 )
99
100 raise RequestError(response.error.code, response.error.name)
101
102 @property
103 def token(self) -> str:
104 return self.__token
105
106 async def close(self) -> None:
107 if self.__session is None:
108 return
109
110 await self.__session.close()
111 self.__session = None
112
113 async def get_me(self) -> m.AppInfo:
114 return await self.__process_request("getMe", result_model=m.AppInfo)
115
116 @overload
117 async def create_invoice(
118 self,
119 *,
120 currency_type: Literal[m.CurrencyType.CRYPTO],
121 asset: m.CryptoAsset,
122 amount: int | float | Decimal,
123 swap_to: m.SwapAsset | None = None,
124 description: str | None = None,
125 hidden_message: str | None = None,
126 paid_btn_name: None = None,
127 paid_btn_url: None = None,
128 payload: str | None = None,
129 allow_comments: bool = True,
130 allow_anonymous: bool = True,
131 expires_in: timedelta | None = None,
132 ) -> m.Invoice: ...
133
134 @overload
135 async def create_invoice(
136 self,
137 *,
138 currency_type: Literal[m.CurrencyType.CRYPTO],
139 asset: m.CryptoAsset,
140 amount: int | float | Decimal,
141 paid_btn_name: m.PaidButtonName,
142 paid_btn_url: str,
143 swap_to: m.SwapAsset | None = None,
144 description: str | None = None,
145 hidden_message: str | None = None,
146 payload: str | None = None,
147 allow_comments: bool = True,
148 allow_anonymous: bool = True,
149 expires_in: timedelta | None = None,
150 ) -> m.Invoice: ...
151
152 @overload
153 async def create_invoice(
154 self,
155 *,
156 currency_type: Literal[m.CurrencyType.FIAT],
157 fiat: m.FiatAsset,
158 accepted_assets: list[m.CryptoAsset] | None = None,
159 amount: int | float | Decimal,
160 swap_to: m.SwapAsset | None = None,
161 description: str | None = None,
162 hidden_message: str | None = None,
163 paid_btn_name: None = None,
164 paid_btn_url: None = None,
165 payload: str | None = None,
166 allow_comments: bool = True,
167 allow_anonymous: bool = True,
168 expires_in: timedelta | None = None,
169 ) -> m.Invoice: ...
170
171 @overload
172 async def create_invoice(
173 self,
174 *,
175 currency_type: Literal[m.CurrencyType.FIAT],
176 fiat: m.FiatAsset,
177 amount: int | float | Decimal,
178 paid_btn_name: m.PaidButtonName,
179 paid_btn_url: str,
180 accepted_assets: list[m.CryptoAsset] | None = None,
181 swap_to: m.SwapAsset | None = None,
182 description: str | None = None,
183 hidden_message: str | None = None,
184 payload: str | None = None,
185 allow_comments: bool = True,
186 allow_anonymous: bool = True,
187 expires_in: timedelta | None = None,
188 ) -> m.Invoice: ...
189
190 async def create_invoice(self, **body: Any) -> m.Invoice:
191 return await self.__process_request(
192 "createInvoice",
193 result_model=m.Invoice,
194 body=serialize_body(body),
195 )
196
197 async def delete_invoice(self, invoice_id: int) -> bool:
198 return await self.__process_request(
199 "deleteInvoice",
200 result_model=bool,
201 body=serialize_body(dict(invoice_id=invoice_id)),
202 )
203
204 async def create_check(
205 self,
206 *,
207 asset: m.CryptoAsset,
208 amount: int | float | Decimal,
209 pin_to_user_id: int | None = None,
210 pin_to_username: str | None = None,
211 ) -> m.Check:
212 return await self.__process_request(
213 "createCheck",
214 result_model=m.Check,
215 body=serialize_body(
216 dict(
217 asset=asset,
218 amount=amount,
219 pin_to_user_id=pin_to_user_id,
220 pin_to_username=pin_to_username,
221 )
222 ),
223 )
224
225 async def delete_check(self, check_id: int) -> bool:
226 return await self.__process_request(
227 "deleteCheck",
228 result_model=bool,
229 body=serialize_body(dict(check_id=check_id)),
230 )
231
232 async def transfer(
233 self,
234 *,
235 user_id: int,
236 asset: m.CryptoAsset,
237 amount: int | float | Decimal,
238 comment: str | None = None,
239 disable_send_notification: bool = False,
240 ) -> m.Transfer:
241 return await self.__process_request(
242 "transfer",
243 m.Transfer,
244 body=serialize_body(
245 dict(
246 user_id=user_id,
247 asset=asset,
248 amount=amount,
249 spend_id=token_hex(32),
250 comment=comment,
251 disable_send_notification=disable_send_notification,
252 )
253 ),
254 )
255
256 @overload
257 async def get_invoices(
258 self,
259 *,
260 asset: m.CryptoAsset,
261 invoice_ids: list[int] | None = None,
262 status: m.InvoiceSearchStatus | None = None,
263 offset: int = 0,
264 count: int = 100,
265 ) -> list[m.Invoice]: ...
266
267 @overload
268 async def get_invoices(
269 self,
270 *,
271 fiat: m.FiatAsset,
272 invoice_ids: list[int] | None = None,
273 status: m.InvoiceSearchStatus | None = None,
274 offset: int = 0,
275 count: int = 100,
276 ) -> list[m.Invoice]: ...
277
278 @overload
279 async def get_invoices(
280 self,
281 *,
282 asset: None = None,
283 fiat: None = None,
284 invoice_ids: list[int] | None = None,
285 status: m.InvoiceSearchStatus | None = None,
286 offset: int = 0,
287 count: int = 100,
288 ) -> list[m.Invoice]: ...
289
290 async def get_invoices(self, **body: Any) -> list[m.Invoice]:
291 items = await self.__process_request(
292 "getInvoices",
293 m.Items[m.Invoice],
294 serialize_body(body),
295 )
296 return items.items
297
298 async def get_transfers(
299 self,
300 asset: m.CryptoAsset | None = None,
301 transfer_ids: list[int] | None = None,
302 offset: int = 0,
303 count: int = 100,
304 ) -> list[m.Transfer]:
305 items = await self.__process_request(
306 "getTransfers",
307 m.Items[m.Transfer],
308 serialize_body(
309 dict(
310 asset=asset,
311 transfer_ids=transfer_ids,
312 offset=offset,
313 count=count,
314 )
315 ),
316 )
317 return items.items
318
319 async def get_checks(
320 self,
321 asset: m.CryptoAsset | None = None,
322 check_ids: list[int] | None = None,
323 status: m.CheckStatus | None = None,
324 offset: int = 0,
325 count: int = 100,
326 ) -> list[m.Check]:
327 items = await self.__process_request(
328 "getChecks",
329 m.Items[m.Check],
330 serialize_body(
331 dict(
332 asset=asset,
333 check_ids=check_ids,
334 status=status,
335 offset=offset,
336 count=count,
337 )
338 ),
339 )
340 return items.items
341
342 async def get_balance(self) -> list[m.Balance]:
343 return await self.__process_request("getBalance", list[m.Balance])
344
345 async def get_exchange_rates(self) -> list[m.ExchangeRate]:
346 return await self.__process_request("getExchangeRates", list[m.ExchangeRate])
347
348 async def get_currencies(self) -> list[m.Currency]:
349 return await self.__process_request("getCurrencies", list[m.Currency])
350
351 async def get_stats(
352 self,
353 *,
354 start_at: datetime | None = None,
355 end_at: datetime | None = None,
356 ) -> m.AppStats:
357 return await self.__process_request(
358 "getStats",
359 m.AppStats,
360 serialize_body(
361 dict(
362 start_at=start_at,
363 end_at=end_at,
364 )
365 ),
366 )
367
368
369__all__ = [
370 "CryptoPayApi",
371]
diff --git a/src/async_crypto_pay_api/exceptions.py b/src/async_crypto_pay_api/exceptions.py
new file mode 100644
index 0000000..a5bcf4e
--- /dev/null
+++ b/src/async_crypto_pay_api/exceptions.py
@@ -0,0 +1,26 @@
1class CryptoPayError(Exception):
2 def __init__(self, message: str) -> None:
3 super().__init__(message)
4
5
6class RequestError(CryptoPayError):
7 status_code: int
8 name: str
9
10 def __init__(self, status_code: int, name: str) -> None:
11 super().__init__(f"{name} [{status_code}]")
12
13 self.status_code = status_code
14 self.name = name
15
16
17class InvalidResponseError(CryptoPayError):
18 def __init__(self, info: str) -> None:
19 super().__init__(f"server response is invalid: {info}")
20
21
22__all__ = [
23 "CryptoPayError",
24 "RequestError",
25 "InvalidResponseError",
26]
diff --git a/src/async_crypto_pay_api/models/__init__.py b/src/async_crypto_pay_api/models/__init__.py
new file mode 100644
index 0000000..36bde28
--- /dev/null
+++ b/src/async_crypto_pay_api/models/__init__.py
@@ -0,0 +1,52 @@
1# isort: off
2from .response import Error, Response
3from .app_info import AppInfo
4from .items import Items
5from .assets import CryptoAsset, FiatAsset, SwapAsset
6from .invoice import (
7 CurrencyType,
8 InvoiceStatus,
9 PaidButtonName,
10 Invoice,
11 InvoiceSearchStatus,
12)
13from .check import CheckStatus, Check
14from .transfer import Transfer
15from .balance import Balance
16from .exchange_rate import ExchangeRate
17from .app_stats import AppStats
18from .currency import Currency
19
20__all__ = [
21 # response
22 "Error",
23 "Response",
24 # app_info
25 "AppInfo",
26 # assets
27 "CryptoAsset",
28 "FiatAsset",
29 "SwapAsset",
30 # items
31 "Items",
32 # invoice,
33 "CurrencyType",
34 "InvoiceStatus",
35 "SwapAsset",
36 "PaidButtonName",
37 "Invoice",
38 "InvoiceSearchStatus",
39 # check
40 "CheckStatus",
41 "Check",
42 # transfer
43 "Transfer",
44 # balance
45 "Balance",
46 # exchange_rate
47 "ExchangeRate",
48 # currency
49 "Currency",
50 # app_stats
51 "AppStats",
52]
diff --git a/src/async_crypto_pay_api/models/app_info.py b/src/async_crypto_pay_api/models/app_info.py
new file mode 100644
index 0000000..e923212
--- /dev/null
+++ b/src/async_crypto_pay_api/models/app_info.py
@@ -0,0 +1,14 @@
1from typing import Literal
2
3from pydantic import BaseModel
4
5
6class AppInfo(BaseModel, frozen=True):
7 app_id: int
8 name: str
9 payment_processing_bot_username: Literal["CryptoBot", "CryptoTestnetBot"]
10
11
12__all__ = [
13 "AppInfo",
14]
diff --git a/src/async_crypto_pay_api/models/app_stats.py b/src/async_crypto_pay_api/models/app_stats.py
new file mode 100644
index 0000000..1f98554
--- /dev/null
+++ b/src/async_crypto_pay_api/models/app_stats.py
@@ -0,0 +1,19 @@
1from datetime import datetime as Datetime
2from decimal import Decimal
3
4from pydantic import BaseModel
5
6
7class AppStats(BaseModel, frozen=True):
8 volume: Decimal
9 conversion: Decimal
10 unique_users_count: int
11 created_invoice_count: int
12 paid_invoice_count: int
13 start_at: Datetime
14 end_at: Datetime
15
16
17__all__ = [
18 "AppStats",
19]
diff --git a/src/async_crypto_pay_api/models/assets.py b/src/async_crypto_pay_api/models/assets.py
new file mode 100644
index 0000000..2616668
--- /dev/null
+++ b/src/async_crypto_pay_api/models/assets.py
@@ -0,0 +1,73 @@
1from enum import Enum
2
3
4class EnumWithUnknown(Enum):
5 UNKNOWN = "UNKNOWN"
6
7
8class CryptoAsset(Enum):
9 USDT = "USDT"
10 TON = "TON"
11 SOL = "SOL"
12 TRX = "TRX"
13 GRAM = "GRAM"
14 BTC = "BTC"
15 ETH = "ETH"
16 DOGE = "DOGE"
17 LTC = "LTC"
18 NOT = "NOT"
19 TRUMP = "TRUMP"
20 MELANIA = "MELANIA"
21 PEPE = "PEPE"
22 WIF = "WIF"
23 BONK = "BONK"
24 MAJOR = "MAJOR"
25 MY = "MY"
26 DOGS = "DOGS"
27 MEMHASH = "MEMHASH"
28 BNB = "BNB"
29 HMSTR = "HMSTR"
30 CATI = "CATI"
31 USDC = "USDC"
32
33
34class FiatAsset(Enum):
35 RUB = "RUB"
36 USD = "USD"
37 EUR = "EUR"
38 BYN = "BYN"
39 UAH = "UAH"
40 GBP = "GBP"
41 CNY = "CNY"
42 KZT = "KZT"
43 UZS = "UZS"
44 GEL = "GEL"
45 TRY = "TRY"
46 AMD = "AMD"
47 THB = "THB"
48 INR = "INR"
49 BRL = "BRL"
50 IDR = "IDR"
51 AZN = "AZN"
52 AED = "AED"
53 PLN = "PLN"
54 ILS = "ILS"
55 KGS = "KGS"
56 TJS = "TJS"
57
58
59class SwapAsset(Enum):
60 USDT = "USDT"
61 TON = "TON"
62 TRX = "TRX"
63 ETH = "ETH"
64 SOL = "SOL"
65 BTC = "BTC"
66 LTC = "LTC"
67
68
69__all__ = [
70 "CryptoAsset",
71 "FiatAsset",
72 "SwapAsset",
73]
diff --git a/src/async_crypto_pay_api/models/balance.py b/src/async_crypto_pay_api/models/balance.py
new file mode 100644
index 0000000..3c64de6
--- /dev/null
+++ b/src/async_crypto_pay_api/models/balance.py
@@ -0,0 +1,16 @@
1from decimal import Decimal
2
3from pydantic import BaseModel
4
5from async_crypto_pay_api.models import CryptoAsset
6
7
8class Balance(BaseModel, frozen=True):
9 currency_code: CryptoAsset
10 available: Decimal
11 onhold: Decimal
12
13
14__all__ = [
15 "Balance",
16]
diff --git a/src/async_crypto_pay_api/models/check.py b/src/async_crypto_pay_api/models/check.py
new file mode 100644
index 0000000..b8f2b7b
--- /dev/null
+++ b/src/async_crypto_pay_api/models/check.py
@@ -0,0 +1,26 @@
1from datetime import datetime as Datetime
2from decimal import Decimal
3from enum import Enum
4
5from pydantic import BaseModel
6
7from async_crypto_pay_api.models import CryptoAsset
8
9
10class CheckStatus(Enum):
11 ACTIVE = "active"
12 ACTIVATED = "activated"
13
14
15class Check(BaseModel, frozen=True):
16 check_id: int
17 hash: str
18 asset: CryptoAsset
19 amount: Decimal
20 bot_check_url: str
21 status: CheckStatus
22 created_at: Datetime
23 activated_at: Datetime | None = None
24
25
26__all__ = []
diff --git a/src/async_crypto_pay_api/models/currency.py b/src/async_crypto_pay_api/models/currency.py
new file mode 100644
index 0000000..64a71a2
--- /dev/null
+++ b/src/async_crypto_pay_api/models/currency.py
@@ -0,0 +1,18 @@
1from pydantic import BaseModel
2
3from async_crypto_pay_api.models import CryptoAsset, FiatAsset
4
5
6class Currency(BaseModel, frozen=True):
7 is_blockchain: bool
8 is_stablecoin: bool
9 is_fiat: bool
10 name: str
11 code: CryptoAsset | FiatAsset
12 url: str | None = None
13 decimals: int
14
15
16__all__ = [
17 "Currency",
18]
diff --git a/src/async_crypto_pay_api/models/exchange_rate.py b/src/async_crypto_pay_api/models/exchange_rate.py
new file mode 100644
index 0000000..bf07719
--- /dev/null
+++ b/src/async_crypto_pay_api/models/exchange_rate.py
@@ -0,0 +1,19 @@
1from decimal import Decimal
2
3from pydantic import BaseModel
4
5from async_crypto_pay_api.models import CryptoAsset, FiatAsset
6
7
8class ExchangeRate(BaseModel, frozen=True):
9 is_valid: bool
10 is_crypto: bool
11 is_fiat: bool
12 source: CryptoAsset | FiatAsset
13 target: FiatAsset
14 rate: Decimal
15
16
17__all__ = [
18 "ExchangeRate",
19]
diff --git a/src/async_crypto_pay_api/models/invoice.py b/src/async_crypto_pay_api/models/invoice.py
new file mode 100644
index 0000000..e42dde0
--- /dev/null
+++ b/src/async_crypto_pay_api/models/invoice.py
@@ -0,0 +1,82 @@
1from datetime import datetime as Datetime
2from decimal import Decimal
3from enum import Enum
4
5from pydantic import BaseModel, Field
6
7from async_crypto_pay_api.models import CryptoAsset, FiatAsset, SwapAsset
8
9
10class CurrencyType(Enum):
11 CRYPTO = "crypto"
12 FIAT = "fiat"
13
14
15class InvoiceStatus(Enum):
16 ACTIVE = "active"
17 PAID = "paid"
18 EXPIRED = "expired"
19
20
21class PaidButtonName(Enum):
22 VIEW_ITEM = "viewItem"
23 OPEN_CHANNEL = "openChannel"
24 OPEN_BOT = "openBot"
25 CALLBACK = "callback"
26
27
28class Invoice(BaseModel, frozen=True):
29 invoice_id: int
30 hash: str
31 currency_type: CurrencyType
32 asset: CryptoAsset | None = None
33 fiat: FiatAsset | None = None
34 amount: Decimal
35 paid_asset: CryptoAsset | None = None
36 paid_amount: Decimal | None = None
37 paid_fiat_rate: Decimal | None = None
38 accepted_assets: list[CryptoAsset] | None = None
39 fee_asset: CryptoAsset | None = None
40 fee_amount: Decimal | None = None
41 # fee: Decimal | None = Field(deprecated=True)
42 pay_url: str | None = Field(default=None, deprecated=True)
43 bot_invoice_url: str
44 mini_app_invoice_url: str
45 web_app_invoice_url: str
46 description: str | None = None
47 status: InvoiceStatus
48 swap_to: SwapAsset | None = None
49 is_swapped: bool | None = None
50 swapped_uid: str | None = None
51 swapped_to: SwapAsset | None = None
52 swapped_rate: Decimal | None = None
53 swapped_output: Decimal | None = None
54 swapped_usd_amount: Decimal | None = None
55 swapped_usd_rate: Decimal | None = None
56 created_at: Datetime
57 paid_usd_rate: Decimal | None = None
58 # usd_rate: Decimal | None = Field(deprecated=True)
59 allow_comments: bool
60 allow_anonymous: bool
61 expiration_date: Datetime | None = None
62 paid_at: Datetime | None = None
63 paid_anonymously: bool | None = None
64 comment: str | None = None
65 hidden_message: str | None = None
66 payload: str | None = None
67 paid_btn_name: PaidButtonName | None = None
68 paid_btn_url: str | None = None
69
70
71class InvoiceSearchStatus(Enum):
72 ACTIVE = "active"
73 PAID = "paid"
74
75
76__all__ = [
77 "CurrencyType",
78 "InvoiceStatus",
79 "PaidButtonName",
80 "Invoice",
81 "InvoiceSearchStatus",
82]
diff --git a/src/async_crypto_pay_api/models/items.py b/src/async_crypto_pay_api/models/items.py
new file mode 100644
index 0000000..05d60ea
--- /dev/null
+++ b/src/async_crypto_pay_api/models/items.py
@@ -0,0 +1,14 @@
1from typing import Generic, TypeVar
2
3from pydantic import BaseModel
4
5T = TypeVar("T")
6
7
8class Items(BaseModel, Generic[T], frozen=True):
9 items: list[T]
10
11
12__all__ = [
13 "Items",
14]
diff --git a/src/async_crypto_pay_api/models/response.py b/src/async_crypto_pay_api/models/response.py
new file mode 100644
index 0000000..6fcfa92
--- /dev/null
+++ b/src/async_crypto_pay_api/models/response.py
@@ -0,0 +1,22 @@
1from typing import Generic, TypeVar
2
3from pydantic import BaseModel
4
5R = TypeVar("R")
6
7
8class Error(BaseModel, frozen=True):
9 code: int
10 name: str
11
12
13class Response(BaseModel, Generic[R], frozen=True):
14 ok: bool
15 result: R | None = None
16 error: Error | None = None
17
18
19__all__ = [
20 "Error",
21 "Response",
22]
diff --git a/src/async_crypto_pay_api/models/transfer.py b/src/async_crypto_pay_api/models/transfer.py
new file mode 100644
index 0000000..fbc9ac3
--- /dev/null
+++ b/src/async_crypto_pay_api/models/transfer.py
@@ -0,0 +1,23 @@
1from datetime import datetime as Datetime
2from decimal import Decimal
3from typing import Literal
4
5from pydantic import BaseModel
6
7from async_crypto_pay_api.models import CryptoAsset
8
9
10class Transfer(BaseModel, frozen=True):
11 transfer_id: int
12 spend_id: str
13 user_id: int
14 asset: CryptoAsset
15 amount: Decimal
16 status: Literal["completed"]
17 completed_at: Datetime
18 comment: str | None = None
19
20
21__all__ = [
22 "Transfer",
23]