微信支付有兩種使用場景,一種是可以在微信之外的瀏覽器(如UC瀏覽器、手機自帶瀏覽器等)中使用,在微信外部喚醒微信進行支付;還有一種是在微信自帶內置瀏覽器中使用,比如微信公衆號裏邊的支付、給客戶在微信上發了一個支付鏈接等這類使用場景,這裏主要說的是後一種“JSAPI支付”(官方文檔地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_1),下面的內容默認你已經有商戶平臺和微信公衆號了。
JSAPI支付原理
直接調用微信的內置函數,就能直接喚起微信支付,難點在於微信支付需要的參數的獲取,下面是官方文檔,我們需要的就是這6個參數(這6個參數的拼寫是大小寫敏感的):
appId:就是微信公衆號的appid
timeStamp:時間戳(秒)
nonceStr:隨機串,這個隨意,隨機一個字符串就行了
package:這個裏邊存儲的是微信支付訂單的訂單號,這是一個難點,也是這6個參數裏邊唯一一個需要從微信官方獲取的參數,這個參數的值是"prepay_id=u802345jgfjsdfgsdg",是一個鍵值對,其中“u802345jgfjsdfgsdg888”是訂單號,這個是微信統一下單接口返回的,統一下單接口在具體實現流程裏邊說。
signType:參數簽名方式,就用MD5吧,與你進行簽名運算的參數保持一致
paySign:參數簽名,前面五個參數和值先排序(參數名按ASCII碼從小到大),然後進行拼接,組成的字符串進行MD5運算或者HMAC-SHA256運算的結果(微信參數簽名算法文檔:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3)
function onBridgeReady(){
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId":"wx24******", //公衆號名稱,由商戶傳入
"timeStamp":"1395712654", //時間戳,自1970年以來的秒數
"nonceStr":"e61463f8efa94090b1f366cccfbbb444", //隨機串
"package":"prepay_id=u802345jgfjsdfgsd",
"signType":"MD5", //微信簽名方式:
"paySign":"70EA570631E4BB79628FBCA90534C63FF7FADD89" //微信簽名
},
function(res){
if(res.err_msg == "get_brand_wcpay_request:ok" ){
// 使用以上方式判斷前端返回,微信團隊鄭重提示:
//res.err_msg將在用戶支付成功後返回ok,但並不保證它絕對可靠。
}
});
}
if (typeof WeixinJSBridge == "undefined"){
if( document.addEventListener ){
document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
}else if (document.attachEvent){
document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
}
}else{
onBridgeReady();
}
JSAPI開發流程
1、商戶平臺和微信公衆號的設置
1.1商戶平臺(pay.weixin.qq.com)設置JSAPI支付支付目錄
即調用上面內置js函數的頁面目錄,設置路徑:商戶平臺-->產品中心-->開發配置。比如調用JSAPI支付的頁面路徑是www.abc.com/a/b/c/pay.html,你要設置的支付授權目錄是www.abc.com/a/b/c/,設置www.abc.com/a/是錯誤的,即頁面地址裏邊最後一個反斜槓之前的內容。
1.2在微信公衆號(mp.weixin.qq.com)裏邊配置能獲取用戶openid的域名
jsapi支付需要獲取用戶的openid,需要在微信統一下單接口中使用,設置路徑:公衆號設置-->功能設置-->網頁授權域名。用哪個域名獲取的openid就用設置哪個域名。
2、獲取用戶的openid
大致流程是打開特定格式的url,微信回調後會在頁面上添加一個code參數,然後用code參數獲取openid。scope可以使用snsapi_base模式,這種不用用戶手動授權,頁面跳轉無感知,缺點是隻能獲取到openid,不過用戶其他信息此處也不用獲取。程序上此處不再實現,這也是微信開發的基礎操作了(官方文檔:https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html)。
3、請求微信統一下單接口
封裝商戶id、公衆號id、訂單信息等數據,然後調用統一下單接口,注意在JSAPI支付中openid是必填的,統一接口的參數最好用SortedDictionary,可以自動對參數按照參數名進行排序,後續簽名方便,統一下單接口方法如下(官方文檔:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1)
string key = "123123在你的商戶平臺上設置";
SortedDictionary<string, object> strdata = new SortedDictionary<string, object>();
strdata.Add("appid", appid);//微信公衆賬號ID
strdata.Add("mch_id", mch_id);//商戶平臺上的商戶號
strdata.Add("nonce_str", nonce_str);//隨機字符串
strdata.Add("body", body);//商品描述,支付的時候顯示
strdata.Add("out_trade_no", out_trade_no);//訂單號,自己生成,可同時在數據庫添加訂單數據
strdata.Add("total_fee", total_fee);//總金額,單位是分,如果是元則換算成分(乘100)
strdata.Add("spbill_create_ip", spbill_create_ip);//終端ip
strdata.Add("notify_url", notify_url);//微信異步通知的url鏈接
strdata.Add("trade_type", "JSAPI");//jsapi交易類型
strdata.Add("product_id", product_id);//商品ID,trade_type=NATIVE時的必填參數
strdata.Add("openid", openid);//上一步獲取到的用戶的openid,trade_type=JSAPI時的必填參數
strdata.Add("sign_type", "MD5");//參數的簽名方法
string strparam = ToUrl(strdata, key);//拼接參數成字符串
string sign = GetWxSign(strparam);//對字典數據進行MD5簽名
strdata.Add("sign", sign);//簽名
string retData = HttpPost("https://api.mch.weixin.qq.com/pay/unifiedorder", parseXML(strdata));
輔助函數:
/// <summary>
/// 拼接字典的參數和數值
/// </summary>
/// <param name="m_values"></param>
/// <param name="key"></param>
/// <returns></returns>
public string ToUrl(SortedDictionary<string, object> m_values, string key)
{
string buff = "";
foreach (KeyValuePair<string, object> pair in m_values)
{
if (pair.Value.ToString() != "")
{
buff += pair.Key + "=" + pair.Value + "&";
}
}
buff += "key=" + key;
return buff;
}
/// <summary>
/// 獲取變量簽名
/// </summary>
/// <param name="data">參數按英文字母排序後的字符串</param>
/// <returns></returns>
public string GetWxSign(string data)
{
//MD5加密
var md5 = MD5.Create();
var bs = md5.ComputeHash(Encoding.UTF8.GetBytes(data));
var sb = new StringBuilder();
foreach (byte b in bs)
{
sb.Append(b.ToString("x2"));
}
string sign = sb.ToString().ToUpper();
return sign;
}
/// <summary>
/// post 參數 轉換成xml
/// </summary>
/// <param name="parameters"></param>
/// <returns></returns>
public string parseXML(SortedDictionary<string, object> parameters)
{
StringBuilder sb = new StringBuilder();
sb.Append("<xml>");
foreach (string k in parameters.Keys)
{
string v = (string)parameters[k];
if (Regex.IsMatch(v, @"^[0-9.]$"))
{
sb.Append("<" + k + ">" + v + "</" + k + ">");
}
else
{
sb.Append("<" + k + "><![CDATA[" + v + "]]></" + k + ">");
}
}
sb.Append("</xml>");
return sb.ToString();
}
/// <summary>
/// HTTP POST方式請求數據
/// </summary>
/// <param name="url">URL.</param>
/// <param name="param">POST的數據</param>
/// <returns></returns>
public string HttpPost(string url, string param)
{
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.Accept = "*/*";
request.Timeout = 15000;
request.AllowAutoRedirect = false;
StreamWriter requestStream = null;
WebResponse response = null;
string responseStr = null;
try
{
requestStream = new StreamWriter(request.GetRequestStream(), Encoding.UTF8);
requestStream.Write(param);
requestStream.Close();
response = request.GetResponse();
if (response != null)
{
StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8);
responseStr = reader.ReadToEnd();
reader.Close();
}
}
catch (Exception ex)
{
return ex.Message;
}
finally
{
request = null;
requestStream = null;
response = null;
}
return responseStr;
}
到這裏,我們終於獲取到了從返回的數據中可以獲取到prepay_id(預支付交易會話標識,也可以理解成微信的預支付訂單id),也就湊齊了JSAPI的6個參數,這6個參數裏邊還有個小問題,就是paySign是另外5個參數的簽名運算,這裏還在後端進行吧,還是使用MD5。
4、封裝JSAPI所需的6個參數
注意預支付訂單參數的拼接方式,另外這是後端代碼,最終的結果是一個字符串,js調用這個參數的時候注意將字符串轉化爲json格式,轉化方法不再贅述。
SortedDictionary<string, object> m_values = new SortedDictionary<string, object>();
m_values.Add("appId", appId);//微信公衆號的appid
m_values.Add("timeStamp", timeStamp);//時間戳
m_values.Add("nonceStr", nonce_str);//隨機字符串
m_values.Add("package", "prepay_id=" +prepay_id);//預支付訂單,注意參數的拼接
m_values.Add("signType", "MD5");//簽名算法
string wxdataStr = ToUrl(m_values, key);//拼接參數字符串
string paysign = GetWxSign(wxdataStr);//計算參數字符串的MD簽名
m_values.Add("paySign", paysign);
string json = JsonConvert.SerializeObject(m_values);//轉化成json格式
5、前端調取微信內置方法
本人第4步的實現是通過異步調取的,前端JS實現如下:
var wxData;
$.ajax({
type: "post",
async: false,
url: '/ashx/WeiXinPay',
data: { "method": "wxpaycreate" },
success: function (data) {
if (data && data != "") {
wxData = JSON.parse(data);
console.log(wxData);
var agent = navigator.userAgent.toLowerCase();
var indexStart = agent.indexOf("micromessenger/");
var version = agent.substr(indexStart + 1, 1);
if (parseInt(version) < 5) {
alert('您的微信版本低於5.0,無法使用微信支付!');
return false;
}
if (typeof WeixinJSBridge == "undefined") {
if (document.addEventListener) {
document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
} else if (document.attachEvent) {
document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
}
} else {
onBridgeReady();
}
}
}
});
function onBridgeReady() {
WeixinJSBridge.invoke(
'getBrandWCPayRequest', wxData,
function (res) {
if (res.err_msg == "get_brand_wcpay_request:ok") {
// 使用以上方式判斷前端返回,微信團隊鄭重提示:
//res.err_msg將在用戶支付成功後返回ok,但並不保證它絕對可靠。
alert('支付成功,返回訂單列表!');
window.location.href = "onlinepay.html?forumid=" + localStorage.getItem("forumid") + "&companyid=" + localStorage.getItem("companyid") + "&ispay=1";
} else if (res.err_msg == 'get_brand_wcpay_request:cancel') {
alert('取消支付!');
} else {
alert("支付失敗!");
}
});
}
到這裏如果提示支付成功,則表示錢已經到你的微信賬戶了,但還少一步。目前只是前端提示支付成功,數據庫裏邊的訂單表中,訂單的支付狀態還沒變,這就要用到統一下單接口裏邊的notify_url參數了,這個你設置的接收微信的異步通知接口,如果支付成功,微信會請求你這個接口,下面就是最後一步,修改訂單的支付狀態。
7、微信請求異步接口,修改訂單裝
notify_url頁面接收到的數據的處理流程
// 接收從微信後臺POST過來的數據
System.IO.Stream s = Request.InputStream;
StringBuilder builder = new StringBuilder();
int count = 0;
byte[] buffer = new byte[1024];
while ((count = s.Read(buffer, 0, 1024)) > 0)
{
builder.Append(Encoding.UTF8.GetString(buffer, 0, count));
}
s.Flush();
s.Close();
s.Dispose();
if (builder.ToString().Length > 0)
{
XmlDocument xmldoc = new XmlDocument();
xmldoc.LoadXml(builder.ToString());//返回的數據轉換成XML對象
string result_code = xmldoc.SelectSingleNode("xml").SelectSingleNode("result_code").InnerText;
if (result_code == "SUCCESS")
{
//支付成功,根據微信返回的訂單號修改數據庫的訂單狀態
string trade_no= xmldoc.SelectSingleNode("xml").SelectSingleNode("out_trade_no").InnerText;//統一下單接口中你設置的訂單號
string wxorderid = xmldoc.SelectSingleNode("xml").SelectSingleNode("transaction_id").InnerText;//微信支付訂單的訂單號
}
else
{
//支付失敗,做好支付失敗的日誌記錄
}
}
else
{
//數據異常,添加日誌記錄
}
其他注意事項
其實微信支付這塊,比較麻煩的還是參數傳遞、校驗這塊,微信的“接口簽名校驗工具”(https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=20_1)是個好東西。檢查下必填項是否全部填寫,再用工具校驗下你生成的數據跟校驗工具生成的數據是否一致,參數沒問題,一般也就沒有其他的問題了