Payment.php 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. <?php namespace App\Servers\WechatPay;
  2. use DateTime;
  3. use DateTimeZone;
  4. use App\Facades\Servers\Logs\Log;
  5. use WeChatPay\Builder;
  6. use WeChatPay\Crypto\Rsa;
  7. use WeChatPay\Util\PemUtil;
  8. use WeChatPay\Formatter;
  9. class Payment
  10. {
  11. private $appid;
  12. private $mchid;
  13. private $instance;
  14. private $merchantPrivateKeyInstance;
  15. private $merchantCertificateSerial;
  16. function __construct()
  17. {
  18. // 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名
  19. $merchantPrivateKeyFilePath = Config('wechat.pay.key');
  20. $merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);
  21. // 「商户API证书」的「证书序列号」
  22. // 从本地文件中加载「微信支付平台证书」,用来验证微信支付应答的签名
  23. $platformCertificateFilePath = Config('wechat.platformCertificate');
  24. $platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);
  25. // 从「微信支付平台证书」中获取「证书序列号」
  26. $platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateFilePath);
  27. // 构造一个 APIv3 客户端实例
  28. $instance = Builder::factory([
  29. 'mchid' => Config('wechat.pay.mch_id'),
  30. 'serial' => Config('wechat.pay.certificate'),
  31. 'privateKey' => $merchantPrivateKeyInstance,
  32. 'certs' => [
  33. $platformCertificateSerial => $platformPublicKeyInstance,
  34. ],
  35. ]);
  36. $this->instance = $instance;
  37. $this->appid = Config('wechat.pay.appid');
  38. $this->mchid = Config('wechat.pay.mch_id');
  39. $this->merchantPrivateKeyInstance = $merchantPrivateKeyInstance;
  40. $this->merchantCertificateSerial = Config('wechat.pay.certificate');
  41. return;
  42. }
  43. /**
  44. * 拉起小程序支付
  45. */
  46. public function pay($params)
  47. {
  48. $notify_url = Config('wechat.pay.notify_url');
  49. //调用微信JSAPI下单获取预支付交易会话标识
  50. try {
  51. $resp = $this->instance
  52. ->chain('v3/pay/transactions/jsapi')
  53. ->post(['json' => [
  54. 'mchid' => $this->mchid,
  55. 'out_trade_no' => $params['out_trade_no'],
  56. 'appid' => $this->appid,
  57. 'description' => $params['description'],
  58. 'time_expire' => $this->timestampToRfc3339(time() + env('ORDER_OUT_TIME')*60),
  59. 'notify_url' => $notify_url,
  60. 'amount' => [
  61. 'total' => $params['total_price'],
  62. 'currency' => 'CNY'
  63. ],
  64. 'payer' => [
  65. 'openid' => $params['openid']
  66. ]
  67. ]]);
  68. $result = json_decode($resp->getBody(),true);
  69. $prepay_id = $result['prepay_id'];
  70. } catch (\Exception $e) {
  71. if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
  72. $r = $e->getResponse();
  73. // 记录错误信息
  74. Log::error('wechat/Payment','拉起小程序支付,出现错误'.$r->getBody().'参数'.json_encode($params));
  75. }
  76. return response()->json(['error' => '拉起支付失败'], 500);
  77. }
  78. //根据返回的预支付交易会话标识,组装调起小程序支付参数
  79. $data = [
  80. 'appId' => $this->appid,
  81. 'timeStamp' => (string)Formatter::timestamp(),
  82. 'nonceStr' => Formatter::nonce(),
  83. 'package' => 'prepay_id='.$prepay_id,
  84. ];
  85. $data += ['paySign' => Rsa::sign(
  86. Formatter::joinedByLineFeed(...array_values($data)),
  87. $this->merchantPrivateKeyInstance
  88. ), 'signType' => 'RSA'];
  89. return response()->json($data);
  90. }
  91. //查询订单
  92. public function query($params)
  93. {
  94. try {
  95. $promise = $this->instance
  96. ->chain('v3/pay/transactions/id/'.$params[''])
  97. ->get(['json' => [
  98. 'mchid' => $this->mchid,
  99. ]]);
  100. } catch (\Exception $e) {
  101. if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
  102. $r = $e->getResponse();
  103. // 记录错误信息
  104. Log::error('wechat/Payment','查询订单,出现错误'.$r->getBody().'参数'.json_encode($params));
  105. }
  106. return response()->json(['error' => '查询订单支付失败'], 500);
  107. }
  108. return response()->json($promise);
  109. }
  110. /**
  111. * 订单微信退款
  112. */
  113. public function refund($params)
  114. {
  115. $notify_url = Config('wechat.course_refund_notify_url');
  116. try {
  117. $resp = $this->instance
  118. ->chain('v3/refund/domestic/refunds')
  119. ->post(['json' => [
  120. 'transaction_id' => $params['transaction_id'],
  121. 'out_refund_no' => $params['out_refund_no'],
  122. 'reason' => $params['reason'] ?? '',
  123. 'notify_url' => $notify_url,
  124. 'amount' => [
  125. 'refund' => $params['refund'],
  126. 'total' => $params['total'],
  127. 'currency' => 'CNY'
  128. ],
  129. ]]);
  130. } catch (\Exception $e) {
  131. if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
  132. $r = $e->getResponse();
  133. Log::error('wechat/Payment','订单微信退款,出现错误'.$r->getBody());
  134. }
  135. return response()->json(['error' => '订单微信退款失败'], 500);
  136. }
  137. return response()->json($resp);
  138. }
  139. //Rfc3339时间日期格式
  140. public function timestampToRfc3339($timestamp)
  141. {
  142. // 转换为DateTime对象
  143. $dateTime = new DateTime();
  144. $dateTime->setTimestamp($timestamp);
  145. // 设置时区,默认为UTC
  146. $dateTime->setTimezone(new DateTimeZone('UTC'));
  147. // 格式化为RFC3339
  148. return $dateTime->format(DateTime::RFC3339);
  149. }
  150. }