微信小程序開發(fā)之---微信免密支付
很多時候我們都要用到免密支付焕刮,也就是常說的自動扣款,但是如何才能自動扣費席噩,這一塊微信api也沒有詳細說明模捂,微信普通的支付官方說的很清楚捶朵,但是免密支付可就少了。狂男。這類的支付的東西很多综看,比如滴滴打車、App Store微信支付并淋、二維碼乘車等寓搬。
下面是我做的一個項目,小程序县耽、后臺接口、數(shù)據(jù)庫構(gòu)建镣典。二維碼乘車兔毙,微信小程序生成二維碼,公交機器上面安裝卡機掃碼扣費兄春。上線四個月澎剥,我們用戶量幾十萬,每天掃碼訂單數(shù)兩萬多赶舆,金額十來萬哑姚。
需要準備的東西:
1祭饭、申請微信小程序號。
2叙量、申請微信商戶號(商戶開通委托代扣倡蝙,好像需要私下找微信走流程,我用的賬號是甲方提供的)绞佩。
3寺鸥、下載微信開發(fā)者工具。
按照下面圖片進入開發(fā)者工具品山,創(chuàng)建好你的項目胆建,根據(jù)圖片選工具自動回給你創(chuàng)建項目目錄,這一點就不詳細說了肘交。
一些發(fā)送的參數(shù)和結(jié)果我不會詳細說笆载,可以看下下面鏈接詳細說明姑曙,這是我們和微信合作粘舟,微信人員給的api內(nèi)部文檔,免密支付(自動扣費)api文檔地址:https://pay.weixin.qq.com/wiki/doc/api/pap.php?chapter=18_3&index=7
其實有了這個鏈接看看基本差不多懂了蟆技!魄懂。沿侈。。市栗。缀拭。
接著往下看吧。
免密支付首先要和用戶簽訂免密支付協(xié)議填帽。怎么簽蛛淋?
首先需要用code查詢用戶openid。
如何獲取code篡腌?
1褐荷、小程序用戶登錄就行。
//登錄
wx.login({
success: function (res) {
//res.code
然后就請求你的后臺接口獲取嘹悼。
},
fail: function () {
}
})
2叛甫、通過用戶登錄獲取的code,請求微信接口獲取openid杨伙。 注 一個code只能用一次其监。
我用的是java獲取用戶openid 小程序不行,因為小程序請求必須填寫安全url域名限匣,微信的域名不能填寫進去抖苦。所有放棄用小程序獲取吧
private Map<String, Object> _getOpenId(String code){
Map<String, Object> ret = new HashMap<>();
Map<String, String> urlData= new HashMap<String, String>();
urlData.put("appid",appid);//小程序id
urlData.put("secret",appKey);//小程序key
urlData.put("grant_type","authorization_code");//固定值這樣寫就行
urlData.put("js_code",code);//小程序傳過來的code
HttpsClientUtil httpsClientUtil = new HttpsClientUtil();
Object data_deserialize = null;
try {
//code2OpenidUrl "https://api.weixin.qq.com/sns/jscode2session";
String dataStr = httpsClientUtil.doGet(code2OpenidUrl, urlData);
data_deserialize = JSONUtil.deserialize(dataStr);
}catch(Exception ex){
ret.put("success", false);
ret.put("msg", "_getOpenId_未知異常");
ret.put("message", ex);
return ret;
}
Map<String, String> data= (Map<String, String>)data_deserialize;
if( data.containsKey("errcode") ){
ret.put("success", false);
ret.put("msg", data.containsKey("errcode"));
ret.put("message", data.containsKey("errmsg"));
}else{
ret.put("success", true);
ret.put("result",data);
}
return ret;
}
好了,開始簽約吧。簽約只需要小程序即可锌历,不需要后臺做什么贮庞。
首先需要app發(fā)起簽約請求,這個是簽約的方法究西,
不過首先要把 簽約小程序放在 app.json中 這是微信新的規(guī)則
"navigateToMiniProgramAppIdList": [
"wxbd687630cd02ce1d", 簽約小程序
"wx5e73c65404eee268" 微信代付 用戶還款小程序
]
wxml
//bindGetUserInfo 是我的邏輯函數(shù)窗慎,不需要看,
<button open-type="getUserInfo" bindgetuserinfo="bindGetUserInfo" class="btn_wxzf">立即綁定</button>
js
//發(fā)起簽約請求 data里面?zhèn)髦凳潜仨殏鞯膸醉椪瑳]強制要求的我沒傳
var me = this;
//裝作參數(shù)
this.globalData.contract_code = this.genID(5);
var data = {
mch_id: this.globalData.mch_id,//你的商戶號
appid: this.globalData.appid,//小程序appid
plan_id: this.globalData.plan_id,//你的商戶簽約模板id(在商戶號里面設(shè)置)
contract_code: this.globalData.contract_code, //簽約碼捉邢,商家生成,商戶側(cè)須唯一
contract_display_account: this.turnNickName(this.globalData.userInfo["nickName"]||""), //簽約用戶名稱商膊,我這里用的是用戶微信名字(怎么獲取下面有)本來我想用手機號的伏伐,但是獲取手機號需要注冊或者是微信api獲取需要用戶點擊同意,甲方說用戶多操作一步用戶體驗不好晕拆。藐翎。。
notify_url: "https://www.***.com/contractNotify",// 簽約成功與否微信返回數(shù)據(jù)的接收地址
request_serial: ((new Date()).getTime() - 1526353000000),//商戶請求簽約時的序列號純數(shù)字,長度不超過12位
timestamp: parseInt((new Date()).getTime() / 1000) + "" //時間戳
};
//簽名 MD5加密
data.sign = util.genSign(data, this.globalData.key);
//開始發(fā)起簽約
wx.navigateToMiniProgram({
appId: 'wxbd687630cd02ce1d', //固定值实幕,這個是填寫微信官方簽約小程序的id
extraData: data,
path: 'pages/index/index',
success(res) {
wx.setStorageSync('contract_id', "");
me.globalData.contract_id = "";
// 成功跳轉(zhuǎn)到簽約小程序
},
fail(res) {
console.log(res);
// 未成功跳轉(zhuǎn)到簽約小程序
}
});
簽約后會返回一些簽約信息吝镣,在app.js文件中 onShow函數(shù)中獲取。
這里面需要注意的是昆庇,用戶點擊的是返回還是點擊左上角的X末贾。。整吆。 我是當用戶點擊簽約按鈕時候拱撵,就開啟一個定時器去監(jiān)控,onShow里面取后臺數(shù)據(jù)庫查詢簽約狀態(tài)表蝙,(微信會把簽約結(jié)果異步通知到notify_url拴测,在里面存到數(shù)據(jù)庫就行)
App({
onShow(res) {
if (res.scene === 1038) { // 場景值1038:從被打開的小程序返回
const { appId, extraData } = res.referrerInfo
if (appId == 'wxbd687630cd02ce1d') { // appId為wxbd687630cd02ce1d:從簽約小程序跳轉(zhuǎn)回來
if (typeof extraData == 'undefined'){
// TODO ***用戶擊左上角的返回***
// 客戶端小程序不確定簽約結(jié)果,需要向商戶側(cè)后臺請求確定簽約結(jié)果
return;
}
if(extraData.return_code == 'SUCCESS'){
// TODO
// 客戶端小程序簽約成功府蛇,需要向商戶側(cè)后臺請求確認簽約結(jié)果
var contract_id = extraData.contract_id
return;
} else {
// TODO
// 簽約失敗
return;
}
}
}
}
})
notify_url函數(shù)集索,用來接收用戶簽約微信返回的結(jié)果xml
注意:只要收到微信通知,你就必須返回結(jié)果
失敾憧纭:
<xml>
<return_code><![CDATA[FAIL]]></return_code>
<return_msg><![CDATA[..]]></return_msg>
</xml>
成功:
<xml>
<return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
</xml>
// 接收發(fā)起免密簽約后小程序返回的結(jié)果接口
public String contractNotify() throws Exception{
StringBuilder sb = new StringBuilder();
BufferedReader reader = request.getReader();
char[]buff = new char[1024];
int len;
while((len = reader.read(buff)) != -1) {
sb.append(buff,0, len);
}
String wxResponseText = sb.toString();
// rootLogger.info("收到contractNotify");
//開始解析xml字符串
Document document = DocumentHelper.parseText(wxResponseText);
Element root = document.getRootElement();
Element successEl=root.element("return_code");//查詢狀態(tài)
String success=successEl.getStringValue();
Element return_msgEl;
String return_msg="";
if(success.equals("SUCCESS")){
Element resultCodeEl=root.element("result_code");//查詢狀態(tài)
String resultCode=resultCodeEl.getStringValue();
if(resultCode.equals("SUCCESS")){
Element contractCodeEl=root.element("contract_code");//查詢狀態(tài)
String contractCode=contractCodeEl.getStringValue();
Element openidEl=root.element("openid");//openid
String openid=openidEl.getStringValue();
Element contractIDEl=root.element("contract_id");//簽約id
String contractID=contractIDEl.getStringValue();
Element operateTimeEl=root.element("operate_time");//簽約操作時間
String operateTime=operateTimeEl.getStringValue();
Element changTypeEl=root.element("change_type");//簽約操作時間
String changType=changTypeEl.getStringValue();
//申明儲存數(shù)據(jù)對象
Map<String,String> contractData = new HashMap<String,String>();
contractData.put("openid",openid);
contractData.put("contract_id",contractID);
contractData.put("contract_code",contractCode);
contractData.put("create_time",operateTime);//如果是插入就當create_time存
contractData.put("update_time",operateTime);//如果是修改就當update_time存
contractData.put("chang_type",changType);//如果是修改就當update_time存
//開始儲存
//先查詢有沒有务荆,存放查詢結(jié)果
List<Map<String, Object>> queryList = null;
try{
queryList = dbExecutor.query("com.sutpc.dao.PayDao.selectContract", contractData);//list.get(0).get(contract_id);
}catch (Exception e){
rootLogger.error("contractNotify--query----查詢失敗扰法!");
rootLogger.error(e);
return "success";
}
//判斷查詢結(jié)果 如果有就修改 如果沒有就插入
if( queryList==null || queryList.size()==0 ){
try{
dbExecutor.insert("com.sutpc.dao.PayDao.saveContract", contractData);
}catch (Exception e){
rootLogger.error("contractNotify---insert---儲存失斢己!");
rootLogger.error(e);
return "success";
}
}else{
try{
dbExecutor.update("com.sutpc.dao.PayDao.updateContract", contractData);
}catch (Exception e){
rootLogger.error("contractNotify---update---儲存失斎洹!");
rootLogger.error(e);
return "success";
}
}
}
}else{
return_msgEl=root.element("return_msg");//查詢狀態(tài)
return_msg=return_msgEl.getStringValue();
}
// rootLogger.info("完成 contracttify");
return "success";
}
其中用到的用戶微信名稱,是通過微信api獲取的 獲取用戶信息
wxml***
<button open-type="getUserInfo" bindgetuserinfo="bindGetUserInfo" class="btn_wxzf">立即綁定</button>
js***
//如果用戶第一次使用我們程序祠锣,獲取用戶信息必須要用戶點擊按鈕同意后才行酷窥,以后就不需要了,商家直接獲取就行
bindGetUserInfo: function (e) {
if (!app.globalData.userInfo){
//新用戶判斷是否同意綁定
if (e.detail.userInfo){
app.globalData.userInfo = e.detail.userInfo;
this.setData({
userInfo: e.detail.userInfo,
hasUserInfo: true
})
//已授權(quán)開始綁定
this.bindBtnBangDing();
}else{
wx.showToast({
title: '您未同意授權(quán)',
image: '../../images/shangxin.png',
duration: 1000,
mask: true
});
}
}
},
到以上伴网,簽約已經(jīng)成功了蓬推,當需要扣款時,就可以用用戶的contract_id進行扣款了(如:掃碼乘車澡腾,滴滴打車等沸伏,我們是掃碼乘車用),不論什么時候扣动分,都能直接成功毅糟,用戶會收到一條微信支付消息。
那么如何扣款澜公? 接下來就是java干的事情了姆另,當需要扣用戶費用時候,后臺服務調(diào)用申請扣款接口即可坟乾。
組裝一些必要的參數(shù)迹辐,然后向微信API發(fā)起扣款請求即可,那么需要什么參數(shù)甚侣?可以看最上面我放的鏈接明吩!詳細的接口介紹!
請求appid殷费、商戶號印荔、隨機字符串、簽名(我用的是MD5)宗兼、商品描述躏鱼、商戶訂單號、總金額殷绍、終端IP(發(fā)起扣款電腦ip染苛,運行程序的服務器地址)、回調(diào)通知url(接收XML扣款結(jié)果地址)主到、交易類型(填PAP微信委托代 扣支付)茶行、委托代扣協(xié)議id(用戶簽約的contract_id) 這些是必須傳的,沒有強制要求的我們傳登钥,我懶畔师。
我用的是java寫的。
注:以下這些微信API需要傳輸?shù)脑敿殔?shù)看上面我放的鏈接牧牢。我只傳了一些必須傳的值看锉。
首先解釋下一些全局變量是什么:
// 一些微信接口api地址-----
// 請求扣款
private String wxPayApplyUrl = "https://api.mch.weixin.qq.com/transit/pay/payapply";
// 查詢openid
private String code2OpenidUrl="https://api.weixin.qq.com/sns/jscode2session";
// 查詢訂單狀態(tài)
private String wxQueryOrderUrl="https://api.mch.weixin.qq.com/pay/paporderquery";
// 查詢用戶狀態(tài)
private String wxQueryStateUrl="https://api.mch.weixin.qq.com/transit/pay/querystate";
// 查詢contract_id
private String wxQueryContractUrl="https://api.mch.weixin.qq.com/papay/querycontract";
// 發(fā)起退款
private String wxRefundUrl="https://api.mch.weixin.qq.com/secapi/pay/refund";
// 查詢退款
private String wxSelectRefund="https://api.mch.weixin.qq.com/pay/refundquery";
// 請求微信接口固定常量
private final String mch_id="***"; //商戶號
private final String plan_id="***";//簽約模板id號
private final String appid="***";//小程序號
private final String trade_type="PAP";//這個填固定值
private final String notify_url="***";//接收返回結(jié)果的地址
private final String appKey="***";//小程序app的key
private final String signKey="***";//簽名的KEY姿锭,每個商戶號一個,可以重置伯铣,但是需要嚴格保密呻此。
注:注意用戶打開小程序最好查詢一下用戶狀態(tài)有沒有支付能力,有沒有欠費(用戶如果錢不夠腔寡,微信會幫用戶墊資一次焚鲜,需要用戶去微信支付點擊還款,如果不還款放前,用戶就使用不了免密支付)
查詢用戶狀態(tài)函數(shù), 查看用戶是否欠費忿磅。有沒有支付能力。
private Map<String,Object> _queryState(String openid, String contract_id){
//將所有參數(shù)組裝成map排序
Map<String, Object> signMap = new TreeMap<>(
new Comparator<String>() {
public int compare(String obj1, String obj2) {
// 升序排序
return obj1.compareTo(obj2);
}
}
);
signMap.put("appid",appid);
signMap.put("mch_id",mch_id);
//生成隨機字符串
String nonce_str= MD5Utils.getNonceStr(32);
signMap.put("nonce_str",nonce_str);
signMap.put("contract_id",contract_id);
signMap.put("sign_type","MD5");
signMap.put("openid",openid);
//獲取簽名結(jié)果
String sign=getSign(signMap);
//拼接XML
StringBuilder XML = new StringBuilder();
XML.append("<xml>");
XML.append("<appid><![CDATA["+appid+"]]></appid>");
XML.append("<mch_id><![CDATA["+mch_id+"]]></mch_id>");
XML.append("<nonce_str><![CDATA["+nonce_str+"]]></nonce_str>");
XML.append("<contract_id><![CDATA["+contract_id+"]]></contract_id>");
XML.append("<sign_type><![CDATA[MD5]]></sign_type>");
XML.append("<sign><![CDATA["+sign+"]]></sign>");
XML.append("<openid><![CDATA["+openid+"]]></openid>");
XML.append("</xml>");
String xml = XML.toString();
// 調(diào)用請求微信接口函數(shù) 并return結(jié)果
HttpsClientUtil httpsClientUtil = new HttpsClientUtil();
Document document = null;
try {
String wxResponseText = httpsClientUtil.doPostXml(wxQueryStateUrl, xml);
//開始解析xml字符串凭语,驗證簽名我就不寫這里了葱她,刪除了,太長了叽粹!你們注意別忘記驗證消息安全性览效。
document = DocumentHelper.parseText(wxResponseText);
}catch (Exception ex){
results.put("success",false);
results.put("msg", "_queryState 發(fā)生異常");
results.put("message", ex);
return results;
}
Element root = document.getRootElement();
Element successEl=root.element("return_code");//查詢狀態(tài)
String success=successEl.getStringValue();
//開始判斷結(jié)果
if(success.equals("SUCCESS")){//判斷查詢狀態(tài)
Element resultCodeEL=root.element("result_code"); //業(yè)務結(jié)果
String resultCode=resultCodeEL.getStringValue();
if( resultCode.equals("SUCCESS") ){//判斷業(yè)務結(jié)果
//存放結(jié)果數(shù)據(jù)
Map<String,Object> item= new HashMap<>();
//用戶狀態(tài)
Element userStateEl=root.element("user_state");
String userState=userStateEl.getStringValue();
String contractState="";
if( userState.equals("NORMAL") ){//允許
item.put("user_state", 0);
//簽約狀態(tài) 0 簽約中 1 解約
Element contractStateEl=root.element("contract_state");
contractState=contractStateEl.getStringValue();
}else if( userState.equals("BLOCKED") ){//不允許
item.put("user_state", 1);
//簽約狀態(tài) 0 簽約中 1 解約
Element contractStateEl=root.element("contract_state");
contractState=contractStateEl.getStringValue();
}else if( userState.equals("OVERDUE") ){//用戶欠費
item.put("user_state", 2);
contractState="0";
}
item.put("contract_state", contractState);
results.put("result",item);
results.put("success", true);
}else if( resultCode.equals("FAIL") ){
Element errCodeEl=root.element("err_code");//錯誤代碼
String errCode=errCodeEl.getStringValue();
Element errCodeDesEl=root.element("err_code_des");//錯誤代碼描述
String errCodeDes=errCodeDesEl.getStringValue();
results.put("success",false);
results.put("msg",errCode);
results.put("message", errCodeDes);
}
}else if(success.equals("FAIL")){
Element msgEl=root.element("return_msg");//錯誤原因
String msg=msgEl.getStringValue();
results.put("success",false);
results.put("msg",msg);
}
return results;
}
發(fā)起扣款函數(shù),簽名我用的是MD5加密簽名,這里需要注意的是簽名的key是你商戶號里面的key虫几,注意保密锤灿。簽名之前,參數(shù)需要排好序123 辆脸、abc等不多說但校。注意,給微信傳的必須是XML字符串
至于md5加密方法網(wǎng)上可以找啡氢,這個是我的
//首先状囱,這是我的MD5加密類
package com.sutpc.utils;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Random;
public class MD5Utils {
private static final String DIGEST_ALGORITHM = "MD5";
private static final char HEX_DIGITS[] =
{'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
public static final String md5(String msg) {
if (msg == null) {
return null;
}
MessageDigest md5 = null;
try {
md5 = MessageDigest.getInstance(DIGEST_ALGORITHM);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
if (md5 == null) {
return null;
}
try {
md5.update(msg.getBytes("utf-8"));
}catch(Exception ex){}
byte[] digest = md5.digest();
return byte2Hex(digest);
}
public static String getNonceStr(int length){
String base = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < length; i++) {
int number = random.nextInt(base.length());
sb.append(base.charAt(number));
}
return sb.toString();
}
private static final String byte2Hex(byte[] b) {
int j = b.length;
char str[] = new char[j * 2];
int k = 0;
for (int i = 0; i < j; i++) {
byte byte0 = b[i];
str[k++] = HEX_DIGITS[byte0 >>> 4 & 0xf];
str[k++] = HEX_DIGITS[byte0 & 0xf];
}
return new String(str);
}
/**
* @param args
*/
public static void main(String[] args) {
System.out.println(MD5Utils.md5(null));
System.out.println(MD5Utils.md5("Ngis_Admin_3975528"));
System.out.println(MD5Utils.md5("111111"));
System.out.println(MD5Utils.md5(""));
System.out.println(MD5Utils.md5("123"));
System.out.println(MD5Utils.md5("12dsafasf3"));
System.out.println(MD5Utils.md5("全是有工在以有"));
System.out.println(MD5Utils.md5("全是有工在以有"));
System.out.println(MD5Utils.md5("1"));
}
}
//其次,這是我的加密函數(shù) 倘是。傳入排序后map亭枷,生成簽名
public String getSign(Map<String, Object> signMap){
//組裝md5簽名字符串
String sign="";
for(String nameKey:signMap.keySet()){
sign += nameKey+"="+signMap.get(nameKey)+"&";
}
sign+="key="+signKey;
//調(diào)用md5簽名方法
return MD5Utils.md5(sign).toUpperCase();
}
這個是我組裝發(fā)起扣款的函數(shù),前面獲取各種參數(shù)每個程序不同我沒放進去搀崭。
這里面需要注意:contract_id 是用戶免密簽約時微信給的叨粘, 但是如果用戶解約了、在簽約這個值會重新變瘤睹,所有我扣款時每次找微信查升敲,以防出錯。 你可以用你用戶簽約時你在數(shù)據(jù)庫存的轰传。
這里說一下驴党,請求微信查詢contract_id時,我們用戶有幾十萬获茬,其中有一兩個用戶信息獲取contract_id時候微信接口返回請求超時港庄,讓我們重復請求倔既,但是依舊是超時,所以從我們數(shù)據(jù)庫獲取這兩個用戶簽約信息攘轩。 信息保存很重要吧叉存。码俩。
api:"https://api.mch.weixin.qq.com/transit/pay/payapply"
發(fā)起扣款接口
// 傳入組裝好的的參數(shù)度帮,請求微信接口發(fā)起扣款申請
public String doWxPay(Map<String, Object> paramMap) throws Exception {
// **** 從傳入對象中獲取值
String sign_type="MD5";
String sign;
String body="公交乘車代扣";
String nonce_str= MD5Utils.getNonceStr(32);//隨機字符串
String start_time= (String) paramMap.get("start_time");//下車時間
String end_time= (String) paramMap.get("end_time");//下車時間
String line_name= (String) paramMap.get("lineName");//線路名稱
String openid= (String) paramMap.get("openid");//訂單號
String out_trade_no= (String) paramMap.get("orderNo");
int total_fee = (int) paramMap.get("total_fee");//微信傳輸單位為分
String spbill_create_ip= InetAddress.getLocalHost().getHostAddress();//獲取本機ip
String scene_info;
String trade_scene;
if( paramMap.containsKey("start_stationName") && paramMap.containsKey("end_stationName") ){
//我們有兩種場景 地鐵和公交 你們看情況用
String start_stationName=line_name +"("+(String) paramMap.get("start_stationName");
if(start_stationName.length()>32){
start_stationName=start_stationName.substring(0,32);
}
String end_stationName= (String) paramMap.get("end_stationName") + ")";
if(end_stationName.length()>32){
end_stationName=end_stationName.substring(0,32);
}
//地鐵場景 上車時間+下車時間+上車站點+下車站點
trade_scene="METRO";
//扣款用的是下車時間
scene_info="{\"scene_info\":{\"start_time\":\""+start_time+"\",\"end_time\":\""+end_time+"\",\"start_station\":\""+start_stationName+"\",\"end_station\":\""+end_stationName+"\"}}";
}else{
//公交場景 上車時間+線路名稱
trade_scene="BUS";
scene_info="{\"scene_info\":{\"start_time\":\""+end_time+"\",\"line_name\":\""+line_name+"\"}}";
}
// **** 開始獲取contract_code
String contract_id;
Map<String, Object> contract_query_rtn = _queryContract(openid);
if((boolean)contract_query_rtn.get("success")){
contract_id = (String)contract_query_rtn.get("contract_id");
}else{
//用openid查contract,微信返回錯誤 此錯誤是 系統(tǒng)超時 提示讓重復查詢 但是重復也一直查不到稿存,所有去數(shù)據(jù)庫查 如果查到就賦值笨篷,下面回去查用戶狀態(tài)。
if(contract_query_rtn.get("message").equals("SYSTEM ERROR")){
Map<String,String> selectData= new HashMap<String, String>();
selectData.put("openid",openid);
List<Map<String, Object>> queryList = null;
try{
queryList = dbExecutor.query("com..selectContract", selectData);
if(queryList.size()>0){
contract_id= (String) queryList.get(0).get("contract_id");
}else{
throw new Exception("數(shù)據(jù)庫中無此用戶contract_id!");
}
}catch (Exception e){
throw new Exception("從數(shù)據(jù)庫查詢contract_id失敗!");
}
}else{
throw new Exception("微信查詢contract_id失敗!");
}
}
// **** 開始儲存訂單到數(shù)據(jù)庫 支付結(jié)果等微信回執(zhí)中更改
try{
dbExecutor.insert("com.sutpc.base.dao.PayDao.saveOrder", paramMap);
}catch (Exception e){
rootLogger.error("doWxPay-----存儲失敯曷摹率翅!");
rootLogger.error(e);
}
// **** 將所有參數(shù)組裝成map排序
Map<String, Object> signMap = new TreeMap<String, Object>(
new Comparator<String>() {
public int compare(String obj1, String obj2) {
// 升序排序
return obj1.compareTo(obj2);
}
});
signMap.put("sign_type",sign_type);
signMap.put("body",body);
signMap.put("nonce_str",nonce_str);
signMap.put("out_trade_no",out_trade_no);
signMap.put("total_fee",total_fee);
signMap.put("spbill_create_ip",spbill_create_ip);
signMap.put("contract_id",contract_id);
signMap.put("scene_info",scene_info);
signMap.put("mch_id",mch_id);
signMap.put("appid",appid);
signMap.put("notify_url",notify_url);
signMap.put("trade_type",trade_type);
signMap.put("trade_scene",trade_scene);
//調(diào)用組合簽名字符串函數(shù),獲取簽名結(jié)果
sign=getSign(signMap);
// ****: 開始拼接xml
StringBuilder XML = new StringBuilder();
XML.append("<xml>");
XML.append("<mch_id><![CDATA["+mch_id+"]]></mch_id>");
XML.append("<appid><![CDATA["+appid+"]]></appid>");
XML.append("<notify_url><![CDATA["+notify_url+"]]></notify_url>");
XML.append("<trade_type><![CDATA["+trade_type+"]]></trade_type>");
XML.append("<trade_scene><![CDATA["+trade_scene+"]]></trade_scene>");
XML.append("<nonce_str><![CDATA["+nonce_str+"]]></nonce_str>");
XML.append("<sign_type><![CDATA["+sign_type+"]]></sign_type>");
XML.append("<sign><![CDATA["+sign+"]]></sign>");
XML.append("<body><![CDATA["+body+"]]></body>");
XML.append("<out_trade_no><![CDATA["+out_trade_no+"]]></out_trade_no>");
XML.append("<total_fee><![CDATA["+total_fee+"]]></total_fee>");
XML.append("<spbill_create_ip><![CDATA["+spbill_create_ip+"]]></spbill_create_ip>");
XML.append("<contract_id><![CDATA["+contract_id+"]]></contract_id>");
XML.append("<scene_info><![CDATA["+scene_info+"]]></scene_info>");
XML.append("</xml>");
String xml = XML.toString();
rootLogger.info(xml);
// ****: 調(diào)用請求微信接口函數(shù) 并return結(jié)果
HttpsClientUtil httpsClientUtil = new HttpsClientUtil();
return httpsClientUtil.doPostXml(wxPayApplyUrl, xml);
}
自己寫的發(fā)送請求方法袖迎,每個人每個庫都不一樣冕臭。
public String doPostXml(String url, String xml) throws Exception{
String result = null;
HttpClient httpClient = new SSLClient();
HttpPost httpPost = new HttpPost(url);
httpPost.addHeader("Content-Type","text/xml;charset=UTF-8");
StringEntity stringEntity = new StringEntity(xml, "UTF-8");
stringEntity.setContentEncoding("UTF-8");
httpPost.setEntity(stringEntity);
HttpResponse response = httpClient.execute(httpPost);
if(response != null){
HttpEntity resEntity = response.getEntity();
if(resEntity != null){
result = EntityUtils.toString(resEntity, "UTF-8");
}
}
return result;
}
這個請求返回的結(jié)果也是一個XML,那么你就要解析這個結(jié)果燕锥,我用的是java的包 dom4j 非常好用辜贵。
//開始解析xml字符串
Document document = DocumentHelper.parseText(wxResponseText);
Element root = document.getRootElement();
Element successEl = root.element("return_code");
String success = successEl.getStringValue();
Element msgEl = root.element("return_msg");
String msg = msgEl.getStringValue();//getTextTrim
//開始判斷結(jié)果
if (success.equals("SUCCESS")) {//判斷握手結(jié)果
Element resultCodeEl = root.element("result_code");
String resultCode = resultCodeEl.getStringValue();
if (resultCode.equals("SUCCESS")) {//判斷業(yè)務結(jié)果
item.put("success", true);
} else {
Element errCodeEl = root.element("err_code");//錯誤代碼
String errCode = errCodeEl.getStringValue();
Element errCodeDesEl = root.element("err_code_des");//錯誤代碼描述
String errCodeDes = errCodeDesEl.getStringValue();
item.put("success", false);
item.put("msg", errCode);
// **** 付款失敗更新訂單狀態(tài)
// 如果錯誤狀態(tài)是 ORDERPAID(訂單已支付 訂單號重復)就不更新。
if (!errCode.equals("ORDERPAID")) {
//付款失敗 更新訂單狀態(tài)以及錯誤碼
itemParam.put("trade_state", resultCode);
itemParam.put("err_code", errCode);
itemParam.put("err_code_des", errCodeDes);
sqlSession.update("com..updateOrder", itemParam);
//付款失敗 開始儲存付款失敗訂單
this.saveFailOrder(itemParam);
}
}
} else {
item.put("success", false);
item.put("msg", msg);
// **** 請求失敗更新訂單狀態(tài)
// 如果錯誤狀態(tài)是 ORDERPAID(訂單已支付 訂單號重復)就不更新归形。
if (!msg.equals("ORDERPAID")) {
//付款失敗 更新訂單狀態(tài)以及錯誤碼
itemParam.put("trade_state", success);
itemParam.put("err_code", msg);
sqlSession.update("com.sutpc.dao.PayDao.updateOrder", itemParam);
//付款失敗 開始儲存付款失敗訂單
this.saveFailOrder(itemParam);
}
}
發(fā)起扣款時托慨,填寫的notify_url接口函數(shù) 用來接收請求微信扣款后,微信異步返回的結(jié)果通知xml
// 接收發(fā)起扣款后小程序返回的結(jié)果接口
public String payNotify() throws Exception{
StringBuilder sb = new StringBuilder();
BufferedReader reader = request.getReader();
char[]buff = new char[1024];
int len;
while((len = reader.read(buff)) != -1) {
sb.append(buff,0, len);
}
String wxResponseText = sb.toString();
// rootLogger.info("收到paynotify");
//開始解析xml字符串
Document document = DocumentHelper.parseText(wxResponseText);
Element root = document.getRootElement();
Element successEl=root.element("return_code");//查詢狀態(tài)
String success=successEl.getStringValue();
Element return_msgEl;
String return_msg="";
if(success.equals("SUCCESS")){
Element resultCodeEl=root.element("result_code");//查詢狀態(tài)
String resultCode=resultCodeEl.getStringValue();
Element tradeStateEl=root.element("trade_state");//訂單狀態(tài) SUCCESS 成功 PAY_FAIL 失敗
String tradeState=tradeStateEl.getStringValue();
Element orderNoEl=root.element("out_trade_no");//商戶訂單號
String orderNo=orderNoEl.getStringValue();
Element transactionIdEl=root.element("transaction_id");//微信訂單號time_end
String transactionId=transactionIdEl.getStringValue();
Element timeEndEl=root.element("time_end");//time_end
String timeEnd=timeEndEl.getStringValue();
//以防掃碼時候存訂單沒存上 所有多放點數(shù)據(jù) 一遍插入時候使用 正常情況肯定都是修改updateOrder 但是以防萬一
Element openidEl=root.element("openid");//time_end
String openid=openidEl.getStringValue();
Element totalFeeEl=root.element("total_fee");//金額
String totalFee=totalFeeEl.getStringValue();
int totalFeeInt=Integer.parseInt(totalFee);//轉(zhuǎn)成int
//申明儲存數(shù)據(jù)對象
Map<String,Object> orderData = new HashMap<String,Object>();
orderData.put("trade_state",tradeState);//訂單狀態(tài)
orderData.put("orderNo",orderNo);//訂單號
orderData.put("transactionId",transactionId);//微信付款訂單號
orderData.put("pay_time",timeEnd);//正常應該是修改
orderData.put("openid",openid);//用戶id
orderData.put("total_fee",totalFeeInt);//最終付款金額
String err_code="";
String err_code_des="";
if( resultCode.equals("FAIL") ){
try{
Element err_codeEl=root.element("err_code");//商戶訂單號
err_code=err_codeEl.getStringValue();
Element err_code_desEl=root.element("err_code_des");//微信訂單號time_end
err_code_des=err_code_desEl.getStringValue();
}catch (Exception e){
rootLogger.error("payNotify------解析結(jié)果時發(fā)送異常暇榴!");
rootLogger.error(wxResponseText);
rootLogger.error(e);
return "success";
}
}
// TODO 如果付款失敗厚棵,且錯誤狀態(tài)是 ORDERPAID(訂單已支付 訂單號重復)就不更新。
if(err_code.equals("ORDERPAID")){
return "success";
}
orderData.put("err_code",err_code);//如果付款失敗記錄原因
orderData.put("err_code_des",err_code_des);//如果付款失敗記錄原因
//開始儲存
//先查詢有沒有蔼紧,存放查詢結(jié)果
List<Map<String, Object>> queryList = null;
try{
queryList = dbExecutor.query("com.sutpc.dao.PayDao.selectOrder", orderData);
}catch (Exception e){
rootLogger.error("payNotify-----query---查詢訂單時發(fā)生未知異常婆硬!");
rootLogger.error(e);
return "success";
}
//判斷查詢結(jié)果 如果有就修改 如果沒有就插入
if( queryList==null || queryList.size()==0 ){
try{
dbExecutor.insert("com.sutpc.dao.PayDao.saveOrder", orderData);
}catch (Exception e){
rootLogger.error("payNotify---insert-----添加訂單時發(fā)生異常!");
rootLogger.error(e);
return "success";
}
}else{
try{
dbExecutor.update("com.sutpc.dao.PayDao.updateOrder", orderData);
}catch (Exception e){
rootLogger.error("payNotify----update---修改訂單支付狀態(tài)時發(fā)送異常奸例!");
rootLogger.error(e);
return "success";
}
}
}else{
return_msgEl=root.element("return_msg");//查詢狀態(tài)
return_msg=return_msgEl.getStringValue();
}
// rootLogger.info("完成 paynotify");
return "success";
//return "<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>";
}
扣款的就這些彬犯,你根據(jù)我上面放的api文檔鏈接看步驟來就行,實在看不懂抄我的這個也行哩至,自己弄好參數(shù)傳過來躏嚎。
注:不論是簽約還是發(fā)起扣款申請,里面填寫的notify_url你必須寫好接口接收微信異步發(fā)送回來的數(shù)據(jù)菩貌,而且只要收到后卢佣,就必須回復,不然微信會一直高頻率的給你發(fā)箭阶,直到你的服務器爆炸虚茶。戈鲁。