之前在開發APP中用到了微信支付,因為是第一次用,所以中途也遇到了好多問題,通過查看文檔和搜集資料,終於完成了該功能的實現。在這里簡單分享一下后台php接口的開發實例。
原文地址:代碼匯個人博客 http://www.codehui.net/info/4.html
開發流程
1:用戶在商戶APP中選擇商品,提交訂單,選擇微信支付。
2:商戶后台收到用戶支付單,調用微信支付統一下單接口。參見【統一下單API】。
3:統一下單接口返回正常的prepay_id,再按簽名規范重新生成簽名后,將數據傳輸給APP。參與簽名的字段名為appid,partnerid,prepayid,noncestr,timestamp,package。注意:package的值格式為Sign=WXPay
4:商戶APP調起微信支付。api參見本章節【app端開發步驟說明】
5:商戶后台接收支付通知。api參見【支付結果通知API】
6:商戶后台查詢支付結果。,api參見【查詢訂單API】
開發中
首先呢我們需要拿到三個參數(appid
,mch_id
,key
),這三個參數分別對應的是 在微信開發平台中創建的移動應用appid
,微信支付商戶號
,商戶支付秘鑰
,詳情看參考【支付結果通知API】
然后我們先把統一下單所需要的參數列出來
$request_data = array(
'appid' => C('WX_APPID'), #應用APPID
'mch_id' => C('WX_MCHID'), #商戶號
'trade_type' => 'APP', #支付類型
'nonce_str' => \Org\Util\String::randString(30), #隨機字符串 不長於32位
'body' => '商品名稱', #商品名稱
'out_trade_no' => '12345678912456', #商戶后台訂單號
'total_fee' => '1', #商品價格
'spbill_create_ip' => get_client_ip(), #用戶端實際ip
'notify_url' => 'http://***/app/index.php/Home/Wxpay/wx_notify', #異步通知回調地址
);
這些都是請求參數必填項,其他參數請查看文檔
下來我們就要使用這些參數生成簽名了
$request_data['sign'] = $this -> get_sign($request_data);
我們下來需要把微信請求的數據拼裝成 xml
格式,注意:xml數據要使用<![CDATA[]]>
包括
$xml_data = $this -> set_xmldata($request_data);
打印$xml_data結果如下
<xml>
<appid><![CDATA[wx7ad3cc6c6111111]]></appid>
<mch_id><![CDATA[1494741111]]></mch_id>
<trade_type><![CDATA[APP]]></trade_type>
<nonce_str><![CDATA[WXXWkMDOgLIqhUnITfNrBbJEVGQdRO]]></nonce_str>
<body><![CDATA[u5546u54c1u540du79f0]]></body>
<out_trade_no><![CDATA[12345678912456]]></out_trade_no>
<total_fee><![CDATA[1]]></total_fee>
<spbill_create_ip><![CDATA[1.86.242.193]]></spbill_create_ip>
<notify_url><![CDATA[http://***/app/index.php/Home/Wxpay/wx_notify]]></notify_url>
<sign><![CDATA[EC0BFB3434A72F20C2CA3378BF07264C]]></sign>
</xml>
現在就可以向微信發送請求了
$res = $this -> send_prePaycurl($xml_data);
這是請求的返回值
{
return_code: "SUCCESS", #業務結果 只有這里返回SUCCESS才會有prepay_id
return_msg: "OK", #返回結果描述
appid: "wx7ad3cc6c6111111", #應用APPID
mch_id: "1494741111", #商戶號
nonce_str: "jkh9mmRlmSHBJxO0", #隨機字符串
sign: "AF3B26B1E58591D6565E61DDFBB7837B", #簽名
result_code: "SUCCESS", #也是業務結果
prepay_id: "wx20171226005556c5c65b325a0132782836", #預支付交易會話標識,用於APP請求微信支付調用,有效期兩小時
trade_type: "APP" #支付類型
}
到這里拿到prepay_id還沒完我們還需要對返回的數據進行二次簽名
if($res['return_code'] == 'SUCCESS' && $res['result_code'] == 'SUCCESS'){
$two_data['appid'] = C('WX_APPID'); #APPID
$two_data['partnerid'] = C('WX_MCHID'); #商戶號
$two_data['prepayid'] = $res['prepay_id']; //預支付交易會話標識
$two_data['noncestr'] = \Org\Util\String::randString(30);
$two_data['timestamp'] = time(); #時間戳
$two_data['package'] = "Sign=WXPay"; #固定值
$two_data['sign'] = $this -> get_twosign($two_data); #二次簽名
$this->ajaxReturn(array('code'=>200,'info'=>$two_data));
}else{
$this->ajaxReturn(array('code'=>201,'info'=>$res['err_code_des']));
}
然后就可以在商戶APP端通過prepayid進行支付了
下面我們來列出上面調用的幾個公共方法
//一次簽名的函數
private function get_sign($data){
ksort($data);
$str = '';
foreach ($data as $key => $value) {
$str .= !$str ? $key . '=' . $value : '&' . $key . '=' . $value;
}
$str.='&key='.C('WX_KEY');
$sign = strtoupper(md5($str));
return $sign;
}
//二次簽名的函數
private function get_twosign($data){
$sign_data = array(
"appid"=>$data['appid'],
"partnerid"=>$data['partnerid'],
"prepayid"=>$data['prepayid'],
"noncestr"=>$data['noncestr'],
"timestamp"=>$data['timestamp'],
"package"=>$data['package'],
);
return $this -> get_sign($sign_data);
}
//生成xml格式的函數
private function set_xmldata($data) {
$xmlData = "<xml>";
foreach ($data as $key => $value) {
$xmlData.="<".$key."><![CDATA[".$value."]]></".$key.">";
}
$xmlData = $xmlData."</xml>";
return $xmlData;
}
//通過curl發送數據給微信接口的函數
private function send_prePaycurl($xmlData) {
$url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
$header[] = "Content-type: text/xml";
$curl = curl_init();
curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $xmlData);
$data = curl_exec($curl);
if (curl_errno($curl)) {
print curl_error($curl);
}
curl_close($curl);
return $this -> _xmldataparse($data);
}
//xml數據解析函數
private function _xmldataparse($data){
$msg = array();
$msg = (array)simplexml_load_string($data, 'SimpleXMLElement', LIBXML_NOCDATA);
return $msg;
}
微信回調
支付有了,肯定還得有回調
//微信回調
public function wx_notify(){
//允許從外部加載XML實體(防止XML注入攻擊)
libxml_disable_entity_loader(true);
$postStr = $this -> post_data(); #接收微信返回數據xml格式
$postObj = simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA); #xml格式數據轉換成對象
$arr = $this -> object_toarray($postObj); #對象轉成數組
ksort($arr); # 對數據進行排序
$str = $this -> params_tourl($arr); #對數據拼接成字符串
$user_sign = strtoupper(md5($str)); //把微信返回的數據進行再次簽名
//驗證簽名
if($user_sign == $arr['sign']){
//驗證簽名成功 處理商戶訂單邏輯
//給微信返回接收到數據通知
return '<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
}else{
//簽名驗證失敗 微信會再次訪問回調方法
return '<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
}
}
回調用到的方法如下
// 接收post數據
/*
* 微信是用$GLOBALS['HTTP_RAW_POST_DATA'];這個函數接收post數據的
*/
public function post_data(){
$receipt = $_REQUEST;
if($receipt==null){
$receipt = file_get_contents("php://input");
if($receipt == null){
$receipt = $GLOBALS['HTTP_RAW_POST_DATA'];
}
}
return $receipt;
}
//把對象轉成數組
public function object_toarray($arr) {
if(is_object($arr)) {
$arr = (array)$arr;
} if(is_array($arr)) {
foreach($arr as $key=>$value) {
$arr[$key] = $this->object_toarray($value);
}
}
return $arr;
}
/**
* 格式化參數格式化成url參數
*/
private function params_tourl($arr)
{
$weipay_key = C('WX_KEY');//微信的key,這個是微信支付給你的key,不要瞎填。
$buff = "";
foreach ($arr as $k => $v)
{
if($k != "sign" && $v != "" && !is_array($v)){
$buff .= $k . "=" . $v . "&";
}
}
$buff = trim($buff, "&");
return $buff.'&key='.$weipay_key;
}
總結:首先微信支付的流程比較多,
公眾號
,開放平台
,微信商戶
,配置參數的時候要看仔細,不要后面的坑特別多,因為是第一次寫微信支付,可能會存在部分問題,歡迎大家可以在下面留言反饋。
這是最后完成的功能
下面分享一下全部的代碼
<?php
namespace Home\Controller;
use Think\Controller;
/**
* php開發微信app支付接口
* @global WX_APPID 開放平台->移動應用appid
* @global WX_MCHID 微信支付商戶號
* @global WX_KEY 商戶支付秘鑰
* @author codehi <admin@codehui.net> 2017-12-23
*/
class WxpayController extends Controller
{
/**
* 微信支付統一下單 >>> 生成預支付交易單
*/
public function wx_pay(){
$request_data = array(
'appid' => C('WX_APPID'), #應用APPID
'mch_id' => C('WX_MCHID'), #商戶號
'trade_type' => 'APP', #支付類型
'nonce_str' => \Org\Util\String::randString(30), #隨機字符串 不長於32位
'body' => '商品名稱', #商品名稱
'out_trade_no' => '12345678912456', #商戶后台訂單號
'total_fee' => '1', #商品價格
'spbill_create_ip' => get_client_ip(), #用戶端實際ip
'notify_url' => 'http://shop.lsmrsd.com/app/index.php/Home/Wxpay/wx_notify', #異步通知回調地址
);
// 獲取簽名
$request_data['sign'] = $this -> get_sign($request_data);
// 拼裝數據
$xml_data = $this -> set_xmldata($request_data);
// 發送請求
$res = $this -> send_prePaycurl($xml_data);
$this->ajaxReturn($res);
if($res['return_code'] == 'SUCCESS' && $res['result_code'] == 'SUCCESS'){
$two_data['appid'] = C('WX_APPID'); #APPID
$two_data['partnerid'] = C('WX_MCHID'); #商戶號
$two_data['prepayid'] = $res['prepay_id']; //預支付交易會話標識
$two_data['noncestr'] = \Org\Util\String::randString(30);
$two_data['timestamp'] = time();
$two_data['package'] = "Sign=WXPay";
$two_data['sign'] = $this->get_twosign($two_data);
$this->ajaxReturn(array('code'=>200,'info'=>$two_data));
}else{
$this->ajaxReturn(array('code'=>201,'info'=>$res['err_code_des']));
}
}
//通過curl發送數據給微信接口的函數
private function send_prePaycurl($xmlData) {
$url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
$header[] = "Content-type: text/xml";
$curl = curl_init();
curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $xmlData);
$data = curl_exec($curl);
if (curl_errno($curl)) {
print curl_error($curl);
}
curl_close($curl);
return $this->_xmldataparse($data);
}
//xml數據解析函數
private function _xmldataparse($data){
$msg = array();
$msg = (array)simplexml_load_string($data, 'SimpleXMLElement', LIBXML_NOCDATA);
return $msg;
}
//生成xml格式的函數
private function set_xmldata($data) {
$xmlData = "<xml>";
foreach ($data as $key => $value) {
$xmlData.="<".$key."><![CDATA[".$value."]]></".$key.">";
}
$xmlData = $xmlData."</xml>";
return $xmlData;
}
//一次簽名的函數
private function get_sign($data){
ksort($data);
$str = '';
foreach ($data as $key => $value) {
$str .= !$str ? $key . '=' . $value : '&' . $key . '=' . $value;
}
$str.='&key='.C('WX_KEY');
$sign = strtoupper(md5($str));
return $sign;
}
//二次簽名的函數
private function get_twosign($data){
$sign_data = array(
"appid"=>$data['appid'],
"partnerid"=>$data['partnerid'],
"prepayid"=>$data['prepayid'],
"noncestr"=>$data['noncestr'],
"timestamp"=>$data['timestamp'],
"package"=>$data['package'],
);
return $this->get_sign($sign_data);
}
//微信回調
public function wx_notify(){
//允許從外部加載XML實體(防止XML注入攻擊)
libxml_disable_entity_loader(true);
$postStr = $this -> post_data();//接收post數據
$postObj = simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
$arr = $this -> object_toarray($postObj);//對象轉成數組
ksort($arr);// 對數據進行排序
$str = $this -> params_tourl($arr);//對數據拼接成字符串
$user_sign = strtoupper(md5($str));
if($user_sign == $arr['sign']){//驗證簽名
return '<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
}else{
return '<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
}
}
// 接收post數據
/*
* 微信是用$GLOBALS['HTTP_RAW_POST_DATA'];這個函數接收post數據的
*/
public function post_data(){
$receipt = $_REQUEST;
if($receipt==null){
$receipt = file_get_contents("php://input");
if($receipt == null){
$receipt = $GLOBALS['HTTP_RAW_POST_DATA'];
}
}
return $receipt;
}
//把對象轉成數組
public function object_toarray($arr) {
if(is_object($arr)) {
$arr = (array)$arr;
} if(is_array($arr)) {
foreach($arr as $key=>$value) {
$arr[$key] = $this->object_toarray($value);
}
}
return $arr;
}
/**
* 格式化參數格式化成url參數
*/
private function params_tourl($arr)
{
$weipay_key = C('WX_KEY');//微信的key,這個是微信支付給你的key,不要瞎填。
$buff = "";
foreach ($arr as $k => $v)
{
if($k != "sign" && $v != "" && !is_array($v)){
$buff .= $k . "=" . $v . "&";
}
}
$buff = trim($buff, "&");
return $buff.'&key='.$weipay_key;
}
}