2025版微信商家转账API V3 JAVA完整源码零障碍对接秘籍

2025版微信商家转账API V3 JAVA完整源码零障碍对接指南

前言

  1. 本教程操作简便,提供完整源码。
  2. 本文主要阐述java后端的对接流程,包含功能开启以及前端调用相关内容。基础版无需提交资料审核,但额度仅有200,具体可参考官方文档产品介绍_商家转账|微信支付商户文档中心
  3. 自去年年末起,原微信商家转账到零钱功能停止开放,替换为商家转账功能。与以往的商家转账到零钱相比,新流程多了用户确认步骤,通过API发起转账后,还需用户在商户小程序中点击确认(前端指引->JSAPI调起用户确认收款_商家转账|微信支付商户文档中心)。

一、引入微信支付官方SDK

无需自行开发,直接引入微信支付官方提供的SDK,相关依赖如下:

<!-- 微信支付sdk  -->
<dependency>
    <groupId>com.github.wechatpay-apiv3</groupId>
    <artifactId>wechatpay-java</artifactId>
    <version>0.2.15</version>
</dependency>

二、封装实体类

后端API包含发起转账、查询转账、撤销转账三个方法,且状态变更有异步回调通知,因此封装如下实体类(请勿修改@serializedName注解)。

1. 请求实体类(仅发起转账需要封装,其他接口通过路径传参)

/**
 * 发起商家转账参数
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TransferCreateRequest {

    @SerializedName("appid")
    private String appid;

    @SerializedName("out_bill_no")
    private String outBillNo;

    @SerializedName("transfer_scene_id")
    private String transferSceneId;

    @SerializedName("openid")
    private String openid;

    @SerializedName("user_name")
    private String userName;

    @SerializedName("transfer_remark")
    private String transferRemark;

    @SerializedName("transfer_amount")
    private Integer transferAmount;

    @SerializedName("notify_url")
    private String notifyUrl;

    /**
     * 【用户收款感知】
     * 用户收款时感知到的收款原因将根据转账场景自动展示默认内容。
     * 如有其他展示需求,可在本字段传入。
     * 各场景展示的默认内容和支持传入的内容,可查看产品文档了解。
     */
    @SerializedName("user_recv_perception")
    private String userRecvPerception;

    /**
     * 【转账场景报备信息】 各转账场景下需报备的内容,商户需要按照所属转账场景规则传参,详见转账场景报备信息字段说明。
     */
    @SerializedName("transfer_scene_report_infos")
    private List<TransferSceneReportInfo> transferSceneReportInfos;

    @Override
    public String toString() {
        return GsonUtil.getGson().toJson(this);
    }

}


/**
 * 转账场景报备信息
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TransferSceneReportInfo {

    /**
     * 【信息类型】 不能超过15个字符,商户所属转账场景下的信息类型,此字段内容为固定值,需严格按照转账场景报备信息字段说明传参。
     */
    @SerializedName("info_type")
    private String infoType;

    /**
     * 【信息内容】 不能超过32个字符,商户所属转账场景下的信息内容,商户可按实际业务场景自定义传参,需严格按照转账场景报备信息字段说明传参。
     */
    @SerializedName("info_content")
    private String infoContent;

    @Override
    public String toString() {
        return GsonUtil.getGson().toJson(this);
    }
}

2. 响应实体类

@Data
public class TransferCreateResponse {

    @SerializedName("transfer_bill_no")
    private String transferBillNo;

    @SerializedName("out_bill_no")
    private String outBillNo;

    @SerializedName("create_time")
    private Date createTime;

    /**
     * 【单据状态】 商家转账订单状态
     * 可选取值
     * ACCEPTED: 转账已受理
     * PROCESSING: 转账锁定资金中。如果一直停留在该状态,建议检查账户余额是否足够,如余额不足,可充值后再原单重试。
     * WAIT_USER_CONFIRM: 待收款用户确认,可拉起微信收款确认页面进行收款确认
     * TRANSFERING: 转账中,可拉起微信收款确认页面再次重试确认收款
     * SUCCESS: 转账成功
     * FAIL: 转账失败
     * CANCELING: 商户撤销请求受理成功,该笔转账正在撤销中
     * CANCELLED: 转账撤销完成
     */
    @SerializedName("state")
    private String state;

    /**
     * 【失败原因】 订单已失败或者已退资金时,会返回订单失败原因
     * <a href="https://pay.weixin.qq.com/doc/v3/merchant/4013774966">...</a>
     */
    @SerializedName("fail_reason")
    private String failReason;

    /**
     * 【跳转领取页面的package信息】 跳转微信支付收款页的package信息,APP调起用户确认收款或者JSAPI调起用户确认收款 时需要使用的参数。
     * 单据创建后,用户24小时内不领取将过期关闭,建议拉起用户确认收款页面前,先查单据状态:如单据状态为待收款用户确认,可用之前的package信息拉起;单据到终态时需更换单号重新发起转账。
     */
    @SerializedName("package_info")
    private String packageInfo;

}


@Data
public class TransferQueryResponse {

    @SerializedName("mch_id")
    private String mchid;


    @SerializedName("transfer_bill_no")
    private String transferBillNo;

    @SerializedName("out_bill_no")
    private String outBillNo;

    @SerializedName("appid")
    private String appid;

    /**
     * 【单据状态】 商家转账订单状态
     * 可选取值
     * ACCEPTED: 转账已受理
     * PROCESSING: 转账锁定资金中。如果一直停留在该状态,建议检查账户余额是否足够,如余额不足,可充值后再原单重试。
     * WAIT_USER_CONFIRM: 待收款用户确认,可拉起微信收款确认页面进行收款确认
     * TRANSFERING: 转账中,可拉起微信收款确认页面再次重试确认收款
     * SUCCESS: 转账成功
     * FAIL: 转账失败
     * CANCELING: 商户撤销请求受理成功,该笔转账正在撤销中
     * CANCELLED: 转账撤销完成
     */
    @SerializedName("state")
    private String state;

    @SerializedName("transfer_amount")
    private Integer transferAmount;

    @SerializedName("transfer_remark")
    private String transferRemark;

    @SerializedName("fail_reason")
    private String failReason;

    @SerializedName("openid")
    private String openid;

    @SerializedName("user_name")
    private String userName;

    /**
     * 【单据创建时间】遵循rfc3339标准格式,格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE,
     * yyyy-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss.表示时分秒,
     * TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。
     * 例如:2015-05-20T13:29:35+08:00表示北京时间2015年05月20日13点29分35秒。
     */
    @SerializedName("create_time")
    private Date createTime;

    /**
     * 【最后一次状态变更时间】遵循rfc3339标准格式,格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE,
     * yyyy-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss.表示时分秒,
     * TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。
     * 例如:2015-05-20T13:29:35+08:00表示北京时间2015年05月20日13点29分35秒。
     */
    @SerializedName("update_time")
    private Date updateTime;
}


@Data
public class TransferCancelResponse {

    /**
     * 商户系统内部的商家单号,要求此参数只能由数字、大小写字母组成,在商户系统内部唯一
     */
    @SerializedName("out_bill_no")
    private String outBillNo;

    /**
     *  商家转账订单的主键,唯一定义此资源的标识
     */
    @SerializedName("transfer_bill_no")
    private String transferBillNo;

    /**
     * 【单据状态】 CANCELING: 撤销中;CANCELLED:已撤销
     */
    @SerializedName("state")
    private String state;

    /**
     * 【最后一次单据状态变更时间】 按照使用rfc3339所定义的格式,格式为yyyy-MM-DDThh:mm:ss+TIMEZONE
     */
    @SerializedName("update_time")
    private Date updateTime;

}

3. 异步回调参数

@Data
public class TransferNotification {

    @SerializedName("transfer_bill_no")
    private String transferBillNo;

    @SerializedName("out_bill_no")
    private String outBillNo;

    /**
     * 【单据状态】 商家转账订单状态
     * 可选取值
     * ACCEPTED: 转账已受理
     * PROCESSING: 转账锁定资金中。如果一直停留在该状态,建议检查账户余额是否足够,如余额不足,可充值后再原单重试。
     * WAIT_USER_CONFIRM: 待收款用户确认,可拉起微信收款确认页面进行收款确认
     * TRANSFERING: 转账中,可拉起微信收款确认页面再次重试确认收款
     * SUCCESS: 转账成功
     * FAIL: 转账失败
     * CANCELING: 商户撤销请求受理成功,该笔转账正在撤销中
     * CANCELLED: 转账撤销完成
     */
    @SerializedName("state")
    private String state;

    @SerializedName("mch_id")
    private String mchId;

    @SerializedName("transfer_amount")
    private Integer transferAmount;

    @SerializedName("openid")
    private String openid;

    /**
     * 【失败原因】 订单已失败或者已退资金时,会返回订单失败原因
     * <a href="https://pay.weixin.qq.com/doc/v3/merchant/4013774966">...</a>
     */
    @SerializedName("fail_reason")
    private String failReason;


    /**
     * 【单据创建时间】遵循rfc3339标准格式,格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE,
     * yyyy-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss.表示时分秒,
     * TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。
     * 例如:2015-05-20T13:29:35+08:00表示北京时间2015年05月20日13点29分35秒。
     */
    @SerializedName("create_time")
    private Date createTime;

    /**
     * 【最后一次状态变更时间】遵循rfc3339标准格式,格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE,
     * yyyy-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss.表示时分秒,
     * TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。
     * 例如:2015-05-20T13:29:35+08:00表示北京时间2015年05月20日13点29分35秒。
     */
    @SerializedName("update_time")
    private Date updateTime;
}

三、封装service

对于只兼容商家转账且单商户配置的情况,可在service上添加@component注解,在构造方法中使用@autowired实现单例模式(官方推荐),示例代码如下:

public class TransferNewService {

    private final HttpClient httpClient;

    private final PrivacyEncryptor encryptor;

    private final PrivacyDecryptor decryptor;

    /**
     * 实际注入实体类为 RSAAutoCertificateConfig
     * @param config 不要引用错了 包路径 com.wechat.pay.java.core.Config; 
     */
    public TransferNewService(Config config) {
        this.httpClient =
                new DefaultHttpClientBuilder()
                        .credential(requireNonNull(config.createCredential()))
                        .validator(requireNonNull(config.createValidator()))
                        .build();
        this.encryptor = config.createEncryptor();
        this.decryptor = config.createDecryptor();
    }
    /**
     * 创建商家转账订单
     * @param request 请求参数
     * @return 响应参数
     */
    public TransferCreateResponse createTransferOrder(TransferCreateRequest request) {
        String requestPath = "https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills";
        Objects.requireNonNull(this.encryptor);
        request.setUserName(this.encryptor.encrypt(request.getUserName()));
        return executeHttpRequest(requestPath,HttpMethod.POST,request.toString(),TransferCreateResponse.class);
    }

    /**
     * 撤销商家转账订单
     * 商户通过转账接口发起付款后,在用户确认收款之前可以通过该接口撤销付款。
     * 该接口返回成功仅表示撤销请求已受理,
     * 系统会异步处理退款等操作,
     * 以最终查询单据返回状态为准。
     * @param outBillNo 商户转账单号
     * @return 响应参数
     */
    public TransferCancelResponse cancelTransferOrder(String outBillNo){
        String requestPath =
                "https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills/out-bill-no/{out_bill_no}/cancel";
        requestPath =
                requestPath.replace("{" + "out_bill_no" + "}", outBillNo);
        return executeHttpRequest(requestPath,HttpMethod.POST,null,TransferCancelResponse.class);
    }

    public TransferQueryResponse queryTransferByOutBillNo(String outBillNo){
        String requestPath =
                "https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills/out-bill-no/{out_bill_no}";
        requestPath =
                requestPath.replace("{" + "out_bill_no" + "}", outBillNo);
        TransferQueryResponse response = executeHttpRequest(requestPath, HttpMethod.GET, null, TransferQueryResponse.class);
        response.setUserName(decryptor.decrypt(response.getUserName()));
        return response;
    }

    public TransferQueryResponse queryTransferByTransferBillNo(String transferBillNo){
        String requestPath =
                "https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills/transfer-bill-no/{transfer_bill_no}";
        requestPath =
                requestPath.replace("{" + "transfer_bill_no" + "}", transferBillNo);
        TransferQueryResponse response = executeHttpRequest(requestPath, HttpMethod.GET, null, TransferQueryResponse.class);
        response.setUserName(decryptor.decrypt(response.getUserName()));
        return response;
    }

    public <T> T executeHttpRequest(String requestPath,HttpMethod method,String body,Class<T> responseClass){
        HttpHeaders headers = new HttpHeaders();
        headers.addHeader(Constant.ACCEPT, MediaType.APPLICATION_JSON.getValue());
        headers.addHeader(Constant.CONTENT_TYPE, MediaType.APPLICATION_JSON.getValue());
        HttpRequest httpRequest =
                new HttpRequest.Builder()
                        .httpMethod(method)
                        .url(requestPath)
                        .headers(headers)
                        .body(StringUtils.hasText(body)?new JsonRequestBody.Builder().body(body).build():null)
                        .build();
        HttpResponse<T> httpResponse =
                httpClient.execute(httpRequest, responseClass);
        return httpResponse.getServiceResponse();
    }
}

四、配置注入

单商户模式只需完成Config配置类注册即可调用。非常关键****若为24年11月以后注册的商户,需检查商户后台配置,有兄弟在评论区提到平台证书模式可能新商户不支持。进入账户中心->API安全,根据自己的商户号证书类型,

相关文章

暂无评论

暂无评论...