第三方APP微信支付Java服务端构建步骤
来源:互联网 发布:拍卖系统源码 编辑:程序博客网 时间:2024/05/17 08:09
因日前工作需要,做了一次微信支付。其中一些关键点记录下来,以备不时之需,也拿出来交流下,如有不足之处,还望多多指教。
第三方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;}}
- 第三方APP微信支付Java服务端构建步骤
- 微信APP支付服务端(JAVA)
- 微信APP支付Java服务端
- java支付宝和微信app支付(服务端处理)
- Java 微信支付之APP支付服务端 (一)
- Android第三方sdk加入App微信支付解决方案
- APP服务端微信支付(PHP服务端)
- java版app微信支付服务端代码【手机app微信支付】
- 【手机app微信支付】app微信支付服务端(java)的实现
- Android第三方支付--微信支付
- 第三方支付之微信支付
- 第三方支付之微信支付
- java服务端–微信APP支付接口
- java服务端–微信APP支付接口
- java服务端–微信APP支付接口
- 三方 app微信支付 java后端实现
- App微信支付服务端流程
- APP 微信支付,服务端处理
- Linux 文件系统的创建与挂载方法
- Bootstrap插件预览
- mongodb安装教程。
- java中hashCode方法与equals方法的用法总结
- 使用UNIVOCITY-PARSERS创建和读取CSV文件
- 第三方APP微信支付Java服务端构建步骤
- 第7章 证据的效力如何
- 写给嵌入式程序员的循环冗余校验(CRC)算法入门引导
- Android中bindService的使用方法
- *leetcode #90 in cpp
- how to use cmake
- CodeForces - 676A Nicholas and Permutation (模拟) 水
- c++ stl容器set成员函数介绍及set集合插入,遍历等用法举例
- AngularJS数据绑定