Payment.php 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2016~2020 https://www.crmeb.com All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
  8. // +----------------------------------------------------------------------
  9. // | Author: CRMEB Team <admin@crmeb.com>
  10. // +----------------------------------------------------------------------
  11. namespace crmeb\services\wechat;
  12. use crmeb\exceptions\PayException;
  13. use crmeb\services\wechat\config\MiniProgramConfig;
  14. use crmeb\services\wechat\config\OpenAppConfig;
  15. use crmeb\services\wechat\config\OpenWebConfig;
  16. use crmeb\services\wechat\config\PaymentConfig;
  17. use crmeb\services\wechat\config\V3PaymentConfig;
  18. use crmeb\services\wechat\v3pay\ServiceProvider;
  19. use EasyWeChat\Factory;
  20. use EasyWeChat\Kernel\Exceptions\Exception;
  21. use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
  22. use EasyWeChat\Kernel\Exceptions\InvalidConfigException;
  23. use EasyWeChat\Kernel\Support\Collection;
  24. use EasyWeChat\Payment\Application;
  25. use GuzzleHttp\Exception\GuzzleException;
  26. use Psr\Http\Message\ResponseInterface;
  27. use Symfony\Component\Cache\Adapter\RedisAdapter;
  28. use Symfony\Component\HttpFoundation\Request;
  29. use think\facade\Cache;
  30. use think\facade\Event;
  31. use think\Response;
  32. use Yurun\Util\Swoole\Guzzle\SwooleHandler;
  33. use crmeb\services\wechat\Factory as miniFactory;
  34. /**
  35. * 微信支付
  36. * Class Payment
  37. * @package crmeb\services\wechat
  38. */
  39. class Payment extends BaseApplication
  40. {
  41. /**
  42. * @var PaymentConfig
  43. */
  44. protected $config;
  45. /**
  46. * @var
  47. */
  48. protected $v3Config;
  49. /**
  50. * 是否v3支付
  51. * @var bool
  52. */
  53. public $isV3PAy = true;
  54. /**
  55. * @var array
  56. */
  57. protected $application = [];
  58. /**
  59. * Payment constructor.
  60. * @param PaymentConfig $config
  61. */
  62. public function __construct(PaymentConfig $config, V3PaymentConfig $v3Config)
  63. {
  64. $this->config = $config;
  65. $this->v3Config = $v3Config;
  66. $this->isV3PAy = $this->v3Config->get('isV3PAy');
  67. $this->debug = DefaultConfig::value('logger');
  68. }
  69. /**
  70. * @return Payment
  71. */
  72. public static function instance()
  73. {
  74. return app()->make(static::class);
  75. }
  76. /**
  77. * @return Application|mixed
  78. * @author 等风来
  79. * @email 136327134@qq.com
  80. * @date 2022/10/11
  81. */
  82. public function application()
  83. {
  84. $request = request();
  85. $config = $this->config->all();
  86. switch ($accessEnd = $this->getAuthAccessEnd($request)) {
  87. case self::APP:
  88. /** @var OpenAppConfig $make */
  89. $make = app()->make(OpenAppConfig::class);
  90. $config['app_id'] = $make->get('appId');
  91. $config['notify_url'] = trim($make->getConfig(DefaultConfig::COMMENT_URL)) . DefaultConfig::value('app.notifyUrl');
  92. break;
  93. case self::PC:
  94. /** @var OpenWebConfig $make */
  95. $make = app()->make(OpenWebConfig::class);
  96. $config['app_id'] = $make->get('appId');
  97. break;
  98. case self::MINI:
  99. /** @var MiniProgramConfig $make */
  100. $make = app()->make(MiniProgramConfig::class);
  101. $config['app_id'] = $make->get('appId');
  102. $config['notify_url'] = trim($make->getConfig(DefaultConfig::COMMENT_URL)) . DefaultConfig::value('mini.notifyUrl');
  103. break;
  104. }
  105. //v3支付配置
  106. $config['v3_payment'] = $this->v3Config->all();
  107. if (!isset($this->application[$accessEnd])) {
  108. $this->application[$accessEnd] = Factory::payment($config);
  109. $this->application[$accessEnd]['guzzle_handler'] = SwooleHandler::class;
  110. $this->application[$accessEnd]->rebind('request', new Request($request->get(), $request->post(), [], [], [], $request->server(), $request->getContent()));
  111. $this->application[$accessEnd]->register(new ServiceProvider());
  112. $this->application[$accessEnd]->rebind('cache', new RedisAdapter(Cache::store('redis')->handler()));
  113. }
  114. return $this->application[$accessEnd];
  115. }
  116. /**
  117. * @return \crmeb\services\wechat\MiniPayment\Application
  118. */
  119. public function miniApplication($isMerchantPay = false)
  120. {
  121. $request = request();
  122. $accessEnd = $this->getAuthAccessEnd($request);
  123. if (!$isMerchantPay && $accessEnd !== 'mini') {
  124. throw new PayException('支付方式错误,请刷新后重试!');
  125. }
  126. $config = $this->config->all();
  127. /** @var MiniProgramConfig $make */
  128. $make = app()->make(MiniProgramConfig::class);
  129. $config['app_id'] = $make->get('appId');
  130. $config['secret'] = $make->get('secret');
  131. $config['mch_id'] = $this->config->get('routineMchId');
  132. if (!isset($this->application[$accessEnd])) {
  133. $this->application[$accessEnd] = miniFactory::MiniPayment($config);
  134. $this->application[$accessEnd]['guzzle_handler'] = SwooleHandler::class;
  135. $this->application[$accessEnd]->rebind('request', new Request($request->get(), $request->post(), [], [], [], $request->server(), $request->getContent()));
  136. }
  137. return $this->application[$accessEnd];
  138. }
  139. /**
  140. * 付款码支付
  141. * @param string $authCode
  142. * @param string $outTradeNo
  143. * @param string $totalFee
  144. * @param string $attach
  145. * @param string $body
  146. * @param string $detail
  147. * @return array
  148. * @throws InvalidConfigException
  149. * @throws InvalidArgumentException
  150. * @throws GuzzleException
  151. */
  152. public static function microPay(string $authCode, string $outTradeNo, string $totalFee, string $attach, string $body, string $detail = '')
  153. {
  154. $application = self::instance()->application();
  155. $totalFee = bcmul($totalFee, 100, 0);
  156. $response = $application->pay([
  157. 'auth_code' => $authCode,
  158. 'out_trade_no' => $outTradeNo,
  159. 'total_fee' => (int)$totalFee,
  160. 'attach' => $attach,
  161. 'body' => $body,
  162. 'detail' => $detail
  163. ]);
  164. self::logger('付款码支付', compact('authCode', 'outTradeNo', 'totalFee', 'attach', 'body', 'detail'), $response);
  165. //下单成功
  166. if ($response['return_code'] === 'SUCCESS') {
  167. //扫码付款直接支付成功
  168. if ($response['result_code'] === 'SUCCESS' && $response['trade_type'] === 'MICROPAY') {
  169. return [
  170. 'paid' => 1,
  171. 'message' => '支付成功',
  172. 'payInfo' => $response,
  173. ];
  174. } else {
  175. return [
  176. 'paid' => 0,
  177. 'message' => $response['err_code_des'],
  178. 'payInfo' => $response
  179. ];
  180. }
  181. } else {
  182. throw new PayException($response['return_msg']);
  183. }
  184. }
  185. /**
  186. * 撤销订单
  187. * @param string $outTradeNo
  188. * @return bool
  189. * @throws InvalidConfigException
  190. */
  191. public static function reverseOrder(string $outTradeNo)
  192. {
  193. $response = self::instance()->application()->reverse->byOutTradeNumber($outTradeNo);
  194. self::logger('撤销订单', compact('outTradeNo'), $response);
  195. if ($response['return_code'] === 'SUCCESS') {
  196. return true;
  197. } else {
  198. throw new PayException($response['return_msg']);
  199. }
  200. }
  201. /**
  202. * 查询订单支付状态
  203. * @param string $outTradeNo
  204. * @return array
  205. * @throws InvalidArgumentException
  206. * @throws InvalidConfigException
  207. */
  208. public static function queryOrder(string $outTradeNo)
  209. {
  210. $response = self::instance()->application()->order->queryByOutTradeNumber($outTradeNo);
  211. self::logger('查询订单支付状态', compact('outTradeNo'), $response);
  212. if ($response['return_code'] === 'SUCCESS') {
  213. if ($response['result_code'] === 'SUCCESS') {
  214. return [
  215. 'paid' => 1,
  216. 'out_trade_no' => $outTradeNo,
  217. 'payInfo' => $response
  218. ];
  219. } else {
  220. return [
  221. 'paid' => 0,
  222. 'out_trade_no' => $outTradeNo,
  223. 'payInfo' => $response
  224. ];
  225. }
  226. } else {
  227. throw new PayException($response['return_msg']);
  228. }
  229. }
  230. /**
  231. * 企业付款到零钱
  232. * @param string $openid openid
  233. * @param string $orderId 订单号
  234. * @param string $amount 金额
  235. * @param string $desc 说明
  236. * @param string $type 类型
  237. * @return bool
  238. * @throws GuzzleException
  239. * @throws InvalidArgumentException
  240. * @throws InvalidConfigException
  241. */
  242. public static function merchantPay(string $openid, string $orderId, string $amount, string $desc, string $type = 'wechat')
  243. {
  244. $application = self::instance()->setAccessEnd($type)->application();
  245. $config = $application->getConfig();
  246. if (!isset($config['cert_path'])) {
  247. throw new PayException('企业微信支付到零钱需要支付证书,检测到您没有上传!');
  248. }
  249. if (!$config['cert_path']) {
  250. throw new PayException('企业微信支付到零钱需要支付证书,检测到您没有上传!');
  251. }
  252. if (self::instance()->isV3PAy) {
  253. //v3支付使用发起商家转账API
  254. $res = $application->v3pay->setType($type)->batches(
  255. $orderId,
  256. $amount,
  257. $desc,
  258. $desc,
  259. [
  260. [
  261. 'out_detail_no' => $orderId,
  262. 'transfer_amount' => $amount,
  263. 'transfer_remark' => $desc,
  264. 'openid' => $openid
  265. ]
  266. ]
  267. );
  268. return $res;
  269. } else {
  270. $merchantPayData = [
  271. 'partner_trade_no' => $orderId, //随机字符串作为订单号,跟红包和支付一个概念。
  272. 'openid' => $openid, //收款人的openid
  273. 'check_name' => 'NO_CHECK', //文档中有三种校验实名的方法 NO_CHECK OPTION_CHECK FORCE_CHECK
  274. 'amount' => (int)bcmul($amount, '100', 0), //单位为分
  275. 'desc' => $desc,
  276. 'spbill_create_ip' => request()->ip(), //发起交易的IP地址
  277. ];
  278. $result = $application->transfer->toBalance($merchantPayData);
  279. self::logger('企业付款到零钱', compact('merchantPayData'), $result);
  280. if ($result['return_code'] == 'SUCCESS' && $result['result_code'] != 'FAIL') {
  281. return true;
  282. } else {
  283. throw new PayException(($result['return_msg'] ?? '支付失败') . ':' . ($result['err_code_des'] ?? '发起企业支付到零钱失败'));
  284. }
  285. }
  286. }
  287. /**
  288. * 生成支付订单对象
  289. * @param $openid
  290. * @param $out_trade_no
  291. * @param $total_fee
  292. * @param $attach
  293. * @param $body
  294. * @param string $detail
  295. * @param $trade_type
  296. * @param array $options
  297. * @return array|Collection|object|ResponseInterface|string
  298. * @throws InvalidConfigException
  299. * @throws InvalidArgumentException
  300. * @throws GuzzleException
  301. */
  302. public static function paymentOrder($openid, $out_trade_no, $total_fee, $attach, $body, $detail = '', $trade_type = 'JSAPI', array $options = [])
  303. {
  304. $total_fee = bcmul($total_fee, 100, 0);
  305. $order = array_merge(compact('out_trade_no', 'total_fee', 'attach', 'body', 'detail', 'trade_type'), $options);
  306. if (!is_null($openid)) $order['openid'] = $openid;
  307. if ($order['detail'] == '') unset($order['detail']);
  308. $order['spbill_create_ip'] = request()->ip();
  309. $result = self::instance()->application()->order->unify($order);
  310. self::logger('生成支付订单对象', compact('order'), $result);
  311. if ($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS') {
  312. return $result;
  313. } else {
  314. if ($result['return_code'] == 'FAIL') {
  315. throw new PayException('微信支付错误返回:' . $result['return_msg']);
  316. } else if (isset($result['err_code'])) {
  317. throw new PayException('微信支付错误返回:' . $result['err_code_des']);
  318. } else {
  319. throw new PayException('没有获取微信支付的预支付ID,请重新发起支付!');
  320. }
  321. }
  322. }
  323. /**
  324. * 生成支付订单对象(小程序商户号支付时)
  325. * @param $openid
  326. * @param $out_trade_no
  327. * @param $total_fee
  328. * @param $attach
  329. * @param $body
  330. * @param string $detail
  331. * @param $trade_type
  332. * @param array $options
  333. * @return array|Collection|object|ResponseInterface|string
  334. * @throws InvalidConfigException
  335. * @throws InvalidArgumentException
  336. * @throws GuzzleException
  337. */
  338. public static function paymentMiniOrder($openid, $out_trade_no, $total_fee, $attach, $body, $detail = '', $trade_type = 'JSAPI', array $options = [])
  339. {
  340. $total_fee = bcmul($total_fee, 100, 0);
  341. $order = array_merge(compact('out_trade_no', 'total_fee', 'attach', 'body', 'detail', 'trade_type'), $options);
  342. if (!is_null($openid)) $order['openid'] = $openid;
  343. if ($order['detail'] == '') unset($order['detail']);
  344. $order['spbill_create_ip'] = request()->ip();
  345. $result = self::instance()->miniApplication()->orders->createorder($order);
  346. self::logger('生成支付订单对象', compact('order'), $result);
  347. if ($result['errcode'] == '0') {
  348. return $result;
  349. } else {
  350. throw new PayException('微信支付错误返回:' . $result['errmsg']);
  351. }
  352. }
  353. /**
  354. * 获得jsSdk支付参数
  355. * @param $openid
  356. * @param $out_trade_no
  357. * @param $total_fee
  358. * @param $attach
  359. * @param $body
  360. * @param string $detail
  361. * @param string $trade_type
  362. * @param array $options
  363. * @return array
  364. */
  365. public static function jsPay($openid, $out_trade_no, $total_fee, $attach, $body, $detail = '', $trade_type = 'JSAPI', $options = [])
  366. {
  367. $paymentPrepare = self::paymentOrder($openid, $out_trade_no, $total_fee, $attach, $body, $detail, $trade_type, $options);
  368. $config = self::instance()->application()->jssdk->bridgeConfig($paymentPrepare['prepay_id'], false);
  369. $config['timestamp'] = $config['timeStamp'];
  370. unset($config['timeStamp']);
  371. return $config;
  372. }
  373. /**
  374. * 获得jsSdk支付参数(小程序商户号支付时)
  375. * @param $openid
  376. * @param $out_trade_no
  377. * @param $total_fee
  378. * @param $attach
  379. * @param $body
  380. * @param string $detail
  381. * @param string $trade_type
  382. * @param array $options
  383. * @return array
  384. */
  385. public static function miniPay($openid, $out_trade_no, $total_fee, $attach, $body, $detail = '', $trade_type = 'JSAPI', $options = [])
  386. {
  387. $paymentPrepare = self::paymentMiniOrder($openid, $out_trade_no, $total_fee, $attach, $body, $detail, $trade_type, $options);
  388. $paymentPrepare['payment_params']['timestamp'] = $paymentPrepare['payment_params']['timeStamp'];
  389. return $paymentPrepare['payment_params'] ?? [];
  390. }
  391. /**
  392. * 获得APP付参数
  393. * @param $openid
  394. * @param $out_trade_no
  395. * @param $total_fee
  396. * @param $attach
  397. * @param $body
  398. * @param string $detail
  399. * @param string $trade_type
  400. * @param array $options
  401. * @return array|string
  402. */
  403. public static function appPay($openid, $out_trade_no, $total_fee, $attach, $body, $detail = '', $trade_type = 'APP', $options = [])
  404. {
  405. if (self::instance()->isV3PAy) {
  406. return self::instance()->application()->v3pay->appPay($out_trade_no, $total_fee, $body, $attach);
  407. } else {
  408. $paymentPrepare = self::paymentOrder($openid, $out_trade_no, $total_fee, $attach, $body, $detail, $trade_type, $options);
  409. return self::instance()->application()->jssdk->appConfig($paymentPrepare['prepay_id']);
  410. }
  411. }
  412. /**
  413. * 获得native支付参数
  414. * @param $openid
  415. * @param $out_trade_no
  416. * @param $total_fee
  417. * @param $attach
  418. * @param $body
  419. * @param string $detail
  420. * @param string $trade_type
  421. * @param array $options
  422. * @return array|string
  423. */
  424. public static function nativePay($openid, $out_trade_no, $total_fee, $attach, $body, $detail = '', $trade_type = 'NATIVE', $options = [])
  425. {
  426. $instance = self::instance();
  427. if ($instance->isV3PAy) {
  428. $data = $instance->application()->v3pay->nativePay($out_trade_no, $total_fee, $body, $attach);
  429. $res['code_url'] = $data['code_url'];
  430. $res['invalid'] = time() + 60;
  431. $res['logo'] = [];
  432. return $res;
  433. }
  434. $data = $instance->setAccessEnd(self::WEB)->paymentOrder($openid, $out_trade_no, $total_fee, $attach, $body, $detail, $trade_type, $options);
  435. if ($data) {
  436. $res['code_url'] = $data['code_url'];
  437. $res['invalid'] = time() + 60;
  438. $res['logo'] = [];
  439. } else $res = [];
  440. return $res;
  441. }
  442. /**
  443. * 使用商户订单号退款
  444. * @param $orderNo
  445. * @param $refundNo
  446. * @param $totalFee
  447. * @param null $refundFee
  448. * @param null $opUserId
  449. * @param string $refundReason
  450. * @param string $type
  451. * @param string $refundAccount
  452. * @return array|Collection|object|ResponseInterface|string
  453. * @throws InvalidConfigException
  454. */
  455. public function refund($orderNo, $refundNo, $totalFee, $refundFee = null, $opUserId = null, string $refundReason = '', string $type = 'out_trade_no', string $refundAccount = 'REFUND_SOURCE_UNSETTLED_FUNDS')
  456. {
  457. $totalFee = floatval($totalFee);
  458. $refundFee = floatval($refundFee);
  459. if ($type == 'out_trade_no') {
  460. $result = $this->application()->refund->byOutTradeNumber($orderNo, $refundNo, $totalFee, $refundFee, [
  461. 'refund_account' => $refundAccount,
  462. 'notify_url' => self::instance()->config->get('refundUrl'),
  463. 'refund_desc' => $refundReason
  464. ]);
  465. } else {
  466. $result = $this->application()->refund->byTransactionId($orderNo, $refundNo, $totalFee, $refundFee, [
  467. 'refund_account' => $refundAccount,
  468. 'notify_url' => self::instance()->config->get('refundUrl'),
  469. 'refund_desc' => $refundReason
  470. ]);
  471. }
  472. self::logger('使用商户订单号退款', compact('orderNo', 'refundNo', 'totalFee', 'refundFee', 'opUserId', 'refundReason', 'type', 'refundAccount'), $result);
  473. return $result;
  474. }
  475. /**
  476. * 小程序商户退款
  477. * @param $orderNo //微信支付单号
  478. * @param $refundNo //微信退款单号
  479. * @param $totalFee
  480. * @param null $refundFee
  481. * @param null $opUserId
  482. * @param string $refundReason
  483. * @param string $type
  484. * @param string $refundAccount
  485. * @return array|Collection|object|ResponseInterface|string
  486. * @throws InvalidConfigException
  487. */
  488. public function miniRefund($orderNo, $refundNo, $totalFee, $refundFee = null, array $opt = [])
  489. {
  490. $totalFee = floatval($totalFee);
  491. $refundFee = floatval($refundFee);
  492. $order = [
  493. 'openid' => $opt['open_id'],
  494. 'trade_no' => $opt['routine_order_id'],
  495. 'transaction_id' => $orderNo,
  496. 'refund_no' => $refundNo,
  497. 'total_amount' => $totalFee,
  498. 'refund_amount' => $refundFee,
  499. ];
  500. $result = $this->miniApplication()->orders->refundorder($order);
  501. self::logger('使用商户订单号退款', compact('orderNo', 'refundNo', 'totalFee', 'refundFee', 'opt'), $result);
  502. return $result;
  503. }
  504. /**
  505. * 退款
  506. * @param $orderNo
  507. * @param array $opt
  508. * @return bool
  509. */
  510. public function payOrderRefund($orderNo, array $opt)
  511. {
  512. if (isset($opt['pay_routine_open']) && $opt['pay_routine_open']) {
  513. return $this->payMiniOrderRefund($orderNo, $opt);
  514. }
  515. if (!isset($opt['pay_price'])) {
  516. throw new PayException('缺少pay_price');
  517. }
  518. $certPath = $this->config->get('certPath');
  519. if (!$certPath) {
  520. throw new PayException('请上传支付证书cert');
  521. }
  522. $keyPath = $this->config->get('keyPath');
  523. if (!$keyPath) {
  524. throw new PayException('请上传支付证书key');
  525. }
  526. if (!is_file($certPath)) {
  527. throw new PayException('支付证书cert不存在');
  528. }
  529. if (!is_file($keyPath)) {
  530. throw new PayException('支付证书key不存在');
  531. }
  532. if ($this->isV3PAy) {
  533. return $this->application()->v3pay->refund($orderNo, $opt);
  534. }
  535. $totalFee = floatval(bcmul($opt['pay_price'], 100, 0));
  536. $refundFee = isset($opt['refund_price']) ? floatval(bcmul($opt['refund_price'], 100, 0)) : null;
  537. $refundReason = $opt['desc'] ?? '';
  538. $refundNo = $opt['refund_id'] ?? $orderNo;
  539. $opUserId = $opt['op_user_id'] ?? null;
  540. $type = $opt['type'] ?? 'out_trade_no';
  541. /*仅针对老资金流商户使用
  542. REFUND_SOURCE_UNSETTLED_FUNDS---未结算资金退款(默认使用未结算资金退款)
  543. REFUND_SOURCE_RECHARGE_FUNDS---可用余额退款*/
  544. $refundAccount = $opt['refund_account'] ?? 'REFUND_SOURCE_UNSETTLED_FUNDS';
  545. try {
  546. $res = $this->refund($orderNo, $refundNo, $totalFee, $refundFee, $opUserId, $refundReason, $type, $refundAccount);
  547. if ($res['return_code'] == 'FAIL') {
  548. throw new PayException('退款失败:' . $res['return_msg']);
  549. }
  550. if (isset($res['err_code'])) {
  551. throw new PayException('退款失败:' . $res['err_code_des']);
  552. }
  553. } catch (\Exception $e) {
  554. self::error($e);
  555. throw new PayException($e->getMessage());
  556. }
  557. return true;
  558. }
  559. /**
  560. * 小程序商户退款
  561. * @param $orderNo
  562. * @param array $opt
  563. * @return bool
  564. */
  565. public function payMiniOrderRefund($orderNo, array $opt)
  566. {
  567. if (!isset($opt['pay_price'])) {
  568. throw new PayException('缺少pay_price');
  569. }
  570. if (!isset($opt['routine_order_id'])) {
  571. throw new PayException('缺少订单单号');
  572. }
  573. $totalFee = floatval(bcmul($opt['pay_price'], 100, 0));
  574. $refundFee = isset($opt['refund_price']) ? floatval(bcmul($opt['refund_price'], 100, 0)) : null;
  575. $refundNo = $opt['refund_no'];
  576. try {
  577. $result = $this->miniRefund($orderNo, $refundNo, $totalFee, $refundFee, $opt);
  578. if ($result['errcode'] == '0') {
  579. return true;
  580. } else {
  581. throw new PayException('退款失败:' . $result['errmsg']);
  582. }
  583. } catch (\Exception $e) {
  584. self::error($e);
  585. throw new PayException($e->getMessage());
  586. }
  587. }
  588. /**
  589. * 微信支付成功回调接口
  590. * @return Response
  591. * @throws Exception
  592. */
  593. public function handleNotify()
  594. {
  595. if ($this->isV3PAy) {
  596. $response = $this->application()->v3pay->handleNotify(function ($notify, $success) {
  597. self::logger('微信支付成功回调接口', [], $notify);
  598. if (isset($notify['out_trade_no']) && $success) {
  599. $res = Event::until('pay.notify', [$notify]);
  600. if ($res) {
  601. return $res;
  602. } else {
  603. return false;
  604. }
  605. }
  606. });
  607. } else {
  608. $response = $this->application()->handlePaidNotify(function ($notify, $fail) {
  609. self::logger('微信支付成功回调接口', [], $notify);
  610. if (isset($notify['out_trade_no'])) {
  611. $res = Event::until('pay.notify', [$notify]);
  612. if ($res) {
  613. return $res;
  614. } else {
  615. return $fail('支付通知失败');
  616. }
  617. }
  618. });
  619. }
  620. return response($response->getContent());
  621. }
  622. /**
  623. * 扫码支付通知
  624. * @return Response
  625. * @throws Exception
  626. */
  627. public static function handleScannedNotify()
  628. {
  629. $make = self::instance();
  630. $response = $make->application()->handleScannedNotify(function ($message, $fail, $alert) use ($make) {
  631. self::logger('扫码支付通知', [], $message);
  632. $res = Event::until('pay.scan.notify', [$message]);
  633. if ($res) {
  634. return $res;
  635. } else {
  636. return $fail('扫码通知支付失败');
  637. }
  638. });
  639. return response($response->getContent());
  640. }
  641. /**
  642. * 退款结果通知
  643. * @return Response
  644. * @throws Exception
  645. */
  646. public function handleRefundedNotify()
  647. {
  648. $response = $this->application()->handleRefundedNotify(function ($message, $reqInfo, $fail) {
  649. self::logger('退款结果通知', [], compact('message', 'reqInfo'));
  650. $res = Event::until('pay.refunded.notify', [$message, $reqInfo]);
  651. if ($res) {
  652. return $res;
  653. } else {
  654. return $fail('扫码通知支付失败');
  655. }
  656. });
  657. return response($response->getContent());
  658. }
  659. /**
  660. * 是否时微信付款二维码值
  661. * @param string $authCode
  662. * @return bool
  663. */
  664. public static function isWechatAuthCode(string $authCode)
  665. {
  666. return preg_match('/^[0-9]{18}$/', $authCode) && in_array(substr($authCode, 0, 2), ['10', '11', '12', '13', '14', '15']);
  667. }
  668. }