getValue('bscApiKey'); if (empty($apikey)) { dump("读取币安apikey错误"); exit(1); } $collectionAddress = Env::get('rental.pay_address'); if (empty($collectionAddress)) { dump("读取平台收款钱包地址错误"); exit(1); } $this->collectionAddress = strtolower($collectionAddress); $this->bscApiUrl = "https://api.bscscan.com/api?module=account&action=tokentx&contractaddress=$this->bscUSDAddress&page=1&offset=10000&endblock=99999999&apikey=$apikey&address="; $this->hashStatusUrl = "https://api.bscscan.com/api?module=transaction&action=gettxreceiptstatus&apikey=$apikey&txhash="; } public function scanRechargeOrders() { $recharges = (new OfflineRechargeRecordModel())->where([ 'type' => RechargeType::CashFromChain, 'status' => RechargeStatus::StatusUnAuth, ])->order('id DESC')->limit(20)->select(); if (!is_null($recharges)) { foreach ($recharges as $v) { $vArr = $v->toArray(); // 请求api $result = $this->verifyTxHash($vArr); // 记录执行记录 (new OfflineRechargeVerifyModel())->insert([ 'order_id' => $vArr['id'], 'user_id' => $vArr['user_id'], 'result' => $result == '', 'fail_reason' => $result, 'create_time' => time(), ]); } } } /* * 扫描收款地址 */ public function scanCollectionAddress() { $max_block_number = (new OfflineRechargeRecordModel()) ->max('block_number'); $max_block_number ++; $url = $this->bscApiUrl . $this->collectionAddress . "&startblock=" . $max_block_number; //$user['address'] = strtolower($user['address']); // 转小写 // 发起请求 $body = Http::get($url); if (empty($body)) { return "api返回内容为空"; } // 转成数组 $rsArr = json_decode($body, true); if (empty($rsArr) || !is_array($rsArr)) { return "api返回数据异常"; } if ($rsArr['status'] != '1') { return 'api返回status不为1,错误信息:' . $rsArr['message']; } $errMsg = ''; $new_block_number = 0; $all_user_address = [];//钱包地址为键值的所有会员的数组 $orderInfo = []; // 查询匹配交易列表 (new Log())->info('开始匹配交易'); (new Log())->info($rsArr['result']); foreach ($rsArr['result'] as $v) { $errMsg = ''; if ($v['to'] != $this->collectionAddress) { $new_block_number = $v['blockNumber']; (new Log())->info('收款地址不是平台地址:' . $v['hash']); continue;//转载地址不是收款地址的数据直接跳过不处理 } if ($v['contractAddress'] != $this->bscUSDAddress) { // $errMsg = '币种错误: ' . $v['contractAddress']; $new_block_number = $v['blockNumber']; continue;//交易币种的合约地址不是指定代币,不处理。一般币种为USDT } $orderInfo['amount'] = bcdiv($v['value'], $this->valueScale, 6);//交易金额 if(!($orderInfo['amount'] > 0)){ $new_block_number = $v['blockNumber']; continue;//交易金额必须大于0 }else{ $minUsdt = (new ParametersModel)->getValue('minUsdt') ?? '0';//最小购买金额 if($minUsdt > $orderInfo['amount']){ $new_block_number = $v['blockNumber']; $errMsg = '交易金额必须大于系统最小购买金额:' . $v['hash']; (new Log())->info('交易金额必须大于系统最小购买金额:' . $v['hash']); continue;//交易金额必须大于系统最小购买金额 } } $check_user = (new UserModel())->getByAddress(strtolower($v['from'])); if(empty($check_user)){ $new_block_number = $v['blockNumber']; (new Log())->info('转账地址不是平台用户账号:' . $v['hash']); continue;//转账地址不是平台账号的数据不处理 } $orderInfo['user_id'] = $check_user['id']; // if(empty($all_user_address)){ // $all_user_address = (new UserModel())->getAllAddress(); // } // if(!(isset($all_user_address[$v['from']]) || isset($all_user_address[strtolower($v['from'])]))){ // $new_block_number = $v['blockNumber']; // (new Log())->info('转账地址不是平台用户账号:' . $v['hash']); // continue;//转账地址不是平台账号的数据不处理 // }else{ // if(isset($all_user_address[$v['from']])){ // $orderInfo['user_id'] = $all_user_address[$v['from']]; // }else{ // $orderInfo['user_id'] = $all_user_address[strtolower($v['from'])]; // } // } $rsData = $this->getHashStatus($v['hash']); if($rsData['status'] == 0){//当前哈希交易状态为失败,跳出循环,等待下次判断,若真是失败交易,过几分钟后,这个数据不会再出现,这个接口拿到的都是有效的交易数据 break; } //判断当前hash是否存在于充值记录 $check_recharge_record = (new OfflineRechargeRecordModel()) ->where('tx_hash', $v['hash']) ->find(); if(empty($check_recharge_record)){//充值记录表中没有当前记录,则补充插入 (new Log())->info('补单:' . $v['hash']); $orderInfo['order_type'] = RechargeOrderType::RentalPower;//订单类型 //判断是否服务器购买订单 $servers_list = (new ServersModel()) ->field('id,title,price,power') ->select(); foreach ($servers_list as $item){ if($item['price'] == $orderInfo['amount']){ $orderInfo['order_type'] = RechargeOrderType::RentalServer;//订单类型 } } try { $orderInfo['id'] = (new OfflineRechargeRecordModel())->createRecord(RechargeType::CashFromChain, $v['hash'], $orderInfo['user_id'], $orderInfo['amount'], RechargeStatus::StatusUnAuth, $orderInfo['order_type'], $v['blockNumber'], '自动补单'); } catch (Exception $e) { (new Log())->error('自动补单失败:' . $e); break; } }else if($check_recharge_record['status'] == 1){ $new_block_number = $v['blockNumber']; continue;//当前交易已处理 }else{ $orderInfo = $check_recharge_record->toArray(); if (bccomp($v['value'], bcmul($check_recharge_record['amount'], $this->valueScale, 6)) !== 0) { $errMsg = '金额错误: ' . $v['value'] . ',' . $check_recharge_record['amount']; (new Log())->info('金额错误: ' . $v['value'] . ',' . $check_recharge_record['amount']); } } $orderInfo['block_number'] = $v['blockNumber']; // 校验正确,更新订单信息为成功,并触发各项收益的发放 $updateErrMsg = $this->updateOrderStatus($orderInfo, $errMsg == ''); if (!empty($updateErrMsg)) { $errMsg .= '|' . $updateErrMsg; } // 记录执行记录 (new OfflineRechargeVerifyModel())->insert([ 'order_id' => $orderInfo['id'], 'user_id' => $orderInfo['user_id'], 'result' => $errMsg == '', 'fail_reason' => $errMsg, 'create_time' => time(), ]); } if($new_block_number > $max_block_number){ //更新最新一条充值记录的区块为最新扫描过的区块 $max_info = (new OfflineRechargeRecordModel()) ->order('id desc') ->find(); $max_info->save([ 'block_number' => $new_block_number ]); } return $errMsg; } private function verifyTxHash(array $orderInfo): string { $user = (new UserModel())->getById($orderInfo['user_id']); if (empty($user)) { return "用户不存在,ID:" . $orderInfo['user_id']; } $rsData = $this->getHashStatus($orderInfo['tx_hash']); if($rsData['status'] == 0){ return $rsData['msg']; } $user['address'] = strtolower($user['address']); // 转小写 $url = $this->bscApiUrl . $user['address']; // 发起请求 $body = Http::get($url); if (empty($body)) { return "api返回内容为空"; } // 转成数组 $rsArr = json_decode($body, true); if (empty($rsArr) || !is_array($rsArr)) { return "api返回数据异常"; } if ($rsArr['status'] != '1') { return 'api返回status不为1,错误信息:' . $rsArr['message']; } $errMsg = ''; // 查询匹配交易列表 foreach ($rsArr['result'] as $v) { // 跳过hash不匹配或确认区块不足的记录 if ($v['hash'] != $orderInfo['tx_hash'] || $v['confirmations'] < $this->minConfirmations) { continue; } // 开始校验 if ($v['from'] != $user['address']) { $errMsg = 'from地址不是用户地址,from: ' . $v['from']; } else if ($v['to'] != $this->collectionAddress) { $errMsg = 'to地址不是平台收款地址,to: ' . $v['to']; } else if ($v['contractAddress'] != $this->bscUSDAddress) { $errMsg = '币种错误: ' . $v['contractAddress']; } else if (bccomp($v['value'], bcmul($orderInfo['amount'], $this->valueScale, 6)) !== 0) { $errMsg = '金额错误: ' . $v['value'] . ',' . $orderInfo['amount']; } break; } // 校验正确,更新订单信息为成功,并触发各项收益的发放 $updateErrMsg = $this->updateOrderStatus($orderInfo, $errMsg == ''); if (!empty($updateErrMsg)) { $errMsg .= '|' . $updateErrMsg; } return $errMsg; } private function updateOrderStatus(array $orderInfo, bool $verify): string { if (!$verify) { // 验证失败的处理 try { (new Log())->error('充值交易验证失败: ' . $verify); (new OfflineRechargeRecordModel())->updateOrderStatus($orderInfo['id'], RechargeStatus::StatusAuthFail, $orderInfo['block_number']); } catch (Exception $e) { return $e->getMessage(); } return ''; } if($orderInfo['order_type'] == 1){ //算力租赁订单处理 // 查询兑换比例 $usdtToPower = (new ParametersModel)->getValue('usdtToPowerRate'); $usdtToPowerFloat = floatval($usdtToPower); if (is_null($usdtToPower) || $usdtToPowerFloat <= 0) { return '获取USDT兑换算力的比例失败'; } $uid = $orderInfo['user_id']; $fee = $orderInfo['amount']; $power = bcmul($fee, $usdtToPowerFloat, 6); // 该用户兑得的算力 // 启动事务 Db::startTrans(); try { // 更新总算力和账变 (new LedgerWalletModel)->changeWalletAccount($uid, Asset::POWER, $power, Action::PowerRentalPower, $orderInfo['id']); // 更新服务器算力,不账变 (new LedgerWalletModel)->changeWalletOnly($uid, Asset::RENTAL_POWER, $power); // 更新自己(有效会员时间)和所有上级的信息(有效直推人数和团队总算力) (new UserModel())->updateForRental($uid, $power); // 发放直推USDT收益 (new LedgerWalletModel)->sendUsdtProfit($uid, $fee); // 发放直推算力收益 (new LedgerWalletModel)->sendDirectProfit($uid, $power); // 发代数收益 (new LedgerWalletModel)->sendGenerateProfit($uid, $fee); // 发放见点奖 (new LedgerWalletModel)->sendRegBonus($uid, $fee); // 更新购买(充值)记录 (new OfflineRechargeRecordModel())->updateOrderStatus($orderInfo['id'], RechargeStatus::StatusAuthSuccess, 0, $power); // 提交事务 Db::commit(); } catch (Exception $e) { // 回滚事务 Db::rollback(); return $e->getMessage(); } }else{ //服务器购买订单处理 $servers_info = (new ServersModel()) ->where('price', $orderInfo['amount']) ->find(); if(empty($servers_info)){ return '支付价格和服务器价格不匹配'; } // 启动事务 Db::startTrans(); try { // 更新服务器算力,不账变 (new LedgerWalletModel)->changeWalletOnly($orderInfo['user_id'], Asset::SERVER_POWER, $servers_info['power']); // 发放服务器市场推荐相关收益 //(new LedgerWalletModel)->sendMarketBonus($orderInfo['user_id'], $servers_info); // 更新购买(充值)记录 (new OfflineRechargeRecordModel())->updateOrderStatus($orderInfo['id'], RechargeStatus::StatusAuthSuccess, 0, $servers_info['power']); // 提交事务 Db::commit(); } catch (Exception $e) { // 回滚事务 Db::rollback(); return $e->getMessage(); } } return ''; } /** * 获取哈希地址成功状态 * * api接口返回数据格式 * { "status": "1", "message": "OK", "result": { "status": "1" } } * @param $orderInfo * @return array|string */ private function getHashStatus($tx_hash):array { $rsData['status'] = 0;//0 失败,1 成功 $rsData['msg'] = ''; $hashStatusUrl = $this->hashStatusUrl . $tx_hash;//接口地址 $body = Http::get($hashStatusUrl); if (empty($body)) { $rsData['msg'] = '状态api返回内容为空'; return $rsData; } // 转成数组 $rsArr = json_decode($body, true); if (empty($rsArr) || !is_array($rsArr)) { $rsData['msg'] = '状态api返回数据异常'; return $rsData; } if ($rsArr['status'] != '1') { $rsData['msg'] = '状态api返回status不为1,当前值为:' . $rsArr['status']; return $rsData; } if ($rsArr['result']['status'] != 1) { $rsData['msg'] = '状态api返回result中的status不为1,当前值为:' . $rsArr['result']['status']; return $rsData; } $rsData['status'] = 1; return $rsData; } }