From 262a02b606354db065250993e40f6c39507a5ade Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 24 Jul 2025 16:43:23 +0800 Subject: [PATCH] =?UTF-8?q?:memo:=20=E4=B8=BA=E5=BE=AE=E4=BF=A1=E6=94=AF?= =?UTF-8?q?=E4=BB=98=E6=96=B0=E5=A2=9E=E5=95=86=E6=88=B7=E8=BD=AC=E8=B4=A6?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E6=B7=BB=E5=8A=A0=E5=85=A8=E9=9D=A2=E7=9A=84?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E5=92=8C=E7=A4=BA=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- NEW_TRANSFER_API_SUPPORT.md | 129 +++++++++ NEW_TRANSFER_API_USAGE.md | 148 +++++++++++ .../wxpay/example/NewTransferApiExample.java | 249 ++++++++++++++++++ 3 files changed, 526 insertions(+) create mode 100644 NEW_TRANSFER_API_SUPPORT.md create mode 100644 NEW_TRANSFER_API_USAGE.md create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/example/NewTransferApiExample.java diff --git a/NEW_TRANSFER_API_SUPPORT.md b/NEW_TRANSFER_API_SUPPORT.md new file mode 100644 index 000000000..c7e9eaf49 --- /dev/null +++ b/NEW_TRANSFER_API_SUPPORT.md @@ -0,0 +1,129 @@ +# 微信支付新版商户转账API支持 + +## 问题解答 + +**问题**: 新开通的商户号只能使用最新版本的商户转账接口,WxJava是否支持? + +**答案**: **WxJava 已经完整支持新版商户转账API!** 从2025年1月15日开始生效的新版转账API已在WxJava中实现。 + +## 新版转账API特性 + +### 1. API接口对比 + +| 特性 | 传统转账API | 新版转账API (2025.1.15+) | +|------|-------------|-------------------------| +| **服务类** | `MerchantTransferService` | `TransferService` | +| **API路径** | `/v3/transfer/batches` | `/v3/fund-app/mch-transfer/transfer-bills` | +| **转账方式** | 批量转账 | 单笔转账 | +| **场景支持** | 基础场景 | 丰富场景(如佣金报酬等) | +| **撤销功能** | ❌ 不支持 | ✅ 支持 | +| **适用范围** | 所有商户 | **新开通商户必须使用** | + +### 2. 新版API功能列表 + +✅ **发起转账** - `transferBills()` +✅ **查询转账** - `getBillsByOutBillNo()` / `getBillsByTransferBillNo()` +✅ **撤销转账** - `transformBillsCancel()` +✅ **回调通知** - `parseTransferBillsNotifyResult()` +✅ **RSA加密** - 自动处理用户姓名加密 +✅ **场景支持** - 支持多种转账场景ID + +## 快速开始 + +### 1. 获取服务实例 + +```java +// 获取WxPayService实例 +WxPayService wxPayService = new WxPayServiceImpl(); +wxPayService.setConfig(config); + +// 获取新版转账服务 - 这就是新开通商户需要使用的服务! +TransferService transferService = wxPayService.getTransferService(); +``` + +### 2. 发起转账(新版API) + +```java +// 构建转账请求 +TransferBillsRequest request = TransferBillsRequest.newBuilder() + .appid("your_appid") // 应用ID + .outBillNo("T" + System.currentTimeMillis()) // 商户转账单号 + .transferSceneId("1005") // 转账场景ID(佣金报酬) + .openid("user_openid") // 用户openid + .userName("张三") // 收款用户姓名(可选,自动加密) + .transferAmount(100) // 转账金额(分) + .transferRemark("佣金报酬") // 转账备注 + .build(); + +// 发起转账 +TransferBillsResult result = transferService.transferBills(request); +System.out.println("转账成功,微信转账单号:" + result.getTransferBillNo()); +``` + +### 3. 查询转账结果 + +```java +// 通过商户单号查询 +TransferBillsGetResult result = transferService.getBillsByOutBillNo("T1642567890123"); + +// 通过微信转账单号查询 +TransferBillsGetResult result2 = transferService.getBillsByTransferBillNo("wx_transfer_bill_no"); + +System.out.println("转账状态:" + result.getState()); +``` + +### 4. 撤销转账(新功能) + +```java +// 撤销转账 +TransferBillsCancelResult cancelResult = transferService.transformBillsCancel("T1642567890123"); +System.out.println("撤销状态:" + cancelResult.getState()); +``` + +## 重要说明 + +### 转账场景ID (transfer_scene_id) +- **1005**: 佣金报酬(常用场景) +- 其他场景需要在微信商户平台申请 + +### 转账状态说明 +- **ACCEPTED**: 转账已受理 +- **PROCESSING**: 转账处理中 +- **SUCCESS**: 转账成功 +- **FAIL**: 转账失败 +- **CANCELLED**: 转账撤销完成 + +### 新开通商户使用建议 + +1. **优先使用** `TransferService` (新版API) +2. **不要使用** `MerchantTransferService` (可能不支持) +3. **必须设置** 转账场景ID (`transfer_scene_id`) +4. **建议开启** 回调通知以实时获取转账结果 + +## 完整示例代码 + +详细的使用示例请参考: +- 📄 [NEW_TRANSFER_API_USAGE.md](./NEW_TRANSFER_API_USAGE.md) - 详细使用指南 +- 💻 [NewTransferApiExample.java](./weixin-java-pay/src/main/java/com/github/binarywang/wxpay/example/NewTransferApiExample.java) - 完整代码示例 + +## 常见问题 + +**Q: 我是新开通的商户,应该使用哪个服务?** +A: 使用 `TransferService`,这是专为新版API设计的服务。 + +**Q: 新版API和旧版API有什么区别?** +A: 新版API使用单笔转账模式,支持更丰富的转账场景,并且支持撤销功能。 + +**Q: 如何设置转账场景ID?** +A: 在商户平台申请相应场景,常用的佣金报酬场景ID是"1005"。 + +**Q: 用户姓名需要加密吗?** +A: WxJava会自动处理RSA加密,您只需要传入明文姓名即可。 + +## 版本要求 + +- WxJava 版本:4.7.0+ +- 支持时间:2025年1月15日+ +- 适用商户:所有商户(新开通商户强制使用) + +通过以上说明,新开通的微信支付商户可以放心使用WxJava进行商户转账操作! \ No newline at end of file diff --git a/NEW_TRANSFER_API_USAGE.md b/NEW_TRANSFER_API_USAGE.md new file mode 100644 index 000000000..9d1ac8254 --- /dev/null +++ b/NEW_TRANSFER_API_USAGE.md @@ -0,0 +1,148 @@ +# 微信支付新版商户转账API使用指南 + +## 概述 + +从2025年1月15日开始,微信支付推出了新版的商户转账API。新开通的商户号只能使用最新版本的商户转账接口。WxJava 已经完整支持新版转账API。 + +## API对比 + +### 传统转账API (仍然支持) +- **服务类**: `MerchantTransferService` +- **API前缀**: `/v3/transfer/batches` +- **特点**: 支持批量转账,一次可以转账给多个用户 + +### 新版转账API (2025.1.15+) +- **服务类**: `TransferService` +- **API前缀**: `/v3/fund-app/mch-transfer/transfer-bills` +- **特点**: 单笔转账,支持更丰富的转账场景 + +## 使用新版转账API + +### 1. 获取服务实例 + +```java +// 获取WxPayService实例 +WxPayService wxPayService = new WxPayServiceImpl(); +wxPayService.setConfig(config); + +// 获取新版转账服务 +TransferService transferService = wxPayService.getTransferService(); +``` + +### 2. 发起转账 + +```java +// 构建转账请求 +TransferBillsRequest request = TransferBillsRequest.newBuilder() + .appid("your_appid") // 应用ID + .outBillNo("T" + System.currentTimeMillis()) // 商户转账单号 + .transferSceneId("1005") // 转账场景ID(佣金报酬) + .openid("user_openid") // 用户openid + .userName("张三") // 收款用户姓名(可选,需要加密) + .transferAmount(100) // 转账金额(分) + .transferRemark("佣金报酬") // 转账备注 + .notifyUrl("https://your-domain.com/notify") // 回调地址(可选) + .userRecvPerception("Y") // 用户收款感知(可选) + .build(); + +try { + TransferBillsResult result = transferService.transferBills(request); + System.out.println("转账成功,微信转账单号:" + result.getTransferBillNo()); + System.out.println("状态:" + result.getState()); +} catch (WxPayException e) { + System.err.println("转账失败:" + e.getMessage()); +} +``` + +### 3. 查询转账结果 + +```java +// 通过商户单号查询 +String outBillNo = "T1642567890123"; +TransferBillsGetResult result = transferService.getBillsByOutBillNo(outBillNo); + +// 通过微信转账单号查询 +String transferBillNo = "1000000000000000000000000001"; +TransferBillsGetResult result2 = transferService.getBillsByTransferBillNo(transferBillNo); + +System.out.println("转账状态:" + result.getState()); +System.out.println("转账金额:" + result.getTransferAmount()); +``` + +### 4. 撤销转账 + +```java +// 撤销转账(仅在特定状态下可撤销) +String outBillNo = "T1642567890123"; +TransferBillsCancelResult cancelResult = transferService.transformBillsCancel(outBillNo); +System.out.println("撤销结果:" + cancelResult.getState()); +``` + +### 5. 处理回调通知 + +```java +// 在回调接口中处理通知 +@PostMapping("/transfer/notify") +public String handleTransferNotify(HttpServletRequest request) throws Exception { + String notifyData = StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8); + + // 构建签名头 + SignatureHeader header = new SignatureHeader(); + header.setTimeStamp(request.getHeader("Wechatpay-Timestamp")); + header.setNonce(request.getHeader("Wechatpay-Nonce")); + header.setSignature(request.getHeader("Wechatpay-Signature")); + header.setSerial(request.getHeader("Wechatpay-Serial")); + + try { + TransferBillsNotifyResult notifyResult = transferService.parseTransferBillsNotifyResult(notifyData, header); + + // 处理业务逻辑 + String outBillNo = notifyResult.getOutBillNo(); + String state = notifyResult.getState(); + + System.out.println("转账单号:" + outBillNo + ",状态:" + state); + + return "SUCCESS"; + } catch (WxPayException e) { + System.err.println("验签失败:" + e.getMessage()); + return "FAIL"; + } +} +``` + +## 重要参数说明 + +### 转账场景ID (transfer_scene_id) +- **1005**: 佣金报酬(常用) +- 其他场景ID需要在商户平台申请 + +### 转账状态 +- **PROCESSING**: 转账中 +- **SUCCESS**: 转账成功 +- **FAILED**: 转账失败 +- **REFUNDED**: 已退款 + +### 用户收款感知 (user_recv_perception) +- **Y**: 用户会收到微信转账通知 +- **N**: 用户不会收到微信转账通知 + +## 新旧API对比总结 + +| 特性 | 传统API (MerchantTransferService) | 新版API (TransferService) | +|------|----------------------------------|---------------------------| +| 发起方式 | 批量转账 | 单笔转账 | +| API路径 | `/v3/transfer/batches` | `/v3/fund-app/mch-transfer/transfer-bills` | +| 场景支持 | 基础转账场景 | 丰富的转账场景 | +| 回调通知 | 支持 | 支持 | +| 撤销功能 | 不支持 | 支持 | +| 适用商户 | 所有商户 | 新开通商户必须使用 | + +## 注意事项 + +1. **新开通的商户号**: 必须使用新版API (`TransferService`) +2. **转账场景ID**: 需要在商户平台申请相应的转账场景 +3. **用户姓名加密**: 如果传入用户姓名,会自动进行RSA加密 +4. **回调验签**: 建议开启回调验签以确保安全性 +5. **错误处理**: 妥善处理各种异常情况 + +通过以上指南,您可以轻松使用WxJava的新版商户转账API功能。 \ No newline at end of file diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/example/NewTransferApiExample.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/example/NewTransferApiExample.java new file mode 100644 index 000000000..8d74e5a4e --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/example/NewTransferApiExample.java @@ -0,0 +1,249 @@ +package com.github.binarywang.wxpay.example; + +import com.github.binarywang.wxpay.bean.notify.SignatureHeader; +import com.github.binarywang.wxpay.bean.transfer.*; +import com.github.binarywang.wxpay.config.WxPayConfig; +import com.github.binarywang.wxpay.exception.WxPayException; +import com.github.binarywang.wxpay.service.TransferService; +import com.github.binarywang.wxpay.service.WxPayService; +import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl; + +/** + * 新版商户转账API使用示例 + * + * 从2025年1月15日开始,微信支付推出了新版的商户转账API + * 新开通的商户号只能使用最新版本的商户转账接口 + * + * @author WxJava Team + * @since 2025-01-15 + */ +public class NewTransferApiExample { + + private final TransferService transferService; + + public NewTransferApiExample(WxPayConfig config) { + // 初始化微信支付服务 + WxPayService wxPayService = new WxPayServiceImpl(); + wxPayService.setConfig(config); + + // 获取新版转账服务 + this.transferService = wxPayService.getTransferService(); + } + + /** + * 发起单笔转账示例 + * 新版API使用 /v3/fund-app/mch-transfer/transfer-bills 接口 + */ + public void transferExample() { + try { + // 构建转账请求 + TransferBillsRequest request = TransferBillsRequest.newBuilder() + .appid("wx1234567890123456") // 应用ID + .outBillNo("TRANSFER_" + System.currentTimeMillis()) // 商户转账单号,确保唯一 + .transferSceneId("1005") // 转账场景ID(1005=佣金报酬) + .openid("oUpF8uMuAJO_M2pxb1Q9zNjWeS6o") // 收款用户的openid + .userName("张三") // 收款用户真实姓名(可选,会自动加密) + .transferAmount(100) // 转账金额,单位:分(此处为1元) + .transferRemark("佣金报酬") // 转账备注,用户可见 + .notifyUrl("https://your-domain.com/transfer/notify") // 异步通知地址(可选) + .userRecvPerception("Y") // 用户收款感知:Y=会收到通知,N=不会收到通知 + .build(); + + // 发起转账 + TransferBillsResult result = transferService.transferBills(request); + + // 输出结果 + System.out.println("=== 转账发起成功 ==="); + System.out.println("商户单号: " + result.getOutBillNo()); + System.out.println("微信转账单号: " + result.getTransferBillNo()); + System.out.println("创建时间: " + result.getCreateTime()); + System.out.println("状态: " + result.getState()); + System.out.println("跳转领取页面信息: " + result.getPackageInfo()); + + } catch (WxPayException e) { + System.err.println("转账失败: " + e.getMessage()); + System.err.println("错误代码: " + e.getErrCode()); + System.err.println("错误描述: " + e.getErrCodeDes()); + } + } + + /** + * 通过商户单号查询转账结果 + */ + public void queryByOutBillNoExample() { + try { + String outBillNo = "TRANSFER_1642567890123"; + TransferBillsGetResult result = transferService.getBillsByOutBillNo(outBillNo); + + System.out.println("=== 查询转账结果(商户单号)==="); + System.out.println("商户单号: " + result.getOutBillNo()); + System.out.println("微信转账单号: " + result.getTransferBillNo()); + System.out.println("状态: " + result.getState()); + System.out.println("转账金额: " + result.getTransferAmount() + "分"); + System.out.println("用户openid: " + result.getOpenid()); + System.out.println("转账备注: " + result.getTransferRemark()); + + } catch (WxPayException e) { + System.err.println("查询失败: " + e.getMessage()); + } + } + + /** + * 通过微信转账单号查询转账结果 + */ + public void queryByTransferBillNoExample() { + try { + String transferBillNo = "1000000000000000000000000001"; + TransferBillsGetResult result = transferService.getBillsByTransferBillNo(transferBillNo); + + System.out.println("=== 查询转账结果(微信单号)==="); + System.out.println("微信转账单号: " + result.getTransferBillNo()); + System.out.println("状态: " + result.getState()); + System.out.println("失败原因: " + result.getFailReason()); + + } catch (WxPayException e) { + System.err.println("查询失败: " + e.getMessage()); + } + } + + /** + * 撤销转账示例 + * 注意:只有在特定状态下才能撤销 + */ + public void cancelTransferExample() { + try { + String outBillNo = "TRANSFER_1642567890123"; + TransferBillsCancelResult result = transferService.transformBillsCancel(outBillNo); + + System.out.println("=== 撤销转账结果 ==="); + System.out.println("商户单号: " + result.getOutBillNo()); + System.out.println("状态: " + result.getState()); + System.out.println("更新时间: " + result.getUpdateTime()); + + } catch (WxPayException e) { + System.err.println("撤销失败: " + e.getMessage()); + } + } + + /** + * 处理转账回调通知示例 + * 这个方法通常在您的Web服务器的回调接口中调用 + */ + public void handleNotifyExample(String notifyData, String timestamp, String nonce, String signature, String serial) { + try { + // 构建签名头信息 + SignatureHeader header = new SignatureHeader(); + header.setTimeStamp(timestamp); + header.setNonce(nonce); + header.setSignature(signature); + header.setSerial(serial); + + // 解析并验签回调数据 + TransferBillsNotifyResult notifyResult = transferService.parseTransferBillsNotifyResult(notifyData, header); + + System.out.println("=== 处理转账回调通知 ==="); + System.out.println("商户单号: " + notifyResult.getResult().getOutBillNo()); + System.out.println("微信转账单号: " + notifyResult.getResult().getTransferBillNo()); + System.out.println("状态: " + notifyResult.getResult().getState()); + System.out.println("转账金额: " + notifyResult.getResult().getTransferAmount() + "分"); + System.out.println("更新时间: " + notifyResult.getResult().getUpdateTime()); + + // 根据状态处理业务逻辑 + switch (notifyResult.getResult().getState()) { + case "SUCCESS": + System.out.println("转账成功,进行业务处理..."); + // 更新订单状态、发送通知等 + break; + case "FAIL": + System.out.println("转账失败,失败原因: " + notifyResult.getResult().getFailReason()); + // 处理失败逻辑 + break; + default: + System.out.println("其他状态: " + notifyResult.getResult().getState()); + } + + } catch (WxPayException e) { + System.err.println("回调处理失败: " + e.getMessage()); + } + } + + /** + * 批量转账示例(使用传统API) + * 注意:新商户可能无法使用此API,建议使用新版单笔转账API + */ + public void batchTransferExample() { + try { + // 构建转账明细列表 + TransferBatchesRequest.TransferDetail detail1 = TransferBatchesRequest.TransferDetail.newBuilder() + .outDetailNo("DETAIL_" + System.currentTimeMillis() + "_1") + .transferAmount(100) // 1元 + .transferRemark("佣金1") + .openid("oUpF8uMuAJO_M2pxb1Q9zNjWeS6o") + .userName("张三") + .build(); + + TransferBatchesRequest.TransferDetail detail2 = TransferBatchesRequest.TransferDetail.newBuilder() + .outDetailNo("DETAIL_" + System.currentTimeMillis() + "_2") + .transferAmount(200) // 2元 + .transferRemark("佣金2") + .openid("oUpF8uMuAJO_M2pxb1Q9zNjWeS6p") + .userName("李四") + .build(); + + // 构建批量转账请求 + TransferBatchesRequest batchRequest = TransferBatchesRequest.newBuilder() + .appid("wx1234567890123456") + .outBatchNo("BATCH_" + System.currentTimeMillis()) + .batchName("佣金批量发放") + .batchRemark("2024年1月佣金") + .totalAmount(300) // 总金额:3元 + .totalNum(2) // 总笔数:2笔 + .transferDetailList(java.util.Arrays.asList(detail1, detail2)) + .transferSceneId("1005") // 转账场景ID + .build(); + + // 发起批量转账 + TransferBatchesResult batchResult = transferService.transferBatches(batchRequest); + + System.out.println("=== 批量转账发起成功 ==="); + System.out.println("商户批次单号: " + batchResult.getOutBatchNo()); + System.out.println("微信批次单号: " + batchResult.getBatchId()); + System.out.println("批次状态: " + batchResult.getBatchStatus()); + + } catch (WxPayException e) { + System.err.println("批量转账失败: " + e.getMessage()); + } + } + + /** + * 使用配置示例 + */ + public static void main(String[] args) { + // 配置微信支付参数 + WxPayConfig config = new WxPayConfig(); + config.setAppId("wx1234567890123456"); // 应用ID + config.setMchId("1234567890"); // 商户ID + config.setApiV3Key("your_api_v3_key_32_chars"); // APIv3密钥 + config.setPrivateKeyPath("path/to/private.pem"); // 商户私钥文件路径 + config.setCertSerialNo("your_certificate_serial"); // 商户证书序列号 + + // 创建示例实例 + NewTransferApiExample example = new NewTransferApiExample(config); + + // 运行示例 + System.out.println("新版商户转账API使用示例"); + System.out.println("==============================="); + + // 1. 发起单笔转账 + example.transferExample(); + + // 2. 查询转账结果 + // example.queryByOutBillNoExample(); + + // 3. 撤销转账 + // example.cancelTransferExample(); + + // 4. 批量转账(传统API) + // example.batchTransferExample(); + } +} \ No newline at end of file