微信商家转账到零钱 V3(JAVA)转账与查询

注:本文仅供参考,应用于生产需完善 刷新支付证书序列号及转账验签操作,具体实现由于本农最近疯狂加班,暂时没有时间更新。不久将来会继续完善本文章

开发前准备:

1.商户id

2.支付证书序列号

3.证书私钥  apiclient_key.pem 文件

4.小程序或公众号需要与商户号绑定

PS

        转账地址:批量转账

        https://api.mch.weixin.qq.com/v3/transfer/batches

        查询地址: 商户批次号查询

        https://api.mch.weixin.qq.com/v3/transfer/batches/out-batch-no

官方jar 坐标

<dependency>
    <groupId>com.github.wechatpay-apiv3</groupId>
    <artifactId>wechatpay-apache-httpclient</artifactId>
    <version>0.4.7</version>
</dependency>    

代码

1.工具类

        

package '文件包路径';

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Random;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;

@Slf4j
public class VechatPayV3Util {


    /**
     * @param method       请求方法 post
     * @param canonicalUrl 请求地址
     * @param body         请求参数   GET请求传空字符
     * @param merchantId   这里用的商户号
     * @param certSerialNo 商户证书序列号
     * @param keyPath      私钥商户证书地址
     * @return
     * @throws Exception
     */
    public static String getToken(
            String method,
            String canonicalUrl,
            String body,
            String merchantId,
            String certSerialNo,
            String keyPath) throws Exception {
        String signStr = "";
        //获取32位随机字符串
        String nonceStr = getRandomString(32);
        //当前系统运行时间
        long timestamp = System.currentTimeMillis() / 1000;
        String message = buildMessage(method, canonicalUrl, timestamp, nonceStr, body);
        //签名操作
        //签名操作
        String signature = sign(message.getBytes("utf-8"), keyPath);
        //组装参数
        signStr = "mchid=\"" + merchantId + "\"," +
                "timestamp=\"" + timestamp + "\"," +
                "nonce_str=\"" + nonceStr + "\"," +
                "serial_no=\"" + certSerialNo + "\"," +
                "signature=\"" + signature + "\"";

        return signStr;
    }

    public static String buildMessage(String method, String canonicalUrl, long timestamp, String nonceStr, String body) {
//		String canonicalUrl = url.encodedPath();
//		if (url.encodedQuery() != null) {
//			canonicalUrl += "?" + url.encodedQuery();
//		}
        return method + "\n" + canonicalUrl + "\n" + timestamp + "\n" + nonceStr + "\n" + body + "\n";
    }

    public static String sign(byte[] message, String keyPath) throws Exception {
        Signature sign = Signature.getInstance("SHA256withRSA");
        sign.initSign(getPrivateKey(keyPath));
        sign.update(message);
        return Base64.encodeBase64String(sign.sign());
    }

    /**
     * 微信支付-前端唤起支付参数-获取商户私钥
     *
     * @param filename 私钥文件路径  (required)
     * @return 私钥对象
     */
    public static PrivateKey getPrivateKey(String filename) throws IOException {

        //log.info("签名 证书地址是 " + filename);
        String content = new String(Files.readAllBytes(Paths.get(filename)), "utf-8");
        try {
            String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
                    .replace("-----END PRIVATE KEY-----", "")
                    .replaceAll("\\s+", "");
            KeyFactory kf = KeyFactory.getInstance("RSA");
            return kf.generatePrivate(
                    new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey)));
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("当前Java环境不支持RSA", e);
        } catch (InvalidKeySpecException e) {
            throw new RuntimeException("无效的密钥格式");
        }
    }

    /**
     * 获取随机位数的字符串
     *
     * @param length  需要的长度
     * @return
     */
    public static String getRandomString(int length) {
        String base = "abcdefghijklmnopqrstuvwxyz0123456789";
        Random random = new Random();
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < length; i++) {
            int number = random.nextInt(base.length());
            sb.append(base.charAt(number));
        }
        return sb.toString();
    }
}

import com.wechat.pay.contrib.apache.httpclient.exception.NotFoundException;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.*;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;

import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import static com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.WECHAT_PAY_SERIAL;
import static org.apache.http.HttpHeaders.*;
import static org.apache.http.entity.ContentType.APPLICATION_JSON;


/**
 * 微信支付专用类 请求操作方法
 *
 * @author Administrator
 */
@Slf4j
public class HttpUtil {

    /**
     * 发起批量转账API 批量转账到零钱
     *
     * @param requestUrl
     * @param requestJson       组合参数
     * @param wechatPayserialNo 商户证书序列号
     * @param privatekeypath    商户私钥证书路径
     * @return
     */
    public static String postTransBatRequest(
            String requestUrl,
            String requestJson,
            String wechatPayserialNo,
            String mchId,
            String privatekeypath, String url) {
        CloseableHttpResponse response = null;
        HttpEntity entity = null;
        CloseableHttpClient httpClient = null;
        try {
            HttpPost httpPost = createHttpPost(requestUrl, requestJson, wechatPayserialNo, mchId, privatekeypath, url);
             httpClient = HttpClients.createDefault();
            //发起转账请求
            response = httpClient.execute(httpPost);
            log.info("response:{}", response);
            entity = response.getEntity();//获取返回的数据
            log.info("-----getHeaders.Request-ID:" + response.getHeaders("Request-ID"));
            return EntityUtils.toString(entity);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 关闭流
            try {
                if (httpClient!=null) {
                    httpClient.close();
                }
                

            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }



    /**
     * 账单查询
     *
     * @param requestUrl        请求完整地址
     * @param wechatPayserialNo 商户证书序列号
     * @param privatekeypath    商户私钥证书路径
     * @param privatekeypath    支付秘钥
     * @return
     */
    public static String getTransBatRequest(
            String requestUrl,
            String wechatPayserialNo,
            String mchId,
            String privatekeypath, String url) throws NotFoundException {
        CloseableHttpResponse response = null;
        HttpEntity entity = null;
        CloseableHttpClient httpClient = null;
        try {
            HttpGet httpPost = createHttpGet(requestUrl, wechatPayserialNo, mchId, privatekeypath, url);
            httpClient = HttpClients.createDefault();
            //发起转账请求
            response = httpClient.execute(httpPost);
            log.info("response:{}", response);
            entity = response.getEntity();//获取返回的数据
            log.info("-----getHeaders.Request-ID:" + response.getHeaders("Request-ID"));
            return EntityUtils.toString(entity);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 关闭流
            try {
                if (httpClient!=null) {
                    httpClient.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     *
     * @param requestUrl          请求完整地址
     * @param requestJson         请求参数
     * @param wechatPayserialNo   支付证书序列号
     * @param mchId                商户号
     * @param privatekeypath      私钥路径
     * @param ServletPath          相对路径
     * @return
     */
    private static HttpPost createHttpPost(String requestUrl,
                                           String requestJson,
                                           String wechatPayserialNo,
                                           String mchId,
                                           String privatekeypath, String ServletPath) {
        //商户私钥证书
        HttpPost httpPost = new HttpPost(requestUrl);
        // NOTE: 建议指定charset=utf-8。低于4.4.6版本的HttpCore,不能正确的设置字符集,可能导致签名错误
        httpPost.addHeader(ACCEPT, APPLICATION_JSON.toString());
        httpPost.addHeader(CONTENT_TYPE, APPLICATION_JSON.toString());
        //"55E551E614BAA5A3EA38AE03849A76D8C7DA735A");
        httpPost.addHeader(WECHAT_PAY_SERIAL, wechatPayserialNo);

        //-------------------------核心认证 start-----------------------------------------------------------------
        String strToken = null;
        try {
            log.info("requestJson:{}", requestJson);
            strToken = VechatPayV3Util.getToken("POST",
                    ServletPath,
                    requestJson, mchId, wechatPayserialNo, privatekeypath);
        } catch (Exception e) {
            log.error("createHttpPost error:", e);
            e.printStackTrace();
        }
        StringEntity reqEntity = new StringEntity(requestJson, APPLICATION_JSON);
        log.info("token " + strToken);
        // 添加认证信息
        httpPost.addHeader("Authorization",
                "WECHATPAY2-SHA256-RSA2048" + " "
                        + strToken);
        //---------------------------核心认证 end---------------------------------------------------------------
        httpPost.setEntity(reqEntity);
        return httpPost;
    }

    /**
     * 创建get 请求
     *
     * @param requestUrl        请求完整地址
     * @param wechatPayserialNo 支付证书序列号
     * @param mchId              商户号
     * @param privatekeypath     私钥路径
     * @param ServletPath        相对路径  请求地址上如果有参数 则此处需要带上参数
     * @return HttpGet
     */
    private static HttpGet createHttpGet(String requestUrl,
                                         String wechatPayserialNo,
                                         String mchId,
                                         String privatekeypath, String ServletPath) {
        //商户私钥证书
        HttpGet httpGet = new HttpGet(requestUrl);
        // NOTE: 建议指定charset=utf-8。低于4.4.6版本的HttpCore,不能正确的设置字符集,可能导致签名错误
        httpGet.addHeader("Content-Type", "application/json");
        httpGet.addHeader("Accept", "application/json");
        //"55E551E614BAA5A3EA38AE03849A76D8C7DA735A");
        httpGet.addHeader("Wechatpay-Serial", wechatPayserialNo);
        //-------------------------核心认证 start-----------------------------------------------------------------
        String strToken = null;
        try {
            strToken = VechatPayV3Util.getToken("GET",
                    ServletPath,
                    "", mchId, wechatPayserialNo, privatekeypath);
        } catch (Exception e) {
            log.error("createHttpGet error:", e);
            e.printStackTrace();
        }

        log.info("token " + strToken);
        // 添加认证信息
        httpGet.addHeader("Authorization",
                "WECHATPAY2-SHA256-RSA2048" + " "
                        + strToken);
        //---------------------------核心认证 end---------------------------------------------------------------
        return httpGet;
    }

2 创建参数调用

         -------------------------------------------------转账------------------------------------------------

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

        //小程序 id
        postMap.put("appid", appId);
         
        postMap.put("out_batch_no", outNo);
        //该笔批量转账的名称
        postMap.put("batch_name", batchName);
        //转账说明,UTF8编码,最多允许32个字符
        postMap.put("batch_remark", batchRemark);
        //转账金额单位为“分”。 总金额
        postMap.put("total_amount",integer );
        //转账总笔数
        postMap.put("total_num", 1);


        List<Map> list = new ArrayList<>();
        Map<String, Object> subMap = new HashMap<>(4);
        //商家明细单号 该商家下唯一
        subMap.put("out_detail_no", outNo);
        //转账金额
        subMap.put("transfer_amount", integer);
        //转账备注
        subMap.put("transfer_remark", remark);
        //用户在直连商户应用下的用户标示
        subMap.put("openid", openId);
        //大金额需要传入真实姓名
		//subMap.put("user_name", RsaCryptoUtil.encryptOAEP(userName,                     VechatPayV3Util.getSaveCertificates(privatekeypath)));
        list.add(subMap);
        postMap.put("transfer_detail_list", list);

        String result = HttpUtil.postTransBatRequest(
                   "https://api.mch.weixin.qq.com/v3/transfer/batches",
               JSONObject.toJSONString(postMap),
                "支付证书序列号",
                "商户号",
               "私钥路径","/v3/transfer/batches");
 JSONObject jsonObject = JSONObject.parseObject(result);
//成功示例
/**
 * {
 *   "out_batch_no": "plfk2020042013",
 *   "batch_id": "1030000071100999991182020050700019480001",
 *   "create_time": "2015-05-20T13:29:35.120+08:00"
 * }
 *
 */

        if (!StringUtils.isEmpty(jsonObject.get("create_time"))){
  //转账成功
}else{
    //失败
}
          

-----------------------------------------------------查询------------------------------------------------------------

//使用商家批次号查询

String result  =  HttpUtil.getTransBatRequest(
                "https://api.mch.weixin.qq.com/v3/transfer/batches/out-batch-no"+"/"+"商家批次号"+"?need_query_detail=True&detail_status=ALL",
                "支付证书序列号",
                "商户号",
                "商户私钥路径","/v3/transfer/batches/out-batch-no"+"/"+"商家批次号"+"?need_query_detail=True&detail_status=ALL");


 JSONObject  jsonOb  =  JSONObject.parseObject(result);
    
//正常返回示例
        
/**
 * {
 *   "limit": 20,
 *   "offset": 1,
 *   "transfer_batch": {
 *     "mchid": "1900001109",
 *     "out_batch_no": "plfk2020042013",
 *     "batch_id": "1030000071100999991182020050700019480001",
 *     "appid": "wxf636efh567hg4356",
 *     "batch_status": "ACCEPTED",
 *     "batch_type": "API",
 *     "batch_name": "2019年1月深圳分部报销单",
 *     "batch_remark": "2019年1月深圳分部报销单",
 *     "close_reason": "OVERDUE_CLOSE",
 *     "total_amount": 4000000,
 *     "total_num": 200,
 *     "create_time": "2015-05-20T13:29:35.120+08:00",
 *     "update_time": "2015-05-20T13:29:35.120+08:00",
 *     "success_amount": 3900000,
 *     "success_num": 199,
 *     "fail_amount": 100000,
 *     "fail_num": 1
 *   },
 *   "transfer_detail_list": [
 *     {
 *       "detail_id": "1040000071100999991182020050700019500100",
 *       "out_detail_no": "x23zy545Bd5436", 详细单号
 *       "detail_status": "SUCCESS"        成功  FAIL=失败  PROCESSING =支付中
 *     }
 *   ]
 * }
 */


            

评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值