diff --git a/CHANGELOG.md b/CHANGELOG.md index 002a208..c4c0294 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,9 @@ and this project uses [Calendar Versioning](https://calver.org/) (`YYYY.MINOR.MI - `@username` / `username` / `https://t.me/username` - `get_wallet()` now returns balances as separate fields: `ton_balance` and `usdt_balance` - Wallet/system test output now prints TON and USDT balances on separate lines +- Balance checks are now method-aware with explicit thresholds: + - `ton`: minimum TON balance threshold via `MIN_TON_BALANCE` (based on current 50 Stars purchase amount) + - `usdt_ton`: minimum USDT balance threshold via `MIN_USDT_BALANCE` (based on current 50 Stars purchase amount) ### Tests diff --git a/examples/purchase/recharge_ads_balance.py b/examples/purchase/recharge_ads_balance.py index d0084bc..5e4c457 100644 --- a/examples/purchase/recharge_ads_balance.py +++ b/examples/purchase/recharge_ads_balance.py @@ -2,7 +2,7 @@ Example: recharge your own Telegram Ads account with TON. Amount must be an integer between 1 and 1 000 000 000 TON. -Your wallet must hold at least the recharge amount + ~0.056 TON for gas. +Your wallet must satisfy the current minimum TON threshold and transaction cost. """ import asyncio diff --git a/examples/purchase/topup_ton_balance.py b/examples/purchase/topup_ton_balance.py index f09d0f3..c4fa58f 100644 --- a/examples/purchase/topup_ton_balance.py +++ b/examples/purchase/topup_ton_balance.py @@ -4,7 +4,7 @@ Example: top up TON to a recipient's Telegram balance. For adding TON to a Telegram Ads account, use recharge_ads() instead. Amount must be an integer between 1 and 1 000 000 000 TON. -Your wallet must hold at least the top-up amount + ~0.056 TON for gas. +Your wallet must satisfy the current minimum TON threshold and transaction cost. """ import asyncio diff --git a/pyfragment/methods/giveaway_premium.py b/pyfragment/methods/giveaway_premium.py index 32fc860..8acb0ea 100644 --- a/pyfragment/methods/giveaway_premium.py +++ b/pyfragment/methods/giveaway_premium.py @@ -77,7 +77,7 @@ async def giveaway_premium( }, page_url=PREMIUM_GIVEAWAY_PAGE, ) - required_payment_amount = parse_required_payment_amount(result, payment_method) + required_payment_amount = parse_required_payment_amount(result) req_id = result.get("req_id") if not req_id: raise FragmentAPIError(FragmentAPIError.NO_REQUEST_ID.format(context="Premium giveaway")) diff --git a/pyfragment/methods/giveaway_stars.py b/pyfragment/methods/giveaway_stars.py index 8eac8b7..90b3dc7 100644 --- a/pyfragment/methods/giveaway_stars.py +++ b/pyfragment/methods/giveaway_stars.py @@ -73,7 +73,7 @@ async def giveaway_stars( }, page_url=STARS_GIVEAWAY_PAGE, ) - required_payment_amount = parse_required_payment_amount(result, payment_method) + required_payment_amount = parse_required_payment_amount(result) req_id = result.get("req_id") if not req_id: raise FragmentAPIError(FragmentAPIError.NO_REQUEST_ID.format(context="Stars giveaway")) diff --git a/pyfragment/methods/purchase_premium.py b/pyfragment/methods/purchase_premium.py index c3a4f11..5924fe1 100644 --- a/pyfragment/methods/purchase_premium.py +++ b/pyfragment/methods/purchase_premium.py @@ -71,7 +71,7 @@ async def purchase_premium( {"recipient": recipient, "months": months, "payment_method": payment_method}, page_url=PREMIUM_PAGE, ) - required_payment_amount = parse_required_payment_amount(result, payment_method) + required_payment_amount = parse_required_payment_amount(result) req_id = result.get("req_id") if not req_id: raise FragmentAPIError(FragmentAPIError.NO_REQUEST_ID.format(context="Premium purchase")) diff --git a/pyfragment/methods/purchase_stars.py b/pyfragment/methods/purchase_stars.py index e51b0e8..44c0e1e 100644 --- a/pyfragment/methods/purchase_stars.py +++ b/pyfragment/methods/purchase_stars.py @@ -67,7 +67,7 @@ async def purchase_stars( {"recipient": recipient, "quantity": amount, "payment_method": payment_method}, page_url=STARS_PAGE, ) - required_payment_amount = parse_required_payment_amount(result, payment_method) + required_payment_amount = parse_required_payment_amount(result) req_id = result.get("req_id") if not req_id: raise FragmentAPIError(FragmentAPIError.NO_REQUEST_ID.format(context="Stars purchase")) diff --git a/pyfragment/types/constants.py b/pyfragment/types/constants.py index 98191cc..af7b29d 100644 --- a/pyfragment/types/constants.py +++ b/pyfragment/types/constants.py @@ -16,8 +16,8 @@ SUPPORTED_WALLET_VERSIONS: frozenset[str] = frozenset(get_args(WalletVersion)) # Wallet class map — used to resolve the correct contract from WALLET_VERSION WALLET_CLASSES: dict[str, Any] = {"V4R2": WalletV4R2, "V5R1": WalletV5R1} -# Minimum minimum TON balance required for payments (transaction amount + gas reserve). -MIN_TON_BALANCE: float = 0.33 # 0.3 TON for transaction + 0.03 TON gas reserve +# Minimum TON balance threshold required for payment flows. +MIN_TON_BALANCE: float = 0.33 # USDT (TON) jetton metadata used for payment-method balance checks. USDT_TON_MASTER_ADDRESS: str = "EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs" diff --git a/pyfragment/utils/html.py b/pyfragment/utils/html.py index e968edf..d0b98b6 100644 --- a/pyfragment/utils/html.py +++ b/pyfragment/utils/html.py @@ -163,14 +163,8 @@ def parse_gift_items(html: str) -> tuple[list[dict[str, Any]], int | None]: return items, next_offset -def parse_required_payment_amount(init_response: dict[str, Any], payment_method: str) -> float | None: - """Extract required payment amount from init*Request response. - - Fragment includes ``amount`` for both ``ton`` and ``usdt_ton`` init flows, - so this helper always reads ``amount``. - """ - del payment_method - +def parse_required_payment_amount(init_response: dict[str, Any]) -> float | None: + """Extract required payment amount from init*Request response.""" raw_amount = init_response.get("amount") try: return float(str(raw_amount)) diff --git a/pyfragment/utils/wallet.py b/pyfragment/utils/wallet.py index 74c3f06..2212690 100644 --- a/pyfragment/utils/wallet.py +++ b/pyfragment/utils/wallet.py @@ -49,13 +49,8 @@ async def _check_ton_payment_balance( balance_ton: float, amount_ton: float, required_payment_amount: float | None, - ton: Any, - wallet_address: str, ) -> None: """Validate balance requirements for TON payment method.""" - del ton - del wallet_address - tx_price_ton = amount_ton if required_payment_amount is not None and required_payment_amount > 0: tx_price_ton = max(tx_price_ton, required_payment_amount) @@ -72,14 +67,11 @@ async def _check_ton_payment_balance( async def _check_usdt_payment_balance( balance_ton: float, - amount_ton: float, required_payment_amount: float | None, ton: Any, wallet_address: str, ) -> None: """Validate balance requirements for USDT payment method.""" - del amount_ton - # USDT payment still needs TON for network fees. if balance_ton < MIN_TON_BALANCE: raise WalletError( @@ -134,18 +126,19 @@ async def process_transaction( await wallet.refresh() balance_ton = wallet.balance / 1_000_000_000 wallet_address = wallet.address.to_str(False, False) - checkers = { - "ton": _check_ton_payment_balance, - "usdt_ton": _check_usdt_payment_balance, - } - checker = checkers[payment_method] - await checker( - balance_ton, - amount_ton, - required_payment_amount, - ton, - wallet_address, - ) + if payment_method == "ton": + await _check_ton_payment_balance( + balance_ton, + amount_ton, + required_payment_amount, + ) + else: + await _check_usdt_payment_balance( + balance_ton, + required_payment_amount, + ton, + wallet_address, + ) except WalletError: raise except Exception as exc: diff --git a/tests/015_test_payment_amount.py b/tests/015_test_payment_amount.py index a202067..51eff63 100644 --- a/tests/015_test_payment_amount.py +++ b/tests/015_test_payment_amount.py @@ -5,7 +5,7 @@ from pyfragment.utils.html import parse_required_payment_amount def test_parse_required_payment_amount_ton_uses_amount() -> None: init_response = {"amount": "0.326"} - assert parse_required_payment_amount(init_response, "ton") == 0.326 + assert parse_required_payment_amount(init_response) == 0.326 def test_parse_required_payment_amount_usdt_uses_amount() -> None: @@ -13,9 +13,9 @@ def test_parse_required_payment_amount_usdt_uses_amount() -> None: "amount": "0.00075", "content": '0.75', } - assert parse_required_payment_amount(init_response, "usdt_ton") == 0.00075 + assert parse_required_payment_amount(init_response) == 0.00075 def test_parse_required_payment_amount_usdt_falls_back_to_amount() -> None: init_response = {"amount": "1.25", "content": "

no usd icon

"} - assert parse_required_payment_amount(init_response, "usdt_ton") == 1.25 + assert parse_required_payment_amount(init_response) == 1.25