上一篇說到支付寶APP支付,說到微信APP支付相對複雜一點,複雜在於微信支付參數的兩次加密返回支付參數;php
至於其餘和支付寶處理流程都相同nginx
流程:客戶端提供數據 -> 服務端處理生成支付參數返回給客戶端調起支付 -> 支付成功 -> 微信回調結果 -> 接受回調修改訂單狀態算法
微信官方文檔也說的比較清楚,微信APP開發者文檔sql
首先,新建一個微信支付類,命名爲appWxPay_class.php ,定義一些支付常量json
const appid ="";
const mch_id ="";
const key ="";
const trade_type = "APP";
const notify_url = "";api
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];
}app
//生成隨機數並返回
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;
}
這裏附上微信官方給的一個簽名校驗工具:簽名校驗curl
//爲微信官方返回的數據類型作準備
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; } } } }