TransferForm.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. <?php
  2. namespace common\models\forms;
  3. use common\helpers\Cache;
  4. use common\helpers\Date;
  5. use common\components\Model;
  6. use common\helpers\Form;
  7. use common\helpers\snowflake\SnowFake;
  8. use common\helpers\Tool;
  9. use common\helpers\user\Balance;
  10. use common\helpers\user\Cash;
  11. use common\helpers\user\Info;
  12. use common\libs\logging\operate\UserOperate;
  13. use common\models\Article;
  14. use common\models\ArticleCategory;
  15. use common\models\DealType;
  16. use common\models\DecOrder;
  17. use common\models\Order;
  18. use common\models\PerfPeriod;
  19. use common\models\Period;
  20. use common\models\Transfer;
  21. use common\models\User;
  22. use common\models\UserBind;
  23. use common\models\UserBonus;
  24. use common\models\UserInfo;
  25. use common\models\UserRelation;
  26. use common\models\UserSystem;
  27. use Yii;
  28. use yii\base\Exception;
  29. use yii\helpers\Json;
  30. /**
  31. * Login form
  32. */
  33. class TransferForm extends Model {
  34. const allowTransferType = [1, 2, 3];
  35. const BONUS_TO_BALANCE = 1;
  36. const BONUS_TO_BONUS = 2;
  37. const BALANCE_TO_BALANCE = 3;
  38. public $toUserName;
  39. public $toRealName;
  40. public $amount;
  41. public $payPassword;
  42. public $type;
  43. public $remark;
  44. public $transferCode;
  45. private $_fromUserInfo;
  46. private $_toUserInfo;
  47. private $_transferProp;
  48. private $_transferConfig;
  49. private $_fee = 0;
  50. public function init() {
  51. parent::init();
  52. $this->userOperateLogger = new UserOperate([
  53. 'fetchClass' => Transfer::class,
  54. ]);
  55. }
  56. /**
  57. * @inheritdoc
  58. */
  59. public function rules() {
  60. return [
  61. [['toUserName', 'toRealName', 'amount', 'type', 'remark'], 'trim'],
  62. [['toUserName', 'toRealName', 'amount', 'type', 'payPassword', 'transferCode'], 'required'],
  63. [['toUserName'], 'exist', 'targetClass' => UserInfo::class, 'targetAttribute' => 'USER_NAME'],
  64. [['type'], 'isType'],
  65. [['toUserName'], 'initUser'],
  66. [['amount'], 'price'],
  67. [['amount'], 'isAmount'],
  68. [['transferCode'], 'validateCode'],
  69. [['payPassword'], 'validatePassword'],
  70. ];
  71. }
  72. /**
  73. * 指定校验场景
  74. * @return array
  75. */
  76. public function scenarios() {
  77. $parentScenarios = parent::scenarios();
  78. $customScenarios = [
  79. 'transfer' => ['type', 'toUserName', 'toRealName', 'amount', 'remark', 'payPassword', 'transferCode'],
  80. ];
  81. return array_merge($parentScenarios, $customScenarios);
  82. }
  83. public function attributeLabels() {
  84. return [
  85. 'toUserName' => 'Transferred Member Code', // 转入会员编号
  86. 'amount' => 'Amount', // 金额
  87. 'payPassword' => 'Payment Password', // 支付密码
  88. 'transferCode' => 'Verification Code', // 转账校验码
  89. ];
  90. }
  91. /**
  92. * 初始化转入转出用户信息
  93. * @param $attribute
  94. * @return null
  95. * @throws Exception
  96. */
  97. public function initUser($attribute) {
  98. // 转账记录
  99. if(!$toUserId = Info::getUserIdByUserName($this->toUserName)){
  100. $this->addError($attribute, 'Transferred member not exists'); // 转入会员不存在
  101. return null;
  102. }
  103. // if(Info::getUserRealNameByUserId($toUserId)!=$this->toRealName){
  104. // $this->addError($attribute, '转入会员会员姓名不正确');
  105. // return null;
  106. // }
  107. $this->_toUserInfo = Info::baseInfo($toUserId);
  108. $fromUserId = \Yii::$app->user->id;
  109. $fromUser = Info::baseInfo($fromUserId);
  110. if (!$fromUser) {
  111. $this->addError($attribute, 'Transferred member not exists'); // 转入会员不存在
  112. return null;
  113. }
  114. // 不能跨国转账
  115. $fromCountryId = User::getEnCodeInfo($fromUserId)['COUNTRY_ID'];
  116. $toCountryId = User::getEnCodeInfo($toUserId)['COUNTRY_ID'];
  117. if ($fromCountryId != $toCountryId) {
  118. $this->addError($attribute, Yii::t('app', 'transferMemberDoesNotSame'));
  119. return null;
  120. }
  121. // 转账条件判断
  122. $orderAmount = Order::find()->where('USER_ID=:USER_ID', [':USER_ID' => $fromUserId])->SUM('ORDER_AMOUNT');
  123. $recNum = intval(DecOrder::find()->where('REC_USER_ID=:REC_USER_ID', [':REC_USER_ID' => $fromUserId])->count());
  124. //$recNum = UserRelation::firstFloorChildNum($fromUserId);
  125. // if ($orderAmount < 300 && $recNum==0) {
  126. // $this->addError($attribute, '消费未满300元或未推荐新人,暂不能转账');
  127. // return null;
  128. // }
  129. $this->_fromUserInfo = $fromUser;
  130. //是否同体系转账
  131. // if($this->_transferConfig['isSystem']==1){
  132. // if($this->_toUserInfo['SYSTEM_ID']!=$this->_fromUserInfo['SYSTEM_ID']){
  133. // $this->addError($attribute, '只能同一体系间转账');
  134. // return null;
  135. // }
  136. // }
  137. //是否点位绑定转账
  138. // if ($this->_transferConfig['isBind'] == 1) {
  139. // if (!UserBind::sameBind($this->_toUserInfo['ID'], $this->_fromUserInfo['ID'])) {
  140. // $this->addError($attribute, '只能点位绑定在一起的会员之间转账');
  141. // return null;
  142. // }
  143. // }
  144. //奖金转奖金限同一身份证
  145. /*if ($this->type == self::BONUS_TO_BONUS) {
  146. $toIdCard = User::findOneAsArray('ID=:ID', [':ID' => $toUser['USER_ID']], 'ID_CARD');
  147. $fromIdCard = User::findOneAsArray('ID=:ID', [':ID' => $fromUser['USER_ID']], 'ID_CARD');
  148. if ($toIdCard['ID_CARD'] && ($toIdCard['ID_CARD'] != $fromIdCard['ID_CARD'])) {
  149. $this->addError($attribute, '奖金转奖金只能在同一身份证下进行');
  150. return null;
  151. }
  152. }*/
  153. //奖金转现金限一周一次
  154. /*if ($this->type == self::BONUS_TO_BALANCE) {
  155. if (Transfer::hasThisWeekTransfer(\Yii::$app->user->id)) {
  156. $this->addError($attribute, '转账失败,每周只可以转账一次');
  157. return null;
  158. }
  159. }*/
  160. }
  161. /**
  162. * 校验申请金额是否小于当前余额并符合配置中的设置
  163. * @param $attribute
  164. * @return null
  165. */
  166. public function isAmount($attribute) {
  167. if(!$this->_fromUserInfo){
  168. $this->addError($attribute, 'Member info has not been verified'); // 会员信息未验证通过
  169. return null;
  170. }
  171. if ($this->amount <= 0) {
  172. $this->addError($attribute, 'The transfer amount must be greater than 0'); // 转账金额必须大于0
  173. }
  174. if ((int)$this->amount!=$this->amount) {
  175. $this->addError('scenario', 'The transfer amount must be a round number'); // 转账金额必须是整数
  176. }
  177. $minAmount = $this->_transferConfig['outMin'];
  178. if ($this->amount < $minAmount) {
  179. $this->addError($attribute, 'The transfer amount is lower than the transfer limit'); // 转账金额低于转账下限
  180. }
  181. $maxAmount = $this->_transferConfig['outMax'];
  182. if ($maxAmount && $this->amount > $maxAmount) {
  183. $this->addError($attribute, 'The transfer amount is higher than the transfer limit'); // 转账金额高于转账上限
  184. }
  185. //周转账上限
  186. if($this->_transferConfig['weekMax']!=0){
  187. if($this->amount+Transfer::weekTransfer($this->_fromUserInfo['ID'])>$this->_transferConfig['weekMax']){
  188. $this->addError($attribute, 'The working book limit is exceeded'); // 超出周转账上限
  189. }
  190. }
  191. //月转账上限
  192. if($this->_transferConfig['monthMax']!=0){
  193. if($this->amount+Transfer::monthTransfer($this->_fromUserInfo['ID'])>$this->_transferConfig['weekMax']){
  194. $this->addError($attribute, 'Exceed the monthly transfer limit'); // 超出月转账上限
  195. }
  196. }
  197. // 获取当前用户的可用金额
  198. if( $this->type == self::BALANCE_TO_BALANCE ) {
  199. $haveBalance = Cash::getAvailableBalance($this->_fromUserInfo['ID']);
  200. }else {
  201. $haveBalance = Balance::getAvailableBalance($this->_fromUserInfo['ID']);
  202. }
  203. if ($this->amount > $haveBalance) {
  204. $this->addError($attribute, 'The transfer amount must be less than your available balance'); // 转账金额必须小于自己的可用余额
  205. }
  206. // 转账比例
  207. if ($this->type == self::BONUS_TO_BALANCE) {
  208. $isCanTransferProp = Cache::getSystemConfig()['isCanTransferProp']['VALUE'];
  209. if ($isCanTransferProp == 1) {
  210. $maxAmount = Tool::formatPrice($this->_fromUserInfo['TRANSFER_PROP'] * Balance::getAvailableBalance($this->_fromUserInfo['ID']) * 0.01);
  211. if ($this->amount > $maxAmount) {
  212. $this->addError($attribute, 'The transfer amount exceeds the transfer ratio limit'); // 转账金额超出转账比例限额
  213. }
  214. }
  215. }
  216. //手续费
  217. if($this->_transferConfig['fee']!=0){
  218. $fee = Tool::formatPrice($this->amount * ($this->_transferConfig['fee']) * 0.01);
  219. if($this->_transferConfig['feeMin']!=0&&$fee<$this->_transferConfig['feeMin']){
  220. $fee = $this->_transferConfig['feeMin'];
  221. }
  222. if($this->_transferConfig['feeMax']!=0&&$fee>$this->_transferConfig['feeMax']){
  223. $fee = $this->_transferConfig['feeMax'];
  224. }
  225. $this->_fee = $fee;
  226. }
  227. }
  228. /**
  229. * 转账类型判断
  230. * @param $attribute
  231. */
  232. public function isType($attribute) {
  233. if (!in_array($this->type, self::allowTransferType)) {
  234. $this->addError($attribute, 'Types of transfers that are not allowed'); // 不允许的转账类型
  235. }
  236. //获取转账参数
  237. if(!$this->_transferConfig = Json::decode(Cache::getSystemConfig()['allowWallet']['VALUE'])[$this->type-1]){
  238. $this->addError($attribute, 'Wrong type of transfer'); // 错误的转账类型
  239. }
  240. if(!$this->_transferConfig['isOpen']){
  241. $this->addError($attribute, 'Type of transfer that is not available'); // 不可用的转账类型
  242. }
  243. }
  244. /**
  245. * 校验支付密码
  246. * @param $attribute
  247. * @param $params
  248. */
  249. public function validatePassword($attribute, $params) {
  250. if (!User::validatePayPassword($this->_fromUserInfo['ID'], $this->payPassword)) {
  251. $this->addError($attribute, 'The payment password is incorrect');//支付密码不正确
  252. }
  253. }
  254. public function validateCode($attribute, $params) {
  255. $uid = \Yii::$app->user->id;
  256. $redisCode = \Yii::$app->redis->getset('transferCode_'.$uid,'');
  257. \Yii::$app->redis->del('transferCode_'.$uid);
  258. if ($this->transferCode!=$redisCode) {
  259. $this->addError($attribute, 'Transfer verification failed '.$redisCode); // 转账校验失败
  260. }
  261. }
  262. /**
  263. * 生成随机数
  264. * @param $length
  265. * @param int $numeric
  266. * @return string
  267. */
  268. public function random($length, $numeric = 0) {
  269. $seed = base_convert(md5(microtime() . $_SERVER['DOCUMENT_ROOT']), 16, $numeric ? 10 : 35);
  270. $seed = $numeric ? (str_replace('0', '', $seed) . '012340567890') : ($seed . 'zZ' . strtoupper($seed));
  271. $hash = '';
  272. $max = strlen($seed) - 1;
  273. for ($i = 0; $i < $length; $i++) {
  274. $hash .= $seed[mt_rand(0, $max)];
  275. }
  276. return $hash;
  277. }
  278. /**
  279. * 转账
  280. * @return Transfer|null
  281. * @throws \Exception
  282. */
  283. public function transfer() {
  284. if (!$this->validate()) {
  285. return null;
  286. }
  287. if ($this->_fromUserInfo['ID']==$this->_toUserInfo['ID']){
  288. throw new \Exception(Yii::t('app', 'notAllowedToTransferToYourself'));
  289. }
  290. $fromData = Balance::getLogData($this->_fromUserInfo['ID']);
  291. $toData = Balance::getLogData($this->_toUserInfo['ID']);
  292. $this->userOperateLogger->saveBeforeContent=array_merge($fromData,$toData);
  293. $db = \Yii::$app->db;
  294. $transaction = $db->beginTransaction();
  295. try {
  296. $amount = Tool::formatPrice($this->amount - $this->_fee);
  297. // 转账记录
  298. $period = Period::instance();
  299. $model = new Transfer();
  300. $model->ID = SnowFake::instance()->generateId();
  301. $model->TRANSFER_SN = Transfer::generateSN();
  302. $model->OUT_UID = $this->_fromUserInfo['ID'];
  303. $model->LAST_OUT_USER_NAME = $this->_fromUserInfo['USER_NAME'];
  304. $model->LAST_OUT_REAL_NAME = $this->_fromUserInfo['REAL_NAME'];
  305. $model->LAST_OUT_DEC_LV = $this->_fromUserInfo['DEC_LV'];
  306. $model->LAST_OUT_DEC_ROLE_ID = $this->_fromUserInfo['DEC_ROLE_ID'];
  307. $model->OUT_WALLET = $this->_transferConfig['out'];
  308. $model->LAST_OUT_SYSTEM_ID = $this->_fromUserInfo['SYSTEM_ID'] ? $this->_fromUserInfo['SYSTEM_ID'] : '';
  309. $model->IN_UID = $this->_toUserInfo['ID'];
  310. $model->LAST_IN_USER_NAME = $this->_toUserInfo['USER_NAME'];
  311. $model->LAST_IN_REAL_NAME = $this->_toUserInfo['REAL_NAME'];
  312. $model->LAST_IN_DEC_LV = $this->_toUserInfo['DEC_LV'];
  313. $model->IN_WALLET = $this->_transferConfig['in'];
  314. $model->LAST_IN_SYSTEM_ID = $this->_toUserInfo['SYSTEM_ID'] ? $this->_toUserInfo['SYSTEM_ID'] : '';
  315. $model->ORI_AMOUNT = $this->amount;
  316. $model->FEE = $this->_fee;
  317. $model->AMOUNT = $amount;
  318. $model->REMARK = $this->remark;
  319. $model->PERIOD_NUM = $period->getNowPeriodNum();
  320. $model->CALC_MONTH = $period->getNowYearMonth();
  321. $model->CREATED_AT = Date::nowTime();
  322. if (!$model->save()) {
  323. throw new Exception(Form::formatErrorsForApi($model->getErrors()));
  324. }
  325. if ($this->type == self::BONUS_TO_BALANCE) {
  326. // 会员金额减少
  327. Balance::changeUserBonus($this->_fromUserInfo['ID'], 'bonus', -abs($this->amount), ['TRANSFER_SN' => $model->TRANSFER_SN, 'DEAL_TYPE_ID' => DealType::TRANSFER_OUT, 'REMARK' => 'To:' . $this->_toUserInfo['USER_NAME'] . ',' . $this->remark]);
  328. Cash::changeUserCash($this->_toUserInfo['ID'], 'CASH', abs($amount), ['TRANSFER_SN' => $model->TRANSFER_SN,'DEAL_TYPE_ID' => DealType::TRANSFER_IN, 'REMARK' => 'From:' . $this->_fromUserInfo['USER_NAME'] . ',' . $this->remark]);
  329. } elseif($this->type == self::BONUS_TO_BONUS) {
  330. // 会员金额减少
  331. Balance::changeUserBonus($this->_fromUserInfo['ID'], 'bonus', -abs($this->amount), ['TRANSFER_SN' => $model->TRANSFER_SN, 'DEAL_TYPE_ID' => DealType::TRANSFER_OUT, 'REMARK' => 'To:' . $this->_toUserInfo['USER_NAME'] . ',' . $this->remark]);
  332. Balance::changeUserBonus($this->_toUserInfo['ID'], 'bonus', abs($amount), ['TRANSFER_SN' => $model->TRANSFER_SN, 'DEAL_TYPE_ID' => DealType::TRANSFER_IN, 'REMARK' => 'From:' . $this->_fromUserInfo['USER_NAME'] . ',' . $this->remark]);
  333. }else if ($this->type == self::BALANCE_TO_BALANCE) {
  334. // 会员金额减少
  335. Cash::changeUserCash($this->_fromUserInfo['ID'], 'CASH', -abs($this->amount), ['TRANSFER_SN' => $model->TRANSFER_SN,'DEAL_TYPE_ID' => DealType::TRANSFER_OUT, 'REMARK' => 'To:' . $this->_toUserInfo['USER_NAME'] . ',' . $this->remark]);
  336. Cash::changeUserCash($this->_toUserInfo['ID'], 'CASH', abs($amount), ['TRANSFER_SN' => $model->TRANSFER_SN,'DEAL_TYPE_ID' => DealType::TRANSFER_IN, 'REMARK' => 'From:' . $this->_fromUserInfo['USER_NAME'] . ',' . $this->remark]);
  337. }else {
  338. throw new \Exception(Yii::t('app', 'wrongTransactionType'));
  339. }
  340. $transaction->commit();
  341. } catch (Exception $e) {
  342. $transaction->rollBack();
  343. $this->addError('edit', $e->getMessage());
  344. return null;
  345. }
  346. $fromData = Balance::getLogData($this->_fromUserInfo['ID']);
  347. $toData = Balance::getLogData($this->_toUserInfo['ID']);
  348. $this->userOperateLogger->saveAfterContent=array_merge($fromData,$toData);
  349. unset($fromData,$toData);
  350. $this->userOperateLogger->clean()->save([
  351. 'optType' => '会员转账',
  352. 'userId' => \Yii::$app->user->id,
  353. 'userName' => Info::getUserNameByUserId(\Yii::$app->user->id),
  354. 'remark' => $this->remark,
  355. ]);
  356. return $model;
  357. }
  358. }