weixinPay.php 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | [ WE CAN DO IT MORE SIMPLE ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2018-2020 rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Author: YingZi
  8. // +----------------------------------------------------------------------
  9. // | Date: 2022-05-27 15:14
  10. // +----------------------------------------------------------------------
  11. declare (strict_types = 1);
  12. namespace library\utils;
  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 WeChatPay\Request\WeChatPayTradeOutTradeNoQueryRequest;
  19. class weixinPay{
  20. private $config;
  21. private $client;
  22. public $errorMsg="系统错误";
  23. private $merchantPrivateKeyInstance="";
  24. /**
  25. * 构造函数
  26. * @param type $config
  27. */
  28. public function __construct($config = [])
  29. {
  30. if(empty($config)) $config = config('wxpay');
  31. $this->config = $config;
  32. //「商户API私钥文件」
  33. $merchantPrivateKeyFilePath = $this->config["PrivateKey"];//示例:file:///path/to/merchant/apiclient_key.pem
  34. $merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);//「商户API私钥」
  35. $merchantCertificateSerial = $this->config["merchantSerialNumber"];//商户API证书」的「证书序列号」示例:'3775B6A45ACD588826D15E583A95F5DD********';
  36. // 「微信支付平台证书」
  37. $platformCertificateFilePath = $this->config["Certificate"];//示例:file:///path/to/wechatpay/cert.pem
  38. $platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);//
  39. $platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateFilePath);//「微信支付平台证证书序列号」
  40. // 构造一个 APIv3 客户端实例
  41. $instance = Builder::factory([
  42. 'mchid' => $this->config["MCHID"],
  43. 'serial' => $merchantCertificateSerial,
  44. 'privateKey' => $merchantPrivateKeyInstance,
  45. 'certs' => [
  46. $platformCertificateSerial => $platformPublicKeyInstance,
  47. ],
  48. ]);
  49. $this->merchantPrivateKeyInstance = $merchantPrivateKeyInstance;
  50. $this->client = $instance;
  51. }
  52. /**
  53. * 微信小程序支付
  54. * @param type $post
  55. */
  56. public function wxmpPay($post=[]){
  57. $apiUrl = "v3/pay/transactions/jsapi";
  58. $jsonData = [];
  59. //商户信息
  60. $jsonData["appid"] = $this->config["APPID"];
  61. $jsonData["mchid"] = $this->config["MCHID"];
  62. $jsonData["notify_url"] = $this->config["NOTIFY_URL"];
  63. //必填参数
  64. $jsonData["description"] = $post["description"];//描述
  65. $jsonData["out_trade_no"] = $post["out_trade_no"];//商户订单号
  66. $jsonData["amount"] = [
  67. "total"=> (int)(floatval($post["total"])*100),//订单金额,分
  68. "currency" => 'CNY'
  69. ];
  70. $jsonData["scene_info"] = [//支付场景描述
  71. "payer_client_ip"=>empty($post["payer_client_ip"])?"127.0.0.1":$post["payer_client_ip"],//客户端IP
  72. ];
  73. $jsonData["time_expire"] = date("Y-m-d\TH:i:s+08:00",time()+30*60);//交易结束时间2018-06-08T10:34:56+08:00
  74. $jsonData["payer"]=[
  75. "openid"=>$post["openid"]
  76. ];
  77. $result = $this->clientHttp("POST", $apiUrl, $jsonData);
  78. if(empty($result)){
  79. if(empty($this->errorMsg)){
  80. $this->errorMsg = "支付错误001";
  81. }
  82. return false;
  83. }
  84. $resuleAr = json_decode($result,true);
  85. if(empty($resuleAr)){
  86. if(empty($this->errorMsg)){
  87. $this->errorMsg = "支付错误002";
  88. }
  89. return false;
  90. }
  91. if(empty($resuleAr["prepay_id"])){
  92. if(empty($this->errorMsg)){
  93. $this->errorMsg = "支付错误003";
  94. }
  95. return false;
  96. }
  97. //组装支付参数
  98. $payInfo=array();
  99. $data=$this->makeSign(["appId"=>$this->config["APPID"],"prepay_id"=>$resuleAr["prepay_id"]]);
  100. $data["payData"] = $jsonData;
  101. return $data;
  102. }
  103. /**
  104. * 查询订单
  105. * @param type $out_trade_no 商户订单号
  106. */
  107. public function searchOrder($out_trade_no){
  108. $apiUrl = "v3/pay/transactions/out-trade-no/{out_trade_no}";
  109. $result = $this->clientHttp("GET", $apiUrl,[
  110. "out_trade_no"=>$out_trade_no,
  111. "query"=>["mchid"=>$this->config["MCHID"]],
  112. ]);
  113. return $result;
  114. }
  115. /**
  116. * 关闭订单
  117. * @param type $out_trade_no 商户订单号
  118. */
  119. public function closeOrder($out_trade_no){
  120. $apiUrl = "v3/pay/transactions/out-trade-no/{out_trade_no}/close";
  121. $result = $this->clientHttp("GET", $apiUrl,[
  122. "out_trade_no"=>$out_trade_no,
  123. "query"=>["mchid"=>$this->config["MCHID"]],
  124. ]);
  125. return $result;
  126. }
  127. /**
  128. * 生成签名
  129. * @param type $info
  130. * @return string
  131. */
  132. private function makeSign($info){
  133. $params = [
  134. 'appId' => $info["appId"],
  135. 'timeStamp' => (string)Formatter::timestamp(),
  136. 'nonceStr' => Formatter::nonce(),
  137. 'package' => 'prepay_id='.$info["prepay_id"],
  138. ];
  139. $params["paySign"] = Rsa::sign(Formatter::joinedByLineFeed(...array_values($params)),$this->merchantPrivateKeyInstance);
  140. $params["signType"] = 'RSA';
  141. return $params;
  142. }
  143. /**
  144. * 解密回调参数
  145. * @param type $data
  146. * @return type
  147. */
  148. public function aesGcmDecrypt($data){
  149. // 加密文本消息解密
  150. $inBodyResource = AesGcm::decrypt($data["ciphertext"], $this->config["apiv3Key"], $data["nonce"], $data["associated_data"]);
  151. // 把解密后的文本转换为PHP Array数组
  152. $inBodyResourceArray = (array)json_decode($inBodyResource, true);
  153. return $inBodyResourceArray;
  154. }
  155. /**
  156. * http提交[同步请求]
  157. * @param type $type
  158. * @param type $url 示例:v3/pay/transactions/native
  159. * @param type $json
  160. * @return boolean
  161. */
  162. private function clientHttp($type='POST',$url='',$json=[]){
  163. try {
  164. $resp=null;
  165. if($type=="POST"){
  166. $resp = $this->client->chain($url)->post(['json' => $json]);
  167. if(empty($json)){
  168. $resp = $this->client->chain($url)->post();
  169. }else{
  170. $resp = $this->client->chain($url)->post(['json' => $json]);
  171. }
  172. }
  173. if($type=="GET"){
  174. if(empty($json)){
  175. $resp = $this->client->chain($url)->get();
  176. }else{
  177. $resp = $this->client->chain($url)->get($json);
  178. }
  179. }
  180. if(empty($resp)){
  181. $this->errorMsg="提交方式错误";
  182. return false;
  183. }
  184. $statusCode = $resp->getStatusCode();
  185. if ($statusCode == 200) { //处理成功
  186. return $resp->getBody()->getContents();
  187. } else if ($statusCode == 204) { //处理成功,无返回Body
  188. $this->errorMsg = "处理成功,无返回Body";
  189. return false;
  190. }else{
  191. $this->errorMsg = "未知错误";
  192. return false;
  193. }
  194. } catch (\Exception $e) {
  195. // 进行错误处理
  196. $this->errorMsg = $e->getMessage();
  197. if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
  198. $r = $e->getResponse();
  199. $this->errorMsg=$r->getStatusCode()."".$r->getReasonPhrase().$r->getBody();
  200. }
  201. return false;
  202. }
  203. }
  204. }