Java实现微信小程序商家转账到零钱功能V3

Java实现微信小程序商家转账到零钱功能V3版本

工具类

该工具类主要用于发送请求签名等

java 复制代码
@Slf4j
public class WechatPayV3Util {


    /**
     * @param method       请求方法 post
     * @param canonicalUrl 请求地址
     * @param body         请求参数
     * @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;
        if (StringUtils.isEmpty(body)) {
            body = "";
        }
        String message = buildMessage(method, canonicalUrl, timestamp, nonceStr, body);
        //签名操作
        //签名操作
        String signature = sign(message.getBytes(StandardCharsets.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) {
        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 {

        //todo 我放在项目里的  也可以放在服务器上 都可以 看自己需求了
        String content = new String(Files.readAllBytes(Paths.get("src/main/resources" + filename)), StandardCharsets.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("不支持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();
    }

    /**
     * 微信敏感数据加密-公钥
     * @return
     */
    public static X509Certificate getSaveCertificates(String platformCertPath) throws IOException {
        InputStream inputStream = Files.newInputStream(new File(platformCertPath).toPath());
        BufferedInputStream bis = new BufferedInputStream(inputStream);
        return PemUtil.loadCertificate(bis);
    }
}

请求地址信息

java 复制代码
public class WxApiUrl {

    /**
     * 商家转账到零钱
     */
    public static String WECHAT_TRANSFERS_URL = "https://api.mch.weixin.qq.com/v3/transfer/batches";
    /**
     * 商户批次号查询
     */
    public static String SELECT_ORDER_URL = "https://api.mch.weixin.qq.com/v3/transfer/batches/out-batch-no";
}

配置文件中值填充到属性中

java 复制代码
@Configuration
@ConfigurationProperties(prefix = "wxpay") //读取wxpay节点
@Data //使用set方法将wxpay节点中的值填充到当前类的属性中
@Slf4j
public class WxPayConfig {

    // 商户号
    private String mchId;

    // 商户API证书序列号
    private String mchSerialNo;

    // 商户私钥文件
    private String privateKeyName;

    // APIv3密钥
    private String apiV3Key;

    // APPID
    private String appid;

    // 微信服务器地址
    private String domain;

    // 接收结果通知地址
    private String notifyDomain;
}

配置文件

java 复制代码
# 微信支付相关参数
wxpay:
  # 商户号
  mch-id: xxxxxxx
  # 商户API证书序列号
  mch-serial-no: xxxxxxxxxxxxxxxxxxxxxxxxxxx
  # 商户私钥文件
  # 注意:该文件放在resource/file
  private-key-name: file/apiclient_key.pem
  # APIv3密钥
  api-v3-key: xxxxxxxxxxxxxxxxxxxxx
  # APPID
  appid: xxxxxxxxxxxxxxx
  # 微信服务器地址
  domain: https://api.mch.weixin.qq.com
  # 接收结果通知地址
  # 注意:每次重新启动ngrok,都需要根据实际情况修改这个配置
  notify-domain: http://tozzn.e3.luyouxia.net:10062

提现与账单查询

java 复制代码
public interface WeChatApiDao {

    /**
     * 提現
     * @return
     */
    boolean transfers(String realName,String openId ,String outTradeNo, BigDecimal amount);


    /**
     * 状态 0待确认 1已受理 2转账中 3已完成 4已关闭
     * @param outTradeNo
     * @return
     */
    Integer queryTransfersStatus(String outTradeNo);
}
java 复制代码
@Slf4j
@Component
public class WeChatApiDaoImpl implements WeChatApiDao {


    @Resource
    private WxPayConfig wxPayConfig;

    @Override
    @SneakyThrows
    public boolean transfers(String realName,String openId ,String outTradeNo, BigDecimal amount) {

//        if (StringUtils.isBlank(realName)) {
//            throw new NingException(500, "请确认是否设置真实姓名");
//        }

        int value = amount.multiply(new BigDecimal(100)).intValue();

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

        //小程序 id
        postMap.put("appid", wxPayConfig.getAppid()); // 需要跟商户ID 绑定的奥
        postMap.put("out_batch_no", outTradeNo);
        //该笔批量转账的名称
        postMap.put("batch_name", "提现");
        //转账说明
        postMap.put("batch_remark", "提现");
        //转账金额单位为"分"。 总金额

        postMap.put("total_amount", value);
        //转账总笔数
        postMap.put("total_num", 1);

        // 转账批次集合
        List<Map<String, Object>> list = new ArrayList<>();
        Map<String, Object> subMap = new HashMap<>(4);
        //商家明细单号 (系统内唯一)
        subMap.put("out_detail_no", CommUtils.getUUID());
        //转账金额
        subMap.put("transfer_amount", value);
        //转账备注
        subMap.put("transfer_remark", "提现到账");
        //用户使用小程序时 记录了用户的微信opneId  必须是在Appid 下的openId  不然不会成功的奥
        subMap.put("openid", openId);
        //大金额需要传入真实姓名 根据自己需求设置
//        subMap.put("user_name", RsaCryptoUtil.encryptOAEP(realName, WechatPayV3Util.getSaveCertificates(wxPayConfig.getPrivateKeyName())));
        list.add(subMap);
        postMap.put("transfer_detail_list", list);

        log.info("请求参数:{}",JSONObject.toJSONString(postMap));

        String result = HttpUtil.postTransBatRequest(
                WxApiUrl.WECHAT_TRANSFERS_URL,// 请求地址
                JSONObject.toJSONString(postMap),// 请求参数
                wxPayConfig.getMchSerialNo(),// 支付证书序列号
                wxPayConfig.getMchId(),// 商户号
                wxPayConfig.getPrivateKeyName()); //证书私钥地址
        JSONObject jsonObject = JSONObject.parseObject(result);

        log.info("返回结果:{}",jsonObject);
        /*正常返回
        *{
			 "out_batch_no" : "plfk2020042013",
			  "batch_id" : "1030000071100999991182020050700019480001",
			  "create_time" : "2015-05-20T13:29:35.120+08:00",
			  "batch_status" : "ACCEPTED"
		}
        */

        return "ACCEPTED".equals(jsonObject.getString("batch_status"));

    }

    @Override
    public Integer queryTransfersStatus(String outTradeNo) {

        String result = HttpUtil.getTransBatRequest(
                WxApiUrl.SELECT_ORDER_URL + "/" + outTradeNo + "?need_query_detail=true&detail_status=ALL",// 请求地址
                wxPayConfig.getMchSerialNo(),// 支付证书序列号
                wxPayConfig.getMchId(),// 商户号
                wxPayConfig.getPrivateKeyName(),
                "/v3/transfer/batches/out-batch-no/" + outTradeNo + "?need_query_detail=true&detail_status=ALL"
                ); //证书私钥地址
        JSONObject jsonObject = JSONObject.parseObject(result);

        JSONArray orderArr = jsonObject.getJSONArray("transfer_detail_list");
        if(orderArr == null){
            return 5;
        }

        JSONObject transferBatch = (JSONObject) orderArr.get(0);


        String status = transferBatch.getString("detail_status");

        if ("WAIT_PAY".equals(status)) {
            return 1;
        }else if ("PROCESSING".equals(status)) {
            return 2;
        }else if ("SUCCESS".equals(status)) {
            return 3;
        }else if ("FAIL".equals(status)) {
            return 4;
        }else if ("INIT".equals(status)) {
            return 0;
        }
        return 5;
    }

ok,大功告成,亲测可用~

相关推荐
尘浮生1 分钟前
Java项目实战II基于Spring Boot的光影视频平台(开发文档+数据库+源码)
java·开发语言·数据库·spring boot·后端·maven·intellij-idea
mosen8683 分钟前
Uniapp去除顶部导航栏-小程序、H5、APP适用
vue.js·微信小程序·小程序·uni-app·uniapp
尚学教辅学习资料9 分钟前
基于SpringBoot的医药管理系统+LW示例参考
java·spring boot·后端·java毕业设计·医药管理
明月看潮生21 分钟前
青少年编程与数学 02-003 Go语言网络编程 15课题、Go语言URL编程
开发语言·网络·青少年编程·golang·编程与数学
雷神乐乐25 分钟前
File.separator与File.separatorChar的区别
java·路径分隔符
小刘|30 分钟前
《Java 实现希尔排序:原理剖析与代码详解》
java·算法·排序算法
南宫理的日知录32 分钟前
99、Python并发编程:多线程的问题、临界资源以及同步机制
开发语言·python·学习·编程学习
qq22951165021 小时前
微信小程序的汽车维修预约管理系统
微信小程序·小程序·汽车
逊嘘1 小时前
【Java语言】抽象类与接口
java·开发语言·jvm