微信支付开发总结(JSAPI支付)

微信支付开发总结

其实整体来讲做微信支付不难,由于第一次开发,前面一两天需要阅读它官方的开发文档和下载sdk看看它官方提供的源码,不得不吐槽一下,官方文档做的不是很好,有些问题讲得不够详细,并且提供的sandbox测试,一直出现网络超时的问题,只有让测试方用真实支付进行测试

首先是看微信的文档,了解它支付的整个流程

官方网址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_1

JSAPI支付流程图:

微信支付开发总结(JSAPI支付)_第1张图片

流程认识:

第一步:就是将生成支付的金额页面或者是支付需要扫的二维码
例如:
微信支付开发总结(JSAPI支付)_第2张图片
第二步:
①用户通过操作前端页面向后台发送下单请求,传入支付金额等所必须的参数,
②随后后台调用微信的统一下单API进行,并对API响应的参数,进行金额,签名等进行验证,所有验证通过后,
③给前端返回所需要的参数,前端给微信发送请求,将后台返回的参数进行配置,配置成功即能进行支付操作
①请求参数(必填中‘是’的参数,统一下单时必须传)
微信支付开发总结(JSAPI支付)_第3张图片微信支付开发总结(JSAPI支付)_第4张图片微信支付开发总结(JSAPI支付)_第5张图片
例如参数:


   wx2421b1c4370ec43b
   支付测试
   JSAPI支付测试
   10000100
   
   1add1a30ac87aa2db72f57a2375d8fec
   http://wxpay.wxutil.com/pub_v2/pay/notify.v2.php
   oUpF8uMuAJO_M2pxb1Q9zNjWeS6o
   1415659990
   14.23.150.211
   1
   JSAPI
   0CB01533B8C1EF103065174F50BCA001
 
  • 代码:
		String notifyUrl = portalURL + ":" + serverPort + "/recharge/notify";

        AppConfig config = new AppConfig();
        WXPay wxpay = new WXPay(config, notifyUrl, false, false);

        Map data = new TreeMap<>();
        //put
        data.put("appid", appId);
        data.put("mch_id", mchId);
        data.put("body", prepareOrderBean.getBody() + new Date());
        data.put("product_id", "12");
        data.put("device_info", "WEB");
        data.put("fee_type", "CNY");
        data.put("nonce_str", PayCommonUtil.CreateNoncestr());
        data.put("notify_url", notifyUrl);
        data.put("openid", prepareOrderBean.getOpenId());
        data.put("out_trade_no", prepareOrderBean.getOutTradeNo());
        data.put("spbill_create_ip", "123.12.12.123");
        data.put("total_fee", "1");
        data.put("trade_type", "JSAPI");

        //调用统一下单api
        Map unifiedOrder = wxpay.unifiedOrder(data);

下单成功后会返应的一些参数,例如:


   
   
   
   
   
   
   
   
   
   
 

验证数据:我们需要对返回的一些参数进行判断,判断下单是否成功,对签名进行验证,防止第三方恶意篡改签名
下单成功后向前端返回相应的参数,这里注意的是签名问题:这里签名是根据你要向前端返回的这些参数,根据一定的算法策略生成,不是使用下单成功后返回的那个签名,这里注意,有点坑

生成签名的算法官方有介绍,在使用sandbox时,默认使用MD5算法,真实情况下的使用的是HMACSHA256算法

算法基本实现过程,根据你Map中所有put的数据,通过ASCLL码进行排序之后通过相应的算法进行加密,生成sign(下载sdk,生成sign的源码很容易看懂)

  • 代码:
		AppConfig config = new AppConfig();
        AppPackageBean appPackageBean = new AppPackageBean();

        Map map = new HashMap<>();
        map.put("appId", config.getAppID());
        map.put("nonceStr", unifiedOrderMap.get("nonce_str"));
        map.put("package", "prepay_id=" + unifiedOrderMap.get("prepay_id"));
        map.put("signType", WXPayConstants.HMACSHA256);
        map.put("timeStamp", DateUtil.getCurrentTimestamp10());// 10 位时间戳
        String requestXml = PayCommonUtil.getRequestXml(map);
        log.debug(requestXml);

        /*
         * final Map data, String key, SignType signType
         * 校验签名
         * */
        String signature = WXPayUtil.generateSignature(map, config.getKey(), WXPayConstants.SignType.HMACSHA256);

前端接收到响应的参数,请求微信端进行参数配置,配置成功,即可使用支付功能进行支付
这里官方有提供参考代码

function onBridgeReady(){
   WeixinJSBridge.invoke(
      'getBrandWCPayRequest', {
         "appId":"wx2421b1c4370ec43b",     //公众号名称,由商户传入     
         "timeStamp":"1395712654",         //时间戳,自1970年以来的秒数     
         "nonceStr":"e61463f8efa94090b1f366cccfbbb444", //随机串     
         "package":"prepay_id=u802345jgfjsdfgsdg888",     
         "signType":"MD5",         //微信签名方式:     
         "paySign":"70EA570631E4BB79628FBCA90534C63FF7FADD89" //微信签名 
      },
      function(res){
      if(res.err_msg == "get_brand_wcpay_request:ok" ){
      // 使用以上方式判断前端返回,微信团队郑重提示:
            //res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
      } 
   }); 
}
if (typeof WeixinJSBridge == "undefined"){
   if( document.addEventListener ){
       document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
   }else if (document.attachEvent){
       document.attachEvent('WeixinJSBridgeReady', onBridgeReady); 
       document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
   }
}else{
   onBridgeReady();
}

第三步,微信端进行回调下单前传入的notifyurl,通知支付结果,这里基本的支付完成,后面涉及关闭失败订单,和查询未回调成功的订单等问题
支付成功后,微信会根据之前统一下单时,传入的notifyurl进行回调,根据判断是否支付成功

  • 例如成功参数:

  
  
  
  
  
  
  
  
  
  
  
  
  
  
  1




  
  
 

根据微信官方文档提供的所有验证的参数,如:支付金额,签名,等参数进行验证,如参数验证成功则返回如下格式的告知微信端


  
  

通知微信端,接收到微信支付结果通知,并停止回调,这里要注意的是,如果在接收到回调是5秒内没有给微信端返回消息,微信会根据一定的回调策略进行多次回调
这里又有一个坑人的地方,就是再最开始进行sandbox测试时,不会进行回调,到真实支付情景下,才会去进行回调
并且注意回调的notifyurl,异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。

官方文档提示

微信支付开发总结(JSAPI支付)_第6张图片

  • 代码:
try {
            log.debug(request.toString());
            //解析请求参数
            InputStream 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 weiXinCallback = new String(outSteam.toByteArray(), "utf-8");
            outSteam.close();//关流
            inStream.close();

            log.debug("weiXinCallback" + weiXinCallback);

            //将字符串转换为sortedMap
            Map sortedMap = XMLUtil.doXMLParse(weiXinCallback);

            log.info("微信支付回调: " + sortedMap.toString());

            String outTradeNo = sortedMap.get("out_trade_no");
            Recharge recharge = rechargeRepository.findByOutTradeNo(outTradeNo);

            /* 防重复回调校验 */
            if (recharge.getStatus() == 2) {
                Map hashMap = new HashMap<>();
                hashMap.put("return_code", "SUCCESS");
                hashMap.put("return_msg", "OK");
                return PayCommonUtil.getRequestXml(hashMap);
            }

            //验证签名
            if (!checkSign(weiXinCallback)) {

                log.debug("微信回调失败,签名可能被第三方篡改");
                Map hashMap = new HashMap<>();
                hashMap.put(PayErrorCodeMessage.ERROR_SIGN.getIndex(),
                        PayErrorCodeMessage.ERROR_SIGN.getName());

                //关闭订单
                if (CloseOrderManager.closeOrder(outTradeNo)) {
                    log.info("关闭订单");
                    //充值记录
                    updateUser(recharge, 3, "支付失败,原因:" + PayErrorCodeMessage.ERROR_SIGN.getName());
                }

                return PayCommonUtil.getRequestXml(hashMap);
            }

            //验证支付状态
            if (!"SUCCESS".equals(sortedMap.get("result_code"))) {
                log.debug("微信回调失败,失败原因: " + weiXinCallback);
                Map hashMap = new HashMap<>();
                hashMap.put(PayErrorCodeMessage.RESULT_CODE_FAIL.getIndex(),
                        PayErrorCodeMessage.RESULT_CODE_FAIL.getName());

                //关闭订单
                if (CloseOrderManager.closeOrder(outTradeNo)) {
                    log.info("关闭订单");
                    //充值记录
                    updateUser(recharge, 3, "订单支付失败,原因:" + PayErrorCodeMessage.RESULT_CODE_FAIL.getName());
                }
                return PayCommonUtil.getRequestXml(hashMap);
            }

            if (!"SUCCESS".equals(sortedMap.get("return_code"))) {
                log.debug("微信回调失败,失败原因: " + weiXinCallback);
                Map hashMap = new HashMap<>();
                hashMap.put(PayErrorCodeMessage.RETURN_CODE_FAIL.getIndex(),
                        PayErrorCodeMessage.RETURN_CODE_FAIL.getName());

                if (CloseOrderManager.closeOrder(outTradeNo)) {
                    log.info("关闭订单");
                    //充值记录
                    updateUser(recharge, 3, "订单支付失败,原因:" + PayErrorCodeMessage.RETURN_CODE_FAIL.getName());
                }
                return PayCommonUtil.getRequestXml(hashMap);
            } else {
                String totalFee = sortedMap.get("total_fee");
                int fee = Integer.valueOf(totalFee) * 100;

                if (Integer.valueOf(totalFee) == recharge.getAmount()) {
                    Map hashMap = new HashMap<>();
                    hashMap.put("return_code", "SUCCESS");
                    hashMap.put("return_msg", "OK");
                    log.debug("校验完成________________________________");

                    //有可能出现重复回调,保证充值积分正确
                    if (recharge.getStatus() != 2) {
                        //充值记录
                        updateUser(recharge, 2, "订单支付成功");

                        User user = userRepository.findById(recharge.getUserId());
                        //todo 用于测试积分
                        user.setGold(user.getGold() + fee / 100);
                        user.setLastModifyTime(new Date());
                        userRepository.save(user);
                    }

                    return PayCommonUtil.getRequestXml(hashMap);
                } else {
                    Map hashMap = new HashMap<>();
                    hashMap.put(WXPayConstants.FAIL, "update bussiness outTrade fail");

                    //关闭订单
                    if (CloseOrderManager.closeOrder(outTradeNo)) {
                        log.info("关闭订单");
                        //充值记录
                        updateUser(recharge, 3, "订单支付失败,失败原因:支付金额错误");
                    }

                    return PayCommonUtil.getRequestXml(hashMap);
                }
            }
        } catch (Exception e) {
            log.error("回调异常,错误信息:" + e.getMessage());
            Map hashMap = new HashMap<>();
            hashMap.put(WXPayConstants.FAIL, "weixin pay server exception");
            return PayCommonUtil.getRequestXml(hashMap);
        }

进行到这里基本简单支付也就如此了,
当然这里还涉及到关闭订单,如客户支付失败,或未支付的订单等或未成功的订单进行关闭

官方介绍:

在这里插入图片描述

  • 代码:
		AppConfig config = new AppConfig();
        WXPay wxpay = new WXPay(config, false, false);
        Map map = new HashMap<>();

        map.put("appid", appId);
        map.put("mch_id", mchId);
        map.put("out_trade_no", outTradeNo);

        Map closeOrder = wxpay.closeOrder(map);

第四步:
①之后涉及到服务器不稳定或宕机等不确定因素,需要在这一时段客户支付的进行确认并记录,
①使用到spring中定时器的注解,没两秒对数据库中状态未明确的订单进行查询,
②如该订单超过五分钟并且未支付或支付失败则关闭该订单,

这里涉及到JAVA 发送 http请求

  • 代码:
 public static String interfaceUtil(String path, String data) {

        HttpURLConnection conn = null;
        InputStream is = null;

        try {
            URL url = new URL(path);
            //打开和url之间的连接
//            conn = (HttpURLConnection) url.openConnection();
            conn = (HttpURLConnection) url.openConnection();

            /**设置URLConnection的参数和普通的请求属性****start***/

            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "Keep-Alive");
            conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)");

            conn.setDoOutput(true);
            conn.setDoInput(true);

            conn.setRequestMethod("GET");//GET和POST必须全大写
            /**GET方法请求*****start*/
            conn.connect();

            //获取URLConnection对象对应的输入流
            is = conn.getInputStream();
            //构造一个字符流缓存
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String str = "";
            while ((str = br.readLine()) != null) {
                str = new String(str.getBytes(), "UTF-8");//解决中文乱码问题
                return str;
            }
            //关闭流
            return str;
            //断开连接,最好写上,disconnect是在底层tcp socket链接空闲时才切断。如果正在被其他线程使用就不切断。
            //固定多线程的话,如果不disconnect,链接会增多,直到收发不出信息。写上disconnect后正常一些。
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        } finally {
            try {
                if (is != null) {
                    is.close();
                }
                if (conn != null) {
                    conn.disconnect();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
下面是一些用到的工具类:

①将xml格式的字符串解析map

package com.demeter.utils;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;

/**
 * @desc:XML 解析工具
 */
@SuppressWarnings("all")
public class XMLUtil {
	/**
	 * 解析xml,返回第一级元素键值对。
	 * 如果第一级元素有子节点,
	 * 则此节点的值是子节点的xml数据。
	 *
	 * @param strxml
	 * @return
	 * @throws JDOMException
	 * @throws IOException
	 */
	public static SortedMap doXMLParse(String strxml)
			throws JDOMException, IOException {
		strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");
		if (null == strxml || "".equals(strxml)) {
			return null;
		}
		SortedMap map = new TreeMap();
		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 key = e.getName();
			String value = "";
			List children = e.getChildren();
			if (children.isEmpty()) {
				value = e.getTextNormalize();
			} else {
				value = XMLUtil.getChildrenText(children);
			}
			map.put(key, value);
		}
		// 关闭流
		in.close();
		return map;
	}

	/**
	 * 获取子结点的xml
	 * @param children
	 * @return
	 */
	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("");
			}
		}
		return sb.toString();
	}

}

② 获取时间戳

 	/*
     * 获取当前系统的时间戳
     * */
    public static String getCurrentTimestamp() {

        long timeStamp = new Date().getTime();
        return String.valueOf(timeStamp);
    }

    public static String getCurrentTimestamp10() {

        long timeStamp = new Date().getTime() / 1000;
        String timestr = String.valueOf(timeStamp);
        return timestr;
    }

    public static String getTimeStamp() {
        int time = (int) (System.currentTimeMillis() / 1000);
        return String.valueOf(time);
    }

你可能感兴趣的:(微信支付开发总结(JSAPI支付))