WechatPay.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  1. <?php
  2. /**
  3. * 重庆赤晓店信息科技有限公司
  4. * https://www.chixiaodian.com
  5. * Copyright (c) 2023 赤店商城 All rights reserved.
  6. */
  7. namespace app\utils\Wechat;
  8. use app\librarys\Application;
  9. use app\models\LevelOrder;
  10. use app\models\Order;
  11. use app\models\OrderUnion;
  12. use app\models\WechatConfig;
  13. use app\utils\DataTransform;
  14. use app\utils\OrderNo;
  15. use EasyWeChat\Kernel\Exceptions\Exception;
  16. use GuzzleHttp\Exception\GuzzleException;
  17. use app\models\AccountLog;
  18. /**
  19. * 微信支付统一入口
  20. * Class WechatPay
  21. * @package app\utils\Wechat
  22. */
  23. class WechatPay extends Wechat
  24. {
  25. // TODO: 微信走统一的订单回调接口
  26. public static $notify_url = 'wechat/notify';
  27. /**
  28. * 下单
  29. * @param string $goods_names
  30. * @param array | Order | \app\plugins\scanCodePay\models\Order | PtOrder | YyOrder | LevelOrder $order
  31. * @param integer $type
  32. * @param integer $total_pay_price
  33. * @param boolean $is_app
  34. * @return array
  35. */
  36. public static function orderUnify($order, $type, $goods_names = null, $total_pay_price = 0, $is_app = false, $balance_price = 0, $is_h5 = false, $is_official = false, $open_id = '')
  37. {
  38. try {
  39. if ($is_app) {
  40. parent::init($order->store_id ?? get_store_id(), Wechat::WECHAT_KIND_PAY, WechatConfig::TYPE_CONFIG_APP);
  41. } else if ($is_official) {
  42. parent::init($order->store_id ?? get_store_id(), Wechat::WECHAT_KIND_PAY, WechatConfig::TYPE_CONFIG_MP);
  43. } else if ($is_h5) {
  44. parent::init($order->store_id ?? get_store_id(), Wechat::WECHAT_KIND_PAY, WechatConfig::TYPE_CONFIG_H5);
  45. } else {
  46. parent::init($order->store_id ?? get_store_id(), Wechat::WECHAT_KIND_PAY);
  47. }
  48. } catch (\Exception $e) {
  49. return [
  50. 'code' => 1,
  51. 'msg' => $e->getMessage()
  52. ];
  53. }
  54. if (!$order || !in_array($type, OrderNo::$validOrderType) || ($type != OrderNo::ORDER_UNION && !$goods_names)) {
  55. return [
  56. 'code' => 1,
  57. 'msg' => '订单信息或订单类型错误'
  58. ];
  59. }
  60. if ($is_official) {
  61. $order->order_origin = Order::ORDER_SOURCE_MP;
  62. $order->save();
  63. }
  64. if (empty($open_id)) {
  65. if ($is_app) {
  66. $open_id = get_user()->wechat_app_open_id;
  67. } else if ($is_official) {
  68. $open_id = get_user()->wechat_platform_open_id;
  69. if (empty($open_id)) {
  70. return [
  71. 'code' => 1,
  72. 'msg' => '支付失败 需前往个人中心 - 点击头像 - 一键同步用户信息 获取信息',
  73. ];
  74. }
  75. } else {
  76. $open_id = get_user()->wechat_open_id;
  77. if($type == OrderNo::ORDER_UNION){
  78. if (intval($order[0]->is_platform) === 1) {
  79. $open_id = get_saas_user()->platform_open_id;
  80. }
  81. }else{
  82. if (intval($order->is_platform) === 1) {
  83. $open_id = get_saas_user()->platform_open_id;
  84. }
  85. }
  86. }
  87. }
  88. if (!$open_id) {
  89. $open_id = get_saas_user()->platform_open_id;
  90. }
  91. if ($type == OrderNo::ORDER_UNION) {
  92. $config = [
  93. 'body' => count($order) . '笔订单合并支付',
  94. 'out_trade_no' => OrderNo::getOrderNo(OrderNo::ORDER_UNION),
  95. 'total_fee' => $balance_price > 0 ? round(floatval($total_pay_price - $balance_price) * 100) : $total_pay_price * 100,
  96. 'notify_url' => pay_notify_url(self::$notify_url, $order->store_id),
  97. 'openid' => $open_id,
  98. ];
  99. if ($is_h5) {
  100. $config['trade_type'] = 'MWEB';
  101. } elseif ($is_app) {
  102. $config['trade_type'] = 'APP';
  103. unset($config['openid']);
  104. } else {
  105. $config['trade_type'] = 'JSAPI';
  106. }
  107. $order_union = new OrderUnion();
  108. $order_union->store_id = get_store_id();
  109. $order_union->user_id = get_user()->id;
  110. $order_union->order_no = $config['out_trade_no'];
  111. $order_union->price = $total_pay_price;
  112. $order_union->is_pay = 0;
  113. $order_union->created_at = time();
  114. $order_union->is_delete = 0;
  115. $order_id_list = [];
  116. foreach ($order as $value) {
  117. $order_id_list[] = $value->id;
  118. }
  119. $order_union->order_id_list = json_encode($order_id_list);
  120. if (!$order_union->save()) {
  121. foreach ($order_union->errors as $error) {
  122. return [
  123. 'code' => 1,
  124. 'msg' => $error
  125. ];
  126. }
  127. }
  128. } else {
  129. $config = [
  130. 'body' => $goods_names,
  131. 'out_trade_no' => $order->order_no,
  132. 'total_fee' => $balance_price > 0 ? round(floatval($order->pay_price - $balance_price) * 100) : round($order->pay_price * 100),
  133. 'notify_url' => pay_notify_url(self::$notify_url, $order->store_id),
  134. 'openid' => $open_id,
  135. ];
  136. if ($is_h5) {
  137. $config['trade_type'] = 'MWEB';
  138. } elseif ($is_app) {
  139. $config['trade_type'] = 'APP';
  140. unset($config['openid']);
  141. } else {
  142. $config['trade_type'] = 'JSAPI';
  143. }
  144. }
  145. try {
  146. $res = self::$wechat_pay->order->unify($config);
  147. } catch (GuzzleException $e) {
  148. return [
  149. 'code' => 1,
  150. 'msg' => $e->getMessage()
  151. ];
  152. } catch (\Exception $e) {
  153. return [
  154. 'code' => 1,
  155. 'msg' => $e->getMessage()
  156. ];
  157. }
  158. if (!$res) {
  159. return [
  160. 'code' => 1,
  161. 'msg' => '支付失败',
  162. ];
  163. }
  164. if ($res['return_code'] != 'SUCCESS') {
  165. return [
  166. 'code' => 1,
  167. 'msg' => '支付失败,' . (isset($res['return_msg']) ? $res['return_msg'] : ''),
  168. 'res' => $res,
  169. ];
  170. }
  171. if ($res['result_code'] != 'SUCCESS') {
  172. if ($res['err_code'] == 'INVALID_REQUEST') { //商户订单号重复
  173. $order->order_no = OrderNo::getOrderNo($type);
  174. $order->save();
  175. if ($type != OrderNo::ORDER_UNION) {
  176. if ($is_app) {
  177. return self::orderUnify($order, $type, $goods_names, 0, true, $balance_price);
  178. } else if ($is_h5) {
  179. return self::orderUnify($order, $type, $goods_names, 0, false, $balance_price, true);
  180. } else if ($is_official) {
  181. return self::orderUnify($order, $type, $goods_names, 0, false, $balance_price, false, true);
  182. } else {
  183. return self::orderUnify($order, $type, $goods_names, 0, false, $balance_price);
  184. }
  185. }
  186. if ($is_app) {
  187. return self::orderUnify($order, $type, '', $total_pay_price, true, $balance_price);
  188. } else if ($is_h5) {
  189. return self::orderUnify($order, $type, '', $total_pay_price, false, $balance_price, true);
  190. } else if ($is_official) {
  191. return self::orderUnify($order, $type, '', $total_pay_price, false, $balance_price, false, true);
  192. } else {
  193. return self::orderUnify($order, $type, '', $total_pay_price, false, $balance_price);
  194. }
  195. } else {
  196. return [
  197. 'code' => 1,
  198. 'msg' => '支付失败,' . (isset($res['err_code_des']) ? $res['err_code_des'] : ''),
  199. 'res' => $res,
  200. ];
  201. }
  202. }
  203. if ($is_app) {
  204. $pay_data = self::$wechat_pay->jssdk->appConfig($res['prepay_id']);
  205. } else {
  206. $pay_data = self::$wechat_pay->jssdk->bridgeConfig($res['prepay_id'], false);
  207. }
  208. $return = [
  209. 'code' => 0,
  210. 'msg' => 'success',
  211. 'res' => $res,
  212. 'data' => $pay_data
  213. ];
  214. if ($type == OrderNo::ORDER_UNION) {
  215. foreach ($order as $value) {
  216. $value->order_union_id = $order_union->id;
  217. $value->save();
  218. }
  219. $return['order_no'] = $config['out_trade_no'];
  220. $return['body'] = $config['body'];
  221. }
  222. \Yii::error($return);
  223. return $return;
  224. }
  225. /**
  226. * 退款
  227. * @param Object $order
  228. * @param string $orderRefundNo
  229. * @param string $type
  230. * @param integer $refundFee
  231. * @param $refund_account
  232. * @return array
  233. */
  234. public static function orderRefund($order, $type, $refundFee, $orderRefundNo, $refund_account = null) {
  235. try {
  236. if ($order->order_origin == Order::ORDER_SOURCE_APP) {
  237. parent::init($order->store_id, Wechat::WECHAT_KIND_PAY, WechatConfig::TYPE_CONFIG_APP);
  238. } else if ($order->order_origin == Order::ORDER_SOURCE_MP) {
  239. parent::init($order->store_id, Wechat::WECHAT_KIND_PAY, WechatConfig::TYPE_CONFIG_MP);
  240. } else if ($order->order_origin == Order::ORDER_SOURCE_WEB) {
  241. parent::init($order->store_id, Wechat::WECHAT_KIND_PAY, WechatConfig::TYPE_CONFIG_H5);
  242. } else {
  243. parent::init($order->store_id, Wechat::WECHAT_KIND_PAY);
  244. }
  245. } catch (\Exception $e) {
  246. return [
  247. 'code' => 1,
  248. 'msg' => $e->getMessage()
  249. ];
  250. }
  251. if ($type != OrderNo::ORDER_UNION) {
  252. if ($order->is_combine_pay == 1 && $order->combine_money > 0) {
  253. $payPrice = round($order->pay_price - $order->combine_money, 2);
  254. } else {
  255. $payPrice = $order->pay_price;
  256. }
  257. } else {
  258. // 联合订单支付的总额
  259. $payPrice = $order->price;
  260. }
  261. if ($order->is_combine_pay == 1 && $order->combine_money > 0) {
  262. $refundFee = round($refundFee - $order->combine_money, 2);
  263. }
  264. $data = [
  265. 'out_trade_no' => $order->order_no,
  266. 'out_refund_no' => $orderRefundNo,
  267. 'total_fee' => intval(bcmul(number_format($payPrice, 2, '.', ''), 100, 2)),
  268. 'refund_fee' => intval(bcmul(number_format($refundFee, 2, '.', ''), 100, 2)),
  269. ];
  270. if (isset($order->order_union_id) && $order->order_union_id != 0) {
  271. // 多商户合并订单退款
  272. $orderUnion = OrderUnion::findOne($order->order_union_id);
  273. if (!$orderUnion) {
  274. return [
  275. 'code' => 1,
  276. 'msg' => '订单取消失败,合并支付订单不存在。',
  277. ];
  278. }
  279. $data['out_trade_no'] = $orderUnion->order_no;
  280. $data['total_fee'] = $orderUnion->price * 100;
  281. }
  282. $config = [];
  283. if ($refund_account) {
  284. $config['refund_account'] = $refund_account;
  285. }
  286. try {
  287. $res = self::$wechat_pay->refund->byOutTradeNumber($data['out_trade_no'], $orderRefundNo, $data['total_fee'],
  288. $data['refund_fee'], $config);
  289. } catch (\Exception $e) {
  290. return [
  291. 'code' => 1,
  292. 'msg' => $e->getMessage()
  293. ];
  294. }
  295. if (!$res) {
  296. return [
  297. 'code' => 1,
  298. 'msg' => '订单取消失败,退款失败,服务端配置出错',
  299. ];
  300. }
  301. if ($res['return_code'] != 'SUCCESS') {
  302. return [
  303. 'code' => 1,
  304. 'msg' => '订单取消失败,退款失败,' . $res['return_msg'],
  305. 'res' => $res,
  306. ];
  307. }
  308. if (isset($res['err_code']) && $res['err_code'] == 'NOTENOUGH' && !$refund_account) {
  309. // 交易未结算资金不足,请使用可用余额退款
  310. return self::orderRefund($order, $type, $refundFee, $orderRefundNo, 'REFUND_SOURCE_RECHARGE_FUNDS');
  311. }
  312. if ($res['result_code'] != 'SUCCESS') {
  313. $refundQuery = self::$wechat_pay->order->queryByOutTradeNumber($order->order_no);
  314. if ($refundQuery['return_code'] != 'SUCCESS') {
  315. return [
  316. 'code' => 1,
  317. 'msg' => '订单取消失败,退款失败,' . ($refundQuery['return_msg'] ?: $res['err_code_des']),
  318. 'res' => $refundQuery,
  319. 'res0' => $res,
  320. ];
  321. }
  322. if ($refundQuery['result_code'] == 'FAIL') {
  323. return [
  324. 'code' => 1,
  325. 'msg' => '订单取消失败,退款失败,' . $res['err_code_des'],
  326. 'res' => $res,
  327. ];
  328. }
  329. if ($refundQuery['result_code'] != 'SUCCESS') {
  330. return [
  331. 'code' => 1,
  332. 'msg' => '订单取消失败,退款失败,' . ($refundQuery['err_code_des'] ?: $res['err_code_des']),
  333. 'res' => $refundQuery,
  334. 'res0' => $res,
  335. ];
  336. }
  337. if ($refundQuery['refund_status_0'] != 'SUCCESS') {
  338. return [
  339. 'code' => 1,
  340. 'msg' => '订单取消失败,退款失败,' . ($refundQuery['err_code_des'] ?: $res['err_code_des']),
  341. 'res' => $refundQuery,
  342. 'res0' => $res,
  343. ];
  344. }
  345. }
  346. // 联合支付,退余额
  347. if ($order->is_combine_pay == 1 && $order->combine_money > 0) {
  348. AccountLog::saveLog($order->user_id, $order->combine_money, AccountLog::TYPE_BALANCE, AccountLog::LOG_TYPE_INCOME, AccountLog::TYPE_PLATFORM_REFUND_ORDER, $order->id, "商城订单退款,订单号:{$order->order_no}");
  349. }
  350. return [
  351. 'code' => 0,
  352. 'msg' => 'success',
  353. 'data' => true
  354. ];
  355. }
  356. /**
  357. * 转账
  358. * @param string $trade_no 商户订单号
  359. * @param integer $amount
  360. * @param integer $user_name
  361. * @param string $desc
  362. * @param string $openid
  363. * @return array
  364. * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
  365. * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
  366. * @throws \GuzzleHttp\Exception\GuzzleException
  367. */
  368. public static function transfer($trade_no, $amount, $user_name, $openid, $desc = null) {
  369. if (!$trade_no || $amount <= 0 || empty($amount) || empty($openid)) {
  370. return [
  371. 'code' => 1,
  372. 'msg' => '缺少必要参数'
  373. ];
  374. }
  375. try {
  376. parent::init(get_store_id(), Wechat::WECHAT_KIND_PAY);
  377. } catch (\Exception $e) {
  378. return [
  379. 'code' => 1,
  380. 'msg' => $e->getMessage()
  381. ];
  382. }
  383. $data = [
  384. 'partner_trade_no' => $trade_no,
  385. 'openid' => $openid,
  386. 'check_name' => 'FORCE_CHECK', // 强校验用户姓名 NO_CHECK:不校验用户姓名
  387. 're_user_name' => $user_name,
  388. 'amount' => $amount * 100,
  389. 'desc' => $desc ? $desc : '转账'
  390. ];
  391. $res = self::$wechat_pay->transfer->toBalance($data);
  392. if ($res['return_code'] != 'SUCCESS') {
  393. return [
  394. 'code' => 1,
  395. 'msg' => '转账失败:,' . $res['return_msg'],
  396. 'res' => $res,
  397. ];
  398. }
  399. if ($res['result_code'] != 'SUCCESS') {
  400. return [
  401. 'code' => 1,
  402. 'msg' => '转账失败:' . $res['err_code_des'],
  403. 'res' => $res,
  404. ];
  405. }
  406. return [
  407. 'code' => 0,
  408. 'msg' => 'success',
  409. 'data' => true
  410. ];
  411. }
  412. /**
  413. * 查询订单
  414. * @param $trade_no
  415. * @return array
  416. */
  417. public static function find($trade_no) {
  418. if (!$trade_no) {
  419. return [
  420. 'code' => 1,
  421. 'msg' => '缺少必要参数'
  422. ];
  423. }
  424. try {
  425. parent::init(get_store_id(), Wechat::WECHAT_KIND_PAY);
  426. } catch (\Exception $e) {
  427. return [
  428. 'code' => 1,
  429. 'msg' => $e->getMessage()
  430. ];
  431. }
  432. try {
  433. $res = self::$wechat_pay->order->queryByOutTradeNumber($trade_no);
  434. if ($res['trade_state'] == 'SUCCESS') {
  435. return [
  436. 'code' => 0,
  437. 'msg' => 'success',
  438. 'res' => $res,
  439. ];
  440. }
  441. } catch (Exception $e) {
  442. \Yii::warning(['WECHAT QUERY ORDER <==========>', $trade_no, $e->getMessage()]);
  443. }
  444. return [
  445. 'code' => 1,
  446. 'msg' => 'fail',
  447. 'data' => false
  448. ];
  449. }
  450. // 微信扫码付款
  451. public static function micropay($order, $type, $goods_names = '赤炎鹰-超市', $total_pay_price = 0,$balance_price = 0,$auth_code){
  452. parent::init(get_store_id(), Wechat::WECHAT_KIND_PAY);
  453. $data = [
  454. 'appid' => self::$wechat_config->app_id,
  455. 'mch_id' => self::$wechat_config->mch_id,
  456. 'nonce_str' => self::makeNonceStr(12),
  457. 'body' => $goods_names,
  458. 'out_trade_no' => $order->order_no,
  459. //'total_fee' => (int)$order->pay_price * 100,
  460. 'total_fee' => $balance_price > 0 ? round(floatval($total_pay_price - $balance_price) * 100) : $total_pay_price * 100,
  461. 'spbill_create_ip' => $_SERVER['REMOTE_ADDR'],
  462. 'auth_code' => $auth_code,
  463. 'sign_type' => 'MD5'
  464. ];
  465. $data['sign'] = self::makeSign($data);
  466. // 请求微信的支付
  467. $data = DataTransform::arrayToXml($data);
  468. $result = cloud_post('https://api.mch.weixin.qq.com/pay/micropay',$data);
  469. return DataTransform::xmlToArray($result);
  470. }
  471. // 查询扫码付款状态
  472. public static function orderQuery($order){
  473. parent::init(get_store_id(), Wechat::WECHAT_KIND_PAY);
  474. $data = [
  475. 'appid' => self::$wechat_config->app_id,
  476. 'mch_id' => self::$wechat_config->mch_id,
  477. 'out_trade_no' => $order->order_no,
  478. 'nonce_str' => self::makeNonceStr(12),
  479. 'sign_type' => 'MD5'
  480. ];
  481. $data['sign'] = self::makeSign($data);
  482. // 请求微信的支付
  483. $data = DataTransform::arrayToXml($data);
  484. $result = cloud_post('https://api.mch.weixin.qq.com/pay/orderquery',$data);
  485. return DataTransform::xmlToArray($result);
  486. }
  487. /**
  488. * 生成签名
  489. * @param $data
  490. * @return string
  491. * User: hankaige
  492. * DATE TIME: 2022/12/8 13:46
  493. */
  494. public static function makeSign($data){
  495. // 去空
  496. foreach($data as $key=>$value){
  497. if(empty($value)){
  498. unset($data[$key]);
  499. }
  500. }
  501. // 按ASSII排序
  502. ksort($data);
  503. // 转字符串
  504. $str = '';
  505. foreach($data as $m=>$n){
  506. $str .= $m . '=' . $n . '&';
  507. }
  508. $str .= 'key='.self::$wechat_config->pay_key;
  509. // 进行
  510. return strtoupper(MD5($str));
  511. }
  512. /**
  513. * 获取随机字符串
  514. * @param $len
  515. * @return string
  516. * User: hankaige
  517. * DATE TIME: 2022/12/8 13:46
  518. */
  519. public static function makeNonceStr($len = 6): string
  520. {
  521. $chars = array(
  522. "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k",
  523. "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v",
  524. "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G",
  525. "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R",
  526. "S", "T", "U", "V", "W", "X", "Y", "Z", "0", "1", "2",
  527. "3", "4", "5", "6", "7", "8", "9"
  528. );
  529. $charsLen = count($chars) - 1;
  530. shuffle($chars); // 将数组打乱
  531. $output = "";
  532. for ($i = 0; $i < $len; $i++) {
  533. $output .= $chars[mt_rand(0, $charsLen)];
  534. }
  535. return $output;
  536. }
  537. }