微信小程式支付開發筆記2--生成簽名-統一下單-二次簽名
使用者選擇商品發起購買請求,後端接收到請求後,先生成本地訂單,以得到一個本地訂單的商戶訂單號。同時從表中讀取該使用者的openID。
我是用一個物件pay來存統一下單需要傳的引數,pay的欄位如下(部分引數並非必填引數,可傳可不傳):
注:notify_url是必填引數,是微信支付成功後需要呼叫的回撥方法,系統支付成功需要做的業務處理都是在這方法裡面執行的。
進行統一下單之前,我們需要根據請求引數進行第一次簽名,簽名演算法可以參考微信支付開發文件,這裡提幾點注意事項:
1、訂單金額需要轉換成以分為單位;
2、引數區分大小寫,且值為空的引數不參與簽名
3、引數需按ASCII碼從小到大排序
簽名方法舉例:
/**
* 生成sign用於請求微信
* @param key祕鑰
* @param parameters引數map
* @return
*/
publicstatic StringcreateSign(Stringkey,Map<Object, Object>parameters) {
StringBuffersb =new StringBuffer();
if (!(parametersinstanceof SortedMap<?,?>)) {
parameters = new
}
Set<?>es =parameters.entrySet();//所有參與傳參的引數按照accsii排序(升序),sign引數不參與簽名
Iterator<?>it =es.iterator();
while (it.hasNext()) {
Map.Entryentry= (Map.Entry)it.next();
Stringk = (String)entry.getKey();
Objectv
if (null !=v && !"".equals(v) && !"sign".equals(k)
&&!"key".equals(k)) {
sb.append(k +"=" +v +"&");
}
}
sb.append("key=" +key);
Stringsign=md5Util.getMD5ofStr(sb.toString()).toUpperCase();
returnsign;
}
簽名成功後,得到sign並賦值pay.setSign(sign) ;接下來就是將pay物件轉換成xml,呼叫統一下單介面進行統一支付,並將統一支付返回的xml轉換成bean,這裡使用的是WechatPayResult物件,WechatPayResult的欄位如下圖;
統一下單成功會返回微信預支付訂單號prepay_id,我們需要根據這個prepay_id進行二次簽名,二次簽名所用引數如下(不包括paySign):
其中,預支付訂單號prepay_id儲存在package欄位中。
二次簽名成功後,即可將參與簽名引數及簽名paySign一併傳到小程式端,小程式端在接受到這些引數後,即可呼叫方法調起支付,調起程式碼如下:
wx.requestPayment({
'timeStamp': pay.timeStamp,
'nonceStr': pay.nonceStr,
'package': pay.package,
'signType': pay.signType,
'paySign': pay.paySign,
'success':function (res) {
console.log("支付成功")
callback();
},
'fail':function (res) {
}
});
支付成功後,微信伺服器將會呼叫我們在呼叫統一下單時傳過去的回撥方法路徑notify_url,在這個方法裡是本地系統在支付成功後需要做的一些業務操作。
需要注意的是,商戶系統對於支付結果通知的內容一定要做簽名驗證,並校驗返回的訂單金額是否與商戶側的訂單金額一致,防止資料洩漏導致出現“假通知”,造成資金損失。其次就是在業務處理完成後,需要向微信伺服器返回一個成功的資訊,否則微信將按一定的策略定期重新發起通知,這樣會頻繁請求本地伺服器的,所以本地系統必須能夠正確處理重複的通知,並在處理完成後一定要向微信端傳送成功的訊息。
推薦的做法是,當收到通知進行處理時,首先檢查對應業務資料的狀態,判斷該通知是否已經處理過,如果沒有處理過再進行處理,如果處理過直接返回結果成功。在對業務資料進行狀態檢查和處理之前,要採用資料鎖進行併發控制,以避免函式重入造成的資料混亂。
附:
物件轉換成xml的方法如下:
publicstaticStringmyBeanToXML(Objectmodel,StringrootName){
StringBuildersb =new StringBuilder();
sb.append("<" +rootName +">");
if (model !=null) {
Field[]field=model.getClass().getDeclaredFields();//獲取實體類的所有屬性,返回Field陣列
for (intj = 0;j <field.length;j++) {// 遍歷所有屬性
field[j].setAccessible(true);
Stringname=field[j].getName();//獲取屬性的名字
Objectvalue=null;
try {
value = field[j].get(model);
}catch(IllegalArgumentExceptione) {
e.printStackTrace();
}catch(IllegalAccessExceptione) {
e.printStackTrace();
}
if (value !=null) {
sb.append("<" +name +">");
if(!"total_fee".equals(name)){
sb.append("<![CDATA["+value.toString()+"]]>");
}else{
sb.append(value.toString());
}
sb.append("</"+name+">");
}
}
}
sb.append("</"+rootName+">");
returnsb.toString();
}