🆕 #3862 【微信支付】 增加V3「微工卡」的批量转账接口实现
This commit is contained in:
@@ -0,0 +1,239 @@
|
||||
package com.github.binarywang.wxpay.bean.marketing.payroll;
|
||||
|
||||
import com.github.binarywang.wxpay.v3.SpecEncrypt;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 微工卡批量转账API请求参数
|
||||
* 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/Offline/apis/chapter4_1_8.shtml
|
||||
*
|
||||
* 适用对象:服务商
|
||||
* 请求URL:https://api.mch.weixin.qq.com/v3/payroll-card/transfer-batches
|
||||
* 请求方式:POST
|
||||
* </pre>
|
||||
*
|
||||
* @author binarywang
|
||||
* created on 2025/01/19
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PayrollTransferBatchesRequest implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:应用ID
|
||||
* 变量名:appid
|
||||
* 是否必填:二选一
|
||||
* 类型:string[1, 32]
|
||||
* 描述:
|
||||
* 服务商在微信申请公众号/小程序或移动应用成功后分配的账号ID
|
||||
* 示例值:wxa1111111
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName(value = "appid")
|
||||
private String appid;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:子商户应用ID
|
||||
* 变量名:sub_appid
|
||||
* 是否必填:二选一
|
||||
* 类型:string[1, 32]
|
||||
* 描述:
|
||||
* 特约商户在微信申请公众号/小程序或移动应用成功后分配的账号ID
|
||||
* 示例值:wxa1111111
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName(value = "sub_appid")
|
||||
private String subAppid;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:子商户号
|
||||
* 变量名:sub_mchid
|
||||
* 是否必填:是
|
||||
* 类型:string[1, 32]
|
||||
* 描述:
|
||||
* 微信服务商下特约商户的商户号,由微信支付生成并下发
|
||||
* 示例值:1111111
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName(value = "sub_mchid")
|
||||
private String subMchid;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:商家批次单号
|
||||
* 变量名:out_batch_no
|
||||
* 是否必填:是
|
||||
* 类型:string[1, 32]
|
||||
* 描述:
|
||||
* 商户系统内部的商家批次单号,要求此参数只能由数字、大小写字母组成,在商户系统内部唯一
|
||||
* 示例值:plfk2020042013
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName(value = "out_batch_no")
|
||||
private String outBatchNo;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:批次名称
|
||||
* 变量名:batch_name
|
||||
* 是否必填:是
|
||||
* 类型:string[1, 32]
|
||||
* 描述:
|
||||
* 该笔批量转账的名称
|
||||
* 示例值:2019年1月深圳分部报销单
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName(value = "batch_name")
|
||||
private String batchName;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:批次备注
|
||||
* 变量名:batch_remark
|
||||
* 是否必填:是
|
||||
* 类型:string[1, 32]
|
||||
* 描述:
|
||||
* 转账说明,UTF8编码,最多允许32个字符
|
||||
* 示例值:2019年1月深圳分部报销单
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName(value = "batch_remark")
|
||||
private String batchRemark;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:转账总金额
|
||||
* 变量名:total_amount
|
||||
* 是否必填:是
|
||||
* 类型:int64
|
||||
* 描述:
|
||||
* 转账金额单位为"分"。转账总金额必须与批次内所有明细转账金额之和保持一致,否则无法发起转账操作
|
||||
* 示例值:4000000
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName(value = "total_amount")
|
||||
private Long totalAmount;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:转账总笔数
|
||||
* 变量名:total_num
|
||||
* 是否必填:是
|
||||
* 类型:int
|
||||
* 描述:
|
||||
* 一个转账批次单最多发起一千笔转账。转账总笔数必须与批次内所有明细之和保持一致,否则无法发起转账操作
|
||||
* 示例值:200
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName(value = "total_num")
|
||||
private Integer totalNum;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:转账明细列表
|
||||
* 变量名:transfer_detail_list
|
||||
* 是否必填:是
|
||||
* 类型:array
|
||||
* 描述:
|
||||
* 发起批量转账的明细列表,最多一千笔
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName(value = "transfer_detail_list")
|
||||
private List<TransferDetail> transferDetailList;
|
||||
|
||||
/**
|
||||
* 转账明细
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class TransferDetail implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:商家明细单号
|
||||
* 变量名:out_detail_no
|
||||
* 是否必填:是
|
||||
* 类型:string[1, 32]
|
||||
* 描述:
|
||||
* 商户系统内部区分转账批次单下不同转账明细单的唯一标识
|
||||
* 示例值:x23zy545Bd5436
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName(value = "out_detail_no")
|
||||
private String outDetailNo;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:转账金额
|
||||
* 变量名:transfer_amount
|
||||
* 是否必填:是
|
||||
* 类型:int64
|
||||
* 描述:
|
||||
* 转账金额单位为"分"
|
||||
* 示例值:200000
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName(value = "transfer_amount")
|
||||
private Long transferAmount;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:转账备注
|
||||
* 变量名:transfer_remark
|
||||
* 是否必填:是
|
||||
* 类型:string[1, 32]
|
||||
* 描述:
|
||||
* 单条转账备注(微信用户会收到该备注),UTF8编码,最多允许32个字符
|
||||
* 示例值:2020年4月报销
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName(value = "transfer_remark")
|
||||
private String transferRemark;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:用户标识
|
||||
* 变量名:openid
|
||||
* 是否必填:是
|
||||
* 类型:string[1, 64]
|
||||
* 描述:
|
||||
* 用户在商户对应appid下的唯一标识
|
||||
* 示例值:o-MYE42l80oelYMDE34nYD456Xoy
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName(value = "openid")
|
||||
private String openid;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:收款用户姓名
|
||||
* 变量名:user_name
|
||||
* 是否必填:否
|
||||
* 类型:string[1, 1024]
|
||||
* 描述:
|
||||
* 收款用户真实姓名。该字段需进行加密处理,加密方法详见敏感信息加密说明
|
||||
* 示例值:757b340b45ebef5467rter35gf464344v3542sdf4t6re4tb4f54ty45t4yyry45
|
||||
* </pre>
|
||||
*/
|
||||
@SpecEncrypt
|
||||
@SerializedName(value = "user_name")
|
||||
private String userName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
package com.github.binarywang.wxpay.bean.marketing.payroll;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 微工卡批量转账API返回结果
|
||||
* 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/Offline/apis/chapter4_1_8.shtml
|
||||
*
|
||||
* 适用对象:服务商
|
||||
* 请求URL:https://api.mch.weixin.qq.com/v3/payroll-card/transfer-batches
|
||||
* 请求方式:POST
|
||||
* </pre>
|
||||
*
|
||||
* @author binarywang
|
||||
* created on 2025/01/19
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class PayrollTransferBatchesResult implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:商家批次单号
|
||||
* 变量名:out_batch_no
|
||||
* 是否必填:是
|
||||
* 类型:string[1, 32]
|
||||
* 描述:
|
||||
* 商户系统内部的商家批次单号
|
||||
* 示例值:plfk2020042013
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName(value = "out_batch_no")
|
||||
private String outBatchNo;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:微信批次单号
|
||||
* 变量名:batch_id
|
||||
* 是否必填:是
|
||||
* 类型:string[1, 64]
|
||||
* 描述:
|
||||
* 微信批次单号,微信商家转账系统返回的唯一标识
|
||||
* 示例值:1030000071100999991182020050700019480001
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName(value = "batch_id")
|
||||
private String batchId;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:批次状态
|
||||
* 变量名:batch_status
|
||||
* 是否必填:是
|
||||
* 类型:string[1, 32]
|
||||
* 描述:
|
||||
* ACCEPTED:已受理,批次已受理成功,若发起批量转账的30分钟后,转账批次单仍处于该状态,可能原因是商户账户余额不足等。商户可查询账户资金流水,若该笔转账批次单的扣款已经发生,则表示批次已经进入转账中,请再次查单确认
|
||||
* PROCESSING:转账中,已开始处理批次内的转账明细单
|
||||
* FINISHED:已完成,批次内的所有转账明细单都已处理完成
|
||||
* CLOSED:已关闭,可查询具体的批次关闭原因确认
|
||||
* 示例值:ACCEPTED
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName(value = "batch_status")
|
||||
private String batchStatus;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:批次类型
|
||||
* 变量名:batch_type
|
||||
* 是否必填:是
|
||||
* 类型:string[1, 32]
|
||||
* 描述:
|
||||
* 批次类型
|
||||
* API:API方式发起
|
||||
* WEB:WEB方式发起
|
||||
* 示例值:API
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName(value = "batch_type")
|
||||
private String batchType;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:批次名称
|
||||
* 变量名:batch_name
|
||||
* 是否必填:是
|
||||
* 类型:string[1, 32]
|
||||
* 描述:
|
||||
* 该笔批量转账的名称
|
||||
* 示例值:2019年1月深圳分部报销单
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName(value = "batch_name")
|
||||
private String batchName;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:批次备注
|
||||
* 变量名:batch_remark
|
||||
* 是否必填:是
|
||||
* 类型:string[1, 32]
|
||||
* 描述:
|
||||
* 转账说明,UTF8编码,最多允许32个字符
|
||||
* 示例值:2019年1月深圳分部报销单
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName(value = "batch_remark")
|
||||
private String batchRemark;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:批次关闭原因
|
||||
* 变量名:close_reason
|
||||
* 是否必填:否
|
||||
* 类型:string[1, 32]
|
||||
* 描述:
|
||||
* 如果批次单状态为"CLOSED"(已关闭),则有关闭原因
|
||||
* 示例值:OVERDUE_CLOSE
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName(value = "close_reason")
|
||||
private String closeReason;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:转账总金额
|
||||
* 变量名:total_amount
|
||||
* 是否必填:是
|
||||
* 类型:int64
|
||||
* 描述:
|
||||
* 转账金额单位为"分"
|
||||
* 示例值:4000000
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName(value = "total_amount")
|
||||
private Long totalAmount;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:转账总笔数
|
||||
* 变量名:total_num
|
||||
* 是否必填:是
|
||||
* 类型:int
|
||||
* 描述:
|
||||
* 一个转账批次单最多发起一千笔转账
|
||||
* 示例值:200
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName(value = "total_num")
|
||||
private Integer totalNum;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:批次创建时间
|
||||
* 变量名:create_time
|
||||
* 是否必填:是
|
||||
* 类型:string[1, 32]
|
||||
* 描述:
|
||||
* 批次受理成功时返回,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss:sss+TIMEZONE
|
||||
* 示例值:2015-05-20T13:29:35.120+08:00
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName(value = "create_time")
|
||||
private String createTime;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:批次更新时间
|
||||
* 变量名:update_time
|
||||
* 是否必填:是
|
||||
* 类型:string[1, 32]
|
||||
* 描述:
|
||||
* 批次最近一次状态变更的时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss:sss+TIMEZONE
|
||||
* 示例值:2015-05-20T13:29:35.120+08:00
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName(value = "update_time")
|
||||
private String updateTime;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:转账成功金额
|
||||
* 变量名:success_amount
|
||||
* 是否必填:否
|
||||
* 类型:int64
|
||||
* 描述:
|
||||
* 转账成功的金额,单位为"分"
|
||||
* 示例值:3900000
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName(value = "success_amount")
|
||||
private Long successAmount;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:转账成功笔数
|
||||
* 变量名:success_num
|
||||
* 是否必填:否
|
||||
* 类型:int
|
||||
* 描述:
|
||||
* 转账成功的笔数
|
||||
* 示例值:199
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName(value = "success_num")
|
||||
private Integer successNum;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:转账失败金额
|
||||
* 变量名:fail_amount
|
||||
* 是否必填:否
|
||||
* 类型:int64
|
||||
* 描述:
|
||||
* 转账失败的金额,单位为"分"
|
||||
* 示例值:100000
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName(value = "fail_amount")
|
||||
private Long failAmount;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:转账失败笔数
|
||||
* 变量名:fail_num
|
||||
* 是否必填:否
|
||||
* 类型:int
|
||||
* 描述:
|
||||
* 转账失败的笔数
|
||||
* 示例值:1
|
||||
* </pre>
|
||||
*/
|
||||
@SerializedName(value = "fail_num")
|
||||
private Integer failNum;
|
||||
}
|
||||
@@ -101,4 +101,16 @@ public interface PayrollService {
|
||||
*/
|
||||
WxPayApplyBillV3Result merchantFundWithdrawBillType(String billType, String billDate, String tarType) throws WxPayException;
|
||||
|
||||
/**
|
||||
* 微工卡批量转账API
|
||||
* 适用对象:服务商
|
||||
* 请求URL:https://api.mch.weixin.qq.com/v3/payroll-card/transfer-batches
|
||||
* 请求方式:POST
|
||||
*
|
||||
* @param request 请求参数
|
||||
* @return 返回数据
|
||||
* @throws WxPayException the wx pay exception
|
||||
*/
|
||||
PayrollTransferBatchesResult payrollCardTransferBatches(PayrollTransferBatchesRequest request) throws WxPayException;
|
||||
|
||||
}
|
||||
|
||||
@@ -193,4 +193,27 @@ public class PayrollServiceImpl implements PayrollService {
|
||||
return GSON.fromJson(response, WxPayApplyBillV3Result.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 微工卡批量转账API
|
||||
* 适用对象:服务商
|
||||
* 请求URL:https://api.mch.weixin.qq.com/v3/payroll-card/transfer-batches
|
||||
* 请求方式:POST
|
||||
*
|
||||
* @param request 请求参数
|
||||
* @return 返回数据
|
||||
* @throws WxPayException the wx pay exception
|
||||
*/
|
||||
@Override
|
||||
public PayrollTransferBatchesResult payrollCardTransferBatches(PayrollTransferBatchesRequest request) throws WxPayException {
|
||||
String url = String.format("%s/v3/payroll-card/transfer-batches", payService.getPayBaseUrl());
|
||||
// 对敏感信息进行加密
|
||||
if (request.getTransferDetailList() != null && !request.getTransferDetailList().isEmpty()) {
|
||||
for (PayrollTransferBatchesRequest.TransferDetail detail : request.getTransferDetailList()) {
|
||||
RsaCryptoUtil.encryptFields(detail, payService.getConfig().getVerifier().getValidCertificate());
|
||||
}
|
||||
}
|
||||
String response = payService.postV3WithWechatpaySerial(url, GSON.toJson(request));
|
||||
return GSON.fromJson(response, PayrollTransferBatchesResult.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.testng.annotations.Guice;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* 微工卡(服务商)
|
||||
*
|
||||
@@ -125,4 +127,29 @@ public class PayrollServiceImplTest {
|
||||
log.info(result.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void payrollCardTransferBatches() throws WxPayException {
|
||||
PayrollTransferBatchesRequest request = PayrollTransferBatchesRequest.builder()
|
||||
.appid("wxa1111111")
|
||||
.subMchid("1111111")
|
||||
.subAppid("wxa1111111")
|
||||
.outBatchNo("plfk2020042013" + System.currentTimeMillis())
|
||||
.batchName("2019年1月深圳分部报销单")
|
||||
.batchRemark("2019年1月深圳分部报销单")
|
||||
.totalAmount(200000L)
|
||||
.totalNum(1)
|
||||
.transferDetailList(Collections.singletonList(
|
||||
PayrollTransferBatchesRequest.TransferDetail.builder()
|
||||
.outDetailNo("x23zy545Bd5436" + System.currentTimeMillis())
|
||||
.transferAmount(200000L)
|
||||
.transferRemark("2020年4月报销")
|
||||
.openid("o-MYE42l80oelYMDE34nYD456Xoy")
|
||||
.userName("张三")
|
||||
.build()
|
||||
))
|
||||
.build();
|
||||
PayrollTransferBatchesResult result = wxPayService.getPayrollService().payrollCardTransferBatches(request);
|
||||
log.info(result.toString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user