class Wechat
{
//公众号的或者小程序支付参数
private $appId = "*************";
private $appSecret = "*************";
//商家的配置信息
private $mch_id = "*************";
private $mch_key = "*************";
//回调地址
public $notify_url = '';
//退款回掉地址
public $refund_notify_url = '';
private $request;
private $nonce_str;
public function __construct()
{
$this->request = Request::instance();
$this->notify_url = base_url() . 'notice.php';
$this->refund_notify_url = base_url() . 'refund.php';
$this->nonce_str = md5(date('YmdHis') . time() . rand(1000, 9999));
}
//小程序登录
/**
* @param $code 获取微信支付的登录code
* @return mixed
*/
public function wxLogin($code)
{
$url = "https://api.weixin.qq.com/sns/jscode2session?appid=" . $this->appId . "&secret=" . $this->appSecret . "&js_code=" . $code . "&grant_type=authorization_code";
return json_decode($this->execute($url), true);
}
/**
* @param $out_trade_no 微信支付唯一订单
* @param $openid 微信支付用户的openid
* @param $price 订单支付的价格,(单位,元)
* @param string $desc 订单描述
* @return array|mixed 组装支付参数
*/
public function getPayParameter($out_trade_no, $openid, $price, $desc = '')
{
header("Content-type:text/html;charset=utf-8"); //此处进行字符集初始化,
$data = array(
'appid' => $this->appId,
'body' => $desc,
'mch_id' => $this->mch_id,
'nonce_str' => $this->nonce_str, //随机字符串
'notify_url' => $this->notify_url, //异步回调地址
'openid' => $openid, //用户登录时获取的code中含有的值
'out_trade_no' => $out_trade_no, //商家订单号
'spbill_create_ip' => $this->get_real_ip(), //APP和网页支付提交用户端ip
'total_fee' => $price * 100, //订单总额
//'total_fee' => 1,
'attach' => 'order', //确定是哪个商家进行的支付
'trade_type' => 'JSAPI' //交易类型
);
//将数组转化为Xml
$data['sign'] = $this->makeSign($data);
$abc_xml = $this->arrayToXml($data);
//统一下单接口prepay_id
$url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
$xml = $this->execute($url, $abc_xml, 1);
//将XMl转化为数组
$info = $this->xml2array($xml);
if (!isset($info['prepay_id'])) {
return $info;
}
$params = array(
'appId' => $this->appId,
'nonceStr' => $data['nonce_str'],
'package' => 'prepay_id=' . $info['prepay_id'],
'signType' => 'MD5',
'timeStamp' => '' . time(),
);
$_info['paySign'] = $this->makeSign($params);
$_info['timeStamp'] = "" . $params['timeStamp'];
$_info['nonceStr'] = $params['nonceStr'];
$_info['package'] = $params['package'];
$_info['signType'] = $params['signType'];
$_info['notify_url'] = $this->notify_url;
//请求成功后进行返回数据信息
if ($info['return_code'] == 'SUCCESS' || $info['result_code'] == 'SUCCESS') {
return $_info;
} else {
return [];
}
}
/**
* @param $openid 支付的用户的openid
* @return mixed
*/
public function userInfo($openid)
{
$token = $this->getWxAccessToken();
$url = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=" . $token . "&openid=$openid&lang=zh_CN";
return json_decode($this->execute($url), true);
}
//获取微信的token
public function getWxAccessToken()
{
$key = $this->appId . 'miniProgram_access_token';
$accessToken = Cache::get($key);
if ($accessToken) return $accessToken;
$url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" . $this->appId . "&secret=" . $this->appSecret;
$con = json_decode($this->execute($url));
Cache::set($key, $con->access_token, $con->expires_in);
return $con->access_token;
}
/**
* @param $url 请求url地址
* @param string $data 请求参数
* @param int $post 是否为post
* @param int $cert 是否为微信的cert
* @return mixed
*/
private function execute($url, $data = '', $post = 0, $cert = 0)
{
if (is_array($data)) {
$data = json_encode($data);
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3);
curl_setopt($ch, CURLOPT_POST, $post);
if ($post)
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
if ($cert) {
curl_setopt($ch, CURLOPT_SSLCERT, WEB_PATH . 'cert' . DS . 'apiclient_cert.pem');
curl_setopt($ch, CURLOPT_SSLKEY, WEB_PATH . 'cert' . DS . 'apiclient_key.pem');
}
// curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type:application/json']);
curl_setopt($ch, CURLOPT_URL, $url);
$result = curl_exec($ch);
curl_close($ch);
return $result;
}
function http_post_data($url, $data_string, $header = [], $is_array = 0, $is_key_pem = false)
{
if (is_array($data_string)) {
$data_string = json_encode($data_string);
}
if (!$header) {
$header = [
"Content-Type: application/json; charset=utf-8",
"Content-Length: " . strlen($data_string)
];
} else {
$header = array_merge($header, [
"Content-Type: application/json; charset=utf-8",
"Content-Length: " . strlen($data_string)
]);
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string);
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
curl_setopt($ch, CURLOPT_TIMEOUT, 3);
if ($is_key_pem) {
$isdir = '/cert/';
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);// 终止从服务端进行验证
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);//
curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');//证书类型
curl_setopt($ch, CURLOPT_SSLCERT, $isdir . 'apiclient_cert.pem');//证书位置
curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');//CURLOPT_SSLKEY中规定的私钥的加密类型
curl_setopt($ch, CURLOPT_SSLKEY, $isdir . 'apiclient_key.pem');//证书位置
// curl_setopt($ch, CURLOPT_CAINFO, 'PEM');
// curl_setopt($ch, CURLOPT_CAINFO, $isdir . 'rootca.pem');
}
ob_start();
curl_exec($ch);
$return_content = ob_get_contents();
ob_end_clean();
$return_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($is_array) {
if (!is_array($return_content)) {
$return_content = json_decode($return_content, true);
}
}
return $return_content;
}
// 数组转化为 xlm
public function arrayToXml($data)
{
$data['sign'] = $this->makeSign($data);
ksort($data);
//进行拼接数据
$abc_xml = "";
foreach ($data as $key => $val) {
if (is_numeric($val)) {
$abc_xml .= "<" . $key . ">" . $val . "" . $key . ">";
} else {
$abc_xml .= "<" . $key . ">" . $key . ">";
}
}
$abc_xml .= " ";
return $abc_xml;
}
//xlm 转化为数组
public function xml2array($xml)
{
if (empty($xml)) {
return array();
}
//禁止引用外部xml实体
libxml_disable_entity_loader(true);
$xmlstring = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
$val = json_decode(json_encode($xmlstring), true);
return $val;
}
//微信进行拼接sign
public function makeSign($params)
{
//签名步骤一:按字典序排序数组参数
ksort($params);
$buff1 = '';
foreach ($params as $k => $v) {
if ($k != "sign" && $v != "" && !is_array($v)) {
$buff1 .= $k . "=" . $v . "&";
}
}
$buff1 = trim($buff1, "&");
//签名步骤二:在string后加入KEY
$string = $buff1 . "&key=" . $this->mch_key;
//签名步骤三:MD5加密
$string = md5($string);
//签名步骤四:所有字符转为大写
$result = strtoupper($string);
return $result;
}
//验证图片或者视频是否涉黄
public function imgSecCheck($media_url, $media_type = 2)
{
$url = 'https://api.weixin.qq.com/wxa/media_check_async?access_token=' . $this->getWxAccessToken();
$return = $this->execute($url, [
'media_url' => $media_url,
'media_type' => $media_type,
], 1);
$return = json_decode($return, true);
if ($return && $return['errcode'] == 0) {
return true;
}
return false;
}
//验证发布内容是否违法以及敏感词汇
public function msgSecCheck($content = '')
{
$url = 'https://api.weixin.qq.com/wxa/msg_sec_check?access_token=' . $this->getWxAccessToken();
$return = $this->execute($url, ['content' => $content], 1);
$return = json_decode($return, true);
if ($return && $return['errcode'] == 0) {
return true;
}
return false;
}
/**
* [sendMoney 企业付款到零钱]
* @param [type] $amount [发送的金额(分)目前发送金额不能少于1元]
* @param [type] $re_openid [发送人的 openid]
* @param string $desc [企业付款描述信息 (必填)]
* @param string $check_name [收款用户姓名 (选填)]
* @return [type] [description]
* https://www.cnblogs.com/echoppy/p/8603286.html
*/
public function sendMoney($amount, $re_openid)
{
$total_amount = (100) * $amount;
$data = array(
'mch_appid' => $this->appId,//商户账号appid
'mchid' => $this->mch_id,//商户号
'nonce_str' => $this->nonce_str,//随机字符串
'partner_trade_no' => date('YmdHis') . rand(1000, 9999),//商户订单号
'openid' => $re_openid,//用户openid
'check_name' => 'NO_CHECK',//校验用户姓名选项,
'amount' => $total_amount,//付款金额,单位为分
'desc' => '商户提现',//企业付款描述信息
'spbill_create_ip' => $this->get_real_ip(),//Ip地址
);
//生成签名算法
$secrect_key = $this->mch_key; ///这个就是个API密码。MD5 32位。
$data = array_filter($data);
ksort($data);
$str = '';
foreach ($data as $k => $v) {
$str .= $k . '=' . $v . '&';
}
$str .= 'key=' . $secrect_key;
$data['sign'] = md5($str);
//生成签名算法
$xml = $this->arraytoxml($data);
$url = 'https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers'; //调用接口
//$res = $this->http_post_data($url, $xml)
$res = $this->execute($url, $xml, 1, 1);
$return = $this->xml2array($res);
return $return;
}
/**
* @param $amount 退款金额
* @param $re_openid 退款用户的openid
* @return bool 退款成功失败
* 开发文档 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4
*/
public function refundUserMoney($transaction_id, $total_fee, $refund_fee, $out_refund_no)
{
$data = array(
'appid' => $this->appId,//商户账号appid
'mch_id' => $this->mch_id,//商户号
'nonce_str' => $this->nonce_str,//随机字符串
'transaction_id' => $transaction_id,//微信支付商户号
'total_fee' => $total_fee * 100,//订单金额
'refund_fee' => $refund_fee * 100,//退款金额
'notify_url' => $this->refund_notify_url,//退款金额
'out_refund_no' => $out_refund_no,//退款金额
);
//生成签名算法
$secrect_key = $this->mch_key; ///这个就是个API密码。MD5 32位。
$data = array_filter($data);
ksort($data);
$str = '';
foreach ($data as $k => $v) {
$str .= $k . '=' . $v . '&';
}
$str .= 'key=' . $secrect_key;
$data['sign'] = md5($str);
//生成签名算法
$xml = $this->arraytoxml($data);
$url = "https://api.mch.weixin.qq.com/secapi/pay/refund";
$res = $this->execute($url, $xml, 1, 1);
if (!$res) {
return false;
}
//$res = $this->http_post_data($url, $xml);
$return = $this->xml2array($res);
if ($return['return_code'] == 'SUCCESS') {
return true;
}
return false;
}
public function get_real_ip()
{
$ip = $_SERVER['REMOTE_ADDR'];
if (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && preg_match_all('#\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}#s', $_SERVER['HTTP_X_FORWARDED_FOR'], $matches)) {
foreach ($matches[0] AS $xip) {
if (!preg_match('#^(10|172\.16|192\.168)\.#', $xip)) {
$ip = $xip;
break;
}
}
} elseif (isset($_SERVER['HTTP_CLIENT_IP']) && preg_match('/^([0-9]{1,3}\.){3}[0-9]{1,3}$/', $_SERVER['HTTP_CLIENT_IP'])) {
$ip = $_SERVER['HTTP_CLIENT_IP'];
} elseif (isset($_SERVER['HTTP_CF_CONNECTING_IP']) && preg_match('/^([0-9]{1,3}\.){3}[0-9]{1,3}$/', $_SERVER['HTTP_CF_CONNECTING_IP'])) {
$ip = $_SERVER['HTTP_CF_CONNECTING_IP'];
} elseif (isset($_SERVER['HTTP_X_REAL_IP']) && preg_match('/^([0-9]{1,3}\.){3}[0-9]{1,3}$/', $_SERVER['HTTP_X_REAL_IP'])) {
$ip = $_SERVER['HTTP_X_REAL_IP'];
}
return $ip ? $ip : '127.0.0.1';
}
}