Extensions
/
Tronado
Tronado 1.0.0
Toman top-ups through the Tronado gateway. The customer enters an amount in toman, pays by bank card in Tronado's mini-app, Tronado sends the equivalent TRX to your wallet, and the bot credits the wallet once the order is accepted. Paid orders are watched for disputes and reversed if annulled.
You are looking at 1.0.0. The current release is 1.0.2.
Install
Install it
php bin/console ext:install Tronado --version=1.0.0
Update it later
php bin/console ext:update Tronado
Remove it
php bin/console ext:remove Tronado
Add --dry-run to any of these to see exactly which files
would change before anything is written.
Readme
Tronado
Toman top-ups through the [Tronado](https://t.me/TronadoSupp) gateway. The
customer enters an amount in toman and pays by bank card inside Tronado's
mini-app. Tronado sends the equivalent TRX to your TRON wallet, and the
bot credits the customer's wallet once Tronado accepts the order.
It registers itself as a payment method (tronado) with core's top-up
system. It shows up in /topup, can be switched off from **/admin →
Top-ups → 💳 Payment methods**, and the money it brings in is attributed to
it on the financial report.
Installing
php bin/console ext:install Tronado
Two settings are required. Both come from Tronado: get an API key from
Tronado support (@TronadoSupp) once your business is verified.
php bin/console ext:set Tronado api_key "YOUR-TRONADO-API-KEY"
php bin/console ext:set Tronado wallet_address "TXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
wallet_address is the TRON address Tronado pays the TRX to. It is checked
against the shape of a TRON address (T + 33 base58 characters). A value
that doesn't fit is treated as not set and stops the method, because a
typo here would send every customer's money to somebody else.
The API key can live in .env instead. ext:settings Tronado prints
the setting in full, so .env is the better home on a shared server:
TRONADO_API_KEY=your-tronado-api-key
The setting wins when both are present. If you move the key to .env,
clear the old setting or it keeps being used:
php bin/console ext:reset Tronado api_key
The payment method's line in /admin → Top-ups → 💳 Payment methods
says which one is in use (API key from .env or API key from settings).
A key exported by systemd or the shell works too. Restart the job worker
after changing .env (php bin/console jobs:service --restart), because
it reads .env only when it starts.
Then switch the method on in /admin → Top-ups → 💳 Payment methods.
The line under its name says what is missing while it is not configured.
Finally, make sure the job worker is running (`php bin/console
jobs:work, or jobs:work --run-service`). It is what notices that an order
was paid, and what watches paid orders for disputes. Without it, orders
are still credited, but only when the customer presses 🔄 بررسی پرداخت.
Wallet currency
Tronado prices everything in toman. The customer always types toman, and
the wallet is credited in its own unit:
| WALLET_CURRENCY | wallet_unit (auto) | 500,000 toman credits |
| --- | --- | --- |
| IRT / TOMAN | toman | 500,000 |
| IRR | rial | 5,000,000 |
| anything else | — | method reports itself unusable |
Set wallet_unit to toman or rial to override the guess.
What the customer sees
User: /topup → 💳 پرداخت با ترونادو
Bot: 💳 افزایش موجودی با ترونادو
⬇️ حداقل : 50,000 تومان
⬆️ حداکثر : 500,000 تومان
💱 قیمت هر ترون : 70,000 تومان
💵 چه مبلغی به کیف پول شما اضافه شود؟ ...
User: ۵۰۰,۰۰۰
Bot: 💳 سفارش ترونادو #12
➕ افزایش موجودی : 500,000 تومان
💰 مبلغ قابل پرداخت (با کارمزد) : 596,270 تومان
⏳ مهلت پرداخت : 2026-10-31 14:20 (به وقت تهران)
📍 وضعیت : ⏳ در انتظار پرداخت
[💳 پرداخت]
[🔄 بررسی پرداخت]
💳 پرداخت opens https://t.me/tronado_robot/customerpayment?startapp=<token>.
That is Tronado's payment page, inside Telegram, and the customer does not
need to start Tronado's bot. The same message updates itself as the order
moves: receipt under review, accepted, rejected. When the wallet is
credited, the customer also gets a separate message with the new balance.
The "amount to pay" line is Tronado's own estimate (EstimatedTomanAmount),
which includes Tronado's fee when the customer pays it.
How much is credited
The amount the customer asked for. The order is sized as toman ÷
Tronado's TRX price, which gives the TronAmount Tronado invoices. When
Tronado accepts it, that same toman amount is credited.
This is what Tronado's documentation says to credit when the fee is split
between you and the customer. It also matches the two documented fields
at both extremes, up to Tronado's rounding:
wage_from_business = 0: creditTomanAmountWithoutWage.wage_from_business = 100: creditUserPaidTomanAmount.
Either way, the customer is never handed Tronado's fee as a bonus.
| wage_from_business | Customer pays | You receive | Credited |
| --- | --- | --- | --- |
| 0 (default) | amount + fee | the full TRX | amount |
| 100 | about the amount | TRX minus fee | amount |
| between | in proportion | in proportion | amount |
Order statuses
| Tronado OrderStatusID | What happens |
| --- | --- |
| 20 waiting for payment | keep watching; after watch_hours the order is marked expired |
| 25 receipt sent, 27 ready to transfer | keep watching closely |
| 30 accepted (or IsPaid) | credited |
| 40 rejected, 200 cancelled | closed; the customer is told |
Expired is not final. Tronado may still accept a late payment, so the
poller stops but the customer's 🔄 بررسی پرداخت button keeps working on
an expired order. If Tronado says it was paid, it is credited then. An
expired order does not count against max_open.
Disputes
After Tronado accepts an order, the card holder can dispute it ("no
deposit", "duplicate receipt", "amount was less", ...). If Tronado accepts
the dispute, it either cancels the order or changes its amount.
So a paid order keeps being checked, hourly, for dispute_watch_days
(default 7):
- Cancelled (annulled): with
reverse_annulledon (the default), the
credit is taken back with a wallet debit. The customer and every admin
in ADMIN_IDS are told. If the customer has already spent the money,
nothing is taken (a balance never goes negative) and the admins are told
to settle it by hand.
- Amount changed: the admins are told the old and new TRX amounts. The
balance is not changed automatically; how to treat a customer who
paid less than they were credited is a decision, not arithmetic.
Set dispute_watch_days to 0 to stop watching once an order is credited.
Tronado's own limits
By default Tronado allows each customer one order a day, **500,000
toman a day and 1,000,000 toman a month**, and counts cancellations
against them. That is why max_toman defaults to 500,000 and max_open
to 1. Raise them only after Tronado support has raised yours. When Tronado
refuses an order (a limit reached, for example), its own message is shown
to the customer, so they know why.
Settings
| Key | Default | Meaning |
| --- | --- | --- |
| api_key | "" | Tronado API key; falls back to TRONADO_API_KEY in .env |
| wallet_address | "" | TRON address Tronado pays the TRX to |
| callback_url | "" | optional CallbackUrl sent to Tronado; see below |
| wage_from_business | 0 | % of Tronado's fee you absorb (0–100) |
| wallet_unit | auto | toman, rial, or auto from WALLET_CURRENCY |
| min_toman | 50000 | smallest order, toman (at least 1,000) |
| max_toman | 500000 | largest order, toman |
| max_open | 1 | unpaid orders one customer may have open (1–5) |
| watch_hours | 72 | hours an unpaid order is polled before it is marked expired (1–168) |
| poll_seconds | 60 | how often fresh orders are checked (30–3600); older ones less often |
| dispute_watch_days | 7 | days a paid order is re-checked for disputes; 0 = off |
| reverse_annulled | true | take the credit back when an order is annulled |
| timeout | 15 | seconds to wait on Tronado (3–30) |
| support_contact | "" | e.g. @yoursupport, added to messages that send the customer to support |
Why it polls instead of using the IPN callback
Tronado confirms payments by POSTing a signed IPN to a CallbackUrl. An
extension has no public HTTP endpoint; the only one this bot has is
Telegram's webhook. So this extension asks Tronado instead, with
POST /Order/GetStatusByPaymentID. That call needs only the API key and
the PaymentID this extension chose (botex-trnd-<row id>).
callback_url is sent only when you set it. The extension doesn't need
the callback. Set it only if Tronado refuses orders without one, and use
an https URL on a domain you have registered with Tronado support.
The IPN's UserPaidTomanAmount and TomanAmountWithoutWage aren't in the
status response. That is fine, because the credit is the order's own
invoice value (see "How much is credited").
Troubleshooting
When an order can't be created, the customer sees either Tronado's own
message (a limit reached, for example) or a generic "ساخت سفارش ترونادو
انجام نشد". The generic line means the problem is yours to fix, and the
reason is in the log:
grep "Tronado" storage/logs/app-$(date +%F).log
| Log says | Fix |
| --- | --- |
| Api key is wrong or not specified | api_key is wrong, or not yet activated by Tronado support |
| No Tronado API key is set | ext:set Tronado api_key ... |
| HTTP 4xx/5xx or Request failed | Tronado is unreachable from your server, or down |
| something about the wallet or callback | register the wallet / callback domain with Tronado support |
Problems with the key, wallet or callback are logged at error level, so
they reach the admins' chat when LOG_CHAT is set. They are never shown to
the customer, who can't do anything about them.
Tronado wraps most answers in an envelope the documentation doesn't show,
and returns HTTP 200 even when it refuses something:
{"IsSuccessful": false, "Code": -1, "Message": "...", "Data": null}.
The client unwraps it and reads the documented fields from Data.
Safety
- Money moves once. Every state change (open → paid, paid →
annulled) is a conditional UPDATE, in the same transaction as the
money it implies. The credit runs through TopUpService::credit() under
tronado:<id>:credit, the reversal under tronado:<id>:reverse. The
poll job and a customer pressing check can both see "accepted" at the
same moment: one credits, the other does nothing. A failed credit rolls
back and is retried on the next look.
- Logic is on
OrderStatusID, never on the Persian
OrderStatusTitle, which Tronado documents as display-only.
- Field names are read case-insensitively, so a Tronado answer in
camelCase is read the same as the documented PascalCase.
- The price endpoint is called without the API key, as documented, and
with {} as its body. IIS answers a bodiless POST with a 411 that looks
like a 404.
Tables
| Table | Holds |
| --- | --- |
| tronado_orders | one row per order, from creation to the end of its dispute window |
uninstall() drops it. Wallet entries are left alone. They are referenced
topup:tronado#<row id> for credits and tronado_order#<row id> for
reversals.
Releases
| Version | Published | Size | Files |
|---|---|---|---|
| 1.0.2 current | 29 Oct 2026 | 39.9 KB | 19 |
| 1.0.1 | 29 Oct 2026 | 39.1 KB | 19 |
| 1.0.0 | 29 Oct 2026 | 36.7 KB | 19 |