首先是生成签名方法
//生成签名
private function MakeSign($params, $KEY)
{
//签名步骤一:按字典序排序数组参数
ksort($params);
$string = $this->ToUrlParams($params); //参数进行拼接key=value&k=v
//签名步骤二:在string后加入KEY
$string = $string . "&key=" . $KEY;
//签名步骤三:MD5加密
$string = md5($string);
//签名步骤四:所有字符转为大写
$result = strtoupper($string);
return $result;
}
//模拟post提交(调接口用)
模拟post提交
//发送http请求
private function http_request($url, $data = null, $headers = array())
{
$curl = curl_init();
if (count($headers) >= 1) {
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
}
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);
//
$zs1 = APP_PATH . "app/cert/apiclient_cert.pem";
$zs2 = APP_PATH . "app/cert/apiclient_key.pem";
// curl_setopt ( $curl, CURLOPT_SSLCERT, $zs1 );
// curl_setopt ( $curl, CURLOPT_SSLKEY, $zs2 );
//设置证书
//使用证书:cert 与 key 分别属于两个.pem文件
curl_setopt($curl, CURLOPT_SSLCERTTYPE, 'PEM');
curl_setopt($curl, CURLOPT_SSLCERT, $zs1);
curl_setopt($curl, CURLOPT_SSLKEYTYPE, 'PEM');
curl_setopt($curl, CURLOPT_SSLKEY, $zs2);
if (!empty($data)) {
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
}
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($curl);
curl_close($curl);
return $output;
}
xml转换数组
//获取xml里面数据,转换成array
private function xml2array($xml)
{
$p = xml_parser_create();
xml_parse_into_struct($p, $xml, $vals, $index);
xml_parser_free($p);
$data = "";
foreach ($index as $key => $value) {
if ($key == 'xml' || $key == 'XML') continue;
$tag = $vals[$value[0]]['tag'];
$value = $vals[$value[0]]['value'];
$data[$tag] = $value;
}
return $data;
}
然后是统一下单方法
//构建订单/* 首先在服务器端调用微信【统一下单】接口,返回prepay_id和sign签名等信息给前端,前端调用微信支付接口 */
private function construct_order($total_fee, $openid, $order_sn, $order_id)
{
//获取系统的配置
$appid = '';//如果是公众号 就是公众号的appid;小程序就是小程序的appid
$body = '商户号构造订单';
$mch_id = '';
$KEY = '';
$nonce_str = $this->rand_str(12);//随机字符串
$notify_url = config('app_url') . "pay/notify"; //支付完成回调地址url,不能带参数
$out_trade_no = $order_sn;//商户订单号
$spbill_create_ip = $_SERVER['SERVER_ADDR'];
$trade_type = 'JSAPI';//交易类型 默认JSAPI
//这里是按照顺序的 因为下面的签名是按照(字典序)顺序 排序错误 肯定出错
$post['appid'] = $appid;
$post['body'] = $body;
$post['mch_id'] = $mch_id;
$post['nonce_str'] = $nonce_str;//随机字符串
$post['notify_url'] = $notify_url;
$post['openid'] = $openid;
$post['out_trade_no'] = $out_trade_no;
$post['spbill_create_ip'] = $spbill_create_ip;//服务器终端的ip
$post['total_fee'] = intval($total_fee); //总金额 最低为一分钱 必须是整数
$post['trade_type'] = $trade_type;
$sign = $this->MakeSign($post, $KEY); //签名
$this->sign = $sign;
//$post_xml = '<xml><appid>'.$appid.'</appid><body>'.$body.'</body><mch_id>'.$mch_id.'</mch_id><nonce_str>'.$nonce_str.'</nonce_str><notify_url>'.$notify_url.'</notify_url><openid>'.$openid.'</openid><out_trade_no>'.$out_trade_no.'</out_trade_no><spbill_create_ip>'.$spbill_create_ip.'</spbill_create_ip><total_fee>'.$total_fee.'</total_fee><trade_type>'.$trade_type.'</trade_type><sign>'.$sign.'</sign></xml>';
$post_xml = "
<xml>
<appid>$appid</appid>
<body>$body</body>
<mch_id>$mch_id</mch_id>
<nonce_str>$nonce_str</nonce_str>
<notify_url>$notify_url</notify_url>
<openid>$openid</openid>
<out_trade_no>$out_trade_no</out_trade_no>
<spbill_create_ip>$spbill_create_ip</spbill_create_ip>
<total_fee>{$post["total_fee"]}</total_fee>
<trade_type>$trade_type</trade_type>
<sign>$sign</sign>
</xml>";
//统一下单接口prepay_id
$url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
$xml = $this->http_request($url, $post_xml); //POST方式请求http
$array = $this->xml2array($xml); //将【统一下单】api返回xml数据转换成数组,全要大写
$array['my_sign'] = $sign;
$array['post_xml'] = $post_xml;
$array['source_xml'] = $xml;
if ($array['RETURN_CODE'] == 'SUCCESS' && $array['RESULT_CODE'] == 'SUCCESS') {
$time = time();
$tmp = ''; //临时数组用于签名
$tmp['appId'] = $appid;
$tmp['nonceStr'] = $nonce_str;
$tmp['package'] = 'prepay_id=' . $array['PREPAY_ID'];
$tmp['signType'] = 'MD5';
$tmp['timeStamp'] = "$time";
$data['state'] = 1;
$data['timeStamp'] = "$time"; //时间戳
$data['nonceStr'] = $nonce_str; //随机字符串
$data['signType'] = 'MD5'; //签名算法,暂支持 MD5
$data['package'] = 'prepay_id=' . $array['PREPAY_ID']; //统一下单接口返回的 prepay_id 参数值,提交格式如:prepay_id=*
$data['paySign'] = $this->MakeSign($tmp, $KEY); //签名,具体签名方案参见微信公众号支付帮助文档;
$data['prepay_id'] = $array['PREPAY_ID'];
$data['out_trade_no'] = $out_trade_no;
$data['order_id'] = $order_id;
} else {
$data['statusCode'] = USER_ERROR;
$data['statusMsg'] = "请求错误";
$data['data']['RETURN_CODE'] = $array['RETURN_CODE'];
$data['data']['RETURN_MSG'] = $array['RETURN_MSG'];
}
return $data;
}
回调函数
//订单支付回调
function notify()
{
$post = $this->post_data(); //接受POST数据XML个数
$post_data = $this->xml_to_array($post); //微信支付成功,返回回调地址url的数据:XML转数组Array
//输出订单号
$order_sn = $post_data['out_trade_no'];
$key = '';//key
$postSign = $post_data['sign'];
unset($post_data['sign']);
/* 微信官方提醒:
* 商户系统对于支付结果通知的内容一定要做【签名验证】,
* 并校验返回的【订单金额是否与商户侧的订单金额】一致,
* 防止数据泄漏导致出现“假通知”,造成资金损失。
*/
ksort($post_data);// 对数据进行排序
$str = $this->ToUrlParams($post_data);//对数组数据拼接成key=value字符串
$KEY = $key;
$str .= '&key=';
$str .= $KEY;
$user_sign = strtoupper(md5($str)); //再次生成签名,与$postSign比较
if ($post_data['return_code'] == 'SUCCESS') {
//判断证书是否正确
if ($postSign != $user_sign) {
Log::write('签名不匹配');
exit;
}
$order_info = Db::table('cs_hborders')->where('order_sn', $order_sn)->find();
if ($order_info['pay_status'] != 1) {
//支付成功后操作(如推送、更改订单状态等)
}
}
}
echo 'success';
} else {
echo '微信支付失败';
}
}