微信企业付款到零钱(微信提现)

由于项目中要用到微信提现,参考网上代码和官方文档写了提现的工具类
用到的相关类库
dom4j
commons-lang3
httpclient-4.4.1

具体相关流程可以看微信企业付款到零钱的开发文档

MD5工具类

package com.kk.wechatextract;

import java.security.MessageDigest;

public class MD5Utils {

    private static final char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

    /*
    * 先转为utf-8
    * */
    public static String MD5Encoding(String s) {
        byte[] btInput = null;
        try {
            btInput = s.getBytes("UTF-8");
        }catch (Exception e){
        }
        return MD5(btInput, 32);
    }


    public static String MD5(String s) {
        byte[] btInput = s.getBytes();
        return MD5(btInput, 32);
    }

    public static String MD5_16(String str) {
        byte[] btInput = str.getBytes();
        return MD5(btInput, 16);
    }

    private static String MD5(byte[] btInput, int length) {
        try {
            // 获得MD5摘要算法的 MessageDigest 对象
            MessageDigest mdInst = MessageDigest.getInstance("MD5");
            // MessageDigest mdInst = MessageDigest.getInstance("SHA-1");
            // 使用指定的字节更新摘要
            mdInst.update(btInput);
            // 获得密文
            byte[] md = mdInst.digest();
            // 把密文转换成十六进制的字符串形式
            int j = md.length;
            char str[] = new char[j * 2];
            int k = 0;
            for (byte byte0 : md) {
                str[k++] = hexDigits[byte0 >>> 4 & 0xf];
                str[k++] = hexDigits[byte0 & 0xf];
            }
            String result = new String(str);
            return length == 16 ? result.substring(8, 24) : result;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    } 
}

url排序,随机字符串的操作

package com.kk.wechatextract;

import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;



public class OtherUtils {

        /*
         * 生成32位随机字符串
         * */
        public static String getNonceStr() {
            UUID uuid = UUID.randomUUID();
            return uuid.toString().replace("-", "");
        }

        /**
         * 方法用途: 对所有传入参数按照字段名的Unicode码从小到大排序(字典序),并且生成url参数串<br>
         * 实现步骤: <br>
         *
         * @param paraMap    要排序的Map对象
         * @param urlEncode  是否需要URLENCODE
         * @param keyToLower 是否需要将Key转换为全小写
         *                   true:key转化成小写,false:不转化
         * @return
         */
        public static String formatUrlMap(Map<String, String> paraMap, boolean urlEncode, boolean keyToLower) {
            String buff = "";
            Map<String, String> tmpMap = paraMap;
            try {
                List<Map.Entry<String, String>> infoIds = new ArrayList<Map.Entry<String, String>>(tmpMap.entrySet());
                // 对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序)
                Collections.sort(infoIds, new Comparator<Map.Entry<String, String>>() {
                    @Override
                    public int compare(Map.Entry<String, String> o1, Map.Entry<String, String> o2) {
                        return (o1.getKey()).toString().compareTo(o2.getKey());
                    }
                });
                // 构造URL 键值对的格式
                StringBuilder buf = new StringBuilder();
                for (Map.Entry<String, String> item : infoIds) {
                    if (StringUtils.isNotBlank(item.getKey())) {
                        String key = item.getKey();
                        String val = item.getValue();
                        if (urlEncode) {
                            val = URLEncoder.encode(val, "utf-8");
                        }
                        if (keyToLower) {
                            buf.append(key.toLowerCase() + "=" + val);
                        } else {
                            buf.append(key + "=" + val);
                        }
                        buf.append("&");
                    }

                }
                buff = buf.toString();
                if (buff.isEmpty() == false) {
                    buff = buff.substring(0, buff.length() - 1);
                }
            } catch (Exception e) {
                return null;
            }
            return buff;
        }



        /**
         * 功能描述: 判断值是否为double
         */
        public static boolean isToDouble(Object o){

            try {
                Double.valueOf(o.toString());
                return true;
            }catch (Exception e){
                return false;
            }
        }


        /**
         * @param xml
         * @return Map
         * @description 将xml字符串转换成map
         */
        public static Map<String, String> readStringXmlOut(String xml) {
            Map<String, String> map = new HashMap<String, String>();
            Document doc = null;
            try {
                doc = DocumentHelper.parseText(xml); // 将字符串转为XML
                Element rootElt = doc.getRootElement(); // 获取根节点
                @SuppressWarnings("unchecked")
                List<Element> list = rootElt.elements();// 获取根节点下所有节点
                for (Element element : list) { // 遍历节点
                    map.put(element.getName(), element.getText()); // 节点的name为map的key,text为map的value
                }
            } catch (DocumentException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return map;
        }


}

微信提现主方法

package com.kk.wechatextract;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.Map;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;



/**
 * @author Zhou Zhong Qing
 * @Title: ${file_name}
 * @Package ${package_name}
 * @Description: 微信提现
 */
@Component
public class WeChatWithdrawUtils {

    private static final Logger log = LoggerFactory.getLogger(WeChatWithdrawUtils.class.getName());
    private byte[] certData;

    /**
     * 加载证书
     */
    public  InputStream getCertStream() {
        ByteArrayInputStream certBis;
        certBis = new ByteArrayInputStream(this.certData);
        return certBis;
    }

    /**
     *读取证书
     */
    public WeChatWithdrawUtils() throws Exception{
        String certPath = Constant.SSLCERT_PATH;
        File file = new File(certPath);
        InputStream certStream = new FileInputStream(file);
        this.certData = new byte[(int) file.length()];
        certStream.read(this.certData);
        certStream.close();
    }



    /**
     *
     * 提现
     * 请求,只请求一次,不做重试
     * @param connectTimeoutMs
     * @param readTimeoutMs
     * @return
     * @throws Exception
     */
    public static String withdrawRequestOnce(Map<String,String> params, int connectTimeoutMs, int readTimeoutMs,boolean useCert) throws Exception {
        /**1.拼凑企业支付需要的参数**/
        Map<String, String> paraMap = new HashMap<>();
        paraMap.putAll(params);
        //微信公众号的appid
        paraMap.put("mch_appid", Constant.APPID);
        //商户号
        paraMap.put("mchid",Constant.MERCHANTID);
        //随机字符串
        //paraMap.put("nonce_str", OtherUtils.getNonceStr());
        paraMap.get("nonce_str");
        //订单号
        //paraMap.put("partner_trade_no","qianchen"+System.currentTimeMillis());
        paraMap.get("partner_trade_no");
        //openid
        //paraMap.put("openid",params.getOrDefault("openId","oelSW0uBU233H5YM3dweIZ08kauk"));//"o5mZ40yBjIqco2NzKc19k9oIBI9o");
        paraMap.get("openid");
        // 校验用户姓名选项 NO_CHECK:不校验真实姓名  FORCE_CHECK:强校验真实姓名
        paraMap.put("check_name","NO_CHECK");
        //paraMap.put("amount",params.getOrDefault("money","0"));//"100");
        paraMap.get("amount");
        //企业付款操作说明信息。必填。
        //paraMap.put("desc",params.getOrDefault("clientId","")+"用户提现");
        paraMap.get("desc");
        //ip地址,地址可以不是真实地址
        paraMap.put("spbill_create_ip",params.getOrDefault("spbillCreateIp","127.0.01"));

        /**2.Unicode码从小到大排序**/
        String url = OtherUtils.formatUrlMap(paraMap, false, false);
        url = url + "&key=" + Constant.MERCHANTKEY;

        /**3.用MD5加密生成签名**/
        String sign = MD5Utils.MD5Encoding(url).toUpperCase();


        /**4.将map拼接成xml格式**/
        StringBuffer xml = new StringBuffer();
        xml.append("<xml>");
        for (Map.Entry<String, String> entry : paraMap.entrySet()) {
            xml.append("<" + entry.getKey() + ">");
            xml.append(entry.getValue());
            xml.append("</" + entry.getKey() + ">" + "\n");
        }
        xml.append("<sign>");
        xml.append(sign);
        xml.append("</sign>");
        xml.append("</xml>");

        log.info("xml {} ", xml.toString());

        BasicHttpClientConnectionManager connManager;
        /**5.操作证书**/
        if (useCert) {
            // 证书
            char[] password = Constant.MERCHANTID.toCharArray();
            InputStream certStream =  new WeChatWithdrawUtils().getCertStream();
            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 org.apache.http.conn.ssl.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
            );
        }

        org.apache.http.client.HttpClient httpClient = HttpClientBuilder.create()
                .setConnectionManager(connManager)
                .build();

        /**发送信息到微信服务器**/
        HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers");

        /**设置超时时间**/
        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(readTimeoutMs).setConnectTimeout(connectTimeoutMs).build();
        httpPost.setConfig(requestConfig);

        StringEntity postEntity = new StringEntity(xml.toString(), "UTF-8");
        httpPost.addHeader("Content-Type", "text/xml");
        httpPost.addHeader("User-Agent", "wxpay sdk java v1.0 " + Constant.MERCHANTID);  // TODO: 很重要,用来检测 sdk 的使用情况,要不要加上商户信息?
        httpPost.setEntity(postEntity);

        /**发送后返回支付结果,xml格式**/
        HttpResponse httpResponse = httpClient.execute(httpPost);
        HttpEntity httpEntity = httpResponse.getEntity();
        return EntityUtils.toString(httpEntity, "UTF-8");
    }


}

微信提现测试

package com.kk.wechatextract;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.Map;

import com.google.common.collect.Maps;

/**
 * @author kk
 * 微信提现
 */
public class WechatExtract {

    public static void main(String[] args) throws Exception {
        //提现金额(单位:元)
        String result = "2";
        Map<String, String> map = Maps.newHashMap();

        //元转换为分,微信支付已分为单位
        BigDecimal sumAmount = new BigDecimal(result);
        //乘以100,转换为分
        BigDecimal transAmt = sumAmount.multiply(new BigDecimal(100)); 



        //订单号
        String Order = "F"+LocalDate.now().toString().replace("-", "")+LocalTime.now().toString().replace("-", "").replace(":","").replace(".", "");
        //随机字符串
        map.put("nonce_str",OtherUtils.getNonceStr().toString());
        //openid("c从数据库中查询")
        //map.put("openid",openid.get(0).getUserlevel());
        map.put("openid","ocYeu0V9WccmTP9axyCp_tDscUN0");
        //金额
        map.put("amount",transAmt.toString());
        企业付款操作说明信息
        map.put("desc","提现测试");

        //调用支付
        String returnInfo =  WeChatWithdrawUtils.withdrawRequestOnce(map,3000,3000,true);
        Map<String,String> resultMap = OtherUtils.readStringXmlOut(returnInfo);

        if(resultMap.containsKey("result_code") && "SUCCESS".equals(resultMap.getOrDefault("result_code",""))){
            System.out.println("提现成功");
            System.out.println("返回结果:"+returnInfo);
        }else{
            System.out.println("提现成功");
            System.out.println("返回结果:"+returnInfo);
        }


    }
}

常量类

package com.kk.wechatextract;

import java.util.LinkedHashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;

public class Constant {

    //微信appid
    public static final String APPID= "**********";

    //微信商户id
    public static final String MERCHANTID = "**********";

    //微信商户秘钥
    public static final String MERCHANTKEY = "*****************";

    public static final String APPSECRET = "*********************";

    //证书路径
    public static final String SSLCERT_PATH = "***********";
}
  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值