微信支付的两种模式

微信支付分为两种,一种是普通商户模式,一种是服务商模式

一、普通商户模式(支付的钱到一个账户上)

关于公众号支付、APP支付或H5支付,参数都差不多(有差异,有些是全小写,有些是驼峰)。
建议从微信文档上直接复制参数
到微信商户平台上开通相对应的支付权限,并拿到对应的参数
微信商户平台文档 这里以公众号支付为例,直接上代码

private WechatPayResponseDto prepay(WechatPayParamsDto params) {
		/* 微信统一下单接口调用 */
		Map<String, String> prepayMap = new HashMap<>();
		// 微信公众账号ID
		prepayMap.put("appid", appid);
		// 商户号
		prepayMap.put("mch_id", mchid);
		// 随机字符串
		prepayMap.put("nonce_str", WechatPayUtils.generateString(30));
		// 商品描述
		prepayMap.put("body", params.getSummary());
		// 商品详情
		prepayMap.put("detail", params.getDetail());
		// 商户订单号
		prepayMap.put("out_trade_no", params.getOrderId());
		// 总金额 单位为分
		prepayMap.put("total_fee", params.getTotalFee());
		// 终端IP
		prepayMap.put("spbill_create_ip", params.getIp());
		// 通知地址 
		prepayMap.put("notify_url", notifyUrl);
		// 交易类型 JSAPI
		prepayMap.put("trade_type", tradeType);
		// 微信公众号支付必须openid
		prepayMap.put("openid", this.getOpenId(params.getMemberId()));
		// 签名 将除了签名以外的参数进行签名
		prepayMap.put("sign", WechatPayUtils.sign(prepayMap, apiKey));
		// 调用微信统一下单接口获得返回参数
		String resultXml = WechatPayUtils.getPrepay(prepayMap);
		String prepayId = WechatPayUtils.getPrepayId(resultXml);
		// 统一下单结果判断
		if (prepayId == null) {
			throw new RuntimeException("获取微信支付请求失败");
		}
		// 给前端JS组合参数
		Map<String, String> signMap = new HashMap<>();
		// 公众号id
		signMap.put("appId", appid);
		// 时间戳 注意:部分系统取到的值为毫秒级,需要转换成秒(10位数字)
		signMap.put("timeStamp", Long.toString(new Date().getTime() / 1000));
		// 随机字符串 随机字符串,不长于32位
		signMap.put("nonceStr", WechatPayUtils.generateString(32));
		// 订单详情扩展字符串 统一下单接口返回的prepay_id参数值,提交格式如:prepay_id=***
		signMap.put("package", "prepay_id=" + prepayId);
		// 签名方式 签名算法,暂支持MD5
		signMap.put("signType", "MD5");
		// 进行签名
		String sign = WechatPayUtils.sign(signMap, apiKey);

		// 放入返回DTO dto就返回给前端需要调起微信的参数
		WechatPayResponseDto response = new WechatPayResponseDto();
		response.setAppId(signMap.get("appId"));
		response.setTimeStamp(signMap.get("timeStamp"));
		response.setNonceStr(signMap.get("nonceStr"));
		response.setPackAge(signMap.get("package"));
		response.setSignType(signMap.get("signType"));
		response.setPaySign(sign);
		return response;
	}

二、服务商模式((钱到多个账号上))

这里以公众号支付为例,直接上代码

private WechatPayResponseDto prepay(WechatPayParamsDto params, String subMchId) {
		/* 微信统一下单接口调用 */
		Map<String, String> prepayMap = new HashMap<>();
		// 微信公众账号ID
		prepayMap.put("appid", serviceAppid);
		// 商户号
		prepayMap.put("mch_id", serviceMchid);
		// 【服务商】子商户号
		prepayMap.put("sub_mch_id", subMchId);
		// 随机字符串
		prepayMap.put("nonce_str", WechatPayUtils.generateString(30));
		// 商品描述
		prepayMap.put("body", params.getSummary());
		// 商品详情
		prepayMap.put("detail", params.getDetail());
		// 商户订单号
		prepayMap.put("out_trade_no", params.getOrderId());
		// 总金额 单位为分
		prepayMap.put("total_fee", params.getTotalFee());
		// 终端IP
		prepayMap.put("spbill_create_ip", params.getIp());
		// 通知地址 回调给服务器用作支付成功标志
		prepayMap.put("notify_url", notifyUrl);
		// 交易类型 JSAPI
		prepayMap.put("trade_type", tradeType);
		// 微信公众号支付必须openid
		prepayMap.put("openid", this.getOpenId(params.getMemberId()));
		// 签名 将除了签名以外的参数进行签名
		prepayMap.put("sign", WechatPayUtils.sign(prepayMap, serviceApiKey));
		// 调用微信统一下单接口获得返回参数
		String resultXml = WechatPayUtils.getPrepay(prepayMap);
		String prepayId = WechatPayUtils.getPrepayId(resultXml);

		// 统一下单结果判断
		if (prepayId == null) {
			throw new RuntimeException("获取微信支付请求失败");
		}
		
		// 给前端JS组合参数
		Map<String, String> signMap = new HashMap<>();
		
		// 公众号id
		signMap.put("appId", appid);
		// 时间戳 注意:部分系统取到的值为毫秒级,需要转换成秒(10位数字)
		signMap.put("timeStamp", Long.toString(new Date().getTime() / 1000));
		// 随机字符串 随机字符串,不长于32位
		signMap.put("nonceStr", WechatPayUtils.generateString(32));
		// 订单详情扩展字符串 统一下单接口返回的prepay_id参数值,提交格式如:prepay_id=***
		signMap.put("package", "prepay_id=" + prepayId);
		// 签名方式 签名算法,暂支持MD5
		signMap.put("signType", "MD5");
		// 进行签名
		String sign = WechatPayUtils.sign(signMap, serviceApiKey);
		// 放入返回DTO
		WechatPayResponseDto response = new WechatPayResponseDto();
		response.setAppId(signMap.get("appId"));
		response.setTimeStamp(signMap.get("timeStamp"));
		response.setNonceStr(signMap.get("nonceStr"));
		response.setPackAge(signMap.get("package"));
		response.setSignType(signMap.get("signType"));
		response.setPaySign(sign);
		return response;
	}

WechatPayUtils 代码: 微信开发文档上查看对应的加密解密算法

public class WechatPayUtils {
    
    
    /*微信支付预下单*/
    private final static String PREPAY_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
    
    /*随机字符串生成办法*/
    private static final String ALLCHAR    = "*****";

    
    /**
     * Description: 获取定长的随机字符串(包含大小写字母、数字)
     *
     * @param length 字符串长度
     * @return
     */
    public static String generateString(int length) {
        // 随机拿字符
        StringBuffer sb = new StringBuffer();
        Random random = new Random();
        for (int i = 0; i < length; i++) {
            sb.append(ALLCHAR.charAt(random.nextInt(ALLCHAR.length())));
        }
        
        return sb.toString();
    }
    
    
    /**
    * Description: 微信公众号支付签名处理
    *
    * @param params
    * @param apiKey
    * @return
    */
    public static String sign(Map<String, String> params, String apiKey) {
        // 字符串拼接
        String signContent = generateSignVertifyString(params, apiKey);
        
        // MD5加密并返回
        return EncryptUtil.encryptMd5(signContent).toUpperCase();
    }
    
    
    /**
    * Description: 微信公众号获取统一下单号
    *
    * @param params
    * @return
    */
    public static String getPrepay(Map<String, String> params) {
       // 解析成XML字符串
       String requestXml = getRequestXml(params);
       
       // 向微信服务器发送请求 http工具类可自行查询
       String resultXml = QHttpClientUtil.httpRequest(PREPAY_URL, "POST", requestXml);
       return resultXml;
    }
    
    
    public static String getPrepayId(String resultXml){
        // 获得数据
        return doXMLParse(resultXml).get("prepay_id");
    }
    
    
    /**
    * Description: 
    *
    * @param params
    * @param apiKey
    * @return
    */
    private static String generateSignVertifyString(Map<String, String> params, String apiKey) {
        // 参数名按字符表排序
        List<String> keys = new ArrayList<String>(params.keySet());
        Collections.sort(keys);

        // 参数组装
        StringBuffer sb = new StringBuffer();
        for (String key : keys) {
            String value = params.get(key);
            if (value != null && !"".equals(value)) {
                sb.append(key).append("=").append(value).append("&");
            }
        }
        
        // 商户自定义密钥
        sb.append("key").append("=").append(apiKey);
          
        return sb.toString();
    }
    
    
    /**
    * Description: 将Map转换为微信要求的XML请求参数格式
    *
    * @param params
    * @return
    */
    private static String getRequestXml(Map<String, String> params) {
        StringBuffer sb = new StringBuffer();
        
        sb.append("<xml>");
        for (Map.Entry<String, String> entry : params.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            if ("attach".equalsIgnoreCase(key) || "body".equalsIgnoreCase(key)) {
                sb.append("<" + key + ">" + "<![CDATA[" + value + "]]></" + key + ">");
            } else {
                sb.append("<" + key + ">" + value + "</" + key + ">");
            }
        }
        sb.append("</xml>");
       
        return sb.toString();
    }
    
    /**
     * Description: 将微信返回的XML数据解析成MAP
     *
     * @param xmlStr
     * @return
     */
    public static Map<String, String> doXMLParse(String xmlStr) {
        Map<String, String> map = new HashMap<String, String>();
        SAXReader reader = new SAXReader();
        /**
         * 防止 XXE漏洞 注入实体攻击
         * 过滤 过滤用户提交的XML数据
         *       过滤关键词:<!DOCTYPE和<!ENTITY,或者SYSTEM和PUBLIC。
         */
        xmlStr = xmlStr.replace("DOCTYPE", "")
        		.replace("SYSTEM", "")
        		.replace("ENTITY", "")
        		.replace("PUBLIC", "");

        try {
            InputSource source = new InputSource(new StringReader(xmlStr));
             
            Document doc = reader.read(source);
            Element root = doc.getRootElement();
             
            @SuppressWarnings("unchecked")
            List<Element> list = root.elements();
            for (Element e : list) {
                map.put(e.getName(), e.getText());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return map;
    }
    
    /**
     * Description: 微信支付结果通知签名验证
     *
     * @return
     */
    public static boolean validateSign(Map<String, String> params,String apiKey) {
        // 排除签名后的数据
        Map<String, String> map = new HashMap<String, String>();
        for (String key : params.keySet()) {
            if (!"sign".equals(key)) {
                map.put(key, params.get(key));
            }
        }

        // MD5加密
        String newSign = WechatPayUtils.sign(map, apiKey);

        // 验证签名
        String sign = params.get("sign");
        return sign.equals(newSign);
    }

    /**
    * Description: 微信支付通知结果
    *
    * @param code
    * @param msg
    * @return
    */
    public static String notifyReturn(String code, String msg) {
        StringBuffer sb = new StringBuffer();
        
        sb.append("<xml>");
        sb.append("<return_code><![CDATA[").append(code).append("]]></return_code>");
        sb.append("<return_msg><![CDATA[").append(msg).append("]]></return_msg>");
        sb.append("</xml>");
        
        return sb.toString();
    }
    
}

三、关于APP支付的服务商模式

微信的APP服务商支付有点坑爹。每一个商户都必须有appId才行(支付宝似乎不支持服务商模式),但是很多商家是没有APP的,也申请不了appId,这就很难受了呀。
我们的需求是从我们平台的APP进入,能够付款到某一个入驻商家。当时电话与微信客服确认了几次,得到的结果还是不行,而且绑定的子商户数量也是有限制的。最终,我们舍弃了APP微信支付的服务商模式,改为APP内部调起H5支付的服务商模式。因为H5支付没有返回支付成功还是失败,需要显示一个已完成支付或者未支付的框,在弱网环境下直接弹出不太友好。目前未找到解决方案。
或许有一种方案是交由第三方平台进行管理,让第三方支付平台进行钱的转账操作会友好很多。