Wxpay.php 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. <?php
  2. /**
  3. * Wxpay.php UTF-8
  4. *
  5. *
  6. * @date : 2018/5/3 23:07
  7. *
  8. * @license 这不是一个自由软件,未经授权不许任何使用和传播。
  9. * @author : wuyonghong <wyh@huosdk.com>
  10. * @version : HUOSDK 8.0
  11. */
  12. namespace huolib\withdraw\driver;
  13. use huolib\constant\SettleConst;
  14. use huolib\withdraw\Driver;
  15. use WxPayException;
  16. require_once EXTEND_PATH."pay/wxpay/WxPay.Data.php";
  17. class Wxpay extends Driver {
  18. private $config
  19. = [
  20. 'app_id' => '', /* 绑定支付的APPID(必须配置,开户邮件中可查看)*/
  21. 'mch_id' => '', /* 商户号(必须配置,开户邮件中可查看) */
  22. 'key' => '', /* 商户支付密钥,参考开户邮件设置(必须配置,登录商户平台自行设置) */
  23. 'app_secret' => '', /* 公众帐号secert */
  24. 'curl_proxy_host' => '0.0.0.0',
  25. 'curl_proxy_port' => '0',
  26. 'report_levenl' => '1',
  27. 'device_info' => 'WEB', /* 终端设备号(门店号或收银设备ID),默认请传"WEB" */
  28. 'sslcert_path' => '',
  29. 'sslkey_path' => '',
  30. ];
  31. /**
  32. * 构造函数
  33. *
  34. * @param array $config
  35. */
  36. public function __construct(array $config = []) {
  37. $_config = $config;
  38. if (empty($config)) {
  39. $_conf_file = GLOBAL_CONF_PATH.'extra/pay/wxpay/config.php';
  40. if (file_exists($_conf_file)) {
  41. $_config = include $_conf_file;
  42. } else {
  43. $_config = array();
  44. }
  45. $this->config = array_merge($this->config, $_config);
  46. $this->config['sslcert_path'] = GLOBAL_CONF_PATH.'extra/pay/wxpay/cert/apiclient_cert.pem';
  47. $this->config['sslkey_path'] = GLOBAL_CONF_PATH.'extra/pay/wxpay/cert/apiclient_key.pem';
  48. } else {
  49. $this->config = array_merge($this->config, $_config);
  50. }
  51. }
  52. /**
  53. * 企业付款
  54. * https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_2
  55. *
  56. * @throws \WxPayException
  57. */
  58. public function withDraw() {
  59. $_re_data = [];
  60. $_input = new \WxPayTransfers();
  61. $_input->SetAppid($this->config['app_id']);//商户账号appid
  62. $_input->SetMch_id($this->config['mch_id']);//商户号
  63. $_input->setKey($this->config['key']);//商户密钥
  64. $_input->SetDevice_info($this->config['device_info']);//设备号
  65. $_input->SetNonce_str(self::getNonceStr());//随机字符串
  66. $_input->SetOut_trade_no($this->order_id);//商户订单号
  67. $_input->setOpenId($this->open_id);//用户openid
  68. $_input->setCheck_name($this->check_name);//校验用户姓名选项 NO_CHECK:不校验真实姓名 FORCE_CHECK:强校验真实姓名
  69. $_input->setRe_user_name($this->payee_real_name);//收款用户姓名
  70. $_input->setAmount(intval($this->real_amount * 100));//金额 单位为分
  71. $_input->setDesc($this->remark);//企业付款描述信息
  72. $_ip = request()->ip();
  73. $_input->setSpbill_create_ip($_ip); //Ip地址
  74. $_url = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers";
  75. //签名
  76. $_input->SetSign();
  77. $_xml = $_input->ToXml();
  78. $response = self::postXmlCurl($_xml, $_url, true, 6, $this->config);
  79. $_wx_data_obj = new \WxPayDataBase();
  80. $_wx_data_obj->FromXml($response);
  81. $_result = $_wx_data_obj->GetValues();
  82. $_re_data['result'] = json_encode($_result);
  83. $_re_data['code'] = $_result['result_code'];
  84. $_re_data['msg'] = $_result['return_msg'];
  85. if (!empty($_result['return_code']) && 'SUCCESS' == $_result['return_code']) {
  86. if (!empty($_result['result_code']) && 'SUCCESS' == $_result['result_code']) { //打款成功
  87. $_re_data['code'] = SettleConst::SETTLE_SUCCESS;
  88. } else {
  89. /*错误处理*/
  90. switch ($_result['err_code']) {
  91. case 'SEND_FAILED': //付款错误,请求查询接口 确认结果
  92. case 'SYSTEMERROR': //微信内部接口调用发生错误,请求查询接口 确认结果
  93. $_re_data['code'] = $_result['err_code'];
  94. $_re_data['msg'] = $_result['err_code_des'];
  95. $_rs = $this->orderQuery($this->order_id);
  96. $_re_data['query_result'] = $_rs['result'];
  97. if (isset($_rs['status'])
  98. && SettleConst::SETTLE_PAY_FAILED != $_rs['status']) { //查询结果不为打款失败,返回状态为成功
  99. $_re_data['code'] = SettleConst::SETTLE_SUCCESS;
  100. }
  101. break;
  102. default: //其他结果返回错误信息
  103. $_re_data['code'] = $_result['err_code'];
  104. $_re_data['msg'] = $_result['err_code_des'];
  105. }
  106. }
  107. }
  108. return $_re_data;
  109. }
  110. /**
  111. * 查询企业付款
  112. * https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_3
  113. *
  114. * @access public
  115. *
  116. * @param string $order_id 商户系统内部订单号
  117. * @param null $ext 扩展信息
  118. *
  119. * @return array
  120. * @throws WxPayException
  121. */
  122. public function orderQuery($order_id, $ext = null) {
  123. $_input = new \WxPayGetTransfer();
  124. $_input->SetAppid($this->config['app_id']);//商户账号appid
  125. $_input->SetMch_id($this->config['mch_id']);//商户号
  126. $_input->setKey($this->config['key']);//商户密钥
  127. $_input->SetNonce_str(self::getNonceStr());//随机字符串
  128. $_input->SetOut_trade_no($this->order_id);//商户订单号
  129. $_url = "https://api.mch.weixin.qq.com/mmpaymkttransfers/gettransferinfo";
  130. //签名
  131. $_input->SetSign();
  132. $_xml = $_input->ToXml();
  133. $response = self::postXmlCurl($_xml, $_url, true, 6, $this->config);
  134. $_wx_data_obj = new \WxPayDataBase();
  135. $_wx_data_obj->FromXml($response);
  136. $_result = $_wx_data_obj->GetValues();
  137. $_rdata = [
  138. 'code' => $_result['return_code'],
  139. 'msg' => $_result['return_msg'],
  140. 'result' => json_encode($_result)
  141. ];
  142. if (!empty($_result['return_code']) && 'SUCCESS' == $_result['return_code']) {
  143. $_rdata['code'] = $_result['err_code'];
  144. $_rdata['msg'] = $_result['err_code_des'];
  145. if (!empty($_result['result_code']) && 'SUCCESS' == $_result['result_code']) {
  146. switch ($_rdata['status']) {
  147. case 'SUCCESS':
  148. $_rdata['status'] = SettleConst::SETTLE_PAY_SUCCESS;
  149. break;
  150. case 'PROCESSING':
  151. $_rdata['status'] = SettleConst::SETTLE_PAY_PROCESSING;
  152. break;
  153. default:
  154. $_rdata['status'] = SettleConst::SETTLE_PAY_FAILED;
  155. }
  156. $_rdata['reason'] = $_result['reason'];
  157. }
  158. }
  159. return $_rdata;
  160. }
  161. /**
  162. *
  163. * 产生随机字符串,不长于32位
  164. *
  165. * @param int $length
  166. *
  167. * @return string 产生的随机字符串
  168. */
  169. public static function getNonceStr($length = 32) {
  170. $chars = "abcdefghijklmnopqrstuvwxyz0123456789";
  171. $str = "";
  172. for ($i = 0; $i < $length; $i++) {
  173. $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
  174. }
  175. return $str;
  176. }
  177. /**
  178. * 获取毫秒级别的时间戳
  179. */
  180. private static function getMillisecond() {
  181. //获取毫秒的时间戳
  182. $time = explode(" ", microtime());
  183. $time = $time[1].($time[0] * 1000);
  184. $time2 = explode(".", $time);
  185. $time = $time2[0];
  186. return $time;
  187. }
  188. /**
  189. * 以post方式提交xml到对应的接口url
  190. *
  191. * @param string $xml 需要post的xml数据
  192. * @param string $url url
  193. * @param bool $useCert 是否需要证书,默认不需要
  194. * @param int $second url执行超时时间,默认30s
  195. *
  196. * @param array $config
  197. *
  198. * @return mixed
  199. * @throws WxPayException
  200. */
  201. private static function postXmlCurl($xml, $url, $useCert = false, $second = 30, array $config) {
  202. $ch = curl_init();
  203. //设置超时
  204. curl_setopt($ch, CURLOPT_TIMEOUT, $second);
  205. //如果有配置代理这里就设置代理
  206. if ($config['curl_proxy_host'] != "0.0.0.0"
  207. && $config['curl_proxy_port'] != 0
  208. ) {
  209. curl_setopt($ch, CURLOPT_PROXY, $config['curl_proxy_host']);
  210. curl_setopt($ch, CURLOPT_PROXYPORT, $config['curl_proxy_port']);
  211. }
  212. curl_setopt($ch, CURLOPT_URL, $url);
  213. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
  214. curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);//严格校验
  215. //设置header
  216. curl_setopt($ch, CURLOPT_HEADER, false);
  217. //要求结果为字符串且输出到屏幕上
  218. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  219. if ($useCert == true) {
  220. //设置证书
  221. //使用证书:cert 与 key 分别属于两个.pem文件
  222. curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');
  223. curl_setopt($ch, CURLOPT_SSLCERT, $config['sslcert_path']);
  224. curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');
  225. curl_setopt($ch, CURLOPT_SSLKEY, $config['sslkey_path']);
  226. }
  227. //post提交方式
  228. curl_setopt($ch, CURLOPT_POST, true);
  229. curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
  230. //运行curl
  231. $data = curl_exec($ch);
  232. //返回结果
  233. if ($data) {
  234. curl_close($ch);
  235. return $data;
  236. } else {
  237. $error = curl_errno($ch);
  238. curl_close($ch);
  239. throw new WxPayException("curl出错,错误码:$error");
  240. }
  241. }
  242. }