【APP支付】关于APP微信支付那些事

关于支付这一块,在没有接触之前,感觉还是很神奇的,以为会很麻烦,在同事写了一半的代码上,参考微信API支付文档,一天就搞定了。

在这之前,第一次接触支付的时候,以为只能用服务器测支付的,但是发现调试很麻烦,还要看服务器上的tomcat的打印信息,这次的测试服务器还有点问题,tomcat一直打印一些刷新xml文件的错误信息,但是不影响项目的正常使用。这就很尴尬了,写好了,没法测了都。。。。

在向同事请教之后,同事让我弄个花生壳试试。

之前听说过花生壳,但是具体没有用过,在安装之后,注册账号,开通了一个体验版的,6块钱永久的,用来测试支付还是不错的。发现这就是一个内网渗透的工具,我做毕业设计的时候找了一个免费的,但是不稳定,网速还特别慢,这个花生壳还挺好用的。

直接百度花生壳,进去官网,注册账号,然后会弹出来购买域名啥的,6块钱弄个体验版的玩玩就可以。

在下图的控制台里面,配置内网渗透的ip,也就是你的电脑的ip和映射的端口。
【APP支付】关于APP微信支付那些事_第1张图片
【APP支付】关于APP微信支付那些事_第2张图片
【APP支付】关于APP微信支付那些事_第3张图片
由于体验版只有一个映射到80端口的地址,部署到测试服务器上之后,记得将内网主机改为测试服务器的ip和端口。

这个外网访问的ip地址,就是后面,项目中配置的回调地址的ip,这个很重要,每次支付成功之后,如果没有进入回调方法,首先要确定,这个ip能访问到你的tomcat,这个必须保证。

下面才是支付开始。
首先要理清楚的就是APP支付的流程,这里贴上微信支付文档里的图:
【APP支付】关于APP微信支付那些事_第4张图片
可能第一次看,看不出来个所以然来,比如我,之前看了半天,还是一脸懵逼的。

如果你也看不出来什么,那么也不用着急,希望你看完这篇博客之后,再回来看这个图,能有所收获。

首先就是微信的配置文件:Configure.java

package com.thinkgem.jeesite.modules.weixin.util;

public class Configure {
	    //商户支付密钥
		public static final String WEIXIN_KEY = "NUQKAR2IB2UOFP1JGDYBIMPXRDKSHD";
		//应用ID
		public static final String WEIXIN_APPID = "wx95e64fc7ddc53f46";
		//应用AppSecret
		public static final String WEIXIN_APPSECRET = "b1c864f6041ff345345e165643a1790543";
		//商户号
		public static final String WEIXIN_MCHID = "1522275497";
		// 交易类型
		public static final String TRADE_TYPE = "APP";
		//支付回调notify_url
		public static final String NOTIFY_URL = "http://上面注册的花生壳的域名/ejtapp/PayCtrl/WeiXinNotifyUrl.do";
		//订单描述
		public static final String BODY = "e境通-商城消费";
		
		public static final String RECHARGE = "e境通-会员充值";
		//扩展字段
		public static final String PACKAGE_INFO="Sign=WXPay";
		//获取tokenurl
		public static final String GET_ACCESSTOKEN_URL="https://api.weixin.qq.com/sns/oauth2/access_token";
		//获取用户信息url
		public static final String GET_USERINFO_URL="https://api.weixin.qq.com/sns/userinfo";
		
}

配置文件里的这些字段的内容,都是可以在微信商户平台上可以找到的,这些字段信息在后面用到的地方还是比较多的,所以抽离出来作为一个单独的文件。

然后是第一步:先生成预支付交易单
具体文档地址:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1
【APP支付】关于APP微信支付那些事_第5张图片
具体的参数说明可以在上面的文档说明里面找到,关于请求参数里面的必填字段,都是必须要传的。

既然要生成预支付订单,肯定是先从APP端发起请求到服务端,将订单数据传过来,由服务器跟微信支付的服务器进行交流,所以需要先写一个接口,来告诉服务器,有新订单要用微信付款了。

	/**
	 * 微信支付订单
	 * 
	 * @param request
	 * @param response
	 * @param data
	 * @return
	 */
	@RequestMapping("/weiXinPay")
	@ResponseBody
	public JSONObject WeiXinPay(HttpServletRequest request, HttpServletResponse response, String data) {
		JSONObject obj = JSON.parseObject(data);
		String orderId = obj.getString("orderId");	// APP端传来订单编号
		System.out.println("========APP调用微信统一下单接口========");
		ShopOrder findByOrderId = shopOrderService.findByOrderId(orderId);	// 利用订单编号,获取订单信息
		Map<String, String> wxAppPay = null;
		try {
			wxAppPay = WechatPayService.WxAppPay(findByOrderId);	// 将订单实体传入微信统一下单接口
			System.out.println("返回的Map数据wxAppPay:" + wxAppPay);
		} catch (Exception e) {
			e.printStackTrace();
			return JsonUtil.getJson(40, null, "下单失败");
		}
		System.out.println("result_code:" + wxAppPay.get("result_code"));
		System.out.println("return_code:" + wxAppPay.get("return_code"));
		if (wxAppPay.get("result_code").equals("SUCCESS") && wxAppPay.get("return_code").equals("SUCCESS")) {
			boolean tenpaySign = WeiXinPayUtil.isTenpaySign(wxAppPay);
			System.out.println("验证签名结果:" + tenpaySign);
			if (tenpaySign) {
				// 封装返回信息
				SortedMap<String, String> map = new TreeMap<String, String>();
				map.put("appid", Configure.WEIXIN_APPID);
				map.put("partnerid", Configure.WEIXIN_MCHID);
				map.put("prepayid", wxAppPay.get("prepay_id"));	// 预支付交易会话标识
				map.put("package", Configure.PACKAGE_INFO);
				map.put("noncestr", wxAppPay.get("nonce_str"));	//随机字符串
				map.put("timestamp", String.valueOf(System.currentTimeMillis()).substring(0, 10));
				String Sign = WeiXinPayUtil.createSign("UTF-8", map);
				map.put("sign", Sign);
				System.out.println("==========微信统一下单成功=======");
				return JsonUtil.getJson(10, map, "微信统一下单成功");
			} else {
				return JsonUtil.getJson(30, null, "微信返回签名验证失败");
			}
		} else {
			return JsonUtil.getJson(20, null, "微信统一下单失败");
		}
		//return JsonUtil.getJson(40, null, "下单失败");
	}

下面会将所用到的工具和方法,全部贴出来,只要一步步来就可以了:
WechatPayService.java

	/**
	 * 商品订单
	 * @param order
	 * @return
	 * @throws Exception
	 */
	public static Map<String, String> WxAppPay(ShopOrder order) throws Exception {
		SortedMap<String, String> map = new TreeMap<String, String>();
		map.put("appid", Configure.WEIXIN_APPID); // 应用ID
		map.put("mch_id", Configure.WEIXIN_MCHID); // 商户号
		map.put("nonce_str", WeiXinPayUtil.getRandomStringByLength(32)); // 随机串
		map.put("body", Configure.BODY); // 商品内容
		map.put("attach", "order");		// 附加数据 表示此次支付的是商品订单
		map.put("out_trade_no",String.valueOf( order.getOrderSn())); // 订单号
		//计算出总金额以分为单位
		String changeY2F = WeiXinPayUtil.changeY2F(order.getOrderAmount());
		//System.out.println(mul+":"+changeY2F);
		map.put("total_fee",changeY2F);// 支付金额 以分为单位 
		map.put("spbill_create_ip",InetAddress.getLocalHost().getHostAddress()); // 机器IP
		//map.put("time_expire", ""); // 过期时间  可根据需要是否添加
		map.put("notify_url", Configure.NOTIFY_URL); // 回调  通知地址
		map.put("trade_type", Configure.TRADE_TYPE);// 交易类型
		//String xml = WXPayUtil.ArrayToXml(map, "你的商户KEY");
		//System.out.println(map);
		String Sign = WeiXinPayUtil.createSign("UTF-8",map);
		map.put("sign", Sign);
		String xml = WeiXinPayUtil.ArrayToXml(map);
		//System.out.println(xml);
		String sendPost = WeiXinPayUtil.httpsRequest("https://api.mch.weixin.qq.com/pay/unifiedorder", "POST",xml);
		Map<String, String> xmlToMap = WeiXinPayUtil.xmlToMap(sendPost);
		return xmlToMap;
	}

WeiXinPayUtil.java

package com.thinkgem.jeesite.modules.weixin.util;
 
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

import javax.servlet.http.HttpServletRequest;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.log4j.Logger;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xmlpull.v1.XmlPullParserException;

import com.thinkgem.jeesite.modules.ejt.util.MD5Util;
 
 
public class WeiXinPayUtil {
	
	/**
	 * 日志记录
	 */
	private static Logger logger = Logger.getLogger(WeiXinPayUtil.class);
	
	public static void main(String[] args) throws Exception {
		Map<String, String> xmlToMap = xmlToMap("");
		System.err.println(xmlToMap);
	}
	
	/**
	 * 将map转换为xml
	 * @param arr
	 * @return
	 */
	public static String ArrayToXml(Map<String, String> arr) {
		String xml = "";
 
		Iterator<Entry<String, String>> iter = arr.entrySet().iterator();
		while (iter.hasNext()) {
			Entry<String, String> entry = iter.next();
			String key = entry.getKey();
			String val = entry.getValue();
			xml += "<" + key + ">" + val + " + key + ">";
		}
 
		xml += "";
		return xml;
	}
	
    /**
     * 请求方法
     * @param requestUrl
     * @param requestMethod
     * @param outputStr
     * @return
     */
    public static String httpsRequest(String requestUrl, String requestMethod, String outputStr) {  
          try {  
               
              URL url = new URL(requestUrl);  
              HttpURLConnection conn = (HttpURLConnection) url.openConnection();  
              
              conn.setDoOutput(true);  
              conn.setDoInput(true);  
              conn.setUseCaches(false);  
              // 设置请求方式(GET/POST)  
              conn.setRequestMethod(requestMethod);  
              conn.setRequestProperty("content-type", "application/x-www-form-urlencoded");  
              // 当outputStr不为null时向输出流写数据  
              if (null != outputStr) {  
                  OutputStream outputStream = conn.getOutputStream();  
                  // 注意编码格式  
                  outputStream.write(outputStr.getBytes("UTF-8"));  
                  outputStream.close();  
              }
              // 从输入流读取返回内容  
              InputStream inputStream = conn.getInputStream();  
              InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");  
              BufferedReader bufferedReader = new BufferedReader(inputStreamReader);  
              String str = null;  
              StringBuffer buffer = new StringBuffer();  
              while ((str = bufferedReader.readLine()) != null) {  
                  buffer.append(str);  
              }  
              // 释放资源  
              bufferedReader.close();  
              inputStreamReader.close();  
              inputStream.close();  
              inputStream = null;  
              conn.disconnect();  
              return buffer.toString();  
          } catch (ConnectException ce) {  
              logger.info("连接超时:{}"+ ce);  
          } catch (Exception e) {  
        	  logger.info("https请求异常:{}"+ e);  
          }  
          return null;  
      } 
	    
	/**
	 * 微信回调参数解析
	 * @param request
	 * @return
	 */
	public static String getPostStr(HttpServletRequest request){
    	 StringBuffer sb = new StringBuffer();
    	 try {
         InputStream is = request.getInputStream();  
         InputStreamReader isr = new InputStreamReader(is, "UTF-8");  
         BufferedReader br = new BufferedReader(isr);  
         String s = "";  
        
			while ((s = br.readLine()) != null) {  
			     sb.append(s);  
			 }
		} catch (IOException e) {			
			e.printStackTrace();
		}  
         String xml = sb.toString(); //次即为接收到微信端发送过来的xml数据  
         logger.info(xml+"========================");
         return xml;
    }
	
	  //定义签名,微信根据参数字段的ASCII码值进行排序 加密签名,故使用SortMap进行参数排序
    public static String createSign(String characterEncoding,Map<String, String> parameters){
        StringBuffer sb = new StringBuffer();
        Set es = parameters.entrySet();
        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=" + Configure.WEIXIN_KEY);//最后加密时添加商户密钥,由于key值放在最后,所以不用添加到SortMap里面去,单独处理,编码方式采用UTF-8
        String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase();
        return sign;
    }
    /**
     * 获取随机字符串
     * @param length
     * @return
     */
    public static String getRandomStringByLength(int length) {  
        String base = "abcdefghijklmnopqrstuvwxyz0123456789";  
        Random random = new Random();  
        StringBuffer sb = new StringBuffer();  
        for (int i = 0; i < length; i++) {  
            int number = random.nextInt(base.length());  
            sb.append(base.charAt(number));  
        }  
        return sb.toString();  
    }  
  //元转分
    public static String changeY2F(String amount) {
        String currency = amount.replaceAll("\\$|\\¥|\\,", ""); // 处理包含, ¥
                                                                // 或者$的金额
        int index = currency.indexOf(".");
        int length = currency.length();
        Long amLong = 0l;
        if (index == -1) {
            amLong = Long.valueOf(currency + "00");
        } else if (length - index >= 3) {
            amLong = Long.valueOf((currency.substring(0, index + 3)).replace(".", ""));
        } else if (length - index == 2) {
            amLong = Long.valueOf((currency.substring(0, index + 2)).replace(".", "") + 0);
        } else {
            amLong = Long.valueOf((currency.substring(0, index + 1)).replace(".", "") + "00");
        }
        return amLong.toString();
    }
    
    /**
     * 分转元  
     * 这个是真的被坑到了,在后面进行金额校验时,微信返回的支付金额仍然是分
     * @param amount
     * @return
     */
    public static String fenToYuan(String amount){    
   	 /**金额为分的格式 */    
       if(!amount.matches("\\-?[0-9]+")) {    
           logger.error(" FuYouFile 金额格式有误");
           return "0";
       }    
       return BigDecimal.valueOf(Long.valueOf(amount)).divide(new BigDecimal(100)).toString();    
   } 
    
    /**
     * 验证回调签名
     * @return
     */
    public static boolean isTenpaySign(Map<String, String> map) {
    	String characterEncoding="utf-8";
       String charset = "utf-8";
       String signFromAPIResponse = map.get("sign");
       if (signFromAPIResponse == null || signFromAPIResponse.equals("")) {
    	   System.out.println("API返回的数据签名数据不存在,有可能被第三方篡改!!!"); 
    	   return false;
       }
       System.out.println("服务器回包里面的签名是:" + signFromAPIResponse);
     //过滤空 设置 TreeMap
       SortedMap<String,String> packageParams = new TreeMap();
       
       for (String parameter : map.keySet()) {
    	   String parameterValue = map.get(parameter);
    	   String v = "";
    	   if (null != parameterValue) {
    		   v = parameterValue.trim();
    	   }
    	   packageParams.put(parameter, v);
       }
       
       StringBuffer sb = new StringBuffer();
       Set es = packageParams.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(!"sign".equals(k) && null != v && !"".equals(v)) {
    		   sb.append(k + "=" + v + "&");
    	   }
       }
       sb.append("key=" + Configure.WEIXIN_KEY);
       
     //将API返回的数据根据用签名算法进行计算新的签名,用来跟API返回的签名进行比较
     //算出签名
       String resultSign = "";
       String tobesign = sb.toString();
       
       if (null == charset || "".equals(charset)) {
    	   resultSign = MD5Util.MD5Encode(tobesign, characterEncoding).toUpperCase();
       }else{
    	   try{
    		   resultSign = MD5Util.MD5Encode(tobesign, characterEncoding).toUpperCase();
    	   }catch (Exception e) {
    		   resultSign = MD5Util.MD5Encode(tobesign, characterEncoding).toUpperCase();
    	   }
       }
       
       String tenpaySign = ((String)packageParams.get("sign")).toUpperCase();
       return tenpaySign.equals(resultSign);
    }
	/**
	 * 解析xml为map
	 * @param xml
	 * @return
	 * @throws Exception 
	 * @throws XmlPullParserException
	 * @throws IOException
	 */
	public static Map<String, String> xmlToMap(String strXML) throws Exception {
		try{
			Map<String, String> data = new HashMap<String, String>();
			DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
			DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
			InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
			org.w3c.dom.Document doc = documentBuilder.parse(stream);
			doc.getDocumentElement().normalize();
			NodeList nodeList = doc.getDocumentElement().getChildNodes();
			for (int idx = 0; idx < nodeList.getLength(); ++idx) {
				Node node = nodeList.item(idx);
				if (node.getNodeType() == Node.ELEMENT_NODE) {
					org.w3c.dom.Element element = (org.w3c.dom.Element) node;
					data.put(element.getNodeName(), element.getTextContent());
				}
			}
			try {
				stream.close();
			} catch (Exception ex){
				// do nothing
			}
			return data;
		}catch (Exception e) {
		}
		return null;
	}
}

MD5Util.java

package com.thinkgem.jeesite.modules.ejt.util;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;

import javax.servlet.http.HttpServletRequest;

public class MD5Util {
	   private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5",
		        "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };

		
	// 字符串拼接
	public static String getDigest(TreeMap<String, String> map, String key,
			String value) {
		StringBuilder sb = new StringBuilder();
		for (Map.Entry entry : map.entrySet()) {
			sb = sb.append(entry.getKey()).append("=").append(entry.getValue())
					.append("&");
		}
		sb.append(key).append("=").append(value);
		System.out.println("拼接后的字符:" + sb.toString());

		return sb.toString();
	}

    /* 
     * 加密算法 
     */  
    public static String encode(String text) throws UnsupportedEncodingException{  
          
            try {  
                MessageDigest digest = MessageDigest.getInstance("md5");  
                //digest.update(str.getBytes("UTF-8"));  
                byte[] result = digest.digest(text.getBytes("UTF-8"));  
                StringBuilder sb =new StringBuilder();  
                for(byte b:result){  
                    int number = b&0xff;  
                    String hex = Integer.toHexString(number);  
                    if(hex.length() == 1){  
                        sb.append("0"+hex);  
                    }else{  
                        sb.append(hex);  
                    }  
                }  
                return sb.toString().toUpperCase();  
            } catch (NoSuchAlgorithmException e) {  
                // TODO Auto-generated catch block  
                e.printStackTrace();  
            }  
          
        return "" ;  
    }  
    
    //通过io流获取请求参数
    public static TreeMap<String, String> getParamString(HttpServletRequest request) {  
        Map<String, String[]> map = request.getParameterMap();  
       TreeMap<String, String> treeMap = new TreeMap<String, String>();  
      
      
            for (Entry<String, String[]> e : map.entrySet()) { 
            	String key = e.getKey();
            	String[] value = e.getValue();  
            	if(!"sign".equals(key)&&value != null && value.length == 1&&!"".equals(value[0])){
            		treeMap.put(key, value[0]);
            	}
            }  
       
        return treeMap;  
    }  
    
    
    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 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];
    }
}

到这里,我们总结一下,目前到底做了哪些事情,支付走到哪一步了。

  1. 首先是APP端告诉服务器有新订单要用微信支付了。
  2. 服务器将订单信息告诉微信服务器,你要准备替我收款了。
  3. 微信服务器收到提醒,告诉APP,开始收钱了,收哪个单子的钱,要收多少钱。
    接口将微信服务器返回的信息以map的形式返回给APP端,APP端来唤起微信支付,让用户输密码支付。其中包括 预支付交易会话标识:prepay_id

然后服务器就等微信服务器和APP端他俩交流了。

如果用户支付成功,微信服务器便开始请求回调函数,也就是我们之前在Configure.java里面定义的回调地址的接口,这个接口在微信文档里面有说明
https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_7&index=3
【APP支付】关于APP微信支付那些事_第6张图片
这里再次强调一下,回调函数的接口链接,必须要外网能访问到才可以成功回调,否则微信收不到回调结果,会在一定时间间隔内,多次发起回调请求。微信总共会发起10次通知,每次通知时间距离最近一次的间隔为15/15/30/180/1800/1800/1800/1800/3600,单位:秒),但微信不保证通知最终一定能成功。也就是说,如果回调接口挂了,用户明明付款成功了,但是数据库里的订单状态还未来得及更新,会仍然显示订单未支付。

	/**
	 * 微信订单支付回调通知地址
	 * 
	 * @param request
	 * @param response
	 * @param data
	 * @throws Exception
	 */
	@RequestMapping("/WeiXinNotifyUrl")
	@ResponseBody
	public String WeiXinNotifyUrl(HttpServletRequest request, HttpServletResponse response)
			throws Exception {
		System.out.println("=========微信支付统一回调=========");
		InputStream inStream;
		Map<String, String> params = null;
		inStream = request.getInputStream();
		ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
		byte[] buffer = new byte[1024];
		int len = 0;
		while ((len = inStream.read(buffer)) != -1) {
			outSteam.write(buffer, 0, len);
		}
		String resultxml = new String(outSteam.toByteArray(), "utf-8");
		params = WeiXinPayUtil.xmlToMap(resultxml);
		outSteam.close();
		inStream.close();
		if (!WeiXinPayUtil.isTenpaySign(params)) {
			// 支付失败,签名验证失败
			return setXml("FAIL", "return_code不正确");
		} else {
			System.out.println("===============付款成功==============");
			// ------------------------------
			// 处理业务开始
			// ------------------------------
			// 此处处理订单状态,结合自己的订单数据完成订单状态的更新
			// ------------------------------
			String orderNum = params.get("out_trade_no");
			System.out.println("微信支付回调返回的订单号:" + orderNum);
			String transactionId = params.get("transaction_id");	//微信支付单号
			String totaFee = params.get("total_fee");
			System.out.println("微信返回的订单金额:" + totaFee);		// 用来对比订单中的金额,防止数据被恶意篡改
			String attach = params.get("attach");	// 订单类别  这个字段是前面自定义的字段内容 
			if (attach.equals("order")) {	// 商品订单支付回调更新订单数据
				ShopOrder findByOrderNo = shopOrderService.findByOrderNo(orderNum);
				if (totaFee.equals(findByOrderNo.getOrderAmount())) {
					findByOrderNo.setPaySn(transactionId);
					findByOrderNo.setOrderState("20");
					
					// 更新订单状态
					int selective = shopOrderService.updateByPrimaryKeySelective(findByOrderNo);
					if (selective > 0) {
						System.out.println("===============更新商品订单成功==============");
						return setXml("SUCCESS", "OK");
					}
				}
			}else if (attach.equals("renew")) {	// 会员续费订单支付回调更新续费记录
				boolean updateRecordLog = updateRecordLog(orderNum, "wechat", transactionId);
				if (updateRecordLog) {
					return setXml("SUCCESS", "OK");
				}
			}else {	// 会员充值订单支付回调更新充值记录
				ShopPdRecharge recharge = shopPdRechargeService.selectByPdrSn(orderNum);
				recharge.setPdrPaymentCode("wechat");
				recharge.setPdrPaymentName("微信");
				recharge.setPdrTradeSn(transactionId);
				recharge.setPdrPaymentTime(ZoscDateUtil.getTime());
				int updateRecharge = shopPdRechargeService.updateByPrimaryKeySelective(recharge);
				
				ShopMember member = shopMemberService.findByMemberId(recharge.getPdrMemberId());
				String oldBalance = member.getAvailablePredeposit();
				String newBalance = BigDecimalUtil.strAdd(oldBalance, recharge.getPdrAmount());
				member.setAvailablePredeposit(newBalance);
				int updateMember = shopMemberService.updateByPrimaryKeySelective(member);
				
				if (updateRecharge + updateMember > 1) {
					System.out.println("===============更新充值订单成功==============");
					return setXml("SUCCESS", "OK");
				}
			}
			return null;
		}
	}

	// 通过xml 发给微信消息
	public static String setXml(String return_code, String return_msg) {
		return " + return_code + "]]>" + " + return_msg
				+ "]]>";
	}

可以看到,回调是微信服务器请求项目服务器,返回的是"SUCCESS", "OK"两个参数,告诉微信,我知道支付成功了,数据库中的订单状态也改好了,你不用请求了。这点可以在下图中可以看到
【APP支付】关于APP微信支付那些事_第7张图片
并且在这一页的最下面可以看到返回的数据格式:
【APP支付】关于APP微信支付那些事_第8张图片

到这里,可以说微信支付已经初步完成了,订单状态也更新完了,但是微信API文档里面,还有一个查询订单的玩意,这个是用来干嘛的呢?看下图
【APP支付】关于APP微信支付那些事_第9张图片
【APP支付】关于APP微信支付那些事_第10张图片
应用场景:
该接口提供所有微信支付订单的查询,商户可以通过该接口主动查询订单状态,完成下一步的业务逻辑。

需要调用查询接口的情况:

◆ 当商户后台、网络、服务器等出现异常,商户系统最终未接收到支付通知;
◆ 调用支付接口后,返回系统错误或未知交易状态情况;
◆ 调用被扫支付API,返回USERPAYING的状态;
◆ 调用关单或撤销接口API之前,需确认支付状态;

用咱们自己的话说就是,用户输完交易密码支付成功了,回到APP里面,可以主动查询一下这个订单是否支付成功了,如果查询微信服务器里显示订单支付成功了,可以再次判断数据库里订单信息是否更新成功了,如果没有更新成功,可能是网络延迟或者回调函数接口除了问题。

可以看这页带图的文档:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=8_1 ,中的第五步第五步:回跳到商户APP中,商户APP根据支付结果个性化展示订单处理结果。见图8.5。
【APP支付】关于APP微信支付那些事_第11张图片

	/**
	 * 查询微信支付结果接口
	 * 
	 * @param httpServletRequest
	 * @return
	 */
	@ResponseBody
	@RequestMapping("orderQuery")
	public JSONObject orderQuery(HttpServletRequest request, HttpServletResponse response, String data) {
		JSONObject obj = JSON.parseObject(data);
		String out_trade_no = obj.getString("out_trade_no");
		String transactionId = obj.getString("transaction_id");	// 微信预支付id即是prepayid
		// 根据订单号查询订单
		ShopOrder findByOrderNo = shopOrderService.findByOrderNo(out_trade_no);
		if (findByOrderNo.getOrderState().equals("20")) {
			return JsonUtil.getJson(10, null, "支付成功");
		} else {
			String nonce_str = WeiXinPayUtil.getRandomStringByLength(32);
			SortedMap<String, String> map = new TreeMap<String, String>();
			map.put("appid", Configure.WEIXIN_APPID);
			map.put("mch_id", Configure.WEIXIN_MCHID);
			map.put("transaction_id", transactionId );// 优先使用微信支付id进行查询
			map.put("nonce_str", nonce_str);
			String sign = WeiXinPayUtil.createSign("UTF-8", map);
			map.put("sign", sign);
			String xml = WeiXinPayUtil.ArrayToXml(map);
			System.out.println(xml);
			String sendPost = WeiXinPayUtil.httpsRequest("https://api.mch.weixin.qq.com/pay/orderquery", "POST", xml);
			Map<String, String> xmlToMap = null;
			try {
				xmlToMap = WeiXinPayUtil.xmlToMap(sendPost);
			} catch (Exception e) {
				e.printStackTrace();
			}
			if (xmlToMap.get("return_code ").equals("SUCCESS") && xmlToMap.get("result_code ").equals("SUCCESS")) {
				// 这里可以判断订单状态是否更新
				return JsonUtil.getJson(10, null, "支付成功");
			} else {
				return JsonUtil.getJson(20, null, "支付失败");
			}
		}

	}

可以看到,这个查询的接口的流程就是:

  1. APP请求商户服务器,参数是订单编号或者支付单号即prepayid,这两个参数都可以在APP端找到,微信建议优先使用prepayid查询。
  2. 商户服务器去微信服务器上查询,如果返回的数据中的return_coderesult_code 都是SUCCESS证明该订单支付成功了。
  3. 服务器判断订单状态是否更新,如果没有更新,证明回调函数没有正确返回"SUCCESS", "OK",需要重新更新一下订单状态,然后告诉APP,支付成功了,修改成功了。

到这里,APP微信支付就算彻底完成了,看到这,当你再回过头来看博客前面提到的微信支付流程图,是否有那么一点点的理解了呢?

如果有什么意见或问题,欢迎评论交流!

你可能感兴趣的:(【总结】)