微信支付实战:企业付款到用户零钱

场景介绍:

企业付款为企业提供付款至用户零钱的能力,支持通过API接口付款,或通过微信支付商户平台(pay.weixin.qq.com)网页操作付款。
1、商户号(或同主体其他非服务商商户号)已入驻90日

2、截止今日回推30天,商户号(或同主体其他非服务商商户号)连续不间断保持有交易

3、 登录微信支付商户平台-产品中心,开通企业付款。

以上来自于官方文档的简单说明,具体可参照官方文档说明

数据准备:

首先,我们需要证书 apiclient_cert.p12证书,在微信商户平台(pay.weixin.qq.com)–>账户设置–>API安全–>证书中下载 。 把下载好的证书文件放到resources目录下。后面会引用
在这里插入图片描述
接下来就进入编码了

老规矩,官方文档和我这个教程一起来:
根据文档,我们知道了一些调用接口必要的字段:

Map map = Maps.newHashMap();
            map.put("mch_appid",wxappId);//appid(微信公众号)
            map.put("mchid",wxMchId);//商户号
            map.put("nonce_str", DateUtil.getAllTime() + RandomUtil.getRandomNumber(4));//随机码
            map.put("partner_trade_no",transfer.getTransferSn());//商户订单号(唯一)
            map.put("openid",openid);//接受企业转账的用户的openid(关联上面微信公众号的)
            map.put("amount",transfer.getApplyMonery().toString());
            map.put("desc","推广佣金");//企业转账描述
            //是否强制校验姓名,如果选了FORCE_CHECK,那就得传一个re_user_name字段,value为被转账用户真实姓名(非实名用户转账会失败)
            map.put("check_name","FORCE_CHECK");//FORCE_CHECK强制校验姓名  NO_CHECK不校验姓名  
            map.put("re_user_name",agent.getAgentName());
            String sign = getSign(map);//根据微信签名规则签名
            map.put("sign",sign);//签名,必传。根据官方文档规则生成数据签名。

上面就是我们调用接口必要的字段,值得注意点是,我们还需对着整个数据段进行sign签名。签名规则如下:对所有字段的key进行自然排序或者按ASCII码从小到大排序(字典序),然后再将数据拼接成url的格式,比如:www.xc123.com?mch_appid=?&mchid=?……
拼接完后,将我们商户号的密钥也加在这个url最后面。最后再用MD5对数据进行加密后转大写。

老规矩,上文档,贴代码。

public String getSign(Map<String,String> map){
        StringBuffer sb = new StringBuffer();
        Set<String> strings = map.keySet();
        Set<String> set = new TreeSet<String>();
        set.addAll(strings);
        Iterator<String> iterator = set.iterator();
        while (iterator.hasNext()){
            String key = iterator.next();
            sb.append(key+"=");
            String value = map.get(key);
            sb.append(value+"&");
        }
        sb.append("key="+wxMchKey);
        String md5String = MD5.getMD5String(sb.toString()).toUpperCase();
        logger.info("MD5加密且转为大写后的签名信息:"+md5String);
        return md5String;
    }

MD5加密代码:

public class MD5 {

    public static final Logger LOG = LoggerFactory.getLogger(MD5.class);

    /**
     * 16进制字符集
     */
    private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
    /**
     * 循环次数
     */
    public final static int HASH_ITERATIONS = 1024;
    /**
     * 加盐参数
     */
    public final static String HASH_ALGORITHM_NAME = "MD5";
    /**
     * 指定算法为MD5的MessageDigest
     */
    private static MessageDigest MESSAGE_DIGEST = null;

    /** 初始化messageDigest的加密算法为MD5 */
    static {
        try {
            MESSAGE_DIGEST = MessageDigest.getInstance("MD5");
        } catch (Exception e) {
            LOG.error(e.getMessage(), e);
        }
    }

    /**
     * * MD5加密字符串
     *
     * @param str 目标字符串
     * @return MD5加密后的字符串
     */

    public static String getMD5String(String str) {
        if (Strings.isNullOrEmpty(str)) {
            return null;
        }
        return getMD5String(str.getBytes());
    }

    /**
     * * MD5加密以byte数组表示的字符串
     *
     * @param bytes 目标byte数组
     * @return MD5加密后的字符串
     */

    public static String getMD5String(byte[] bytes) {
        MESSAGE_DIGEST.update(bytes);
        return bytesToHex(MESSAGE_DIGEST.digest());
    }

    /**
     * 获取文件的MD5值
     *
     * @param file 目标文件
     * @return MD5字符串
     */
    public static String getFileMD5String(File file) {
        String ret = "";
        FileInputStream in = null;
        FileChannel ch = null;
        try {
            in = new FileInputStream(file);
            ch = in.getChannel();
            ByteBuffer byteBuffer = ch.map(FileChannel.MapMode.READ_ONLY, 0, file.length());
            MESSAGE_DIGEST.update(byteBuffer);
            ret = bytesToHex(MESSAGE_DIGEST.digest());
        } catch (Exception e) {
            LOG.error(e.getMessage(), e);
        } finally {
            IOUtils.closeQuietly(in);
            IOUtils.closeQuietly(ch);
        }
        return ret;
    }

    /**
     * * 获取文件的MD5值
     *
     * @param fileName 目标文件的完整名称
     * @return MD5字符串
     */
    public static String getFileMD5String(String fileName) {
        return getFileMD5String(new File(fileName));
    }


    /**
     * * 将字节数组转换成16进制字符串
     *
     * @param bytes 目标字节数组
     * @return 转换结果
     */
    public static String bytesToHex(byte[] bytes) {
        return bytesToHex(bytes, 0, bytes.length);
    }

    /**
     * * 将字节数组中指定区间的子数组转换成16进制字符串
     *
     * @param bytes 目标字节数组
     * @param start 起始位置(包括该位置)
     * @param end   结束位置(不包括该位置)
     * @return 转换结果
     */
    public static String bytesToHex(byte[] bytes, int start, int end) {
        StringBuilder sb = new StringBuilder();
        for (int i = start; i < start + end; i++) {
            sb.append(byteToHex(bytes[i]));
        }
        return sb.toString();
    }

    /**
     * * 将单个字节码转换成16进制字符串
     *
     * @param bt 目标字节
     * @return 转换结果
     */
    public static String byteToHex(byte bt) {
        return HEX_DIGITS[(bt & 0xf0) >> 4] + "" + HEX_DIGITS[bt & 0xf];
    }


    /**
     * shiro密码加密工具类
     *
     * @param credentials 密码
     * @param salt        密码盐
     * @return
     */
    public static String md5(String credentials, String salt) {
        MessageDigest messageDigest = null;
        try {
            messageDigest = MessageDigest.getInstance("MD5");
            messageDigest.reset();
            //先加盐
            messageDigest.update(salt.getBytes("UTF-8"));
            //再放需要被加密的数据
            messageDigest.update(credentials.getBytes("UTF-8"));
        } catch (NoSuchAlgorithmException e) {
            System.out.println("NoSuchAlgorithmException caught!");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        byte[] byteArray = messageDigest.digest();

        StringBuffer md5StrBuff = new StringBuffer();

        for (int i = 0; i < byteArray.length; i++) {
            if (Integer.toHexString(0xFF & byteArray[i]).length() == 1) {
                md5StrBuff.append("0").append(Integer.toHexString(0xFF & byteArray[i]));
            } else {
                md5StrBuff.append(Integer.toHexString(0xFF & byteArray[i]));
            }
        }

        return md5StrBuff.toString();
    }

 public static void main(String[] args) {
        System.out.println(MD5.md5("admin", "8pgby"));
    }
}

到这里,调接口前的数据准备就已经完成了。接下来就是接口调用了

接口调用

根据文档,我们知道了企业付款到零钱的接口是基于xml格式交互的。所以我们需要有一段代码将你的map数据转成xml格式。

public String getXml(Map<String,String> map){
        StringBuffer sb = new StringBuffer();
        sb.append("<xml>");
        Set<String> strings = map.keySet();
        Iterator<String> iterator = strings.iterator();
        while (iterator.hasNext()){
            String key = iterator.next();
            sb.append("<"+key+">");
            String value = map.get(key);
            sb.append(value);
            sb.append("</"+key+">");
        }
        sb.append("</xml>");

        logger.info("生成后的xml格式:"+sb.toString());
        return sb.toString();
    }

由于企业到零钱转账只是单层嵌套,所以这里就随便写了个循环转换格式。
然后这边拿到xml格式的数据后,就可以直接调接口请求转账了。
继续看代码

String xml = getXml(map);//map转xml
            CloseableHttpClient httpClient = null;
            HttpPost httpPost = new HttpPost(TRANSFER_PAY);
            String body = null;
            CloseableHttpResponse response = null;
            try{
                httpClient = HttpClients.custom().setDefaultRequestConfig(HttpUtils.REQUEST_CONFIG).setSslcontext(HttpUtils.wx_ssl_context).build();
                httpPost.setEntity(new StringEntity(xml, "UTF-8"));
                response = httpClient.execute(httpPost);
                body = EntityUtils.toString(response.getEntity(), "UTF-8");
                logger.info("微信付款返回的消息:"+body);
                if (StringUtil.isNotEmpty(body)){

                    Map<String,String> resultMap = getMap(body.trim());//xml转map

                    if (resultMap.get("return_code").equals("SUCCESS") ){
                        if (resultMap.get("result_code").equals("SUCCESS")){
                        //处理自己的业务逻辑
                        }else if (resultMap.get("result_code").equals("FAIL")) {
                            logger.error("业务结果未明确,具体信息如下:");
                            logger.error("错误代码{},错误描述{}",resultMap.get("err_code"),resultMap.get("err_code_des"));
                            return Rets.failure("转账失败,失败原因:"+resultMap.get("err_code_des"));
                        }


                    }else {
                        logger.error("连接微信转账接口通信失败,信息:"+map.get("return_msg"));
                    }
                }else {
                    return Rets.failure("转账失败,微信返回的消息为空");
                }


            }catch (Exception e){
                logger.error("转账异常,异常消息如下:"+e.getMessage());
                return Rets.failure("转账异常,信息如下:"+e.getMessage());
            }finally {
                if (response != null) {
                    try {
                        response.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

                if (httpClient != null) {
                    try {
                        httpClient.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

            }

上面发送请求的代码中,还有一个要注意的点是 httpClient = HttpClients.custom().setDefaultRequestConfig(HttpUtils.REQUEST_CONFIG).setSslcontext(HttpUtils.wx_ssl_context).build();

因为微信支付接口中,涉及资金回滚的接口会使用到API证书,所以这里我们发送企业付款到零钱接口请求的时候需要将这个api证书带上。

之前我们有从微信商户号后台下载好了专门的证书文件apiclient_cert.p12 并将它放到了resources文件下。这里要用到它。

public class HttpUtils {

    private static final String DEFAULT_CHARSET = "UTF-8";

    private static final int CONNECT_TIME_OUT = 5000; //链接超时时间3秒

    public static final RequestConfig REQUEST_CONFIG = RequestConfig.custom().setConnectTimeout(CONNECT_TIME_OUT).build();

    public static SSLContext wx_ssl_context = null; //微信支付ssl证书

    static{
        Resource resource = new ClassPathResource("cert/apiclient_cert.p12");//证书存放的路径
        try {
            KeyStore keystore = KeyStore.getInstance("PKCS12");
            char[] keyPassword = "********".toCharArray(); //证书密码,默认为你的商户号id
            keystore.load(resource.getInputStream(), keyPassword);
            wx_ssl_context = SSLContexts.custom().loadKeyMaterial(keystore, keyPassword).build();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

这里初始化一个SSLcontext 对象,配置好你的证书以及RequestConfig 对象,见上文 加粗 代码处。

接口发送成功后,我们接收微信的返回也是xml格式,所以我们同样得把xml格式转成map格式后再去判断处理结果。

public Map<String,String> getMap(String text) throws DocumentException {
        Map<String,String> resultMap = Maps.newHashMap();
        //适用于单层嵌套的xml格式
        Document document = DocumentHelper.parseText(text);//将xml字符串转为document对象
        Element rootElement = document.getRootElement();//获取到document的根节点元素
        List<Element> elements = rootElement.elements();//获取根节点下面所有子节点的元素。
        for (Element e:elements
        ) {
            resultMap.put(e.getName(),e.getText());//遍历转为map
        }
        return resultMap;
    }

至此,企业付款到零钱的接口已经完成了,该项接口功能适用于很多业务场景。比如最基本的分销、红包等等,具体可根据业务需求来。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值