V3.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | 萤火商城系统 [ 致力于通过产品和服务,帮助商家高效化开拓市场 ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2017~2024 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' => '微信异步回调验证成功']);
  206. return $this->notifyParams['out_trade_no'];
  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. * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
  318. */
  319. public function getNotifyResponse(): string
  320. {
  321. return 'SUCCESS';
  322. }
  323. /**
  324. * 返回统一下单API的结果 (用于前端)
  325. * @return array
  326. * @throws BaseException
  327. */
  328. public function getUnifyResult(): array
  329. {
  330. if (empty($this->result)) {
  331. $this->throwError('getUnifyResult', '当前没有unify结果');
  332. }
  333. // 允许输出的字段 (防止泄露敏感信息)
  334. $result = helper::pick($this->result, [
  335. 'out_trade_no',
  336. 'nonce_str', 'prepay_id', 'sign', 'trade_type', 'mweb_url', 'h5_url',
  337. 'appid', 'partnerid', 'noncestr', 'prepayid', 'timestamp', 'package', 'sign',
  338. 'appId', 'timeStamp', 'nonceStr', 'package', 'signType', 'paySign',
  339. ]);
  340. // 当前的时间戳
  341. $result['time_stamp'] = (string)time();
  342. return $result;
  343. }
  344. /**
  345. * 设置异步通知的错误信息
  346. * @param string $error 错误信息
  347. * @param bool $outputFail 是否输出fail信息 (会使微信服务器重复发起通知)
  348. */
  349. private function notifyPaidError(string $error, bool $outputFail = true)
  350. {
  351. }
  352. /**
  353. * 输出错误信息
  354. * @param string $action 当前的操作
  355. * @param string $errMessage 错误信息
  356. * @throws BaseException
  357. */
  358. private function throwError(string $action, string $errMessage)
  359. {
  360. $this->error = $errMessage;
  361. Log::append("Wechat-{$action}", ['errMessage' => $errMessage]);
  362. throwError($errMessage);
  363. }
  364. /**
  365. * 根据客户端选择对应的微信支付方式
  366. * @return string
  367. * @throws BaseException
  368. */
  369. private function tradeType(): string
  370. {
  371. $tradeTypes = [
  372. ClientEnum::H5 => 'h5',
  373. ClientEnum::MP_WEIXIN => 'jsapi',
  374. ClientEnum::APP => 'app'
  375. ];
  376. if (!isset($tradeTypes[$this->client])) {
  377. $this->throwError('tradeType', '未找到当前客户端适配的微信支付方式');
  378. }
  379. return $tradeTypes[$this->client];
  380. }
  381. /**
  382. * 请求错误时错误信息
  383. */
  384. private function resultError(ResponseInterface $resp)
  385. {
  386. }
  387. /**
  388. * 获取微信支付应用类
  389. * @return \WeChatPay\BuilderChainable
  390. * @throws BaseException
  391. */
  392. private function getApp(): \WeChatPay\BuilderChainable
  393. {
  394. // 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名
  395. $merchantPrivateKeyInstance = $this->getMerchantPrivateKeyInstance();
  396. // 从本地文件中加载「微信支付平台证书」或者「微信支付平台公钥」,用来验证微信支付应答的签名
  397. $platformCertificateOrPublicKeyFilePath = $this->getPlatformCertificateOrPublicKeyFilePath();
  398. try {
  399. $platformPublicKeyInstance = Rsa::from("file://{$platformCertificateOrPublicKeyFilePath}", Rsa::KEY_TYPE_PUBLIC);
  400. } catch (\UnexpectedValueException $e) {
  401. $platformPublicKeyInstance = null;
  402. throwError('平台证书文件或微信支付平台公钥不正确');
  403. }
  404. // 「平台证书序列号」及/或「平台公钥ID」
  405. $platformCertificateSerialOrPublicKeyId = $this->platformCertificateSerialOrPublicKeyId($platformCertificateOrPublicKeyFilePath);
  406. // 构造一个 APIv3 客户端实例
  407. return Builder::factory([
  408. // 微信支付商户号
  409. 'mchid' => $this->config['mch_id'],
  410. // 「商户API证书」的「证书序列号」
  411. 'serial' => $this->serialno($this->config['cert_path']),
  412. 'privateKey' => $merchantPrivateKeyInstance,
  413. 'certs' => [
  414. $platformCertificateSerialOrPublicKeyId => $platformPublicKeyInstance,
  415. ],
  416. ]);
  417. }
  418. /**
  419. * 获取「微信支付平台证书」或者「微信支付平台公钥」的路径
  420. * @return string
  421. */
  422. private function getPlatformCertificateOrPublicKeyFilePath(): string
  423. {
  424. return $this->config['signature_method'] == 'publicKey' ? $this->config['public_key_path']
  425. : $this->config['platform_cert_path'];
  426. }
  427. /**
  428. * 获取「平台证书序列号」及/或「平台公钥ID」
  429. * @param string $platformCertificateOrPublicKeyFilePath 「微信支付平台证书」或者「微信支付平台公钥」的路径
  430. * @return mixed|string
  431. */
  432. private function platformCertificateSerialOrPublicKeyId(string $platformCertificateOrPublicKeyFilePath)
  433. {
  434. return $this->config['signature_method'] == 'publicKey' ? $this->config['public_key_id']
  435. : PemUtil::parseCertificateSerialNo("file://{$platformCertificateOrPublicKeyFilePath}");
  436. }
  437. /**
  438. * 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名
  439. * @return mixed|\OpenSSLAsymmetricKey|resource
  440. * @throws BaseException
  441. */
  442. private function getMerchantPrivateKeyInstance()
  443. {
  444. try {
  445. return Rsa::from("file://{$this->config['key_path']}", Rsa::KEY_TYPE_PRIVATE);
  446. } catch (\UnexpectedValueException $e) {
  447. throwError('证书文件(KEY)不正确');
  448. }
  449. }
  450. /**
  451. * 读取公钥中的序列号
  452. * @param string $publicKey
  453. * @return mixed
  454. * @throws BaseException
  455. */
  456. private function serialno(string $publicKey)
  457. {
  458. $content = file_get_contents($publicKey);
  459. $plaintext = !empty($content) ? openssl_x509_parse($content) : false;
  460. empty($plaintext) && throwError('证书文件(CERT)不正确');
  461. return $plaintext['serialNumberHex'];
  462. }
  463. /**
  464. * 构建微信支付配置
  465. * @return string[]
  466. */
  467. private function getConfig($options): array
  468. {
  469. if ($options['mchType'] === 'provider') {
  470. return [
  471. 'mch_type' => 'provider',
  472. 'app_id' => $options['provider']['spAppId'],
  473. 'mch_id' => $options['provider']['spMchId'],
  474. 'key' => $options['provider']['spApiKey'],
  475. 'cert_path' => $options['provider']['spApiclientCertPath'],
  476. 'key_path' => $options['provider']['spApiclientKeyPath'],
  477. 'sub_mchid' => $options['provider']['subMchId'],
  478. 'sub_appid' => $options['provider']['subAppId'],
  479. 'signature_method' => $options['provider']['spSignatureMethod'],
  480. 'public_key_id' => $options['provider']['spPublicKeyId'] ?? '',
  481. 'public_key_path' => $options['provider']['spPublicKeyPath'] ?? '',
  482. 'platform_cert_path' => $options['provider']['platformCertPath'] ?? ''
  483. ];
  484. } else {
  485. return [
  486. 'mch_type' => 'normal',
  487. 'app_id' => $options['normal']['appId'],
  488. 'mch_id' => $options['normal']['mchId'],
  489. 'key' => $options['normal']['apiKey'],
  490. 'cert_path' => $options['normal']['apiclientCertPath'],
  491. 'key_path' => $options['normal']['apiclientKeyPath'],
  492. 'signature_method' => $options['normal']['signatureMethod'],
  493. 'public_key_id' => $options['normal']['publicKeyId'] ?? '',
  494. 'public_key_path' => $options['normal']['publicKeyPath'] ?? '',
  495. 'platform_cert_path' => $options['normal']['platformCertPath'] ?? ''
  496. ];
  497. }
  498. }
  499. /**
  500. * 异步回调地址
  501. * @return string
  502. */
  503. private function notifyUrl(): string
  504. {
  505. // 例如:https://www.xxxx.com/wxpayNoticeV3.php
  506. return base_url() . 'wxpayNoticeV3.php';
  507. }
  508. /**
  509. * 当前是否为服务商模式
  510. * @return bool
  511. */
  512. private function isProvider(): bool
  513. {
  514. return $this->config['mch_type'] === 'provider';
  515. }
  516. /**
  517. * Generate app payment parameters.
  518. * @param string $prepayId
  519. * @return array
  520. * @throws BaseException
  521. */
  522. private function appConfig(string $prepayId): array
  523. {
  524. $params = [
  525. 'appid' => $this->config['app_id'],
  526. 'timestamp' => (string)Formatter::timestamp(),
  527. 'noncestr' => Formatter::nonce(),
  528. 'prepayid' => $prepayId,
  529. ];
  530. return array_merge($params, [
  531. 'sign' => Rsa::sign(
  532. Formatter::joinedByLineFeed(...array_values($params)),
  533. $this->getMerchantPrivateKeyInstance()
  534. ),
  535. 'partnerid' => $this->config['mch_id'],
  536. 'package' => 'Sign=WXPay',
  537. ]);
  538. }
  539. /**
  540. * [WeixinJSBridge] Generate js config for payment.
  541. *
  542. * <pre>
  543. * WeixinJSBridge.invoke(
  544. * 'getBrandWCPayRequest',
  545. * ...
  546. * );
  547. * </pre>
  548. *
  549. * @param string $prepayId
  550. * @return string|array
  551. * @throws BaseException
  552. */
  553. private function bridgeConfig(string $prepayId)
  554. {
  555. $params = [
  556. 'appId' => $this->isProvider() ? $this->config['sub_appid'] : $this->config['app_id'],
  557. 'timeStamp' => (string)Formatter::timestamp(),
  558. 'nonceStr' => Formatter::nonce(),
  559. 'package' => "prepay_id=$prepayId",
  560. ];
  561. $params += ['paySign' => Rsa::sign(
  562. Formatter::joinedByLineFeed(...array_values($params)),
  563. $this->getMerchantPrivateKeyInstance()
  564. ), 'signType' => 'RSA'];
  565. return $params;
  566. }
  567. /**
  568. * 处理API的异常
  569. * @param \Throwable $e
  570. * @return mixed|string
  571. */
  572. private function getThrowMessage(\Throwable $e)
  573. {
  574. $message = $e->getMessage();
  575. if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
  576. $body = (string)$e->getResponse()->getBody();
  577. if (!empty($body)) {
  578. $result = helper::jsonDecode($body);
  579. isset($result['message']) && $message = $result['message'];
  580. }
  581. }
  582. return $message;
  583. }
  584. /**
  585. * 统一下单API的Url [需判断是否为服务商支付以及客户端]
  586. * @return string
  587. * @throws BaseException
  588. */
  589. private function getUnifyApiUrl(): string
  590. {
  591. $partnerNodo = $this->isProvider() ? 'partner/' : '';
  592. return "v3/pay/{$partnerNodo}transactions/" . $this->tradeType();
  593. }
  594. /**
  595. * 订单查询API的Url [需判断是否为服务商支付以及客户端]
  596. * @param string $outTradeNo
  597. * @return string
  598. */
  599. private function getTradeApiUrl(string $outTradeNo): string
  600. {
  601. $partnerNodo = $this->isProvider() ? 'partner/' : '';
  602. return "v3/pay/{$partnerNodo}transactions/out-trade-no/{$outTradeNo}";
  603. }
  604. /**
  605. * 申请退款API的Url
  606. * @return string
  607. */
  608. private function getRefundApiUrl(): string
  609. {
  610. return 'v3/refund/domestic/refunds';
  611. }
  612. /**
  613. * 商家转账到零钱API的Url
  614. * @return string
  615. */
  616. private function getTransfersUrl(): string
  617. {
  618. return 'v3/transfer/batches';
  619. }
  620. }