mirror of
https://github.com/bohd4nx/FragmentAPI.git
synced 2026-07-25 06:14:29 +00:00
feat: enhance payment processing with minimum balance checks and update documentation
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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))
|
||||
|
||||
+13
-20
@@ -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:
|
||||
|
||||
@@ -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": '<span class="icon-before icon-usd">0.75</span>',
|
||||
}
|
||||
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": "<p>no usd icon</p>"}
|
||||
assert parse_required_payment_amount(init_response, "usdt_ton") == 1.25
|
||||
assert parse_required_payment_amount(init_response) == 1.25
|
||||
|
||||
Reference in New Issue
Block a user