一、前言
微信企业付款到个人钱包,此功能模块需要提前在微信商户平台开通。因为开通需要先决条件,并不是所有商户都满足。商户需满足三个条件,可以在商户平台的产品中心,找到入口,申请开通。
1)入驻满90天
2)截至今日,回推30天有连续不断交易
3)交易需为健康交易
一、需要准备的配置
1. appid 可以是您的小程序或者公众号的appid(需要和微信的商户绑定)
2. mchId 商户号,微信商户开通时分配的商户id
3. paternerKey 密钥,在后台配置,在向微信发送请求时需要用来加密签名
4. certfile 支付证书,在商户后台下载
5. openid 用户的唯一标识,需要是你绑定的appid下的openid,因为企业付款需要通过 appid + openid 指定一个收款用户
比较重要的一点是,企业账户里得有钱。。。
二、开发
思路:
企业付款到零钱的入参:
微信入参需要是xml形式的入参,所以你需要先将你的入参转为xml格式
在此之前,你需要获取签名,这很重要
但是签名的生成,需要你其他的参数的拼接,加密获取
最后,你可以加载证书,并向微信发送付款的请求
所以我们现在的思路就已经很明确了,在我们接收前端发送的请求时,我们可以将入参直接设定成一个付款对象。里面可以包含一些,姓名,提现金额这些个信息。后台可以做一些配置处理,比如将我们配置好的商户信息set进去,通过这些现有的信息,获取到签名。然后加载证书,发送请求。
三、代码实现
public class TransfersDto {
/**
* 与商户号关联应用(如微信公众号/小程序)的APPID
*/
private String mch_appid;
/**
* 微信支付分配的商户号
*/
private String mchid;
/**
* 微信支付分配的终端设备号
*/
private String device_info;
/**
* 随机字符串,不长于32位
*/
private String nonce_str;
/**
* 签名
*/
private String sign;
/**
* 商户订单号,需保持唯一性(只能是字母或者数字,不能包含有其他字符)
*/
private String partner_trade_no;
/**
* 商户appid下,某用户的openid
*/
private String openid;
/**
* NO_CHECK:不校验真实姓名 FORCE_CHECK:强校验真实姓名
*/
private String check_name;
/**
* 收款用户真实姓名。
* 强校验必填项
*/
private String re_user_name;
/**
* 企业付款金额,单位为分
*/
private Integer amount;
/**
* 企业付款备注
*/
private String desc;
/**
* 发起者IP地址+该IP可传用户端或者服务端的IP。
*/
private String spbill_create_ip;
此处忽略get set 方法
private static final String FIELD_SIGN = "sign";
/**
* 创建签名
* @param map 方法
* @param paterNerkey api密钥
* @return
*/
public static String createSign(Map<String, Object> map, String paterNerkey) {
return createSign(map, paterNerkey, SignType.MD5);
}
public static String createSign(Map<String, Object> map, String partnerKey, SignType signType) {
map.remove(FIELD_SIGN);
String sign = createLinkString(map, "&");
String SignTemp = sign += "&key=" + partnerKey;
if (signType == SignType.MD5) {
return md5(SignTemp).toUpperCase();
} else {
return hmacSha256(SignTemp, partnerKey).toUpperCase();
}
}
/**
* 生成MD5字符串
* SecureUtil 来自 hutool
* @param data 数据
* @return MD5字符串
*/
public static String md5(String data) {
return SecureUtil.md5(data);
}
/**
* 生成16进制的 sha256 字符串
* SecureUtil 来自 hutool
* @param data 数据
* @param key 密钥
* @return sha256 字符串
*/
public static String hmacSha256(String data, String key) {
return SecureUtil.hmac(HmacAlgorithm.HmacSHA256, key).digestHex(data, CharsetUtil.UTF_8);
}
/**
* 排序并拼接
* @param map 需要排序并拼接的map
* @param delimiter 拼接符
* @return 拼接字符串
*/
public static String createLinkString(Map<String, Object> map, String delimiter) {
List<String> keys = new ArrayList<>(map.keySet());
Collections.sort(keys);
String sign = keys.stream()
.filter(k -> !Objects.isNull(map.get(k)))
.map(key -> {
return key + "=" + map.get(key).toString();
})
.collect(Collectors.joining(delimiter));
return sign;
}
微信签名算法:
第一步,设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA。
特别注意以下重要规则:
◆ 参数名ASCII码从小到大排序(字典序);
◆ 如果参数的值为空不参与签名;
◆ 参数名区分大小写;
◆ 验证调用返回或微信主动通知签名时,传送的sign参数不参与签名,将生成的签名与该sign值作校验。
◆ 微信接口可能增加字段,验证签名时必须支持增加的扩展字段
第二步,在stringA最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值signValue
开始加载证书,发送请求给微信,进行打款。
public class XmlUtil {
// xml转java对象
public static <T> T xmlToBean(String xml, Class<T> clazz) {
XStream xstream = new XStream() {
@Override
protected MapperWrapper wrapMapper(MapperWrapper next) {
return new MapperWrapper(next) {
@Override
public boolean shouldSerializeMember(Class definedIn, String fieldName) {
if (definedIn == Object.class) {
try {
return this.realClass(fieldName) != null;
} catch (Exception e) {
return false;
}
} else {
return super.shouldSerializeMember(definedIn, fieldName);
}
}
};
}
};
XStream.setupDefaultSecurity(xstream);
xstream.autodetectAnnotations(true);
xstream.alias("xml", clazz);
xstream.allowTypes(new Class[] { clazz });
return (T) xstream.fromXML(xml);
}
// java对象转xml
public static <T> String beanToXml(T t, Class<T> clazz) {
XStream xstream = new XStream(new DomDriver("UTF-8",new XmlFriendlyReplacer("_-", "_")));
XStream.setupDefaultSecurity(xstream);
xstream.autodetectAnnotations(true);
xstream.alias("xml", clazz);
xstream.allowTypes(new Class[] { clazz });
return xstream.toXML(t);
}
}
public class WeChatPayRequest {
//此配置你可以换成自己维护的商户配置
private WechatMchTransInfo config;
public WeChatPayRequest(WechatMchTransInfo config) {
this.config = config;
}
private static int socketTimeout = 10000;// 连接超时时间,默认10秒
private static int connectTimeout = 30000;// 传输超时时间,默认30秒
/**
* 向微信发送请求
* @param data
* @param useCert 是否使用证书
* @return
* @throws Exception
*/
public String request(final String url, String data, boolean useCert) throws Exception {
BasicHttpClientConnectionManager connManager;
if (useCert) {
// 证书
char[] password = config.getMchId().toCharArray();
//因为我司多租户模式,会存在多个商户,所以做了商户的维护,证书通过byte[]存储
InputStream certStream = new ByteArrayInputStream(config.getCertfile());
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(certStream, password);
// 实例化密钥库 & 初始化密钥工厂
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, password);
// 创建 SSLContext
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(
sslContext,
new String[]{"TLSv1"},
null,
new DefaultHostnameVerifier());
connManager = new BasicHttpClientConnectionManager(
RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", sslConnectionSocketFactory)
.build(),
null,
null,
null
);
}
else {
connManager = new BasicHttpClientConnectionManager(
RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", SSLConnectionSocketFactory.getSocketFactory())
.build(),
null,
null,
null
);
}
HttpClient httpClient = HttpClientBuilder.create()
.setConnectionManager(connManager)
.build();
HttpPost httpPost = new HttpPost(url);
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout).setConnectTimeout(connectTimeout).build();
httpPost.setConfig(requestConfig);
StringEntity postEntity = new StringEntity(data, "UTF-8");
httpPost.addHeader("Content-Type", "text/xml");
httpPost.setEntity(postEntity);
HttpResponse httpResponse = httpClient.execute(httpPost);
HttpEntity httpEntity = httpResponse.getEntity();
return EntityUtils.toString(httpEntity, "UTF-8");
}
}
WeChatPayRequest weChatPayRequest = new WeChatPayRequest(merChantDto);
String returnXml = null;
try {
returnXml = weChatPayRequest.request(url,xmlStr,true);
} catch (Exception e) {
logger.error("postWxTransfers 微信处理异常 ==>{}",ExceptionUtils.getStackTrace(e));
}
最终效果
总结
商户需满足三个条件,可以在商户平台的产品中心,找到入口,申请开通。
1)入驻满90天
2)截至今日,回推30天有连续不断交易
3)交易需为健康交易
本人花费2个月时间,整理了一套JAVA开发技术资料,内容涵盖java基础,分布式、微服务等主流技术资料,包含大厂面经,学习笔记、源码讲义、项目实战、讲解视频。
希望可以帮助一些想通过自学提升能力的朋友,领取资料,扫码关注一下
记得关注公众号【编码师兄】
领取更多学习资料