tronado 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. tronado-0.1.0/.gitignore +35 -0
  2. tronado-0.1.0/CHANGELOG.md +26 -0
  3. tronado-0.1.0/LICENSE +21 -0
  4. tronado-0.1.0/PKG-INFO +367 -0
  5. tronado-0.1.0/README.md +332 -0
  6. tronado-0.1.0/docs/DESIGN.md +100 -0
  7. tronado-0.1.0/examples/async_quickstart.py +38 -0
  8. tronado-0.1.0/examples/fastapi_webhook.py +61 -0
  9. tronado-0.1.0/examples/full_payment_flow.py +62 -0
  10. tronado-0.1.0/examples/quickstart.py +48 -0
  11. tronado-0.1.0/pyproject.toml +93 -0
  12. tronado-0.1.0/src/tronado/__init__.py +111 -0
  13. tronado-0.1.0/src/tronado/_http/__init__.py +21 -0
  14. tronado-0.1.0/src/tronado/_http/async_transport.py +103 -0
  15. tronado-0.1.0/src/tronado/_http/base.py +48 -0
  16. tronado-0.1.0/src/tronado/_http/processing.py +219 -0
  17. tronado-0.1.0/src/tronado/_http/retry.py +86 -0
  18. tronado-0.1.0/src/tronado/_http/sync_transport.py +102 -0
  19. tronado-0.1.0/src/tronado/client.py +237 -0
  20. tronado-0.1.0/src/tronado/config.py +138 -0
  21. tronado-0.1.0/src/tronado/constants.py +78 -0
  22. tronado-0.1.0/src/tronado/exceptions.py +130 -0
  23. tronado-0.1.0/src/tronado/models/__init__.py +50 -0
  24. tronado-0.1.0/src/tronado/models/base.py +80 -0
  25. tronado-0.1.0/src/tronado/models/common.py +26 -0
  26. tronado-0.1.0/src/tronado/models/order.py +107 -0
  27. tronado-0.1.0/src/tronado/models/price.py +107 -0
  28. tronado-0.1.0/src/tronado/models/webhook.py +71 -0
  29. tronado-0.1.0/src/tronado/py.typed +0 -0
  30. tronado-0.1.0/src/tronado/versions/__init__.py +63 -0
  31. tronado-0.1.0/src/tronado/versions/base.py +65 -0
  32. tronado-0.1.0/src/tronado/versions/v5/__init__.py +55 -0
  33. tronado-0.1.0/src/tronado/versions/v5/operations.py +109 -0
  34. tronado-0.1.0/src/tronado/versions/v5/resources.py +329 -0
  35. tronado-0.1.0/src/tronado/webhook.py +134 -0
  36. tronado-0.1.0/tests/__init__.py +0 -0
  37. tronado-0.1.0/tests/conftest.py +40 -0
  38. tronado-0.1.0/tests/test_async.py +114 -0
  39. tronado-0.1.0/tests/test_client.py +53 -0
  40. tronado-0.1.0/tests/test_config.py +93 -0
  41. tronado-0.1.0/tests/test_errors.py +129 -0
  42. tronado-0.1.0/tests/test_models.py +99 -0
  43. tronado-0.1.0/tests/test_order.py +112 -0
  44. tronado-0.1.0/tests/test_price.py +78 -0
  45. tronado-0.1.0/tests/test_retry.py +47 -0
  46. tronado-0.1.0/tests/test_webhook.py +91 -0
@@ -0,0 +1,35 @@
1
+ # Byte-compiled / optimized
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # Distribution / packaging
7
+ build/
8
+ dist/
9
+ *.egg-info/
10
+ .eggs/
11
+ *.egg
12
+
13
+ # Virtual environments
14
+ .venv/
15
+ venv/
16
+ env/
17
+
18
+ # Test / coverage
19
+ .pytest_cache/
20
+ .coverage
21
+ .coverage.*
22
+ htmlcov/
23
+ .mypy_cache/
24
+ .ruff_cache/
25
+
26
+ # Editors / OS
27
+ .idea/
28
+ .vscode/
29
+ *.swp
30
+ .DS_Store
31
+ Thumbs.db
32
+
33
+ # Local secrets
34
+ .env
35
+ .env.*
@@ -0,0 +1,26 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format is based on
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to
5
+ [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [0.1.0] - 2026-06-23
8
+
9
+ ### Added
10
+
11
+ - Initial release with full coverage of the Tronado Public API **v5**.
12
+ - Synchronous `TronadoClient` and asynchronous `AsyncTronadoClient` (built on `httpx`).
13
+ - Version-aware architecture (`tronado.versions`) with v5 implemented; new versions plug
14
+ in via the registry without touching the transport, models, or error handling.
15
+ - Typed Pydantic v2 request/response models with PascalCase aliasing and `Decimal`
16
+ amounts.
17
+ - `Order` resource: `get_order_token`, `get_status`, `get_status_by_payment_id`.
18
+ - `price` namespace: `tron`, `toman`, and `dollar` price/conversion endpoints.
19
+ - Inbound IPN/webhook helpers: `verify_signature`, `parse_callback`, `construct_event`
20
+ implementing the documented HMAC-SHA512 (`X-Tronado-Sig`) scheme.
21
+ - Configurable base URL, timeout, retries (exponential backoff with jitter, `Retry-After`
22
+ aware), and default headers.
23
+ - Idempotency-aware retries: `GetOrderToken` is never auto-retried (it creates a
24
+ transaction); reads are retried.
25
+ - Full exception hierarchy rooted at `TronadoError`.
26
+ - Unit test-suite (sync + async) and runnable examples.
tronado-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tronado SDK contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
tronado-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,367 @@
1
+ Metadata-Version: 2.4
2
+ Name: tronado
3
+ Version: 0.1.0
4
+ Summary: Production-grade, version-aware Python SDK for the Tronado Public API (v5).
5
+ Project-URL: Homepage, https://t.me/TronadoBusiness
6
+ Project-URL: Documentation, https://documenter.getpostman.com/view/48018954/2sBXwwm7St
7
+ Project-URL: Support, https://t.me/TronadoSupp
8
+ Author: Tronado SDK contributors
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: api-client,crypto,payments,sdk,tron,tronado,trx
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.9
24
+ Requires-Dist: httpx<1,>=0.27
25
+ Requires-Dist: pydantic<3,>=2.5
26
+ Provides-Extra: dev
27
+ Requires-Dist: mypy>=1.10; extra == 'dev'
28
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
29
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
30
+ Requires-Dist: pytest>=8.0; extra == 'dev'
31
+ Requires-Dist: respx>=0.21; extra == 'dev'
32
+ Requires-Dist: ruff>=0.5; extra == 'dev'
33
+ Requires-Dist: tox>=4.0; extra == 'dev'
34
+ Description-Content-Type: text/markdown
35
+
36
+ # Tronado Python SDK
37
+
38
+ A production-grade, **version-aware** Python SDK for the [Tronado Public API](https://documenter.getpostman.com/view/48018954/2sBXwwm7St).
39
+ Tronado lets a business accept TRON (TRX) payments: you create an order, the customer
40
+ pays through Tronado's in-app/web payment UI, and Tronado notifies your server with a
41
+ signed webhook.
42
+
43
+ - **Sync & async** clients on a single `httpx` core
44
+ - **Typed** Pydantic v2 request/response models (amounts are `Decimal`, never `float`)
45
+ - **Idempotency-aware retries** — order creation is never silently retried
46
+ - **Webhook verification** for the documented `X-Tronado-Sig` HMAC-SHA512 scheme
47
+ - **Designed for API versioning** — v5 today, future versions plug in cleanly
48
+
49
+ > Currently implements API **v5** (the recommended version).
50
+
51
+ ---
52
+
53
+ ## Installation
54
+
55
+ ```bash
56
+ pip install tronado
57
+ ```
58
+
59
+ From source (this repository):
60
+
61
+ ```bash
62
+ pip install -e ".[dev]" # includes test/lint tooling
63
+ ```
64
+
65
+ Requires Python **3.9+**. Runtime dependencies: `httpx` and `pydantic` (v2).
66
+
67
+ ---
68
+
69
+ ## Authentication
70
+
71
+ Every outbound endpoint requires your API key in the **`x-api-key`** header (this is the
72
+ documented scheme — there is no `Authorization: Bearer`). Request a key from
73
+ [Tronado support](https://t.me/TronadoSupp).
74
+
75
+ ```python
76
+ from tronado import TronadoClient
77
+
78
+ client = TronadoClient(api_key="YOUR_API_KEY")
79
+ ```
80
+
81
+ Or via environment variable (no argument needed):
82
+
83
+ ```bash
84
+ export TRONADO_API_KEY="YOUR_API_KEY"
85
+ # optional: export TRONADO_BASE_URL="https://bot.tronado.cloud"
86
+ ```
87
+
88
+ ---
89
+
90
+ ## Configuration
91
+
92
+ | Option | Default | Description |
93
+ |---|---|---|
94
+ | `api_key` | `TRONADO_API_KEY` env | API key for the `x-api-key` header |
95
+ | `base_url` | `https://bot.tronado.cloud` | API base URL (`TRONADO_BASE_URL` honoured) |
96
+ | `timeout` | `30.0` | Per-request timeout (seconds) |
97
+ | `max_retries` | `3` | Max retries for **idempotent** operations |
98
+ | `backoff_factor` | `0.5` | Exponential-backoff base multiplier (seconds) |
99
+ | `default_version` | `"v5"` | Version used by the `order` / `price` shortcuts |
100
+ | `default_headers` | `{}` | Extra headers added to every request |
101
+ | `user_agent` | `tronado-python/<ver>` | `User-Agent` header |
102
+ | `http_client` | `None` | Inject your own `httpx.Client`/`AsyncClient` |
103
+
104
+ ```python
105
+ client = TronadoClient(
106
+ api_key="YOUR_API_KEY",
107
+ timeout=15.0,
108
+ max_retries=5,
109
+ backoff_factor=0.25,
110
+ default_headers={"X-Trace-Id": "abc123"},
111
+ )
112
+ ```
113
+
114
+ You can also pass a fully built `TronadoConfig`:
115
+
116
+ ```python
117
+ from tronado import TronadoConfig, TronadoClient
118
+
119
+ config = TronadoConfig(api_key="YOUR_API_KEY", timeout=10)
120
+ client = TronadoClient(config=config)
121
+ ```
122
+
123
+ ---
124
+
125
+ ## Quickstart (sync)
126
+
127
+ ```python
128
+ from decimal import Decimal
129
+ from tronado import TronadoClient
130
+
131
+ with TronadoClient(api_key="YOUR_API_KEY") as tron:
132
+ # 1. Tronado's TRX price differs from exchanges — always price first.
133
+ price = tron.price.tron.get_price_to_toman()
134
+ print(price.tron_price_toman, "Toman per TRX")
135
+
136
+ # 2. Create the order and get a payment link.
137
+ order = tron.order.get_order_token(
138
+ payment_id="inv-1001", # your unique id
139
+ wallet_address="TXYZ12345abcdef...", # destination wallet
140
+ tron_amount=Decimal("12.123456"), # invoice amount in TRX
141
+ callback_url="https://your-domain.com/payment/callback",
142
+ wage_from_business_percentage=0, # who absorbs the fee (0–100)
143
+ )
144
+ print("Pay here:", order.full_payment_url)
145
+
146
+ # 3. Later, check status (by Tronado OrderId / TrndOrderID_x / TXID).
147
+ status = tron.order.get_status(id="TrndOrderID_55")
148
+ print("Paid?" , status.is_payment_accepted)
149
+ ```
150
+
151
+ ## Quickstart (async)
152
+
153
+ ```python
154
+ import asyncio
155
+ from tronado import AsyncTronadoClient
156
+
157
+ async def main() -> None:
158
+ async with AsyncTronadoClient(api_key="YOUR_API_KEY") as tron:
159
+ price = await tron.price.tron.get_price_to_toman()
160
+ order = await tron.order.get_order_token(
161
+ payment_id="inv-1002",
162
+ wallet_address="TXYZ...",
163
+ tron_amount="8.5",
164
+ callback_url="https://your-domain.com/payment/callback",
165
+ )
166
+ print(order.full_payment_url)
167
+
168
+ asyncio.run(main())
169
+ ```
170
+
171
+ ---
172
+
173
+ ## Endpoint reference
174
+
175
+ The async client exposes the same methods with `await`.
176
+
177
+ ### Order — `client.order`
178
+
179
+ | Method | Endpoint | Returns |
180
+ |---|---|---|
181
+ | `get_order_token(payment_id, wallet_address, tron_amount, callback_url, wage_from_business_percentage=0)` | `POST /api/v5/GetOrderToken` | `OrderTokenData` |
182
+ | `get_status(id)` | `POST /Order/GetStatus` | `OrderStatus` (raises `OrderNotFoundError` if absent) |
183
+ | `get_status_by_payment_id(id)` | `POST /Order/GetStatusByPaymentID` | `OrderStatus` |
184
+
185
+ ### Price — `client.price`
186
+
187
+ | Method | Endpoint | Returns |
188
+ |---|---|---|
189
+ | `price.tron.get_price_to_toman()` | `POST /Tron/GetPriceToToman` | `TronPrice` |
190
+ | `price.tron.get_price_with_wage_to_toman(request_code, wallet_address, tron_amount)` | `POST /Tron/GetPriceWithWageToToman` | `PriceWithWage` |
191
+ | `price.toman.convert_to_tron_wage_subtracted(toman, wallet)` | `POST /Toman/ConvertToTronWageSubtracted` | `TronConversion` |
192
+ | `price.toman.get_price_to_toman()` | `POST /Toman/GetPriceToToman` | `DollarPrice` |
193
+ | `price.dollar.convert_to_tron_wage_subtracted(dollar, wallet)` | `POST /Dollar/ConvertToTronWageSubtracted` | `TronConversion` |
194
+ | `price.dollar.get_price_to_toman()` | `POST /Dollar/GetPriceToToman` | `DollarPrice` |
195
+
196
+ > Amount arguments (`tron_amount`, `dollar`) accept `Decimal`, `int`, `float`, or `str`.
197
+ > They are coerced via `str` to avoid binary-float rounding, and sent on the wire as JSON
198
+ > numbers.
199
+
200
+ ---
201
+
202
+ ## Handling webhooks (IPN)
203
+
204
+ Tronado POSTs a JSON callback to your `CallbackUrl` **on every order status change** and
205
+ signs it with `X-Tronado-Sig = HMAC_SHA512(raw_body, your_ipn_signing_key)` (lowercase
206
+ hex). Get your `IpnSigningKey` from support. **Always verify against the raw body** — do
207
+ not re-serialize parsed JSON.
208
+
209
+ ```python
210
+ from tronado.webhook import construct_event
211
+ from tronado.exceptions import InvalidSignatureError
212
+
213
+ IPN_SIGNING_KEY = "YOUR_IPN_SIGNING_KEY"
214
+
215
+ def handle_webhook(raw_body: bytes, signature_header: str) -> int:
216
+ try:
217
+ event = construct_event(raw_body, signature_header, IPN_SIGNING_KEY)
218
+ except InvalidSignatureError:
219
+ return 401 # reject
220
+
221
+ # De-duplicate: the same (payment_id, status) may arrive more than once.
222
+ if already_processed(event.dedup_key):
223
+ return 200
224
+
225
+ if event.is_payment_accepted: # IsPaid == True or status 30
226
+ # Charge the user with what they actually paid (includes wage), per the docs.
227
+ credit_user(event.payment_id, event.user_paid_toman_amount)
228
+
229
+ return 200 # return 2xx to acknowledge, else Tronado retries
230
+ ```
231
+
232
+ FastAPI example (reads the **raw** body before parsing):
233
+
234
+ ```python
235
+ from fastapi import FastAPI, Request, Response
236
+ from tronado.webhook import construct_event
237
+ from tronado.exceptions import InvalidSignatureError, TronadoWebhookError
238
+
239
+ app = FastAPI()
240
+
241
+ @app.post("/payment/callback")
242
+ async def callback(request: Request) -> Response:
243
+ raw = await request.body()
244
+ sig = request.headers.get("X-Tronado-Sig", "")
245
+ try:
246
+ event = construct_event(raw, sig, "YOUR_IPN_SIGNING_KEY")
247
+ except (InvalidSignatureError, TronadoWebhookError):
248
+ return Response(status_code=401)
249
+ # ... process event ...
250
+ return Response(status_code=200)
251
+ ```
252
+
253
+ If you only need to verify or parse separately:
254
+
255
+ ```python
256
+ from tronado.webhook import verify_signature, parse_callback
257
+
258
+ if verify_signature(raw, sig, signing_key):
259
+ event = parse_callback(raw)
260
+ ```
261
+
262
+ ---
263
+
264
+ ## Error handling
265
+
266
+ All errors derive from `TronadoError`:
267
+
268
+ ```python
269
+ from tronado.exceptions import (
270
+ TronadoError, TronadoAuthenticationError, TronadoRateLimitError,
271
+ OrderNotFoundError, TronadoValidationError, TronadoServerError,
272
+ TronadoTimeoutError, TronadoConnectionError,
273
+ )
274
+
275
+ try:
276
+ status = client.order.get_status(id="maybe-missing")
277
+ except OrderNotFoundError:
278
+ status = None
279
+ except TronadoAuthenticationError:
280
+ ... # bad/missing API key (HTTP 401 or envelope Code == -1)
281
+ except TronadoRateLimitError:
282
+ ... # 429 / per-user limits
283
+ except (TronadoTimeoutError, TronadoConnectionError):
284
+ ... # network problems
285
+ except TronadoServerError:
286
+ ... # 5xx (already retried for idempotent calls)
287
+ except TronadoError:
288
+ ... # catch-all
289
+ ```
290
+
291
+ `TronadoAPIError` (and its subclasses) carry `.status_code`, `.code`, `.message`, and
292
+ `.response` for diagnostics.
293
+
294
+ ### Retries & idempotency
295
+
296
+ - Idempotent reads (price/status) are retried on timeouts, connection errors, `429`, and
297
+ `5xx`, using exponential backoff with jitter (and `Retry-After` when present).
298
+ - **`get_order_token` is never auto-retried** — it creates a transaction, so retrying on
299
+ an ambiguous failure could create a duplicate order. Handle its failures explicitly.
300
+
301
+ ---
302
+
303
+ ## Versioning
304
+
305
+ The Tronado API versions endpoints in the URL path (`/api/v{version}/...`). In v5 only
306
+ `GetOrderToken` is version-pathed; the price/status endpoints live at unversioned roots.
307
+ The SDK models this with per-version **operation descriptors**, so:
308
+
309
+ ```python
310
+ client.v5.order.get_order_token(...) # explicit version pin
311
+ client.version("v5").price.tron.get_price_to_toman()
312
+ client.order.get_order_token(...) # uses default_version
313
+ ```
314
+
315
+ When Tronado ships a new version, it becomes a new `tronado/versions/vN/` package and a
316
+ one-line registry entry — the transport, models, and error handling are reused unchanged.
317
+
318
+ ```python
319
+ from tronado import available_versions
320
+ print(available_versions()) # ('v5',)
321
+ ```
322
+
323
+ ---
324
+
325
+ ## The fee (wage) model
326
+
327
+ The default wage is 20% (configurable per business; minimum is the greater of 9,000 Toman
328
+ or $0.10). `wage_from_business_percentage` on `get_order_token` controls who absorbs it:
329
+
330
+ - `0` (default): the whole fee is added on top of the user's payment; you receive the full
331
+ invoice TRX.
332
+ - `100`: the whole fee is taken from your share; the user pays roughly the base value and
333
+ your received `TronAmount` is the net after fee.
334
+ - Values in between split the fee proportionally.
335
+
336
+ To charge your user correctly, use `user_paid_toman_amount` from the **v5 webhook** (what
337
+ the user actually paid) rather than the TRX you receive.
338
+
339
+ ---
340
+
341
+ ## Development
342
+
343
+ ```bash
344
+ pip install -e ".[dev]"
345
+ pytest # run the test-suite (sync + async)
346
+ ruff check . # lint
347
+ mypy src # type-check
348
+ ```
349
+
350
+ Run the suite across every supported interpreter with **tox** (included in the `dev`
351
+ extra installed above):
352
+
353
+ ```bash
354
+ tox # py39–py313 + a lint/type-check env
355
+ ```
356
+
357
+ CI (GitHub Actions, [`.github/workflows/ci.yml`](.github/workflows/ci.yml)) runs ruff and
358
+ mypy once, then the test-suite on Python **3.9, 3.10, 3.11, 3.12, and 3.13**.
359
+
360
+ See [`examples/`](examples/) for runnable scripts and [`docs/DESIGN.md`](docs/DESIGN.md)
361
+ for the architecture and the full endpoint matrix.
362
+
363
+ ## License
364
+
365
+ MIT — see [LICENSE](LICENSE).
366
+
367
+ Support: [t.me/TronadoSupp](https://t.me/TronadoSupp)