上一篇说到支付宝APP支付,说到微信APP支付相对复杂一点,复杂在于微信支付参数的两次加密返回支付参数;
至于其他和支付宝处理流程都相同
流程:客户端提供数据 -> 服务端处理生成支付参数返回给客户端调起支付 -> 支付成功 -> 微信回调结果 -> 接受回调修改订单状态
微信官方文档也说的比较清楚,微信APP开发者文档
首先,新建一个微信支付类,命名为appWxPay_class.php ,定义一些支付常量
const appid ="";
const mch_id ="";
const key ="";
const trade_type = "APP";
const notify_url = "";
post方法用于请求
//建立请求
public function http_post($url='',$post_data=array(),$header=array(),$timeout=30) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 跳过证书检查
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // 从证书中检查SSL加密算法是否存在
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
$response = curl_exec($ch);
curl_close($ch);
return $response;
}
获取客户端ip
//获取客户端ip
public function get_client_ip($type = 0) {
$type = $type ? 1 : 0;
static $ip = NULL;
if ($ip !== NULL) return $ip[$type];
if($_SERVER['HTTP_X_REAL_IP']){//nginx 代理模式下,获取客户端真实IP
$ip=$_SERVER['HTTP_X_REAL_IP'];
}elseif (isset($_SERVER['HTTP_CLIENT_IP'])) {//客户端的ip
$ip = $_SERVER['HTTP_CLIENT_IP'];
}elseif (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {//浏览当前页面的用户计算机的网关
$arr = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
$pos = array_search('unknown',$arr);
if(false !== $pos) unset($arr[$pos]);
$ip = trim($arr[0]);
}elseif (isset($_SERVER['REMOTE_ADDR'])) {
$ip = $_SERVER['REMOTE_ADDR'];//浏览当前页面的用户计算机的ip地址
}else{
$ip=$_SERVER['REMOTE_ADDR'];
}
// IP地址合法验证
$long = sprintf("%u",ip2long($ip));
$ip = $long ? array($ip, $long) : array('0.0.0.0', 0);
return $ip[$type];
}
//生成随机数并返回
public function getNonceStr() {
$code = "";
for ($i=0; $i > 10; $i++) {
$code .= mt_rand(1000); //获取随机数
}
$nonceStrTemp = md5($code);
$nonce_str = mb_substr($nonceStrTemp, 5,37); //MD5加密后截取32位字符
return $nonce_str;
}
/**
* 获取参数签名;
* @param Array 要传递的参数数组
* @return String 通过计算得到的签名;
*/
private function getSign($params) {
ksort($params); //将参数数组按照参数名ASCII码从小到大排序
foreach ($params as $key => $item) {
if (!empty($item)) { //剔除参数值为空的参数
$newArr[] = $key.'='.$item; // 整合新的参数数组
}
}
$stringA = implode("&", $newArr); //使用 & 符号连接参数
$stringSignTemp = $stringA."&key=".self::key; //拼接key
$stringSignTemp = MD5($stringSignTemp); //将字符串进行MD5加密
$sign = strtoupper($stringSignTemp); //将所有字符转换为大写
return $sign;
}
这里附上微信官方给的一个签名校验工具:签名校验
//为微信官方返回的数据类型做准备
public function arrToString($params){
ksort($params); //将参数数组按照参数名ASCII码从小到大排序
foreach ($params as $key => $item) {
if (!empty($item)) { //剔除参数值为空的参数
$newArr[] = $key.'='.$item; // 整合新的参数数组
}
}
$stringA = implode("&", $newArr);
return $stringA;
}
/**
* 拼装请求的数据
* @return String 拼装完成的数据
*/
public function setSendData($data=array()) {
$sTpl = "<xml>
<appid><![CDATA[%s]]></appid>
<body><![CDATA[%s]]></body>
<mch_id><![CDATA[%s]]></mch_id>
<nonce_str><![CDATA[%s]]></nonce_str>
<notify_url><![CDATA[%s]]></notify_url>
<out_trade_no><![CDATA[%s]]></out_trade_no>
<spbill_create_ip><![CDATA[%s]]></spbill_create_ip>
<total_fee><![CDATA[%d]]></total_fee>
<trade_type><![CDATA[%s]]></trade_type>
<sign><![CDATA[%s]]></sign>
</xml>"; //xml数据模板
$nonce_str = self::getNonceStr(); //调用随机字符串生成方法获取随机字符串
$data['appid'] = self::appid;
$data['mch_id'] = self::mch_id;
$data['nonce_str'] = $nonce_str;
$data['spbill_create_ip'] = self::get_client_ip();
$data['notify_url'] = self::notify_url;
$data['trade_type'] = self::trade_type; //将参与签名的数据保存到数组
// 注意:以上几个参数是追加到$data中的,$data中应该同时包含开发文档中要求必填的剔除sign以外的所有数据
$sign = self::getSign($data); //获取签名
$data = sprintf($sTpl,self::appid,$data['body'],self::mch_id,$nonce_str,self::notify_url,$data['out_trade_no'],$data['spbill_create_ip'],$data['total_fee'],self::trade_type,$sign);
//生成xml数据格式
return $data;
}
//获取支付参数
public function sendRequest($data=array()) {
$post_data = self::setSendData($data); //获取要发送的数据
$url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
$content = self::http_post($url,$post_data);
// $objectxml = (array)simplexml_load_string($content,'SimpleXMLElement',LIBXML_NOCDATA); //将微信返回的XML 转换成数组
$objectxml = self::xmlToObject($content);
return $objectxml; //返回请求到的数据
}
public function setNotifyUrl($url) {
if (is_string($url)) {
$this->notify_url = $url;
}
}
/**
* 获取客户端支付信息
* @param Array $data 参与签名的信息数组
* @return String 签名字符串
*/
public function getClientPay($data) {
$sign = self::getSign($data); // 生成签名并返回
return $sign;
}
//生成第二次签名,返回给客户端
public function secondRequest($firstObj){
if ($firstObj->return_code == 'FAIL') {
return $firstObj->return_msg; // 如果微信返回错误码为FAIL,则代表请求失败,返回失败信息;
} else {
//如果上一次请求成功,那么我们将返回的数据重新拼装,进行第二次签名
// $resignData = array(
// 'appid' => $firstObj->appid,
// 'partnerId' => $firstObj->mch_id,
// 'prepayId' => $firstObj->prepay_id,
// 'nonceStr' => $firstObj->nonce_str,
// 'timeStamp' => time(),
// 'package' => 'Sign=WXPay'
// );
$resignData = array(
'appid' => $firstObj->appid,
'partnerid' => $firstObj->mch_id,
'prepayid' => $firstObj->prepay_id,
'noncestr' => $firstObj->nonce_str,
'timestamp' => time(),
'package' => 'Sign=WXPay'
);
//二次签名;
$sign = self::getClientPay($resignData);
$resignData['sign'] = $sign;
// print_r($resignData);
return $resignData;
}
}
//xml格式转object
public function xmlToObject($xmlStr) {
if (!is_string($xmlStr) || empty($xmlStr)) {
return false;
}
// 由于解析xml的时候,即使被解析的变量为空,依然不会报错,会返回一个空的对象,所以,我们这里做了处理,当被解析的变量不是字符串,或者该变量为空,直接返回false
$postObj = simplexml_load_string($xmlStr, 'SimpleXMLElement', LIBXML_NOCDATA);
$postObj = json_decode(json_encode($postObj));
//将xml数据转换成对象返回
return $postObj;
}
//获取回调参数
public function getNotifyData() {
$postXml = $GLOBALS["HTTP_RAW_POST_DATA"]; // 接受通知参数;
if (empty($postXml)) {
return false;
}
$postObj = self::xmlToObject($postXml); // 调用解析方法,将xml数据解析成对象
if ($postObj === false) {
return false;
}
if (!empty($postObj->return_code)) {
if ($postObj->return_code == 'FAIL') {
return false;
}
}
return $postObj; // 返回结果对象;
}
/**
* 查询订单状态
* @param Curl $curl 工具类
* @param string $out_trade_no 订单号
* @return xml 订单查询结果
*/
public function queryOrder($out_trade_no) {
$nonce_str = self::getNonceStr();
$data = array(
'appid' => self::appid,
'mch_id' => self::mch_id,
'out_trade_no' => $out_trade_no,
'nonce_str' => $nonce_str,
);
$sign = self::getSign($data);
$xml_data = '<xml>
<appid>%s</appid>
<mch_id>%s</mch_id>
<nonce_str>%s</nonce_str>
<out_trade_no>%s</out_trade_no>
<sign>%s</sign>
</xml>';
$xml_data = sprintf($xml_data,self::appid,self::mch_id,$nonce_str,$out_trade_no,$sign);
$url = "https://api.mch.weixin.qq.com/pay/orderquery";
$content = self::http_post($url,$xml_data);
return $content;
}
至此,准备工作结束,下面来调用了
//为客户端支付准备
$data = array(
"body"=> "商品购买", //商品信息
"total_fee"=> $goodsResult['final_sum']*100, //支付价格
"out_trade_no"=>$order_id, //自己的订单id,支付成功回调修改订单状态
);
//第一次加密
$objectxml = appWxPay_class::sendRequest($data);
//第二次加密
$wxPayArr = appWxPay_class::secondRequest($objectxml);
//返回给客户端的支付参数,这里我转成了用&拼接,和支付宝一样的,微信默认是数组
$this->wxPayString = appWxPay_class::arrToString($wxPayArr);
支付成功接受微信回调参数,修改订单操作
//微信app支付回调
function appWxPayStatu(){
$postXml = file_get_contents("php://input"); // 接受通知参数;
$postObj = appWxPay_class::xmlToObject($postXml); // 调用解析方法,将xml数据解析成对象
//追加日志
file_put_contents(dirname(__file__)."/log/wxPay.log",$postXml.PHP_EOL,FILE_APPEND);
//确认支付成功
if ($postObj->return_code=="SUCCESS" && $postObj->result_code=="SUCCESS") {
//修改支付订单状态
$order_id = $postObj->out_trade_no;
$orderGoodsDB = new IModel('order_goods');
$result = $orderGoodsDB->query("pay_status = 0 and order_id = ".$order_id);
//是否存在该订单
if ($result) {
$sqlData = array(
"pay_status" => 1,
"pay_time" => ITime::getDateTime(),
"pay_type" => 6,
);
$orderGoodsDB->setData($sqlData);
$res = $orderGoodsDB->update('order_id = '.$order_id);
//修改成功
if ($res) {
//添加交易记录
$model = new IModel("niuScore_trans");
$arr=array(
"user_id" => $orderArr["user_id"],
"reduce" => $obj->total_fee,
"detail" => "微信支付",
"remain" => 0,
"type" => "3",
"trans_time" => ITime::getDateTime(),
);
$model->setData($arr);
$model->add();
$reply = "<xml>
<return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
</xml>";
echo $reply; // 向微信后台返回结果。
exit;
}
}
}
}