1
0
mirror of synced 2025-12-22 00:48:00 +08:00

🆕 #3507 【微信支付】实现运营工具-商家转账API相关接口

This commit is contained in:
Copilot
2025-11-15 17:04:27 +08:00
committed by GitHub
parent 57e12dee29
commit 0854e4d971
11 changed files with 720 additions and 0 deletions

View File

@@ -0,0 +1,44 @@
package com.github.binarywang.wxpay.bean.transfer;
import com.google.gson.annotations.SerializedName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 运营工具-商家转账查询请求参数
*
* @author WxJava Team
* @see <a href="https://pay.weixin.qq.com/doc/v3/merchant/4012711988">运营工具-商家转账API</a>
*/
@Data
@Builder(builderMethodName = "newBuilder")
@NoArgsConstructor
@AllArgsConstructor
public class BusinessOperationTransferQueryRequest implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 商户系统内部的商家单号
* 与transfer_bill_no二选一
*/
@SerializedName("out_bill_no")
private String outBillNo;
/**
* 微信转账单号
* 与out_bill_no二选一
*/
@SerializedName("transfer_bill_no")
private String transferBillNo;
/**
* 直连商户的appid
* 可选
*/
@SerializedName("appid")
private String appid;
}

View File

@@ -0,0 +1,101 @@
package com.github.binarywang.wxpay.bean.transfer;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 运营工具-商家转账查询结果
*
* @author WxJava Team
* @see <a href="https://pay.weixin.qq.com/doc/v3/merchant/4012711988">运营工具-商家转账API</a>
*/
@Data
@NoArgsConstructor
public class BusinessOperationTransferQueryResult implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 直连商户的appid
*/
@SerializedName("appid")
private String appid;
/**
* 商户系统内部的商家单号
*/
@SerializedName("out_bill_no")
private String outBillNo;
/**
* 微信转账单号
*/
@SerializedName("transfer_bill_no")
private String transferBillNo;
/**
* 运营工具转账场景ID
*/
@SerializedName("operation_scene_id")
private String operationSceneId;
/**
* 用户在直连商户应用下的用户标示
*/
@SerializedName("openid")
private String openid;
/**
* 收款用户姓名
* 已脱敏
*/
@SerializedName("user_name")
private String userName;
/**
* 转账金额
* 单位为"分"
*/
@SerializedName("transfer_amount")
private Integer transferAmount;
/**
* 转账备注
*/
@SerializedName("transfer_remark")
private String transferRemark;
/**
* 转账状态
* WAIT_PAY等待确认
* PROCESSING转账中
* SUCCESS转账成功
* FAIL转账失败
* REFUND已退款
*/
@SerializedName("transfer_state")
private String transferState;
/**
* 发起转账的时间
* 遵循rfc3339标准格式格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE
*/
@SerializedName("create_time")
private String createTime;
/**
* 转账更新时间
* 遵循rfc3339标准格式格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE
*/
@SerializedName("update_time")
private String updateTime;
/**
* 失败原因
* 当transfer_state为FAIL时返回
*/
@SerializedName("fail_reason")
private String failReason;
}

View File

@@ -0,0 +1,89 @@
package com.github.binarywang.wxpay.bean.transfer;
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;
/**
* 运营工具-商家转账请求参数
*
* @author WxJava Team
* @see <a href="https://pay.weixin.qq.com/doc/v3/merchant/4012711988">运营工具-商家转账API</a>
*/
@Data
@Builder(builderMethodName = "newBuilder")
@NoArgsConstructor
@AllArgsConstructor
public class BusinessOperationTransferRequest implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 直连商户的appid
* 必须
*/
@SerializedName("appid")
private String appid;
/**
* 商户系统内部的商家单号
* 必须,要求此参数只能由数字、大小写字母组成,在商户系统内部唯一
*/
@SerializedName("out_bill_no")
private String outBillNo;
/**
* 运营工具转账场景ID
* 必须,用于标识运营工具转账的具体业务场景
*/
@SerializedName("operation_scene_id")
private String operationSceneId;
/**
* 用户在直连商户应用下的用户标示
* 必须
*/
@SerializedName("openid")
private String openid;
/**
* 收款用户姓名
* 可选,传入则校验收款用户姓名
* 使用RSA加密使用OAEP填充方式
*/
@SpecEncrypt
@SerializedName("user_name")
private String userName;
/**
* 转账金额
* 必须,单位为"分"
*/
@SerializedName("transfer_amount")
private Integer transferAmount;
/**
* 转账备注
* 必须,会在转账成功消息和转账详情页向用户展示
*/
@SerializedName("transfer_remark")
private String transferRemark;
/**
* 用户收款感知
* 可选,用于在转账成功消息中向用户展示特定内容
*/
@SerializedName("user_recv_perception")
private String userRecvPerception;
/**
* 异步接收微信支付转账结果通知的回调地址
* 可选通知URL必须为外网可以正常访问的地址不能携带查询参数
*/
@SerializedName("notify_url")
private String notifyUrl;
}

View File

@@ -0,0 +1,64 @@
package com.github.binarywang.wxpay.bean.transfer;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 运营工具-商家转账结果
*
* @author WxJava Team
* @see <a href="https://pay.weixin.qq.com/doc/v3/merchant/4012711988">运营工具-商家转账API</a>
*/
@Data
@NoArgsConstructor
public class BusinessOperationTransferResult implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 商户系统内部的商家单号
*/
@SerializedName("out_bill_no")
private String outBillNo;
/**
* 微信转账单号
* 微信商家转账系统返回的唯一标识
*/
@SerializedName("transfer_bill_no")
private String transferBillNo;
/**
* 转账状态
* WAIT_PAY等待确认
* PROCESSING转账中
* SUCCESS转账成功
* FAIL转账失败
* REFUND已退款
*/
@SerializedName("transfer_state")
private String transferState;
/**
* 发起转账的时间
* 遵循rfc3339标准格式格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE
*/
@SerializedName("create_time")
private String createTime;
/**
* 转账更新时间
* 遵循rfc3339标准格式格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE
*/
@SerializedName("update_time")
private String updateTime;
/**
* 失败原因
* 当transfer_state为FAIL时返回
*/
@SerializedName("fail_reason")
private String failReason;
}

View File

@@ -412,6 +412,29 @@ public class WxPayConstants {
public static final String CASH_MARKETING = "1001";
}
/**
* 【运营工具转账场景ID】 运营工具专用转账场景,用于商户日常运营活动
*
* @see <a href="https://pay.weixin.qq.com/doc/v3/merchant/4012711988">运营工具-商家转账API</a>
*/
@UtilityClass
public static class OperationSceneId {
/**
* 运营工具现金营销
*/
public static final String OPERATION_CASH_MARKETING = "2001";
/**
* 运营工具佣金报酬
*/
public static final String OPERATION_COMMISSION = "2002";
/**
* 运营工具推广奖励
*/
public static final String OPERATION_PROMOTION = "2003";
}
/**
* 用户收款感知
*

View File

@@ -0,0 +1,135 @@
package com.github.binarywang.wxpay.example;
import com.github.binarywang.wxpay.bean.transfer.*;
import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.constant.WxPayConstants;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.BusinessOperationTransferService;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
/**
* 运营工具-商家转账API使用示例
*
* 微信支付为商户提供的运营工具转账能力,用于商户的日常运营活动中进行转账操作
*
* @author WxJava Team
* @see <a href="https://pay.weixin.qq.com/doc/v3/merchant/4012711988">运营工具-商家转账API</a>
*/
public class BusinessOperationTransferExample {
private WxPayService wxPayService;
private BusinessOperationTransferService businessOperationTransferService;
public void init() {
// 初始化配置
WxPayConfig config = new WxPayConfig();
config.setAppId("your_app_id");
config.setMchId("your_mch_id");
config.setMchKey("your_mch_key");
config.setKeyPath("path_to_your_cert.p12");
// 初始化服务
wxPayService = new WxPayServiceImpl();
wxPayService.setConfig(config);
businessOperationTransferService = wxPayService.getBusinessOperationTransferService();
}
/**
* 发起运营工具转账示例
*/
public void createOperationTransferExample() {
try {
// 构建转账请求
BusinessOperationTransferRequest request = BusinessOperationTransferRequest.newBuilder()
.appid("your_app_id") // 应用ID
.outBillNo("OT" + System.currentTimeMillis()) // 商户转账单号
.operationSceneId(WxPayConstants.OperationSceneId.OPERATION_CASH_MARKETING) // 运营工具转账场景ID
.openid("user_openid") // 用户openid
.userName("张三") // 用户姓名(可选)
.transferAmount(100) // 转账金额,单位分
.transferRemark("运营活动奖励") // 转账备注
.userRecvPerception(WxPayConstants.UserRecvPerception.CASH_MARKETING.CASH) // 用户收款感知
.notifyUrl("https://your-domain.com/notify") // 回调通知地址
.build();
// 发起转账
BusinessOperationTransferResult result = businessOperationTransferService.createOperationTransfer(request);
System.out.println("转账成功!");
System.out.println("商户单号: " + result.getOutBillNo());
System.out.println("微信转账单号: " + result.getTransferBillNo());
System.out.println("转账状态: " + result.getTransferState());
System.out.println("创建时间: " + result.getCreateTime());
} catch (WxPayException e) {
System.err.println("转账失败: " + e.getMessage());
e.printStackTrace();
}
}
/**
* 通过商户单号查询转账结果示例
*/
public void queryByOutBillNoExample() {
try {
String outBillNo = "OT1640995200000"; // 商户转账单号
BusinessOperationTransferQueryResult result = businessOperationTransferService
.queryOperationTransferByOutBillNo(outBillNo);
System.out.println("查询成功!");
System.out.println("商户单号: " + result.getOutBillNo());
System.out.println("微信转账单号: " + result.getTransferBillNo());
System.out.println("转账状态: " + result.getTransferState());
System.out.println("转账金额: " + result.getTransferAmount() + "");
System.out.println("创建时间: " + result.getCreateTime());
System.out.println("更新时间: " + result.getUpdateTime());
} catch (WxPayException e) {
System.err.println("查询失败: " + e.getMessage());
e.printStackTrace();
}
}
/**
* 通过微信转账单号查询转账结果示例
*/
public void queryByTransferBillNoExample() {
try {
String transferBillNo = "1040000071100999991182020050700019480001"; // 微信转账单号
BusinessOperationTransferQueryResult result = businessOperationTransferService
.queryOperationTransferByTransferBillNo(transferBillNo);
System.out.println("查询成功!");
System.out.println("商户单号: " + result.getOutBillNo());
System.out.println("微信转账单号: " + result.getTransferBillNo());
System.out.println("运营场景ID: " + result.getOperationSceneId());
System.out.println("转账状态: " + result.getTransferState());
} catch (WxPayException e) {
System.err.println("查询失败: " + e.getMessage());
e.printStackTrace();
}
}
/**
* 使用配置示例
*/
public static void main(String[] args) {
BusinessOperationTransferExample example = new BusinessOperationTransferExample();
// 初始化配置
example.init();
// 1. 发起运营工具转账
example.createOperationTransferExample();
// 2. 查询转账结果
// example.queryByOutBillNoExample();
// 3. 通过微信转账单号查询
// example.queryByTransferBillNoExample();
}
}

View File

@@ -0,0 +1,82 @@
package com.github.binarywang.wxpay.service;
import com.github.binarywang.wxpay.bean.transfer.BusinessOperationTransferRequest;
import com.github.binarywang.wxpay.bean.transfer.BusinessOperationTransferResult;
import com.github.binarywang.wxpay.bean.transfer.BusinessOperationTransferQueryRequest;
import com.github.binarywang.wxpay.bean.transfer.BusinessOperationTransferQueryResult;
import com.github.binarywang.wxpay.exception.WxPayException;
/**
* 运营工具-商家转账API
* <p>
* 微信支付为商户提供的运营工具转账能力,用于商户的日常运营活动中进行转账操作
*
* @author WxJava Team
* @see <a href="https://pay.weixin.qq.com/doc/v3/merchant/4012711988">运营工具-商家转账API</a>
*/
public interface BusinessOperationTransferService {
/**
* <pre>
* 发起运营工具商家转账
*
* 请求方式POSTHTTPS
* 请求地址https://api.mch.weixin.qq.com/v3/fund-app/operation/mch-transfer/transfer-bills
*
* 文档地址:<a href="https://pay.weixin.qq.com/doc/v3/merchant/4012711988">运营工具-商家转账API</a>
* </pre>
*
* @param request 运营工具转账请求参数
* @return BusinessOperationTransferResult 转账结果
* @throws WxPayException 微信支付异常
*/
BusinessOperationTransferResult createOperationTransfer(BusinessOperationTransferRequest request) throws WxPayException;
/**
* <pre>
* 查询运营工具转账结果
*
* 请求方式GETHTTPS
* 请求地址https://api.mch.weixin.qq.com/v3/fund-app/operation/mch-transfer/transfer-bills/out-bill-no/{out_bill_no}
*
* 文档地址:<a href="https://pay.weixin.qq.com/doc/v3/merchant/4012711988">运营工具-商家转账API</a>
* </pre>
*
* @param request 查询请求参数
* @return BusinessOperationTransferQueryResult 查询结果
* @throws WxPayException 微信支付异常
*/
BusinessOperationTransferQueryResult queryOperationTransfer(BusinessOperationTransferQueryRequest request) throws WxPayException;
/**
* <pre>
* 通过商户单号查询运营工具转账结果
*
* 请求方式GETHTTPS
* 请求地址https://api.mch.weixin.qq.com/v3/fund-app/operation/mch-transfer/transfer-bills/out-bill-no/{out_bill_no}
*
* 文档地址:<a href="https://pay.weixin.qq.com/doc/v3/merchant/4012711988">运营工具-商家转账API</a>
* </pre>
*
* @param outBillNo 商户单号
* @return BusinessOperationTransferQueryResult 查询结果
* @throws WxPayException 微信支付异常
*/
BusinessOperationTransferQueryResult queryOperationTransferByOutBillNo(String outBillNo) throws WxPayException;
/**
* <pre>
* 通过微信转账单号查询运营工具转账结果
*
* 请求方式GETHTTPS
* 请求地址https://api.mch.weixin.qq.com/v3/fund-app/operation/mch-transfer/transfer-bills/transfer-bill-no/{transfer_bill_no}
*
* 文档地址:<a href="https://pay.weixin.qq.com/doc/v3/merchant/4012711988">运营工具-商家转账API</a>
* </pre>
*
* @param transferBillNo 微信转账单号
* @return BusinessOperationTransferQueryResult 查询结果
* @throws WxPayException 微信支付异常
*/
BusinessOperationTransferQueryResult queryOperationTransferByTransferBillNo(String transferBillNo) throws WxPayException;
}

View File

@@ -1646,6 +1646,13 @@ public interface WxPayService {
*/
TransferService getTransferService();
/**
* 获取运营工具-商家转账服务类
*
* @return the business operation transfer service
*/
BusinessOperationTransferService getBusinessOperationTransferService();
/**
* 获取服务商支付分服务类
*

View File

@@ -130,6 +130,9 @@ public abstract class BaseWxPayServiceImpl implements WxPayService {
@Getter
private final BrandMerchantTransferService brandMerchantTransferService = new BrandMerchantTransferServiceImpl(this);
@Getter
private final BusinessOperationTransferService businessOperationTransferService = new BusinessOperationTransferServiceImpl(this);
protected Map<String, WxPayConfig> configMap = new ConcurrentHashMap<>();
@Override
@@ -1415,4 +1418,9 @@ public abstract class BaseWxPayServiceImpl implements WxPayService {
public TransferService getTransferService() {
return transferService;
}
@Override
public BusinessOperationTransferService getBusinessOperationTransferService() {
return businessOperationTransferService;
}
}

View File

@@ -0,0 +1,74 @@
package com.github.binarywang.wxpay.service.impl;
import com.github.binarywang.wxpay.bean.transfer.*;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.BusinessOperationTransferService;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.v3.util.RsaCryptoUtil;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.security.cert.X509Certificate;
/**
* 运营工具-商家转账API实现
*
* @author WxJava Team
* @see <a href="https://pay.weixin.qq.com/doc/v3/merchant/4012711988">运营工具-商家转账API</a>
*/
@Slf4j
@RequiredArgsConstructor
public class BusinessOperationTransferServiceImpl implements BusinessOperationTransferService {
private static final Gson GSON = new GsonBuilder().create();
private final WxPayService wxPayService;
@Override
public BusinessOperationTransferResult createOperationTransfer(BusinessOperationTransferRequest request) throws WxPayException {
// 设置默认appid
if (StringUtils.isEmpty(request.getAppid())) {
request.setAppid(this.wxPayService.getConfig().getAppId());
}
String url = String.format("%s/v3/fund-app/operation/mch-transfer/transfer-bills", this.wxPayService.getPayBaseUrl());
// 如果传入了用户姓名需要进行RSA加密
if (StringUtils.isNotEmpty(request.getUserName())) {
X509Certificate validCertificate = this.wxPayService.getConfig().getVerifier().getValidCertificate();
RsaCryptoUtil.encryptFields(request, validCertificate);
}
String response = wxPayService.postV3WithWechatpaySerial(url, GSON.toJson(request));
return GSON.fromJson(response, BusinessOperationTransferResult.class);
}
@Override
public BusinessOperationTransferQueryResult queryOperationTransfer(BusinessOperationTransferQueryRequest request) throws WxPayException {
if (StringUtils.isNotEmpty(request.getOutBillNo())) {
return queryOperationTransferByOutBillNo(request.getOutBillNo());
} else if (StringUtils.isNotEmpty(request.getTransferBillNo())) {
return queryOperationTransferByTransferBillNo(request.getTransferBillNo());
} else {
throw new WxPayException("商户单号(out_bill_no)和微信转账单号(transfer_bill_no)必须提供其中一个");
}
}
@Override
public BusinessOperationTransferQueryResult queryOperationTransferByOutBillNo(String outBillNo) throws WxPayException {
String url = String.format("%s/v3/fund-app/operation/mch-transfer/transfer-bills/out-bill-no/%s",
this.wxPayService.getPayBaseUrl(), outBillNo);
String response = wxPayService.getV3(url);
return GSON.fromJson(response, BusinessOperationTransferQueryResult.class);
}
@Override
public BusinessOperationTransferQueryResult queryOperationTransferByTransferBillNo(String transferBillNo) throws WxPayException {
String url = String.format("%s/v3/fund-app/operation/mch-transfer/transfer-bills/transfer-bill-no/%s",
this.wxPayService.getPayBaseUrl(), transferBillNo);
String response = wxPayService.getV3(url);
return GSON.fromJson(response, BusinessOperationTransferQueryResult.class);
}
}

View File

@@ -0,0 +1,93 @@
package com.github.binarywang.wxpay.service;
import com.github.binarywang.wxpay.bean.transfer.*;
import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.constant.WxPayConstants;
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* 运营工具-商家转账API测试
*
* @author WxJava Team
*/
public class BusinessOperationTransferServiceTest {
private WxPayService wxPayService;
@BeforeClass
public void setup() {
WxPayConfig config = new WxPayConfig();
config.setAppId("test_app_id");
config.setMchId("test_mch_id");
wxPayService = new WxPayServiceImpl();
wxPayService.setConfig(config);
}
@Test
public void testServiceInitialization() {
BusinessOperationTransferService service = this.wxPayService.getBusinessOperationTransferService();
assertThat(service).isNotNull();
}
@Test
public void testRequestBuilder() {
BusinessOperationTransferRequest request = BusinessOperationTransferRequest.newBuilder()
.appid("test_app_id")
.outBillNo("OT" + System.currentTimeMillis())
.operationSceneId(WxPayConstants.OperationSceneId.OPERATION_CASH_MARKETING)
.openid("test_openid")
.transferAmount(100)
.transferRemark("测试转账")
.userRecvPerception(WxPayConstants.UserRecvPerception.CASH_MARKETING.CASH)
.build();
assertThat(request.getAppid()).isEqualTo("test_app_id");
assertThat(request.getOperationSceneId()).isEqualTo(WxPayConstants.OperationSceneId.OPERATION_CASH_MARKETING);
assertThat(request.getTransferAmount()).isEqualTo(100);
assertThat(request.getTransferRemark()).isEqualTo("测试转账");
}
@Test
public void testQueryRequestBuilder() {
BusinessOperationTransferQueryRequest request = BusinessOperationTransferQueryRequest.newBuilder()
.outBillNo("OT123456789")
.appid("test_app_id")
.build();
assertThat(request.getOutBillNo()).isEqualTo("OT123456789");
assertThat(request.getAppid()).isEqualTo("test_app_id");
}
@Test
public void testConstants() {
// 测试运营工具转账场景ID常量
assertThat(WxPayConstants.OperationSceneId.OPERATION_CASH_MARKETING).isEqualTo("2001");
assertThat(WxPayConstants.OperationSceneId.OPERATION_COMMISSION).isEqualTo("2002");
assertThat(WxPayConstants.OperationSceneId.OPERATION_PROMOTION).isEqualTo("2003");
}
@Test
public void testResultClasses() {
// 测试结果类的基本功能
BusinessOperationTransferResult result = new BusinessOperationTransferResult();
result.setOutBillNo("test_out_bill_no");
result.setTransferBillNo("test_transfer_bill_no");
result.setTransferState("SUCCESS");
assertThat(result.getOutBillNo()).isEqualTo("test_out_bill_no");
assertThat(result.getTransferBillNo()).isEqualTo("test_transfer_bill_no");
assertThat(result.getTransferState()).isEqualTo("SUCCESS");
BusinessOperationTransferQueryResult queryResult = new BusinessOperationTransferQueryResult();
queryResult.setOperationSceneId("2001");
queryResult.setTransferAmount(100);
assertThat(queryResult.getOperationSceneId()).isEqualTo("2001");
assertThat(queryResult.getTransferAmount()).isEqualTo(100);
}
}