微信支付的两种模式
微信支付分为两种,一种是普通商户模式,一种是服务商模式
一、普通商户模式(支付的钱到一个账户上)
关于公众号支付、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支付没有返回支付成功还是失败,需要显示一个已完成支付或者未支付的框,在弱网环境下直接弹出不太友好。目前未找到解决方案。
或许有一种方案是交由第三方平台进行管理,让第三方支付平台进行钱的转账操作会友好很多。