ÿØÿà JFIF    ÿÛ „  ( %"1!%)+...383,7(-.+  -+++--++++---+-+-----+---------------+---+-++7-----ÿÀ  ß â" ÿÄ     ÿÄ H    !1AQaq"‘¡2B±ÁÑð#R“Ò Tbr‚²á3csƒ’ÂñDS¢³$CÿÄ   ÿÄ %  !1AQa"23‘ÿÚ   ? ôÿ ¨pŸªáÿ —åYõõ\?àÒü©ŠÄï¨pŸªáÿ —åYõõ\?àÓü©ŠÄá 0Ÿªáÿ Ÿå[úƒ ú®ði~TÁbqÐ8OÕpÿ ƒOò¤Oè`–RÂáœá™êi€ßÉ< FtŸI“öÌ8úDf´°å}“¾œ6  öFá°y¥jñÇh†ˆ¢ã/ÃÐ:ªcÈ "Y¡ðÑl>ÿ ”ÏËte:qž\oäŠe÷󲍷˜HT4&ÿ ÓÐü6ö®¿øþßèô Ÿ•7Ñi’•j|“ñì>b…þS?*Óôÿ ÓÐü*h¥£ír¶ü UãS炟[AÐaè[ûª•õ&õj?†Éö+EzP—WeÒírJFt ‘BŒ†Ï‡%#tE Øz ¥OÛ«!1›üä±Í™%ºÍãö]°î(–:@<‹ŒÊö×òÆt¦ãº+‡¦%ÌÁ²h´OƒJŒtMÜ>ÀÜÊw3Y´•牋4ǍýʏTì>œú=Íwhyë,¾Ôò×õ¿ßÊa»«þˆѪQ|%6ž™A õ%:øj<>É—ÿ Å_ˆCbõ¥š±ý¯Ýƒï…¶|RëócÍf溪“t.СøTÿ *Ä¿-{†çàczůŽ_–^XþŒ±miB[X±d 1,é”zEù»& î9gœf™9Ð'.;—™i}!ôšåîqêÛ٤ёý£½ÆA–àôe"A$˝Úsäÿ ÷Û #°xŸëí(l »ý3—¥5m! rt`†0~'j2(]S¦¦kv,ÚÇ l¦øJA£Šƒ J3E8ÙiŽ:cÉžúeZ°€¯\®kÖ(79«Ž:¯X”¾³Š&¡* ….‰Ž(ÜíŸ2¥ª‡×Hi²TF¤ò[¨íÈRëÉ䢍mgÑ.Ÿ<öäS0í„ǹÁU´f#Vß;Õ–…P@3ío<ä-±»Ž.L|kªÀê›fÂ6@»eu‚|ÓaÞÆŸ…¨ááå>åŠ?cKü6ùTÍÆ”†sĤÚ;H2RÚ†õ\Ö·Ÿn'¾ ñ#ºI¤Å´%çÁ­‚â7›‹qT3Iï¨ÖÚ5I7Ë!ÅOóŸ¶øÝñØôת¦$Tcö‘[«Ö³šÒ';Aþ ¸èíg A2Z"i¸vdÄ÷.iõ®§)¿]¤À†–‡É&ä{V¶iŽ”.Ó×Õÿ û?h¬Mt–íª[ÿ Ñÿ ÌV(í}=ibÔ¡›¥¢±b Lô¥‡piη_Z<‡z§èŒ)iÖwiÇ 2hÙ3·=’d÷8éŽ1¦¸c¤µ€7›7Ø ð\á)} ¹fËí›pAÃL%âc2 í§æQz¿;T8sæ°qø)QFMð‰XŒÂ±N¢aF¨…8¯!U  Z©RÊ ÖPVÄÀÍin™Ì-GˆªÅËŠ›•zË}º±ŽÍFò¹}Uw×#ä5B¤{î}Ð<ÙD é©¤&‡ïDbàÁôMÁ." ¤‡ú*õ'VŽ|¼´Úgllº¼klz[Æüï÷Aób‡Eÿ dÑ»Xx9ÃÜ£ÁT/`¼¸vI±Ýµ·Ë‚“G³þ*Ÿû´r|*}<¨îºœ @¦mÄ’M¹”.œ«Y–|6ÏU¤jç¥ÕÞqO ˜kDÆÁ¨5ÿ š;ÐЦ¦€GÙk \ –Þ=â¼=SͧµªS°ÚÍpÜãQűÀõ¬?ÃÁ1Ñ•õZà?hóœ€ L¦l{Y*K˜Ù›zc˜–ˆâ ø+¾ ­-Ök¥%ùEÜA'}ˆ><ÊIè“bpÍ/qÞâvoX€w,\úªò6Z[XdÒæ­@Ö—€$òJí#é>'°Ú ôª˜<)4ryÙ£|óAÅn5žêŸyÒäMÝ2{"}‰–¤l÷ûWX\l¾Á¸góÉOÔ /óñB¤f¸çñ[.P˜ZsÊË*ßT܈§QN¢’¡¨§V¼(Üù*eÕ“”5T¨‹Âê¥FŒã½Dü[8'Ò¥a…Ú¶k7a *•›¼'Ò·\8¨ª\@\õ¢¦íq+DÙrmÎ…_ªæ»ŠÓœ¡¯’Ré9MÅ×D™lælffc+ŒÑ,ý™ÿ ¯þǤ=Å’Á7µ÷ÚÛ/“Ü€ñýã¼àí¾ÕÑ+ƒ,uµMâÀÄbm:ÒÎPæ{˜Gz[ƒ¯«® KHà`ߨŠéí¯P8Aq.C‰ à€kòpj´kN¶qô€…Õ,ÜNŠª-­{Zö’æû44‰sŽè‰îVíRœÕm" 6?³D9¡ÇTíÅꋇ`4«¸ÝÁô ï’ýorqКÇZ«x4Žâéþuïf¹µö[P ,Q£éaX±`PÉÍZ ¸äYúg üAx ’6Lê‚xÝÓ*äQ  Ï’¨hÍ =²,6ï#rÃ<¯–£»ƒ‹,–ê•€ aÛsñ'%Æ"®ÛüìBᝠHÚ3ß°©$“XnœÖ’î2ËTeûìxîß ¦å¿çÉ ðK§þ{‘t‚Ϋ¬jéîZ[ ”š7L¥4VÚCE×]m¤Øy”ä4-dz£œ§¸x.*ãÊÊ b÷•h:©‡¦s`BTÁRû¾g⻩‹jø sF¢àJøFl‘È•Xᓁà~*j¯ +(ÚÕ6-£¯÷GŠØy‚<Ç’.F‹Hœw(+)ÜÜâÈzÄäT§FߘãÏ;DmVœ3Àu@mÚüXÝü•3B¨òÌÁÛ<·ÃÜ z,Ì@õÅ·d2]ü8s÷IôÞ¯^Ç9¢u„~ëAŸï4«M? K]­ÅàPl@s_ p:°¬ZR”´›JC[CS.h‹ƒïËœ«Æ]–÷ó‚wR×k7X‰k›‘´ù¦=¡«‰¨¨Â')—71ó’c‡Ðúµ `é.{§p¹ój\Ž{1h{o±Ý=áUÊïGÖŒõ–-BÄm+AZX¶¡ ïHðæ¥JmÙ;…䡟ˆ¦ ° äšiÉg«$üMk5¤L“’çÊvïâï ,=f“"íἊ5ô¬x6{ɏžID0e¸vçmi'︧ºð9$ò¹÷*£’9ÿ ²TÔ…×>JV¥}Œ}$p[bÔ®*[jzS*8 ”·T›Í–ñUîƒwo$áè=LT™ç—~ô·¤ÈÚ$榍q‰„+´kFm)ž‹©i–ËqÞŠ‰à¶ü( ‚•§ •°ò·‡#5ª•µÊ﯅¡X¨šÁ*F#TXJÊ ušJVÍ&=iÄs1‚3•'fý§5Ñ<=[íÞ­ PÚ;ѱÌ_~Ä££8rÞ ²w;’hDT°>ÈG¬8Á²ÚzŽ®ò®qZcqJêäÞ-ö[ܘbň±çb“ж31²n×iƒðÕ;1¶þÉ ªX‰,ßqÏ$>•î íZ¥Z 1{ç൵+ƒÕµ¥°T$§K]á»Ûï*·¤tMI’ÂZbŽÕiÒ˜}bÓ0£ª5›¨ [5Ž^ÝœWøÂÝh° ¢OWun£¤5 a2Z.G2³YL]jåtì”ä ÁÓ‘%"©<Ôúʰsº UZvä‡ÄiÆÒM .÷V·™ø#kèýiíÌ–ª)µT[)BˆõÑ xB¾B€ÖT¨.¥~ð@VĶr#¸ü*åZNDŽH;âi ],©£öØpù(šºãö¼T.uCê•4@ÿ GÕÛ)Cx›®0ø#:ÏðFÒbR\(€€Ä®fã4Þ‰Fä¯HXƒÅ,†öEÑÔÜ]Öv²?tLÃvBY£ú6Êu5ÅAQ³1‘’¬x–HŒÐ‡ ^ ¸KwJôÖŽ5×CÚ¨vÜ«/B0$×k°=ðbÇ(Ï)w±A†Á† 11Í=èQšµ626ŒÜ/`G«µ<}—-Ö7KEHÈÉðóȤmݱû±·ø«Snmá=“䫚mݱŸ¡¶~ó·“äUóJæúòB|E LêŽy´jDÔ$G¢þÐñ7óR8ýÒ…Ç› WVe#·Ÿ p·Fx~•ݤF÷0Èÿ K¯æS<6’¡WШ; ´ÿ ¥Êø\Òuî†åÝ–VNœkÒ7oòX¨Á­Ø÷FÎÑä±g÷ÿ M~Çî=p,X´ ÝÌÚÅ‹’ÃjÖ.ØöÏñ qïQ¤ÓZE†° =6·]܈ s¸>v•Ž^Ý\wq9r‰Î\¸¡kURÒ$­*‹Nq?Þª*!sŠÆ:TU_u±T+øX¡ ®¹¡,ÄâÃBTsÜ$Ø›4m椴zÜK]’’›Pƒ @€#â˜`é¹=I‡fiV•Ôî“nRm+µFPOhÍ0B£ €+¬5c v•:P'ÒyÎ ‰V~‚Ó†ÖuókDoh$å\*ö%Ю=£«…aȼ½÷Û.-½VŒŠ¼'lyî±1¬3ó#ÞE¿ÔS¤gV£m›=§\û"—WU¤ÚǼÿ ÂnÁGŒÃ ‚õN D³õNÚíŒÕ;HôyÄÈ©P¹Ä{:?R‘Ô¨âF÷ø£bÅó® JS|‚R÷ivýáâ€Æé¡è³´IئÑT!§˜•ت‚¬â@q€wnïCWÄ@JU€ê¯m6]Ï:£âx'+ÒðXvÓ¦Úm=–´7œ $ì“B£~p%ÕŸUþ« N@¼üï~w˜ñø5®—'Ôe»¤5ã//€ž~‰Tþ›Å7•#¤× Íö pÄ$ùeåì*«ÓŠEØWEÈsßg ¦ûvžSsLpºÊW–âµEWöˬH; ™!CYõZ ÃÄf æ#1W. \uWâ\,\Çf j’<qTbên›Î[vxx£ë 'ö¨1›˜ÀM¼Pÿ H)ƒêêŒA7s,|F“ 꺸k³9Ìö*ç®;Ö!Ö$Eiž•¹ÒÚ†ýóéÝû¾ÕS®ó$’NÝäŸz¤5r¦ãÄÃD÷Üø!°ø‡Ô&@m™Ì^Ãä­d q5Lnÿ N;.6½·N|#ä"1Nƒx“ã<3('&ñßt  ~ªu”1Tb㫨9ê–›–bìd$ߣ=#ÕãÒmU¯eí$EFù5ýYô櫨æì™Ç—±ssM]·á¿0ÕåJRÓªîiƒ+O58ÖñªŠÒx" \µâá¨i’¤i —Ö ” M+M¤ë9‚‰A¦°Qõ¾ßøK~¼Ã‘g…Ö´~÷Ï[3GUœÒ½#…kàÔ®Ò”‰³·dWV‰IP‰Ú8u¹”E ÖqLj¾êÕCBš{A^Âß;–¨`¯¬ìö ˼ ×tìø.tƐm*n¨y4o&Àx¥n¦×î‡aupáÛj8¿m›è¶ã!o½;ß0y^ý×^EÑ¿ÒjzŒ­)vÚÑnÄL …^ªô× ‡—‚3k Îý­hï]içå–îÏ*÷ñþ»Ô CÒjøjÍznˆ´ ¹#b'Fô‹ ‰v¥'’à'T´ƒHýÍ%M‰ ƒ&ÆÇŒï1 ‘ –Þ ‰i¬s žR-Ÿ kЬá¬7:þ 0ŒÅÒÕ/aÙ¬ÃÝ#Úøœ ©aiVc‰. ¹¦ãµ” ›Yg¦›ÆÎýº°f³7ƒhá·¸­}&D9¡ÂsÉÙÞèŠõØàC™¨ñbFC|´Ü(ŸƒÚÒ-%»'a Ì¿)ËÇn¿úÿ ÞŽX…4ÊÅH^ôΑí@ù¹Eh¶“L8Çjù ¼ÎåVªóR©Ï5uà V4lZß®=€xÖŸ–ÑÈ ÷”¨°¾__yM1tÉ?uÆþIkÄgæ@þ[¢†°XÃJ£j·:nkÅ¢u ‘}âGzö­/IµèЬ¼48q¦F°ŽR¼=ûì{´¯RýicS ÕÛ íNtÍÙï£,w4rêì®»~x(©Uñ§#Ñ&œÕ¤>ÎåÍÓ9’Ö{9eV­[Öjâ²ãu]˜å2›qÑšÕJç0€sÄ|Êëè0튔bÁ>“{×_F`Ø©ºê:µä,v¤ðfc1±"«ÔÍän1#=· Âøv~H½ÐßA¾¿Ü€Óš]Õ; I¾÷ç‚Qi†î¹9ywÔKG˜áñ zQY—§ÃÕZ07§X‚ Áh;ÁM)iÌCH-¯T‘ë|A0{Ò½LÚ–TâÖkÜ’dÀ“rmm»”جPF³ÖcbE§T€ÒxKºû’Ó®7±²(\4ŽÃ¸Uu@j™yĵ;³µ!Á¢b.W¤=mõ´êµK k ¸K^ÜÛ#p*Ü14qkZç5ïë †°5Ï%ÍÛ<Õ¤×Ô¥ê†C Õ´¼ú$ƒÖ“”]Ù¬qÞÚ[4©ý!ûÏ—Áb쳐XµA¬â~`›Çr¸8ìùÝ䫦<>ä÷«?xs´ÇÑ /á;¹øüÊÈÙà{"@Žïzâ¬[âß‚ U_<ÇŸ½4èN˜ú61®qŠu ¦þF£»äJ_ˆÙÎ~ ÞAã–݄ϗrŠD;xTž‘ô`É«…suãO`?³à™ô Lý#Íc5öoæØ‚y´´÷«ZR§<&JÇ+éâô´€i!Àˆ0æAoàðLèÖ-2ŸõW.’t^–(KÁmHµV@xÜÇy®Ñø­â^:Ú3w· 7½¹°ñ¸â¹®:',«Mœ—n­Á+Ãbš LÈ‘ÄnRÓÅœ%¦²‰¨ùQ:¤f‚ "PÕtô¸…cæl…&˜Ú˜Ôkv‹ž+vŠ,=¢v­6—Xy*¥t£«<™:“aîϲ=¦6rO]XI¿Œ÷¤zÚ­›¶ 6÷”w\d ü~v®ˆÌk«^m<ÿ ¢‰Õ\)ùºŽ;… lîÙÅEŠ®cѾ@vnMÏ,¼“ñ•ŽBxðÃzãÇç%3ˆ"}Ù•Åî> BÉú;Ò]V+P˜F_´ßé> Øše|ï‡ÄOmFæÇ ãqÞ$/xÐx­z`ï9"œÜij‚!7.\Td…9M‡•iŽ‹¾‘50ÞŽn¥ß4ÉôO ¹*í^QêËÜÇÌ8=ާs‰'ÂëÙ«á%Pú[O †ÅP¯Vsް.‰,kc¶ ¬A9n˜XÎ-ÞšN["¹QÕ‰ƒMýÁߺXJæÍaLj¾×Ãmã¾ãÚ uñÒþåQô¦¥ /ÄUx:‚ÍÜ’ Đ©ØÝ3V¨‰ÕnÐ6ó*óúK­«…c ¯U òhsý­jóÔj#,ímŒRµ«lbïUTŒÑ8†Ä0œÏr`ð¡¬É Ї ë"À² ™ 6¥ f¶ ¢ÚoܱԷ-<Àî)†a¶ž'Ú»¨TXqØæ¶÷YÄHy˜9ÈIW­YÀuMFë ºÏ’AqÌ4·/Ú †ô'i$øä­=Ä Ý|öK×40è|È6p‘0§)o¥ctî§H+CA-“ xØ|ÐXАç l8íºð3Ø:³¤¬KX¯UÿÙ token = $token; global $database; $this->database = $database; // Путь к файлу состояний пользователей $this->statesFile = dirname(__FILE__) . '/user_states.json'; // Создаем файл если его нет if (!file_exists($this->statesFile)) { file_put_contents($this->statesFile, '{}'); } } // ✅ НОВЫЙ МЕТОД: Генерация скрытого ID заказа private function generateOrderHash($orderId) { // Создаем уникальный хеш на основе ID и секретного ключа $secretKey = $this->database->getSetting('order_hash_secret', 'default_secret_key_change_me'); $hash = substr(md5($orderId . $secretKey . date('Y-m-d')), 0, 8); return $orderId . $hash; } // ✅ НОВЫЙ МЕТОД: Извлечение реального ID из хеша private function extractOrderId($hashedId) { if (strlen($hashedId) < 9) { return false; } // Извлекаем предполагаемый ID (все символы кроме последних 8) $orderId = substr($hashedId, 0, -8); if (!is_numeric($orderId)) { return false; } // Проверяем что хеш совпадает $expectedHash = $this->generateOrderHash($orderId); if ($hashedId === $expectedHash) { return (int)$orderId; } return false; } // ✅ УЛУЧШЕННЫЙ метод getNextBtcWallet() в bot.php private function getNextBtcWallet() { $this->database->log('debug', 'Starting wallet rotation process'); // Проверяем есть ли метод getBtcWallets в Database if (!method_exists($this->database, 'getBtcWallets')) { $this->database->log('error', 'getBtcWallets method not found in Database class'); $systemWallet = $this->database->getSetting('system_btc_wallet'); $this->database->log('warning', 'Using system wallet as fallback', [ 'system_wallet' => $systemWallet, 'reason' => 'getBtcWallets method missing' ]); return $systemWallet; } // Получаем все активные BTC кошельки $wallets = $this->database->getBtcWallets(); $this->database->log('debug', 'Retrieved wallets from database', [ 'wallets_count' => count($wallets), 'wallets_data' => $wallets ]); if (empty($wallets)) { // Fallback на системный кошелек $systemWallet = $this->database->getSetting('system_btc_wallet'); $this->database->log('warning', 'No rotation wallets available, using system wallet', [ 'system_wallet' => $systemWallet, 'reason' => 'No active wallets in rotation' ]); return $systemWallet; } // Получаем текущий индекс ротации $currentIndex = (int)$this->database->getSetting('btc_wallet_rotation_index', 0); $this->database->log('debug', 'Current rotation state', [ 'current_index' => $currentIndex, 'total_wallets' => count($wallets) ]); // Проверяем что индекс в допустимых пределах if ($currentIndex >= count($wallets)) { $currentIndex = 0; $this->database->log('debug', 'Reset rotation index to 0 (was out of bounds)'); } $selectedWallet = $wallets[$currentIndex]; // Обновляем индекс для следующего использования $nextIndex = ($currentIndex + 1) % count($wallets); $updateResult = $this->database->setSetting('btc_wallet_rotation_index', $nextIndex); $this->database->log('info', 'BTC wallet selected from rotation', [ 'wallet_id' => $selectedWallet['id'], 'wallet_address' => $selectedWallet['address'], 'wallet_label' => $selectedWallet['label'] ?? 'No label', 'current_index' => $currentIndex, 'next_index' => $nextIndex, 'total_wallets' => count($wallets), 'index_update_success' => $updateResult ]); // Проверяем что адрес корректный if (empty($selectedWallet['address']) || strlen($selectedWallet['address']) < 10) { $this->database->log('error', 'Selected wallet has invalid address', [ 'wallet_data' => $selectedWallet ]); // Fallback на системный кошелек $systemWallet = $this->database->getSetting('system_btc_wallet'); $this->database->log('warning', 'Using system wallet due to invalid selected wallet', [ 'system_wallet' => $systemWallet ]); return $systemWallet; } return $selectedWallet['address']; } // Методы для работы с состояниями пользователей private function setUserState($userId, $state) { $states = $this->loadStates(); $states[$userId] = $state; $this->saveStates($states); $this->database->log('debug', 'User state set', [ 'user_id' => $userId, 'state' => $state, 'total_states' => count($states) ]); } private function getUserState($userId) { $states = $this->loadStates(); $state = $states[$userId] ?? null; $this->database->log('debug', 'User state get', [ 'user_id' => $userId, 'has_state' => $state !== null, 'state' => $state, 'total_states' => count($states) ]); return $state; } private function clearUserState($userId) { $states = $this->loadStates(); unset($states[$userId]); $this->saveStates($states); $this->database->log('debug', 'User state cleared', [ 'user_id' => $userId, 'remaining_states' => count($states) ]); } private function loadStates() { try { if (!file_exists($this->statesFile)) { return []; } $content = file_get_contents($this->statesFile); if ($content === false) { return []; } $states = json_decode($content, true); if (!is_array($states)) { return []; } // Очищаем старые состояния (старше 1 часа) $now = time(); foreach ($states as $userId => $state) { if (isset($state['timestamp']) && ($now - $state['timestamp']) > 3600) { unset($states[$userId]); } } return $states; } catch (Exception $e) { $this->database->log('error', 'Failed to load states: ' . $e->getMessage()); return []; } } private function saveStates($states) { try { // Добавляем timestamp ко всем состояниям $now = time(); foreach ($states as $userId => $state) { if (is_array($state)) { $states[$userId]['timestamp'] = $now; } } $content = json_encode($states, JSON_PRETTY_PRINT); $result = file_put_contents($this->statesFile, $content, LOCK_EX); if ($result === false) { $this->database->log('error', 'Failed to save states to file'); } return $result !== false; } catch (Exception $e) { $this->database->log('error', 'Failed to save states: ' . $e->getMessage()); return false; } } public function sendMessage($chatId, $text, $keyboard = null, $parseMode = 'HTML') { $data = [ 'chat_id' => $chatId, 'text' => $text, 'parse_mode' => $parseMode ]; if ($keyboard) { $data['reply_markup'] = json_encode($keyboard); } return $this->makeRequest('sendMessage', $data); } public function editMessage($chatId, $messageId, $text, $keyboard = null) { $data = [ 'chat_id' => $chatId, 'message_id' => $messageId, 'text' => $text, 'parse_mode' => 'HTML' ]; if ($keyboard) { $data['reply_markup'] = json_encode($keyboard); } return $this->makeRequest('editMessageText', $data); } public function answerCallbackQuery($callbackId, $text = '', $showAlert = false) { $data = [ 'callback_query_id' => $callbackId, 'text' => $text, 'show_alert' => $showAlert ]; return $this->makeRequest('answerCallbackQuery', $data); } private function makeRequest($method, $data) { $url = "https://api.telegram.org/bot{$this->token}/" . $method; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data)); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 30); $response = curl_exec($ch); $error = curl_error($ch); curl_close($ch); if ($error) { $this->database->log('error', 'Telegram API error: ' . $error, $data); return false; } return json_decode($response, true); } public function processUpdate($update) { try { if (isset($update['message'])) { $this->processMessage($update['message']); } elseif (isset($update['callback_query'])) { $this->processCallbackQuery($update['callback_query']); } } catch (Exception $e) { $this->database->log('error', 'Process update error: ' . $e->getMessage(), $update); } } // Исправленный метод processMessage для обработки реферальных ссылок private function processMessage($message) { $chatId = $message['chat']['id']; $text = $message['text'] ?? ''; $userId = $message['from']['id']; // Получаем или создаем пользователя $user = $this->database->getUserByTelegramId($userId); if (!$user) { $referrerId = null; // ИСПРАВЛЕНО: правильная обработка реферальной ссылки if (strpos($text, '/start ref_') === 0) { $referralLink = substr($text, 11); // Убираем "/start ref_" $referrerId = $this->database->getUserByReferralLink($referralLink); if ($referrerId) { $this->sendMessage($chatId, "🎉 Вы присоединились по реферальной ссылке! Получите бонус за первую покупку."); } } $userId = $this->database->createUser( $message['from']['id'], $message['from']['username'] ?? null, $message['from']['first_name'] ?? null, $message['from']['last_name'] ?? null, $referrerId ); $user = $this->database->getUserByTelegramId($message['from']['id']); // Приветственное сообщение для новых пользователей $welcomeKeyboard = [ 'inline_keyboard' => [ [ ['text' => '💰 Купить BTC', 'callback_data' => 'buy_btc'], ['text' => '💸 Продать BTC', 'callback_data' => 'sell_btc'] ], [['text' => '📞 Поддержка', 'callback_data' => 'support']] ] ]; $welcomeMsg = "👋 Добро пожаловать в OneTouch Change Bot!\n\n"; $welcomeMsg .= "🎯 Здесь вы можете:\n"; $welcomeMsg .= "• Покупать и продавать Bitcoin\n"; $welcomeMsg .= "• Получать выгодные курсы\n"; $welcomeMsg .= "• Зарабатывать на реферальной программе\n\n"; $welcomeMsg .= "💰 Ваш персональный BTC кошелек:\n"; $welcomeMsg .= "{$user['btc_wallet']}\n"; $welcomeMsg .= "💡 Можете использовать для покупок или указать другой\n\n"; $welcomeMsg .= "🚀 Начните с вашей первой операции!\n\n"; $welcomeMsg .= "❓ По всем вопросам обращайтесь в поддержку."; $this->sendMessage($chatId, $welcomeMsg, $welcomeKeyboard); } if ($user['is_blocked']) { $keyboard = [ 'inline_keyboard' => [ [['text' => '📞 Поддержка', 'callback_data' => 'support']] ] ]; $this->sendMessage($chatId, "❌ Ваш аккаунт заблокирован\n\nОбратитесь в поддержку для разблокировки.", $keyboard); return; } // Обработка команд if ($text === '/start') { $this->showMainMenu($chatId, $user); } elseif ($text === '/menu') { $this->showMainMenu($chatId, $user); } else { $this->processUserInput($chatId, $userId, $text, $user); } } private function processUserInput($chatId, $userId, $text, $user) { // Важно: $userId здесь - это telegram_id пользователя из сообщения $state = $this->getUserState($userId); $this->database->log('debug', 'Processing user input', [ 'telegram_id' => $userId, 'text' => $text, 'has_state' => $state !== null, 'state' => $state ]); // Обработка команд if ($text === '/start' || $text === '/menu' || $text === '🏠 Главное меню') { $this->showMainMenu($chatId, $user); return; } // Если есть активное состояние, обрабатываем ввод пользователя if ($state) { $this->processStateInput($chatId, $userId, $text, $state, $user); } else { // Если нет активного состояния, показываем главное меню с подсказками $keyboard = [ 'inline_keyboard' => [ [ ['text' => '💰 Купить BTC', 'callback_data' => 'buy_btc'], ['text' => '💸 Продать BTC', 'callback_data' => 'sell_btc'] ], [ ['text' => '👤 Профиль', 'callback_data' => 'profile'], ['text' => '📊 Заработок', 'callback_data' => 'referral_stats'] ], [['text' => '📞 Поддержка', 'callback_data' => 'support']] ] ]; $this->sendMessage($chatId, "❓ Используйте кнопки меню для навигации\n\nВыберите нужное действие:", $keyboard); } } // ✅ ОБНОВЛЕННЫЙ метод processCallbackQuery с поддержкой скрытых ID и новых команд private function processCallbackQuery($callback) { $chatId = $callback['message']['chat']['id']; $messageId = $callback['message']['message_id']; $data = $callback['data']; $userId = $callback['from']['id']; $user = $this->database->getUserByTelegramId($userId); if (!$user) { $this->answerCallbackQuery($callback['id'], 'Пользователь не найден'); return; } // Отвечаем на callback query чтобы убрать "загрузку" $this->answerCallbackQuery($callback['id']); $this->database->log('debug', 'Processing callback query', [ 'callback_data' => $data, 'user_telegram_id' => $userId, 'user_db_id' => $user['id'] ]); // ✅ НОВЫЕ обработчики для системы управления заказами if (strpos($data, 'view_order_') === 0) { $hashedOrderId = substr($data, 11); $orderId = $this->extractOrderId($hashedOrderId); if ($orderId) { $this->showOrderDetails($chatId, $messageId, $orderId, $user); } } elseif (strpos($data, 'show_payment_details_') === 0) { $hashedOrderId = substr($data, 21); $orderId = $this->extractOrderId($hashedOrderId); if ($orderId) { $this->showPaymentDetails($chatId, $messageId, $orderId, $user); } } elseif (strpos($data, 'refresh_order_') === 0) { $hashedOrderId = substr($data, 14); $orderId = $this->extractOrderId($hashedOrderId); if ($orderId) { $this->showOrderDetails($chatId, $messageId, $orderId, $user); } } elseif (strpos($data, 'copy_wallet_') === 0 || strpos($data, 'copy_address_') === 0) { $this->answerCallbackQuery($callback['id'], 'Адрес скопирован! Перейдите в чат для вставки', true); } // Специальные обработчики для операций с заказами elseif (strpos($data, 'pay_') === 0) { $this->processPayment($chatId, $messageId, $data, $userId, $user); } elseif (strpos($data, 'confirm_sell_') === 0) { $hashedOrderId = substr($data, 13); $orderId = $this->extractOrderId($hashedOrderId); if (!$orderId) { $this->database->log('error', 'Invalid hashed order ID in confirm_sell callback', [ 'callback_data' => $data, 'hashed_order_id' => $hashedOrderId, 'user_id' => $user['id'] ]); $this->editMessage($chatId, $messageId, "❌ Неверный ID заказа\n\nПопробуйте создать заказ заново.", [ 'inline_keyboard' => [ [['text' => '💸 Продать BTC', 'callback_data' => 'sell_btc']], [['text' => '🏠 Главное меню', 'callback_data' => 'main_menu']] ] ]); return; } $this->confirmSellOrder($chatId, $messageId, $orderId, $user); } elseif (strpos($data, 'confirm_payment_') === 0) { $hashedOrderId = substr($data, 16); $orderId = $this->extractOrderId($hashedOrderId); if ($orderId) { $this->confirmPayment($chatId, $messageId, $orderId, $user); } } elseif (strpos($data, 'cancel_order_') === 0) { $hashedOrderId = substr($data, 13); $orderId = $this->extractOrderId($hashedOrderId); if ($orderId) { $this->cancelOrder($chatId, $messageId, $orderId, $user); } } elseif ($data === 'cancel') { $this->cancelOperation($chatId, $messageId, $userId); } elseif ($data === 'withdraw') { $this->processWithdrawal($chatId, $messageId, $user); } elseif ($data === 'use_my_wallet') { $this->useMyWallet($chatId, $messageId, $userId, $user); } elseif ($data === 'edit_wallet') { $this->editWalletStep($chatId, $messageId, $userId, $user); } elseif ($data === 'edit_sell_wallet') { $this->editSellWalletStep($chatId, $messageId, $userId, $user); } // Основное меню else { switch ($data) { case 'buy_btc': $this->startBuyProcess($chatId, $user); break; case 'sell_btc': $this->startSellProcess($chatId, $user); break; case 'profile': $this->showProfile($chatId, $user); break; case 'referral_stats': $this->showReferralInfo($chatId, $user); break; case 'orders_history': $this->showOrdersHistory($chatId, $user); break; case 'support': $this->showSupport($chatId); break; case 'main_menu': $this->showMainMenu($chatId, $user); break; default: $this->database->log('warning', 'Unknown callback data', [ 'callback_data' => $data, 'user_id' => $user['id'] ]); break; } } } /** * ✅ ОБНОВЛЕННЫЙ метод showMainMenu с отображением резерва */ private function showMainMenu($chatId, $user) { $keyboard = [ 'inline_keyboard' => [ [ ['text' => '💰 Купить BTC', 'callback_data' => 'buy_btc'], ['text' => '💸 Продать BTC', 'callback_data' => 'sell_btc'] ], [ ['text' => '👤 Профиль', 'callback_data' => 'profile'], ['text' => '📊 Заработок', 'callback_data' => 'referral_stats'] ], [ ['text' => '📋 История заказов', 'callback_data' => 'orders_history'] ], [ ['text' => '📞 Поддержка', 'callback_data' => 'support'] ] ] ]; $btcRate = $this->getBtcRate(); $rateText = $btcRate ? number_format($btcRate, 0, ',', ' ') . " ₽" : "Недоступен"; // ✅ НОВОЕ: Получаем информацию о резерве $reserveInfo = $this->getBtcReserveInfo(); $welcomeText = "🎯 OneTouch Change Bot\n\n"; $welcomeText .= "📈 Текущий курс BTC: {$rateText}\n"; // ✅ НОВОЕ: Показываем резерв если включено if ($reserveInfo['display_enabled']) { $welcomeText .= "💎 Резерв обменника: " . number_format($reserveInfo['available_btc'], 4) . " BTC\n"; $welcomeText .= "🔒 Зарезервировано: " . number_format($reserveInfo['reserved_btc'], 4) . " BTC\n"; } $welcomeText .= "\nВыберите действие:\n\n"; $welcomeText .= "💰 Купить BTC - мгновенная покупка\n"; $welcomeText .= "💸 Продать BTC - быстрая продажа\n"; $welcomeText .= "👤 Профиль - ваш аккаунт\n"; $welcomeText .= "📊 Заработок - реферальная программа\n\n"; $welcomeText .= "🔄 Работаем 24/7\n"; $welcomeText .= "⚡ Обработка заказов до 30 минут"; $this->database->log('debug', 'Main menu shown with reserve info', [ 'user_id' => $user['telegram_id'], 'btc_rate' => $btcRate, 'reserve_displayed' => $reserveInfo['display_enabled'], 'available_btc' => $reserveInfo['available_btc'] ?? 0 ]); $this->sendMessage($chatId, $welcomeText, $keyboard); } /** * ✅ НОВОЕ: Получение информации о резерве для отображения */ private function getBtcReserveInfo() { try { $displayEnabled = $this->database->getSetting('btc_reserve_display_enabled', '1') === '1'; if (!$displayEnabled) { return ['display_enabled' => false]; } $reserve = $this->database->getBtcReserve(); return [ 'display_enabled' => true, 'total_btc' => $reserve['total_btc'], 'reserved_btc' => $reserve['reserved_btc'], 'available_btc' => $reserve['available_btc'], 'last_updated' => $reserve['last_updated'] ]; } catch (Exception $e) { $this->database->log('error', 'Failed to get reserve info for display: ' . $e->getMessage()); return ['display_enabled' => false]; } } /** * ✅ ОБНОВЛЕННЫЙ метод startBuyProcess с проверкой резерва */ private function startBuyProcess($chatId, $user) { $btcRate = $this->getBtcRate(); if (!$btcRate) { $keyboard = [ 'inline_keyboard' => [ [['text' => '🔄 Попробовать снова', 'callback_data' => 'buy_btc']], [['text' => '🏠 Главное меню', 'callback_data' => 'main_menu']] ] ]; $this->sendMessage($chatId, "❌ Не удалось получить курс BTC. Попробуйте позже.", $keyboard); return; } // ✅ НОВОЕ: Проверяем резерв $reserveInfo = $this->getBtcReserveInfo(); $reserveWarning = ''; if ($reserveInfo['display_enabled']) { $minThreshold = $this->database->getSetting('btc_reserve_min_threshold', '0.1'); if ($reserveInfo['available_btc'] <= 0) { $keyboard = [ 'inline_keyboard' => [ [['text' => '💸 Продать BTC', 'callback_data' => 'sell_btc']], [['text' => '📞 Поддержка', 'callback_data' => 'support']], [['text' => '🏠 Главное меню', 'callback_data' => 'main_menu']] ] ]; $this->sendMessage($chatId, "⚠️ Временно недоступно\n\nВ данный момент резерв обменника исчерпан.\nОбратитесь в поддержку или попробуйте позже.\n\n💡 Вы можете продать нам BTC!", $keyboard); return; } elseif ($reserveInfo['available_btc'] < $minThreshold) { $reserveWarning = "\n⚠️ Внимание: Ограниченный резерв - " . number_format($reserveInfo['available_btc'], 4) . " BTC\n"; } } $commission = $this->database->getSetting('buy_commission', 3); $minAmount = $this->database->getSetting('min_amount', 1000); $maxAmount = $this->database->getSetting('max_amount', 1000000); $keyboard = [ 'inline_keyboard' => [ [['text' => '❌ Отмена', 'callback_data' => 'main_menu']] ] ]; $text = "💰 Покупка BTC\n\n"; $text .= "📈 Текущий курс: " . number_format($btcRate, 0, ',', ' ') . " ₽/BTC\n"; $text .= "💸 Комиссия: {$commission}%\n"; // Показываем резерв если включено if ($reserveInfo['display_enabled']) { $text .= "💎 Доступно к покупке: " . number_format($reserveInfo['available_btc'], 4) . " BTC\n"; } $text .= "\n💵 Лимиты:\n"; $text .= "• Минимум: " . number_format($minAmount, 0, ',', ' ') . " ₽\n"; $text .= "• Максимум: " . number_format($maxAmount, 0, ',', ' ') . " ₽\n"; $text .= $reserveWarning; $text .= "\n📝 Введите сумму для покупки:\n"; $text .= "• В рублях (например: 5000)\n"; $text .= "• В BTC (например: 0.002)\n\n"; $text .= "💡 Отправьте сообщение с суммой"; $this->sendMessage($chatId, $text, $keyboard); $this->setUserState($user['telegram_id'], [ 'action' => 'buy_amount', 'btc_rate' => $btcRate, 'commission' => $commission, 'available_btc' => $reserveInfo['available_btc'] ?? 999999 // Большое число если резерв отключен ]); } private function startSellProcess($chatId, $user) { $btcRate = $this->getBtcRate(); if (!$btcRate) { $keyboard = [ 'inline_keyboard' => [ [['text' => '🔄 Попробовать снова', 'callback_data' => 'sell_btc']], [['text' => '🏠 Главное меню', 'callback_data' => 'main_menu']] ] ]; $this->sendMessage($chatId, "❌ Не удалось получить курс BTC. Попробуйте позже.", $keyboard); return; } $commission = $this->database->getSetting('sell_commission', 2); $minAmount = $this->database->getSetting('min_amount', 1000); $keyboard = [ 'inline_keyboard' => [ [['text' => '❌ Отмена', 'callback_data' => 'main_menu']] ] ]; $text = "💸 Продажа BTC\n\n"; $text .= "📉 Текущий курс: " . number_format($btcRate, 0, ',', ' ') . " ₽/BTC\n"; $text .= "💸 Комиссия: {$commission}%\n"; $text .= "💰 Минимальная сумма: " . number_format($minAmount, 0, ',', ' ') . " ₽\n\n"; $text .= "📝 Введите количество BTC для продажи:\n"; $text .= "• Например: 0.002 или 0,002\n\n"; $text .= "💡 Отправьте сообщение с количеством BTC"; $this->sendMessage($chatId, $text, $keyboard); // Сохраняем состояние с telegram_id пользователя (это важно!) $this->setUserState($user['telegram_id'], [ 'action' => 'sell_amount', 'btc_rate' => $btcRate, 'commission' => $commission ]); $this->database->log('debug', 'Sell process started, state saved', [ 'telegram_id' => $user['telegram_id'], 'action' => 'sell_amount' ]); } // ✅ ВОССТАНАВЛИВАЕМ processStateInput с buy_wallet private function processStateInput($chatId, $userId, $text, $state, $user) { switch ($state['action']) { case 'buy_amount': $this->processBuyAmount($chatId, $userId, $text, $state, $user); break; case 'buy_wallet': $this->processBuyWallet($chatId, $userId, $text, $state, $user); break; case 'sell_amount': $this->processSellAmount($chatId, $userId, $text, $state, $user); break; case 'sell_wallet': $this->processSellWallet($chatId, $userId, $text, $state, $user); break; } } /** * ✅ ОБНОВЛЕННЫЙ метод processBuyAmount с проверкой резерва */ private function processBuyAmount($chatId, $userId, $text, $state, $user) { $amount = $this->parseAmount($text); if (!$amount) { $keyboard = [ 'inline_keyboard' => [ [['text' => '❌ Отмена', 'callback_data' => 'main_menu']] ] ]; $this->sendMessage($chatId, "❌ Неверный формат суммы\n\nПримеры правильного ввода:\n• 5000 (рубли)\n• 0.002 (BTC)\n• 0,002 (BTC)", $keyboard); return; } $btcRate = $state['btc_rate']; $commission = $state['commission']; $availableBtc = $state['available_btc'] ?? 999999; $minAmount = $this->database->getSetting('min_amount', 1000); $maxAmount = $this->database->getSetting('max_amount', 1000000); // Определяем, что ввел пользователь - рубли или BTC if ($amount < 1) { // Пользователь ввел BTC $amountBtc = $amount; $amountRub = $amountBtc * $btcRate; } else { // Пользователь ввел рубли $amountRub = $amount; $amountBtc = $amountRub / $btcRate; } // ✅ НОВОЕ: Проверяем достаточность резерва if ($amountBtc > $availableBtc) { $maxAvailableRub = $availableBtc * $btcRate; $keyboard = [ 'inline_keyboard' => [ [['text' => '🔄 Ввести другую сумму', 'callback_data' => 'buy_btc']], [['text' => '📞 Поддержка', 'callback_data' => 'support']], [['text' => '❌ Отмена', 'callback_data' => 'main_menu']] ] ]; $text = "⚠️ Недостаточно BTC в резерве\n\n"; $text .= "💎 Доступно: " . number_format($availableBtc, 4) . " BTC\n"; $text .= "💰 Максимальная покупка: " . number_format($maxAvailableRub, 0, ',', ' ') . " ₽\n\n"; $text .= "📝 Введите меньшую сумму или обратитесь в поддержку"; $this->sendMessage($chatId, $text, $keyboard); return; } if ($amountRub < $minAmount || $amountRub > $maxAmount) { $keyboard = [ 'inline_keyboard' => [ [['text' => '🔄 Ввести другую сумму', 'callback_data' => 'buy_btc']], [['text' => '❌ Отмена', 'callback_data' => 'main_menu']] ] ]; $this->sendMessage($chatId, "❌ Сумма вне допустимых лимитов\n\n💵 Допустимая сумма: от " . number_format($minAmount, 0, ',', ' ') . " до " . number_format($maxAmount, 0, ',', ' ') . " ₽", $keyboard); return; } $commissionAmount = $amountRub * ($commission / 100); $finalAmount = $amountRub + $commissionAmount; $keyboard = [ 'inline_keyboard' => [ [['text' => '💳 Использовать мой кошелек', 'callback_data' => 'use_my_wallet']], [['text' => '❌ Отмена', 'callback_data' => 'main_menu']] ] ]; $text = "💰 Подтверждение покупки\n\n"; $text .= "📊 Детали операции:\n"; $text .= "• 💵 Сумма: " . number_format($amountRub, 2, ',', ' ') . " ₽\n"; $text .= "• ₿ К получению: " . number_format($amountBtc, 8) . " BTC\n"; $text .= "• 💸 Комиссия ({$commission}%): " . number_format($commissionAmount, 2, ',', ' ') . " ₽\n"; $text .= "• 💳 К оплате: " . number_format($finalAmount, 2, ',', ' ') . " ₽\n\n"; // ✅ НОВОЕ: Показываем статус резерва $remainingBtc = $availableBtc - $amountBtc; if ($remainingBtc < 0.001) { $text .= "⚠️ Внимание: Это последний доступный BTC в резерве\n\n"; } $text .= "📝 Введите BTC кошелек для получения:\n"; $text .= "💡 Пример: 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa\n\n"; $text .= "🔹 Ваш профильный кошелек:\n"; $text .= "{$user['btc_wallet']}"; $this->sendMessage($chatId, $text, $keyboard); $this->setUserState($user['telegram_id'], [ 'action' => 'buy_wallet', 'amount_btc' => $amountBtc, 'amount_rub' => $amountRub, 'commission' => $commission, 'final_amount' => $finalAmount, 'btc_rate' => $btcRate ]); } // ✅ ВОССТАНАВЛИВАЕМ метод processBuyWallet для ввода кошелька private function processBuyWallet($chatId, $userId, $text, $state, $user) { if (!$this->isValidBtcAddress($text)) { $keyboard = [ 'inline_keyboard' => [ [['text' => '💳 Использовать мой кошелек', 'callback_data' => 'use_my_wallet']], [['text' => '❌ Отмена', 'callback_data' => 'main_menu']] ] ]; $this->sendMessage($chatId, "❌ Неверный формат BTC адреса\n\nПримеры правильных адресов:\n• 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa\n• bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh\n\nПопробуйте еще раз:", $keyboard); return; } $keyboard = [ 'inline_keyboard' => [ [['text' => '💳 Банковская карта', 'callback_data' => 'pay_card']], [['text' => '📱 СБП (Система быстрых платежей)', 'callback_data' => 'pay_sbp']], [['text' => '❌ Отмена', 'callback_data' => 'cancel']] ] ]; $confirmText = "✅ Подтверждение заказа\n\n"; $confirmText .= "📋 Детали заказа:\n"; $confirmText .= "• ₿ BTC кошелек: {$text}\n"; $confirmText .= "• 💵 Сумма: " . number_format($state['amount_rub'], 2, ',', ' ') . " ₽\n"; $confirmText .= "• ₿ К получению: " . number_format($state['amount_btc'], 8) . " BTC\n"; $confirmText .= "• 💳 К оплате: " . number_format($state['final_amount'], 2, ',', ' ') . " ₽\n\n"; $confirmText .= "💳 Выберите способ оплаты:"; $this->sendMessage($chatId, $confirmText, $keyboard); $state['user_wallet'] = $text; $this->setUserState($user['telegram_id'], $state); } // ✅ НОВЫЙ метод для использования персонального кошелька private function useMyWallet($chatId, $messageId, $userId, $user) { $state = $this->getUserState($userId); if (!$state || $state['action'] !== 'buy_wallet') { $this->editMessage($chatId, $messageId, "❌ Сессия истекла. Начните заново.", [ 'inline_keyboard' => [ [['text' => '🏠 Главное меню', 'callback_data' => 'main_menu']] ] ]); return; } $userPersonalWallet = $user['btc_wallet']; if (empty($userPersonalWallet)) { $this->editMessage($chatId, $messageId, "❌ Ошибка\n\nВаш персональный кошелек не назначен. Обратитесь в поддержку.", [ 'inline_keyboard' => [ [['text' => '📞 Поддержка', 'callback_data' => 'support']], [['text' => '🏠 Главное меню', 'callback_data' => 'main_menu']] ] ]); return; } $keyboard = [ 'inline_keyboard' => [ [['text' => '💳 Банковская карта', 'callback_data' => 'pay_card']], [['text' => '📱 СБП (Система быстрых платежей)', 'callback_data' => 'pay_sbp']], [['text' => '❌ Отмена', 'callback_data' => 'cancel']] ] ]; $confirmText = "✅ Подтверждение заказа\n\n"; $confirmText .= "📋 Детали заказа:\n"; $confirmText .= "• ₿ BTC кошелек: {$userPersonalWallet}\n"; $confirmText .= "• 💵 Сумма: " . number_format($state['amount_rub'], 2, ',', ' ') . " ₽\n"; $confirmText .= "• ₿ К получению: " . number_format($state['amount_btc'], 8) . " BTC\n"; $confirmText .= "• 💳 К оплате: " . number_format($state['final_amount'], 2, ',', ' ') . " ₽\n\n"; $confirmText .= "💳 Выберите способ оплаты:"; $this->editMessage($chatId, $messageId, $confirmText, $keyboard); $state['user_wallet'] = $userPersonalWallet; $this->setUserState($user['telegram_id'], $state); } private function processSellAmount($chatId, $userId, $text, $state, $user) { $amountBtc = $this->parseAmount($text); if (!$amountBtc || $amountBtc <= 0) { $keyboard = [ 'inline_keyboard' => [ [['text' => '❌ Отмена', 'callback_data' => 'main_menu']] ] ]; $this->sendMessage($chatId, "❌ Неверный формат количества BTC\n\nПримеры правильного ввода:\n• 0.002\n• 0,002\n• 0.15\n\nПопробуйте еще раз:", $keyboard); return; } $btcRate = $state['btc_rate']; $commission = $state['commission']; $amountRub = $amountBtc * $btcRate; $minAmount = $this->database->getSetting('min_amount', 1000); if ($amountRub < $minAmount) { $minBtc = $minAmount / $btcRate; $keyboard = [ 'inline_keyboard' => [ [['text' => '🔄 Ввести другое количество', 'callback_data' => 'sell_btc']], [['text' => '❌ Отмена', 'callback_data' => 'main_menu']] ] ]; $this->sendMessage($chatId, "❌ Сумма меньше минимальной\n\n💰 Минимальная сумма: " . number_format($minAmount, 0, ',', ' ') . " ₽\n₿ Это примерно: " . number_format($minBtc, 8) . " BTC", $keyboard); return; } $commissionAmount = $amountRub * ($commission / 100); $finalAmount = $amountRub - $commissionAmount; $keyboard = [ 'inline_keyboard' => [ [['text' => '❌ Отмена', 'callback_data' => 'main_menu']] ] ]; $text = "💸 Подтверждение продажи\n\n"; $text .= "📊 Детали операции:\n"; $text .= "• ₿ Количество: " . number_format($amountBtc, 8) . " BTC\n"; $text .= "• 💵 Сумма: " . number_format($amountRub, 2, ',', ' ') . " ₽\n"; $text .= "• 💸 Комиссия ({$commission}%): " . number_format($commissionAmount, 2, ',', ' ') . " ₽\n"; $text .= "• 💰 К получению: " . number_format($finalAmount, 2, ',', ' ') . " ₽\n\n"; $text .= "📝 Введите реквизиты для получения денег:\n"; $text .= "• Номер карты: 1234 5678 9012 3456\n"; $text .= "• Номер телефона для СБП: +7 999 123 45 67\n"; $text .= "• Или другие платежные реквизиты\n\n"; $text .= "💡 Укажите полные реквизиты и ФИО получателя"; $this->sendMessage($chatId, $text, $keyboard); $this->setUserState($user['telegram_id'], [ 'action' => 'sell_wallet', 'amount_btc' => $amountBtc, 'amount_rub' => $amountRub, 'commission' => $commission, 'final_amount' => $finalAmount, 'btc_rate' => $btcRate ]); $this->database->log('debug', 'Sell wallet state saved', [ 'telegram_id' => $user['telegram_id'], 'action' => 'sell_wallet' ]); } // ✅ ИСПРАВЛЕННЫЙ метод processSellWallet в bot.php private function processSellWallet($chatId, $userId, $text, $state, $user) { // Валидация реквизитов (простая проверка) if (strlen(trim($text)) < 10) { $keyboard = [ 'inline_keyboard' => [ [['text' => '🔄 Изменить количество', 'callback_data' => 'sell_btc']], [['text' => '❌ Отмена', 'callback_data' => 'main_menu']] ] ]; $this->sendMessage($chatId, "❌ Реквизиты слишком короткие\n\nУкажите полные реквизиты:\n• Номер карты или телефона\n• ФИО получателя\n• Банк (для карты)", $keyboard); return; } // Сразу создаем заказ после ввода реквизитов $state['payment_details'] = $text; $this->setUserState($user['telegram_id'], $state); // Сразу создаем заказ $orderId = $this->database->createOrder( $user['id'], // user_id 'sell', // type $state['amount_btc'], // amount_btc $state['amount_rub'], // amount_rub $state['commission'], // commission $state['final_amount'], // final_amount $state['payment_details'], // userWallet (реквизиты) 'bank_transfer', // payment_method $state['btc_rate'] // btc_rate ); $this->database->log('info', 'Order creation in processSellWallet', [ 'order_id' => $orderId, 'order_id_type' => gettype($orderId), 'user_db_id' => $user['id'], 'user_telegram_id' => $user['telegram_id'] ]); if (!$orderId || !is_numeric($orderId) || $orderId <= 0) { $this->database->log('error', 'Order creation failed in processSellWallet', [ 'returned_value' => $orderId, 'user_id' => $user['id'] ]); $keyboard = [ 'inline_keyboard' => [ [['text' => '🔄 Попробовать снова', 'callback_data' => 'sell_btc']], [['text' => '❌ Отмена', 'callback_data' => 'main_menu']] ] ]; $this->sendMessage($chatId, "❌ Ошибка создания заказа\n\nПопробуйте позже или обратитесь в поддержку.", $keyboard); return; } $orderId = (int)$orderId; // Проверяем что заказ создался $createdOrder = $this->database->getOrder($orderId); if (!$createdOrder) { $this->database->log('error', 'Order verification failed in processSellWallet', [ 'order_id' => $orderId, 'user_id' => $user['id'] ]); $keyboard = [ 'inline_keyboard' => [ [['text' => '🔄 Попробовать снова', 'callback_data' => 'sell_btc']], [['text' => '❌ Отмена', 'callback_data' => 'main_menu']] ] ]; $this->sendMessage($chatId, "❌ Ошибка верификации заказа\n\nПопробуйте позже.", $keyboard); return; } // ✅ ИСПРАВЛЕНО: Получаем следующий BTC кошелек из ротации $assignedWallet = $this->getNextBtcWallet(); // Логирование для отладки $this->database->log('debug', 'BTC wallet from rotation', [ 'wallet' => $assignedWallet, 'wallet_length' => strlen($assignedWallet), 'order_id' => $orderId ]); if (!$assignedWallet || strlen(trim($assignedWallet)) < 10) { $this->database->log('error', 'No BTC wallet available from rotation', [ 'wallet' => $assignedWallet, 'order_id' => $orderId ]); $keyboard = [ 'inline_keyboard' => [ [['text' => '📞 Поддержка', 'callback_data' => 'support']], [['text' => '🏠 Главное меню', 'callback_data' => 'main_menu']] ] ]; $this->sendMessage($chatId, "❌ Временные технические работы\n\n🔧 BTC кошельки не настроены.\nОбратитесь в поддержку.", $keyboard); return; } // ✅ НОВОЕ: Обновляем assigned_wallet в заказе $this->database->updateOrderWallet($orderId, $assignedWallet); // ✅ НОВОЕ: Генерируем скрытый ID заказа $hashedOrderId = $this->generateOrderHash($orderId); // Создаем правильные кнопки с хешированным ID заказа $keyboard = [ 'inline_keyboard' => [ [['text' => '✅ Я отправил BTC', 'callback_data' => "confirm_sell_{$hashedOrderId}"]], [['text' => '❌ Отменить заказ', 'callback_data' => "cancel_order_{$hashedOrderId}"]] ] ]; $this->database->log('debug', 'Generated callback buttons with hashed ID', [ 'real_order_id' => $orderId, 'hashed_order_id' => $hashedOrderId, 'confirm_callback' => "confirm_sell_{$hashedOrderId}", 'cancel_callback' => "cancel_order_{$hashedOrderId}", 'assigned_wallet' => $assignedWallet ]); $orderText = "🔄 Заказ #{$hashedOrderId} создан\n\n"; $orderText .= "📋 Инструкция по переводу:\n"; $orderText .= "1️⃣ Переведите " . number_format($state['amount_btc'], 8) . " BTC на адрес:\n\n"; $orderText .= "{$assignedWallet}\n\n"; $orderText .= "2️⃣ После подтверждения транзакции нажмите кнопку ниже\n\n"; $orderText .= "⚠️ ВАЖНО!\n"; $orderText .= "• Переводите точно указанную сумму\n"; $orderText .= "• Сохраните хеш транзакции\n"; $orderText .= "• Время на перевод: 60 минут\n\n"; $orderText .= "💰 К получению: " . number_format($state['final_amount'], 2, ',', ' ') . " ₽\n"; $orderText .= "📋 Реквизиты:\n" . htmlspecialchars($text) . ""; $this->sendMessage($chatId, $orderText, $keyboard); $this->clearUserState($user['telegram_id']); // Отправляем дополнительное сообщение с навигацией $navKeyboard = [ 'inline_keyboard' => [ [ ['text' => '📋 Мои заказы', 'callback_data' => 'orders_history'], ['text' => '👤 Профиль', 'callback_data' => 'profile'] ], [['text' => '🏠 Главное меню', 'callback_data' => 'main_menu']] ] ]; $navText = "📱 Полезные ссылки:\n\n"; $navText .= "• Мои заказы - отслеживание статуса\n"; $navText .= "• Профиль - ваш баланс и статистика\n"; $navText .= "• Главное меню - вернуться к началу"; $this->sendMessage($chatId, $navText, $navKeyboard); // Уведомление админам $adminText = "💸 Новая заявка на продажу #{$hashedOrderId}\n\n"; $adminText .= "👤 Пользователь: " . ($user['username'] ? "@{$user['username']}" : "ID {$user['id']}") . "\n"; $adminText .= "₿ Количество: " . number_format($state['amount_btc'], 8) . " BTC\n"; $adminText .= "💵 Сумма: " . number_format($state['amount_rub'], 2, ',', ' ') . " ₽\n"; $adminText .= "💰 К выплате: " . number_format($state['final_amount'], 2, ',', ' ') . " ₽\n"; $adminText .= "🏦 Назначенный кошелек: {$assignedWallet}\n"; $adminText .= "📋 Реквизиты получателя:\n{$text}\n\n"; $adminText .= "⏳ Ожидается поступление BTC\n"; $adminText .= "🔗 Реальный ID: {$orderId}"; $this->notifyAdmins($adminText); } // ✅ ИСПРАВЛЕННЫЙ processPayment метод private function processPayment($chatId, $messageId, $paymentType, $userId, $user) { $state = $this->getUserState($userId); if (!$state || $state['action'] !== 'buy_wallet') { // ✅ ИСПРАВЛЕНО: возвращаем buy_wallet $this->editMessage($chatId, $messageId, "❌ Сессия истекла\n\nНачните операцию заново.", [ 'inline_keyboard' => [ [['text' => '🏠 Главное меню', 'callback_data' => 'main_menu']] ] ]); return; } $type = $paymentType === 'pay_card' ? 'card' : 'sbp'; $paymentDetails = $this->database->getActivePaymentDetails($type); if (!$paymentDetails) { $this->editMessage($chatId, $messageId, "❌ Способ оплаты временно недоступен\n\nПопробуйте другой способ или обратитесь в поддержку.", [ 'inline_keyboard' => [ [['text' => '🔄 Попробовать снова', 'callback_data' => 'buy_btc']], [['text' => '🏠 Главное меню', 'callback_data' => 'main_menu']] ] ]); return; } // Создаем заказ с указанным пользователем кошельком $orderId = $this->database->createOrder( $user['id'], 'buy', $state['amount_btc'], $state['amount_rub'], $state['commission'], $state['final_amount'], $state['user_wallet'], // Кошелек который указал пользователь $type, $state['btc_rate'] ); if (!$orderId) { $this->editMessage($chatId, $messageId, "❌ Ошибка создания заказа\n\nПопробуйте позже или обратитесь в поддержку.", [ 'inline_keyboard' => [ [['text' => '🏠 Главное меню', 'callback_data' => 'main_menu']] ] ]); return; } // Генерируем скрытый ID заказа $hashedOrderId = $this->generateOrderHash($orderId); $keyboard = [ 'inline_keyboard' => [ [['text' => '✅ Я оплатил', 'callback_data' => "confirm_payment_{$hashedOrderId}"]], [['text' => '❌ Отменить заказ', 'callback_data' => "cancel_order_{$hashedOrderId}"]] ] ]; $paymentIcon = $type === 'card' ? '💳' : '📱'; $paymentName = $type === 'card' ? 'Банковская карта' : 'СБП'; $paymentText = "{$paymentIcon} Реквизиты для оплаты\n\n"; $paymentText .= "🔢 Заказ: #{$hashedOrderId}\n"; $paymentText .= "💰 К оплате: " . number_format($state['final_amount'], 2, ',', ' ') . " ₽\n"; $paymentText .= "💳 Способ: {$paymentName}\n\n"; $paymentText .= "📋 Реквизиты:\n"; $paymentText .= $paymentDetails['details'] . "\n\n"; $paymentText .= "⚠️ ВАЖНО!\n"; $paymentText .= "• Переводите точную сумму: " . number_format($state['final_amount'], 2, ',', ' ') . " ₽\n"; $paymentText .= "• Сохраните чек об оплате\n"; $paymentText .= "• Время на оплату: 60 минут\n"; $paymentText .= "• После оплаты нажмите кнопку ниже\n\n"; $paymentText .= "💎 BTC поступят на кошелек:\n"; $paymentText .= "{$state['user_wallet']}"; $this->editMessage($chatId, $messageId, $paymentText, $keyboard); $this->clearUserState($user['telegram_id']); // Уведомление админам о новом заказе $adminText = "💰 Новый заказ на покупку #{$hashedOrderId}\n\n"; $adminText .= "👤 Пользователь: " . ($user['username'] ? "@{$user['username']}" : "ID {$user['id']}") . "\n"; $adminText .= "💵 Сумма: " . number_format($state['amount_rub'], 2, ',', ' ') . " ₽\n"; $adminText .= "₿ К выдаче: " . number_format($state['amount_btc'], 8) . " BTC\n"; $adminText .= "💳 К оплате: " . number_format($state['final_amount'], 2, ',', ' ') . " ₽\n"; $adminText .= "💳 Способ: {$paymentName}\n"; $adminText .= "🏦 Кошелек клиента: {$state['user_wallet']}\n\n"; $adminText .= "⏳ Ожидается оплата от пользователя\n"; $adminText .= "🔗 Реальный ID: {$orderId}"; $this->notifyAdmins($adminText); } private function notifyAdmins($message) { $adminChatId = $this->database->getSetting('admin_chat_id'); if ($adminChatId) { $this->sendMessage($adminChatId, $message); } } private function confirmPayment($chatId, $messageId, $orderId, $user) { $order = $this->database->getOrder($orderId); if (!$order || $order['user_id'] !== $user['id']) { $this->sendMessage($chatId, "❌ Заказ не найден."); return; } if ($order['status'] !== 'created') { $this->sendMessage($chatId, "❌ Заказ уже обработан."); return; } $this->database->updateOrderStatus($orderId, 'paid'); // ✅ НОВОЕ: Генерируем скрытый ID для отображения $hashedOrderId = $this->generateOrderHash($orderId); $keyboard = [ 'inline_keyboard' => [ [ ['text' => '📋 Мои заказы', 'callback_data' => 'orders_history'], ['text' => '👤 Профиль', 'callback_data' => 'profile'] ], [['text' => '🏠 Главное меню', 'callback_data' => 'main_menu']] ] ]; $text = "✅ Заказ #{$hashedOrderId} принят к обработке\n\n"; $text .= "Ваша оплата зафиксирована в системе.\n"; $text .= "После подтверждения поступления средств BTC будут переведены на указанный кошелек.\n\n"; $text .= "⏰ Время обработки: до 30 минут\n\n"; $text .= "📋 Детали заказа:\n"; $text .= "• Сумма: " . number_format($order['amount_rub'], 2, ',', ' ') . " ₽\n"; $text .= "• К получению: " . number_format($order['amount_btc'], 8) . " BTC\n"; $text .= "• Кошелек: {$order['user_wallet']}\n\n"; $text .= "💡 Что дальше?\n"; $text .= "• Отслеживайте статус в разделе \"Мои заказы\"\n"; $text .= "• Получите уведомление о завершении\n"; $text .= "• При вопросах обращайтесь в поддержку"; $this->editMessage($chatId, $messageId, $text, $keyboard); // Уведомление админам $adminText = "💰 Новая оплаченная заявка #{$hashedOrderId}\n\n"; $adminText .= "👤 Пользователь: " . ($user['username'] ? "@{$user['username']}" : "ID {$user['id']}") . "\n"; $adminText .= "💵 Сумма: " . number_format($order['amount_rub'], 2, ',', ' ') . " ₽\n"; $adminText .= "₿ К выдаче: " . number_format($order['amount_btc'], 8) . " BTC\n"; $adminText .= "💳 Способ оплаты: " . ($order['payment_method'] === 'card' ? 'Карта' : 'СБП') . "\n"; $adminText .= "🏦 Кошелек: {$order['user_wallet']}\n\n"; $adminText .= "⚠️ Требуется проверка поступления оплаты!\n"; $adminText .= "🔗 Реальный ID: {$orderId}"; $this->notifyAdmins($adminText); // Начисляем реферальный бонус если есть реферер if ($user['referrer_id'] && $order['type'] === 'buy') { $referralPercent = $this->database->getSetting('referral_percent', 1); $referralAmount = $order['amount_rub'] * ($referralPercent / 100); $this->database->addReferralEarning($user['referrer_id'], $user['id'], $orderId, $referralAmount); } } // ✅ ОБНОВЛЕННЫЙ метод confirmSellOrder с поддержкой скрытых ID private function confirmSellOrder($chatId, $messageId, $orderId, $user) { $this->database->log('debug', 'confirmSellOrder called with parameters', [ 'order_id' => $orderId, 'order_id_type' => gettype($orderId), 'user_db_id' => $user['id'], 'user_telegram_id' => $user['telegram_id'] ]); // Проверяем что orderId является числом if (!is_numeric($orderId) || $orderId <= 0) { $this->database->log('error', 'Invalid order ID in confirmSellOrder', [ 'order_id' => $orderId, 'order_id_type' => gettype($orderId), 'user_id' => $user['id'] ]); $this->editMessage($chatId, $messageId, "❌ Неверный ID заказа\n\nПопробуйте создать заказ заново.", [ 'inline_keyboard' => [ [['text' => '💸 Продать BTC', 'callback_data' => 'sell_btc']], [['text' => '🏠 Главное меню', 'callback_data' => 'main_menu']] ] ]); return; } $order = $this->database->getOrder($orderId); $this->database->log('debug', 'Order lookup result in confirmSellOrder', [ 'order_id' => $orderId, 'order_found' => $order !== false, 'order_data' => $order ? [ 'id' => $order['id'], 'user_id' => $order['user_id'], 'type' => $order['type'], 'status' => $order['status'] ] : null, 'current_user_id' => $user['id'] ]); if (!$order) { $this->database->log('error', 'Order not found in confirmSellOrder', [ 'order_id' => $orderId, 'user_id' => $user['id'] ]); $this->editMessage($chatId, $messageId, "❌ Заказ не найден\n\n🔍 ID заказа: {$orderId}\nВозможно, заказ был удален из системы.", [ 'inline_keyboard' => [ [['text' => '💸 Продать BTC', 'callback_data' => 'sell_btc']], [['text' => '🏠 Главное меню', 'callback_data' => 'main_menu']] ] ]); return; } if ($order['user_id'] !== $user['id']) { $this->database->log('warning', 'Order ownership mismatch in confirmSellOrder', [ 'order_id' => $orderId, 'order_user_id' => $order['user_id'], 'current_user_id' => $user['id'] ]); $this->editMessage($chatId, $messageId, "❌ Заказ не найден\n\nВозможно, заказ не принадлежит вам.", [ 'inline_keyboard' => [ [['text' => '🏠 Главное меню', 'callback_data' => 'main_menu']] ] ]); return; } if ($order['status'] !== 'created') { $statusName = $this->getOrderStatusName($order['status']); $this->editMessage($chatId, $messageId, "❌ Заказ уже обработан\n\nТекущий статус: {$statusName}", [ 'inline_keyboard' => [ [['text' => '📋 Мои заказы', 'callback_data' => 'orders_history']], [['text' => '🏠 Главное меню', 'callback_data' => 'main_menu']] ] ]); return; } $this->database->updateOrderStatus($orderId, 'paid'); // ✅ НОВОЕ: Генерируем скрытый ID для отображения $hashedOrderId = $this->generateOrderHash($orderId); $text = "✅ Заказ #{$hashedOrderId} принят к обработке\n\n"; $text .= "Спасибо! Ваша заявка передана на проверку.\n\n"; $text .= "📋 Что происходит дальше:\n"; $text .= "1️⃣ Проверяем поступление BTC на наш кошелек\n"; $text .= "2️⃣ Подтверждаем транзакцию в блокчейне\n"; $text .= "3️⃣ Переводим деньги на ваши реквизиты\n\n"; $text .= "⏰ Время обработки: до 30 минут\n"; $text .= "💰 К получению: " . number_format($order['final_amount'], 2, ',', ' ') . " ₽\n\n"; $text .= "📩 Вы получите уведомление о смене статуса заказа"; $keyboard = [ 'inline_keyboard' => [ [['text' => '📋 Мои заказы', 'callback_data' => 'orders_history']], [['text' => '🏠 Главное меню', 'callback_data' => 'main_menu']] ] ]; $this->editMessage($chatId, $messageId, $text, $keyboard); // Уведомление админам $adminText = "✅ Пользователь подтвердил отправку BTC\n\n"; $adminText .= "🔢 Заказ: #{$hashedOrderId}\n"; $adminText .= "👤 Пользователь: " . ($user['username'] ? "@{$user['username']}" : "ID {$user['id']}") . "\n"; $adminText .= "₿ Ожидается: " . number_format($order['amount_btc'], 8) . " BTC\n"; $adminText .= "💰 К выплате: " . number_format($order['final_amount'], 2, ',', ' ') . " ₽\n\n"; $adminText .= "🔍 Требуется проверка поступления BTC!\n"; $adminText .= "🔗 Реальный ID: {$orderId}"; $this->notifyAdmins($adminText); } // ✅ НОВЫЙ метод для просмотра детального заказа private function showOrderDetails($chatId, $messageId, $orderId, $user) { $order = $this->database->getOrder($orderId); if (!$order || $order['user_id'] !== $user['id']) { $this->editMessage($chatId, $messageId, "❌ Заказ не найден", [ 'inline_keyboard' => [ [['text' => '🏠 Главное меню', 'callback_data' => 'main_menu']] ] ]); return; } $hashedOrderId = $this->generateOrderHash($order['id']); $statusEmoji = $this->getOrderStatusEmoji($order['status']); $statusName = $this->getOrderStatusName($order['status']); $typeEmoji = $order['type'] === 'buy' ? '💰' : '💸'; $typeName = $order['type'] === 'buy' ? 'Покупка' : 'Продажа'; // Формируем основной текст $text = "{$typeEmoji} Заказ #{$hashedOrderId}\n\n"; $text .= "📊 Статус: {$statusEmoji} {$statusName}\n"; $text .= "🕐 Создан: " . date('d.m.Y H:i', strtotime($order['created_at'])) . "\n\n"; $text .= "📋 Детали операции:\n"; $text .= "• 💵 Сумма: " . number_format($order['amount_rub'], 2, ',', ' ') . " ₽\n"; $text .= "• ₿ Количество: " . number_format($order['amount_btc'], 8) . " BTC\n"; $text .= "• 💰 Итого: " . number_format($order['final_amount'], 2, ',', ' ') . " ₽\n"; $text .= "• 📈 Курс: " . number_format($order['btc_rate'], 0, ',', ' ') . " ₽/BTC\n\n"; // Дополнительная информация в зависимости от типа заказа if ($order['type'] === 'buy') { $text .= "💎 BTC кошелек получателя:\n"; $text .= "{$order['user_wallet']}\n\n"; if ($order['payment_method']) { $paymentName = $order['payment_method'] === 'card' ? 'Банковская карта' : 'СБП'; $text .= "💳 Способ оплаты: {$paymentName}\n\n"; } } else { if ($order['assigned_wallet']) { $text .= "📨 BTC адрес для отправки:\n"; $text .= "{$order['assigned_wallet']}\n\n"; } if ($order['payment_details']) { $text .= "💳 Ваши реквизиты для получения:\n"; $text .= "" . htmlspecialchars($order['payment_details']) . "\n\n"; } } // Статус-специфичная информация $text .= $this->getStatusSpecificInfo($order); // Формируем клавиатуру в зависимости от статуса $keyboard = $this->getOrderActionKeyboard($order, $hashedOrderId); $this->editMessage($chatId, $messageId, $text, $keyboard); } // ✅ НОВЫЙ метод для получения статус-специфичной информации private function getStatusSpecificInfo($order) { $text = ""; switch ($order['status']) { case 'created': if ($order['type'] === 'buy') { $text .= "⏰ Ожидается оплата\n"; $text .= "💡 Переведите указанную сумму и нажмите \"Я оплатил\"\n"; $text .= "🕐 Время на оплату: 60 минут"; } else { $text .= "⏰ Ожидается отправка BTC\n"; $text .= "💡 Отправьте BTC на указанный адрес и нажмите \"Я отправил\"\n"; $text .= "🕐 Время на отправку: 60 минут"; } break; case 'paid': $text .= "✅ Платеж зафиксирован\n"; $text .= "🔍 Проверяем поступление средств\n"; $text .= "⏰ Время обработки: до 30 минут"; break; case 'confirmed': $text .= "🔍 Платеж подтвержден\n"; if ($order['type'] === 'buy') { $text .= "⚡ Переводим BTC на ваш кошелек\n"; } else { $text .= "⚡ Переводим деньги на ваши реквизиты\n"; } $text .= "⏰ Время выполнения: до 15 минут"; break; case 'completed': if ($order['type'] === 'buy') { $text .= "🎉 BTC переведен на ваш кошелек!\n"; $text .= "🔍 Проверьте поступление в blockchain\n"; $text .= "⏰ Подтверждение сети: 10-60 минут"; } else { $text .= "🎉 Деньги переведены на ваши реквизиты!\n"; $text .= "💳 Проверьте поступление на счет"; } break; case 'cancelled': $text .= "❌ Заказ отменен\n"; $text .= "💬 По вопросам возврата обращайтесь в поддержку"; break; } return $text . "\n\n"; } // ✅ НОВЫЙ метод для формирования клавиатуры действий private function getOrderActionKeyboard($order, $hashedOrderId) { $keyboard = ['inline_keyboard' => []]; // Основные действия в зависимости от статуса if ($order['status'] === 'created') { if ($order['type'] === 'buy') { $keyboard['inline_keyboard'][] = [ ['text' => '✅ Я оплатил', 'callback_data' => "confirm_payment_{$hashedOrderId}"] ]; $keyboard['inline_keyboard'][] = [ ['text' => '💳 Показать реквизиты', 'callback_data' => "show_payment_details_{$hashedOrderId}"] ]; } else { $keyboard['inline_keyboard'][] = [ ['text' => '✅ Я отправил BTC', 'callback_data' => "confirm_sell_{$hashedOrderId}"] ]; } $keyboard['inline_keyboard'][] = [ ['text' => '❌ Отменить заказ', 'callback_data' => "cancel_order_{$hashedOrderId}"] ]; } elseif (in_array($order['status'], ['paid', 'confirmed'])) { // Для активных заказов показываем кнопку обновления $keyboard['inline_keyboard'][] = [ ['text' => '🔄 Обновить статус', 'callback_data' => "refresh_order_{$hashedOrderId}"] ]; $keyboard['inline_keyboard'][] = [ ['text' => '📞 Связаться с поддержкой', 'callback_data' => 'support'] ]; } // Дополнительные действия if ($order['type'] === 'buy' && $order['user_wallet']) { $keyboard['inline_keyboard'][] = [ ['text' => '📋 Копировать кошелек', 'callback_data' => "copy_wallet_{$hashedOrderId}"] ]; } if ($order['type'] === 'sell' && $order['assigned_wallet']) { $keyboard['inline_keyboard'][] = [ ['text' => '📋 Копировать адрес', 'callback_data' => "copy_address_{$hashedOrderId}"] ]; } // Навигация $keyboard['inline_keyboard'][] = [ ['text' => '📋 Все заказы', 'callback_data' => 'orders_history'], ['text' => '🏠 Главное меню', 'callback_data' => 'main_menu'] ]; return $keyboard; } // ✅ НОВЫЙ метод для показа реквизитов заказа private function showPaymentDetails($chatId, $messageId, $orderId, $user) { $order = $this->database->getOrder($orderId); if (!$order || $order['user_id'] !== $user['id'] || $order['type'] !== 'buy') { $this->answerCallbackQuery($callback['id'], 'Заказ не найден'); return; } // Получаем актуальные реквизиты для этого типа оплаты $paymentDetails = $this->database->getActivePaymentDetails($order['payment_method']); if (!$paymentDetails) { $this->answerCallbackQuery($callback['id'], 'Реквизиты недоступны'); return; } $hashedOrderId = $this->generateOrderHash($order['id']); $paymentIcon = $order['payment_method'] === 'card' ? '💳' : '📱'; $paymentName = $order['payment_method'] === 'card' ? 'Банковская карта' : 'СБП'; $text = "{$paymentIcon} Реквизиты для оплаты\n\n"; $text .= "🔢 Заказ: #{$hashedOrderId}\n"; $text .= "💰 К оплате: " . number_format($order['final_amount'], 2, ',', ' ') . " ₽\n"; $text .= "💳 Способ: {$paymentName}\n\n"; $text .= "📋 Реквизиты:\n"; $text .= $paymentDetails['details'] . "\n\n"; $text .= "⚠️ ВАЖНО!\n"; $text .= "• Переводите точную сумму: " . number_format($order['final_amount'], 2, ',', ' ') . " ₽\n"; $text .= "• Сохраните чек об оплате\n"; $text .= "• После оплаты нажмите \"Я оплатил\""; $keyboard = [ 'inline_keyboard' => [ [['text' => '✅ Я оплатил', 'callback_data' => "confirm_payment_{$hashedOrderId}"]], [['text' => '👁️ Вернуться к заказу', 'callback_data' => "view_order_{$hashedOrderId}"]], [['text' => '🏠 Главное меню', 'callback_data' => 'main_menu']] ] ]; $this->editMessage($chatId, $messageId, $text, $keyboard); } /** * ✅ ОБНОВЛЕННЫЙ метод showProfile с поддержкой скрытых ID в истории заказов и отображением резерва */ private function showProfile($chatId, $user) { $activeOrders = $this->database->getUserOrders($user['id'], 'created'); $activeOrders = array_merge($activeOrders, $this->database->getUserOrders($user['id'], 'paid')); $activeOrders = array_merge($activeOrders, $this->database->getUserOrders($user['id'], 'confirmed')); $completedOrders = $this->database->getUserOrders($user['id'], 'completed'); $keyboard = [ 'inline_keyboard' => [ [ ['text' => '📋 История заказов', 'callback_data' => 'orders_history'], ['text' => '📊 Реферальная программа', 'callback_data' => 'referral_stats'] ], [['text' => '🏠 Главное меню', 'callback_data' => 'main_menu']] ] ]; $text = "👤 Ваш профиль\n\n"; $text .= "🆔 Telegram ID: {$user['telegram_id']}\n"; if ($user['username']) { $text .= "👤 Username: @{$user['username']}\n"; } $text .= "👨‍💼 Имя: " . htmlspecialchars($user['first_name'] . ' ' . $user['last_name']) . "\n"; $text .= "₿ Персональный кошелек: {$user['btc_wallet']}\n"; $text .= "💡 Можете использовать для покупок или указать другой\n\n"; $text .= "💰 Ваш баланс:\n"; $text .= "• RUB: " . number_format($user['balance_rub'], 2, ',', ' ') . " ₽\n"; $text .= "• BTC: " . number_format($user['balance_btc'], 8) . " BTC\n\n"; // ✅ НОВОЕ: Показываем информацию о резерве обменника в профиле $reserveInfo = $this->getBtcReserveInfo(); if ($reserveInfo['display_enabled']) { $text .= "🏦 Резерв обменника:\n"; $text .= "• 💎 Доступно: " . number_format($reserveInfo['available_btc'], 4) . " BTC\n"; $text .= "• 🔒 Зарезервировано: " . number_format($reserveInfo['reserved_btc'], 4) . " BTC\n"; $text .= "• 📊 Всего: " . number_format($reserveInfo['total_btc'], 4) . " BTC\n\n"; } $text .= "📊 Статистика:\n"; $text .= "• 🔄 Активные заказы: " . count($activeOrders) . "\n"; $text .= "• ✅ Завершенные заказы: " . count($completedOrders) . "\n"; $text .= "• 📅 Регистрация: " . date('d.m.Y', strtotime($user['created_at'])) . "\n\n"; // Реферальная ссылка $text .= "🔗 Ваша реферальная ссылка:\n"; $text .= "https://t.me/OneTouchChangeBot?start=ref_{$user['referral_link']}"; if (!empty($activeOrders)) { $text .= "\n\n🔄 Активные заказы:\n"; foreach (array_slice($activeOrders, 0, 3) as $order) { $statusEmoji = $this->getOrderStatusEmoji($order['status']); $typeText = $order['type'] === 'buy' ? 'Покупка' : 'Продажа'; // ✅ НОВОЕ: Показываем скрытый ID в профиле $hashedOrderId = $this->generateOrderHash($order['id']); $text .= "• #{$hashedOrderId} - {$statusEmoji} {$typeText} " . number_format($order['amount_btc'], 8) . " BTC\n"; } if (count($activeOrders) > 3) { $text .= "... и еще " . (count($activeOrders) - 3) . " заказов\n"; } } $this->sendMessage($chatId, $text, $keyboard); } // Исправленный метод showReferralInfo private function showReferralInfo($chatId, $user) { $referralEarnings = $this->database->getReferralEarnings($user['id']); $totalEarned = array_sum(array_column($referralEarnings, 'amount')); $referralCount = count($referralEarnings); $keyboard = [ 'inline_keyboard' => [ [['text' => '💸 Вывести средства', 'callback_data' => 'withdraw']], [['text' => '🏠 Главное меню', 'callback_data' => 'main_menu']] ] ]; // Если баланс меньше минимального для вывода, скрываем кнопку $minWithdrawal = $this->database->getSetting('min_withdrawal', 1000); if ($user['balance_rub'] < $minWithdrawal) { $keyboard['inline_keyboard'] = [ [['text' => '🏠 Главное меню', 'callback_data' => 'main_menu']] ]; } $text = "📊 Реферальная программа\n\n"; $text .= "🎯 Условия программы:\n"; $text .= "• 1% с каждой покупки реферала\n"; $text .= "• Автоматическое зачисление на баланс\n"; $text .= "• Минимальный вывод: " . number_format($minWithdrawal, 0, ',', ' ') . " ₽\n\n"; $text .= "💰 Ваша статистика:\n"; $text .= "• 👥 Приглашено пользователей: {$referralCount}\n"; $text .= "• 💵 Заработано всего: " . number_format($totalEarned, 2, ',', ' ') . " ₽\n"; $text .= "• 💳 Текущий баланс: " . number_format($user['balance_rub'], 2, ',', ' ') . " ₽\n\n"; // Реферальная ссылка $text .= "🔗 Ваша реферальная ссылка:\n"; $text .= "https://t.me/OneTouchChangeBot?start=ref_{$user['referral_link']}\n\n"; $text .= "📢 Как заработать больше:\n"; $text .= "• Делитесь ссылкой в социальных сетях\n"; $text .= "• Рассказывайте друзьям о выгодных курсах\n"; $text .= "• Размещайте ссылку в тематических чатах\n\n"; if ($referralCount > 0) { $text .= "💎 Последние рефералы:\n"; foreach (array_slice($referralEarnings, 0, 5) as $earning) { $date = date('d.m.Y', strtotime($earning['created_at'])); $username = $earning['referral_username'] ? "@{$earning['referral_username']}" : "Пользователь"; $text .= "• {$date} - {$username} - " . number_format($earning['amount'], 2) . " ₽\n"; } } if ($user['balance_rub'] < $minWithdrawal) { $text .= "\n⚠️ Для вывода средств необходимо минимум " . number_format($minWithdrawal, 0, ',', ' ') . " ₽"; } $this->sendMessage($chatId, $text, $keyboard); } private function showSupport($chatId) { $supportUsername = $this->database->getSetting('support_username', '@support'); $keyboard = [ 'inline_keyboard' => [ [['text' => '💬 Написать в поддержку', 'url' => "https://t.me/{$supportUsername}"]], [['text' => '🏠 Главное меню', 'callback_data' => 'main_menu']] ] ]; $text = "📞 Техническая поддержка\n\n"; $text .= "👨‍💻 Служба поддержки: {$supportUsername}\n\n"; $text .= "⏰ Время работы: 24/7\n"; $text .= "⚡ Среднее время ответа: до 30 минут\n\n"; $text .= "❓ Частые вопросы:\n\n"; $text .= "📋 Время обработки заказов:\n"; $text .= "• Покупка BTC: до 30 минут после оплаты\n"; $text .= "• Продажа BTC: до 30 минут после поступления\n\n"; $text .= "💰 Лимиты и комиссии:\n"; $text .= "• Минимальная сумма: " . number_format($this->database->getSetting('min_amount', 1000), 0, ',', ' ') . " ₽\n"; $text .= "• Максимальная сумма: " . number_format($this->database->getSetting('max_amount', 1000000), 0, ',', ' ') . " ₽\n"; $text .= "• Комиссия за покупку: " . $this->database->getSetting('buy_commission', 3) . "%\n"; $text .= "• Комиссия за продажу: " . $this->database->getSetting('sell_commission', 2) . "%\n\n"; $text .= "🔒 Безопасность:\n"; $text .= "• Проверяйте точность сумм при переводе\n"; $text .= "• Не передавайте данные третьим лицам\n"; $text .= "• Сохраняйте скриншоты операций\n\n"; $text .= "📱 Для быстрой помощи нажмите кнопку ниже"; $this->sendMessage($chatId, $text, $keyboard); } // ✅ ОБНОВЛЕННЫЙ метод showOrdersHistory с поддержкой скрытых ID и кнопками для входа в заказы private function showOrdersHistory($chatId, $user) { $orders = $this->database->getUserOrders($user['id']); $ordersCount = count($orders); $keyboard = [ 'inline_keyboard' => [ [['text' => '🏠 Главное меню', 'callback_data' => 'main_menu']] ] ]; $text = "📋 История ваших заказов\n\n"; if ($ordersCount === 0) { $text .= "📭 У вас пока нет заказов.\n\n"; $text .= "Начните с первой операции:"; $keyboard['inline_keyboard'] = [ [ ['text' => '💰 Купить BTC', 'callback_data' => 'buy_btc'], ['text' => '💸 Продать BTC', 'callback_data' => 'sell_btc'] ], [['text' => '🏠 Главное меню', 'callback_data' => 'main_menu']] ]; } else { // Группируем заказы по статусам $activeOrders = []; $completedOrders = []; foreach ($orders as $order) { if (in_array($order['status'], ['created', 'paid', 'confirmed'])) { $activeOrders[] = $order; } else { $completedOrders[] = $order; } } $text .= "📊 Всего заказов: {$ordersCount}\n"; $text .= "🔄 Активные: " . count($activeOrders) . "\n"; $text .= "✅ Завершенные: " . count($completedOrders) . "\n\n"; // Активные заказы с кнопками if (!empty($activeOrders)) { $text .= "🔄 Активные заказы:\n\n"; foreach ($activeOrders as $order) { $hashedOrderId = $this->generateOrderHash($order['id']); $statusEmoji = $this->getOrderStatusEmoji($order['status']); $statusName = $this->getOrderStatusName($order['status']); $typeEmoji = $order['type'] === 'buy' ? '💰' : '💸'; $typeName = $order['type'] === 'buy' ? 'Покупка' : 'Продажа'; $date = date('d.m H:i', strtotime($order['created_at'])); $text .= "#{$hashedOrderId} - {$typeEmoji} {$typeName}\n"; $text .= "🔹 " . number_format($order['amount_btc'], 8) . " BTC\n"; $text .= "🔹 {$statusEmoji} {$statusName} • {$date}\n\n"; // Добавляем кнопку для входа в заказ $keyboard['inline_keyboard'][] = [ ['text' => "👁️ #{$hashedOrderId} - {$typeName}", 'callback_data' => "view_order_{$hashedOrderId}"] ]; } } // Последние завершенные заказы (без кнопок) if (!empty($completedOrders)) { $text .= "✅ Последние завершенные:\n\n"; foreach (array_slice($completedOrders, 0, 5) as $order) { $hashedOrderId = $this->generateOrderHash($order['id']); $typeEmoji = $order['type'] === 'buy' ? '💰' : '💸'; $typeName = $order['type'] === 'buy' ? 'Покупка' : 'Продажа'; $date = date('d.m.Y', strtotime($order['created_at'])); $text .= "#{$hashedOrderId} - {$typeEmoji} {$typeName} • {$date}\n"; $text .= "🔹 " . number_format($order['amount_btc'], 8) . " BTC\n\n"; } if (count($completedOrders) > 5) { $text .= "... и еще " . (count($completedOrders) - 5) . " завершенных заказов\n\n"; } } // Дополнительные кнопки $keyboard['inline_keyboard'][] = [ ['text' => '🔄 Обновить', 'callback_data' => 'orders_history'] ]; } $this->sendMessage($chatId, $text, $keyboard); } private function processWithdrawal($chatId, $messageId, $user) { $minWithdrawal = $this->database->getSetting('min_withdrawal', 1000); if ($user['balance_rub'] < $minWithdrawal) { $text = "❌ Недостаточно средств для вывода\n\n"; $text .= "💰 Ваш баланс: " . number_format($user['balance_rub'], 2, ',', ' ') . " ₽\n"; $text .= "💳 Минимум для вывода: " . number_format($minWithdrawal, 0, ',', ' ') . " ₽\n\n"; $text .= "💡 Пригласите больше друзей для увеличения баланса!"; $keyboard = [ 'inline_keyboard' => [ [['text' => '🏠 Главное меню', 'callback_data' => 'main_menu']] ] ]; $this->editMessage($chatId, $messageId, $text, $keyboard); return; } $text = "💸 Заявка на вывод средств\n\n"; $text .= "💰 Доступно к выводу: " . number_format($user['balance_rub'], 2, ',', ' ') . " ₽\n\n"; $text .= "📝 Для вывода средств напишите в поддержку:\n"; $text .= "• Сумму к выводу\n"; $text .= "• Реквизиты для перевода\n"; $text .= "• Ваш Telegram ID: {$user['telegram_id']}\n\n"; $text .= "⏰ Время обработки: до 24 часов\n"; $text .= "💳 Комиссия за вывод: 0%"; $supportUsername = $this->database->getSetting('support_username', '@support'); $keyboard = [ 'inline_keyboard' => [ [['text' => '💬 Написать в поддержку', 'url' => "https://t.me/{$supportUsername}"]], [['text' => '🏠 Главное меню', 'callback_data' => 'main_menu']] ] ]; $this->editMessage($chatId, $messageId, $text, $keyboard); } private function cancelOperation($chatId, $messageId, $userId) { // Очищаем состояние пользователя $this->clearUserState($userId); $this->editMessage($chatId, $messageId, "❌ Операция отменена\n\nВы можете начать новую операцию в любое время.", [ 'inline_keyboard' => [ [ ['text' => '💰 Купить BTC', 'callback_data' => 'buy_btc'], ['text' => '💸 Продать BTC', 'callback_data' => 'sell_btc'] ], [['text' => '🏠 Главное меню', 'callback_data' => 'main_menu']] ] ]); } // ✅ ОБНОВЛЕННЫЙ метод cancelOrder с поддержкой скрытых ID private function cancelOrder($chatId, $messageId, $orderId, $user) { $order = $this->database->getOrder($orderId); $this->database->log('debug', 'Canceling order', [ 'order_id' => $orderId, 'user_id' => $user['id'], 'user_telegram_id' => $user['telegram_id'], 'order_found' => $order !== false, 'order_user_id' => $order['user_id'] ?? null ]); if (!$order) { $this->editMessage($chatId, $messageId, "❌ Заказ не найден.", [ 'inline_keyboard' => [ [['text' => '🏠 Главное меню', 'callback_data' => 'main_menu']] ] ]); return; } // Проверяем принадлежность заказа пользователю if ($order['user_id'] !== $user['id']) { $this->database->log('warning', 'Order cancel: user mismatch', [ 'order_id' => $orderId, 'order_user_id' => $order['user_id'], 'current_user_id' => $user['id'] ]); $this->editMessage($chatId, $messageId, "❌ Заказ не найден.", [ 'inline_keyboard' => [ [['text' => '🏠 Главное меню', 'callback_data' => 'main_menu']] ] ]); return; } if ($order['status'] === 'completed') { $this->editMessage($chatId, $messageId, "❌ Заказ уже завершен\n\nОтменить завершенный заказ невозможно.", [ 'inline_keyboard' => [ [['text' => '🏠 Главное меню', 'callback_data' => 'main_menu']] ] ]); return; } $this->database->updateOrderStatus($orderId, 'cancelled'); // ✅ НОВОЕ: Генерируем скрытый ID для отображения $hashedOrderId = $this->generateOrderHash($orderId); $text = "❌ Заказ #{$hashedOrderId} отменен\n\n"; $text .= "Заказ успешно отменен. Если вы уже произвели оплату, обратитесь в поддержку для возврата средств.\n\n"; $text .= "💡 Вы можете создать новый заказ в любое время."; $keyboard = [ 'inline_keyboard' => [ [ ['text' => '💰 Купить BTC', 'callback_data' => 'buy_btc'], ['text' => '💸 Продать BTC', 'callback_data' => 'sell_btc'] ], [['text' => '🏠 Главное меню', 'callback_data' => 'main_menu']] ] ]; $this->editMessage($chatId, $messageId, $text, $keyboard); // Уведомляем админов об отмене $this->notifyAdmins("❌ Заказ #{$hashedOrderId} (ID: {$orderId}) отменен пользователем " . ($user['username'] ? "@{$user['username']}" : "ID {$user['id']}")); } private function getBtcRate() { // Проверяем кеш $cacheInterval = $this->database->getSetting('rate_update_interval', 300); if (self::$btcRateCache && (time() - self::$btcRateCacheTime) < $cacheInterval) { return self::$btcRateCache; } // Проверяем, включен ли Binance API $binanceEnabled = $this->database->getSetting('binance_api_enabled', 1); if (!$binanceEnabled) { // Используем сохраненный курс из БД $savedRate = $this->database->getSetting('last_btc_rate'); if ($savedRate) { self::$btcRateCache = floatval($savedRate); self::$btcRateCacheTime = time(); return self::$btcRateCache; } } // Получаем новый курс через API $btcUsdtRate = $this->getBinanceBtcUsdt(); $usdRubRate = $this->getUsdRubRate(); if (!$btcUsdtRate || !$usdRubRate) { // Используем последний сохраненный курс $savedRate = $this->database->getSetting('last_btc_rate'); if ($savedRate) { $this->database->log('warning', 'Using saved BTC rate due to API failure', ['rate' => $savedRate]); self::$btcRateCache = floatval($savedRate); self::$btcRateCacheTime = time() - $cacheInterval + 60; // Кеш на 1 минуту return self::$btcRateCache; } $this->database->log('error', 'Failed to get exchange rates', [ 'btc_usdt' => $btcUsdtRate, 'usd_rub' => $usdRubRate ]); return false; } // Рассчитываем BTC/RUB $btcRubRate = $btcUsdtRate * $usdRubRate; // Сохраняем в кеш и БД self::$btcRateCache = $btcRubRate; self::$btcRateCacheTime = time(); $this->database->setSetting('last_btc_rate', $btcRubRate); $this->database->setSetting('last_rate_update', date('Y-m-d H:i:s')); $this->database->log('info', 'Exchange rates updated', [ 'btc_usdt' => $btcUsdtRate, 'usd_rub' => $usdRubRate, 'btc_rub' => $btcRubRate ]); return $btcRubRate; } private function getBinanceBtcUsdt() { $url = "https://api.binance.com/api/v3/ticker/price?symbol=BTCUSDT"; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 10); curl_setopt($ch, CURLOPT_USERAGENT, 'OneTouch Change Bot'); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $error = curl_error($ch); curl_close($ch); if ($error) { $this->database->log('error', 'Binance API curl error: ' . $error); return false; } if ($httpCode !== 200) { $this->database->log('error', 'Binance API HTTP error: ' . $httpCode, ['response' => $response]); return false; } $data = json_decode($response, true); if (!$data || !isset($data['price'])) { $this->database->log('error', 'Invalid Binance API response', ['response' => $response]); return false; } return floatval($data['price']); } private function getUsdRubRate() { // Используем несколько источников для получения курса USD/RUB $sources = [ 'https://api.exchangerate-api.com/v4/latest/USD', 'https://api.fixer.io/latest?base=USD&access_key=YOUR_FIXER_KEY', // Требует ключа 'https://open.er-api.com/v6/latest/USD' ]; foreach ($sources as $url) { // Пропускаем fixer.io если нет ключа if (strpos($url, 'fixer.io') !== false && strpos($url, 'YOUR_FIXER_KEY') !== false) { continue; } $rate = $this->fetchUsdRubFromSource($url); if ($rate) { return $rate; } } // Fallback: используем фиксированный курс (обновляется вручную в админке) $fallbackRate = $this->database->getSetting('usd_rub_fallback', 100); $this->database->log('warning', 'Using fallback USD/RUB rate', ['rate' => $fallbackRate]); return floatval($fallbackRate); } private function fetchUsdRubFromSource($url) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 8); curl_setopt($ch, CURLOPT_USERAGENT, 'OneTouch Change Bot'); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $error = curl_error($ch); curl_close($ch); if ($error || $httpCode !== 200) { $this->database->log('debug', 'USD/RUB source failed', [ 'url' => $url, 'error' => $error, 'http_code' => $httpCode ]); return false; } $data = json_decode($response, true); if (!$data) { return false; } // Обрабатываем разные форматы ответов $rubRate = null; if (isset($data['rates']['RUB'])) { $rubRate = $data['rates']['RUB']; } elseif (isset($data['conversion_rates']['RUB'])) { $rubRate = $data['conversion_rates']['RUB']; } if ($rubRate && is_numeric($rubRate)) { return floatval($rubRate); } return false; } private function parseAmount($text) { // Убираем лишние символы и заменяем запятую на точку $text = str_replace([',', ' ', '₽', 'BTC', 'btc'], ['.' , '', '', '', ''], trim($text)); $text = preg_replace('/[^\d.]/', '', $text); // Проверяем что остались только цифры и максимум одна точка if (!preg_match('/^\d+(\.\d+)?$/', $text)) { return false; } $amount = floatval($text); // Проверяем разумные лимиты if ($amount <= 0 || $amount > 10000000) { return false; } return $amount; } private function isValidBtcAddress($address) { $address = trim($address); // Legacy адреса (P2PKH и P2SH) if (preg_match('/^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$/', $address)) { return true; } // Bech32 адреса (P2WPKH и P2WSH) if (preg_match('/^bc1[a-z0-9]{39,59}$/', $address)) { return true; } // Taproot адреса (P2TR) if (preg_match('/^bc1p[a-z0-9]{58}$/', $address)) { return true; } return false; } private function getOrderStatusEmoji($status) { $emojis = [ 'created' => '🟡', 'paid' => '🟠', 'confirmed' => '🔵', 'completed' => '🟢', 'cancelled' => '🔴' ]; return $emojis[$status] ?? '⚫'; } private function getOrderStatusName($status) { $names = [ 'created' => 'Создан', 'paid' => 'Оплачен', 'confirmed' => 'Подтвержден', 'completed' => 'Завершен', 'cancelled' => 'Отменен' ]; return $names[$status] ?? $status; } private function editWalletStep($chatId, $messageId, $userId, $user) { $state = $this->getUserState($userId); if (!$state || $state['action'] !== 'buy_wallet') { $this->editMessage($chatId, $messageId, "❌ Сессия истекла. Начните заново.", [ 'inline_keyboard' => [ [['text' => '🏠 Главное меню', 'callback_data' => 'main_menu']] ] ]); return; } $keyboard = [ 'inline_keyboard' => [ [['text' => '💰 Изменить сумму', 'callback_data' => 'buy_btc']], [['text' => '❌ Отмена', 'callback_data' => 'main_menu']] ] ]; $text = "📝 Введите новый BTC кошелек для получения:\n\n"; $text .= "💡 Примеры правильных адресов:\n"; $text .= "• 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa\n"; $text .= "• bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh\n\n"; $text .= "📋 Текущие данные:\n"; $text .= "• Сумма: " . number_format($state['amount_rub'], 2, ',', ' ') . " ₽\n"; $text .= "• К получению: " . number_format($state['amount_btc'], 8) . " BTC\n"; $text .= "• К оплате: " . number_format($state['final_amount'], 2, ',', ' ') . " ₽"; $this->editMessage($chatId, $messageId, $text, $keyboard); // Меняем состояние обратно на ввод кошелька $state['action'] = 'buy_wallet'; unset($state['user_wallet']); // Убираем старый кошелек $this->setUserState($userId, $state); } private function editSellWalletStep($chatId, $messageId, $userId, $user) { $state = $this->getUserState($userId); if (!$state) { $this->editMessage($chatId, $messageId, "❌ Сессия истекла. Начните заново.", [ 'inline_keyboard' => [ [['text' => '🏠 Главное меню', 'callback_data' => 'main_menu']] ] ]); return; } $keyboard = [ 'inline_keyboard' => [ [['text' => '💰 Изменить количество', 'callback_data' => 'sell_btc']], [['text' => '❌ Отмена', 'callback_data' => 'main_menu']] ] ]; $text = "📝 Введите новые реквизиты для получения денег:\n\n"; $text .= "💡 Примеры:\n"; $text .= "• Номер карты: 1234 5678 9012 3456\n"; $text .= "• Номер телефона для СБП: +7 999 123 45 67\n"; $text .= "• Укажите ФИО получателя\n\n"; $text .= "📋 Текущие данные:\n"; $text .= "• Количество: " . number_format($state['amount_btc'], 8) . " BTC\n"; $text .= "• К получению: " . number_format($state['final_amount'], 2, ',', ' ') . " ₽"; $this->editMessage($chatId, $messageId, $text, $keyboard); // Меняем состояние обратно на ввод реквизитов $state['action'] = 'sell_wallet'; unset($state['payment_details']); // Убираем старые реквизиты $this->setUserState($userId, $state); } } ?>