第三方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;}}




3 0
原创粉丝点击