最近做小程序涉及到微信支付,連微信支付都沒有做過的我無從下手,在網上搜索到了幾篇帖子也沒看明白,沒辦法只好照着某一篇(來源:微信小程序中實現微信支付 和 小程序支付,詳細過程)硬着頭皮先寫了,最後經過幾次調試終獲成功,所以我對支付做一個總結,分享出來方便他人。
一,準備小程序id,小程序密鑰,商戶號,商戶密鑰,需要openid(微信登錄後獲得,調用https://api.weixin.qq.com/sns/jscode2session)
二,接口開發
接口有3個:預支付,支付,回調,php代碼在後面
(一)預支付:(1)獲取參數:openid,商品信息,金額;(2)設定回調接口地址,自己生成訂單號,可將訂單號與openid和商品信息一併寫入mysql,等支付完成後在修改狀態;(3)把所有字段彙集起來做簽名一起提交給統一下單地址(https://api.mch.weixin.qq.com/pay/unifiedorder),成功的話會返回prepay_id,將prepay_id輸出(給 支付接口)。
統一下單需要的參數:
<xml>
<appid>小程序id</appid>
<body>商品信息,比如商品名稱</body>
<mch_id>商戶號</mch_id>
<nonce_str>隨機字符串,僅用於加密</nonce_str>
<notify_url>回調接口地址</notify_url>
<openid>openid,即用戶id</openid>
<out_trade_no>訂單號,自己生成</out_trade_no>
<spbill_create_ip>服務器ip</spbill_create_ip>
<total_fee>金額,單位分</total_fee>
<trade_type>交易類型,默認JSAPI</trade_type>
<sign>以上所有字段的簽名</sign>
</xml>
統一下單返回數據:
<xml><return_code>狀態碼,SUCCESS|FAIL</return_code>
<return_msg>狀態信息</return_msg>
<appid>小程序id</appid>
<mch_id>商戶號</mch_id>
<nonce_str>微信生成的隨機字符串</nonce_str>
<sign>簽名</sign>
<result_code>同return_code?</result_code>
<prepay_id>預支付id,重要</prepay_id>
<trade_type>交易類型,默認JSAPI</trade_type>
</xml>
(二)支付:支付接口很簡單,獲取prepay_id後計算簽名返回即可,小程序拿到簽名後會發起真正的支付。此時會提醒用戶輸入密碼,如下圖:
(三)回調:當支付成功時,微信會調用此接口。可以實現自己業務邏輯,比如訂單完成後修改狀態。
接口輸入xml數據:
<xml><appid>小程序id</appid>
<bank_type>銀行類型,CFT</bank_type>
<cash_fee>金額</cash_fee>
<fee_type>金額類型,CNY</fee_type>
<is_subscribe><![CDATA[N]]></is_subscribe>
<mch_id>商戶號</mch_id>
<nonce_str>隨機字符串,自己生成發給微信的</nonce_str>
<openid>openid</openid>
<out_trade_no>訂單號</out_trade_no>
<result_code>狀態碼,SUCCESS|FAIL</result_code>
<return_code>狀態碼,SUCCESS|FAIL</return_code>
<sign>簽名</sign>
<time_end>支付完成時間</time_end>
<total_fee>金額</total_fee>
<trade_type><![CDATA[JSAPI]]></trade_type>
<transaction_id>微信交易號</transaction_id>
</xml>
驗證簽名正確後,處理自己業務流程。
三,小程序端調用流程
小程序流程不懂,流程可參考文檔 微信小程序中實現微信支付 在提交prepay_id時需要從字符串中截取出來。
四,php代碼
(1)預支付prepay.php:
/**
* 預支付請求接口(POST)
* @param string $openid openid
* @param string $body 商品簡單描述
* @param string $total_fee 金額,單位分
* @return json的數據
*/
require_once("config.php"); // config.php記錄了小程序id,密鑰,商戶號及密鑰
require_once("utils.php"); // 一些功能函數
$openid = Request('openid');
$body = Request('body'); // 商品信息,比如title
$total_fee = RequestInt('total_fee', 1); // 金額
flog("prepay openid:$openid total_fee:$total_fee ");
$nonce_str = nonce_str(); //隨機字符串
$notify_url = "https://你的域名/notify.php"; // 回調地址
$out_trade_no = trade_no(); //商戶訂單號,隨機生成
$spbill_create_ip = '你的ip'; // 服務器ip
$trade_type = 'JSAPI';//交易類型 默認
// 訂單發起時,可將信息存入mysql
$sql = "insert into table";
...
flog("prepay:$sql");
//這裏是按照順序的 因爲下面的簽名是按照順序 排序錯誤 肯定出錯
$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'] = $total_fee;
$post['trade_type'] = $trade_type;
$sign = sign($post);//簽名
$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> ';
/*
$arr = xml($post_xml);//全要大寫
print_r($arr);
exit();
*/
//統一接口prepay_id
$url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
$xml = http_request($url,$post_xml);
flog("prepay: get xml: $xml");
$arr = xml2arr($xml);
if ($arr['return_code'] == 'SUCCESS' && $arr['return_code'] == 'SUCCESS') {
$time = time();
$tmp=array();//臨時數組用於簽名
$tmp['appId'] = $appid;
$tmp['nonceStr'] = $nonce_str;
$tmp['package'] = 'prepay_id='.$arr['prepay_id'];
$tmp['signType'] = 'MD5';
$tmp['timeStamp'] = "$time";
$data['error'] = 0;
$data['timeStamp'] = "$time";//時間戳
$data['nonceStr'] = $nonce_str;//隨機字符串
$data['signType'] = 'MD5';//簽名算法,暫支持 MD5
$data['package'] = 'prepay_id='.$arr['prepay_id'];//統一下單接口返回的 prepay_id 參數值,提交格式如:prepay_id=*
$data['paySign'] = sign($tmp);//簽名,具體簽名方案參見微信公衆號支付幫助文檔;
$data['out_trade_no'] = $out_trade_no;
} else {
$data['error'] = 1;
$data['msg'] = $arr['return_msg'];
$data['wxcode'] = $arr['return_code'];
}
flog("prepay ok:".json_encode($data));
echo json_encode($data); //小程序需要的數據 返回前端
(2)支付pay.php
/**
* 進行支付接口(POST)
* @param string $prepay_id 預支付ID(調用prepay()方法之後的返回數據中獲取)
* @return json的數據
*/
require_once("config.php");
require_once("utils.php");
$prepay_id = Request('prepay_id');
$data = array(
'appId' => $appid,
'nonceStr' => nonce_str(),
'package' => 'prepay_id='.$prepay_id,
'signType' => 'MD5',
'timeStamp' => time()
);
$data['paySign'] = sign($data);
flog("pay:".json_encode($data));
echo json_encode($data);
(3)回調notify.php
require_once("config.php");
require_once("utils.php");
$xml = file_get_contents("php://input"); // 獲取輸入
//$xml = $GLOBALS['HTTP_RAW_POST_DATA'];
flog("notify: get xml:$xml");
//將服務器返回的XML數據轉化爲數組
$data = xml2arr($xml);
// 保存微信服務器返回的簽名sign
$data_sign = $data['sign'];
// sign不參與簽名算法
unset($data['sign']);
$sign = sign($data);
flog("notify sign:$sign");
// 判斷簽名是否正確 判斷支付狀態
if (($sign===$data_sign) && ($data['return_code']=='SUCCESS') && ($data['return_code']=='SUCCESS') ) {
$result = $data;
//獲取服務器返回的數據
$order_sn = $data['out_trade_no']; //訂單單號
$openid = $data['openid']; //付款人openID
$total_fee = $data['total_fee']; //付款金額
$transaction_id = $data['transaction_id']; //微信支付流水號
//可根據訂單號更新數據庫
$sql = "update table set where";
...
// TODO: 支付完成後,業務流程需處理
} else {
$result = false;
}
// 返回狀態給微信服務器
if ($result) {
$str='<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
} else {
$str='<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[簽名失敗]]></return_msg></xml>';
}
flog("notify result:$str");
echo $str; // 返回給微信
//return $result; // 這一步沒必要
(4)utils.php
// Author: [email protected] (Kunlong She)
// Created Time:2018-08-20 11:29:24
function RequestInt($str, $default = 0) {
if (isset($_REQUEST[$str])) {
return intval($_REQUEST[$str]);
} else {
return $default;
}
}
function Request($str) {
if (isset($_REQUEST[$str])) {
$rst = str_replace("'", "''", $_REQUEST[$str]);
$rst = str_replace("\\", "\\\\", $rst);
return $rst;
} else {
return "";
}
}
function Str($str) {
if (empty($str)) {
return "";
} else {
return $str;
}
}
function flog($str) {
$fp = fopen("/tmp/a.log", "a+");
$date = date("Y-m-d H:i:s");
fprintf($fp, "$date $str\n");
fclose($fp);
}
// 隨機字符串,僅用於簽名
function nonce_str(){
$result = '';
$str = 'QWERTYUIOPASDFGHJKLZXVBNMqwertyuioplkjhgfdsamnbvcxz';
for ($i=0;$i<32;$i++){
$result .= $str[rand(0,48)];
}
return $result;
}
// 訂單號 隨機字符串
// 隨便怎麼實現,儘量保證唯一,可以根據它來確定訂單狀態
function trade_no() {
$result = date("YmdHis", time());
$str = "0123456789";
for ($i=0;$i<4;$i++) {
$result .= $str[rand(0,9)];
}
return $result;
}
//curl
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);
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;
}
//簽名函數,未排序,傳入數據需要對key按字母序排好,否則簽名不正確
function sign($data){
$stringA = '';
foreach ($data as $key=>$value){
if(!$value) continue;
if($stringA) $stringA .= '&'.$key."=".$value;
else $stringA = $key."=".$value;
}
$wx_key = '你的key';//申請支付後有給予一個商戶賬號和密碼,登陸後自己設置key
$stringSignTemp = $stringA.'&key='.$wx_key;//申請支付後有給予一個商戶賬號和密碼,登陸後自己設置key
return strtoupper(md5($stringSignTemp));
}
//獲取xml,注意key統一轉成了小寫
function xml2arr($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[strtolower($tag)] = $value;
}
return $data;
}
五,一些問題
(1)代碼根據網上提供的改編而來,如果需要thinkphp上運行,要麼csdn上找一份源碼,要麼依照這個改寫
(2)回調函數是給微信調用的,裏面可以不寫return(thinkphp中有return),簽名驗證成功後可以寫業務邏輯,可通過訂單號處理,預支付寫入訂單號,回調修改訂單號完成訂單及後續處理。
(3)小程序id修改後,小程序與接口都需要修改,數據庫中的openid需要重新登錄,否則appid與openid不匹配。
(4)簽名函數需要商戶key
。。。