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

********已有新版SDK集成,不需要获取平台证书,加解密,点击下方链接*************

链接:微信APIV3 JAVA SDK集成(JSAPI支付、商家转账到零钱、订单退款、多商户配置)超简单、新鲜,附源码_草不生的博客-CSDN博客

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

前言:应兄弟们要求,已附上demo,填好配置可直接运行,里面也加入了回查方法,具体的大家慢慢研究,全是哥们儿对着文档一点一点研究出来的,兄弟们顺手点个赞,谢谢

本文先展示发起转账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://myssl.com/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列表 - 商家转账到零钱 | 微信支付商户文档中心

微信商家转账零钱 V3 的实现需要通过微信支付 API 接口来实现,以下是 JAVA 版本的代码实现: 1. 导入依赖包 ```java import java.util.HashMap; import java.util.Map; import com.github.wxpay.sdk.WXPay; import com.github.wxpay.sdk.WXPayConfig; import com.github.wxpay.sdk.WXPayConstants; import com.github.wxpay.sdk.WXPayUtil; ``` 2. 构造微信支付配置对象 ```java public class WXPayConfigImpl implements WXPayConfig { private String appID; // 公众账号ID或应用ID private String mchID; // 商户号 private String key; // 商户密钥 private String certPath; // 商户证书路径 private int httpConnectTimeoutMs = 6 * 1000; // 连接时时间 private int httpReadTimeoutMs = 8 * 1000; // 读取时时间 public WXPayConfigImpl(String appID, String mchID, String key, String certPath) { this.appID = appID; this.mchID = mchID; this.key = key; this.certPath = certPath; } @Override public String getAppID() { return appID; } @Override public String getMchID() { return mchID; } @Override public String getKey() { return key; } @Override public InputStream getCertStream() { try { return new FileInputStream(new File(certPath)); } catch (FileNotFoundException e) { e.printStackTrace(); } return null; } @Override public int getHttpConnectTimeoutMs() { return httpConnectTimeoutMs; } @Override public int getHttpReadTimeoutMs() { return httpReadTimeoutMs; } } ``` 3. 构造微信支付对象 ```java WXPayConfig wxPayConfig = new WXPayConfigImpl(appID, mchID, key, certPath); WXPay wxPay = new WXPay(wxPayConfig, WXPayConstants.SignType.MD5, true); ``` 4. 构造参数并调用接口 ```java // 构造请求参数 Map<String, String> reqData = new HashMap<String, String>(); reqData.put("mch_appid", appID); reqData.put("mchid", mchID); reqData.put("nonce_str", WXPayUtil.generateNonceStr()); reqData.put("partner_trade_no", "xxxxxxxxxxxx"); // 商户订单号 reqData.put("openid", "xxxxxxxxxxxx"); // 用户openid reqData.put("check_name", "NO_CHECK"); // 不校验真实姓名 reqData.put("amount", "100"); // 转账金额 reqData.put("desc", "测试转账"); // 转账描述 reqData.put("spbill_create_ip", "127.0.0.1"); // 调用接口的机器IP地址 // 调用接口 Map<String, String> respData = wxPay.transfer(reqData); ``` 其中,`appID`、`mchID`、`key`、`certPath` 等参数需要根据实际情况填写。`wxPay.transfer(reqData)` 方法返回的是一个 Map 对象,包含了接口响应的所有信息,可以根据业务需求进行处理。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值