天天看点

APP微信支付---Java服务端接口(不使用证书)开通微信支付介绍详细接口工具类改进总结

APP微信支付---Java服务端接口(不使用证书)

  • 开通微信支付
  • 介绍
  • 详细接口
    • service层,参数用户id,移动端传过来的
    • 获取预支付id
    • 返会给移动端的调用微信支付参数,商户号和APPID移动端存储
    • 回调接口请求微信统一下单的时候传过去的
    • 上面接口调用这个方法
    • 查询微信支付订单状态接口
  • 工具类
    • 生成签名方法,将请求参数转换为xml格式的string的方法 工具类
    • 加密类,生成签名时使用
    • HttpClient请求类
    • 解析XML工具类,将xml字符串转换成map
    • redis工具类,这个就不发了,网上一堆,把调用redis工具类的地方改成自己的方法就行
  • 改进
  • 总结

开通微信支付

参考微信支付接入商户文档 https://developers.weixin.qq.com/doc/oplatform/Mobile_App/WeChat_Pay/Vendor_Service_Center.html

APP支付API文档:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=8_1

直接使用微信正式环境测试

介绍

简单的集成APP支付,把商户号和APPID存到Android和IOS代码里面,防止调接口返回字段时,把这两个字段暴漏出去,私钥存到

Java后台代码里面,生成签名都是从后台生成,然后在返回给移动端。

详细接口

移动端请求接口获取调起微信支付的参数,服务端生成商户订单号,将订单号存入redis,设置时间大于2小时(微信支付生成的预支付订单号有效时长2小时),这个订单号后面回调的时候会用到,只是为了方便没有存入数据库,也可以存到数据库,下面是代码,工具类在最下面

service层,参数用户id,移动端传过来的

public String wxPaySubmitOrder(String accountId) throws Exception {
     	//生成商户订单号
        //格式化当前时间
		SimpleDateFormat sfDate = new SimpleDateFormat("yyyyMMddHHmmssSSS");
		String strDate = sfDate.format(new Date());
		String random = "TEST"+strDate;
        //订单号存入redis,过期时间12个小时,微信生成的与支付的订单号有效时长也是两个小时,值:用户id
        RedisHelper.setEX(random, accountId , 12 * 60);
        //测试默认费用1分钱,微信支付单位是分
        String pay = "1";
        //获取预支付id
        String paySubmitOrder = paySubmitOrder("测试", pay, random);
        if (paySubmitOrder != null) {
        	//app调起微信支付参数
        	Map<Object, Object> mmap = getWXPayParam(paySubmitOrder);
            return JSONUtil.toJSONString(mmap);
        }
        return null;
    }
           

获取预支付id

微信官方api文档:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1

/**
     * 获取预支付id接口
     * @param desc  描述
     * @param totleFee 花费 微信支付单位 分
     * @param outTradeNo 商户订单号
     * @return
     * @throws Exception
     */
public String paySubmitOrder(String desc, String totleFee, String outTradeNo) throws Exception {
       //组装参数,微信支付参数正序
       SortedMap<Object, Object> packageParams = new TreeMap<>();
       packageParams.put("appid", “123456”);//APPID,微信开放平台上可以看到
       packageParams.put("mch_id", “123456”); //商户编号,微信开放平台上可以看到
        packageParams.put("nonce_str", String.valueOf(System.currentTimeMillis()));  //随机字符串
        packageParams.put("body", desc); //商品描述
        packageParams.put("out_trade_no", outTradeNo); //商户订单号
        packageParams.put("total_fee", totleFee); //花费 单位分
        //回调地址,接收微信支付异步通知回调地址,通知url必须为直接可访问的url,不能携带参数。
        packageParams.put("notify_url", "http://www.weixin.qq.com/wxpay/pay.php"); 
        packageParams.put("trade_type", “APP”); //支付类型
        packageParams.put("spbill_create_ip", InetAddress.getLocalHost().getHostAddress()); //请求ip,可以从移动端传过来
        //生成签名的方法在最文章后面
        String sign = PayCommonUtil.createSign("UTF-8", packageParams, “123456”); //生成签名,最后一个参数是你的私钥,微信开放平台查看
        packageParams.put("sign", sign); //签名;
        //微信参数封装成XML格式的字符串,方法在文章最后面
        String requestXML = PayCommonUtil.getRequestXml(packageParams);
        //调用微信统一下单接口,得到含有prepay_id的XML,方法在文章最后面
        String resXml = HttpUtil.postData(“https://api.mch.weixin.qq.com/pay/unifiedorder”, requestXML);
        //解析XML转换成Map,方法在文章最后面
        Map map = XMLUtil.doXMLParse(resXml);
        //是否生成与支付订单号
        if(map.get("return_code").toString().equals("SUCCESS") && map.get("result_code").toString().equals("SUCCESS")){
       		//获取预支付订单号,返回到service层
            return map.get("prepay_id").toString();
        }
        return null;
    }
           

返会给移动端的调用微信支付参数,商户号和APPID移动端存储

微信官方api文档:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_12&index=2

/**
     * app调起微信支付参数
     * @param prepayid 预支付id
     * 返回参数移除APPID和商户id,移动端代码中有,接口不返回
     * @return
     */
    public Map<Object, Object> getWXPayParam(String prepayid) {
        SortedMap<Object, Object> packageParams = new TreeMap<>();
        packageParams.put("appid", “123456”);  //APPID
        packageParams.put("partnerid", “123456”); //商户号
        packageParams.put("prepayid",prepayid); //预支付id
        packageParams.put("noncestr",String.valueOf(System.currentTimeMillis())); //随机字符串
        packageParams.put("timestamp",System.currentTimeMillis()/1000); //时间戳
        packageParams.put("package","Sign=WXPay"); //默认参数
        String sign = PayCommonUtil.createSign("UTF-8", packageParams, “123456”); //生成签名,最后一个参数是你的私钥,微信开放平台查看
        packageParams.put("sign", sign); //签名
        packageParams.remove("appid"); //移除APPID
        packageParams.remove("partnerid"); //移除商户id
        return packageParams;
    }
           

回调接口请求微信统一下单的时候传过去的

public String wxPayNotify(HttpServletRequest request) {
        SortedMap<Object, Object> packageParams = new TreeMap<>();
        if (wxPayNotifyOperate(request)) {
            packageParams.put("return_code", "SUCCESS");
            packageParams.put("return_msg", "OK");
            return PayCommonUtil.getRequestXml(packageParams);
        }
        packageParams.put("return_code", "FAIL");
        packageParams.put("return_msg", "FAIL");
        return PayCommonUtil.getRequestXml(packageParams);
    }
           

上面接口调用这个方法

public boolean wxPayNotifyOperate(HttpServletRequest request) {
        //获取结果集
        InputStream inputStream;
        StringBuffer sb = new StringBuffer();
        try {
            inputStream = request.getInputStream();
            String s;
            BufferedReader in = new BufferedReader(new InputStreamReader(
                    inputStream, "UTF-8"));
            while ((s = in.readLine()) != null) {
                sb.append(s);
            }
            in.close();
            inputStream.close();
            //将返回的xml转换成map
            Map<String, String> map = XMLUtil.doXMLParse(sb.toString()); 
            if (map.get("return_code").equals("SUCCESS") && map.get("result_code").equals("SUCCESS")) {
                String transaction_id = map.get("transaction_id"); //微信订单号
                String out_trade_no = map.get("out_trade_no"); //商户订单号
                //查询redis中是否存在该商户订单号
                String data = RedisHelper.get(out_trade_no);
                if (data != null && !data.equals("")) {
                    //data == 用户id
                    String id = data;
                    //查询支付结果
                    boolean payResult = findPayResult(transaction_id, out_trade_no);
                    //支付成功,进行数据库操作
                    if (payResult) {
                       //进行数据库操作,省略,业务不同。

						
                        //操作完成删除key
                        RedisHelper.del(out_trade_no);
                    } else {
                    	//状态异常
                        return false;
                    }
                }
                return true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }
           

查询微信支付订单状态接口

微信官方api文档:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_2&index=4

/**
     * 查询订单结果
     * @param transactionId  微信订单号
     * @param outTradeNo 商户订单号
     * @return
     * @throws Exception
     */
    public boolean findPayResult(String transactionId, String outTradeNo) throws Exception {
        String timeMillis = String.valueOf(System.currentTimeMillis());
        SortedMap<Object, Object> packageParams = new TreeMap<>();
        packageParams.put("appid", "123456"); //APPID
        packageParams.put("mch_id", "123456"); //商户号
        packageParams.put("transaction_id", transactionId); //微信订单号
        packageParams.put("out_trade_no", outTradeNo); //商户订单号
        packageParams.put("nonce_str", timeMillis); //随机字符串
        String sign = PayCommonUtil.createSign("UTF-8", packageParams, “123456”); //生成签名,最后一个参数是你的私钥,微信开放平台查看
        packageParams.put("sign", sign); //签名
        String requestXML = PayCommonUtil.getRequestXml(packageParams);//组装参数xml格式
        //得到含有prepay_id的XML
        String resXml = HttpUtil.postData(“https://api.mch.weixin.qq.com/pay/orderquery”, requestXML);
        //解析XML存入Map
        Map map = XMLUtil.doXMLParse(resXml);
        //查询支付结果 SUCCESS—支付成功
        if(map.get("return_code").toString().equals("SUCCESS") && map.get("result_code").toString().equals("SUCCESS")){
        	//支付状态 == SUCCESS ,支付成功
            if(map.get("trade_state").toString().equals("SUCCESS")){
                return true;
            }
        }
        return false;
    }
           

工具类

生成签名方法,将请求参数转换为xml格式的string的方法 工具类

public class PayCommonUtil {
	/** 
	 * @Description:sign签名 
	 * @return 
	 */  
	public static String createSign(String characterEncoding, SortedMap<Object, Object> packageParams, String API_KEY) {  
		StringBuffer sb = new StringBuffer();  
		Set es = packageParams.entrySet();  
		Iterator it = es.iterator();  
		while (it.hasNext()) {  
			Map.Entry entry = (Map.Entry) it.next();  
			String k = entry.getKey().toString();  
			String v = entry.getValue().toString();  
			if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {  
				sb.append(k + "=" + v + "&");  
			}  
		}  
		sb.append("key=" + API_KEY);  
		String sign = WeixMD5.MD5Encode(sb.toString(), characterEncoding).toUpperCase();  
		return sign;  
	}  
	   
	/** 
	 * @Description:将请求参数转换为xml格式的string 
	 * @return 
	 */  
	public static String getRequestXml(SortedMap<Object, Object> parameters) {  
		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 = entry.getKey().toString();  
			String v = entry.getValue().toString();   
			if ("attach".equalsIgnoreCase(k) || "body".equalsIgnoreCase(k) || "sign".equalsIgnoreCase(k)) {  
				sb.append("<" + k + ">"  + v + "</" + k + ">");  
			} else {  
				sb.append("<" + k + ">" + v + "</" + k + ">");  
			}  
		}  
		sb.append("</xml>");  
		return sb.toString();  
	}  
}
           

加密类,生成签名时使用

public class WeixMD5
{

	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];
    }
	
	 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 final String hexDigits[] = { "0", "1", "2", "3", "4", "5","6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };

}
           

HttpClient请求类

public class HttpUtil {
    private final static int CONNECT_TIMEOUT = 100000   ;
    private final static String DEFAULT_ENCODING = "UTF-8";

    public static String postData(String urlStr, String data) {
        BufferedReader reader = null;
        try {
            URL url = new URL(urlStr);
            URLConnection conn = url.openConnection();
            conn.setDoOutput(true);
            conn.setConnectTimeout(CONNECT_TIMEOUT);
            conn.setReadTimeout(CONNECT_TIMEOUT);
            OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream(), DEFAULT_ENCODING);
            if (data == null)
                data = "";
            writer.write(data);
            writer.flush();
            writer.close();
            reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), DEFAULT_ENCODING));
            StringBuilder sb = new StringBuilder();
            String line = null;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
                sb.append("\r\n");
            }
            return sb.toString();
        } catch (IOException e) {
        
        } finally {
            try {
                if (reader != null)
                    reader.close();
            } catch (IOException e) {
            }
        }
        return null;
    }
}
           

解析XML工具类,将xml字符串转换成map

public class XMLUtil
{
	public static Map doXMLParse(String strxml) throws Exception
	{
		strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");
		if (null == strxml || "".equals(strxml))
		{
			return null;
		}
		Map m = new HashMap();
		InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8"));
		SAXBuilder builder = new SAXBuilder();
		Document doc = builder.build(in);
		Element root = doc.getRootElement();
		List list = root.getChildren();
		Iterator it = list.iterator();
		while (it.hasNext())
		{
			Element e = (Element) it.next();
			String k = e.getName();
			String v = "";
			List children = e.getChildren();
			if (children.isEmpty())
			{
				v = e.getTextNormalize();
			}
			else
			{
				v = XMLUtil.getChildrenText(children);
			}
			m.put(k, v);
		}
		//关闭流  
		in.close();
		return m;
	}

	/** 
	 * 获取子结点的xml 
	 * @param children 
	 * @return String 
	 */
	public static String getChildrenText(List children)
	{
		StringBuffer sb = new StringBuffer();
		if (!children.isEmpty())
		{
			Iterator it = children.iterator();
			while (it.hasNext())
			{
				Element e = (Element) it.next();
				String name = e.getName();
				String value = e.getTextNormalize();
				List list = e.getChildren();
				sb.append("<" + name + ">");
				if (!list.isEmpty())
				{
					sb.append(XMLUtil.getChildrenText(list));
				}
				sb.append(value);
				sb.append("</" + name + ">");
			}
		}
		return sb.toString();
	}
}
           

redis工具类,这个就不发了,网上一堆,把调用redis工具类的地方改成自己的方法就行

改进

我把生成的商户订单号存入redis中,设置了12个小时,但是也可能会过期,所以最好存到数据库。

总结

主要流程是:移动端请求>>服务端生成商户订单号,存入redis,请求统一下单接口,获取预支付id,组装移动端调起微信支付参数>>返回移动端>>移动端组装参数,调起微信支付>>支付成功,回调本地接口,获取接口中商户订单号,查询redis中是否存在相应的key,如果存在,判断交易状态是否是成功,如果成功,进行业务操作,删除redis中等于商户订单号的key。

写的非常简单,有很多不足,希望可以帮我改进。

继续阅读