java微信支付v3系列——9.微信支付之商家转账API

news2024/11/19 2:46:47

这个功能就比较复杂了,首先是得有90天的资金流水才能开通,其次开通后还需要在官网进行配置,不能直接调用,并且限制了IP地址。

如下图所示,首先需要进行产品设置,将里面都设置好后才能进行开发,只开通功能是远远不够的。

image-20220805130622451

image-20220805130533437

商家转账设置说明及避坑指南

  • 页面发起:即登录微信支付后,手动输入用户信息,进行转账
  • 验密批量API:开启这个才能调用API,否则会提示没有调用API的权限

单笔付款区间默认1-200元,最低改为0.3,所以你去提现0.01去做测试是没有效果的。

image-20220805164244551

::: danger
这个钱不是从“基本账户”扣除的,而是从运营账户扣除的,如果提示“提现失败,商家运营账户资金不足”,那你可就要看看运营账户里面是不是没钱了。
:::

请求参数及讲解

商家转账支持一次性多次转账,一次最多可以发起3000笔转账,估计是为了防止营销手段,所以该接口设置了QPS为50,注意自己的业务,别踩雷!

那么数据可以理解为,转账说明,和详细说明。也就是由转账信息+转账对象列表组成,请求的json官方实例如下。

{
  "appid": "wxf636efh567hg4356",
  "out_batch_no": "plfk2020042013",
  "batch_name": "2019年1月深圳分部报销单",
  "batch_remark": "2019年1月深圳分部报销单",
  "total_amount": 4000000,
  "total_num": 200,
  "transfer_detail_list": [
    {
      "out_detail_no": "x23zy545Bd5436",
      "transfer_amount": 200000,
      "transfer_remark": "2020年4月报销",
      "openid": "o-MYE42l80oelYMDE34nYD456Xoy",
      "user_name": "757b340b45ebef5467rter35gf464344v3542sdf4t6re4tb4f54ty45t4yyry45"
    }
  ]
}

那么我们可以封装为:

/**
 * @author cv大魔王
 * @version 1.0
 * @description 微信支付商家转账到零钱请求参数
 * @date 2022/8/4
 */
@Data
public class WechatTransferBatchesParam {

    /**
     * 商户系统内部的商家批次单号,对应 out_batch_no,
     */
    private String batchId;

    /**
     * 该笔批量转账的名称,对应 batch_name,示例值:2019年1月深圳分部报销单
     */
    private String title;

    /**
     * 批次备注
     */
    private String remark;

    /**
     * 转账总金额
     */
    private BigDecimal totalMoney;

    /**
     * 转账明细列表
     */
    private List<transferDetail> transferDetailList;

    @Data
    public static class transferDetail {

        /**
         * 商户系统内部的商家批次单号,对应 out_detail_no,
         */
        private String batchId;

        /**
         * 转账金额
         */
        private BigDecimal totalDetailMoney;

        /**
         * 转账备注
         */
        private String detailRemark;

        /**
         * openid是微信用户在公众号(小程序)appid下的唯一用户标识
         */
        private String openid;
    }
}

商家转账请求工具类

这里和发起创建支付订单的请求相似,因此不单独列出,首先是封装转账请求参数,然后是获取请求对象发起请求,完成签名验证发送请求,最后解析数据,如果转账失败则返回提示信息给用户。

@Slf4j
public class WxPayTransferBatchesUtils {

    /**
     * 发起商家转账,支持批量转账
     *
     * @param wxPayConfig 微信配置信息
     * @param param 转账请求参数
     * @param wxPayClient 微信请求客户端()
     * @return 微信支付二维码地址
     */
    public static String transferBatches(WxPayConfig wxPayConfig, WechatTransferBatchesParam param, CloseableHttpClient wxPayClient) {
        // 1.获取请求参数的Map格式
        Map<String, Object> paramsMap = getParams(wxPayConfig, param);

        // 2.获取请求对象,WxApiType.TRANSFER_BATCHES="/v3/transfer/batches"
        HttpPost httpPost = getHttpPost(wxPayConfig,WxApiType.TRANSFER_BATCHES , paramsMap);

        // 3.完成签名并执行请求
        CloseableHttpResponse response = null;
        try {
            response = wxPayClient.execute(httpPost);
        } catch (IOException e) {
            e.printStackTrace();
            throw new DefaultException("商家转账请求失败");
        }

        // 4.解析response对象
        HashMap<String, String> resultMap = resolverResponse(response);
        if (resultMap != null) {
            // batch_id微信批次单号,微信商家转账系统返回的唯一标识
            return resultMap.get("batch_id");
        }
        return null;
    }


    /**
     * 解析响应数据
     * @param response 发送请求成功后,返回的数据
     * @return 微信返回的参数
     */
    private static HashMap<String, String> resolverResponse(CloseableHttpResponse response) {
        try {
            // 1.获取请求码
            int statusCode = response.getStatusLine().getStatusCode();
            // 2.获取返回值 String 格式
            final String bodyAsString = EntityUtils.toString(response.getEntity());

            Gson gson = new Gson();
            if (statusCode == 200) {
                // 3.如果请求成功则解析成Map对象返回
                HashMap<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
                return resultMap;
            } else {
                if (StringUtils.isNoneBlank(bodyAsString)) {
                    log.error("商户转账请求失败,提示信息:{}", bodyAsString);
                    // 4.请求码显示失败,则尝试获取提示信息
                    HashMap<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
                    throw new DefaultException(resultMap.get("message"));
                }
                log.error("商户转账请求失败,未查询到原因,提示信息:{}", response);
                // 其他异常,微信也没有返回数据,这就需要具体排查了
                throw new IOException("request failed");
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new DefaultException(e.getMessage());
        } finally {
            try {
                response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 获取请求对象(Post请求)
     *
     * @param wxPayConfig 微信配置类
     * @param apiType     接口请求地址
     * @param paramsMap   请求参数
     * @return Post请求对象
     */
    private static HttpPost getHttpPost(WxPayConfig wxPayConfig, WxApiType apiType, Map<String, Object> paramsMap) {
        // 1.设置请求地址
        HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(apiType.getType()));

        // 2.设置请求数据
        Gson gson = new Gson();
        String jsonParams = gson.toJson(paramsMap);

        // 3.设置请求信息
        StringEntity entity = new StringEntity(jsonParams, "utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");
        return httpPost;
    }

    /**
     * 封装转账请求参数
     *
     * @param wxPayConfig 微信的配置文件
     * @param param 批量转账请求数据
     * @return 封装后的map对象
     */
    private static Map<String, Object> getParams(WxPayConfig wxPayConfig, WechatTransferBatchesParam param) {
        Map<String, Object> paramsMap = new HashMap<>();
        paramsMap.put("appid", wxPayConfig.getAppid());
        paramsMap.put("out_batch_no", param.getBatchId());
        paramsMap.put("batch_name", param.getTitle());
        paramsMap.put("batch_remark", param.getRemark());
        paramsMap.put("total_amount", param.getTotalMoney().multiply(new BigDecimal("100")).intValue());
        paramsMap.put("total_num", param.getTransferDetailList().size());

        // 存储转账明细,一次最多三千笔
        if (param.getTransferDetailList().size() > 3000) {
            throw new DefaultException("发起批量转账一次最多三千笔");
        }
        List<Map<String, Object>> detailList = new ArrayList<>();
        for (WechatTransferBatchesParam.transferDetail detail : param.getTransferDetailList()) {
            Map<String, Object> detailMap = new HashMap<>();
            detailMap.put("out_detail_no",detail.getBatchId());
            detailMap.put("transfer_amount",detail.getTotalDetailMoney().multiply(new BigDecimal("100")).intValue());
            detailMap.put("transfer_remark",detail.getDetailRemark());
            detailMap.put("openid",detail.getOpenid());
            detailList.add(detailMap);
        }
        paramsMap.put("transfer_detail_list", detailList);
        return paramsMap;
    }
}

使用方法

@Autowired
private WxPayConfig wxPayConfig;

@Autowired
private CloseableHttpClient wxPayClient;
@ApiOperation("转账测试")
@GetMapping("/transfer/batches")
public String transferBatches() {
    WechatTransferBatchesParam param = new WechatTransferBatchesParam();
    String batchId = IdWorker.getIdStr();
    log.info("转账Id:{}", batchId);
    param.setBatchId(batchId);
    param.setTitle("转账测试");
    param.setRemark("转账测试");
    param.setTotalMoney(new BigDecimal("0.02"));


    // 批量转账,可以同时转账给多人,但是不能超过3000,我这里只转账给一个人,只用来测试
    List<WechatTransferBatchesParam.transferDetail> detailList = new ArrayList<>();
    WechatTransferBatchesParam.transferDetail detail = new WechatTransferBatchesParam.transferDetail();
    detail.setBatchId(batchId);
    detail.setTotalDetailMoney(new BigDecimal("0.02"));
    detail.setDetailRemark("转账测试详情");
    detail.setOpenid("DF7E6901802E9F75A6FB45137C6D3685");
    detailList.add(detail);


    param.setTransferDetailList(detailList);
    return WxPayTransferBatchesUtils.transferBatches(wxPayConfig,param,wxPayClient);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/107016.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

feign 调用常见问题避坑指南!

摘要&#xff1a;主要是总结了一下这段时间在使用 feign 的过程中的遇到的一些坑点。一、Get请求自动转化成POST的问题1、client 请求参数没有加上 RequestParam 注解问题代码&#xff1a;GetMapping("/showName") String showName(String name);错误提示&#xff1a…

让 APISpace 告诉你什么场景使用什么API

Q1&#xff1a;某商家打算搞年底促销活动&#xff0c;需要将活动信息通过短信的形式通知给用户&#xff0c;这个场景可以用什么接口&#xff1f; 发送通知类的短信&#xff0c;可以使用 通知短信 API~ 通知短信&#xff0c;支持三大运营商&#xff0c;虚拟运营商短信发送&…

第14章 并发控制与恢复

第14章 并发控制与恢复 考试范围&#xff1a; 14.1-14.3, 14.8-14.11 考试题型&#xff1a; 事务操作 考试内容&#xff1a; 1、锁/共享锁/排它锁的概念 2、多粒度锁 Multiple Granularity 3、两阶段封锁协议 The Two-Phase Locking Protocol 两段锁协议是指同一事务对任何…

2.前端笔记-JS-JS3种书写位置、注释、输入输出

书写位置 行内式嵌入式外部文件引入 1、行内式JS 可以将单行或少量的JS代码写在HTML标签的事件属性中&#xff08;以on开头的属性&#xff09;&#xff0c;如onclick单双引号使用&#xff1a;HTML中推荐双引号&#xff0c;JS中推荐单引号&#xff0c;如 <input type&quo…

vue实现将自己网站(h5链接)分享到微信中形成小卡片(超详细)

大家好&#xff0c;我是雄雄。 前言 我们在分享公众号信息到微信或者群中的时候&#xff0c;会出现一个小卡片&#xff0c;如下所示&#xff1a; 但是呢&#xff0c;这种小卡片只能走微信的接口来实现&#xff0c;比如我们从公众号、小程序中分享的内容可以是这样的。如果我们…

0基础转行,四个月,改变了我的人生!

转行对于很多人而言都是一个新的开始&#xff0c;但有的人是决定了立马去做&#xff0c;而有的人则是犹犹豫豫&#xff0c;我考虑考虑吧、还没有决定好、过段时间再说吧…… 就这样&#xff0c;相似情况的两个人&#xff0c;最后有了不同的结果。 很多人总是以我很忙、学历不高…

自学100天,零基础转行软件测试,我要以更好的姿态奔赴下一场山海!

三年大专一场空 专业是电子商务&#xff0c;18年毕业&#xff0c;当时在报考时时觉得电子商务挺高大上的&#xff0c;觉得电商肯定会有前途&#xff0c;以后毕业肯定好找工作&#xff0c;跟大多数人一样&#xff0c;我开始幻想我以后毕业以后的纸醉金迷的生活&#xff0c;我以…

模数转换器ADC

模数转换器ADC F28335内部的ADC模块是一个12位分辨率的、具有流水线结构的模数转换器,其结构框图如图11-1所示。从图11-1可以看到,F28335的ADC模块一共具有16个采样通道,分成了两组,一组为ADCINAO~ ADCINA7,另一组为ADCINBO~ADCINB7。A组的通道使用采样保持器A,也就是图…

【SCI论文解读复现NO.1】基于Transformer-YOLOv5的侧扫声纳图像水下海洋目标实时检测

前言 此前出了目标改进算法专栏&#xff0c;但是对于应用于什么场景&#xff0c;需要什么改进方法对应与自己的应用场景有效果&#xff0c;并且多少改进点能发什么水平的文章&#xff0c;为解决大家的困惑&#xff0c;此系列文章旨在给大家解读最新目标检测算法论文&#xff0…

【小学信息技术教资面试】《制作通讯录》教案

1.题目&#xff1a;制作通讯录 2.内容&#xff1a; 3.基本要求&#xff1a; &#xff08;1&#xff09;使用任务驱动法进行教学。 &#xff08;2&#xff09;掌握表格的插入和信息的填写。 &#xff08;3&#xff09;试讲时间是10分钟。 《制作通讯录》教案 一、教学目标&am…

我国核桃种植深加工行业供给较为充足 未来大健康消费时代将带来广阔市场

根据观研报告网发布的《2022年中国核桃种植深加工行业分析报告-行业竞争策略与发展动向研究》显示&#xff0c;核桃加工分为初加工与深加工。其中核桃深加工包括核桃油的压榨、核桃蛋白粉的制备、以核桃仁为原料生产休闲食品、以核桃青皮、壳等为原料生产加工日化产品等&#x…

Java agent 使用

一、前言 于一个即将上线的应用来说&#xff0c;系统监控是必不可少的&#xff0c;为什么需要监控呢&#xff1f;应用是跑在服务器上的&#xff0c;应用在运行过程中会发生各自意想不到的问题&#xff0c;像大家熟知的OOM&#xff0c;mysql故障&#xff0c;服务器宕机&#xff…

SpringBoot+Vue实现前后端分离的教务评教系统

文末获取源码 开发语言&#xff1a;Java 使用框架&#xff1a;spring boot 前端技术&#xff1a;JavaScript、Vue.js 、css3 开发工具&#xff1a;IDEA/MyEclipse/Eclipse、Visual Studio Code 数据库&#xff1a;MySQL 5.7/8.0 数据库管理工具&#xff1a;phpstudy/Navicat JD…

将spark的数据保存到MySQL

文章目录前言环境的准备是必要的下载解压放置文件代码书写注意事项结束语前言 我们用spark对数据进行分析和提取数据后要对得到的数据进行保存接下来的内容是将数据保存到MySQL数据库中 环境的准备是必要的 下载 &#xff08;本小博主已经为看官大人准备好了下载地址点击下载…

【JVM实战系列】「监控调优体系」实战开发arthas-spring-boot-starter监控你的微服务是否健康

前提介绍 相信如果经历了我的上一篇Arthas的文章[【JVM实战系列】「监控调优体系」针对于Alibaba-Arthas的安装入门及基础使用开发实战指南]之后&#xff0c;相信你对Arthas的功能和使用应该有了一定的理解了。那么我们就要进行下一步的探索功能。 Arthas对于SpringBoot2的支…

无效回表谁的锅?存储引擎:这事儿不赖我

明确场景 要回答这个问题&#xff0c;我们一般分几步来走&#xff1a; 1.确认问题&#xff0c;对齐Sql语句&#xff1b; 2.解答问题本身&#xff0c;也就是时间复杂度分析&#xff1b; 3.针对本身提出这个场景&#xff0c;可能出现的性能瓶颈进行分析&#xff1b; 4.针对瓶…

vue+element模仿腾讯视频电影网站

一.布局设计 官方图例&#xff1a; demo效果&#xff1a; 顶部1&#xff1b;左侧菜单栏2&#xff1b;右侧内容展示区3&#xff1b; 关键点&#xff1a; 顶部固定&#xff0c;不随页面滚动而滚动&#xff1b;左侧可局部滚动显示更多菜单&#xff1b;右侧局部滚动&#xff…

拓扑梅尔智慧办公平台(Topomel Box) 3.0发布

今天&#xff0c;2022年12月21日&#xff0c;我很高兴地宣布&#xff1a;拓扑梅尔智慧办公平台(Topomel Box)的3.0版本正式发布。 下面&#xff0c;请允许我简单地介绍下新版本的一些基本情况。 新特性 1) 统一的文件管理 将所有你关心的文件都统一存放在同一个地方&#xff…

Linux 4.7内核syncookie的性能

虽然现在的内核都已经是4.11版本了&#xff0c;但本文依旧基于较老的内核版本旧事重提&#xff0c;就4.7版本的一个针对syncookie的一个优化书写一段吹捧与嘲讽。 自从4.4版本的Lockless TCP listener以来&#xff0c;针对TCP在大并发连接处理这块一直都没有更大的突破&#x…

RNA-seq 详细教程:注释(15)

学习内容 了解可用的基因组注释数据库和存储信息的不同类型比较和对比可用于基因组注释数据库的工具应用各种 R 包检索基因组注释基因组注释 对二代测序结果的分析需要将基因、转录本、蛋白质等与功能或调控信息相关联。为了对基因列表进行功能分析&#xff0c;我们通常需要获得…