/* MBEX — Create order and order status flow */ function NetworkChips() { return (
TRC-20
); } const BUY_PRESETS_RUB = [5000, 10000, 50000, 100000]; const SELL_PRESETS_USDT = [300, 1000, 5000, 10000]; const TRC20_RE = /^T[a-zA-Z0-9]{33}$/; function visitorFioValid(value) { return String(value || "").trim().split(/\s+/).filter(Boolean).length >= 2; } function cleanAmountInput(value) { return String(value || "").replace(/[^\d.,]/g, ""); } function parseAmountInput(value) { const parsed = Number(String(value || "").replace(/\s/g, "").replace(",", ".") || 0); return Number.isFinite(parsed) ? parsed : 0; } function CreateOrderScreen({ draft, app, onConfirm, nav }) { const profile = app.privateState.profile || {}; const wallets = app.privateState.wallets || []; const couriers = app.privateState.couriers || []; const referral = app.privateState.referral || {}; const telegramUser = window.MBEX.getTelegramUser() || {}; const defaultFio = profile.fio || `${telegramUser.last_name || ""} ${telegramUser.first_name || ""}`.trim(); const buying = draft.dir === "buy"; const selling = draft.dir === "sell"; const directionAvailable = buying || selling; const hasInitialAmount = draft.inputValue !== "" && draft.inputValue !== null && draft.inputValue !== undefined; const initialAmount = hasInitialAmount && buying ? String(Math.round(Number(draft.inputValue || 0))) : (hasInitialAmount ? String(draft.inputValue) : ""); const [amount, setAmount] = React.useState(initialAmount); const [fio, setFio] = React.useState(draft.fio || defaultFio); const [wallet, setWallet] = React.useState(wallets[0] ? wallets[0].address : ""); const [selectedWallet, setSelectedWallet] = React.useState(wallets[0] ? wallets[0].id : null); const [delivery, setDelivery] = React.useState("self"); const [courierFio, setCourierFio] = React.useState(couriers[0] ? couriers[0].fio : ""); const [selectedCourier, setSelectedCourier] = React.useState(couriers[0] ? couriers[0].id : null); const [agree, setAgree] = React.useState(false); const liveRate = buying ? app.publicState.rates.buy : app.publicState.rates.sell; const rate = Number(liveRate || draft.rate || 0); const amountNum = parseAmountInput(amount); const effectiveAmountNum = buying ? window.MBEX.snapRubStepAmount(amountNum, 1000) : amountNum; const networkFeeUsdt = Number(app.publicState.rates.commissionSettings?.network_fee_usdt || 0); const buyEstimate = buying ? window.MBEX.calculateBuyEstimate(effectiveAmountNum, rate, profile, referral, networkFeeUsdt) : (draft.buyEstimate || { walletUsdt: 0, networkFeeUsdt: 0, commissionUsdt: 0, grossUsdt: 0, commissionPct: 0 }); const sellEstimate = selling ? window.MBEX.calculateSellEstimate(effectiveAmountNum, rate, profile) : (draft.sellEstimate || { payoutRub: 0, commissionRub: 0, grossRub: 0, commissionPct: 0 }); const limits = app.publicState.rates.sellLimits || { min_usdt: 65, max_usdt: 100000 }; const minSell = Number(limits.min_usdt || 65); const maxSell = Number(limits.max_usdt || 100000); const buyEnabled = app.publicState.rates.tradeAvailability?.buy_enabled !== false; const sellEnabled = app.publicState.rates.tradeAvailability?.sell_enabled !== false; const kycVerified = Boolean(profile.verified); React.useEffect(() => { setFio(draft.fio || defaultFio); }, [defaultFio, draft.fio]); React.useEffect(() => { if (wallets.length && !wallet) { setWallet(wallets[0].address); setSelectedWallet(wallets[0].id); } }, [wallet, wallets]); React.useEffect(() => { if (couriers.length && !courierFio) { setCourierFio(couriers[0].fio || ""); setSelectedCourier(couriers[0].id); } }, [courierFio, couriers]); React.useEffect(() => { if (!kycVerified && delivery === "courier") { setDelivery("self"); } }, [delivery, kycVerified]); function onAmountChange(event) { setAmount(cleanAmountInput(event.target.value)); } function onAmountBlur() { if (!buying || amountNum <= 0) { return; } setAmount(String(window.MBEX.snapRubStepAmount(amountNum, 1000))); } async function pasteWallet() { if (!navigator.clipboard || !navigator.clipboard.readText) { app.pushToast("Вставьте адрес вручную", "error"); return; } try { const text = (await navigator.clipboard.readText()).trim(); if (!text) { app.pushToast("Буфер обмена пуст", "error"); return; } setWallet(text); setSelectedWallet(null); } catch (error) { app.pushToast("Не удалось прочитать буфер. Вставьте адрес вручную.", "error"); } } function submit() { if (!valid) { app.pushToast(validationMessage, "error"); return; } onConfirm({ dir: draft.dir, sumRub: effectiveAmountNum, sumUsdt: effectiveAmountNum, fio: fio.trim(), wallet: wallet.trim(), delivery, courierFio: delivery === "courier" ? courierFio.trim() : "", }); } const currentEnabled = buying ? buyEnabled : sellEnabled; const amountOk = buying ? amountNum > 0 && effectiveAmountNum >= 1000 : amountNum >= minSell && amountNum <= maxSell; const fioOk = visitorFioValid(fio); const walletOk = buying ? TRC20_RE.test(wallet.trim()) : true; const courierOk = delivery !== "courier" || (kycVerified && visitorFioValid(courierFio)); const valid = currentEnabled && amountOk && fioOk && walletOk && courierOk && agree; const validationMessage = !currentEnabled ? (buying ? "Покупка USDT временно недоступна" : "Продажа USDT временно недоступна") : !amountOk ? (buying ? "Минимальная сумма покупки — 1 000 ₽" : `Сумма продажи должна быть от ${window.MBEX.fmtUsdt(minSell)} до ${window.MBEX.fmtUsdt(maxSell)} USDT`) : !fioOk ? "Укажите фамилию и имя клиента" : !walletOk ? "Укажите корректный TRC-20 адрес: T + 33 символа" : !courierOk ? (!kycVerified ? "Чтобы оформить заявку на другого получателя, сначала пройдите KYC" : "Укажите фамилию и имя получателя") : !agree ? "Подтвердите правила заявки" : ""; const receiveText = buying ? `${window.MBEX.fmtUsdt(buyEstimate.walletUsdt || 0)} USDT` : `${window.MBEX.fmtRub(sellEstimate.payoutRub || 0)} ₽`; const summaryMode = buying ? "Покупка" : "Продажа"; const summaryMeta = `1 USDT = ${window.MBEX.fmtRub2(rate)} ₽`; const summaryPayText = buying ? `${window.MBEX.fmtRub(effectiveAmountNum)} ₽` : `${window.MBEX.fmtUsdt(effectiveAmountNum)} USDT`; if (!directionAvailable) { return (
} title="Сценарий недоступен" text="Создайте заявку на покупку или продажу USDT." />
); } return (
{summaryMode} {summaryMeta}
Вы платите {summaryPayText}
Вы получаете {receiveText}
{buying ? ( <>
Сумма покупки Минимум 1 000 ₽, шаг 500 ₽
RUB
{BUY_PRESETS_RUB.map((preset) => ( ))}
ФИО setFio(event.target.value)} />
{!kycVerified ? (
Чтобы оформить заявку на другого получателя, сначала пройдите KYC.
) : null} {delivery === "courier" ? (
ФИО получателя { setCourierFio(event.target.value); setSelectedCourier(null); }} /> {couriers.length ? (
{couriers.slice(0, 5).map((item) => ( ))}
) : null} {nav ? ( ) : null}
) : null}
Сеть получения
TRC-20 кошелёк для получения USDT
{ setWallet(event.target.value); setSelectedWallet(null); }} />
{wallets.length ? (
Сохранённые кошельки {nav ? : null}
{wallets.slice(0, 5).map((item) => (
{ setSelectedWallet(item.id); setWallet(item.address); }} >
{item.name}
{item.address}
))}
) : ( <> } title="Сохранённых кошельков нет" text="Вставьте TRC-20 адрес вручную. Сохранённые адреса появятся здесь после добавления в профиле." /> {nav ? ( ) : null} )}
setAgree((value) => !value)}> {agree ? : null}
Я проверил сумму, ФИО и TRC-20 адрес. Понимаю, что курс и комиссия фиксируются по правилам MBEX при обработке заявки.
) : ( <>
Сумма продажи
USDT
{SELL_PRESETS_USDT.map((preset) => ( ))}
ФИО setFio(event.target.value)} />
{!kycVerified ? (
Чтобы оформить заявку на другого получателя, сначала пройдите KYC.
) : null} {delivery === "courier" ? (
ФИО получателя RUB { setCourierFio(event.target.value); setSelectedCourier(null); }} /> {couriers.length ? (
{couriers.slice(0, 5).map((item) => ( ))}
) : null} {nav ? ( ) : null}
) : null}
Перевод USDT
Сумма перевода {window.MBEX.fmtUsdt(minSell)}-{window.MBEX.fmtUsdt(maxSell)} USDT
Куда отправить Адрес TRC-20 на 30 минут
Что дальше Проверим перевод и рассчитаем сумму в рублях
Отправьте USDT в сети TRC-20 на указанный адрес в течение 30 минут. Адрес создан только для этой заявки. После поступления перевода мы покажем точную сумму к получению в рублях.
setAgree((value) => !value)}> {agree ? : null}
Я понимаю, что нужно отправить USDT в сети TRC-20 в пределах указанных лимитов. Точная сумма к получению будет рассчитана после поступления перевода.
)}
{!valid ? (
{validationMessage}
) : null}
); } function refundStatusText(refund) { if (!refund) { return ""; } const amount = Number(refund.amount_usdt || 0); const amountText = amount > 0 ? `${window.MBEX.fmtUsdt(amount)} USDT` : "депозит"; if (refund.status === "pending_review") { return `Обращение создано. Оператор проверяет ${amountText}.`; } if (refund.status === "approved" || refund.status === "sweeping") { return "Возврат одобрен. Средства переводятся на возвратный кошелёк MBEX."; } if (refund.status === "awaiting_wallet") { return "Оператор одобрил возврат. Укажите TRC-20 кошелёк для получения."; } if (refund.status === "sent") { return "Возврат отправлен вручную. Подтвердите получение после проверки транзакции."; } if (refund.status === "confirmed") { return "Клиент подтвердил получение возврата."; } if (refund.status === "rejected") { return "Обращение отклонено оператором."; } return refund.status || "Статус возврата обновляется"; } function depositStatusText(status) { const labels = { waiting: "Ожидает USDT", aml_pending: "AML проверка", deposited: "USDT получены", swept: "Средства обработаны", refund_swept: "Готовится возврат", aml_failed: "AML разбор", limit_failed: "Сумма вне лимитов", expired: "Адрес истёк", }; return labels[status || "waiting"] || status || "Ожидает USDT"; } const DEPOSIT_COPY_NOTICE_SUPPRESS_KEY = "mbex_deposit_copy_notice_suppressed_until"; const DEPOSIT_COPY_NOTICE_SUPPRESS_MS = 7 * 24 * 60 * 60 * 1000; function isDepositCopyNoticeSuppressed() { try { return Number(localStorage.getItem(DEPOSIT_COPY_NOTICE_SUPPRESS_KEY)) > Date.now(); } catch { return false; } } function suppressDepositCopyNotice() { try { localStorage.setItem( DEPOSIT_COPY_NOTICE_SUPPRESS_KEY, String(Date.now() + DEPOSIT_COPY_NOTICE_SUPPRESS_MS), ); return true; } catch { return false; } } function DepositCopyNotice({ address, onClose }) { const [suppressForWeek, setSuppressForWeek] = React.useState(false); React.useEffect(() => { const handleKeyDown = (event) => { if (event.key === "Escape") { onClose(); } }; document.addEventListener("keydown", handleKeyDown); return () => document.removeEventListener("keydown", handleKeyDown); }, [onClose]); function handleConfirm() { if (suppressForWeek) { suppressDepositCopyNotice(); } onClose(); } return ReactDOM.createPortal(
{address} скопирован

Отправляйте USDT на этот адрес только через сеть TRC-20. Перевод через другую сеть может привести к тому, что депозит не будет зачислен.

, document.body, ); } function DetailValue({ label, value, copy, url, onCopied, wrapValue = false }) { if (value === null || value === undefined || value === "") { return null; } async function handleCopy(event) { if (!copy) { return; } event.stopPropagation(); const copied = await window.MBEX.copyText(value); if (copied && onCopied) { onCopied(value); } } return (
{label} {value} {url ? ( ) : null}
); } function OrderTimeline({ order, app }) { const items = window.MBEX.buildOrderTimeline(order, app.nowMs); const [expanded, setExpanded] = React.useState(false); const activeIndex = items.findIndex((item) => ["current", "warn", "error"].includes(item.state)); const currentIndex = activeIndex >= 0 ? activeIndex : Math.max(items.length - 1, 0); const currentItem = items[currentIndex]; const showNext = ( order.serverStatus !== "cancelled" && currentItem && !["warn", "error"].includes(currentItem.state) && currentIndex < items.length - 1 ); const compactItems = items .slice(currentIndex, currentIndex + (showNext ? 2 : 1)) .map((item, offset) => ({ item, index: currentIndex + offset })); const visibleItems = expanded ? items.map((item, index) => ({ item, index })) : compactItems; const canExpand = compactItems.length < items.length; React.useEffect(() => { setExpanded(false); }, [order.id]); return (
Этапы Этап {currentIndex + 1} из {items.length}
{window.MBEX.orderDirectionCode(order)}
{visibleItems.map(({ item, index }, visibleIndex) => (
{visibleIndex < visibleItems.length - 1 ?
: null}
{item.state === "done" ? : index + 1}
{item.title} {item.text}
))}
{canExpand ? ( ) : null}
); } function OrderDetailCard({ order, app, statusMeta, onDepositCopied }) { const buying = order.typeKey === "buy"; const selling = order.typeKey === "sell"; const transfer = order.typeKey === "transfer"; const tronscanBase = app.publicState.config?.tronscan_base; const route = [order.fromCity, order.toCity].filter(Boolean).join(" → "); const recipient = order.delivery === "courier" && order.courierFio ? order.courierFio : ""; const visitorPassLabels = { pending_create: "Оформляем", created: "Создан, статус уточняется", pending_approval: "На согласовании", active: "Действующий", prepared: "Оформлен", ready: "Готов к выдаче", used: "Отработан", rejected: "Отклонён", withdraw_pending: "Отзываем", withdrawn: "Отозван", expired: "Просрочен", replaced: "Заменён", manual_review: "Проверяет оператор", }; const amountRows = [ , , (buying || selling) ? : null, , , , order.sumUsdt > 0 ? : null, order.rate > 0 ? : null, order.commissionUsdt > 0 ? : null, buying ? : null, order.commissionDiscountPct > 0 ? : null, , recipient ? : null, order.visitorPassState ? : null, order.visitorPassVisitDate ? : null, order.visitorPassExternalId ? : null, route ? : null, ]; const walletRows = [ buying && order.wallet ? ( ) : null, selling && order.depositAddress ? ( ) : null, order.txHash ? ( ) : null, order.depositTxHash ? ( ) : null, ]; return (
Детали заявки
{amountRows} {walletRows}
); } function OrderStatusScreen({ order, app, onCancel, onRepeat }) { const [refundMessage, setRefundMessage] = React.useState(""); const [copiedDepositAddress, setCopiedDepositAddress] = React.useState(""); const [refundWallet, setRefundWallet] = React.useState(order?.refund?.client_wallet || ""); React.useEffect(() => { setRefundWallet(order?.refund?.client_wallet || ""); }, [order]); React.useEffect(() => { setCopiedDepositAddress(""); }, [order?.id]); if (!order) { return (
} title="Заявка не найдена" text="Обновите список заявок и попробуйте открыть статус ещё раз." />
); } const statusMeta = window.MBEX.getStatusMeta(order, app.nowMs); const secondsLeft = window.MBEX.getOrderSecondsLeft(order, app.nowMs); const buying = order.typeKey === "buy"; const selling = order.typeKey === "sell"; const transfer = order.typeKey === "transfer"; const StatusIcon = transfer ? Icons.swap : buying ? Icons.arrowDown : Icons.arrowUp; const canCancel = order.serverStatus === "pending"; const refund = order.refund || null; const refundEligible = window.MBEX.isRefundEligible(order); const depositReady = selling && Boolean(order.depositAddress); const depositStatus = order.depositStatus || "waiting"; function handleDepositCopied(address) { if (!isDepositCopyNoticeSuppressed()) { setCopiedDepositAddress(address); } } async function handleRefundRequest() { await app.requestRefund(order.id, refundMessage); } async function handleRefundWallet() { if (!refund) { return; } await app.submitRefundWallet(refund.id, refundWallet); } async function handleRefundConfirm() { if (!refund) { return; } await app.confirmRefund(refund.id); } return (
{window.MBEX.orderDirectionCode(order)}
{statusMeta.title}
{statusMeta.subtitle}
{!["completed", "cancelled"].includes(order.serverStatus) ? (
{secondsLeft > 0 ? window.MBEX.formatDuration(secondsLeft) : "00:00"}
) : null}
{transfer ? (
Маршрут перестановки
Откуда {order.fromCity || "уточняется"}
Куда {order.toCity || "уточняется"}
Сумма {window.MBEX.fmtRub(order.sumRub)} ₽
Менеджер обработает перестановку и обновит статус заявки. Если нужно уточнить маршрут или время, напишите в поддержку.
) : null} {selling ? ( <>
TRC-20 депозит {depositReady ? "Переведите USDT на этот адрес" : "Адрес ещё готовится"}
0 ? "pill-neg" : "wallet-timer-expired"}> {secondsLeft > 0 ? window.MBEX.formatDuration(secondsLeft) : "00:00"}
{depositReady ? ( ) : (
Backend создал заявку, но депозитный адрес пока не вернулся. Обновите статус или свяжитесь с поддержкой, если адрес не появился.
)}
Переводите только USDT TRC-20. Другие сети и суммы вне лимитов потребуют ручного разбора.
Статус депозита {depositStatusText(depositStatus)}
Получено {order.depositAmountReceived > 0 ? `${window.MBEX.fmtUsdt(order.depositAmountReceived)} USDT` : "0 USDT"}
К выдаче {order.depositSumRubCalculated > 0 ? `${window.MBEX.fmtRub(order.depositSumRubCalculated)} ₽` : "ждём расчёт"}
{order.depositTxHash ? ( ) : null}
) : null} {refund ? (
Возврат проблемного депозита продажи
{refundStatusText(refund)}
{refund.status === "awaiting_wallet" ? ( <> setRefundWallet(event.target.value)} /> ) : null} {refund.status === "sent" ? ( ) : null}
) : null} {refundEligible && !refund ? (
Оставить обращение на возврат