分享

微信支付商家转账到零钱(超详细JAVA教程,全源码)

 hncdman 2023-06-05 发布于湖南

文章标签: java 微信 开发语言

版权

开通过程不做叙述,查看微信官方文档,仅介绍java调用api。

本文先展示发起转账API,后面再讲述发起转账需要的资料以及途中遇到的坑。

1、发起商家转账到零钱方法:

/**

*调用API参数准备 当传入姓名的时候需要做敏感信息加解密

*/

public HttpResult wechatTransfer(String orderNo, String openid, String userName, Integer amount){

    Map<String, Object> postMap = new HashMap<>(10);

    postMap.put("appid", DouDianConfig.APPID);

    postMap.put("out_batch_no", orderNo);

    //该笔批量转账的名称

    postMap.put("batch_name", "商户提现");

    //转账说明,UTF8编码,最多允许32个字符

    postMap.put("batch_remark", username);

    //转账金额单位为“分”。 总金额

    System.out.println("转账总金额"+amount.toString());

    postMap.put("total_amount", amount);

    //。转账总笔数

    postMap.put("total_num", 1);

    List<Map> list = new ArrayList<>();

    Map<String, Object> subMap = new HashMap<>(4);

    //商家明细单号

    subMap.put("out_detail_no", orderNo);

    //转账金额

    subMap.put("transfer_amount", amount);

    //转账备注

    subMap.put("transfer_remark", "用户提现(姓名:"+ userName +")");

    //用户在直连商户应用下的用户标示

    subMap.put("openid", openid);

    //转账金额

    log.info("用户提现(姓名:"+ userName)");

    subMap.put("user_name", rsaEncryptOAEP(userName));

    list.add(subMap);

    postMap.put("transfer_detail_list", list);

    return postTransBatRequest(

            "https://api.mch.weixin.qq.com/v3/transfer/batches",  JSON.toJSONString(postMap));

}

/**

 * 发起转账请求 (微信所有的接口都需要获取access_token,下面有getToken方法)

 * @param requestUrl 请求地址

 * @param requestJson 请求参数

 * @return 响应

 */

public HttpResult postTransBatRequest(

        String requestUrl,

        String requestJson) {

    CloseableHttpClient httpclient = HttpClients.createDefault();

    CloseableHttpResponse response = null;

    HttpEntity entity = null;

    try {

        //商户私钥证书

        HttpPost httpPost = new HttpPost(requestUrl);

        // NOTE: 建议指定charset=utf-8。低于4.4.6版本的HttpCore,不能正确的设置字符集,可能导致签名错误

        httpPost.addHeader("Content-Type", "application/json");

        httpPost.addHeader("Accept", "application/json");

        //"55E551E614BAA5A3EA38AE03849A76D8C7DA735A");

        httpPost.addHeader("Wechatpay-Serial", DouDianConfig.WX_SERIAL_NO);

        //-------------------------核心认证 start-----------------------------------------------------------------

        String authorization = WechatUtils.getToken("POST","/v3/transfer/batches",requestJson,DouDianConfig.APICLIENT_KEY,DouDianConfig.MCHID,DouDianConfig.SERIAL_NO);

        // 添加认证信息

        httpPost.addHeader("Authorization",

                "WECHATPAY2-SHA256-RSA2048" + " "

                        + authorization);

        //---------------------------核心认证 end---------------------------------------------------------------

        httpPost.setEntity(new StringEntity(requestJson, "UTF-8"));

        //发起转账请求

        response = httpclient.execute(httpPost);

        String content = EntityUtils.toString(response.getEntity(), "UTF-8");

        log.info("微信转账响应"+content);

        return new HttpResult(response.getStatusLine().getStatusCode(),

                content);

    } catch (Exception e) {

        e.printStackTrace();

    } finally {

        // 关闭流

    }

    return null;

}

/**

 * 获取微信api v3请求验证头 Authorization

 * @param method http请求方法  大写

 * @param url 请求地址 不含域名

 * @param body 请求报文主体  无填写 ""

 * @param privateKey 请求

 * @param mchid 商户号

 * @param serialNo 证书序列号

 * @return

 */

public static String getToken (String method, String url, String body, String privateKey, String mchid, String serialNo){

    try {

        String nonceStr = WXPayUtil.generateNonceStr();

        long timestamp = System.currentTimeMillis() / 1000;

        String msg = buildMessage(method, url, timestamp, nonceStr, body);

        return "mchid=\"" + mchid + "\","

                + "nonce_str=\"" + nonceStr + "\","

                + "timestamp=\"" + timestamp + "\","

                + "serial_no=\"" + serialNo + "\","

                + "signature=\"" + sha256withRSA(msg,privateKey) + "\"";

    } catch (Exception e) {

        e.printStackTrace();

        log.error("[WechatUtils getToken] SHA256withRSA 转义失败 error message:{}",e.getMessage());

    }

   return "fail";

}

/**

 * 敏感信息加密 (这是最坑的接口,没有之一,加密用到的证书是平台证书!!!不是商家证书)

 * 后面再叙述平台证书获取

 * @param message 需加密信息

 * @return 加密之后的信息

 */

private String rsaEncryptOAEP(String message) {

    try {

        Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");

        cipher.init(Cipher.ENCRYPT_MODE,getCertificate(new ByteArrayInputStream(DouDianConfig.CIPHERTEXT.getBytes())));

        byte[] data = message.getBytes("utf-8");

        byte[] cipherdata = cipher.doFinal(data);

        return Base64.getEncoder().encodeToString(cipherdata);

    } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {

        throw new RuntimeException("当前Java环境不支持RSA v1.5/OAEP", e);

    } catch (InvalidKeyException e) {

        throw new IllegalArgumentException("无效的证书", e);

    } catch (IllegalBlockSizeException | BadPaddingException e) {

        //throw new IllegalBlockSizeException("加密原串的长度不能超过214字节");

        throw new BizException("加密原串的长度不能超过214字节");

    }catch (Exception e){

        e.printStackTrace();

        throw new BizException("系统错误");

    }

}

/**

 * 证书解析

 * @param inputStream 证书数据流

 * @return X509

 */

public static X509Certificate getCertificate(InputStream inputStream) {

    try {

        CertificateFactory cf = CertificateFactory.getInstance("X509");

        X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream);

        cert.checkValidity();

        return cert;

    } catch (CertificateExpiredException e) {

        throw new RuntimeException("证书已过期", e);

    } catch (CertificateNotYetValidException e) {

        throw new RuntimeException("证书尚未生效", e);

    } catch (CertificateException e) {

        throw new RuntimeException("无效的证书", e);

    }

}

2、配置类,除平台证书外的参数请自行在商家平台获取

public class DouDianConfig {

    /**

     * 固定值

     */

    public static final String GRANT_TYPE = "client_credential";

    /**

     * 小程序appid

     */

    public static final String APPID = "";

    /**

     * 小程序密钥

     */

    public static final String SECRET = "";

    /**

     * 商户号

     */

    public static final String MCHID = "";

    /**

     * 商户密钥 --- 同 API V3 密钥

     */

    public static final String KEY = "";

    /**

     * API V3 秘钥

     */

    public static final String API_V3_KEY = "";

    public static final String BODY = "";

    /**

     * 支付分签名方式

     */

    public static final String SIGN_TYPE = "HMAC-SHA256";

    /**

     * 金额环回说明

     */

    public static final String REFUND_DESC = "";

    /**

     * 商户api证书序列号

     * https:///cert_decode.html 证书解析地址

     */

    public static final String SERIAL_NO = "";

    /**

     * 证书内容  apiclient_cert.pem文件中的内容 除去开头结尾行

     *  请求量大为节约资源直接拿出来

     */

    public static final String PRIVATE_CONTENT = "";

    public static final String APICLIENT_KEY = "";

    /**

     * 微信平台证书  5年有效期 通过接口获取

     */

    public static final String CIPHERTEXT="";

    /**

     * 平台证书序列号

     */

    public static final  String WX_SERIAL_NO="";

}

3、平台证书获取(在转账不需要校验用户姓名的情况下是不需要平台证书的)

/**

 * 获取微信支付平台证书

 */

public static HttpResult getWeChatCertificates(){

    CloseableHttpClient httpclient = HttpClients.createDefault();

    CloseableHttpResponse response = null;

    HttpEntity entity = null;

    try {

        String requestUrl = "https://api.mch.weixin.qq.com/v3/certificates";

        //商户私钥证书

        HttpGet httpPost = new HttpGet(requestUrl);

        // NOTE: 建议指定charset=utf-8。低于4.4.6版本的HttpCore,不能正确的设置字符集,可能导致签名错误

        httpPost.addHeader("Content-Type", "application/json");

        httpPost.addHeader("Accept", "application/json");

        //"55E551E614BAA5A3EA38AE03849A76D8C7DA735A");

        httpPost.addHeader("User-Agent", WXPayConstants.USER_AGENT + " " + DouDianConfig.MCHID);

        //-------------------------核心认证 start-----------------------------------------------------------------

        String authorization =getToken("GET", "/v3/certificates", "", DouDianConfig.APICLIENT_KEY, DouDianConfig.MCHID, DouDianConfig.SERIAL_NO);

        // 添加认证信息

        httpPost.addHeader("Authorization",

                "WECHATPAY2-SHA256-RSA2048" + " "

                        + authorization);

        //发起转账请求

        response = httpclient.execute(httpPost);

        String content = EntityUtils.toString(response.getEntity(), "UTF-8");

        System.out.println(content);

        return new HttpResult(response.getStatusLine().getStatusCode(),

                content);

    } catch (Exception e) {

        e.printStackTrace();

    } finally {

        // 关闭流

    }

    return null;

}

上面接口返回的是一串json字符串因为比较懒,而且证书有效期是5年,所以我这边是把请求拿到的值没有直接转成对象,而是把需要的三个参数复制出来:

String ciphertext = "";

String nonce="";

String associated_data ="";

EncodeRsa encodeRsa = new EncodeRsa(DouDianConfig.KEY.getBytes());

String s = encodeRsa.decryptToString(associated_data.getBytes(),nonce.getBytes(),ciphertext);

s就是最终得到的平台证书内容了,复制到配置类中就行了;

4、接口回查

不要以为上面的流程走完就结束了,还有最大的一个坑,商家转账到零钱新接口因为是批量操作,接口调起以后并不会直接返回给你成功还是失败,需要进行回查才能知道哪一笔转账是否成功,一般情况下是不会出现失败,但是如果转账时候传入了username,需要校验姓名是否正确的时候,出现失败的可能性就极大的提高了,必须回查,有空再写回查方法,也可以去微信官方文档复制

API列表 - 商家转账到零钱 | 微信支付商户文档中心

————————————————

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约