最近需要做微信公众号支付,网上找了大堆的代码,大多都只说了个原理,自己踩了太多坑,所有的坑,都会再下面的文章中标注,代码我也贴上最全的(叫我雷锋)!!!
第一步:配置支付授权目录
你需要有将你公司的微信公众号开通支付(审核要等个几天),登录后找到 微信支付-->开发配置,你会看到如下图所示:
说明一下:配置支付授权目录,就是当你再H5界面调起支付控件进行支付时,要对你支付的post请求进行校验(再不懂看下面的调起微信支付控件代码):
如果出现错误,支付界面一闪而过(有调起支付控件的迹象),你看不出报什么错,你可以打印出微信返回的res:alert(JSON.stringify(res));
function onBridgeReady(){
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId":appId, //公众号名称,由商户传入
"paySign":sign, //微信签名
"timeStamp":timeStamp, //时间戳,自1970年以来的秒数
"nonceStr":nonceStr , //随机串
"package":packageStr, //预支付交易会话标识
"signType":signType //微信签名方式
},
function(res){
alert(JSON.stringify(res));//出错可以在这里看看.....
if(res.err_msg == "get_brand_wcpay_request:ok" ) {
//window.location.replace("index.html");
alert('支付成功');
}else if(res.err_msg == "get_brand_wcpay_request:cancel"){
alert('支付取消');
}else if(res.err_msg == "get_brand_wcpay_request:fail" ){
alert('支付失败');
}
//使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。
}
);
}
以下信息你都能从公众好+商户号中获得:
public interface WeChatConst {
//公众号支付APPID
//String APPID = "上图中的appId";
//公众号支付AppSecret
//String APP_SECRET = "上图中的secret";
//公众号支付商户号
String MCH_ID = "xxxx";
//商户后台配置的一个32位的key,位置:微信商户平台-账户中心-API安全
String KEY = "xxxxx";
//交易类型
String TRADE_TYPE = "JSAPI";
}
注:每个微信公众号对应一个商户号,当用户关注你的公众号后,如果在你的公众号里面要进行支付操作,那么他支付的软妹币就流入到了你这个商户号里去了
上面代码中的 公众号支付商户号 哪里获得?进入微信商户号,登录后找到导航栏的 帐户中心--->商户信息【就是上面接口WeChatConst中说的 公众号支付商户号 !!!】
KEY是啥? 同理,商户号中 账户中心--API安全 ↓↓↓ 长度32位哦。。。。
做完上面公众号配置跟商户号配置,就开始写代码啦。。。。。。不知道上面这些步骤的,找起来真的累。。。。
第一步:授权(可以是用户进你的界面就直接做,也可以用户点击界面中的某个按钮再授权,主要是拿openId)
一开始我觉得不需要这个openId,取了干啥?难道后面的下单一定要?开发文档里说了,trade_type=JSAPI时(即公众号支付),此参数必传,此参数为微信用户在商户对应appid下的唯一标识,好吧,老实点。。。。。
/**
跳转支付界面,将code带过去
**/ @RequestMapping("toPay.do")
public ModelAndView toPay(HttpServletRequest request, HttpServletResponse response) throws Exception { ModelAndView modelAndView = new ModelAndView();
logger.debug("玩家准备填写充值信息了:" + HttpUtil.buildOriginalURL(request)); //重定向Url
String redirecUri = URLEncoder.encode(GlobalThreadLocal.getSiteConfig().getBasePath() + "/wxOfficialAccountsPay/toInputAccountInfo.do");
//用于获取成员信息的微信返回码
String code = null;
if( request.getParameter("code")!=null ){
code =request.getParameter("code");
}
if( code == null) {
//授权
return authorization(redirecUri);
}
code =request.getParameter("code");
// 获取用户信息
WeixinLoginUser weixinLoginUser = getWeixinLoginUser(code); modelAndView.addObject("openId",des.getEncString(weixinLoginUser.getOpenID()));
// 跳转到支付界面
String viewName = "/wxOfficialAccountsPay/pay";
modelAndView.setViewName(viewName);
return modelAndView;
}
/**
* 授权方法
* @param redirecUri 重定向链接
*
* */
private ModelAndView authorization(String redirecUri) {
String siteURL="redirect:https://open.weixin.qq.com/connect/oauth2/authorize?appid="
+GlobalThreadLocal.getSiteConfig().getWeixin_appId()
+"&redirect_uri="+redirecUri+"&response_type=code&scope=snsapi_userinfo&state=1234#wechat_redirect";
logger.debug("授权路径:[ "+siteURL+" ]");
return new ModelAndView(siteURL);
}
注:GlobalThreadLocal.getSiteConfig().getWeixin_appId():是我配置的全局的appId
(就是前面公众号图中的的appId,我放一个接口来存这些信息,是给你们一个事例。。。)
GlobalThreadLocal.getSiteConfig().getWeixin_appSecret() :公众号的Secret
或许你会说为什么用scope=snsapi_userinfo 这个值(用户界面操作有感知的授权)而不用 scope=snsapi_base ? 因为你用后者,在进行授权的时候,会报redirec_Uri出错,反正我遇到了,到现在也没搞懂。。。。
根据code,获取用户授权信息
/**
* 获取微信授权登陆用户
* @param code
* @return
* @throws Exception
*/
private WeixinLoginUser getWeixinLoginUser(String code) throws Exception {
logger.debug("由code获取授权用户信息");
Oauth oauth = new Oauth();
// 由code获取access_token等信息
String str = oauth.getToken(code, GlobalThreadLocal.getSiteConfig().getWeixin_appId(), GlobalThreadLocal.getSiteConfig().getWeixin_appSecret());
// 解析返回的json数据,获取所需的信息
String openID = (String) JSON.parseObject(str, Map.class).get("openid");
String accessToken = (String) JSON.parseObject(str, Map.class).get("access_token");
String refreshToken = (String) JSON.parseObject(str, Map.class).get("refresh_token");
// 用openid,access_token获取用户的信息,返回userinfo对象
UserInfo userInfo = oauth.getSnsUserInfo(openID, accessToken);
// 将用户信息放入登录session中
WeixinLoginUser weixinLoginUser = new WeixinLoginUser();
weixinLoginUser.setOpenID(openID);
weixinLoginUser.setUnionID(userInfo.getUnionid());
weixinLoginUser.setHeadImageUrl(userInfo.getHeadimgurl());
weixinLoginUser.setNickName(userInfo.getNickname());
weixinLoginUser.setRefreshToken(refreshToken);
//
int siteID = GlobalThreadLocal.getSiteConfig().getSiteId();
weixinLoginUser.setSiteID(siteID);
// 返回weixinLoginUser对象
return weixinLoginUser;
}
用户信息封装类:
public class WeixinLoginUser implements Serializable{ private static final long serialVersionUID = -8449856597137213512L; private String openID;
private String unionID;
private String headImageUrl;
private String nickName;
private String refreshToken;
private int siteID; public String getOpenID() {
return openID;
}
public void setOpenID(String openID) {
this.openID = openID;
}
public String getUnionID() {
return unionID;
}
public void setUnionID(String unionID) {
this.unionID = unionID;
}
public String getHeadImageUrl() {
return headImageUrl;
}
public void setHeadImageUrl(String headImageUrl) {
this.headImageUrl = headImageUrl;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
public String getRefreshToken() {
return refreshToken;
}
public void setRefreshToken(String refreshToken) {
this.refreshToken = refreshToken;
}
public int getSiteID() {
return siteID;
}
public void setSiteID(int siteID) {
this.siteID = siteID;
}
}
授权做完,openId拿到了,跳转到支付界面,如下:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>支付界面</title>
<script type="text/javascript" src="../public/jquery/jquery-2.0.3.min.js"></script>
</head>
<body>
<input type="button" value="pay" onclick="pay()"/> <script>
var prepay_id ;
var sign ;
var appId ;
var timeStamp ;
var nonceStr ;
var packageStr ;
var signType ; function pay(){
var url = '${ctx}/wxOfficialAccountsPay/pay.do';
$.ajax({
type:"post",
url:url,
dataType:"json",
data:{openId:'${openId}'},
success:function(data) { if(data.result_code == 'SUCCESS'){
appId = data.appid;
sign = data.sign;
timeStamp = data.timeStamp;
nonceStr = data.nonce_str;
packageStr = data.packageStr;
signType = data.signType;
//调起微信支付控件
callpay();
}else{
alert("统一下单失败");
}
}
});
} function onBridgeReady(){
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId":appId, //公众号名称,由商户传入
"paySign":sign, //微信签名
"timeStamp":timeStamp, //时间戳,自1970年以来的秒数
"nonceStr":nonceStr , //随机串
"package":packageStr, //预支付交易会话标识
"signType":signType //微信签名方式
},
function(res){
if(res.err_msg == "get_brand_wcpay_request:ok" ) {
//window.location.replace("index.html");
alert('支付成功');
}else if(res.err_msg == "get_brand_wcpay_request:cancel"){
alert('支付取消');
}else if(res.err_msg == "get_brand_wcpay_request:fail" ){
alert('支付失败');
}
//使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。
}
);
} function callpay(){
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();
}
}
</script> </body>
</html>
点击支付按钮,进入下面一步,耐心点,继续往下看,我写到这里,感觉也弄了挺久了,心情好,继续 2333
第三步:统一下单(支付前,需要获取一系列支付参数,在这步做)
/**
* 点击确认充值 统一下单,获得预付id(prepay_id)
* @param request
* @param response
* @return
*/
@ResponseBody
@RequestMapping({"pay"})
public WxPaySendData prePay(HttpServletRequest request,HttpServletResponse response,String openId){
WxPaySendData result = new WxPaySendData();
try {
//商户订单号
String out_trade_no = WeChatUtil.getOut_trade_no();
//产品价格,单位:分
Integer total_fee = 1;
//客户端ip
String ip = HttpUtil.getIpAddr(request);
//支付成功后回调的url地址
String notify_url = "http://你的域名/odao-weixin-site/wxOfficialAccountsPay/callback.do"; //统一下单
String strResult = WeChatUtil.unifiedorder("testPay", out_trade_no, total_fee, ip, notify_url,openId); //解析xml
XStream stream = new XStream(new DomDriver());
stream.alias("xml", WxPaySendData.class);
WxPaySendData wxReturnData = (WxPaySendData)stream.fromXML(strResult); //两者都为SUCCESS才能获取prepay_id
if( wxReturnData.getResult_code().equals("SUCCESS") && wxReturnData.getReturn_code().equals("SUCCESS") ){
//业务逻辑,写入订单日志(你自己的业务) ..... String timeStamp = WeChatUtil.getTimeStamp();//时间戳
String nonce_str = WeChatUtil.getNonceStr();//随机字符串
注:上面这两个参数,一定要拿出来作为后续的value,不能每步都创建新的时间戳跟随机字符串,不然H5调支付API,会报签名参数错误
result.setResult_code(wxReturnData.getResult_code());
result.setAppid(GlobalThreadLocal.getSiteConfig().getWeixin_appId());
result.setTimeStamp(timeStamp);
result.setNonce_str(nonce_str);
result.setPackageStr("prepay_id="+wxReturnData.getPrepay_id());
result.setSignType("MD5"); WeChatUtil.unifiedorder(.....) 下单操作中,也有签名操作,那个只针对统一下单,要区别于下面的paySign
//第二次签名,将微信返回的数据再进行签名
SortedMap<Object,Object> signMap = new TreeMap<Object,Object>();
signMap.put("appId", GlobalThreadLocal.getSiteConfig().getWeixin_appId());
signMap.put("timeStamp", timeStamp);
signMap.put("nonceStr", nonce_str);
signMap.put("package", "prepay_id="+wxReturnData.getPrepay_id()); //注:看清楚,值为:prepay_id=xxx,别直接放成了wxReturnData.getPrepay_id()
signMap.put("signType", "MD5");
String paySign = WxSign.createSign(signMap, WeChatConst.KEY);//支付签名 result.setSign(paySign);
}else{
result.setResult_code("fail");
}
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
统一下单封装方法:
public class WeChatUtil { private static final Logger logger = Logger.getLogger(WeChatUtil.class); /**
* 统一下单
* 获得PrePayId
* @param body 商品或支付单简要描述
* @param out_trade_no 商户系统内部的订单号,32个字符内、可包含字母
* @param total_fee 订单总金额,单位为分
* @param IP APP和网页支付提交用户端ip
* @param notify_url 接收微信支付异步通知回调地址
* @param openid 用户openId
* @throws IOException
*/
public static String unifiedorder(String body,String out_trade_no,Integer total_fee,String ip,String notify_url,String openId)throws IOException {
/**
* 设置访问路径
*/
HttpPost httppost = new HttpPost("https://api.mch.weixin.qq.com/pay/unifiedorder");
String nonce_str = getNonceStr();//随机数据
SortedMap<Object,Object> parameters = new TreeMap<Object,Object>();
/**
* 组装请求参数
* 按照ASCII排序
*/
parameters.put("appid",GlobalThreadLocal.getSiteConfig().getWeixin_appId() );
parameters.put("body", body);
parameters.put("mch_id", WeChatConst.MCH_ID );
parameters.put("nonce_str", nonce_str);
parameters.put("out_trade_no", out_trade_no);
parameters.put("notify_url", notify_url);
parameters.put("spbill_create_ip", ip);
parameters.put("total_fee",total_fee.toString() );
parameters.put("trade_type",WeChatConst.TRADE_TYPE );
parameters.put("openid", openId); String sign = WxSign.createSign(parameters, WeChatConst.KEY); /**
* 组装XML
*/
StringBuilder sb = new StringBuilder("");
sb.append("<xml>");
setXmlKV(sb,"appid",GlobalThreadLocal.getSiteConfig().getWeixin_appId());
setXmlKV(sb,"body",body);
setXmlKV(sb,"mch_id",WeChatConst.MCH_ID);
setXmlKV(sb,"nonce_str",nonce_str);
setXmlKV(sb,"notify_url",notify_url);
setXmlKV(sb,"out_trade_no",out_trade_no);
setXmlKV(sb,"spbill_create_ip",ip);
setXmlKV(sb,"total_fee",total_fee.toString());
setXmlKV(sb,"trade_type",WeChatConst.TRADE_TYPE);
setXmlKV(sb,"sign",sign);
setXmlKV(sb,"openid",openId);
sb.append("</xml>"); StringEntity reqEntity = new StringEntity(new String (sb.toString().getBytes("UTF-8"),"ISO8859-1"));//这个处理是为了防止传中文的时候出现签名错误
httppost.setEntity(reqEntity);
DefaultHttpClient httpclient = new DefaultHttpClient();
HttpResponse response = httpclient.execute(httppost);
String strResult = EntityUtils.toString(response.getEntity(), Charset.forName("utf-8")); return strResult; } //获得随机字符串
public static String getNonceStr(){
Random random = new Random();
return MD5Util.MD5Encode(String.valueOf(random.nextInt(10000)), "UTF-8");
} //插入XML标签
public static StringBuilder setXmlKV(StringBuilder sb,String Key,String value){
sb.append("<");
sb.append(Key);
sb.append(">"); sb.append(value); sb.append("</");
sb.append(Key);
sb.append(">"); return sb;
} //解析XML 获得 PrePayId
public static String getPrePayId(String xml){
int start = xml.indexOf("<prepay_id>");
int end = xml.indexOf("</prepay_id>");
if(start < 0 && end < 0){
return null;
}
return xml.substring(start + "<prepay_id>".length(),end).replace("<![CDATA[","").replace("]]>","");
} //商户订单号
public static String getOut_trade_no(){
DateFormat df = new SimpleDateFormat("yyyyMMddHHmmssSSS");
return df.format(new Date()) + RandomChars.getRandomNumber(7);
} //时间戳
public static String getTimeStamp() {
return String.valueOf(System.currentTimeMillis() / 1000);
} //随机4位数字
public static int buildRandom(int length) {
int num = 1;
double random = Math.random();
if (random < 0.1) {
random = random + 0.1;
}
for (int i = 0; i < length; i++) {
num = num * 10;
}
return (int) ((random * num));
} public static String inputStream2String(InputStream inStream, String encoding){
String result = null;
try {
if(inStream != null){
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
byte[] tempBytes = new byte[1024];
int count = -1;
while((count = inStream.read(tempBytes, 0, 1024)) != -1){
outStream.write(tempBytes, 0, count);
}
tempBytes = null;
outStream.flush();
result = new String(outStream.toByteArray(), encoding);
}
} catch (Exception e) {
result = null;
}
return result;
} public static void main(String[] args) {
System.out.println(getOut_trade_no());
} }
签名封装类(网上很多,原理差不多):
public class WxSign { /**
* 创建签名
* @param parameters
* @param key
* @return
*/
@SuppressWarnings("rawtypes")
public static String createSign(SortedMap<Object,Object> parameters,String key){
StringBuffer sb = new StringBuffer();
Set es = parameters.entrySet();//所有参与传参的参数按照accsii排序(升序)
Iterator it = es.iterator();
while(it.hasNext()) {
Map.Entry entry = (Map.Entry)it.next();
String k = (String)entry.getKey();
Object v = entry.getValue();
if(null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + key);
String sign = MD5Util.MD5Encode(sb.toString(), "UTF-8").toUpperCase();
return sign;
}
}
支付参数封装类(还算全,够用了,这个appid,是小写的 i):
/package com.odao.weixin.site.cases2017.wxpay.entity; /**
* 微信支付参数
* @author wangfj
*
*/
public class WxPaySendData {
//公众账号ID
private String appid;
//附加数据
private String attach;
//商品描述
private String body;
//商户号
private String mch_id;
//随机字符串
private String nonce_str;
//通知地址
private String notify_url;
//商户订单号
private String out_trade_no;
//标价金额
private String total_fee;
//交易类型
private String trade_type;
//终端IP
private String spbill_create_ip;
//用户标识
private String openid;
//签名
private String sign;
//预支付id
private String prepay_id;
//签名类型:MD5
private String signType;
//时间戳
private String timeStamp;
//微信支付时用到的prepay_id
private String packageStr;
private String return_code;
private String return_msg;
private String result_code;
private String bank_type;
private Integer cash_fee;
private String fee_type;
private String is_subscribe;
private String time_end;
//微信支付订单号
private String transaction_id;
private String ip;
private Integer coupon_count;
private Integer coupon_fee;
private Integer coupon_fee_0;
private String coupon_type_0;
private String coupon_id_0;
public String getCoupon_type_0() {
return coupon_type_0;
}
public void setCoupon_type_0(String coupon_type_0) {
this.coupon_type_0 = coupon_type_0;
}
public String getCoupon_id_0() {
return coupon_id_0;
}
public void setCoupon_id_0(String coupon_id_0) {
this.coupon_id_0 = coupon_id_0;
}
public Integer getCoupon_fee_0() {
return coupon_fee_0;
}
public void setCoupon_fee_0(Integer coupon_fee_0) {
this.coupon_fee_0 = coupon_fee_0;
}
public Integer getCoupon_fee() {
return coupon_fee;
}
public void setCoupon_fee(Integer coupon_fee) {
this.coupon_fee = coupon_fee;
}
public Integer getCoupon_count() {
return coupon_count;
}
public void setCoupon_count(Integer coupon_count) {
this.coupon_count = coupon_count;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public String getBank_type() {
return bank_type;
}
public void setBank_type(String bank_type) {
this.bank_type = bank_type;
}
public Integer getCash_fee() {
return cash_fee;
}
public void setCash_fee(Integer cash_fee) {
this.cash_fee = cash_fee;
}
public String getFee_type() {
return fee_type;
}
public void setFee_type(String fee_type) {
this.fee_type = fee_type;
}
public String getIs_subscribe() {
return is_subscribe;
}
public void setIs_subscribe(String is_subscribe) {
this.is_subscribe = is_subscribe;
}
public String getTime_end() {
return time_end;
}
public void setTime_end(String time_end) {
this.time_end = time_end;
}
public String getTransaction_id() {
return transaction_id;
}
public void setTransaction_id(String transaction_id) {
this.transaction_id = transaction_id;
}
public String getAppid() {
return appid;
}
public void setAppid(String appid) {
this.appid = appid;
}
public String getAttach() {
return attach;
}
public void setAttach(String attach) {
this.attach = attach;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public String getMch_id() {
return mch_id;
}
public void setMch_id(String mch_id) {
this.mch_id = mch_id;
}
public String getNonce_str() {
return nonce_str;
}
public void setNonce_str(String nonce_str) {
this.nonce_str = nonce_str;
}
public String getNotify_url() {
return notify_url;
}
public void setNotify_url(String notify_url) {
this.notify_url = notify_url;
}
public String getOut_trade_no() {
return out_trade_no;
}
public void setOut_trade_no(String out_trade_no) {
this.out_trade_no = out_trade_no;
}
public String getTotal_fee() {
return total_fee;
}
public void setTotal_fee(String total_fee) {
this.total_fee = total_fee;
}
public String getTrade_type() {
return trade_type;
}
public void setTrade_type(String trade_type) {
this.trade_type = trade_type;
}
public String getSpbill_create_ip() {
return spbill_create_ip;
}
public void setSpbill_create_ip(String spbill_create_ip) {
this.spbill_create_ip = spbill_create_ip;
}
public String getOpenid() {
return openid;
}
public void setOpenid(String openid) {
this.openid = openid;
}
public String getReturn_code() {
return return_code;
}
public void setReturn_code(String return_code) {
this.return_code = return_code;
}
public String getReturn_msg() {
return return_msg;
}
public void setReturn_msg(String return_msg) {
this.return_msg = return_msg;
}
public String getResult_code() {
return result_code;
}
public void setResult_code(String result_code) {
this.result_code = result_code;
}
public String getSign() {
return sign;
}
public void setSign(String sign) {
this.sign = sign;
}
public String getPrepay_id() {
return prepay_id;
}
public void setPrepay_id(String prepay_id) {
this.prepay_id = prepay_id;
}
public String getSignType() {
return signType;
}
public void setSignType(String signType) {
this.signType = signType;
}
public String getTimeStamp() {
return timeStamp;
}
public void setTimeStamp(String timeStamp) {
this.timeStamp = timeStamp;
}
public String getPackageStr() {
return packageStr;
}
public void setPackageStr(String packageStr) {
this.packageStr = packageStr;
}
@Override
public String toString() {
return "WxPaySendData [appid=" + appid + ", attach=" + attach
+ ", body=" + body + ", mch_id=" + mch_id + ", nonce_str="
+ nonce_str + ", notify_url=" + notify_url + ", out_trade_no="
+ out_trade_no + ", total_fee=" + total_fee + ", trade_type="
+ trade_type + ", spbill_create_ip=" + spbill_create_ip
+ ", openid=" + openid + ", sign=" + sign + ", prepay_id="
+ prepay_id + ", signType=" + signType + ", timeStamp="
+ timeStamp + ", packageStr=" + packageStr + ", return_code="
+ return_code + ", return_msg=" + return_msg + ", result_code="
+ result_code + ", bank_type=" + bank_type + ", cash_fee="
+ cash_fee + ", fee_type=" + fee_type + ", is_subscribe="
+ is_subscribe + ", time_end=" + time_end + ", transaction_id="
+ transaction_id + ", ip=" + ip + ", coupon_count="
+ coupon_count + ", coupon_fee=" + coupon_fee
+ ", coupon_fee_0=" + coupon_fee_0 + ", coupon_type_0="
+ coupon_type_0 + ", coupon_id_0=" + coupon_id_0 + "]";
}
}
好了,做完上面,你已经在微信支付控件里输入了密码,支付完了,跟着我的步伐,别乱。。。。
第四步:微信支付回调(还记得上面第三部中配置的notify_url个字段吗,忘记了翻上去看看,为什么要做这一步?你需要告诉微信,你支付成功了,然后你可以在回调函数中写你的业务逻辑。。。)
/**
* 支付回调接口
* @param request
* @return
*/
@RequestMapping("/callback")
public void callBack(HttpServletRequest request, HttpServletResponse response){
response.setContentType("text/xml;charset=UTF-8");
try {
InputStream is = request.getInputStream();
String result = IOUtils.toString(is, "UTF-8");
if("".equals(result)){
response.getWriter().write("<xm><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[参数错误!]]></return_msg></xml>");
return ;
}
//解析xml
XStream stream = new XStream(new DomDriver());
stream.alias("xml", WxPaySendData.class);
WxPaySendData wxPaySendData = (WxPaySendData)stream.fromXML(result);
System.out.println(wxPaySendData.toString()); String appid = wxPaySendData.getAppid();
String mch_id =wxPaySendData.getMch_id();
String nonce_str = wxPaySendData.getNonce_str();
String out_trade_no = wxPaySendData.getOut_trade_no();
String total_fee = wxPaySendData.getTotal_fee();
//double money = DBUtil.getDBDouble(DBUtil.getDBInt(wxPaySendData.getTotal_fee())/100.0);
String trade_type = wxPaySendData.getTrade_type();
String openid =wxPaySendData.getOpenid();
String return_code = wxPaySendData.getReturn_code();
String result_code = wxPaySendData.getResult_code();
String bank_type = wxPaySendData.getBank_type();
Integer cash_fee = wxPaySendData.getCash_fee();
String fee_type = wxPaySendData.getFee_type();
String is_subscribe = wxPaySendData.getIs_subscribe();
String time_end = wxPaySendData.getTime_end();
String transaction_id = wxPaySendData.getTransaction_id();
String sign = wxPaySendData.getSign(); //签名验证
SortedMap<Object,Object> parameters = new TreeMap<Object,Object>();
parameters.put("appid",appid);
parameters.put("mch_id",mch_id);
parameters.put("nonce_str",nonce_str);
parameters.put("out_trade_no",out_trade_no);
parameters.put("total_fee",total_fee);
parameters.put("trade_type",trade_type);
parameters.put("openid",openid);
parameters.put("return_code",return_code);
parameters.put("result_code",result_code);
parameters.put("bank_type",bank_type);
parameters.put("cash_fee",cash_fee);
parameters.put("fee_type",fee_type);
parameters.put("is_subscribe",is_subscribe);
parameters.put("time_end",time_end);
parameters.put("transaction_id",transaction_id);
//以下4个参数针对优惠券(鼓励金之类的)这个坑真的弄了好久
parameters.put("coupon_count",wxPaySendData.getCoupon_count());
parameters.put("coupon_fee",wxPaySendData.getCoupon_fee());
parameters.put("coupon_id_0",wxPaySendData.getCoupon_id_0());
parameters.put("coupon_fee_0",wxPaySendData.getCoupon_fee_0()); String sign2 = WxSign.createSign(parameters, WeChatConst.KEY); if(sign.equals(sign2)){//校验签名,两者需要一致,防止别人绕过支付操作,不付钱直接调用你的业务,不然,哈哈,你老板会很开心的 233333.。。。
if(return_code.equals("SUCCESS") && result_code.equals("SUCCESS")){
//业务逻辑(先判断数据库中订单号是否存在,并且订单状态为未支付状态)
//do something ...
//request.setAttribute("out_trade_no", out_trade_no);
//通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了.
response.getWriter().write("<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>");
}else{
response.getWriter().write("<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[交易失败]]></return_msg></xml>");
}
}else{
//通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了.
response.getWriter().write("<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[签名校验失败]]></return_msg></xml>");
}
response.getWriter().flush();
response.getWriter().close();
return ;
} catch (IOException e) {
e.printStackTrace();
}
}
如果上面的签名校验,你两个签名怎么都对不上,那么点这里:https://pay.weixin.qq.com/wiki/tools/signverify/ 如图配置你上面的参数,再在控制台看你打印的签名跟这个是不是一直,不一致肯定是上面的参数值有问题(在这里尤其注意那个金额 total_fee )。。。
好了,到了这里,我想整个流程你应该理解了,把上面的代码COPY到你的项目里,我再补一下具体的工具类。。。如下:
/**
* md5加密算法实现
*/
public class MD5Util { private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5","6", "7", "8", "9", "a", "b", "c", "d", "e", "f" }; /**
* md5加密
*
* @param text 需要加密的文本
* @return
*/
public static String encode(String text) {
try {// aes rsa
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] result = md.digest(text.getBytes()); // 对文本进行加密
// b
// 000000..0000011111111
StringBuilder sb = new StringBuilder();
for (byte b : result) {
int i = b & 0xff ; // 取字节的后八位有效值
String s = Integer.toHexString(i);
if (s.length() < 2) {
s = "0" + s;
}
sb.append(s);
} // 加密的结果
return sb.toString();
} catch (NoSuchAlgorithmException e) {
// 找不到该算法,一般不会进入这里
e.printStackTrace();
} return "";
} public static String MD5Encode(String origin, String charsetname) {
String resultString = null;
try {
resultString = new String(origin);
MessageDigest md = MessageDigest.getInstance("MD5");
if (charsetname == null || "".equals(charsetname))
resultString = byteArrayToHexString(md.digest(resultString.getBytes()));
else
resultString = byteArrayToHexString(md.digest(resultString.getBytes(charsetname)));
} catch (Exception exception) { }
return resultString;
} private static String byteArrayToHexString(byte b[]) {
StringBuffer resultSb = new StringBuffer();
for (int i = 0; i < b.length; i++)
resultSb.append(byteToHexString(b[i])); return resultSb.toString();
} private static String byteToHexString(byte b) {
int n = b;
if (n < 0)
n += 256;
int d1 = n / 16;
int d2 = n % 16;
return hexDigits[d1] + hexDigits[d2];
}
}
好了,看到这里,我相信你也差不多大功告成了,出去溜达溜达。。。。然后。。。。