微信小程序开发-微信支付之免密支付(自动扣费)一
微信小程序开发之---微信免密支付
很多时候我们都要用到免密支付,也就是常说的自动扣款,但是如何才能自动扣费,这一块微信api也没有详细说明,微信普通的支付官方说的很清楚,但是免密支付可就少了。。这类的支付的东西很多,比如滴滴打车、App Store微信支付、二维码乘车等。
下面是我做的一个项目,小程序、后台接口、数据库构建。二维码乘车,微信小程序生成二维码,公交机器上面安装卡机扫码扣费。上线四个月,我们用户量几十万,每天扫码订单数两万多,金额十来万。
![](https://img.haomeiwen.com/i4321660/3cc06edcd035dbf5.png)
![](https://img.haomeiwen.com/i4321660/b0f53991d7e2e0b6.png)
![](https://img.haomeiwen.com/i4321660/be1a008bde097a38.png)
![](https://img.haomeiwen.com/i4321660/85f5aa5fcfb1c40a.png)
![](https://img.haomeiwen.com/i4321660/da96b4dec45aa870.png)
![](https://img.haomeiwen.com/i4321660/9f7531c3f2178196.jpg)
需要准备的东西:
1、申请微信小程序号。
2、申请微信商户号。
3、下载微信开发者工具。
按照下面图片进入开发者工具,创建好你的项目,根据图片选工具自动回给你创建项目目录,这一点就不详细说了。
一些发送的参数和结果我不会详细说,可以看下下面链接详细说明,这是我们和微信合作,微信人员给的api内部文档,免密支付(自动扣费)api文档地址:https://pay.weixin.qq.com/wiki/doc/api/pap.php?chapter=18_3&index=7
其实有了这个链接看看基本差不多懂了!。。。。。
接着往下看吧。
免密支付首先要和用户签订免密支付协议。怎么签?
首先需要用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发起签约请求,这个是签约的方法,
wxml
//bindGetUserInfo 是我的逻辑函数,不需要看,
<button open-type="getUserInfo" bindgetuserinfo="bindGetUserInfo" class="btn_wxzf">立即绑定</button>
js
//发起签约请求 data里面传值是必须传的几项,没强制要求的我没传
var me = this;
//装作参数
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(在商户号里面设置)
contract_code: this.globalData.contract_code, //签约码,商家生成,商户侧须唯一
contract_display_account: this.turnNickName(this.globalData.userInfo["nickName"]||""), //签约用户名称,我这里用的是用户微信名字(怎么获取下面有)本来我想用手机号的,但是获取手机号需要注册或者是微信api获取需要用户点击同意,甲方说用户多操作一步用户体验不好。。。
notify_url: "https://www.***.com/contractNotify",// 签约成功与否微信返回数据的接收地址
request_serial: ((new Date()).getTime() - 1526353000000),//商户请求签约时的序列号纯数字,长度不超过12位
timestamp: parseInt((new Date()).getTime() / 1000) + "" //时间戳
};
//签名 MD5加密
data.sign = util.genSign(data, this.globalData.key);
//开始发起签约
wx.navigateToMiniProgram({
appId: 'wxbd687630cd02ce1d', //固定值,这个是填写微信官方签约小程序的id
extraData: data,
path: 'pages/index/index',
success(res) {
wx.setStorageSync('contract_id', "");
me.globalData.contract_id = "";
// 成功跳转到签约小程序
},
fail(res) {
console.log(res);
// 未成功跳转到签约小程序
}
});
注:wx.navigateToMiniProgram即将废弃,建议用新的navigator标签,我也刚更新了程序。
<!-- 需要在用户点击之前组装好数据,跟上面那种格式数据一模一样,可以用bindsuccess事件开启监控 -->
<navigator disabled="{{waitResult}}" class="btn_wxzf" target="miniProgram" open-type="navigate" app-id="wxbd687630cd02ce1d" path="pages/index/index" extra-data="{{navContractData}}" version="release" bindsuccess="bindBtnBangDing">立即绑定</navigator>
签约后会返回一些签约信息,在app.js文件中 onShow函数中获取。
App({
onShow(res) {
if (res.scene === 1038) { // 场景值1038:从被打开的小程序返回
const { appId, extraData } = res.referrerInfo
if (appId == 'wxbd687630cd02ce1d') { // appId为wxbd687630cd02ce1d:从签约小程序跳转回来
if (typeof extraData == 'undefined'){
// TODO
// 客户端小程序不确定签约结果,需要向商户侧后台请求确定签约结果
return;
}
if(extraData.return_code == 'SUCCESS'){
// TODO
// 客户端小程序签约成功,需要向商户侧后台请求确认签约结果
var contract_id = extraData.contract_id
return;
} else {
// TODO
// 签约失败
return;
}
}
}
}
})
以下是我的,主要用的是res.contract_id这个,扣款需要用。注意一定要把签约信息存到数据库,以便以后查询或者是用方便。其他代码忽略就行,那是我用到的,你们用不到。这里面一些变量主要是我在别的页面签约的,开启了一个定时器,监控签约结果。。。。
这里面需要注意的是,用户点击的是返回还是点击左上角的X。。。
//从签约页面跳转回来
onShow(res) {
var me = this;
if (res.scene === 1038) { // 场景值1038:从被打开的小程序返回
const { appId, extraData } = res.referrerInfo
if (appId == 'wxbd687630cd02ce1d') { //wxbd687630cd02ce1d签约小程序 :从签约小程序跳转回来
if (typeof extraData == 'undefined') {
// 客户端小程序不确定签约结果,需要向商户侧后台请求确定签约结果
if (this.globalData.openid){
this.getUserStatus(undefined, this.globalData.openid, function (res) {
// console.log(res);
if (res.contract_id) {
me.globalData.contract_id = res.contract_id;
wx.setStorageSync('contract_id', res.contract_id);
var data = {
contract_id: me.globalData.contract_id,
openid: me.globalData.openid,
contract_code: me.globalData.contract_code,
create_time: me.getTimer(),
nickName: me.turnNickName(me.globalData.userInfo["nickName"] || "")
};
//开始储存用户的contract
me.saveContract(data);
me.globalData.callSignStatus = 1;
}else{
me.globalData.onContract = false;
}
}, function () {
me.globalData.onContract = false;
console.log('fail', arguments);
})
return;
}else{ //一般不会走else 为了以防万一
wx.login({
success: function (res) {
//获取openid
me.getUserStatus(res.code, undefined, function (res) {
// console.log(res);
if (res.openid) {
me.globalData.openid = res.openid;
wx.setStorageSync('openid', res.openid);
}
if (res.contract_id) {
me.globalData.contract_id = res.contract_id;
wx.setStorageSync('contract_id', res.contract_id);
//开始储存用户的contract
var data = {
contract_id: me.globalData.contract_id,
openid: me.globalData.openid,
contract_code: me.globalData.contract_code,
create_time: me.getTimer(),
nickName: me.turnNickName(me.globalData.userInfo["nickName"] || "")
};
me.saveContract(data);
me.globalData.callSignStatus = 1;
} else {
me.globalData.onContract = false;
}
}, function () {
me.globalData.onContract = false;
console.log('fail', arguments);
})
}
})
return;
}
}else{
if (extraData.return_code == 'SUCCESS') {
// 客户端小程序签约成功,需要向商户侧后台请求确认签约结果
if (extraData.contract_id) {
wx.setStorageSync('contract_id', extraData.contract_id);
this.globalData.contract_id = extraData.contract_id;
if (this.globalData.openid){
var data = {
contract_id: this.globalData.contract_id,
openid: this.globalData.openid,
contract_code: this.globalData.contract_code,
create_time: this.getTimer(),
nickName: this.turnNickName(this.globalData.userInfo["nickName"]||"")
};
//开始储存用户的contract
this.saveContract(data);
} else { //一般不会走else 为了以防万一
wx.login({
success: function (res) {
//获取openid
me.getUserStatus(res.code, undefined, function (res) {
// console.log(res);
if (res.openid) {
me.globalData.openid = res.openid;
wx.setStorageSync('openid', res.openid);
var data = {
contract_id: me.globalData.contract_id,
openid: me.globalData.openid,
contract_code: me.globalData.contract_code,
create_time: me.getTimer(),
nickName: me.turnNickName(me.globalData.userInfo["nickName"]||"")
};
//开始储存用户的contract
me.saveContract(data);
}
}, function () {
console.log('fail', arguments);
})
}
})
}
this.globalData.callSignStatus = 1;
}else{
this.globalData.onContract = false;
}
return;
} else {
this.globalData.onContract = false;
// 签约失败
if (extraData.return_msg == '你已签约该协议') {
this.globalData.callSignStatus = 2;
}
return;
}
}
}
}
},
notify_url函数,用来接收用户签约微信返回的结果xml
注意:只要收到微信通知,你就必须返回结果
失败:
<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>
// 接收发起免密签约后小程序返回的结果接口
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");//查询状态
String success=successEl.getStringValue();
Element return_msgEl;
String return_msg="";
if(success.equals("SUCCESS")){
Element resultCodeEl=root.element("result_code");//查询状态
String resultCode=resultCodeEl.getStringValue();
if(resultCode.equals("SUCCESS")){
Element contractCodeEl=root.element("contract_code");//查询状态
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();
//申明储存数据对象
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存
//开始储存
//先查询有没有,存放查询结果
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";
}
//判断查询结果 如果有就修改 如果没有就插入
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");//查询状态
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
})
//已授权开始绑定
this.bindBtnBangDing();
}else{
wx.showToast({
title: '您未同意授权',
image: '../../images/shangxin.png',
duration: 1000,
mask: true
});
}
}
},
到以上,签约已经成功了,当需要扣款时,就可以用用户的contract_id进行扣款了(如:扫码乘车,滴滴打车等,我们是扫码乘车用),不论什么时候扣,都能直接成功,用户会收到一条微信支付消息。
那么如何扣款?
组装一些必要的参数,然后向微信API发起扣款请求即可,那么需要什么参数?可以看最上面我放的链接!详细的接口介绍!
请求appid、商户号、随机字符串、签名(我用的是MD5)、商品描述、商户订单号、总金额、终端IP(发起扣款电脑ip,运行程序的服务器地址)、回调通知url(接收XML扣款结果地址)、交易类型(填PAP微信委托代 扣支付)、委托代扣协议id(用户签约的contract_id) 这些是必须传的,没有强制要求的我们传,我懒。
我用的是java写的。
注:以下这些微信API需要传输的详细参数看上面我放的链接。我只传了一些必须传的值。
首先解释下一些全局变量是什么:
// 一些微信接口api地址-----
// 请求扣款
private String wxPayApplyUrl = "https://api.mch.weixin.qq.com/transit/pay/payapply";
// 查询openid
private String code2OpenidUrl="https://api.weixin.qq.com/sns/jscode2session";
// 查询订单状态
private String wxQueryOrderUrl="https://api.mch.weixin.qq.com/pay/paporderquery";
// 查询用户状态
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";
// 发起退款
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="***";//接收返回结果的地址
private final String appKey="***";//小程序app的key
private final String signKey="***";//签名的KEY,每个商户号一个,可以重置,但是需要严格保密。
注:注意用户打开小程序最好查询一下用户状态有没有支付能力,有没有欠费(用户如果钱不够,微信会帮用户垫资一次,需要用户去微信支付点击还款,如果不还款,用户就使用不了免密支付)
查询用户状态函数, 查看用户是否欠费。有没有支付能力。
private Map<String,Object> _queryState(String openid, String contract_id){
//将所有参数组装成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);
//获取签名结果
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();
// 调用请求微信接口函数 并return结果
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 发生异常");
results.put("message", ex);
return results;
}
Element root = document.getRootElement();
Element successEl=root.element("return_code");//查询状态
String success=successEl.getStringValue();
//开始判断结果
if(success.equals("SUCCESS")){//判断查询状态
Element resultCodeEL=root.element("result_code"); //业务结果
String resultCode=resultCodeEL.getStringValue();
if( resultCode.equals("SUCCESS") ){//判断业务结果
//存放结果数据
Map<String,Object> item= new HashMap<>();
//用户状态
Element userStateEl=root.element("user_state");
String userState=userStateEl.getStringValue();
String contractState="";
if( userState.equals("NORMAL") ){//允许
item.put("user_state", 0);
//签约状态 0 签约中 1 解约
Element contractStateEl=root.element("contract_state");
contractState=contractStateEl.getStringValue();
}else if( userState.equals("BLOCKED") ){//不允许
item.put("user_state", 1);
//签约状态 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;
}
发起扣款函数,签名我用的是MD5加密签名,这里需要注意的是签名的key是你商户号里面的key,注意保密。签名之前,参数需要排好序123 、abc等不多说。注意,给微信传的必须是XML字符串
至于md5加密方法网上可以找,这个是我的
//首先,这是我的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"));
}
}
//其次,这是我的加密函数 。传入排序后map,生成签名
public String getSign(Map<String, Object> signMap){
//组装md5签名字符串
String sign="";
for(String nameKey:signMap.keySet()){
sign += nameKey+"="+signMap.get(nameKey)+"&";
}
sign+="key="+signKey;
//调用md5签名方法
return MD5Utils.md5(sign).toUpperCase();
}
这个是我组装发起扣款的函数,前面获取各种参数每个程序不同我没放进去。
这里面需要注意:contract_id 是用户免密签约时微信给的, 但是如果用户解约了、在签约这个值会重新变,所有我扣款时每次找微信查,以防出错。 你可以用你用户签约时你在数据库存的。
这里说一下,请求微信查询contract_id时,我们用户有几十万,其中有一两个用户信息获取contract_id时候微信接口返回请求超时,让我们重复请求,但是依旧是超时,所以从我们数据库获取这两个用户签约信息。 信息保存很重要吧。。
api:"https://api.mch.weixin.qq.com/transit/pay/payapply"
发起扣款接口
// 传入组装好的的参数,请求微信接口发起扣款申请
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,微信返回错误 此错误是 系统超时 提示让重复查询 但是重复也一直查不到,所有去数据库查 如果查到就赋值,下面回去查用户状态。
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("数据库中无此用户contract_id!");
}
}catch (Exception e){
throw new Exception("从数据库查询contract_id失败!");
}
}else{
throw new Exception("微信查询contract_id失败!");
}
}
// **** 开始储存订单到数据库 支付结果等微信回执中更改
try{
dbExecutor.insert("com.sutpc.base.dao.PayDao.saveOrder", paramMap);
}catch (Exception e){
rootLogger.error("doWxPay-----存储失败!");
rootLogger.error(e);
}
// **** 将所有参数组装成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);
//调用组合签名字符串函数,获取签名结果
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);
// ****: 调用请求微信接口函数 并return结果
HttpsClientUtil httpsClientUtil = new HttpsClientUtil();
return httpsClientUtil.doPostXml(wxPayApplyUrl, xml);
}
自己写的发送请求方法,每个人每个库都不一样。
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;
}
这个请求返回的结果也是一个XML,那么你就要解析这个结果,我用的是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
//开始判断结果
if (success.equals("SUCCESS")) {//判断握手结果
Element resultCodeEl = root.element("result_code");
String resultCode = resultCodeEl.getStringValue();
if (resultCode.equals("SUCCESS")) {//判断业务结果
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);
// **** 付款失败更新订单状态
// 如果错误状态是 ORDERPAID(订单已支付 订单号重复)就不更新。
if (!errCode.equals("ORDERPAID")) {
//付款失败 更新订单状态以及错误码
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);
// **** 请求失败更新订单状态
// 如果错误状态是 ORDERPAID(订单已支付 订单号重复)就不更新。
if (!msg.equals("ORDERPAID")) {
//付款失败 更新订单状态以及错误码
itemParam.put("trade_state", success);
itemParam.put("err_code", msg);
sqlSession.update("com.sutpc.dao.PayDao.updateOrder", itemParam);
//付款失败 开始储存付款失败订单
this.saveFailOrder(itemParam);
}
}
发起扣款时,填写的notify_url接口函数 用来接收请求微信扣款后,微信异步返回的结果通知xml
// 接收发起扣款后小程序返回的结果接口
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");//查询状态
String success=successEl.getStringValue();
Element return_msgEl;
String return_msg="";
if(success.equals("SUCCESS")){
Element resultCodeEl=root.element("result_code");//查询状态
String resultCode=resultCodeEl.getStringValue();
Element tradeStateEl=root.element("trade_state");//订单状态 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();
//以防扫码时候存订单没存上 所有多放点数据 一遍插入时候使用 正常情况肯定都是修改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);//转成int
//申明储存数据对象
Map<String,Object> orderData = new HashMap<String,Object>();
orderData.put("trade_state",tradeState);//订单状态
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------解析结果时发送异常!");
rootLogger.error(wxResponseText);
rootLogger.error(e);
return "success";
}
}
// TODO 如果付款失败,且错误状态是 ORDERPAID(订单已支付 订单号重复)就不更新。
if(err_code.equals("ORDERPAID")){
return "success";
}
orderData.put("err_code",err_code);//如果付款失败记录原因
orderData.put("err_code_des",err_code_des);//如果付款失败记录原因
//开始储存
//先查询有没有,存放查询结果
List<Map<String, Object>> queryList = null;
try{
queryList = dbExecutor.query("com.sutpc.dao.PayDao.selectOrder", orderData);
}catch (Exception e){
rootLogger.error("payNotify-----query---查询订单时发生未知异常!");
rootLogger.error(e);
return "success";
}
//判断查询结果 如果有就修改 如果没有就插入
if( queryList==null || queryList.size()==0 ){
try{
dbExecutor.insert("com.sutpc.dao.PayDao.saveOrder", orderData);
}catch (Exception e){
rootLogger.error("payNotify---insert-----添加订单时发生异常!");
rootLogger.error(e);
return "success";
}
}else{
try{
dbExecutor.update("com.sutpc.dao.PayDao.updateOrder", orderData);
}catch (Exception e){
rootLogger.error("payNotify----update---修改订单支付状态时发送异常!");
rootLogger.error(e);
return "success";
}
}
}else{
return_msgEl=root.element("return_msg");//查询状态
return_msg=return_msgEl.getStringValue();
}
// rootLogger.info("完成 paynotify");
return "success";
//return "<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>";
}
扣款的就这些,你根据我上面放的api文档链接看步骤来就行,实在看不懂抄我的这个也行,自己弄好参数传过来。
注:不论是签约还是发起扣款申请,里面填写的notify_url你必须写好接口接收微信异步发送回来的数据,而且只要收到后,就必须回复,不然微信会一直高频率的给你发,直到你的服务器爆炸。。