| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452 |
- <?php
- namespace app\common\logic;
- use app\common\library\Log;
- use app\common\model\LedgerWalletModel;
- use app\common\model\OfflineRechargeRecordModel;
- use app\common\model\OfflineRechargeVerifyModel;
- use app\common\model\ParametersModel;
- use app\common\model\UserModel;
- use app\common\model\ServersModel;
- use Exception;
- use fast\Action;
- use fast\Asset;
- use fast\Http;
- use fast\RechargeOrderType;
- use fast\RechargeStatus;
- use fast\RechargeType;
- use think\Db;
- use think\Env;
- use think\Error;
- use think\Model;
- class ScanLogic
- {
- /**
- * 币安API地址
- * @var string
- */
- private string $bscApiUrl = '';
- /**
- * 币安链USDT地址
- * @var string
- */
- private string $bscUSDAddress = '0x55d398326f99059ff775485246999027b3197955';
- /**
- * 平台收款钱包地址
- * @var string
- */
- private string $collectionAddress = '';
- /**
- * 最少确认节点数量
- * @var int
- */
- private int $minConfirmations = 12;
- /**
- * value的放大比例 固定为: 10的18次方
- * @var int
- */
- private string $valueScale = '1000000000000000000';
- public function __construct()
- {
- $apikey = (new ParametersModel)->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;
- }
- }
|