UserWalletApply.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. <?php
  2. namespace addons\shopro\model;
  3. use think\Db;
  4. use think\Model;
  5. use traits\model\SoftDelete;
  6. use addons\shopro\exception\Exception;
  7. /**
  8. * 钱包
  9. */
  10. class UserWalletApply extends Model
  11. {
  12. use SoftDelete;
  13. // 表名,不含前缀
  14. protected $name = 'shopro_user_wallet_apply';
  15. // 自动写入时间戳字段
  16. protected $autoWriteTimestamp = 'int';
  17. // 定义时间戳字段名
  18. protected $createTime = 'createtime';
  19. protected $updateTime = 'updatetime';
  20. protected $deleteTime = false;
  21. protected $hidden = ['actual_money', 'log', 'payment_json', 'updatetime'];
  22. // 追加属性
  23. protected $append = [
  24. 'status_text',
  25. 'apply_type_text',
  26. ];
  27. /**
  28. * 获取提现单号
  29. *
  30. * @param int $user_id
  31. * @return string
  32. */
  33. public static function getSn($user_id)
  34. {
  35. $rand = $user_id < 9999 ? mt_rand(100000, 99999999) : mt_rand(100, 99999);
  36. $order_sn = date('Yhis') . $rand;
  37. $id = str_pad($user_id, (24 - strlen($order_sn)), '0', STR_PAD_BOTH);
  38. return 'W' . $order_sn . $id;
  39. }
  40. // 提现记录
  41. public static function getList()
  42. {
  43. $user = User::info();
  44. $walletApplys = self::where(['user_id' => $user->id])->order('id desc')->paginate(10);
  45. return $walletApplys;
  46. }
  47. /**
  48. * 申请提现
  49. *
  50. * @param int $type 提现方式 wechat|alipay|bank
  51. * @param int $money 提现金额
  52. */
  53. public static function apply($type, $money)
  54. {
  55. $user = User::info();
  56. $config = self::getWithdrawConfig();
  57. if (!in_array($type, $config['methods'])) {
  58. throw \Exception('暂不支持该提现方式');
  59. }
  60. $min = round(floatval($config['min']), 2);
  61. $max = round(floatval($config['max']), 2);
  62. $service_fee = round(floatval($config['service_fee']), 3); // 三位小数
  63. // 检查最小提现金额
  64. if ($money < $min || $money <= 0) {
  65. throw \Exception('提现金额不能少于 ' . $min . '元');
  66. }
  67. // 检查最大提现金额
  68. if ($max && $money > $max) {
  69. throw \Exception('提现金额不能大于 ' . $max . '元');
  70. }
  71. // 计算手续费
  72. $charge = $money * $service_fee;
  73. if ($user->money < $charge + $money) {
  74. throw \Exception('可提现余额不足');
  75. }
  76. // 检查每日最大提现次数
  77. if (isset($config['perday_num']) && $config['perday_num'] > 0) {
  78. $num = self::where(['user_id' => $user->id, 'createtime' => ['egt', strtotime(date("Y-m-d", time()))]])->count();
  79. if ($num >= $config['perday_num']) {
  80. throw \Exception('每日提现次数不能大于 ' . $config['perday_num'] . '次');
  81. }
  82. }
  83. // 检查每日最大提现金额
  84. if (isset($config['perday_amount']) && $config['perday_num'] > 0) {
  85. $amount = self::where(['user_id' => $user->id, 'createtime' => ['egt', strtotime(date("Y-m-d", time()))]])->sum('money');
  86. if ($amount >= $config['perday_amount']) {
  87. throw \Exception('每日提现金额不能大于 ' . $config['perday_amount'] . '元');
  88. }
  89. }
  90. // 检查提现账户信息
  91. $bank = \addons\shopro\model\UserBank::info($type, false);
  92. // 添加提现记录
  93. $platform = request()->header('platform');
  94. $apply = new self();
  95. $apply->apply_sn = self::getSn($user->id);
  96. $apply->user_id = $user->id;
  97. $apply->money = $money;
  98. $apply->charge_money = $charge;
  99. $apply->service_fee = $service_fee;
  100. $apply->apply_type = $type;
  101. $apply->platform = $platform;
  102. switch ($type) {
  103. case 'wechat':
  104. $applyInfo = [
  105. '微信用户' => $bank['real_name'],
  106. '微信ID' => $bank['card_no'],
  107. ];
  108. break;
  109. case 'alipay':
  110. $applyInfo = [
  111. '真实姓名' => $bank['real_name'],
  112. '支付宝账户' => $bank['card_no']
  113. ];
  114. break;
  115. case 'bank':
  116. $applyInfo = [
  117. '真实姓名' => $bank['real_name'],
  118. '开户行' => $bank['bank_name'],
  119. '银行卡号' => $bank['card_no']
  120. ];
  121. break;
  122. }
  123. if (!isset($applyInfo)) {
  124. throw \Exception('您的提现信息有误');
  125. }
  126. $apply->apply_info = $applyInfo;
  127. $apply->status = 0;
  128. $apply->save();
  129. self::handleLog($apply, '用户发起提现申请');
  130. // 扣除用户余额
  131. User::money(- ($money + $charge), $user->id, 'cash', $apply->id);
  132. // 检查是否执行自动打款
  133. $autoCheck = false;
  134. if ($type !== 'bank' && $config['wechat_alipay_auto']) {
  135. $autoCheck = true;
  136. }
  137. if ($autoCheck) {
  138. $apply = self::handleAgree($apply);
  139. $apply = self::handleWithdraw($apply);
  140. }
  141. return $apply;
  142. }
  143. public static function handleLog($apply, $oper_info)
  144. {
  145. $log = $apply->log;
  146. $oper = \addons\shopro\library\Oper::set();
  147. $log[] = [
  148. 'oper_type' => $oper['oper_type'],
  149. 'oper_id' => $oper['oper_id'],
  150. 'oper_info' => $oper_info,
  151. 'oper_time' => time()
  152. ];
  153. $apply->log = $log;
  154. $apply->save();
  155. return $apply;
  156. }
  157. // 同意
  158. public static function handleAgree($apply)
  159. {
  160. if ($apply->status != 0) {
  161. throw \Exception('请勿重复操作');
  162. }
  163. $apply->status = 1;
  164. $apply->save();
  165. return self::handleLog($apply, '同意提现申请');
  166. }
  167. // 处理打款
  168. public static function handleWithdraw($apply)
  169. {
  170. $withDrawStatus = false;
  171. if ($apply->status != 1) {
  172. throw \Exception('请勿重复操作');
  173. }
  174. if ($apply->apply_type !== 'bank') {
  175. $withDrawStatus = self::handleTransfer($apply);
  176. } else {
  177. $withDrawStatus = true;
  178. }
  179. if ($withDrawStatus) {
  180. $apply->status = 2;
  181. $apply->actual_money = $apply->money;
  182. $apply->save();
  183. return self::handleLog($apply, '已打款');
  184. }
  185. return $apply;
  186. }
  187. // 拒绝
  188. public static function handleReject($apply, $rejectInfo)
  189. {
  190. if ($apply->status != 0 && $apply->status != 1) {
  191. throw \Exception('请勿重复操作');
  192. }
  193. $apply->status = -1;
  194. $apply->save();
  195. User::money($apply->money + $apply->charge_money, $apply->user_id, 'cash_error', $apply->id);
  196. return self::handleLog($apply, '拒绝:' . $rejectInfo);
  197. }
  198. // 企业付款提现
  199. private static function handleTransfer($apply)
  200. {
  201. $type = $apply->apply_type;
  202. $platform = $apply->platform;
  203. // 1.企业自动付款
  204. $pay = new \addons\shopro\library\PayService($type, $platform, '', 'transfer');
  205. // 2.组装数据
  206. try {
  207. if ($type == 'wechat') {
  208. $payload = [
  209. 'partner_trade_no' => $apply->apply_sn,
  210. 'openid' => $apply->apply_info['微信ID'],
  211. 'check_name' => 'NO_CHECK',
  212. 'amount' => $apply->money,
  213. 'desc' => "用户[{$apply->apply_info['微信用户']}]提现"
  214. ];
  215. } elseif ($type == 'alipay') {
  216. $payload = [
  217. 'out_biz_no' => $apply->apply_sn,
  218. 'trans_amount' => $apply->money,
  219. 'product_code' => 'TRANS_ACCOUNT_NO_PWD',
  220. 'biz_scene' => 'DIRECT_TRANSFER',
  221. // 'order_title' => '余额提现到',
  222. 'remark' => '用户提现',
  223. 'payee_info' => [
  224. 'identity' => $apply->apply_info['支付宝账户'],
  225. 'identity_type' => 'ALIPAY_LOGON_ID',
  226. 'name' => $apply->apply_info['真实姓名'],
  227. ]
  228. ];
  229. }
  230. } catch (\Exception $e) {
  231. throw \Exception('提现信息不正确');
  232. }
  233. // 3.发起付款
  234. try {
  235. list($code, $response) = $pay->transfer($payload);
  236. if ($code === 1) {
  237. $apply->payment_json = json_encode($response, JSON_UNESCAPED_UNICODE);
  238. $apply->save();
  239. return true;
  240. }
  241. } catch (\Exception $e) {
  242. // \think\Log::error('提现失败:' . ' 行号:' . $e->getLine() . '文件:' . $e->getFile() . '错误信息:' . $e->getMessage());
  243. throw \Exception($e->getMessage());
  244. }
  245. return false;
  246. }
  247. /**
  248. * 提现类型列表
  249. */
  250. public function getApplyTypeList()
  251. {
  252. return ['bank' => '银行卡', 'wechat' => '微信零钱', 'alipay' => '支付宝账户'];
  253. }
  254. /**
  255. * 提现类型中文
  256. */
  257. public function getApplyTypeTextAttr($value, $data)
  258. {
  259. $value = isset($data['apply_type']) ? $data['apply_type'] : '';
  260. $list = $this->getApplyTypeList();
  261. return isset($list[$value]) ? $list[$value] : '';
  262. }
  263. /**
  264. * 提现信息
  265. */
  266. public function getApplyInfoAttr($value, $data)
  267. {
  268. $value = isset($data['apply_info']) ? $data['apply_info'] : $value;
  269. return json_decode($value, true);
  270. }
  271. /**
  272. * 提现信息 格式转换
  273. */
  274. public function setApplyInfoAttr($value, $data)
  275. {
  276. $value = isset($data['apply_info']) ? $data['apply_info'] : $value;
  277. $applyInfo = json_encode($value, JSON_UNESCAPED_UNICODE);
  278. return $applyInfo;
  279. }
  280. public function getStatusTextAttr($value, $data)
  281. {
  282. switch ($data['status']) {
  283. case 0:
  284. $status_name = '审核中';
  285. break;
  286. case 1:
  287. $status_name = '处理中';
  288. break;
  289. case 2:
  290. $status_name = '已处理';
  291. break;
  292. case -1:
  293. $status_name = '已拒绝';
  294. break;
  295. default:
  296. $status_name = '';
  297. }
  298. return $status_name;
  299. }
  300. public static function getWithdrawConfig()
  301. {
  302. $config = \addons\shopro\model\Config::where('name', 'withdraw')->find();
  303. return json_decode($config['value'], true);
  304. }
  305. /**
  306. * 获取日志字段数组
  307. */
  308. public function getLogAttr($value, $data)
  309. {
  310. $value = array_filter((array)json_decode($value, true));
  311. return (array)$value;
  312. }
  313. /**
  314. * 设置日志字段
  315. * @param mixed $value
  316. * @return string
  317. */
  318. public function setLogAttr($value)
  319. {
  320. $value = is_object($value) || is_array($value) ? json_encode($value, JSON_UNESCAPED_UNICODE) : $value;
  321. return $value;
  322. }
  323. }