微信提现到银行卡

微信端提供接口文档:
付款到银行卡API:https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay_yhk.php?chapter=25_2

主方法:

 /**
     * 发送微信提现请求
     *
     * @param userName              用户姓名
     * @param userBank              用户银行卡号
     * @param userBankCode          用户银行所属编码
     * @param amount                提现金额 单位分
     * @param desc                  描述
     * @param outMoney              扣除金额
     * @param idNumber              身份证号
     * @param platformServiceCharge 平台手续费
     * @param inWxServiceCharge     入微信平台手续费
     * @param outWxServiceCharge    出微信平台手续费
     */
    public Map<String, String> transferBank(String userName, String userBank, String userBankCode, String orderId, String amount, String desc,
                                            String outMoney, String idNumber, String platformServiceCharge, String inWxServiceCharge,
                                            String outWxServiceCharge,String userBankName) throws Exception {
        int intValue = new BigDecimal(amount).multiply(new BigDecimal("100")).intValue();
        String name = RSAUtils.encrypt(userName);
        String bank = RSAUtils.encrypt(userBank);
        String str = RequestUtils.getUUID();
        Map<String, Object> params = new TreeMap<>();
        params.put("mch_id", IExtractConstant.MCH_ID);
        params.put("partner_trade_no", orderId);
        params.put("nonce_str", str);
        params.put("enc_bank_no", bank);
        params.put("enc_true_name", name);
        params.put("bank_code", userBankCode);
        params.put("amount", intValue);
        if (StringUtils.isNoneBlank(desc)) {
            params.put("desc", desc);
        }
        //生成签名
        String sign = SignUtils.getSign(params, IExtractConstant.MCH_KEY);
        params.put("sign", sign);
        //将请求参数转换为xml格式
        String requestXml = XmlUtil.getRequestXml(params);
        //请求前插入提现记录表
        extractCashMapper.insertExtractCashRecord(userName, idNumber, outMoney, orderId, userBank, userBankCode,
                intValue + "", desc, platformServiceCharge, inWxServiceCharge, outWxServiceCharge, "1", str,userBankName);
        String weChatResponse = SendWeChatRequestUtil.sendWeChatRequest(IExtractConstant.MCH_ID,
                IExtractConstant.PAY_BANK_URL,
                requestXml,
                IExtractConstant.CERT_PATH);
        return XmlUtil.xmlToMap(weChatResponse);
    }

RSAUtils:

package com.sdjx.wx.util;

import com.sdjx.wx.constant.IExtractConstant;
import org.springframework.util.Base64Utils;

import javax.crypto.Cipher;
import java.io.*;
import java.lang.reflect.Method;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * @Author:wpf
 * @ClassName:RSAUtils
 * @Date:2022/10/8
 * @Description:
 */

public class RSAUtils {

    /**
     * rsa加密公钥
     * 请求微信api获得pkcs1格式
     * 通过转换工具转换成pkcs8格式
     */
    private static final String PKCS8_PUBLIC = IExtractConstant.RSA_PUBLIC_KEY_PKCS8;


    public static byte[] decrypt(byte[] encryptedBytes, PrivateKey privateKey, int keyLength, int reserveSize, String cipherAlgorithm) throws Exception {
        int keyByteSize = keyLength / 8;
        int decryptBlockSize = keyByteSize - reserveSize;
        int nBlock = encryptedBytes.length / keyByteSize;
        ByteArrayOutputStream outbuf = null;
        try {
            Cipher cipher = Cipher.getInstance(cipherAlgorithm);
            cipher.init(Cipher.DECRYPT_MODE, privateKey);

            outbuf = new ByteArrayOutputStream(nBlock * decryptBlockSize);
            for (int offset = 0; offset < encryptedBytes.length; offset += keyByteSize) {
                int inputLen = encryptedBytes.length - offset;
                if (inputLen > keyByteSize) {
                    inputLen = keyByteSize;
                }
                byte[] decryptedBlock = cipher.doFinal(encryptedBytes, offset, inputLen);
                outbuf.write(decryptedBlock);
            }
            outbuf.flush();
            return outbuf.toByteArray();
        } catch (Exception e) {
            throw new Exception("DEENCRYPT ERROR:", e);
        } finally {
            try{
                if(outbuf != null){
                    outbuf.close();
                }
            }catch (Exception e){
                outbuf = null;
                throw new Exception("CLOSE ByteArrayOutputStream ERROR:", e);
            }
        }
    }
    public static byte[] encrypt(byte[] plainBytes, PublicKey publicKey, int keyLength, int reserveSize, String cipherAlgorithm) throws Exception {
        int keyByteSize = keyLength / 8;
        int encryptBlockSize = keyByteSize - reserveSize;
        int nBlock = plainBytes.length / encryptBlockSize;
        if ((plainBytes.length % encryptBlockSize) != 0) {
            nBlock += 1;
        }
        ByteArrayOutputStream outbuf = null;
        try {
            Cipher cipher = Cipher.getInstance(cipherAlgorithm);
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);

            outbuf = new ByteArrayOutputStream(nBlock * keyByteSize);
            for (int offset = 0; offset < plainBytes.length; offset += encryptBlockSize) {
                int inputLen = plainBytes.length - offset;
                if (inputLen > encryptBlockSize) {
                    inputLen = encryptBlockSize;
                }
                byte[] encryptedBlock = cipher.doFinal(plainBytes, offset, inputLen);
                outbuf.write(encryptedBlock);
            }
            outbuf.flush();
            return outbuf.toByteArray();
        } catch (Exception e) {
            throw new Exception("ENCRYPT ERROR:", e);
        } finally {
            try{
                if(outbuf != null){
                    outbuf.close();
                }
            }catch (Exception e){
                outbuf = null;
                throw new Exception("CLOSE ByteArrayOutputStream ERROR:", e);
            }
        }
    }


    public static PublicKey getPublicKey(String keyAlgorithm) throws Exception {
        try
        {
            //加载公钥
            X509EncodedKeySpec pubX509 = new X509EncodedKeySpec(decodeBase64(PKCS8_PUBLIC));
            KeyFactory keyFactory = KeyFactory.getInstance(keyAlgorithm);
            PublicKey publicKey = keyFactory.generatePublic(pubX509);
            return publicKey;
        } catch (Exception e) {
            e.printStackTrace();
            throw new Exception("READ PUBLIC KEY ERROR:", e);
        }
    }


    //一下面是base64的编码和解码
    public static String encodeBase64(byte[]input) throws Exception{
        Class clazz=Class.forName("com.sun.org.apache.xerces.internal.impl.dv.util.Base64");
        Method mainMethod= clazz.getMethod("encode", byte[].class);
        mainMethod.setAccessible(true);
        Object retObj=mainMethod.invoke(null, new Object[]{input});
        return (String)retObj;
    }


    /***
     * decode by Base64
     */
    public static byte[] decodeBase64(String input) throws Exception{
        Class clazz=Class.forName("com.sun.org.apache.xerces.internal.impl.dv.util.Base64");
        Method mainMethod= clazz.getMethod("decode", String.class);
        mainMethod.setAccessible(true);
        Object retObj=mainMethod.invoke(null, input);
        return (byte[])retObj;
    }



    /**
     * <p>
     * 按照微信要求进行加密
     * </p>
     *
     * @param data 加密前的开户姓名/银行卡号
     * @return java.lang.String 加密后秘文
     * @author Winder
     * @date 2021/1/26 上午10:28
     */
    public static String encrypt(String data){
        PublicKey pub = null;
        try {
            //rsa加密
            pub = RSAUtils.getPublicKey("RSA");
            String rsa = "RSA/ECB/OAEPWITHSHA-1ANDMGF1PADDING";
            byte[] estrName = RSAUtils.encrypt(data.getBytes(), pub, 2048, 11, rsa);
            //base64加密
            data = Base64Utils.encodeToString(estrName);
            return data;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public static void main(String[] args) {
        String str = encrypt("张三");
        System.out.println(str);
    }

}

RequestUtils:

package com.sdjx.wx.util;

import com.sdjx.wx.constant.IExtractConstant;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.UUID;

/**
 * @Author:wpf
 * @ClassName:RequestUtils
 * @Date:2022/9/30
 * @Description:f发送请求工具类
 */

@Slf4j
public class RequestUtils {

    /**
     * 获取RSA加密公钥
     * @return RSA加密公钥
     *接口默认输出PKCS#1格式的公钥,商户需根据自己开发的语言选择公钥格式
     *
     * RSA公钥格式PKCS#1,PKCS#8互转说明
     * PKCS#1 转 PKCS#8:
     * openssl rsa -RSAPublicKey_in -in <filename> -pubout
     *
     * PKCS#8 转 PKCS#1:
     * openssl rsa -pubin -in <filename> -RSAPublicKey_out
     *
     * PKCS#1 格式密钥:
     * -----BEGIN RSA PUBLIC KEY-----
     * MIIBCgKCAQEArT82k67xybiJS9AD8nNAeuDYdrtCRaxkS6cgs8L9h83eqlDTlrdw
     * zBVSv5V4imTq/URbXn4K0V/KJ1TwDrqOI8hamGB0fvU13WW1NcJuv41RnJVua0QA
     * lS3tS1JzOZpMS9BEGeFvyFF/epbi/m9+2kUWG94FccArNnBtBqqvFncXgQsm98JB
     * 3a62NbS1ePP/hMI7Kkz+JNMyYsWkrOUFDCXAbSZkWBJekY4nGZtK1erqGRve8Jbx
     * TWirAm/s08rUrjOuZFA21/EI2nea3DidJMTVnXVPY2qcAjF+595shwUKyTjKB8v1
     * REPB3hPF1Z75O6LwuLfyPiCrCTmVoyfqjwIDAQAB
     * -----END RSA PUBLIC KEY-----
     *
     * PKCS#8 格式密钥:
     * -----BEGIN PUBLIC KEY-----
     * MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArT82k67xybiJS9AD8nNA
     * euDYdrtCRaxkS6cgs8L9h83eqlDTlrdwzBVSv5V4imTq/URbXn4K0V/KJ1TwDrqO
     * I8hamGB0fvU13WW1NcJuv41RnJVua0QAlS3tS1JzOZpMS9BEGeFvyFF/epbi/m9+
     * lkUWG94FccArNnBtBqqvFncXgQsm98JB3a42NbS1ePP/hMI7Kkz+JNMyYsWkrOUF
     * DCXAbSZkWBJekY4nGZtK1erqGRve8JbxTWirAm/s08rUrjOuZFA21/EI2nea3Did
     * JMTVnXVPY2qcAjF+595shwUKyTjKB8v1REPB3hPF1Z75O6LwuLfyPiCrCTmVoyfq
     * jwIDAQAB
     * -----END PUBLIC KEY-----
     */
    public static String getEncryptPublicKey() {
        try {
            Map<String, Object> param = new TreeMap<>();
            param.put("mch_id", IExtractConstant.MCH_ID);
            param.put("nonce_str", getUUID());
            param.put("sign_type", "MD5");
            String sign = SignUtils.getSign(param, IExtractConstant.MCH_KEY);
            param.put("sign", sign);
            String response = SendWeChatRequestUtil.sendWeChatRequest(IExtractConstant.MCH_ID,
                    IExtractConstant.GET_WECHAT_ENCRYPT_PUBLIC_KEY_URL,
                    XmlUtil.getRequestXml(param),
                    IExtractConstant.CERT_PATH);
            System.out.println("-------------------");
            System.out.println(response);
            if (StringUtils.isNotEmpty(response)) {
                Map<String, String> responseMap = XmlUtil.xmlToMap(response);
                if ("SUCCESS".equals(responseMap.get("return_code")) && "SUCCESS".equals(responseMap.get("result_code"))) {
                    return responseMap.get("pub_key");
                }else {
                    log.info(" weixin return fall :{}", response);
                }
            }

        } catch (Exception e) {
            log.error("调用微信企业付款至银行卡生成公钥异常:{}", e.getMessage());
        }
        return null;
    }

    /**
     * uuid 随机字符串
     * @return uuid 随机字符串
     */
    public static String getUUID() {
        return UUID.randomUUID().toString().replace("-", "");
    }

    /**
     * 变更公钥格式PKCS#1至PKCS#8
     * @param publicKeyString
     * @return
     * @throws InvalidKeySpecException
     * @throws NoSuchAlgorithmException
     */
    public  static PublicKey getPublicKey(String publicKeyString) throws InvalidKeySpecException, NoSuchAlgorithmException {
        //把RSA公钥格式PKCS#1转换成PKCS#8
        org.bouncycastle.asn1.pkcs.RSAPublicKey rsaPublicKey =  org.bouncycastle.asn1.pkcs.RSAPublicKey.getInstance(
                org.bouncycastle.util.encoders.Base64.decode(publicKeyString));

        java.security.spec.RSAPublicKeySpec publicKeySpec = new java.security.spec.RSAPublicKeySpec(rsaPublicKey.getModulus(), rsaPublicKey.getPublicExponent());

        java.security.KeyFactory keyFactory = java.security.KeyFactory.getInstance("RSA");

        PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);

        return publicKey;
    }

    public  static String readLocalPubKey(String pubKey) throws IOException {
        File file = null;
        try {

            Path tmpFile = Files.createTempFile("payToBank", ".pem");
            File file1 = tmpFile.toFile();
            String absolutePath = file1.getAbsolutePath();
            log.info("本地公钥路径:"+absolutePath);
            try (BufferedWriter writer = Files.newBufferedWriter(tmpFile)) {
                writer.write(pubKey);
            } catch (IOException e) {
                e.printStackTrace();
            }
            List<String> lines = Files.readAllLines(Paths.get(absolutePath), StandardCharsets.UTF_8);
            StringBuilder sb = new StringBuilder();
            for (String line : lines) {
                if (line.charAt(0) == '-') {
                    continue;
                } else {
                    sb.append(line);
                    sb.append('\r');
                    System.out.println("**"+sb);
                }
            }
            return sb.toString();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if (file != null){
                file.deleteOnExit();
            }
        }
        return null;
    }

}


SignUtils:

package com.sdjx.wx.util;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;

import java.util.Map;
import java.util.TreeMap;


/**
 * @Author:wpf
 * @ClassName:SignUtils
 * @Date:2022/9/29
 * @Description:获取签名工具类
 */

@Slf4j
public class SignUtils {

    /**
     * 获取签名方法
     *
     * @param requestParam 所有发送或者接收到的数据
     * @param key          key为商户平台设置的密钥key
     * @return 签名
     * 第一步,设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA。
     * 特别注意以下重要规则:
     * <p>
     * ◆ 参数名ASCII码从小到大排序(字典序);
     * ◆ 如果参数的值为空不参与签名;
     * ◆ 参数名区分大小写;
     * ◆ 验证调用返回或微信主动通知签名时,传送的sign参数不参与签名,将生成的签名与该sign值作校验。
     * ◆ 微信接口可能增加字段,验证签名时必须支持增加的扩展字段
     * 第二步,在stringA最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值signValue。 注意:密钥的长度为32个字节。
     * <p>
     * ◆ key设置路径:微信商户平台(pay.weixin.qq.com)-->账户中心-->账户设置-->API安全-->设置API密钥
     */
    public static String getSign(Map<String, Object> requestParam, String key) {
        Map<String, Object> params = new TreeMap<>();
        params.putAll(requestParam);
        StringBuffer buffer = new StringBuffer();
        for (Map.Entry<String, Object> param : params.entrySet()) {
            if (param.getValue() != null) {
                buffer.append(param.getKey() + "=" + param.getValue() + "&");
            }
        }
        buffer.append("key=" + key);
        String stringSignTemp = buffer.toString();
        String signValue = DigestUtils.md5Hex(stringSignTemp).toUpperCase();
        return signValue;
    }



}

XmlUtil:

package com.sdjx.wx.util;

import com.jfinal.weixin.sdk.utils.XmlHelper;
import org.apache.commons.lang3.StringUtils;
import org.xml.sax.SAXException;

import java.io.IOException;
import java.util.Map;


/**
 * @Author:wpf
 * @ClassName:XmlUtils
 * @Date:2022/9/29
 * @Description:xml和对象之间相互转化工具类
 */

public class XmlUtil {

    /**
     * 将请求集合拼接成xml字符串
     * @param params 请求集合
     * @return xml字符串
     */
    public static String getRequestXml(Map<String, Object> params) {
        StringBuffer sb = new StringBuffer();
        sb.append("<xml>");
        for (Map.Entry<String, Object> entry : params.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue().toString();
            // 略过空值
            if (StringUtils.isBlank(value)) continue;
            sb.append("<").append(key).append(">");
            sb.append(entry.getValue());
            sb.append("</").append(key).append(">");
        }
        sb.append("</xml>");
        return sb.toString();
    }

    /**
     * 微信返回xml解析成为Map集合
     * @param xmlStr 待解析xml字符串
     * @return 解析后Map集合
     */
    public static Map<String,String> xmlToMap(String xmlStr){
        XmlHelper xmlHelper = XmlHelper.of(xmlStr);
        return xmlHelper.toMap();
    }


    public static void main(String[] args) throws IOException, SAXException {
        Map<String, String> stringStringMap = xmlToMap("<xml>\n" +
                "<return_code><![CDATA[SUCCESS]]></return_code>\n" +
                "<return_msg><![CDATA[支付失败]]></return_msg>\n" +
                "<result_code><![CDATA[FAIL]]></result_code>\n" +
                "<err_code><![CDATA[FATAL_ERROR]]></err_code>\n" +
                "<err_code_des><![CDATA[订单信息不一致,请先查询订单]]></err_code_des>\n" +
                "<nonce_str><![CDATA[50780e0cca98c8c8e814883e5caa672e]]></nonce_str>\n" +
                "<mch_id><![CDATA[2302758702]]></mch_id>\n" +
                "<partner_trade_no><![CDATA[1212121221278]]></partner_trade_no>\n" +
                "<amount>5000</amount>\n" +
                "</xml>");
        System.out.println(stringStringMap);
        System.out.println(stringStringMap.get("return_msg"));
    }


}

SendWeChatRequestUtil:

package com.sdjx.wx.util;

import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
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.SSLContext;
import java.io.FileInputStream;
import java.security.KeyStore;

/**
 * @Author:wpf
 * @ClassName:SendWeChatRequestUtil
 * @Date:2022/9/29
 * @Description:携带证书发送微信请求
 */

public class SendWeChatRequestUtil {

    /**
     * 携带证书发送微信请求
     *
     * @param mchId    证书密码 默认MCHID
     * @param url      请求路径
     * @param data     请求体
     * @param certPath 证书路径
     * @return 微信请求响应结果
     * <p>
     * 注意PKCS12证书 是从微信商户平台-》账户设置-》 API安全 中下载的
     */
    public static String sendWeChatRequest(String mchId, String url, String data, String certPath) throws Exception {
        //指定读取证书格式为PKCS12
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        //读取证书文件
        FileInputStream inputStream = new FileInputStream(certPath);
        try {
            //指定PKCS12的密码
            keyStore.load(inputStream, mchId.toCharArray());
        } finally {
            inputStream.close();
        }
        //信任自己的CA和所有自签名证书
        SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, mchId.toCharArray()).build();
        //仅允许TLSv1协议
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, SSLConnectionSocketFactory.getDefaultHostnameVerifier());
        CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
        try {
            HttpPost httpPost = new HttpPost(url);
            httpPost.setEntity(new StringEntity(data, "UTF-8"));
            CloseableHttpResponse response = httpclient.execute(httpPost);
            try {
                HttpEntity entity = response.getEntity();
                String jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8");
                EntityUtils.consume(entity);
                return jsonStr;
            } finally {
                response.close();
            }
        } finally {
            httpclient.close();
        }

    }
}

PKCS#1转成PKCS#8在线工具 网址 http://www.metools.info/code/c85.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值