V3.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | 萤火商城系统 [ 致力于通过产品和服务,帮助商家高效化开拓市场 ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2017~2025 https://www.yiovo.com All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed 这不是一个自由软件,不允许对程序代码以任何形式任何目的的再发行
  8. // +----------------------------------------------------------------------
  9. // | Author: 萤火科技 <admin@yiovo.com>
  10. // +----------------------------------------------------------------------
  11. declare (strict_types=1);
  12. namespace app\common\library\payment\gateway\driver\wechat;
  13. use WeChatPay\Builder;
  14. use WeChatPay\Formatter;
  15. use WeChatPay\Crypto\Rsa;
  16. use WeChatPay\Crypto\AesGcm;
  17. use WeChatPay\Util\PemUtil;
  18. use app\common\library\Log;
  19. use app\common\library\helper;
  20. use app\common\enum\Client as ClientEnum;
  21. use cores\traits\ErrorTrait;
  22. use cores\exception\BaseException;
  23. use Psr\Http\Message\ResponseInterface;
  24. /**
  25. * 微信支付驱动 [V3]
  26. * Class Wechat
  27. * @package app\common\library\payment\gateway\driver
  28. */
  29. class V3
  30. {
  31. use ErrorTrait;
  32. /**
  33. * 支付的客户端
  34. * @var string|null
  35. */
  36. protected ?string $client = null;
  37. /**
  38. * 支付配置参数
  39. * @var array
  40. */
  41. protected array $config = [];
  42. // 统一下单API的返回结果
  43. private array $result;
  44. // 异步通知的请求参数 (由第三方支付发送)
  45. private array $notifyParams;
  46. /**
  47. * 设置支付配置参数
  48. * @param array $options 配置信息
  49. * @param string $client 下单客户端
  50. * @return static|null
  51. */
  52. public function setOptions(array $options, string $client): ?V3
  53. {
  54. $this->client = $client ?: null;
  55. $this->config = $this->getConfig($options);
  56. return $this;
  57. }
  58. /**
  59. * 统一下单API
  60. * @param string $outTradeNo 交易订单号
  61. * @param string $totalFee 实际付款金额
  62. * @param array $extra 附加的数据 (需要携带openid)
  63. * @return bool
  64. * @throws BaseException
  65. */
  66. public function unify(string $outTradeNo, string $totalFee, array $extra = []): bool
  67. {
  68. // 下单的参数
  69. $params = [
  70. 'out_trade_no' => $outTradeNo,
  71. 'description' => '线上商城商品',
  72. 'notify_url' => $this->notifyUrl(), // 支付结果异步通知地址
  73. 'amount' => ['total' => (int)helper::bcmul($totalFee, 100), 'currency' => 'CNY'],
  74. 'scene_info' => ['payer_client_ip' => \request()->ip()]
  75. ];
  76. // 普通商户参数和服务商支付参数
  77. if ($this->isProvider()) {
  78. $params['sp_appid'] = $this->config['app_id'];
  79. $params['sp_mchid'] = $this->config['mch_id'];
  80. $params['sub_appid'] = $this->config['sub_appid'];
  81. $params['sub_mchid'] = $this->config['sub_mchid'];
  82. } else {
  83. $params['appid'] = $this->config['app_id'];
  84. $params['mchid'] = $this->config['mch_id'];
  85. }
  86. // 用户的openid (只有JSAPI支付时需要)
  87. if ($this->tradeType() === 'jsapi') {
  88. $params['payer'][$this->isProvider() ? 'sub_openid' : 'openid'] = $extra['openid'];
  89. }
  90. // H5info
  91. if ($this->tradeType() === 'h5') {
  92. $params['scene_info']['h5_info'] = ['type' => 'Wap'];
  93. }
  94. try {
  95. // 统一下单API
  96. // Doc: https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_1.shtml
  97. $resp = $this->getApp()
  98. ->chain($this->getUnifyApiUrl())
  99. ->post(['json' => $params]);
  100. // 记录api返回的数据
  101. $unifyResult = helper::jsonDecode((string)$resp->getBody());
  102. $this->result = $unifyResult;
  103. // 生成app支付的配置
  104. if ($this->client === ClientEnum::APP) {
  105. $this->result = $this->appConfig($unifyResult['prepay_id']);
  106. }
  107. // 生成jssdk支付的配置
  108. if (in_array($this->client, [ClientEnum::MP_WEIXIN])) {
  109. $this->result = $this->bridgeConfig($unifyResult['prepay_id']);
  110. }
  111. // 记录商户订单号
  112. $this->result['out_trade_no'] = $outTradeNo;
  113. // 记录日志
  114. Log::append('Wechat-unify', [
  115. 'client' => $this->client,
  116. 'params' => $params,
  117. 'extra' => $extra,
  118. 'result' => $this->result
  119. ]);
  120. return true;
  121. } catch (\Throwable $e) {
  122. // 异常处理
  123. $message = $this->getThrowMessage($e);
  124. $this->throwError('unify', "微信支付API下单失败:{$message}");
  125. }
  126. return false;
  127. }
  128. /**
  129. * 交易查询 (主动查询订单支付状态)
  130. * @param string $outTradeNo 交易订单号
  131. * @return array|null
  132. * @throws BaseException
  133. */
  134. public function tradeQuery(string $outTradeNo): ?array
  135. {
  136. // 下单的参数
  137. $params = [];
  138. // 普通商户参数和服务商支付参数
  139. if ($this->isProvider()) {
  140. $params['sp_mchid'] = $this->config['mch_id'];
  141. $params['sub_mchid'] = $this->config['sub_mchid'];
  142. } else {
  143. $params['mchid'] = $this->config['mch_id'];
  144. }
  145. try {
  146. // 订单查询API
  147. // Doc: https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_2.shtml
  148. $resp = $this->getApp()
  149. ->chain($this->getTradeApiUrl($outTradeNo))
  150. ->get(['query' => $params]);
  151. // 记录api返回的数据
  152. $result = helper::jsonDecode((string)$resp->getBody());
  153. // 记录日志
  154. Log::append('Wechat-tradeQuery', ['outTradeNo' => $outTradeNo, 'result' => $result]);
  155. // 判断订单支付成功
  156. return [
  157. // 支付状态: true成功 false失败
  158. 'paySuccess' => $result['trade_state'] === 'SUCCESS',
  159. // 第三方交易流水号
  160. 'tradeNo' => $result['transaction_id'] ?? ''
  161. ];
  162. } catch (\Throwable $e) {
  163. // 异常处理
  164. $message = $this->getThrowMessage($e);
  165. $this->throwError('tradeQuery', "微信支付交易查询失败:{$message}");
  166. }
  167. return null;
  168. }
  169. /**
  170. * 支付成功后的异步通知
  171. * @param string $apiV3Key 微信支付v3秘钥
  172. * @param string $platformCertificateOrPublicKeyFilePath 微信支付公钥或平台证书路径
  173. * @return bool|string
  174. */
  175. public function notify(string $apiV3Key, string $platformCertificateOrPublicKeyFilePath)
  176. {
  177. // 微信异步通知参数
  178. $header = \request()->header();
  179. $inBody = file_get_contents('php://input');
  180. // 微信支付平台证书
  181. $platformPublicKeyInstance = Rsa::from("file://{$platformCertificateOrPublicKeyFilePath}", Rsa::KEY_TYPE_PUBLIC);
  182. // 检查通知时间偏移量,允许5分钟之内的偏移
  183. // $timeOffsetStatus = 300 >= abs(Formatter::timestamp() - (int)$inWechatpayTimestamp);
  184. $timeOffsetStatus = true;
  185. $verifiedStatus = Rsa::verify(
  186. // 构造验签名串
  187. Formatter::joinedByLineFeed($header['wechatpay-timestamp'], $header['wechatpay-nonce'], $inBody),
  188. $header['wechatpay-signature'],
  189. $platformPublicKeyInstance
  190. );
  191. if ($timeOffsetStatus && $verifiedStatus) {
  192. // 转换通知的JSON文本消息为PHP Array数组
  193. $inBodyArray = (array)json_decode($inBody, true);
  194. // 使用PHP7的数据解构语法,从Array中解构并赋值变量
  195. ['resource' => [
  196. 'ciphertext' => $ciphertext,
  197. 'nonce' => $nonce,
  198. 'associated_data' => $aad
  199. ]] = $inBodyArray;
  200. // 加密文本消息解密
  201. $inBodyResource = AesGcm::decrypt($ciphertext, $apiV3Key, $nonce, $aad);
  202. // 把解密后的文本转换为PHP Array数组
  203. $this->notifyParams = helper::jsonDecode($inBodyResource);
  204. // 记录日志
  205. Log::append('Wechat-notify', ['message' => '微信异步回调验证成功', 'notifyParams' => $this->notifyParams]);
  206. return true;
  207. }
  208. return false;
  209. }
  210. /**
  211. * 微信支付退款API
  212. * @param string $outTradeNo 第三方交易单号
  213. * @param string $refundAmount 退款金额
  214. * @param array $extra 附加数据 (需要携带订单付款总金额)
  215. * @return bool
  216. * @throws BaseException
  217. */
  218. public function refund(string $outTradeNo, string $refundAmount, array $extra = []): bool
  219. {
  220. // 下单的参数
  221. $params = [
  222. 'out_trade_no' => $outTradeNo,
  223. 'out_refund_no' => time() . '-' . uniqid(),
  224. 'amount' => [
  225. 'refund' => (int)helper::bcmul($refundAmount, 100),
  226. 'total' => (int)helper::bcmul($extra['totalFee'], 100),
  227. 'currency' => 'CNY',
  228. ],
  229. ];
  230. // 普通商户参数和服务商支付参数
  231. if ($this->isProvider()) {
  232. $params['sub_mchid'] = $this->config['sub_mchid'];
  233. }
  234. try {
  235. // 申请退款API
  236. // Doc: https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_3_9.shtml
  237. $resp = $this->getApp()
  238. ->chain($this->getRefundApiUrl())
  239. ->post(['json' => $params]);
  240. // 记录api返回的数据
  241. $result = helper::jsonDecode((string)$resp->getBody());
  242. // 记录日志
  243. Log::append('Wechat-refund', [
  244. 'outTradeNo' => $outTradeNo,
  245. 'refundAmount' => $refundAmount,
  246. 'result' => $result
  247. ]);
  248. // 请求成功
  249. return true;
  250. } catch (\Throwable $e) {
  251. // 异常处理
  252. $message = $this->getThrowMessage($e);
  253. $this->throwError('tradeQuery', "微信退款api请求失败:{$message}");
  254. }
  255. return false;
  256. }
  257. /**
  258. * 商家转账到零钱API
  259. * @param string $outTradeNo 交易订单号
  260. * @param string $totalFee 实际付款金额
  261. * @param array $extra 附加的数据 (需要携带openid、desc)
  262. * @return bool
  263. * @throws BaseException
  264. */
  265. public function transfers(string $outTradeNo, string $totalFee, array $extra = []): bool
  266. {
  267. // 下单的参数
  268. $params = [
  269. 'appid' => $this->config['app_id'],
  270. 'out_batch_no' => $outTradeNo,
  271. 'batch_name' => $extra['desc'],
  272. 'batch_remark' => $extra['desc'],
  273. 'total_amount' => (int)helper::bcmul($totalFee, 100), // 转账金额,单位:分
  274. 'total_num' => 1, // 转账总笔数
  275. 'transfer_detail_list' => [
  276. [
  277. 'out_detail_no' => time() . uniqid(),
  278. 'transfer_amount' => (int)helper::bcmul($totalFee, 100),
  279. 'transfer_remark' => $extra['desc'],
  280. 'openid' => $extra['openid'],
  281. ]
  282. ]
  283. ];
  284. try {
  285. // 商家转账到零钱API
  286. // Doc: https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter4_3_1.shtml
  287. $resp = $this->getApp()
  288. ->chain($this->getTransfersUrl())
  289. ->post(['json' => $params]);
  290. // 记录api返回的数据
  291. $result = helper::jsonDecode((string)$resp->getBody());
  292. // 记录日志
  293. Log::append('Wechat-transfers', ['outTradeNo' => $outTradeNo, 'result' => $result]);
  294. // 请求成功
  295. return true;
  296. } catch (\Throwable $e) {
  297. // 异常处理
  298. $message = $this->getThrowMessage($e);
  299. $this->throwError('transfers', "商家转账到零钱api请求失败:{$message}");
  300. }
  301. return false;
  302. }
  303. /**
  304. * 获取异步回调的请求参数
  305. * @return array
  306. */
  307. public function getNotifyParams(): array
  308. {
  309. return [
  310. // 第三方交易流水号
  311. 'tradeNo' => $this->notifyParams['transaction_id']
  312. ];
  313. }
  314. /**
  315. * 返回异步通知结果的输出内容
  316. * @return string
  317. */
  318. public function getNotifyResponse(): string
  319. {
  320. return 'SUCCESS';
  321. }
  322. /**
  323. * 返回统一下单API的结果 (用于前端)
  324. * @return array
  325. * @throws BaseException
  326. */
  327. public function getUnifyResult(): array
  328. {
  329. if (empty($this->result)) {
  330. $this->throwError('getUnifyResult', '当前没有unify结果');
  331. }
  332. // 允许输出的字段 (防止泄露敏感信息)
  333. $result = helper::pick($this->result, [
  334. 'out_trade_no',
  335. 'nonce_str', 'prepay_id', 'sign', 'trade_type', 'mweb_url', 'h5_url',
  336. 'appid', 'partnerid', 'noncestr', 'prepayid', 'timestamp', 'package', 'sign',
  337. 'appId', 'timeStamp', 'nonceStr', 'package', 'signType', 'paySign',
  338. ]);
  339. // 当前的时间戳
  340. $result['time_stamp'] = (string)time();
  341. return $result;
  342. }
  343. /**
  344. * 设置异步通知的错误信息
  345. * @param string $error 错误信息
  346. * @param bool $outputFail 是否输出fail信息 (会使微信服务器重复发起通知)
  347. */
  348. private function notifyPaidError(string $error, bool $outputFail = true)
  349. {
  350. }
  351. /**
  352. * 输出错误信息
  353. * @param string $action 当前的操作
  354. * @param string $errMessage 错误信息
  355. * @throws BaseException
  356. */
  357. private function throwError(string $action, string $errMessage)
  358. {
  359. $this->error = $errMessage;
  360. Log::append("Wechat-{$action}", ['errMessage' => $errMessage]);
  361. throwError($errMessage);
  362. }
  363. /**
  364. * 根据客户端选择对应的微信支付方式
  365. * @return string
  366. * @throws BaseException
  367. */
  368. private function tradeType(): string
  369. {
  370. $tradeTypes = [
  371. ClientEnum::H5 => 'h5',
  372. ClientEnum::MP_WEIXIN => 'jsapi',
  373. ClientEnum::APP => 'app'
  374. ];
  375. if (!isset($tradeTypes[$this->client])) {
  376. $this->throwError('tradeType', '未找到当前客户端适配的微信支付方式');
  377. }
  378. return $tradeTypes[$this->client];
  379. }
  380. /**
  381. * 请求错误时错误信息
  382. */
  383. private function resultError(ResponseInterface $resp)
  384. {
  385. }
  386. /**
  387. * 获取微信支付应用类
  388. * @return \WeChatPay\BuilderChainable
  389. * @throws BaseException
  390. */
  391. private function getApp(): \WeChatPay\BuilderChainable
  392. {
  393. // 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名
  394. $merchantPrivateKeyInstance = $this->getMerchantPrivateKeyInstance();
  395. // 从本地文件中加载「微信支付平台证书」或者「微信支付平台公钥」,用来验证微信支付应答的签名
  396. $platformCertificateOrPublicKeyFilePath = $this->getPlatformCertificateOrPublicKeyFilePath();
  397. try {
  398. $platformPublicKeyInstance = Rsa::from("file://{$platformCertificateOrPublicKeyFilePath}", Rsa::KEY_TYPE_PUBLIC);
  399. } catch (\UnexpectedValueException $e) {
  400. $platformPublicKeyInstance = null;
  401. throwError('平台证书文件或微信支付平台公钥不正确');
  402. }
  403. // 「平台证书序列号」及/或「平台公钥ID」
  404. $platformCertificateSerialOrPublicKeyId = $this->platformCertificateSerialOrPublicKeyId($platformCertificateOrPublicKeyFilePath);
  405. // 构造一个 APIv3 客户端实例
  406. return Builder::factory([
  407. // 微信支付商户号
  408. 'mchid' => $this->config['mch_id'],
  409. // 「商户API证书」的「证书序列号」
  410. 'serial' => $this->serialno($this->config['cert_path']),
  411. 'privateKey' => $merchantPrivateKeyInstance,
  412. 'certs' => [
  413. $platformCertificateSerialOrPublicKeyId => $platformPublicKeyInstance,
  414. ],
  415. ]);
  416. }
  417. /**
  418. * 获取「微信支付平台证书」或者「微信支付平台公钥」的路径
  419. * @return string
  420. */
  421. private function getPlatformCertificateOrPublicKeyFilePath(): string
  422. {
  423. return $this->config['signature_method'] == 'publicKey' ? $this->config['public_key_path']
  424. : $this->config['platform_cert_path'];
  425. }
  426. /**
  427. * 获取「平台证书序列号」及/或「平台公钥ID」
  428. * @param string $platformCertificateOrPublicKeyFilePath 「微信支付平台证书」或者「微信支付平台公钥」的路径
  429. * @return mixed|string
  430. */
  431. private function platformCertificateSerialOrPublicKeyId(string $platformCertificateOrPublicKeyFilePath)
  432. {
  433. return $this->config['signature_method'] == 'publicKey' ? $this->config['public_key_id']
  434. : PemUtil::parseCertificateSerialNo("file://{$platformCertificateOrPublicKeyFilePath}");
  435. }
  436. /**
  437. * 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名
  438. * @return mixed|\OpenSSLAsymmetricKey|resource
  439. * @throws BaseException
  440. */
  441. private function getMerchantPrivateKeyInstance()
  442. {
  443. try {
  444. return Rsa::from("file://{$this->config['key_path']}", Rsa::KEY_TYPE_PRIVATE);
  445. } catch (\UnexpectedValueException $e) {
  446. throwError('证书文件(KEY)不正确');
  447. }
  448. }
  449. /**
  450. * 读取公钥中的序列号
  451. * @param string $publicKey
  452. * @return mixed
  453. * @throws BaseException
  454. */
  455. private function serialno(string $publicKey)
  456. {
  457. $content = file_get_contents($publicKey);
  458. $plaintext = !empty($content) ? openssl_x509_parse($content) : false;
  459. empty($plaintext) && throwError('证书文件(CERT)不正确');
  460. return $plaintext['serialNumberHex'];
  461. }
  462. /**
  463. * 构建微信支付配置
  464. * @return string[]
  465. */
  466. private function getConfig($options): array
  467. {
  468. if ($options['mchType'] === 'provider') {
  469. return [
  470. 'mch_type' => 'provider',
  471. 'app_id' => $options['provider']['spAppId'],
  472. 'mch_id' => $options['provider']['spMchId'],
  473. 'key' => $options['provider']['spApiKey'],
  474. 'cert_path' => $options['provider']['spApiclientCertPath'],
  475. 'key_path' => $options['provider']['spApiclientKeyPath'],
  476. 'sub_mchid' => $options['provider']['subMchId'],
  477. 'sub_appid' => $options['provider']['subAppId'],
  478. 'signature_method' => $options['provider']['spSignatureMethod'],
  479. 'public_key_id' => $options['provider']['spPublicKeyId'] ?? '',
  480. 'public_key_path' => $options['provider']['spPublicKeyPath'] ?? '',
  481. 'platform_cert_path' => $options['provider']['platformCertPath'] ?? ''
  482. ];
  483. } else {
  484. return [
  485. 'mch_type' => 'normal',
  486. 'app_id' => $options['normal']['appId'],
  487. 'mch_id' => $options['normal']['mchId'],
  488. 'key' => $options['normal']['apiKey'],
  489. 'cert_path' => $options['normal']['apiclientCertPath'],
  490. 'key_path' => $options['normal']['apiclientKeyPath'],
  491. 'signature_method' => $options['normal']['signatureMethod'],
  492. 'public_key_id' => $options['normal']['publicKeyId'] ?? '',
  493. 'public_key_path' => $options['normal']['publicKeyPath'] ?? '',
  494. 'platform_cert_path' => $options['normal']['platformCertPath'] ?? ''
  495. ];
  496. }
  497. }
  498. /**
  499. * 异步回调地址
  500. * @return string
  501. */
  502. private function notifyUrl(): string
  503. {
  504. // 例如:https://www.xxxx.com/wxpayV3.php
  505. return base_url() . 'notice/wxpayV3.php';
  506. }
  507. /**
  508. * 当前是否为服务商模式
  509. * @return bool
  510. */
  511. private function isProvider(): bool
  512. {
  513. return $this->config['mch_type'] === 'provider';
  514. }
  515. /**
  516. * Generate app payment parameters.
  517. * @param string $prepayId
  518. * @return array
  519. * @throws BaseException
  520. */
  521. private function appConfig(string $prepayId): array
  522. {
  523. $params = [
  524. 'appid' => $this->config['app_id'],
  525. 'timestamp' => (string)Formatter::timestamp(),
  526. 'noncestr' => Formatter::nonce(),
  527. 'prepayid' => $prepayId,
  528. ];
  529. return \array_merge($params, [
  530. 'sign' => Rsa::sign(
  531. Formatter::joinedByLineFeed(...array_values($params)),
  532. $this->getMerchantPrivateKeyInstance()
  533. ),
  534. 'partnerid' => $this->config['mch_id'],
  535. 'package' => 'Sign=WXPay',
  536. ]);
  537. }
  538. /**
  539. * [WeixinJSBridge] Generate js config for payment.
  540. *
  541. * <pre>
  542. * WeixinJSBridge.invoke(
  543. * 'getBrandWCPayRequest',
  544. * ...
  545. * );
  546. * </pre>
  547. *
  548. * @param string $prepayId
  549. * @return string|array
  550. * @throws BaseException
  551. */
  552. private function bridgeConfig(string $prepayId)
  553. {
  554. $params = [
  555. 'appId' => $this->isProvider() ? $this->config['sub_appid'] : $this->config['app_id'],
  556. 'timeStamp' => (string)Formatter::timestamp(),
  557. 'nonceStr' => Formatter::nonce(),
  558. 'package' => "prepay_id=$prepayId",
  559. ];
  560. $params += ['paySign' => Rsa::sign(
  561. Formatter::joinedByLineFeed(...array_values($params)),
  562. $this->getMerchantPrivateKeyInstance()
  563. ), 'signType' => 'RSA'];
  564. return $params;
  565. }
  566. /**
  567. * 处理API的异常
  568. * @param \Throwable $e
  569. * @return mixed|string
  570. */
  571. private function getThrowMessage(\Throwable $e)
  572. {
  573. $message = $e->getMessage();
  574. if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
  575. $body = (string)$e->getResponse()->getBody();
  576. if (!empty($body)) {
  577. $result = helper::jsonDecode($body);
  578. isset($result['message']) && $message = $result['message'];
  579. }
  580. }
  581. return $message;
  582. }
  583. /**
  584. * 统一下单API的Url [需判断是否为服务商支付以及客户端]
  585. * @return string
  586. * @throws BaseException
  587. */
  588. private function getUnifyApiUrl(): string
  589. {
  590. $partnerNodo = $this->isProvider() ? 'partner/' : '';
  591. return "v3/pay/{$partnerNodo}transactions/" . $this->tradeType();
  592. }
  593. /**
  594. * 订单查询API的Url [需判断是否为服务商支付以及客户端]
  595. * @param string $outTradeNo
  596. * @return string
  597. */
  598. private function getTradeApiUrl(string $outTradeNo): string
  599. {
  600. $partnerNodo = $this->isProvider() ? 'partner/' : '';
  601. return "v3/pay/{$partnerNodo}transactions/out-trade-no/{$outTradeNo}";
  602. }
  603. /**
  604. * 申请退款API的Url
  605. * @return string
  606. */
  607. private function getRefundApiUrl(): string
  608. {
  609. return 'v3/refund/domestic/refunds';
  610. }
  611. /**
  612. * 商家转账到零钱API的Url
  613. * @return string
  614. */
  615. private function getTransfersUrl(): string
  616. {
  617. return 'v3/transfer/batches';
  618. }
  619. }