SpringBoot实现微信企业付款到个人钱包,详细流程

一、前言
微信企业付款到个人钱包,此功能模块需要提前在微信商户平台开通。因为开通需要先决条件,并不是所有商户都满足。商户需满足三个条件,可以在商户平台的产品中心,找到入口,申请开通。
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基础,分布式、微服务等主流技术资料,包含大厂面经,学习笔记、源码讲义、项目实战、讲解视频。


 希望可以帮助一些想通过自学提升能力的朋友,领取资料,扫码关注一下

 

记得关注公众号【编码师兄】

领取更多学习资料
 

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值