* @version : HUOSDK 8.0 */ namespace huolib\withdraw\driver; use huolib\constant\SettleConst; use huolib\withdraw\Driver; use WxPayException; require_once EXTEND_PATH."pay/wxpay/WxPay.Data.php"; class Wxpay extends Driver { private $config = [ 'app_id' => '', /* 绑定支付的APPID(必须配置,开户邮件中可查看)*/ 'mch_id' => '', /* 商户号(必须配置,开户邮件中可查看) */ 'key' => '', /* 商户支付密钥,参考开户邮件设置(必须配置,登录商户平台自行设置) */ 'app_secret' => '', /* 公众帐号secert */ 'curl_proxy_host' => '0.0.0.0', 'curl_proxy_port' => '0', 'report_levenl' => '1', 'device_info' => 'WEB', /* 终端设备号(门店号或收银设备ID),默认请传"WEB" */ 'sslcert_path' => '', 'sslkey_path' => '', ]; /** * 构造函数 * * @param array $config */ public function __construct(array $config = []) { $_config = $config; if (empty($config)) { $_conf_file = GLOBAL_CONF_PATH.'extra/pay/wxpay/config.php'; if (file_exists($_conf_file)) { $_config = include $_conf_file; } else { $_config = array(); } $this->config = array_merge($this->config, $_config); $this->config['sslcert_path'] = GLOBAL_CONF_PATH.'extra/pay/wxpay/cert/apiclient_cert.pem'; $this->config['sslkey_path'] = GLOBAL_CONF_PATH.'extra/pay/wxpay/cert/apiclient_key.pem'; } else { $this->config = array_merge($this->config, $_config); } } /** * 企业付款 * https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_2 * * @throws \WxPayException */ public function withDraw() { $_re_data = []; $_input = new \WxPayTransfers(); $_input->SetAppid($this->config['app_id']);//商户账号appid $_input->SetMch_id($this->config['mch_id']);//商户号 $_input->setKey($this->config['key']);//商户密钥 $_input->SetDevice_info($this->config['device_info']);//设备号 $_input->SetNonce_str(self::getNonceStr());//随机字符串 $_input->SetOut_trade_no($this->order_id);//商户订单号 $_input->setOpenId($this->open_id);//用户openid $_input->setCheck_name($this->check_name);//校验用户姓名选项 NO_CHECK:不校验真实姓名 FORCE_CHECK:强校验真实姓名 $_input->setRe_user_name($this->payee_real_name);//收款用户姓名 $_input->setAmount(intval($this->real_amount * 100));//金额 单位为分 $_input->setDesc($this->remark);//企业付款描述信息 $_ip = request()->ip(); $_input->setSpbill_create_ip($_ip); //Ip地址 $_url = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers"; //签名 $_input->SetSign(); $_xml = $_input->ToXml(); $response = self::postXmlCurl($_xml, $_url, true, 6, $this->config); $_wx_data_obj = new \WxPayDataBase(); $_wx_data_obj->FromXml($response); $_result = $_wx_data_obj->GetValues(); $_re_data['result'] = json_encode($_result); $_re_data['code'] = $_result['result_code']; $_re_data['msg'] = $_result['return_msg']; if (!empty($_result['return_code']) && 'SUCCESS' == $_result['return_code']) { if (!empty($_result['result_code']) && 'SUCCESS' == $_result['result_code']) { //打款成功 $_re_data['code'] = SettleConst::SETTLE_SUCCESS; } else { /*错误处理*/ switch ($_result['err_code']) { case 'SEND_FAILED': //付款错误,请求查询接口 确认结果 case 'SYSTEMERROR': //微信内部接口调用发生错误,请求查询接口 确认结果 $_re_data['code'] = $_result['err_code']; $_re_data['msg'] = $_result['err_code_des']; $_rs = $this->orderQuery($this->order_id); $_re_data['query_result'] = $_rs['result']; if (isset($_rs['status']) && SettleConst::SETTLE_PAY_FAILED != $_rs['status']) { //查询结果不为打款失败,返回状态为成功 $_re_data['code'] = SettleConst::SETTLE_SUCCESS; } break; default: //其他结果返回错误信息 $_re_data['code'] = $_result['err_code']; $_re_data['msg'] = $_result['err_code_des']; } } } return $_re_data; } /** * 查询企业付款 * https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_3 * * @access public * * @param string $order_id 商户系统内部订单号 * @param null $ext 扩展信息 * * @return array * @throws WxPayException */ public function orderQuery($order_id, $ext = null) { $_input = new \WxPayGetTransfer(); $_input->SetAppid($this->config['app_id']);//商户账号appid $_input->SetMch_id($this->config['mch_id']);//商户号 $_input->setKey($this->config['key']);//商户密钥 $_input->SetNonce_str(self::getNonceStr());//随机字符串 $_input->SetOut_trade_no($this->order_id);//商户订单号 $_url = "https://api.mch.weixin.qq.com/mmpaymkttransfers/gettransferinfo"; //签名 $_input->SetSign(); $_xml = $_input->ToXml(); $response = self::postXmlCurl($_xml, $_url, true, 6, $this->config); $_wx_data_obj = new \WxPayDataBase(); $_wx_data_obj->FromXml($response); $_result = $_wx_data_obj->GetValues(); $_rdata = [ 'code' => $_result['return_code'], 'msg' => $_result['return_msg'], 'result' => json_encode($_result) ]; if (!empty($_result['return_code']) && 'SUCCESS' == $_result['return_code']) { $_rdata['code'] = $_result['err_code']; $_rdata['msg'] = $_result['err_code_des']; if (!empty($_result['result_code']) && 'SUCCESS' == $_result['result_code']) { switch ($_rdata['status']) { case 'SUCCESS': $_rdata['status'] = SettleConst::SETTLE_PAY_SUCCESS; break; case 'PROCESSING': $_rdata['status'] = SettleConst::SETTLE_PAY_PROCESSING; break; default: $_rdata['status'] = SettleConst::SETTLE_PAY_FAILED; } $_rdata['reason'] = $_result['reason']; } } return $_rdata; } /** * * 产生随机字符串,不长于32位 * * @param int $length * * @return string 产生的随机字符串 */ public static function getNonceStr($length = 32) { $chars = "abcdefghijklmnopqrstuvwxyz0123456789"; $str = ""; for ($i = 0; $i < $length; $i++) { $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1); } return $str; } /** * 获取毫秒级别的时间戳 */ private static function getMillisecond() { //获取毫秒的时间戳 $time = explode(" ", microtime()); $time = $time[1].($time[0] * 1000); $time2 = explode(".", $time); $time = $time2[0]; return $time; } /** * 以post方式提交xml到对应的接口url * * @param string $xml 需要post的xml数据 * @param string $url url * @param bool $useCert 是否需要证书,默认不需要 * @param int $second url执行超时时间,默认30s * * @param array $config * * @return mixed * @throws WxPayException */ private static function postXmlCurl($xml, $url, $useCert = false, $second = 30, array $config) { $ch = curl_init(); //设置超时 curl_setopt($ch, CURLOPT_TIMEOUT, $second); //如果有配置代理这里就设置代理 if ($config['curl_proxy_host'] != "0.0.0.0" && $config['curl_proxy_port'] != 0 ) { curl_setopt($ch, CURLOPT_PROXY, $config['curl_proxy_host']); curl_setopt($ch, CURLOPT_PROXYPORT, $config['curl_proxy_port']); } curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);//严格校验 //设置header curl_setopt($ch, CURLOPT_HEADER, false); //要求结果为字符串且输出到屏幕上 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); if ($useCert == true) { //设置证书 //使用证书:cert 与 key 分别属于两个.pem文件 curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM'); curl_setopt($ch, CURLOPT_SSLCERT, $config['sslcert_path']); curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM'); curl_setopt($ch, CURLOPT_SSLKEY, $config['sslkey_path']); } //post提交方式 curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $xml); //运行curl $data = curl_exec($ch); //返回结果 if ($data) { curl_close($ch); return $data; } else { $error = curl_errno($ch); curl_close($ch); throw new WxPayException("curl出错,错误码:$error"); } } }