From b14c000bc31dd0de2e3451b05cf77b200a99c11d Mon Sep 17 00:00:00 2001 From: cano Date: Fri, 8 Mar 2024 00:58:23 +0800 Subject: [PATCH] wallet --- app/Cache/Lock/BaseLock.php | 30 ++ .../WalletPlatformUserTransactionLock.php | 13 + app/Exceptions/ModelException.php | 7 + app/Jobs/AddPostQueue.php | 2 +- app/Jobs/UserActiveStatusQueue.php | 2 +- app/Jobs/WalletAddrTransactionChangeQueue.php | 39 ++ app/Jobs/WalletPlatformUserWithdrawQueue.php | 35 ++ .../Api/Customer/CustomerUserExtendModel.php | 6 +- ...ryModel.php => CustomerWsHistoryModel.php} | 2 +- .../Platform/WalletPlatformBalanceModel.php | 125 +++++++ .../WalletPlatformBalanceTransactionModel.php | 241 ++++++++++++ .../Wallet/Platform/WalletPlatformModel.php | 58 +++ .../WalletPlatformUserTransactionModel.php | 346 ++++++++++++++++++ ...lletPlatformUserWalletAddrHistoryModel.php | 44 +++ .../WalletPlatformUserWalletAddrModel.php | 81 ++++ app/Models/Wallet/Wallet/WalletAddrModel.php | 312 ++++++++++++++++ .../Wallet/WalletAddrTransactionModel.php | 208 +++++++++++ .../Wallet/Wallet/WalletCurrencyModel.php | 41 +++ .../Wallet/Wallet/WalletSettingModel.php | 18 + ...QueueWalletAddrTransactionChangeStruct.php | 12 + .../QueueWalletPlatformUserWithdrawStruct.php | 11 + app/Tools/Math.php | 31 ++ app/Tools/Times.php | 12 + app/Tools/Tools.php | 5 + composer.json | 8 +- 25 files changed, 1681 insertions(+), 8 deletions(-) create mode 100644 app/Cache/Lock/BaseLock.php create mode 100644 app/Cache/Lock/WalletPlatformUserTransactionLock.php create mode 100644 app/Jobs/WalletAddrTransactionChangeQueue.php create mode 100644 app/Jobs/WalletPlatformUserWithdrawQueue.php rename app/Models/Api/WebSocket/{ApiWsHistoryModel.php => CustomerWsHistoryModel.php} (97%) create mode 100644 app/Models/Wallet/Platform/WalletPlatformBalanceModel.php create mode 100644 app/Models/Wallet/Platform/WalletPlatformBalanceTransactionModel.php create mode 100644 app/Models/Wallet/Platform/WalletPlatformModel.php create mode 100644 app/Models/Wallet/PlatformUser/WalletPlatformUserTransactionModel.php create mode 100644 app/Models/Wallet/PlatformUser/WalletPlatformUserWalletAddrHistoryModel.php create mode 100644 app/Models/Wallet/PlatformUser/WalletPlatformUserWalletAddrModel.php create mode 100644 app/Models/Wallet/Wallet/WalletAddrModel.php create mode 100644 app/Models/Wallet/Wallet/WalletAddrTransactionModel.php create mode 100644 app/Models/Wallet/Wallet/WalletCurrencyModel.php create mode 100644 app/Models/Wallet/Wallet/WalletSettingModel.php create mode 100644 app/Structs/QueueWalletAddrTransactionChangeStruct.php create mode 100644 app/Structs/QueueWalletPlatformUserWithdrawStruct.php create mode 100644 app/Tools/Math.php create mode 100644 app/Tools/Times.php diff --git a/app/Cache/Lock/BaseLock.php b/app/Cache/Lock/BaseLock.php new file mode 100644 index 0000000..8f05f8a --- /dev/null +++ b/app/Cache/Lock/BaseLock.php @@ -0,0 +1,30 @@ +set($key, $value, 'EX', self::TTL, 'NX'); + } + + function setLock($key, $value = '1') + { + return Redis::client()->setex($key, self::TTL , $value); + } + + function unLock($key) + { + return Redis::client()->del($key); + } + + function checkLock($key) + { + return Redis::client()->get($key); + } + +} diff --git a/app/Cache/Lock/WalletPlatformUserTransactionLock.php b/app/Cache/Lock/WalletPlatformUserTransactionLock.php new file mode 100644 index 0000000..fd06f24 --- /dev/null +++ b/app/Cache/Lock/WalletPlatformUserTransactionLock.php @@ -0,0 +1,13 @@ + '钱包金额地址不存在', + self::CODE_WALLET_ADDR_BALANCE_LOW => '钱包余额不足', + ]; } diff --git a/app/Jobs/AddPostQueue.php b/app/Jobs/AddPostQueue.php index a66a7c2..a8717cc 100644 --- a/app/Jobs/AddPostQueue.php +++ b/app/Jobs/AddPostQueue.php @@ -19,7 +19,7 @@ class AddPostQueue implements ShouldQueue */ public function __construct() { - // + //处理用户的推文推送 } /** diff --git a/app/Jobs/UserActiveStatusQueue.php b/app/Jobs/UserActiveStatusQueue.php index 2d56c64..079611a 100644 --- a/app/Jobs/UserActiveStatusQueue.php +++ b/app/Jobs/UserActiveStatusQueue.php @@ -18,7 +18,7 @@ class UserActiveStatusQueue implements ShouldQueue */ public function __construct() { - // + //处理用户活跃状态改变后任务 } /** diff --git a/app/Jobs/WalletAddrTransactionChangeQueue.php b/app/Jobs/WalletAddrTransactionChangeQueue.php new file mode 100644 index 0000000..e061c59 --- /dev/null +++ b/app/Jobs/WalletAddrTransactionChangeQueue.php @@ -0,0 +1,39 @@ +walletAddrTransactionChangeConsumer($wallet_addr_transaction_id, $wallet_addr_transaction_type); + } +} diff --git a/app/Jobs/WalletPlatformUserWithdrawQueue.php b/app/Jobs/WalletPlatformUserWithdrawQueue.php new file mode 100644 index 0000000..ab7e006 --- /dev/null +++ b/app/Jobs/WalletPlatformUserWithdrawQueue.php @@ -0,0 +1,35 @@ +withdrawConsumer($id); + + } +} diff --git a/app/Models/Api/Customer/CustomerUserExtendModel.php b/app/Models/Api/Customer/CustomerUserExtendModel.php index 16c6147..b6bc898 100644 --- a/app/Models/Api/Customer/CustomerUserExtendModel.php +++ b/app/Models/Api/Customer/CustomerUserExtendModel.php @@ -5,7 +5,7 @@ namespace App\Models\Api\Customer; use App\Exceptions\ModelException; use App\Jobs\UserActiveStatusQueue; use App\Models\Api\Post\PostPushBoxModel; -use App\Models\Api\WebSocket\ApiWsHistoryModel; +use App\Models\Api\WebSocket\CustomerWsHistoryModel; use App\Models\Api\Base\ApiBaseModel; use App\Structs\QueueUserActiveStatusStruct; use Carbon\Carbon; @@ -93,7 +93,7 @@ class CustomerUserExtendModel extends ApiBaseModel { if (empty($date)) $date = Carbon::yesterday()->toDateString(); - $oCustomerWsHistoryModel = new ApiWsHistoryModel(); + $oCustomerWsHistoryModel = new CustomerWsHistoryModel(); $aActiveUserIdList = $oCustomerWsHistoryModel->getActiveUserIdList($date); //三日内活跃用户 if (empty($aActiveUserIdList)) return; $oCustomerChangeInfoLogModel = new CustomerChangeInfoLogModel(); @@ -139,7 +139,7 @@ class CustomerUserExtendModel extends ApiBaseModel { try{ Db::beginTransaction(); - $oCustomerWsHistoryModel = new ApiWsHistoryModel(); + $oCustomerWsHistoryModel = new CustomerWsHistoryModel(); $aActiveUserId = $oCustomerWsHistoryModel->findActiveUserId($uid, Carbon::yesterday()->toDateString()); //三日内活跃用户 if(empty($aActiveUserId)) return; $oCustomerUserExtendModel = $this->newQuery()->where('uid', $uid)->first(['uid','is_active']); diff --git a/app/Models/Api/WebSocket/ApiWsHistoryModel.php b/app/Models/Api/WebSocket/CustomerWsHistoryModel.php similarity index 97% rename from app/Models/Api/WebSocket/ApiWsHistoryModel.php rename to app/Models/Api/WebSocket/CustomerWsHistoryModel.php index 3763ab8..a493e6e 100644 --- a/app/Models/Api/WebSocket/ApiWsHistoryModel.php +++ b/app/Models/Api/WebSocket/CustomerWsHistoryModel.php @@ -6,7 +6,7 @@ use App\Models\Api\Base\ApiBaseModel; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\DB; -class ApiWsHistoryModel extends ApiBaseModel +class CustomerWsHistoryModel extends ApiBaseModel { protected $table = 'customer_ws_history'; protected $primaryKey = 'id'; diff --git a/app/Models/Wallet/Platform/WalletPlatformBalanceModel.php b/app/Models/Wallet/Platform/WalletPlatformBalanceModel.php new file mode 100644 index 0000000..92ae8aa --- /dev/null +++ b/app/Models/Wallet/Platform/WalletPlatformBalanceModel.php @@ -0,0 +1,125 @@ + '加', + self::TYPE_DEC => '减', + ]; + + function newPlatformBalance($platform_id, $currency_code): \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Builder|bool + { + $resModel = $this->findItemByWhere(['platform_id' => $platform_id, 'currency_code' => $currency_code]); + if ($resModel) return $resModel; + + $oWalletCurrencyModel = new WalletCurrencyModel(); + $resWalletCurrencyModel = $oWalletCurrencyModel->findByCode($currency_code); + if (!$resWalletCurrencyModel) throw new ModelException('currency_code error'); + + + $insert = [ + 'platform_id' => $platform_id, + 'currency_id' => $oWalletCurrencyModel->id, + 'currency_code' => $oWalletCurrencyModel->code, + 'created_at' => Times::getNowDateTime(), + 'updated_at' => Times::getNowDateTime(), + ]; + return $this->addItem($insert); + } + + function findPlatformBalance($platform_id, $currency_code): \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Builder|null + { + return $this->findItemByWhere(['platform_id' => $platform_id, 'currency_code' => $currency_code]); + } + + function findPlatformBalanceOrCreate($platform_id, $currency_code): \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|\Illuminate\Database\Eloquent\Builder|bool|array|null + { + $resModel = $this->findItemByWhere(['platform_id' => $platform_id, 'currency_code' => $currency_code]); + if (!$resModel) { //不存在就创建一个 + $resModel = $this->newPlatformBalance($platform_id, $currency_code); + } + return $resModel; + } + + //增加平台余额 + function incAvailableBalance($id, $amount): bool + { + $amount = abs($amount); + $oModel = $this->newQuery()->where('id', $id)->first(['total_amount', 'available_amount']); + if (!$oModel) throw new ModelException('找不到id'); + $oModel->frozen_amount = Db::raw('total_amount + ' . $amount); + $oModel->available_amount = Db::raw('available_amount + ' . $amount); + return $oModel->save(); + } + + //扣除平台余额 + function decAvailableBalance($id, $amount): bool + { + $amount = abs($amount); + $oModel = $this->newQuery()->where('id', $id)->first(['total_amount', 'available_amount']); + if (!$oModel) throw new ModelException('找不到id'); + $oModel->frozen_amount = Db::raw('total_amount - ' . $amount); + $oModel->available_amount = Db::raw('available_amount - ' . $amount); + return $oModel->save(); + } + + function decFrozenAmountById($id, $amount): int + { + $amount = abs($amount); + $oModel = $this->newQuery()->where('id', $id)->first(['total_amount', 'frozen_amount']); + if (!$oModel) throw new ModelException('找不到id'); + $oModel->frozen_amount = Db::raw('frozen_amount - ' . $amount); + $oModel->total_amount = Db::raw('total_amount - ' . $amount); + return $oModel->save(); + } + + function unFrozenAmountById($id, $amount): int + { + $amount = abs($amount); + $oModel = $this->newQuery()->where('id', $id)->first(['frozen_amount', 'available_amount']); + if (!$oModel) throw new ModelException('找不到id'); + $oModel->frozen_amount = Db::raw('frozen_amount - ' . $amount); + $oModel->available_amount = Db::raw('available_amount + ' . $amount); + return $oModel->save(); + } + + function frozenAmountById($id, $amount): int + { + $amount = abs($amount); + $oModel = $this->newQuery()->where('id', $id)->first(['frozen_amount', 'available_amount']); + if (!$oModel) throw new ModelException('找不到id'); + $oModel->frozen_amount = Db::raw('frozen_amount + ' . $amount); + $oModel->available_amount = Db::raw('available_amount - ' . $amount); + return $oModel->save(); + } + + + +} diff --git a/app/Models/Wallet/Platform/WalletPlatformBalanceTransactionModel.php b/app/Models/Wallet/Platform/WalletPlatformBalanceTransactionModel.php new file mode 100644 index 0000000..8dc2462 --- /dev/null +++ b/app/Models/Wallet/Platform/WalletPlatformBalanceTransactionModel.php @@ -0,0 +1,241 @@ + '用户充值', + self::TYPE_USER_WITHDRAW => '用户提现', + self::TYPE_ADMIN_ADD => '管理员加', + self::TYPE_ADMIN_DEC => '管理员减', + ]; + + const STATUS_WAITING_QUEUE = 1; + const STATUS_CHAIN_WAITING_CALLBACK = 2; + const STATUS_SUCCESS = 3; + const STATUS_FAIL = 4; + const STATUS = [ + self::STATUS_WAITING_QUEUE => '等待队列', + self::STATUS_CHAIN_WAITING_CALLBACK => '链上等待回调', + self::STATUS_SUCCESS => '成功', + self::STATUS_FAIL => '失败', + ]; + + function addPlatformTransaction( + $type, + $status, + $received_amount, + $entered_amount, + $platform_id, + $currency_code, + $from_user_transaction_id = '', + $from_wallet_addr_id = '', + $wallet_addr = '', + $desc_key = '', + $desc = '', + $remark = '', + ): \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Builder|bool + { + $received_amount = abs($received_amount); + $entered_amount = abs($entered_amount); + $oWalletPlatformBalanceModel = new WalletPlatformBalanceModel(); + $oWalletCurrencyModel = new WalletCurrencyModel(); + $resWalletPlatformBalanceModel = $oWalletPlatformBalanceModel->findPlatformBalance($platform_id, $currency_code); + if (!$resWalletPlatformBalanceModel) throw new ModelException('platform balance not found'); + $resWalletCurrencyModel = $oWalletCurrencyModel->findByCode($currency_code); + if (!$resWalletCurrencyModel) throw new ModelException('currency_code error'); + + if (in_array($type, [self::TYPE_ADMIN_DEC, self::TYPE_USER_WITHDRAW])) { + if (Math::bcComp($resWalletPlatformBalanceModel->available_amount, $received_amount) == -1) throw new ModelException('platform balance not enough'); + } + //提现费率计算 + $fee_amount = 0; + if ($type == self::TYPE_USER_WITHDRAW) { + $fee_amount = $oWalletCurrencyModel->computeTransferRate($received_amount, $oWalletCurrencyModel->transfer_rate); + $entered_amount = Math::bcSub($received_amount, $fee_amount); + } + + if($type == self::TYPE_USER_RECHARGE){ + $fee_amount = Math::bcSub($received_amount, $entered_amount); + } + + $insert = [ + 'type' => $type, + 'status' => $status, + 'platform_id' => $platform_id, + 'currency_id' => $resWalletPlatformBalanceModel->currency_id, + 'currency_code' => $resWalletPlatformBalanceModel->currency_code, + 'currency_type' => $resWalletPlatformBalanceModel->currency_type, + 'from_wallet_addr_id' => $from_wallet_addr_id, + 'from_user_transaction_id' => $from_user_transaction_id, + 'wallet_addr' => $wallet_addr, + 'received_amount' => $received_amount, + 'entered_amount' => $entered_amount, + 'fee_amount' => $fee_amount, + 'before_total_amount' => $resWalletPlatformBalanceModel->total_amount, + 'after_total_amount' => Math::bcAdd($resWalletPlatformBalanceModel->total_amount, $entered_amount), + 'desc_key' => $desc_key, + 'desc' => $desc, + 'remark' => $remark, + ]; +// $insert['sign'] = ''; + $insert['created_at'] = Times::getNowDateTime(); + $insert['updated_at'] = Times::getNowDateTime(); + return $this->addItem($insert); + } + + //增加提现账变并且冻结平台金额 + function newWithdrawPlatformTransaction( + $amount, + $platform_id, + $currency_code, + ): \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Builder|bool + { + try { + Db::beginTransaction(); + $resModel = $this->addPlatformTransaction(self::TYPE_USER_WITHDRAW, self::STATUS_WAITING_QUEUE, $amount,$amount, $platform_id, $currency_code); + if(!$resModel) throw new ModelException('addPlatformTransaction error'); + $oWalletPlatformBalanceModel = new WalletPlatformBalanceModel(); + $res = $oWalletPlatformBalanceModel->findPlatformBalance($platform_id,$currency_code); + if (!$res) throw new ModelException('findPlatformBalance error'); + $res = $oWalletPlatformBalanceModel->frozenAmountById($oWalletPlatformBalanceModel->id, $amount); //变更平台余额 + if (!$res) throw new ModelException('frozenAmountById error'); + Db::commit(); + return $resModel; + }catch (\Exception $e) { + Db::rollBack(); + Log::error('newWithdrawPlatformTransaction', ['code' => $e->getCode(), 'error' => $e->getMessage()]); + return false; + } + + } + + //成功提现处理 + function withdrawSuccByUserTransactionId($user_transaction_id, $remark = ''): bool + { + try{ + Db::beginTransaction(); + $resModel = $this->findItemByWhere(['from_user_transaction_id' => $user_transaction_id, 'type' => self::TYPE_USER_WITHDRAW]); + if (!$resModel) return false; + if (!in_array($resModel->status, [self::STATUS_WAITING_QUEUE, self::STATUS_CHAIN_WAITING_CALLBACK])) return false; + $resModel->status = self::STATUS_SUCCESS; + if(!empty($remark)) $resModel->remark = $remark; + $resModel->updated_at = Times::getNowDateTime(); + $res = $resModel->save(); + if(!$res) throw new ModelException('save error'); + + //扣除平台冻结余额 + $oWalletPlatformBalanceModel = new WalletPlatformBalanceModel(); + $resWalletPlatformBalanceModel = $oWalletPlatformBalanceModel->findPlatformBalance($resModel->platform_id, $resModel->currency_code); + if (!$resWalletPlatformBalanceModel) throw new ModelException('platform balance not found'); + $res = $oWalletPlatformBalanceModel->decFrozenAmountById($resWalletPlatformBalanceModel->id, $resModel->received_amount); + if (!$res) throw new ModelException('incBalance error'); + + Db::commit(); + return true; + }catch (\Exception $e){ + Db::rollBack(); + Log::error('withdrawSuccByUserTransactionIdErr', ['code' => $e->getCode(), 'error' => $e->getMessage()]); + return false; + } + + } + + + //失败提现处理 + function withdrawErrByUserTransactionId($user_transaction_id, $remark = ''): bool + { + try { + Db::beginTransaction(); + $resModel = $this->findItemByWhere(['from_user_transaction_id' => $user_transaction_id, 'type' => self::TYPE_USER_WITHDRAW]); + if (!$resModel) throw new ModelException('user transaction not found'); + if (!in_array($resModel->status, [self::STATUS_WAITING_QUEUE, self::STATUS_CHAIN_WAITING_CALLBACK])) throw new ModelException('status error'); + //更改账变状态 + $resModel->status = self::STATUS_FAIL; + if(!empty($remark)) $resModel->remark = $remark; + $res = $resModel->save(); + if (!$res) throw new ModelException('save error'); + + //解冻平台余额 + $oWalletPlatformBalanceModel = new WalletPlatformBalanceModel(); + $resWalletPlatformBalanceModel = $oWalletPlatformBalanceModel->findPlatformBalance($resModel->platform_id, $resModel->currency_code); + if (!$resWalletPlatformBalanceModel) throw new ModelException('platform balance not found'); + $res = $oWalletPlatformBalanceModel->unFrozenAmountById($resWalletPlatformBalanceModel->id, $resModel->received_amount); + if (!$res) throw new ModelException('incBalance error'); + Db::commit(); + return true; + } catch (\Exception $e) { + Db::rollBack(); + Log::error('withdrawErrByUserTransactionIdErr', ['code' => $e->getCode(), 'error' => $e->getMessage()]); + return false; + } + } + + function newRechargePlatformTransaction( + $amount, + $platform_id, + $currency_code, + ): \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Builder|bool + { + try { + Db::beginTransaction(); + $resModel = $this->addPlatformTransaction(self::TYPE_USER_RECHARGE, self::STATUS_SUCCESS, $amount,$amount, $platform_id, $currency_code); + if(!$resModel) throw new ModelException('addPlatformTransaction error'); + $oWalletPlatformBalanceModel = new WalletPlatformBalanceModel(); + $resWalletPlatformBalanceModel = $oWalletPlatformBalanceModel->findPlatformBalance($platform_id,$currency_code); + if (!$resWalletPlatformBalanceModel) throw new ModelException('findPlatformBalance error'); + $res = $oWalletPlatformBalanceModel->incAvailableBalance($resWalletPlatformBalanceModel->id, $amount); //变更平台余额 + if (!$res) throw new ModelException('frozenAmountById error'); + Db::commit(); + return $resModel; + }catch (\Exception $e) { + Db::rollBack(); + Log::error('newWithdrawPlatformTransaction', ['code' => $e->getCode(), 'error' => $e->getMessage()]); + return false; + } + + } + + +} diff --git a/app/Models/Wallet/Platform/WalletPlatformModel.php b/app/Models/Wallet/Platform/WalletPlatformModel.php new file mode 100644 index 0000000..3d242f1 --- /dev/null +++ b/app/Models/Wallet/Platform/WalletPlatformModel.php @@ -0,0 +1,58 @@ + $name, + 'code' => $code, + 'created_at' => Times::getNowDateTime(), + ]; + try { + Db::beginTransaction(); + $resModel = $this->addItem($insert); + if(!$resModel) throw new ModelException('添加失败'); + + $appid_key = $resModel->id . $resModel->name . $resModel->code; + $resModel->appid = Tools::getMd5($appid_key); + $resModel->secret = Tools::generateRandStr(32); + $res = $resModel->save(); + if(!$res) throw new ModelException('添加appid失败'); + + Db::commit(); + }catch (\Exception $e) { + Db::rollBack(); + throw $e; + } + return $resModel; + } + + //@@给平台下发用户信息 + static function userTransactionNotifyToPlatform($user_transaction_id) + { + + } + +} diff --git a/app/Models/Wallet/PlatformUser/WalletPlatformUserTransactionModel.php b/app/Models/Wallet/PlatformUser/WalletPlatformUserTransactionModel.php new file mode 100644 index 0000000..3a459d6 --- /dev/null +++ b/app/Models/Wallet/PlatformUser/WalletPlatformUserTransactionModel.php @@ -0,0 +1,346 @@ + '充值', + self::TYPE_WITHDRAW => '提现', + ]; + + const STATUS_WAITING_QUEUE = 1; + const STATUS_CODE_PROCESSING = 2; + const STATUS_CHAIN_WAITING = 3; + const STATUS_SUCCESS = 4; + const STATUS_FAIL = 5; + const STATUS = [ + self::STATUS_WAITING_QUEUE => '等待队列', + self::STATUS_CODE_PROCESSING => '程序处理中', + self::STATUS_CHAIN_WAITING => '链上等待', + self::STATUS_SUCCESS => '成功', + self::STATUS_FAIL => '失败', + ]; + + const DESC_KEY_NOT_FOUND_WITHDRAW_AMOUNT_WALLET_ADDR = 'notFoundWithdrawAmountWalletAddr'; + const DESC_KEY_WITHDRAW_WALLET_AMOUNT_LOW = 'withdrawWalletAmountLow'; + const DESC_KEY_WITHDRAW_CALL_CHAIN_WRONG = 'withdrawCallChainWrong'; + + const DESC = [ + self::DESC_KEY_NOT_FOUND_WITHDRAW_AMOUNT_WALLET_ADDR => '未找到足额提现金额钱包地址', + self::DESC_KEY_WITHDRAW_WALLET_AMOUNT_LOW => '提现钱包金额不足', + self::DESC_KEY_WITHDRAW_CALL_CHAIN_WRONG => '提现调用链上接口失败', + ]; + + //请求提现发起 + function withdrawFirst($amount, $platform_id, $uid, $currency_code, $desc_key = null, $remark = ''): \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Builder|bool + { + try { + Db::beginTransaction(); + $amount = abs($amount); + //检查平台余额 + $oWalletPlatformBalanceModel = new WalletPlatformBalanceModel(); + $resWalletPlatformBalanceModel = $oWalletPlatformBalanceModel->findPlatformBalance($platform_id, $currency_code); + if (!$resWalletPlatformBalanceModel) throw new ModelException('platform_id or currency_code error'); + if (Math::bcComp($resWalletPlatformBalanceModel->available_amount, $amount) == -1) throw new ModelException('platform balance not enough'); + + //冻结平台余额并增加账变 + $oWalletPlatformBalanceTransactionModel = new WalletPlatformBalanceTransactionModel(); + $resWalletPlatformBalanceTransactionModel = $oWalletPlatformBalanceTransactionModel->newWithdrawPlatformTransaction($amount, $platform_id, $currency_code); + if (!$resWalletPlatformBalanceTransactionModel) throw new ModelException('newWithdrawPlatformTransaction error'); + + //根据币种计算费率 + $oWalletCurrencyModel = new WalletCurrencyModel(); + $oWalletCurrencyModel = $oWalletCurrencyModel->findByCode($currency_code); + if (!$oWalletCurrencyModel) throw new ModelException('currency_code error'); + $fee_amount = $oWalletCurrencyModel->computeTransferRate($amount, $oWalletCurrencyModel->transfer_rate); + //预计费率计算 + if ($fee_amount <= 0) throw new ModelException('fee_amount error'); + $desc = isset(self::DESC[$desc_key]) ? self::DESC[$desc_key] : ''; + $resModel = $this->addWithdrawTransaction($platform_id, $uid, $oWalletCurrencyModel, $amount, $fee_amount, $desc_key, $desc, $remark); + if (!$resModel) throw new ModelException('addItem error'); + $oWalletPlatformBalanceTransactionModel->updateItem([ + 'id' => $resWalletPlatformBalanceTransactionModel->id, + 'from_user_transaction_id' => $resModel->id, + ]); + $this->putWithdrawToQueue($resModel->id); //投递到队列处理 + Db::commit(); + Log::info('newWithdrawSucc', ['platform_id' => $platform_id, 'uid' => $uid, 'currency_code' => $currency_code, 'amount' => $amount]); + return $resModel; + } catch (\Exception $e) { + Db::rollBack(); + Log::error('newWithdrawErr', ['platform_id' => $platform_id, 'uid' => $uid, 'currency_code' => $currency_code, 'amount' => $amount, 'msg' => $e->getMessage()]); + return false; + } + } + + function addWithdrawTransaction($platform_id, $uid, $oWalletCurrencyModel, $amount, $fee_amount, $desc_key = null, $desc = '', $remark = ''): \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Builder|bool + { + $insert = [ + 'type' => self::TYPE_WITHDRAW, + 'status' => self::STATUS_WAITING_QUEUE, + 'platform_id' => $platform_id, + 'uid' => $uid, + 'currency_id' => $oWalletCurrencyModel->id, + 'currency_code' => $oWalletCurrencyModel->code, + 'currency_type' => $oWalletCurrencyModel->type, + 'received_amount' => abs($amount), +// 'entered_amount' => abs($entered_amount), + 'fee_amount' => abs($fee_amount), + 'desc_key' => $desc_key ?? '', + 'desc' => $desc, + 'remark' => $remark, + 'created_at' => date('Y-m-d H:i:s'), + ]; + return $this->addItem($insert); + } + + function addRechargeTransaction($platform_id, $uid, $oWalletCurrencyModel, $amount, $desc_key = null, $desc = '', $remark = ''): \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Builder|bool + { + $amount = abs($amount); + $insert = [ + 'type' => self::TYPE_RECHARGE, + 'status' => self::STATUS_SUCCESS, //充提成功 + 'platform_id' => $platform_id, + 'uid' => $uid, + 'currency_id' => $oWalletCurrencyModel->id, + 'currency_code' => $oWalletCurrencyModel->code, + 'currency_type' => $oWalletCurrencyModel->type, + 'received_amount' => $amount, + 'entered_amount' => $amount, + 'desc_key' => $desc_key ?? '', + 'desc' => $desc, + 'remark' => $remark, + 'created_at' => date('Y-m-d H:i:s'), + ]; + return $this->addItem($insert); + } + + function addTransactionWithPlatformBalance($platform_id, $uid, $oWalletCurrencyModel, $amount, $desc_key = null, $desc = '', $remark = '') + { + try { + Db::beginTransaction(); + $resModel = $this->addRechargeTransaction($platform_id, $uid, $oWalletCurrencyModel, $amount, $desc_key, $desc, $remark); + if(!$resModel) throw new ModelException('addRechargeTransaction error'); + $oWalletPlatformBalanceTransactionModel = new WalletPlatformBalanceTransactionModel(); + $res = $oWalletPlatformBalanceTransactionModel->newRechargePlatformTransaction($amount, $platform_id, $oWalletCurrencyModel->code); + if(!$res) throw new ModelException('newRechargePlatformTransaction error'); + Db::commit(); + return true; + }catch (\Exception $e) { + Db::rollBack(); + Log::error('addTransactionWithBalance', ['platform_id' => $platform_id, 'uid' => $uid, 'amount' => $amount, 'msg' => $e->getMessage()]); + return false; + } + + } + + //@@投递到rabbitmq队列处理 + function putWithdrawToQueue($wallet_platform_user_transaction_id): void + { + $params = [ + 'wallet_platform_user_transaction_id' => $wallet_platform_user_transaction_id, + ]; + WalletPlatformUserWithdrawQueue::dispatch($params)->onQueue(QueueWalletPlatformUserWithdrawStruct::QUEUE_NAME); + } + + //处理用户提现队列,发起链上提现 + function withdrawConsumer($id): void + { + //@新增任务处理表 + //查询前先检查redis中锁 + $oWalletPlatformUserTransactionLock = new WalletPlatformUserTransactionLock(); + $isLock = $oWalletPlatformUserTransactionLock->checkLock($oWalletPlatformUserTransactionLock->getWithdrawKey($id)); + if ($isLock) return; //有锁就跳过 + //加锁 + $isLock = $oWalletPlatformUserTransactionLock->setLock($oWalletPlatformUserTransactionLock->getWithdrawKey($id)); + if (!$isLock) return; //加锁失败 + //查询任务 + $resWalletPlatformUserTransactionModel = $this->findItem($id); + if (!$resWalletPlatformUserTransactionModel) throw new ModelException('id error'); + if ($resWalletPlatformUserTransactionModel->type != self::TYPE_WITHDRAW) throw new ModelException('type error'); + if ($resWalletPlatformUserTransactionModel->status != self::STATUS_WAITING_QUEUE) throw new ModelException('status error'); + $resWalletPlatformUserTransactionModel->status = self::STATUS_CODE_PROCESSING; + $resWalletPlatformUserTransactionModel->save(); + + //获取提现地址 + $oWalletAddrModel = new WalletAddrModel(); + try { + $resWalletAddrModel = $oWalletAddrModel->findWithdrawAddrWithAmount($resWalletPlatformUserTransactionModel->received_amount, $resWalletPlatformUserTransactionModel->currency_code); + if (!$resWalletAddrModel) throw new ModelException('not found withdraw amount wallet addr'); + } catch (ModelException $e) { //未找到足额提现金额钱包地址 + $code = $e->getCode(); + $msg = $e->getMessage(); + $resWalletPlatformUserTransactionModel->status = self::STATUS_FAIL; + if ($code == ModelException::CODE_WALLET_AMOUNT_ADDR_NOT_FOUND) { //未找到足额提现金额钱包地址 + $resWalletPlatformUserTransactionModel->desc_key = self::DESC_KEY_NOT_FOUND_WITHDRAW_AMOUNT_WALLET_ADDR; + $resWalletPlatformUserTransactionModel->desc = self::DESC[self::DESC_KEY_NOT_FOUND_WITHDRAW_AMOUNT_WALLET_ADDR]; + } + if ($code == ModelException::CODE_WALLET_ADDR_BALANCE_LOW) { //提现钱包金额不足 + $resWalletPlatformUserTransactionModel->desc_key = self::DESC_KEY_WITHDRAW_WALLET_AMOUNT_LOW; + $resWalletPlatformUserTransactionModel->desc = self::DESC[self::DESC_KEY_WITHDRAW_WALLET_AMOUNT_LOW]; + } + + $resWalletPlatformUserTransactionModel->save(); + $this->withdrawFailUnLock($id); //解锁 + return; + } + + try { + $resWalletPlatformUserTransactionModel->from_wallet_addr_id = $resWalletAddrModel->id; + $resWalletPlatformUserTransactionModel->wallet_addr = $resWalletAddrModel->addr; + $resWalletPlatformUserTransactionModel->save(); + + //增加钱包账变 + $oWalletAddrTransactionModel = new WalletAddrTransactionModel(); + $resWalletAddrTransactionModel = $oWalletAddrTransactionModel->newPlatformUserWithdraw( + $resWalletAddrModel->id, + $resWalletPlatformUserTransactionModel->entered_amount, + $resWalletPlatformUserTransactionModel->id + ); + if (!$resWalletAddrTransactionModel) { + $this->withdrawFailUnLock($id); //解锁 + return; + } + //调用链上接口 + $resChain = $oWalletAddrModel->callWalletAddrChainTransferApi($resWalletPlatformUserTransactionModel->id, $resWalletAddrModel->id); + if (!$resChain) { //链上提现失败 + $resWalletPlatformUserTransactionModel->status = self::STATUS_FAIL; + $resWalletPlatformUserTransactionModel->desc_key = self::DESC_KEY_WITHDRAW_CALL_CHAIN_WRONG; + $resWalletPlatformUserTransactionModel->desc = self::DESC[self::DESC_KEY_WITHDRAW_CALL_CHAIN_WRONG]; + $resWalletPlatformUserTransactionModel->save(); + //解冻钱包余额 + $oWalletAddrTransactionModel->platformUserWithdrawCallback($resWalletAddrTransactionModel->id, $resWalletAddrTransactionModel->received_amount, WalletAddrTransactionModel::STATUS_FAIL); + $this->withdrawFailUnLock($id); //解锁 + return; + } + $resWalletPlatformUserTransactionModel->status = self::STATUS_CHAIN_WAITING; + $resWalletPlatformUserTransactionModel->save(); + $this->withdrawFailUnLock($id); //解锁 + + } catch (\Exception $e) { + Log::error('withdrawConsumer', ['id' => $id, 'msg' => $e->getMessage()]); + $this->withdrawFailUnLock($id); //解锁 + } + + } + + //用户提现失败解锁并返还平台余额 + function withdrawFailUnLock($id): void + { + //返还用户余额 + $oWalletPlatformBalanceTransactionModel = new WalletPlatformBalanceTransactionModel(); + $oWalletPlatformBalanceTransactionModel->withdrawErrByUserTransactionId($id); + //解锁 + $oWalletPlatformUserTransactionLock = new WalletPlatformUserTransactionLock(); + $oWalletPlatformUserTransactionLock->unLock($oWalletPlatformUserTransactionLock->getWithdrawKey($id)); + } + + //提现链上回调监听 + function listenWithdrawCallback($wallet_addr_transaction_id): void + { + Log::info('listenWithdrawCallback', ['wallet_addr_transaction_id' => $wallet_addr_transaction_id]); + //查找钱包账变 + $oWalletAddrTransactionModel = new WalletAddrTransactionModel(); + $resWalletAddrTransactionModel = $oWalletAddrTransactionModel->findItem($wallet_addr_transaction_id); + if (!$resWalletAddrTransactionModel) throw new ModelException('wallet_addr_transaction_id error'); + if ($resWalletAddrTransactionModel->type !== WalletAddrTransactionModel::TYPE_TRANSFER) throw new ModelException('wallet_addr_transaction_type error'); + if (!in_array($resWalletAddrTransactionModel->status, [WalletAddrTransactionModel::STATUS_SUCCESS, WalletAddrTransactionModel::STATUS_FAIL])) throw new ModelException('status error'); + if ($resWalletAddrTransactionModel->status == WalletAddrTransactionModel::STATUS_SUCCESS) $status = self::STATUS_SUCCESS; + if ($resWalletAddrTransactionModel->status == WalletAddrTransactionModel::STATUS_FAIL) $status = self::STATUS_FAIL; + if (empty($resWalletAddrTransactionModel->platform_user_transaction_id)) throw new ModelException('platform_user_transaction_id error'); + $id = $resWalletAddrTransactionModel->platform_user_transaction_id; + + if (!in_array($status, [self::STATUS_SUCCESS, self::STATUS_FAIL])) throw new ModelException('status error'); + $resWalletPlatformUserTransactionModel = $this->findItem($id); + if (!$resWalletPlatformUserTransactionModel) throw new ModelException('id error'); + if ($resWalletPlatformUserTransactionModel->type != self::TYPE_WITHDRAW) throw new ModelException('type error'); + if ($resWalletPlatformUserTransactionModel->status != self::STATUS_CHAIN_WAITING) throw new ModelException('status error'); + //用户账变处理 + $resWalletPlatformUserTransactionModel->status = $status; + $resWalletPlatformUserTransactionModel->save(); + + //平台余额和账变处理 + $oWalletPlatformBalanceTransactionModel = new WalletPlatformBalanceTransactionModel(); + if ($status == self::STATUS_SUCCESS) { //成功 + $oWalletPlatformBalanceTransactionModel->withdrawSuccByUserTransactionId($id); + } else { //失败 + $oWalletPlatformBalanceTransactionModel->withdrawErrByUserTransactionId($id); + } + } + + function listenRechargeCallback($wallet_addr_transaction_id): void + { + Log::info('listenRechargeCallback', ['wallet_addr_transaction_id' => $wallet_addr_transaction_id]); + //查找钱包账变 + $oWalletAddrTransactionModel = new WalletAddrTransactionModel(); + $resWalletAddrTransactionModel = $oWalletAddrTransactionModel->findItem($wallet_addr_transaction_id); + if (!$resWalletAddrTransactionModel) throw new ModelException('wallet_addr_transaction_id error'); + if ($resWalletAddrTransactionModel->type !== WalletAddrTransactionModel::TYPE_RECHARGE) throw new ModelException('wallet_addr_transaction_type error'); + if (!in_array($resWalletAddrTransactionModel->status, [WalletAddrTransactionModel::STATUS_SUCCESS, WalletAddrTransactionModel::STATUS_FAIL])) throw new ModelException('status error'); + if ($resWalletAddrTransactionModel->status == WalletAddrTransactionModel::STATUS_SUCCESS) $status = self::STATUS_SUCCESS; + if ($resWalletAddrTransactionModel->status == WalletAddrTransactionModel::STATUS_FAIL) $status = self::STATUS_FAIL; + if ($status != self::STATUS_SUCCESS) throw new ModelException('status not success'); + + $oWalletCurrencyModel = new WalletCurrencyModel(); + $resWalletCurrencyModel = $oWalletCurrencyModel->findItem($resWalletAddrTransactionModel->currency_id); + + //查找用户绑定钱包地址 + $oWalletPlatformUserWalletAddrModel = new WalletPlatformUserWalletAddrModel(); + $resWalletPlatformUserWalletAddrModel = $oWalletPlatformUserWalletAddrModel->findByWalletAddrId($resWalletAddrTransactionModel->wallet_addr_id); + if (!$resWalletPlatformUserWalletAddrModel) throw new ModelException('找不到wallet_addr_id绑定用户'); + + //给用户增加账变/平台增加账变增加余额 + $this->addTransactionWithPlatformBalance( + $resWalletPlatformUserWalletAddrModel->platform_id, + $resWalletPlatformUserWalletAddrModel->uid, + $resWalletCurrencyModel, + $resWalletAddrTransactionModel->received_amount, + ); + + } + + +} diff --git a/app/Models/Wallet/PlatformUser/WalletPlatformUserWalletAddrHistoryModel.php b/app/Models/Wallet/PlatformUser/WalletPlatformUserWalletAddrHistoryModel.php new file mode 100644 index 0000000..bce51dc --- /dev/null +++ b/app/Models/Wallet/PlatformUser/WalletPlatformUserWalletAddrHistoryModel.php @@ -0,0 +1,44 @@ + '绑定', + self::TYPE_UNBIND => '解绑', + ]; + + function addUserWalletAddrHistory($aItem,$type,$desc = ''): \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Builder|array|null + { + if(!in_array($type,self::TYPE)) throw new ModelException('type error'); + unset($aItem['id']);//去掉id(主键) + $aItem['type'] = $type; + $aItem['desc'] = $desc; + $sDateTime = date('Y-m-d H:i:s'); + $aItem['created_at'] = $sDateTime; + return $this->addItem($aItem); + } + +} diff --git a/app/Models/Wallet/PlatformUser/WalletPlatformUserWalletAddrModel.php b/app/Models/Wallet/PlatformUser/WalletPlatformUserWalletAddrModel.php new file mode 100644 index 0000000..5401385 --- /dev/null +++ b/app/Models/Wallet/PlatformUser/WalletPlatformUserWalletAddrModel.php @@ -0,0 +1,81 @@ +findUserWalletAddr($platform_id, $uid, $currency_code); + if($mode == WalletPlatformUserWalletAddrHistoryModel::TYPE_BIND && $resOldModel){ //默认检测是否已经绑定过 + throw new ModelException('已经绑定过地址'); + } + try { + DB::beginTransaction(); + //从钱包地址表取值 + $walletAddrModel = new WalletAddrModel(); + $oWalletPlatformUserWalletAddrHistoryModel = new WalletPlatformUserWalletAddrHistoryModel(); + $walletAddrModel = $walletAddrModel->findUnusedAddrPutUsing($currency_code); + if(!$walletAddrModel) throw new ModelException('获取新地址失败'); + if($mode == WalletPlatformUserWalletAddrHistoryModel::TYPE_UNBIND){ + if($resOldModel){ + $res = $this->delItem($resOldModel->id); + if(!$res) throw new ModelException('删除原有地址失败'); + $res = $oWalletPlatformUserWalletAddrHistoryModel->addUserWalletAddrHistory($resOldModel->toArray(),WalletPlatformUserWalletAddrHistoryModel::TYPE_UNBIND); + if(!$res) throw new ModelException('增加历史绑定地址失败'); + } + } + + $insert = [ + 'platform_id' => $platform_id, + 'uid' => $uid, + 'wallet_addr_id' => $walletAddrModel->id, + 'wallet_addr' => $walletAddrModel->addr, + 'currency_id' => $walletAddrModel->currency_id, + 'currency_code' => $walletAddrModel->currency_code, + 'created_at' => Times::getNowDateTime(), + ]; + $resModel = $this->addItem($insert); + if($resModel) $oWalletPlatformUserWalletAddrHistoryModel->addUserWalletAddrHistory($resModel->toArray(),WalletPlatformUserWalletAddrHistoryModel::TYPE_BIND); + DB::commit(); + }catch (\Exception $e) { + DB::rollBack(); + throw $e; + } + + return $resModel; + } + + function findUserWalletAddr($platform_id, $uid, $currency_code): \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|\Illuminate\Database\Eloquent\Builder|array|null + { + return $this->findItemByWhere(['platform_id' => $platform_id, 'uid' => $uid, 'currency_code' => $currency_code]); + } + + function findByWalletAddrId($wallet_addr_id): \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|\Illuminate\Database\Eloquent\Builder|array|null + { + return $this->findItemByWhere(['from_wallet_addr_id' => $wallet_addr_id]); + } + +} diff --git a/app/Models/Wallet/Wallet/WalletAddrModel.php b/app/Models/Wallet/Wallet/WalletAddrModel.php new file mode 100644 index 0000000..29af06f --- /dev/null +++ b/app/Models/Wallet/Wallet/WalletAddrModel.php @@ -0,0 +1,312 @@ + '用户', + self::ROLE_SYSTEM => '系统', + ]; + + const TYPE_CASH = 1; + const TYPE_CRYPTO_COIN = 2; + const TYPE = [ + self::TYPE_CASH => '现金', + self::TYPE_CRYPTO_COIN => '加密货币', + ]; + + const USE_STATUS_UNUSED = 1; + const USE_STATUS_USING = 2; + const USE_STATUS_USED = 3; + const USE_STATUS = [ + self::USE_STATUS_UNUSED => '未使用', + self::USE_STATUS_USING => '使用中', + self::USE_STATUS_USED => '已使用', + ]; + + const DESC_KEY_USER_RECHARGE = 'userRecharge'; + const DESC_KEY_USER_WITHDRAW = 'userWithdraw'; + const DESC = [ + self::DESC_KEY_USER_RECHARGE => '用户充值', + self::DESC_KEY_USER_WITHDRAW => '用户提现', + ]; + + function addAddr($addr, $secret, $currency_code = WalletCurrencyModel::CODE_USDT_TRC20, $desc_key = '', $desc = '', $remark = '', $available_amount = 0, $role = self::ROLE_USER, $type = self::TYPE_CRYPTO_COIN, $use_status = self::USE_STATUS_UNUSED): int + { + $oWalletCurrencyModel = (new WalletCurrencyModel())->findByCode($currency_code, ['id', 'code']); + if (!$oWalletCurrencyModel) throw new ModelException('币种不存在'); + return $this->newQuery()->insertGetId([ + 'role' => $role, + 'type' => $type, + 'use_status' => $use_status, + 'addr' => $addr, + 'secret' => $secret, + 'desc_key' => $desc_key, + 'desc' => $desc, + 'remark' => $remark, + 'currency_id' => $oWalletCurrencyModel->id, + 'currency_code' => $oWalletCurrencyModel->code, + 'total_amount' => $available_amount, + 'available_amount' => $available_amount, + 'created_at' => date('Y-m-d H:i:s'), + ]); + } + + function findAddr($currency_code = WalletCurrencyModel::CODE_USDT_TRC20, $use_status = self::USE_STATUS_UNUSED): \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Builder|null + { + return $this->newQuery()->where('currency_code', $currency_code)->where('use_status', $use_status)->first(); + } + + function findUnusedAddrPutUsing($currency_code = WalletCurrencyModel::CODE_USDT_TRC20): \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Builder|null + { + $resModel = $this->newQuery()->where('currency_code', $currency_code)->where('use_status', self::USE_STATUS_UNUSED)->first(); + if (!$resModel) { + //@@获取不到地址就发送到队列生成新地址 + $this->makeNewWalletAddr($currency_code); + throw new ModelException('获取地址失败'); + } + $resModel->use_status = self::USE_STATUS_USING; + $resModel->save(); + return $resModel; + + } + + function makeNewWalletAddr($currency_code = WalletCurrencyModel::CODE_USDT_TRC20) + { + + } + + function changeUseStatus($id, $use_status): int + { + return $this->newQuery()->where('id', $id)->update(['use_status' => $use_status]); + } + + function incAvailableAmount($id, $amount): int + { + $amount = abs($amount); + $oModel = $this->newQuery()->where('id', $id)->first(['total_amount', 'available_amount']); + if (!$oModel) throw new ModelException('找不到wallet_addr_id'); + $oModel->frozen_amount = Db::raw('available_amount + ' . $amount); + $oModel->total_amount = Db::raw('total_amount + ' . $amount); + return $oModel->save(); + } + + function decAvailableAmount($id, $amount): int + { + $amount = abs($amount); + $oModel = $this->newQuery()->where('id', $id)->first(['total_amount', 'available_amount']); + if (!$oModel) throw new ModelException('找不到wallet_addr_id'); + $oModel->frozen_amount = Db::raw('available_amount - ' . $amount); + $oModel->total_amount = Db::raw('total_amount - ' . $amount); + return $oModel->save(); + } + + function decFrozenAmountById($id, $amount): int + { + $amount = abs($amount); + $oModel = $this->newQuery()->where('id', $id)->first(['total_amount', 'frozen_amount']); + if (!$oModel) throw new ModelException('找不到wallet_addr_id'); + $oModel->frozen_amount = Db::raw('frozen_amount - ' . $amount); + $oModel->total_amount = Db::raw('total_amount - ' . $amount); + return $oModel->save(); + } + + function unFrozenAmountById($id, $amount): int + { + $amount = abs($amount); + $oModel = $this->newQuery()->where('id', $id)->first(['frozen_amount', 'available_amount']); + if (!$oModel) throw new ModelException('找不到wallet_addr_id'); + $oModel->frozen_amount = Db::raw('frozen_amount - ' . $amount); + $oModel->available_amount = Db::raw('available_amount + ' . $amount); + return $oModel->save(); + } + + function frozenAmountById($id, $amount): int + { + $amount = abs($amount); + $oModel = $this->newQuery()->where('id', $id)->first(['frozen_amount', 'available_amount']); + if (!$oModel) throw new ModelException('找不到wallet_addr_id'); + $oModel->frozen_amount = Db::raw('frozen_amount + ' . $amount); + $oModel->available_amount = Db::raw('available_amount - ' . $amount); + return $oModel->save(); + } + + /** + * 充值回调监听 + * @throws ModelException + */ + function walletAddrTransactionChangeConsumer($wallet_addr_transaction_id,$wallet_addr_transaction_type): void + { + Log::info('walletAddrTransactionChangeConsumer', ['wallet_addr_transaction_id' => $wallet_addr_transaction_id,]); + $oWalletPlatformUserTransactionModel = new WalletPlatformUserTransactionModel(); + if($wallet_addr_transaction_type == WalletAddrTransactionModel::TYPE_RECHARGE){ + $oWalletPlatformUserTransactionModel->listenRechargeCallback($wallet_addr_transaction_id); + } + if($wallet_addr_transaction_type == WalletAddrTransactionModel::TYPE_TRANSFER){ + $oWalletPlatformUserTransactionModel->listenWithdrawCallback($wallet_addr_transaction_id); + } + + + } + + //提现链上回调监听 + function listenWalletAddrCallback($chain_sn, $token, $type, $addr, $amount, $status): void + { + //@@新增表记录每个地址的监听消息 + Log::info('listenWalletAddrCallbackParams', ['token' => $token, 'addr' => $addr, 'amount' => $amount, 'status' => $status]); + if (!in_array($type, [WalletAddrTransactionModel::TYPE_RECHARGE, WalletAddrTransactionModel::TYPE_TRANSFER])) { + Log::error('listenWalletAddrCallbackErr', ['token' => $token, 'addr' => $addr, 'amount' => $amount, 'status' => $status, 'error' => 'type类型错误']); + throw new ModelException('type类型错误'); + } + //充值不成功默认不处理 + if($type == WalletAddrTransactionModel::TYPE_RECHARGE && $status != WalletAddrTransactionModel::STATUS_SUCCESS){ + Log::error('listenWalletAddrCallbackStatus', ['token' => $token, 'addr' => $addr, 'amount' => $amount, 'status' => $status, 'error' => '充值失败']); + throw new ModelException('链上充值失败'); + } + + try { + Db::beginTransaction(); + $oWalletCurrencyModel = (new WalletCurrencyModel())->findByToken($token); + if (!$oWalletCurrencyModel) { + Log::error('listenWalletAddrCallbackErr', ['token' => $token, 'addr' => $addr, 'amount' => $amount, 'status' => $status, 'error' => '币种不存在']); + throw new ModelException('币种不存在'); + } + //查找钱包地址 + $resModel = $this->findItemByWhere([ + 'currency_id' => $oWalletCurrencyModel->id, + 'addr' => $addr, + ]); + if (!$resModel) { + Log::error('listenWalletAddrCallbackErr', ['token' => $token, 'addr' => $addr, 'amount' => $amount, 'status' => $status, 'error' => '地址不存在']); + throw new ModelException('地址不存在'); + } + + if ($type == WalletAddrTransactionModel::TYPE_RECHARGE) { //充值到账 + $res = $this->incAvailableAmount($resModel->id, $amount); + } else { //提现到账 + //查询账变表提现记录中是否存在 + $oWalletAddrTransactionModel = new WalletAddrTransactionModel(); + $resWalletAddrTransactionModel = $oWalletAddrTransactionModel->findItemByWhere([ + 'wallet_addr_id' => $resModel->id, + 'type' => WalletAddrTransactionModel::TYPE_TRANSFER, + 'block_sn' => $chain_sn, + ]); + if ($resWalletAddrTransactionModel->status != WalletAddrTransactionModel::STATUS_WAITING_CHAIN_CALLBACK) { //已经处理过 + Log::error('listenWalletAddrCallbackErr', ['token' => $token, 'addr' => $addr, 'amount' => $amount, 'status' => $status, 'wallet_addr_id' => $resModel->id, 'wallet_transaction_id' => $resWalletAddrTransactionModel->id, 'error' => '该提现条目已经处理过']); + throw new ModelException('该提现条目已经处理过'); + } + if ($resWalletAddrTransactionModel) { //存在就扣除冻结金额 + $res = $resWalletAddrTransactionModel->platformUserWithdrawCallback($resModel->id,$amount,$status); + } else { //未找到就直接扣除余额 + $res = $this->decAvailableAmount($resModel->id, $amount); + } + } + + if (!$res) { + Log::error('listenWalletAddrCallbackErr', ['token' => $token, 'addr' => $addr, 'amount' => $amount, 'status' => $status, 'wallet_addr_id' => $resModel->id, 'error' => '修改地址余额失败']); + throw new ModelException('修改地址余额失败'); + } + + if ($type == WalletAddrTransactionModel::TYPE_TRANSFER && $resWalletAddrTransactionModel) { //提现已有账变,修改账变状态即可 + $res = $oWalletAddrTransactionModel->updateItem([ + 'id' => $resWalletAddrTransactionModel->id, + 'status' => $status, + ]); + if (!$res) { + Log::error('listenWalletAddrCallbackErr', ['token' => $token, 'addr' => $addr, 'amount' => $amount, 'status' => $status, 'wallet_addr_id' => $resModel->id, 'wallet_transaction_id' => $resWalletAddrTransactionModel->id, 'error' => '修改账变状态失败']); + throw new ModelException('修改账变状态失败'); + } + } else { //充值和未记录的转账,增加到钱包账变记录 + $oWalletAddrTransactionModel = new WalletAddrTransactionModel(); + $res = $oWalletAddrTransactionModel->addTransaction($resModel->id, $type, WalletAddrTransactionModel::ROLE_SYSTEM, $status, $amount); + if (!$res) { + Log::error('listenWalletAddrCallbackErr', ['token' => $token, 'addr' => $addr, 'amount' => $amount, 'status' => $status, 'wallet_addr_id' => $resModel->id, 'error' => '增加到钱包账变记录失败']); + throw new ModelException('增加到钱包账变记录失败'); + } + } + //成功记录日志 + $log = ['token' => $token, 'addr' => $addr, 'amount' => $amount, 'status' => $status, 'wallet_addr_id' => $resModel->id, 'info' => '监听钱包处理成功']; + if (isset($resWalletAddrTransactionModel) && $resWalletAddrTransactionModel->id) $log['wallet_transaction_id'] = $resWalletAddrTransactionModel->id; + Log::info('listenWalletAddrCallbackSucc', $log); + + //投递到钱包账变队列 + self::putWalletAddrTransactionChangeQueue($resWalletAddrTransactionModel->id, $resWalletAddrTransactionModel->type); + + Db::commit(); + } catch (\Exception $e) { + Db::rollBack(); + throw $e; + } + } + + static function putWalletAddrTransactionChangeQueue($wallet_addr_transaction_id, $type): void + { + WalletAddrTransactionChangeQueue::dispatch([ + 'wallet_addr_transaction_id_id' => $wallet_addr_transaction_id, + 'wallet_addr_transaction_type' => $type, + ])->onQueue(QueueWalletAddrTransactionChangeStruct::QUEUE_NAME); + } + + function findWithdrawAddrWithAmount($amount, $currency_code = WalletCurrencyModel::CODE_USDT_TRC20): \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Builder|null + { + //获取当前系统使用中的热钱包 + $oModel = $this->findItemByWhere([ + 'currency_code' => $currency_code, + 'use_status' => self::USE_STATUS_USING, + 'role' => self::ROLE_SYSTEM, + ['available_amount', '>=', $amount] + ]); + if (!$oModel) throw new ModelException(ModelException::CODE[ModelException::CODE_WALLET_AMOUNT_ADDR_NOT_FOUND], ModelException::CODE_WALLET_AMOUNT_ADDR_NOT_FOUND); + if ($oModel->available_amount < $amount) throw new ModelException(ModelException::CODE[ModelException::CODE_WALLET_ADDR_BALANCE_LOW], ModelException::CODE_WALLET_ADDR_BALANCE_LOW); + return $oModel; + } + + + + //@@调用链上接口,发起转账,要返回链上交易订单号用于追踪并更新到数据库 + function callWalletAddrChainTransferApi($id, $wallet_addr_id): bool + { + return true; + } + + +} diff --git a/app/Models/Wallet/Wallet/WalletAddrTransactionModel.php b/app/Models/Wallet/Wallet/WalletAddrTransactionModel.php new file mode 100644 index 0000000..ade1f55 --- /dev/null +++ b/app/Models/Wallet/Wallet/WalletAddrTransactionModel.php @@ -0,0 +1,208 @@ + '充值', + self::TYPE_TRANSFER => '转账', + self::TYPE_FROZEN => '冻结', + self::TYPE_UN_FROZEN => '解冻', + ]; + + const ROLE_USER = 1; + const ROLE_SYSTEM = 2; + const ROLE_ADMIN = 3; + const ROLE_OTHER = 4; + const ROLE = [ + self::ROLE_USER => '用户操作', + self::ROLE_SYSTEM => '系统操作', + self::ROLE_ADMIN => '管理员操作', + self::ROLE_OTHER => '其他操作', + ]; + + const STATUS_WAITING_CHAIN_CALLBACK = 1; + const STATUS_SUCCESS = 2; + const STATUS_FAIL = 3; + const STATUS = [ + self::STATUS_WAITING_CHAIN_CALLBACK => '等待链回调', + self::STATUS_SUCCESS => '成功', + self::STATUS_FAIL => '失败', + ]; + + const DESC_KEY_WITHDRAW_FROZEN = 'withdrawFrozen'; + const DESC_KEY_WITHDRAW_UN_FROZEN = 'withdrawUnFrozen'; + const DESC_KEY_WITHDRAW_DETECT = 'withdrawDetect'; + const DESC = [ + self::DESC_KEY_WITHDRAW_FROZEN => '提现冻结', + self::DESC_KEY_WITHDRAW_UN_FROZEN => '提现解冻', + self::DESC_KEY_WITHDRAW_DETECT => '提现成功扣除', + ]; + + //@@钱包账变处理 + + function addTransaction( + $wallet_addr_id, + $type, + $role, + $status, + $received_amount, + $entered_amount = null, + $fee_amount = null, + $desc_key = '', + $remark = '', + $platform_user_transaction_id = null, + ): \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Builder|bool + { + $entered_amount = abs($entered_amount); + + $oWalletAddrModel = new WalletAddrModel(); + $resWalletAddrModel = $oWalletAddrModel->findItem($wallet_addr_id); + + $received_amount = abs($received_amount); + if (empty($fee_amount)) $fee_amount = 0; + if (empty($entered_amount)) $entered_amount = Math::bcSub($received_amount, $fee_amount); + + $desc = ''; + if (!empty($desc_key) && isset(self::DESC[$desc_key])) $desc = self::DESC[$desc_key]; + + if ($type == self::TYPE_RECHARGE) { //充值 + $after_total_amount = Math::bcAdd($oWalletAddrModel->total_amount, $entered_amount); + } elseif ($type == self::TYPE_TRANSFER) { //转账 + $after_total_amount = Math::bcSub($oWalletAddrModel->total_amount, $entered_amount); + } elseif (in_array($type, [self::TYPE_FROZEN, self::TYPE_UN_FROZEN])) { //冻结/解冻 + $after_total_amount = $oWalletAddrModel->total_amount; + } else { + throw new ModelException('未知类型'); + } + + $insert = [ + 'type' => $type, + 'role' => $role, + 'status' => $status, + 'currency_id' => $resWalletAddrModel->currency_id, + 'currency_code' => $resWalletAddrModel->currency_code, + 'received_amount' => $received_amount, + 'entered_amount' => $entered_amount, + 'fee_amount' => $fee_amount, + 'before_total_amount' => $oWalletAddrModel->total_amount, + 'after_total_amount' => $after_total_amount, + 'desc_key' => $desc_key, + 'desc' => $desc, + 'remark' => $remark, + 'platform_user_transaction_id' => $platform_user_transaction_id, + ]; +// $insert['sign'] = ''; + $insert['created_at'] = Times::getNowDateTime(); + return $this->addItem($insert); + } + + //用户发起提现 + function newPlatformUserWithdraw( + $wallet_addr_id, + $received_amount, + $platform_user_transaction_id, + ): \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Builder|bool + { + try { + Db::beginTransaction(); + //查询钱包 + $oWalletAddrModel = new WalletAddrModel(); + $resWalletAddrModel = $oWalletAddrModel->findItem($wallet_addr_id); + if (!$resWalletAddrModel) return false; + + if (Math::bcComp($resWalletAddrModel->available_amount, $received_amount) == -1) throw new ModelException('余额不足'); + + //冻结钱包余额 + $res = $oWalletAddrModel->frozenAmountById($wallet_addr_id, $received_amount); + if (!$res) throw new ModelException('钱包余额冻结失败'); + + //增加钱包账变 + $res = $this->addTransaction( + $wallet_addr_id, + $type = self::TYPE_TRANSFER, + $role = self::ROLE_USER, + $status = self::STATUS_WAITING_CHAIN_CALLBACK, + $received_amount, + $entered_amount = null, + $fee_amount = null, + $desc_key = '', + $remark = '', + $platform_user_transaction_id, + ); + if (!$res) throw new ModelException('钱包账变失败'); + Db::commit(); + return $res; + } catch (\Exception $e) { + Db::rollBack(); + Log::error('newPlatformUserWithdraw', ['wallet_addr_id' => $wallet_addr_id, 'received_amount' => $received_amount, 'platform_user_transaction_id' => $platform_user_transaction_id, 'msg' => $e->getMessage()]); + return false; + } + } + + + function platformUserWithdrawCallback($wallet_transaction_id, $amount, $status) + { + if (!in_array($status, [self::STATUS_SUCCESS, self::STATUS_FAIL])) throw new ModelException('提现状态错误'); + $resModel = $this->findItem($wallet_transaction_id); + if (!$resModel) throw new ModelException('钱包账变不存在'); + $oWalletAddrModel = new WalletAddrModel(); + try { + Db::beginTransaction(); + if ($status == self::STATUS_SUCCESS) { + $res = $oWalletAddrModel->decFrozenAmountById($resModel->wallet_addr_id, $amount); + if (!$res) throw new ModelException('扣除冻结失败'); + } else if ($status == WalletAddrTransactionModel::STATUS_FAIL) { //失败就解冻金额 + $resModel->status = WalletAddrTransactionModel::STATUS_FAIL; + $res = $resModel->save(); + if (!$res) throw new ModelException('更新状态失败'); + $res = $oWalletAddrModel->unFrozenAmountById($resModel->id, $resModel->received_amount); + if (!$res) throw new ModelException('解冻失败'); + } + Db::commit(); + return true; + } catch (\Exception $e) { + Db::rollBack(); + Log::error('platformUserWithdrawCallback', ['wallet_transaction_id' => $wallet_transaction_id, 'amount' => $amount, 'status' => $status, 'msg' => $e->getMessage()]); + return false; + } + + } + +} diff --git a/app/Models/Wallet/Wallet/WalletCurrencyModel.php b/app/Models/Wallet/Wallet/WalletCurrencyModel.php new file mode 100644 index 0000000..f10549a --- /dev/null +++ b/app/Models/Wallet/Wallet/WalletCurrencyModel.php @@ -0,0 +1,41 @@ +newQuery()->where('code',$code)->first($columns); + } + + function findByToken($token,$columns = ['*']): \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Builder|null + { + return $this->newQuery()->where('token',$token)->first($columns); + } + + function computeTransferRate($amount,$rate): string + { + if(empty($amount)) return 0; + if(empty($rate)) return 0; + return Math::bcMul(abs($amount),$rate,Math::SCALE_4); + } + +} diff --git a/app/Models/Wallet/Wallet/WalletSettingModel.php b/app/Models/Wallet/Wallet/WalletSettingModel.php new file mode 100644 index 0000000..62b1750 --- /dev/null +++ b/app/Models/Wallet/Wallet/WalletSettingModel.php @@ -0,0 +1,18 @@ +'', + 'wallet_addr_transaction_type'=>'', + ]; + +} diff --git a/app/Structs/QueueWalletPlatformUserWithdrawStruct.php b/app/Structs/QueueWalletPlatformUserWithdrawStruct.php new file mode 100644 index 0000000..504cf62 --- /dev/null +++ b/app/Structs/QueueWalletPlatformUserWithdrawStruct.php @@ -0,0 +1,11 @@ +'', + ]; + +} diff --git a/app/Tools/Math.php b/app/Tools/Math.php new file mode 100644 index 0000000..b501134 --- /dev/null +++ b/app/Tools/Math.php @@ -0,0 +1,31 @@ +