WeChatPayService.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | likeadmin快速开发前后端分离管理后台(PHP版)
  4. // +----------------------------------------------------------------------
  5. // | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
  6. // | 开源版本可自由商用,可去除界面版权logo
  7. // | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
  8. // | github下载:https://github.com/likeshop-github/likeadmin
  9. // | 访问官网:https://www.likeadmin.cn
  10. // | likeadmin团队 版权所有 拥有最终解释权
  11. // +----------------------------------------------------------------------
  12. // | author: likeadminTeam
  13. // +----------------------------------------------------------------------
  14. namespace app\common\service\pay;
  15. use app\common\enum\PayEnum;
  16. use app\common\enum\user\UserTerminalEnum;
  17. use app\common\logic\PayNotifyLogic;
  18. use app\common\model\recharge\RechargeOrder;
  19. use app\common\model\user\UserAuth;
  20. use app\common\model\works\ServiceWork;
  21. use app\common\model\group_activity\GroupUserOrder;
  22. use app\common\service\wechat\WeChatConfigService;
  23. use EasyWeChat\Pay\Application;
  24. use EasyWeChat\Pay\Message;
  25. use think\facade\Log;
  26. /**
  27. * 微信支付
  28. * Class WeChatPayService
  29. * @package app\common\server
  30. */
  31. class WeChatPayService extends BasePayService
  32. {
  33. /**
  34. * 授权信息
  35. * @var UserAuth|array|\think\Model
  36. */
  37. protected $auth;
  38. /**
  39. * 微信配置
  40. * @var
  41. */
  42. protected $config;
  43. /**
  44. * easyWeChat实例
  45. * @var
  46. */
  47. protected $app;
  48. /**
  49. * 当前使用客户端
  50. * @var
  51. */
  52. protected $terminal;
  53. /**
  54. * 初始化微信支付配置
  55. * @param $terminal //用户终端
  56. * @param null $userId //用户id(获取授权openid)
  57. */
  58. public function __construct($terminal, $userId = null)
  59. {
  60. $this->terminal = $terminal;
  61. $this->config = WeChatConfigService::getPayConfigByTerminal($terminal);
  62. $this->app = new Application($this->config);
  63. if ($userId !== null) {
  64. $this->auth = UserAuth::where(['user_id' => $userId, 'terminal' => $terminal])->findOrEmpty();
  65. }
  66. }
  67. /**
  68. * @notes 发起微信支付统一下单
  69. * @param $from
  70. * @param $order
  71. * @return array|false|string
  72. * @author 段誉
  73. * @date 2021/8/4 15:05
  74. */
  75. public function pay($from, $order)
  76. {
  77. try {
  78. switch ($this->terminal) {
  79. case UserTerminalEnum::WECHAT_MMP:
  80. $config = WeChatConfigService::getMnpConfig();
  81. $result = $this->jsapiPay($from, $order, $config['app_id']);
  82. break;
  83. case UserTerminalEnum::WECHAT_OA:
  84. $config = WeChatConfigService::getOaConfig();
  85. $result = $this->jsapiPay($from, $order, $config['app_id']);
  86. break;
  87. case UserTerminalEnum::IOS:
  88. case UserTerminalEnum::ANDROID:
  89. $config = WeChatConfigService::getOpConfig();
  90. $result = $this->appPay($from, $order, $config['app_id']);
  91. break;
  92. case UserTerminalEnum::H5:
  93. $config = WeChatConfigService::getOaConfig();
  94. $result = $this->mwebPay($from, $order, $config['app_id']);
  95. break;
  96. case UserTerminalEnum::PC:
  97. $config = WeChatConfigService::getOaConfig();
  98. $result = $this->nativePay($from, $order, $config['app_id']);
  99. break;
  100. default:
  101. throw new \Exception('支付方式错误');
  102. }
  103. return [
  104. 'config' => $result,
  105. 'pay_way' => PayEnum::WECHAT_PAY
  106. ];
  107. } catch (\Exception $e) {
  108. $this->setError($e->getMessage());
  109. return false;
  110. }
  111. }
  112. /**
  113. * @notes jsapiPay
  114. * @param $from
  115. * @param $order
  116. * @param $appId
  117. * @return mixed
  118. * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
  119. * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
  120. * @author 段誉
  121. * @date 2023/2/28 12:12
  122. */
  123. public function jsapiPay($from, $order, $appId)
  124. {
  125. $response = $this->app->getClient()->postJson("v3/pay/transactions/jsapi", [
  126. "appid" => $appId,
  127. "mchid" => $this->config['mch_id'],
  128. "description" => $this->payDesc($from),
  129. "out_trade_no" => $order['pay_sn'],
  130. "notify_url" => $this->config['notify_url'],
  131. "amount" => [
  132. "total" => intval($order['order_amount'] * 100),
  133. ],
  134. "payer" => [
  135. "openid" => $this->auth['openid']
  136. ],
  137. 'attach' => $from
  138. ]);
  139. Log::write(json_encode($this->config, JSON_UNESCAPED_UNICODE));
  140. $result = $response->toArray(false);
  141. $this->checkResultFail($result);
  142. return $this->getPrepayConfig($result['prepay_id'], $appId);
  143. }
  144. /**
  145. * @notes 网站native
  146. * @param $from
  147. * @param $order
  148. * @param $appId
  149. * @return mixed
  150. * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
  151. * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
  152. * @author 段誉
  153. * @date 2023/2/28 12:12
  154. */
  155. public function nativePay($from, $order, $appId)
  156. {
  157. $response = $this->app->getClient()->postJson('v3/pay/transactions/native', [
  158. 'appid' => $appId,
  159. 'mchid' => $this->config['mch_id'],
  160. 'description' => $this->payDesc($from),
  161. 'out_trade_no' => $order['pay_sn'],
  162. 'notify_url' => $this->config['notify_url'],
  163. 'amount' => [
  164. 'total' => intval($order['order_amount'] * 100),
  165. ],
  166. 'attach' => $from
  167. ]);
  168. $result = $response->toArray(false);
  169. $this->checkResultFail($result);
  170. return $result['code_url'];
  171. }
  172. /**
  173. * @notes appPay
  174. * @param $from
  175. * @param $order
  176. * @param $appId
  177. * @return mixed
  178. * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
  179. * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
  180. * @author 段誉
  181. * @date 2023/2/28 12:12
  182. */
  183. public function appPay($from, $order, $appId)
  184. {
  185. $response = $this->app->getClient()->postJson('v3/pay/transactions/app', [
  186. 'appid' => $appId,
  187. 'mchid' => $this->config['mch_id'],
  188. 'description' => $this->payDesc($from),
  189. 'out_trade_no' => $order['pay_sn'],
  190. 'notify_url' => $this->config['notify_url'],
  191. 'amount' => [
  192. 'total' => intval($order['order_amount'] * 100),
  193. ],
  194. 'attach' => $from
  195. ]);
  196. $result = $response->toArray(false);
  197. $this->checkResultFail($result);
  198. return $result['prepay_id'];
  199. }
  200. /**
  201. * @notes h5
  202. * @param $from
  203. * @param $order
  204. * @param $appId
  205. * @param $redirectUrl
  206. * @return mixed
  207. * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
  208. * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
  209. * @author 段誉
  210. * @date 2023/2/28 12:13
  211. */
  212. public function mwebPay($from, $order, $appId)
  213. {
  214. $ip = request()->ip();
  215. if (!empty(env('project.test_web_ip')) && env('APP_DEBUG')) {
  216. $ip = env('project.test_web_ip');
  217. }
  218. $response = $this->app->getClient()->postJson('v3/pay/transactions/h5', [
  219. 'appid' => $appId,
  220. 'mchid' => $this->config['mch_id'],
  221. 'description' => $this->payDesc($from),
  222. 'out_trade_no' => $order['pay_sn'],
  223. 'notify_url' => $this->config['notify_url'],
  224. 'amount' => [
  225. 'total' => intval(strval($order['order_amount'] * 100)),
  226. ],
  227. 'attach' => $from,
  228. 'scene_info' => [
  229. 'payer_client_ip' => $ip,
  230. 'h5_info' => [
  231. 'type' => 'Wap',
  232. ]
  233. ]
  234. ]);
  235. $result = $response->toArray(false);
  236. $this->checkResultFail($result);
  237. $domain = request()->domain();
  238. if (!empty(env('project.test_web_domain')) && env('APP_DEBUG')) {
  239. $domain = env('project.test_web_domain');
  240. }
  241. $redirectUrl = $domain . '/mobile'. $order['redirect_url'] .'?id=' . $order['id'] . '&from='. $from . '&checkPay=true';
  242. return $result['h5_url'] . '&redirect_url=' . urlencode($redirectUrl);
  243. }
  244. /**
  245. * @notes 退款
  246. * @param array $refundData
  247. * @return mixed
  248. * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
  249. * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
  250. * @author 段誉
  251. * @date 2023/2/28 16:53
  252. */
  253. public function refund(array $refundData)
  254. {
  255. $response = $this->app->getClient()->postJson('v3/refund/domestic/refunds', [
  256. 'transaction_id' => $refundData['transaction_id'],
  257. 'out_refund_no' => $refundData['refund_sn'],
  258. 'amount' => [
  259. 'refund' => intval($refundData['refund_amount'] * 100),
  260. 'total' => intval($refundData['total_amount'] * 100),
  261. 'currency' => 'CNY',
  262. ]
  263. ]);
  264. $result = $response->toArray(false);
  265. $this->checkResultFail($result);
  266. return $result;
  267. }
  268. /**
  269. * @notes 查询退款
  270. * @param $refundSn
  271. * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
  272. * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
  273. * @author 段誉
  274. * @date 2023/3/1 11:16
  275. */
  276. public function queryRefund($refundSn)
  277. {
  278. $response = $this->app->getClient()->get("v3/refund/domestic/refunds/{$refundSn}");
  279. return $response->toArray(false);
  280. }
  281. /**
  282. * @notes 支付描述
  283. * @param $from
  284. * @return string
  285. * @author 段誉
  286. * @date 2023/2/27 17:54
  287. */
  288. public function payDesc($from)
  289. {
  290. $desc = [
  291. 'goods' => '商品',
  292. 'recharge' => '充值',
  293. ];
  294. return $desc[$from] ?? '商品';
  295. }
  296. /**
  297. * @notes 捕获错误
  298. * @param $result
  299. * @throws \Exception
  300. * @author 段誉
  301. * @date 2023/2/28 12:09
  302. */
  303. public function checkResultFail($result)
  304. {
  305. if (!empty($result['code']) || !empty($result['message'])) {
  306. throw new \Exception('微信:'. $result['code'] . '-' . $result['message']);
  307. }
  308. }
  309. /**
  310. * @notes 预支付配置
  311. * @param $prepayId
  312. * @param $appId
  313. * @return mixed[]
  314. * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
  315. * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
  316. * @author 段誉
  317. * @date 2023/2/28 17:38
  318. */
  319. public function getPrepayConfig($prepayId, $appId)
  320. {
  321. return $this->app->getUtils()->buildBridgeConfig($prepayId, $appId);
  322. }
  323. /**
  324. * @notes 支付回调
  325. * @return \Psr\Http\Message\ResponseInterface
  326. * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
  327. * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
  328. * @throws \ReflectionException
  329. * @throws \Throwable
  330. * @author 段誉
  331. * @date 2023/2/28 14:20
  332. */
  333. public function notify()
  334. {
  335. $server = $this->app->getServer();
  336. // 支付通知
  337. $server->handlePaid(function (Message $message) {
  338. if ($message['trade_state'] === 'SUCCESS') {
  339. $extra['transaction_id'] = $message['transaction_id'];
  340. $attach = $message['attach'];
  341. $message['out_trade_no'] = mb_substr($message['out_trade_no'], 0, 18);
  342. switch ($attach) {
  343. case 'recharge':
  344. $order = RechargeOrder::where(['sn' => $message['out_trade_no']])->findOrEmpty();
  345. if($order->isEmpty() || $order->pay_status == PayEnum::ISPAID) {
  346. return true;
  347. }
  348. PayNotifyLogic::handle('recharge', $message['out_trade_no'], $extra);
  349. break;
  350. case 'goods':
  351. $order = RechargeOrder::where(['sn' => $message['out_trade_no']])->findOrEmpty();
  352. if($order->isEmpty() || $order->pay_status == PayEnum::ISPAID) {
  353. return true;
  354. }
  355. $res = PayNotifyLogic::handle('goods', $message['out_trade_no'], $extra);
  356. if($res === true){
  357. // 用户下单后,给订单运营专员(配置固定ID)发送公众号提醒(订单信息)
  358. $order = RechargeOrder::where('sn', $message['out_trade_no'])
  359. ->where('payment_type','IN',[0,1])
  360. ->where('pay_status','=',1)
  361. ->findOrEmpty();
  362. if(!$order->isEmpty()){
  363. $workDetail = ServiceWork::findOrEmpty($order->work_id);
  364. if(!$workDetail->isEmpty()){
  365. event('Notice', [
  366. 'scene_id' => 100,
  367. 'params' => [
  368. 'user_id' => 0,
  369. 'order_id' => $workDetail['id'],
  370. 'thing3' => $workDetail['title'],
  371. 'time6' => $workDetail['appointment_time'],
  372. 'phone_number8' => asteriskString($workDetail['mobile']),
  373. 'thing5' => (iconv_strlen($workDetail['address'])>15)?(mb_substr($workDetail['address'],0,15,'UTF-8').'...'):$workDetail['address'],
  374. ]
  375. ]);
  376. }
  377. }
  378. // 订单完成通知【给用户】 - 尾款 -通知
  379. $order = RechargeOrder::where('sn', $message['out_trade_no'])
  380. ->where('payment_type','=',2)
  381. ->where('pay_status','=',1)
  382. ->findOrEmpty();
  383. if(!$order->isEmpty()){
  384. event('Notice', [
  385. 'scene_id' => 120,
  386. 'params' => [
  387. 'user_id' => $order['user_id']
  388. ]
  389. ]);
  390. }
  391. }
  392. break;
  393. case 'group':
  394. $order = GroupUserOrder::where(['sn' => $message['out_trade_no']])->findOrEmpty();
  395. if($order->isEmpty() || $order->pay_status == PayEnum::ISPAID) {
  396. return true;
  397. }
  398. PayNotifyLogic::handle('group', $message['out_trade_no'], $extra);
  399. break;
  400. }
  401. }
  402. return true;
  403. });
  404. // 退款通知
  405. $server->handleRefunded(function (Message $message) {
  406. return true;
  407. });
  408. return $server->serve();
  409. }
  410. }