UserTransfer.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. <?php
  2. namespace app\data\command;
  3. use app\data\service\UserRebateService;
  4. use think\admin\Command;
  5. use think\admin\Exception;
  6. use think\admin\storage\LocalStorage;
  7. use think\console\Input;
  8. use think\console\Output;
  9. use WePay\Transfers;
  10. use WePay\TransfersBank;
  11. /**
  12. * 用户提现处理
  13. * Class UserTransfer
  14. * @package app\data\command
  15. */
  16. class UserTransfer extends Command
  17. {
  18. protected function configure()
  19. {
  20. $this->setName('xdata:UserTransfer');
  21. $this->setDescription('批量执行线上打款操作');
  22. }
  23. /**
  24. * 执行微信提现操作
  25. * @param Input $input
  26. * @param Output $output
  27. * @return void
  28. * @throws Exception
  29. * @throws \think\db\exception\DbException
  30. */
  31. protected function execute(Input $input, Output $output)
  32. {
  33. $map = [['type', 'in', ['wechat_banks', 'wechat_wallet']], ['status', 'in', [3, 4]]];
  34. [$total, $count, $error] = [$this->app->db->name('DataUserTransfer')->where($map)->count(), 0, 0];
  35. foreach ($this->app->db->name('DataUserTransfer')->where($map)->cursor() as $vo) try {
  36. $this->queue->message($total, ++$count, "开始处理订单 {$vo['code']} 提现");
  37. if ($vo['status'] === 3) {
  38. $this->queue->message($total, $count, "尝试处理订单 {$vo['code']} 打款", 1);
  39. if ($vo['type'] === 'wechat_banks') {
  40. [$config, $result] = $this->createTransferBank($vo);
  41. } else {
  42. [$config, $result] = $this->createTransferWallet($vo);
  43. }
  44. if ($result['return_code'] === 'SUCCESS' && $result['result_code'] === 'SUCCESS') {
  45. $this->app->db->name('DataUserTransfer')->where(['code' => $vo['code']])->update([
  46. 'status' => 4,
  47. 'appid' => $config['appid'],
  48. 'openid' => $config['openid'],
  49. 'trade_no' => $result['partner_trade_no'],
  50. 'trade_time' => $result['payment_time'] ?? date('Y-m-d H:i:s'),
  51. 'change_time' => date('Y-m-d H:i:s'),
  52. 'change_desc' => '创建微信提现成功',
  53. ]);
  54. } else {
  55. $this->app->db->name('DataUserTransfer')->where(['code' => $vo['code']])->update([
  56. 'change_time' => date('Y-m-d H:i:s'), 'change_desc' => $result['err_code_des'] ?? '线上提现失败',
  57. ]);
  58. }
  59. } elseif ($vo['status'] === 4) {
  60. $this->queue->message($total, $count, "刷新提现订单 {$vo['code']} 状态", 1);
  61. if ($vo['type'] === 'wechat_banks') {
  62. $this->queryTransferBank($vo);
  63. } else {
  64. $this->queryTransferWallet($vo);
  65. }
  66. }
  67. } catch (\Exception $exception) {
  68. $error++;
  69. $this->queue->message($total, $count, "处理提现订单 {$vo['code']} 失败, {$exception->getMessage()}", 1);
  70. $this->app->db->name('DataUserTransfer')->where(['code' => $vo['code']])->update([
  71. 'change_time' => date('Y-m-d H:i:s'), 'change_desc' => $exception->getMessage(),
  72. ]);
  73. }
  74. $this->setQueueSuccess("此次共处理 {$total} 笔提现操作, 其中有 {$error} 笔处理失败。");
  75. }
  76. /**
  77. * 尝试提现转账到银行卡
  78. * @param array $item
  79. * @return array [config, result]
  80. * @throws Exception
  81. * @throws \WeChat\Exceptions\InvalidDecryptException
  82. * @throws \WeChat\Exceptions\InvalidResponseException
  83. * @throws \WeChat\Exceptions\LocalCacheException
  84. * @throws \think\db\exception\DataNotFoundException
  85. * @throws \think\db\exception\DbException
  86. * @throws \think\db\exception\ModelNotFoundException
  87. */
  88. private function createTransferBank(array $item): array
  89. {
  90. $config = $this->getConfig($item['uid']);
  91. return [$config, TransfersBank::instance($config)->create([
  92. 'partner_trade_no' => $item['code'],
  93. 'enc_bank_no' => $item['bank_code'],
  94. 'enc_true_name' => $item['bank_user'],
  95. 'bank_code' => $item['bank_wseq'],
  96. 'amount' => intval($item['amount'] - $item['charge_amount']) * 100,
  97. 'desc' => '微信银行卡提现',
  98. ])];
  99. }
  100. /**
  101. * 尝试提现转账到微信钱包
  102. * @param array $item
  103. * @return array [config, result]
  104. * @throws Exception
  105. * @throws \WeChat\Exceptions\InvalidResponseException
  106. * @throws \WeChat\Exceptions\LocalCacheException
  107. * @throws \think\db\exception\DataNotFoundException
  108. * @throws \think\db\exception\DbException
  109. * @throws \think\db\exception\ModelNotFoundException
  110. */
  111. private function createTransferWallet(array $item): array
  112. {
  113. $config = $this->getConfig($item['uid']);
  114. return [$config, Transfers::instance($config)->create([
  115. 'openid' => $config['openid'],
  116. 'amount' => intval($item['amount'] - $item['charge_amount']) * 100,
  117. 'partner_trade_no' => $item['code'],
  118. 'spbill_create_ip' => '127.0.0.1',
  119. 'check_name' => 'NO_CHECK',
  120. 'desc' => '微信余额提现',
  121. ])];
  122. }
  123. /**
  124. * 查询更新提现打款状态
  125. * @param array $item
  126. * @throws Exception
  127. * @throws \WeChat\Exceptions\InvalidResponseException
  128. * @throws \WeChat\Exceptions\LocalCacheException
  129. * @throws \think\db\exception\DataNotFoundException
  130. * @throws \think\db\exception\DbException
  131. * @throws \think\db\exception\ModelNotFoundException
  132. */
  133. private function queryTransferWallet(array $item)
  134. {
  135. $config = $this->getConfig($item['uid']);
  136. [$config['appid'], $config['openid']] = [$item['appid'], $item['openid']];
  137. $result = Transfers::instance($config)->query($item['partner_trade_no']);
  138. if ($result['return_code'] === 'SUCCESS' && $result['result_code'] === 'SUCCESS') {
  139. $this->app->db->name('DataUserTransfer')->where(['code' => $item['code']])->update([
  140. 'status' => 5,
  141. 'appid' => $config['appid'],
  142. 'openid' => $config['openid'],
  143. 'trade_time' => $result['payment_time'],
  144. 'change_time' => date('Y-m-d H:i:s'),
  145. 'change_desc' => '微信提现打款成功',
  146. ]);
  147. }
  148. }
  149. /**
  150. * 查询更新提现打款状态
  151. * @param array $item
  152. * @throws Exception
  153. * @throws \WeChat\Exceptions\InvalidResponseException
  154. * @throws \WeChat\Exceptions\LocalCacheException
  155. * @throws \think\db\exception\DataNotFoundException
  156. * @throws \think\db\exception\DbException
  157. * @throws \think\db\exception\ModelNotFoundException
  158. */
  159. private function queryTransferBank(array $item)
  160. {
  161. $config = $this->getConfig($item['uid']);
  162. [$config['appid'], $config['openid']] = [$item['appid'], $item['openid']];
  163. $result = TransfersBank::instance($config)->query($item['partner_trade_no']);
  164. if ($result['return_code'] === 'SUCCESS' && $result['result_code'] === 'SUCCESS') {
  165. if ($result['status'] === 'SUCCESS') {
  166. $this->app->db->name('DataUserTransfer')->where(['code' => $item['code']])->update([
  167. 'status' => 5,
  168. 'appid' => $config['appid'],
  169. 'openid' => $config['openid'],
  170. 'trade_time' => $result['pay_succ_time'] ?: date('Y-m-d H:i:s'),
  171. 'change_time' => date('Y-m-d H:i:s'),
  172. 'change_desc' => '微信提现打款成功',
  173. ]);
  174. }
  175. if (in_array($result['status'], ['FAILED', 'BANK_FAIL'])) {
  176. $this->app->db->name('DataUserTransfer')->where(['code' => $item['code']])->update([
  177. 'status' => 0,
  178. 'change_time' => date('Y-m-d H:i:s'),
  179. 'change_desc' => '微信提现打款失败',
  180. ]);
  181. // 刷新用户可提现余额
  182. UserRebateService::instance()->amount($item['uid']);
  183. }
  184. }
  185. }
  186. /**
  187. * 获取微信提现参数
  188. * @param int $uid
  189. * @return array
  190. * @throws Exception
  191. * @throws \think\db\exception\DataNotFoundException
  192. * @throws \think\db\exception\DbException
  193. * @throws \think\db\exception\ModelNotFoundException
  194. */
  195. private function getConfig(int $uid): array
  196. {
  197. $data = sysdata('TransferWxpay');
  198. if (empty($data)) throw new Exception('未配置微信提现商户');
  199. // 商户证书文件处理
  200. $local = LocalStorage::instance();
  201. if (!$local->has($file1 = "{$data['wechat_mch_id']}_key.pem", true)) {
  202. $local->set($file1, $data['wechat_mch_key_text'], true);
  203. }
  204. if (!$local->has($file2 = "{$data['wechat_mch_id']}_cert.pem", true)) {
  205. $local->set($file2, $data['wechat_mch_cert_text'], true);
  206. }
  207. // 获取用户支付信息
  208. $result = $this->getWechatInfo($uid, $data['wechat_type']);
  209. if (empty($result)) throw new Exception('无法读取打款数据');
  210. return [
  211. 'appid' => $result[0],
  212. 'openid' => $result[1],
  213. 'mch_id' => $data['wechat_mch_id'],
  214. 'mch_key' => $data['wechat_mch_key'],
  215. 'ssl_key' => $local->path($file1),
  216. 'ssl_cer' => $local->path($file2),
  217. 'cache_path' => $this->app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR . 'wechat',
  218. ];
  219. }
  220. /**
  221. * 根据配置获取用户OPENID
  222. * @param int $uid
  223. * @param string $type
  224. * @return mixed|null
  225. * @throws \think\db\exception\DataNotFoundException
  226. * @throws \think\db\exception\DbException
  227. * @throws \think\db\exception\ModelNotFoundException
  228. */
  229. private function getWechatInfo(int $uid, string $type): ?array
  230. {
  231. $user = $this->app->db->name('DataUser')->where(['id' => $uid])->find();
  232. if (empty($user)) return null;
  233. $appid1 = sysdata('data.wxapp_appid');
  234. if (strtolower(sysconf('wechat.type')) === 'api') {
  235. $appid2 = sysconf('wechat.appid');
  236. } else {
  237. $appid2 = sysconf('wechat.thr_appid');
  238. }
  239. if ($type === 'normal') {
  240. if (!empty($user['openid1'])) return [$appid1, $user['openid1']];
  241. if (!empty($user['openid2'])) return [$appid2, $user['openid2']];
  242. }
  243. if ($type === 'wxapp' && !empty($user['openid1'])) {
  244. return [$appid1, $user['openid1']];
  245. }
  246. if ($type === 'wechat' && !empty($user['openid2'])) {
  247. return [$appid2, $user['openid2']];
  248. }
  249. return null;
  250. }
  251. }