微信企业付款到零钱

官方文档

企业付款

直接上代码

关键入参

package cn.country.cunyue.app.pojo.dto.weixin;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

/**
 * @Desc https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_2
 */
@Data
@ApiModel(value = "net.mondelez.scan.microservice.entity.vo.WxMchPayB2c", description = "微信商户平台-微信支付-企业付款到零钱")
public class WxMchPayB2c {

    @ApiModelProperty(value = "申请商户号的appid或商户号绑定的appid", required = true)
    private String mch_appid;

    @ApiModelProperty(value = "微信支付分配的商户号", required = true)
    private String mchid;

    @ApiModelProperty(value = "微信支付分配的终端设备号", required = false)
    private String device_info;

    @ApiModelProperty(value = "随机字符串,不长于32位", required = true)
    private String nonce_str;

    @ApiModelProperty(value = "签名,签名算法:https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=4_3", required = true)
    private String sign;

    @ApiModelProperty(value = "商户订单号,需保持唯一性(只能是字母或者数字,不能包含有其它字符)", required = true)
    private String partner_trade_no;

    @ApiModelProperty(value = "商户appid下,某用户的openid", required = true)
    private String openid;

    @ApiModelProperty(value = "校验用户姓名选项,NO_CHECK:不校验真实姓名,FORCE_CHECK:强校验真实姓名", required = true)
    private String check_name = "NO_CHECK";

    @ApiModelProperty(value = "收款用户真实姓名,如果check_name设置为FORCE_CHECK,则必填用户真实姓名,如需电子回单,需要传入收款用户姓名", required = false)
    private String re_user_name;

    @ApiModelProperty(value = "企业付款金额,单位为分", required = true)
    private Integer amount;

    @ApiModelProperty(value = "企业付款备注,必填。注意:备注中的敏感词会被转成字符*", required = true)
    private String desc;

    @ApiModelProperty(value = "Ip地址,该IP同在商户平台设置的IP白名单中的IP没有关联,该IP可传用户端或者服务端的IP", required = false)
    private String spbill_create_ip;
}

帮助方法

package cn.country.cunyue.app.application.utils;

import cn.country.cunyue.app.pojo.dto.weixin.WxMchPayB2c;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.ConnectionPoolTimeoutException;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.SocketTimeoutException;
import java.security.*;
import java.security.cert.CertificateException;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

/**
 * 微信支付工具类
 *
 * @author: joker
 * @date: 2021-3-25 14:28:40
 */
@Slf4j
public class WeixinPayUtils {

    /**
     * 企业付款到零钱接口URL.
     */
    private static final String TRANS_URL = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers";

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

    /**
     * 微信支付签名算法sign
     *
     * @param parameters 参数集合
     * @return 返回md5签名
     */
    public static String createSign(String signKey, SortedMap<Object, Object> parameters) {
        StringBuffer sb = new StringBuffer();
        Set es = parameters.keySet();                 // 所有参与传参的key按照accsii排序(升序)
        for (Object set : es) {
            String k = set.toString();
            Object v = parameters.get(k);
            sb.append(k).append("=").append(v.toString()).append("&");
        }
        sb.append("key=").append(signKey);
        return str2MD5(sb.toString(), "utf-8").toUpperCase();
    }

    /**
     * MD5加密
     *
     * @param data   要加密的数据
     * @param encode 加密的编码
     * @return md5字符串
     */
    public static String str2MD5(String data, String encode) {
        String resultString = null;
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            if (encode == null || "".equals(encode))
                resultString = byteArrayToHexString(md.digest(data.getBytes()));
            else {
                resultString = byteArrayToHexString(md.digest(data.getBytes(encode)));
            }
        } catch (Exception exception) {
        }
        return resultString;
    }

    /**
     * byte数组转换16进制字符串
     *
     * @param b 要转换的byte数组
     * @return 16进制字符串
     */
    private static String byteArrayToHexString(byte b[]) {
        StringBuffer resultSb = new StringBuffer();
        for (int i = 0; i < b.length; i++) {
            resultSb.append(byteToHexString(b[i]));
        }
        return resultSb.toString();
    }

    /**
     * byte转换成16进制字符串
     *
     * @param b 要转换的byte
     * @return byte对应的16进制字符串
     */
    private static String byteToHexString(byte b) {
        int n = b;
        if (n < 0) {
            n += 256;
        }
        int d1 = n / 16;
        int d2 = n % 16;
        return hexDigits[d1] + hexDigits[d2];
    }

    /**
     * map集合参数转换为xml格式
     *
     * @param dataMap 要转换的map对象
     * @return XML格式的字符串
     */
    public static String map2XML(SortedMap<Object, Object> dataMap) {
        StringBuffer sb = new StringBuffer();
        Set<Object> objSet = dataMap.keySet();
        sb.append("<xml>\n");
        for (Object key : objSet) {
            if (key == null) {
                continue;
            }
            Object value = dataMap.get(key);
            sb.append("<").append(key.toString()).append(">").append(value).append("</").append(key.toString()).append(">\n");
        }
        sb.append("</xml>");
        return sb.toString();
    }

    /**
     * 生成随机数.
     *
     * @param count 要生成的随机数位数
     * @return 随机数字符串
     */
    private String createNonceStr(int count) {
        String[] nums = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"};
        int maxIndex = nums.length - 1;
        int numIndex;
        StringBuilder builder = new StringBuilder(count);
        for (int i = 0; i < count; i++) {
            numIndex = (int) (Math.random() * maxIndex);
            builder.append(nums[numIndex]);
        }
        return builder.toString();
    }

    /**
     * 生成签名和xml参数的main方法测试.
     * see https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=4_3
     *
     * @param args
     */
    public static void main(String[] args) {
        String key = "192006250b4c09247ec02edce69f6a2d";
        SortedMap<Object, Object> map = new TreeMap<>();
        map.put("appid", "wxd930ea5d5a258f4f");
        map.put("mch_id", "10000100");
        map.put("device_info", "1000");
        map.put("body", "test");
        map.put("nonce_str", "ibuaiVcKdpRxkhJA");           // 这里为了跟文档的结果对应,直接使用文档的随机数
        String sign = WeixinPayUtils.createSign(key, map);
        map.put("sign", sign);
        String xmlParam = WeixinPayUtils.map2XML(map);
        System.out.println(xmlParam);
    }

    /**
     * 以下是http请求相关接口方法.
     */

    /**
     * 加载证书.
     *
     * @param certPath 证书文件绝对路径
     * @param mchid    商户id
     */
    private static KeyStore initCert(String certPath, String mchid) {
        KeyStore keyStore = null;
        // 加载本地的证书进行https加密传输
        FileInputStream instream = null;
        try {
            keyStore = KeyStore.getInstance("PKCS12");
            instream = new FileInputStream(new File(certPath));
            keyStore.load(instream, mchid.toCharArray()); // 加载证书密码,默认为商户ID
        } catch (KeyStoreException e) {
            log.error(e.getMessage(), e);
        } catch (FileNotFoundException e) {
            log.error(e.getMessage(), e);
        } catch (NoSuchAlgorithmException e) {
            log.error(e.getMessage(), e);
        } catch (CertificateException e) {
            log.error(e.getMessage(), e);
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        } finally {
            if (instream != null) {
                try {
                    instream.close();
                } catch (IOException e) {
                    log.error(e.getMessage(), e);
                }
            }
        }
        return keyStore;
    }

    /**
     * 创建CloseableHttpClient.
     *
     * @param keyStore
     * @param keyPassword
     * @return
     */
    private static CloseableHttpClient createHttpClient(KeyStore keyStore, char[] keyPassword) {
        // Trust own CA and all self-signed certs
        SSLContext sslcontext = null;
        try {
            sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, keyPassword).build();
        } catch (NoSuchAlgorithmException e) {
            log.error(e.getMessage(), e);
        } catch (KeyManagementException e) {
            log.error(e.getMessage(), e);
        } catch (KeyStoreException e) {
            log.error(e.getMessage(), e);
        } catch (UnrecoverableKeyException e) {
            log.error(e.getMessage(), e);
        }
        // Allow TLSv1 protocol only
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
                sslcontext,
                new String[]{"TLSv1"}, null,
                new HostnameVerifier() {
                    @Override
                    public boolean verify(String hostname, SSLSession sslSession) {
                        // 微信商户平台的支付接口验证直接通过
                        if ("api.mch.weixin.qq.com".equals(hostname)) {
                            return true;
                        } else {
                            HostnameVerifier hv = SSLConnectionSocketFactory.getDefaultHostnameVerifier();
                            return hv.verify(hostname, sslSession);
                        }
                    }
                });
        CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
        // 根据默认超时限制初始化requestConfig
        return httpClient;
    }

    /**
     * post传输xml数据.
     *
     * @param url      传输url
     * @param xmlObj   xml参数
     * @param mchid    商户id
     * @param certPath 证书文件的绝对路径
     * @return
     */
    public static String post(String url, String xmlObj, String mchid, String certPath) {
        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(5000).setConnectTimeout(10000).build();
        // 加载证书
        KeyStore keyStore = initCert(certPath, mchid);
        CloseableHttpClient httpClient = createHttpClient(keyStore, mchid.toCharArray());
        String result = null;
        HttpPost httpPost = new HttpPost(url);
        // 得指明使用UTF-8编码,否则到API服务器XML的中文不能被成功识别
        String defaultCharset = "UTF-8";
        StringEntity postEntity = new StringEntity(xmlObj, defaultCharset);
        httpPost.addHeader("Content-Type", "text/xml");
        httpPost.setEntity(postEntity);
        // 设置请求器的配置
        httpPost.setConfig(requestConfig);
        try {
            HttpResponse res = httpClient.execute(httpPost);
            if (res.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                HttpEntity entity = res.getEntity();
                result = EntityUtils.toString(entity, defaultCharset);
            }
        } catch (ConnectionPoolTimeoutException e) {
            log.error(e.getMessage(), e);
        } catch (ConnectTimeoutException e) {
            log.error(e.getMessage(), e);
        } catch (SocketTimeoutException e) {
            log.error(e.getMessage(), e);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            httpPost.abort();
        }
        return result;
    }

    /**
     * 发送请求.
     *
     * @param key      密钥
     * @param certPath API证书证书apiclient_cert.p12
     * @param model    企业付款到零钱接口所需参数
     * @return String
     */
    public static String doTransfers(String key, String certPath, WxMchPayB2c model) throws IllegalAccessException {
        SortedMap<Object, Object> map = new TreeMap<>();
        Field[] declaredFields = model.getClass().getDeclaredFields();
        for (Field field : declaredFields) {
            field.setAccessible(true);
            String k = field.getName();
            Object v = field.get(model);
            if (v != null) {
                map.put(k, v);
            }
        }
        String sign = WeixinPayUtils.createSign(key, map);
        map.put("sign", sign);
        String xmlParam = WeixinPayUtils.map2XML(map);
        String result = post(TRANS_URL, xmlParam, model.getMchid(), certPath);
        return result;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值