第三方APP微信支付Java服务端构建步骤
2016-06-13 10:44
501 查看
因日前工作需要,做了一次微信支付。其中一些关键点记录下来,以备不时之需,也拿出来交流下,如有不足之处,还望多多指教。
第三方APP微信支付时序图
详见:"https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=8_3"
1.准备工作获取appid和appkey(api密匙)
微信支付申请审核通过后,您就可以收到微信发给您的邮件,里面有您账户相关参数。
第一个坑:公众平台的密钥和商户号的密钥是不一样的!!!微信支付审核成功之后会收到一封邮件,邮件中有appid
商户号,商户后台登录上号和密码,登录到商户后台:账户设置-安全设置-切换到API安全,下载证书,下面有一个api密匙,进去填写一个字符串,保存,后续两次签名都是用的这个手动设置的key!!!
2.服务端工作流程
这里跳过流程图中的前三步
2.1调用统一下单接口,获取prepayid
2.1.1准备所需参数
统一下单接口必需参数:appid,mch_id,nonce_str,body,out_trade_no,total_fee,spbill_create_ip,notify_url,trade_type,这九个参数部分是客户端传来的,其中notify_url是异步通知(微信通知应用服务端的URL地址)
详见https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
第二个坑:以上所需九个参数变量名必是小写,total_fee以分为单位,是大于0的整数;同一个应用的安卓和苹果分配的appid不一样,可以让客户端携带appid来请求
2.1.1对参数进行签名
按照接口文档给的签名说明:需要先对参数按照ascii码进行排序,值为空的不参与排序,参与排序的字段不包括appkey(api密匙)。排完序后,将排好序的字段塞进xml中,同时用排好序的字符串与key及其值通过&拼接起来,之后再使用MD5加密,然后将小写转大写,这就是生成的sign(签名)值。最后将sign塞进xml中。然后就可以开始向微信统一下单接口发起请求了,接收prepayid。
以下是我自己的代码,大部分和网上的一样:
//这个是将参数从reqInfo对象放进一个map中
public StringgetXmlStr(WCPayGetPrePayIdReqInfo reqInfo) {
Map<String,Object>params = new HashMap<String,Object>();
params.put("appid",reqInfo.getAppId()); //上面的appid,注意大小写
params.put("mch_id",reqInfo.getMchId()); //商户id
params.put("key",reqInfo.getKey()); //appkey(api密匙)
params.put("nonce_str",WCPayUtils.getRandomNumber(32)); //32位随机数
params.put("body",reqInfo.getBody()); //商品描述
params.put("out_trade_no",reqInfo.getOutTradeNo()); //应用后台生成的订单id
params.put("total_fee",reqInfo.getTotalFee()); //总金额
params.put("spbill_create_ip",reqInfo.getSpbillCreateIp()); //用户终端ip
params.put("notify_url","application/test"); //异步通知URL
params.put("trade_type",reqInfo.getTradeType()); //交易方式,参见微信接口文档
try{
returnWCPayUtils.getXmlFromParamsMap(params);
}catch (Exception e) {
LogFactory.getLog("Message").debug("生成xml字符串出错");
}
return null;
}
2.1.2调用统一下单接口
将以上参xml字符串准备妥当后,用httpclient向微信统一下单接口发起请求,拿到返回的数据后,对其进行签名认证,认证成功后封装到WCPayGetPrePayIdRespInfo对象中,请求方法如下
private void getPrepayId(StringxmlStr,WCPayGetPrePayIdRespInfo respInfo) {
try {
HttpClientBuilderhttpClientBuilder = HttpClientBuilder.create();
CloseableHttpClientcloseableHttpClient = httpClientBuilder.build();
HttpPost httpPost =new HttpPost("https://api.mch.weixin.qq.com/pay/unifiedorder");
HttpClientContextcontext = HttpClientContext.create();
StringEntity se = newStringEntity(xmlStr);
se.setContentType("text/xml");
se.setContentEncoding(newBasicHeader(HTTP.CONTENT_TYPE, "application/xml"));
httpPost.setEntity(se);
httpPost.setConfig(RequestConfig.DEFAULT);
HttpResponsehttpResponse = closeableHttpClient.execute(httpPost, context);
HttpEntity httpEntity= httpResponse.getEntity();
if (httpEntity !=null) {
// 打印响应内容
InputStreamcontent = httpEntity.getContent();
Map<String,String> params = WCPayUtils.getParamsMapFromXml(content);
params.put("key",pContect.getWcPayKey());
if(params.containsKey("sign")&& params.get("prepay_id") != null &&
!"".equals(params.get("prepay_id"))&& !"null".equals(params.get("prepay_id"))){
if(WCPayUtils.checkSign(params)){//签名认证成功
for(Map.Entry<String, String> param : params.entrySet()) {
if("appid".equals(param.getKey()))
respInfo.setAppId(param.getValue());
elseif("mch_id".equals(param.getKey()))
respInfo.setPartnerId(param.getValue());
elseif("prepay_id".equals(param.getKey()))
respInfo.setPrepayId(param.getValue());//将prepayid放进WCPayGetPrePayIdRespInfo对象中
else
continue;
}
}
}else{
LogFactory.getLog("system").info("第一次响应签名认证失败");
}
}
closeableHttpClient.close();
} catch (Exception e) {
e.printStackTrace();
}
}
2.2 给客户端返回数据
对WCPayGetPrePayIdRespInfo对象中的其他字段值进行设置,WCPayGetPrePayIdRespInfo对象是返回给app端的对象,这里有app端调起支付接口所需的七个必需参数:
appid,partnerid(商户id),prepayid(预支付标识,微信返回给服务端的xml数据中有),noncestr(32位随机字符串,建议新生成一个),package(取固定值 Sign=WXPay), timestamp(时间戳),sign(签名)
第三个坑这里的sign还是通过要对上面六个(不包括sign)参数进行排序,然后拼接appkey后在进行md5加密,再小写变大写获取),
之后将sign也放进WCPayGetPrePayIdRespInfo对象中,然后传递给app客户端,
2.3接收微信异步通知并处理相应业务逻辑
当app客户端向微信发起支付请求,并付款成功后,微信会向异步通知URL也就是notify_url上面传递支付接口信息,也是xml字符串。我们需要将之解析完成后,并再次生成sign,然后将生成的sign与传来的sign进行比对认证,认证成功则说明是微信发来的信息。然后从里面拿到result_code,如果result_code是“SUCCESS”说明支付成功。处理相应业务逻辑。
以上,就是第三方APP微信支付Java服务端构建步骤。如有不对的之处,还请指正交流。
下面附上上面所用到的两个工具类
WCPayUtils工具类
MD5Utils工具类
第三方APP微信支付时序图
详见:"https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=8_3"
1.准备工作获取appid和appkey(api密匙)
微信支付申请审核通过后,您就可以收到微信发给您的邮件,里面有您账户相关参数。
第一个坑:公众平台的密钥和商户号的密钥是不一样的!!!微信支付审核成功之后会收到一封邮件,邮件中有appid
商户号,商户后台登录上号和密码,登录到商户后台:账户设置-安全设置-切换到API安全,下载证书,下面有一个api密匙,进去填写一个字符串,保存,后续两次签名都是用的这个手动设置的key!!!
2.服务端工作流程
这里跳过流程图中的前三步
2.1调用统一下单接口,获取prepayid
2.1.1准备所需参数
统一下单接口必需参数:appid,mch_id,nonce_str,body,out_trade_no,total_fee,spbill_create_ip,notify_url,trade_type,这九个参数部分是客户端传来的,其中notify_url是异步通知(微信通知应用服务端的URL地址)
详见https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
第二个坑:以上所需九个参数变量名必是小写,total_fee以分为单位,是大于0的整数;同一个应用的安卓和苹果分配的appid不一样,可以让客户端携带appid来请求
2.1.1对参数进行签名
按照接口文档给的签名说明:需要先对参数按照ascii码进行排序,值为空的不参与排序,参与排序的字段不包括appkey(api密匙)。排完序后,将排好序的字段塞进xml中,同时用排好序的字符串与key及其值通过&拼接起来,之后再使用MD5加密,然后将小写转大写,这就是生成的sign(签名)值。最后将sign塞进xml中。然后就可以开始向微信统一下单接口发起请求了,接收prepayid。
以下是我自己的代码,大部分和网上的一样:
//这个是将参数从reqInfo对象放进一个map中
public StringgetXmlStr(WCPayGetPrePayIdReqInfo reqInfo) {
Map<String,Object>params = new HashMap<String,Object>();
params.put("appid",reqInfo.getAppId()); //上面的appid,注意大小写
params.put("mch_id",reqInfo.getMchId()); //商户id
params.put("key",reqInfo.getKey()); //appkey(api密匙)
params.put("nonce_str",WCPayUtils.getRandomNumber(32)); //32位随机数
params.put("body",reqInfo.getBody()); //商品描述
params.put("out_trade_no",reqInfo.getOutTradeNo()); //应用后台生成的订单id
params.put("total_fee",reqInfo.getTotalFee()); //总金额
params.put("spbill_create_ip",reqInfo.getSpbillCreateIp()); //用户终端ip
params.put("notify_url","application/test"); //异步通知URL
params.put("trade_type",reqInfo.getTradeType()); //交易方式,参见微信接口文档
try{
returnWCPayUtils.getXmlFromParamsMap(params);
}catch (Exception e) {
LogFactory.getLog("Message").debug("生成xml字符串出错");
}
return null;
}
2.1.2调用统一下单接口
将以上参xml字符串准备妥当后,用httpclient向微信统一下单接口发起请求,拿到返回的数据后,对其进行签名认证,认证成功后封装到WCPayGetPrePayIdRespInfo对象中,请求方法如下
private void getPrepayId(StringxmlStr,WCPayGetPrePayIdRespInfo respInfo) {
try {
HttpClientBuilderhttpClientBuilder = HttpClientBuilder.create();
CloseableHttpClientcloseableHttpClient = httpClientBuilder.build();
HttpPost httpPost =new HttpPost("https://api.mch.weixin.qq.com/pay/unifiedorder");
HttpClientContextcontext = HttpClientContext.create();
StringEntity se = newStringEntity(xmlStr);
se.setContentType("text/xml");
se.setContentEncoding(newBasicHeader(HTTP.CONTENT_TYPE, "application/xml"));
httpPost.setEntity(se);
httpPost.setConfig(RequestConfig.DEFAULT);
HttpResponsehttpResponse = closeableHttpClient.execute(httpPost, context);
HttpEntity httpEntity= httpResponse.getEntity();
if (httpEntity !=null) {
// 打印响应内容
InputStreamcontent = httpEntity.getContent();
Map<String,String> params = WCPayUtils.getParamsMapFromXml(content);
params.put("key",pContect.getWcPayKey());
if(params.containsKey("sign")&& params.get("prepay_id") != null &&
!"".equals(params.get("prepay_id"))&& !"null".equals(params.get("prepay_id"))){
if(WCPayUtils.checkSign(params)){//签名认证成功
for(Map.Entry<String, String> param : params.entrySet()) {
if("appid".equals(param.getKey()))
respInfo.setAppId(param.getValue());
elseif("mch_id".equals(param.getKey()))
respInfo.setPartnerId(param.getValue());
elseif("prepay_id".equals(param.getKey()))
respInfo.setPrepayId(param.getValue());//将prepayid放进WCPayGetPrePayIdRespInfo对象中
else
continue;
}
}
}else{
LogFactory.getLog("system").info("第一次响应签名认证失败");
}
}
closeableHttpClient.close();
} catch (Exception e) {
e.printStackTrace();
}
}
2.2 给客户端返回数据
对WCPayGetPrePayIdRespInfo对象中的其他字段值进行设置,WCPayGetPrePayIdRespInfo对象是返回给app端的对象,这里有app端调起支付接口所需的七个必需参数:
appid,partnerid(商户id),prepayid(预支付标识,微信返回给服务端的xml数据中有),noncestr(32位随机字符串,建议新生成一个),package(取固定值 Sign=WXPay), timestamp(时间戳),sign(签名)
第三个坑这里的sign还是通过要对上面六个(不包括sign)参数进行排序,然后拼接appkey后在进行md5加密,再小写变大写获取),
之后将sign也放进WCPayGetPrePayIdRespInfo对象中,然后传递给app客户端,
2.3接收微信异步通知并处理相应业务逻辑
当app客户端向微信发起支付请求,并付款成功后,微信会向异步通知URL也就是notify_url上面传递支付接口信息,也是xml字符串。我们需要将之解析完成后,并再次生成sign,然后将生成的sign与传来的sign进行比对认证,认证成功则说明是微信发来的信息。然后从里面拿到result_code,如果result_code是“SUCCESS”说明支付成功。处理相应业务逻辑。
以上,就是第三方APP微信支付Java服务端构建步骤。如有不对的之处,还请指正交流。
下面附上上面所用到的两个工具类
WCPayUtils工具类
import java.io.InputStream; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; import org.dom4j.Attribute; import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.io.SAXReader; public class WCPayUtils { /** * 将微信支付所需参数拼接为xml字符串 * @param <T> * * @param paramsMap * @return * @throws Exception */ public static <T> String getXmlFromParamsMap(Map<String, T> paramsMap) throws Exception { if (paramsMap != null && paramsMap.size() > 0) { Map<String, Object> params = new TreeMap<String, Object>(new Comparator<String>() { public int compare(String s1, String s2) { return s1.compareTo(s2); } }); params.putAll(paramsMap); StringBuffer ss = new StringBuffer("<xml>"); for (Entry<String, Object> param : params.entrySet()) { if (!"key".equals(param.getKey())) { ss.append("<" + param.getKey() + "><![CDATA[").append(param.getValue()) .append("]]></" + param.getKey() + ">"); } } String sign = getSignFromParamMap(params); ss.append("<sign>" + sign + "</sign>"); ss.append("</xml>"); String xmlString = ss.toString(); return new String(xmlString.getBytes(), "ISO-8859-1"); } return null; } /** * 从xml字符串中解析参数 * @param xml * @return * @throws Exception */ public static Map<String, String> getParamsMapFromXml(InputStream xml) throws Exception { Map<String, String> params = new HashMap<String, String>(0); SAXReader saxReader = new SAXReader(); Document read = saxReader.read(xml); Element node = read.getRootElement(); listNodes(node, params); return params; } /** * 生成count位的随机数 * * @param count * @return */ public static String getRandomNumber(int count) { StringBuffer ss = new StringBuffer(count); for (int i = 0; i < count; i++) { int a = (int) (Math.random() * 10); ss.append(a); } return ss.toString(); } @SuppressWarnings({ "unchecked" }) public static void listNodes(Element node, Map<String, String> params) { // 获取当前节点的所有属性节点 List<Attribute> list = node.attributes(); // 遍历属性节点 if ((list == null || list.size() == 0) && !(node.getTextTrim().equals(""))) { if(node.getTextTrim().contains("<![CDATA[")){ String[] split = node.getTextTrim().split("<![CDATA["); split[1].replaceAll("]]>", ""); params.put(node.getName(), split[1]); }else{ params.put(node.getName(),node.getTextTrim()); } } // 当前节点下面子节点迭代器 Iterator<Element> it = node.elementIterator(); // 遍历 while (it.hasNext()) { // 获取某个子节点对象 Element e = it.next(); // 对子节点进行遍历 listNodes(e, params); } } /** * 从map中获取签名sign * @param paramsMap * @return * @throws Exception */ public static <T> String getSignFromParamMap(Map<String, T> paramsMap) throws Exception{ if (paramsMap != null && paramsMap.size() > 0) { Map<String, T> params = new TreeMap<String, T>(new Comparator<String>() { public int compare(String s1, String s2) { return s1.compareTo(s2); } }); params.putAll(paramsMap); StringBuffer tempStr = new StringBuffer(); for (Entry<String, T> param : params.entrySet()) { if (!"sign".equals(param.getKey()) && !"key".equals(param.getKey()) && !"".equals(param.getValue()) && param.getValue() != null) { tempStr.append(param.getKey() + "=" + param.getValue() + "&"); } } String temp = tempStr.toString().concat("key="+params.get("key")); return MD5Utils.getMD5(temp).toUpperCase(); } return null; } /** * 签名认证 * @param paramsMap * @return * @throws Exception */ public static <T> boolean checkSign(Map<String, T> paramsMap) throws Exception { String sign = getSignFromParamMap(paramsMap); return paramsMap.get("sign").equals(sign); } }
MD5Utils工具类
import java.security.MessageDigest; public class MD5Utils { // 十六进制下数字到字符的映射数组 private final static String[] HEXDIGITS = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d","e", "f" }; public final static String getMD5(String str){ if (str != null) { try { // 创建具有指定算法名称的信息摘要 MessageDigest md = MessageDigest.getInstance("MD5"); // 使用指定的字节数组对摘要进行最后更新,然后完成摘要计算 byte[] results = md.digest(str.getBytes()); // 将得到的字节数组变成字符串返回 StringBuffer resultSb = new StringBuffer(); String a = ""; for (int i = 0; i < results.length; i++) { int n = results[i]; if (n < 0) n = 256 + n; int d1 = n / 16; int d2 = n % 16; a = HEXDIGITS[d1] + HEXDIGITS[d2]; resultSb.append(a); } return resultSb.toString(); } catch (Exception ex) { ex.printStackTrace(); } } return null; } }
相关文章推荐
- 微信Oauth2.0鉴权 40029 问题
- weex技术交流微信群
- RF-微信文章
- 微信硬件蓝牙开发指南
- JavaScript判断微信浏览器实例代码
- JavaScript判断是否是微信浏览器
- Java微信语音开发
- ubuntu安装微信
- 第三方应用之微信登录与分享!
- 仿微博、微信QQ,包含展示动态(Feed)图片,和识别包含超链接、#字话题、@人的文本效果。
- android微信支付
- Android-仿微信菜单
- iOS 支付 [支付宝、银联、微信]
- 微信内置浏览器H5页面异常
- 微信分享到朋友圈失败,分享给朋友等正常
- iOS微信分享功能实现
- 微信网页开发之创建Controller(三)
- 微信公众帐号开发-消息及消息处理工具的封装
- 安卓版微信自带浏览器和IE6浏览器ajax请求abort错误处理
- 微信竟然还能当笔记用!【微信高级教程8】