首先微信公众号支付分为两大部分:
1. 统一支付订单获取prepay_id; 2.前端调起微信支付页面
说一下大致流程
说白了就是打游戏,有一群小怪(参数)需要打过去,好不容易打过去了,出来了一个大boss sign(这个大boss是前面的小怪组合的),打完了这个大boss,去救公主,结果救公主需要将前面所有的怪物灵魂收集起来转换为龙珠(封装为xml字符串),然后消耗龙珠召唤神龙(用post发送请求接口),神龙(接口)给了你一个蛋(xml字符串),需要你打开(返回的xml字符串转换为map),蛋里面是钥匙(prepay_id).拿着钥匙,再去打小怪(参数),(小怪又组成了新的boss paySign,继续打完,拿着怪物灵魂去另一个世界(前端页面)用那个世界的秘术救回公主.
可以创建一个实体类:wechatPay(也可以不用,反正就是为了把那些固定的常量参数给统一放到一个地方)
public class weChatPay {
/**
* 公众号AppId
*/
public static final String APP_ID = "XXXXXXXX";
/**
* 微信支付商户号
*/
public static final String MCH_ID = "XXXXXXX";
/**
* 订单名称
*/
public static final String BODY = "购买";
/**
* 通知地址(不能带参数)
*/
public static final String NOTIFY_URL = "http://XXXXXXXXXX";
/**
* 交易类型(微信公众支付)
*/
public static final String TRADE_TYPE = "JSAPI";
/**
* 设备号(微信公众支付)
*/
public static final String DEVICE_INFO = "WEB";
/**
* 用钻石茶苑的IP地址经过MD5进行32位加密(商店统一下单KEY)*/
public static final String KEY = "XXXXXXXXXX";
}
然后就是找到各种方法来获取参数了
公众号支付主要要用到这几个参数
appid 这个appid绝对是你的公众号的appid,商户也会给你一个应用appid别用那个,而且appid要在商户里面与微信公众号绑定起来,微信公众号的微信支付功能也一定要开通
body 这个就相当于标题了随便写个啥就行
mch_id 这个就是商户账号了,申请商户的时候会给的
nonce_str 这个是微信公众号支付需要的随机字符串这个在后面也会用到具体方法随便找啦(我这里是16位的)
/** * 生成16位随机编号 */ public static String getNonce(){ Random random = new Random(); String letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; String numsLetters = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; String agentCode = ""; for (int i = 0; i < 16; i++) { agentCode += numsLetters.charAt(random.nextInt(letters.length())); } return agentCode; }
notify_url 回调地址,不知道做啥的反正设置一个外网可以访问的一个不带参数的地址就行,应该是接收微信返回的东西的(反正不设置不行,设置了我也不知道在哪里用到了...)
out_trade_no 商家的订单编号32位不重复随机字符串(使用UUID来创建的,本人菜狗子不考虑高并发)
/** * 获取32位商家订单号 */ public static String getUUID() { UUID uuid =UUID.randomUUID(); String str = uuid.toString(); // 去掉"-"符号 String temp = str.substring(0, 8) +str.substring(9, 13) + str.substring(14, 18) + str.substring(19, 23) +str.substring(24); return temp; }
spbill_create_ip:你的请求ip地址(直接获取本机ip )
total_fee:支付金额(单位:分)
trade_type: “JSAPI” 付款类型 反正只要是微信公众号支付就写这个(JSAPI)其他的得自己找哈
device_info: “WEB” 付款设备号也是固定的只要是微信公众号支付就是WEB其他的自己找
open_id: openid就难了,可以找网上咋找的,我做的时候因为接入网站的时候就获取了code然后根据code又获取了access_token,然后又得到了json返回数据,json中就有openid,然后我就直接从进入网站的时候就将openid获取到了.
String code = request.getParameter("code");
NetWorkHelper netHelper = new NetWorkHelper();
String Url = String.format("https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code", weChatConfig.APP_ID,weChatConfig.APP_SEC,code);
String result = netHelper.getHttpsResponse(Url,"");
JSONObject json = JSON.parseObject(result);
openid = json.getString("openid");//openid
这一段代码就是了.
sign : 签名
获取sign首先要把上面的所有的参数获取到
然后封装到一个sortedMap里传给生成签名的方法
生成签名的算法中需要用到MD5的算法,那个自己找,网上一大堆
生成签名的里面有一个KEY 这个key是固定的要绑定在微信公众号(还是商户我给忘了)上的秘钥(32位自己找一个固定的数字字母字符串就行,然后一定要在公众号/商户号里面绑定)
SortedMap<String, String> signParams = new TreeMap<>(); signParams.put("appid", weChatPay.APP_ID);//app_id String body = new String(weChatPay.BODY.getBytes("utf-8")); signParams.put("body", body);//商品参数信息 signParams.put("mch_id", weChatPay.MCH_ID);//微信商户账号 signParams.put("nonce_str",nonceStr);//32位不重复的编号 signParams.put("notify_url",weChatPay.NOTIFY_URL);//回调页面 signParams.put("out_trade_no", orderId);//订单编号 signParams.put("spbill_create_ip",SPBLING_CREATE_IP);//请求的实际ip地址 signParams.put("total_fee",TOTAL_FEE);//支付金额 单位为分 signParams.put("trade_type", weChatPay.TRADE_TYPE);//付款类型为APP signParams.put("device_info",weChatPay.DEVICE_INFO);//付款设备号微信公众号支付 signParams.put("openid",openid); String sign = createSign(signParams);//生成签名
/** * 微信支付签名算法sign * @param parameters * @return */ public static String createSign( SortedMap<String, String> parameters) { 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=" + weChatPay.KEY); System.out.println("签名字符串:" + sb.toString()); String sign = MD5Encryption.MD5(sb.toString()).toUpperCase(); return sign; }
如果生成sign签名成功了
就把上面所有的参数包括sign的都给转换成xml字符串(人家要求的,具体的方法如下:)
/** * @author * @date * @Description:将请求参数转换为xml格式的string * @param parameters * @return */ public static String getRequestXml(SortedMap<String, String> parameters) throws UnsupportedEncodingException { StringBuffer sb = new StringBuffer(); sb.append("<xml>"); Set es = parameters.entrySet(); Iterator it = es.iterator(); while (it.hasNext()) { Map.Entry entry = (Map.Entry) it.next(); String k = (String) entry.getKey(); String v = (String) entry.getValue(); if ("attach".equalsIgnoreCase(k) || "body".equalsIgnoreCase(k) || "sign".equalsIgnoreCase(k)) { sb.append("<" + k + ">" + "<![CDATA[" + v + "]]></" + k + ">"); } else { sb.append("<" + k + ">" + v + "</" + k + ">"); } } sb.append("</xml>"); String prePayXml = sb.toString(); return prePayXml; }
转换为xml后,就可以发送httppost请求去调用接口了
String orderUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder";//下单接口 String result = doHttpPost(requestXml,orderUrl);//以post请求的方式调用统一下单接口
具体post请求方法如下:
/** * 发送Http post请求 * @param xmlInfo * xml格式的的字符串 * @param URL * 请求url * @return 返回信息 */ public static String doHttpPost(String xmlInfo, String URL) { System.out.println("发起的数据:" + xmlInfo); byte[] xmlData = xmlInfo.getBytes(); InputStream instr = null; java.io.ByteArrayOutputStream out = null; try { URL url = new URL(URL); URLConnection urlCon = url.openConnection(); urlCon.setDoOutput(true); urlCon.setDoInput(true); urlCon.setUseCaches(false); urlCon.setRequestProperty("content-Type", "application/json"); urlCon.setRequestProperty("charset", "utf-8"); urlCon.setRequestProperty("Content-length", String.valueOf(xmlData.length)); System.out.println(String.valueOf(xmlData.length)); DataOutputStream printout = new DataOutputStream( urlCon.getOutputStream()); printout.write(xmlData); printout.flush(); printout.close(); instr = urlCon.getInputStream(); byte[] bis = IOUtils.toByteArray(instr); String ResponseString = new String(bis, "UTF-8"); if ((ResponseString == null) || ("".equals(ResponseString.trim()))) { System.out.println("返回空"); } System.out.println("返回数据为:" + ResponseString); return ResponseString; } catch (Exception e) { e.printStackTrace(); return "0"; } finally { try { if(out!=null){ out.close(); } if(instr!=null){ instr.close(); } } catch (Exception ex) { return "0"; } } }
我把整体的预支付封装在一个方法
/** * 微信预支付 * * @return */ public static String requestByWeiXinPay(String openid,String nonceStr,Long money,String orderId) throws Exception { String SPBLING_CREATE_IP="...";//访问ip // String TOTAL_FEE = money1.toString(); String TOTAL_FEE = "1";//测试用的钱 String notify_url = weChatPay.NOTIFY_URL;//回调地址 SortedMap<String, String> signParams = new TreeMap<>(); signParams.put("appid", weChatPay.APP_ID);//app_id String body = new String(weChatPay.BODY.getBytes("utf-8"));//这一步处理是中文转码报错 body不是utf-8类型的错误,所以要将body单独转换为中文编码. signParams.put("body", body);//商品参数信息 signParams.put("mch_id", weChatPay.MCH_ID);//微信商户账号 signParams.put("nonce_str",nonceStr);//32位不重复的编号 signParams.put("notify_url",weChatPay.NOTIFY_URL);//回调页面 signParams.put("out_trade_no", orderId);//订单编号 signParams.put("spbill_create_ip",SPBLING_CREATE_IP);//请求的实际ip地址 signParams.put("total_fee",TOTAL_FEE);//支付金额 单位为分 signParams.put("trade_type", weChatPay.TRADE_TYPE);//付款类型为APP signParams.put("device_info",weChatPay.DEVICE_INFO);//付款设备号微信公众号支付 signParams.put("openid",openid); String sign = createSign(signParams);//生成签名的方法(调用上文中的createSign方法就行) signParams.put("sign", sign); signParams.remove(weChatPay.KEY);//调用统一下单无需key(商户应用密钥) String requestXml = getRequestXml(signParams);//生成Xml格式的字符串 String orderUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder";//下单接口 String result = doHttpPost(requestXml,orderUrl);//以post请求的方式调用统一下单接口 return result; }
就是上面那些方法七七八八的合起来的总的方法
最终得到的结果result在后台打印一下就行,然后再服务器看一下返回的东西,有时候会包appid和mchid 不匹配就表示你的appid可能用的不是公众号的,或者公众号与商户没绑定.
当result获取到后,返回的也是xml的字符串所以又要转回map
/** * 将xml转换为map * @param xml * @return */ public static Map<String,String> turnString(String xml){ Map<String,String >map = new HashMap<>(); Document doc = null; try { doc= DocumentHelper.parseText(xml); Element rootElt = doc.getRootElement(); // 获取根节点 List<Element> list = rootElt.elements();//获取根节点下所有节点 for (Element element : list) { //遍历节点 map.put(element.getName(), element.getText()); //节点的name为map的key,text为map的value } } catch (DocumentException e){ e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } return map; }
然后获取到prepay_id
到现在预支付的部分就完成了
然后就开始第二部调起前端微信支付页面
第二部分差不多和第一部分
一样凑参数:
Appid
Mchid
这两个都是原来的就行
Nonce_str
这一个得重新调用方法创建新的字符串(有人说要和旧的一样,我不知道,反正众说纷纭,我自己新创建的是可以用的就行)
TimeStamp
时间戳(System.currentTimeMillis/1000就得到了当前时间的时间戳)
SignType :“MD5” 固定的
Prepay_id :获取到的
然后继续用前面的算法生成新的sign,当然参数要变成这几个
SortedMap<String,String> map1 = new TreeMap<>(); map1.put("appId",weChatPay.APP_ID); map1.put("timeStamp",timeStamp); map1.put("nonceStr",nonceStr); map1.put("signType","MD5"); map1.put("package","prepay_id="+prepayId); String sign = weChatPayConfig.createSign(map1);
注意啦,这里面的和前面不一样的有两点:
- 原来的是appid 现在的是appId有区分大小写,这种细节具体的看开发文档上的,那个是权威
- map1.put("package","prepay_id="+prepayId); 看清楚这里要经过处理的
然后要把这些参数传到你的前台去重要的事情就是必须是json格式
JSONObject json = new JSONObject(); json.put("appId",weChatPay.APP_ID); json.put("timeStamp",timeStamp); json.put("nonceStr",nonceStr); json.put("signType","MD5"); json.put("package","prepay_id="+prepayId); json.put("paySign",sign);
转换以后再传出去
下面这个是前端代码
获取到的东西转换为json
其他的参数按照下面的写就行了
<script> var json = eval(${resultMap}); //反正要返回的东西转换一下 var appId = json.appId; var timeStamp = json.timeStamp; var nonceStr = json.nonceStr; var signType = json.signType; var package1 = json.package; var sign = json.paySign; function onBridgeReady(){ WeixinJSBridge.invoke( 'getBrandWCPayRequest', { "appId":appId, //公众号名称,由商户传入 "timeStamp":timeStamp, //时间戳,自1970年以来的秒数 "nonceStr":nonceStr, //随机串 "package":package1, "signType":signType, //微信签名方式: "paySign":sign //微信签名 },
function(res){ if(res.err_msg == "get_brand_wcpay_request:ok" ){//如果支付成功了 //这里面就是你支付成功就得逻辑处理了,什么改数据啊,加表啊啥的就是这个了 } function pay() {//这个方法是你的点击的方法,想啥时候调用这个方法就写这里面,当然只有微信浏览器有效 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>
差不多就这么多了,各种菜鸡儿操作乱七八糟拼凑起来的,大量的不足还请多多指教!