From 63117698887895a8ef48603de882421def0d0ff4 Mon Sep 17 00:00:00 2001
From: 0katekate0 <32161300+0katekate0@users.noreply.github.com>
Date: Wed, 15 May 2024 23:06:48 +0800
Subject: [PATCH] =?UTF-8?q?:new:=20#3278=E3=80=90=E5=BE=AE=E4=BF=A1?=
 =?UTF-8?q?=E6=94=AF=E4=BB=98=E3=80=91=20=E5=A2=9E=E5=8A=A0=E8=A7=A3?=
 =?UTF-8?q?=E6=9E=90=E5=95=86=E5=AE=B6=E8=BD=AC=E8=B4=A6=E6=89=B9=E6=AC=A1?=
 =?UTF-8?q?=E5=9B=9E=E8=B0=83=E9=80=9A=E7=9F=A5=E7=9A=84=E6=96=B9=E6=B3=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../WxPayTransferBatchesNotifyV3Result.java   | 131 ++++++++++++++++++
 .../wxpay/service/WxPayService.java           |  11 ++
 .../service/impl/BaseWxPayServiceImpl.java    |   5 +
 .../impl/BaseWxPayServiceImplTest.java        |  69 +++++++++
 4 files changed, 216 insertions(+)
 create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayTransferBatchesNotifyV3Result.java

diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayTransferBatchesNotifyV3Result.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayTransferBatchesNotifyV3Result.java
new file mode 100644
index 000000000..4280c62c0
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayTransferBatchesNotifyV3Result.java
@@ -0,0 +1,131 @@
+package com.github.binarywang.wxpay.bean.notify;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 商家转账批次回调通知
+ * 文档见:https://pay.weixin.qq.com/docs/merchant/apis/batch-transfer-to-balance/transfer-batch-callback-notice.html
+ *
+ * @author Wang_Wong
+ */
+@Data
+@NoArgsConstructor
+public class WxPayTransferBatchesNotifyV3Result implements Serializable, WxPayBaseNotifyV3Result<WxPayTransferBatchesNotifyV3Result.DecryptNotifyResult> {
+  private static final long serialVersionUID = -1L;
+  /**
+   * 源数据
+   */
+  private OriginNotifyResponse rawData;
+  /**
+   * 解密后的数据
+   */
+  private DecryptNotifyResult result;
+
+  @Data
+  @NoArgsConstructor
+  public static class DecryptNotifyResult implements Serializable {
+    private static final long serialVersionUID = -1L;
+    /**
+     * <pre>
+     * 字段名:直连商户号
+     * 变量名:mchid
+     * 是否必填:是
+     * 类型:string[1,32]
+     * 描述:
+     *  直连商户的商户号,由微信支付生成并下发。
+     *  示例值:1900000100
+     * </pre>
+     */
+    @SerializedName(value = "mchid")
+    private String mchid;
+
+    /**
+     * 【商家批次单号】
+     * 商户系统内部的商家批次单号,在商户系统内部唯一
+     */
+    @SerializedName(value = "out_batch_no")
+    private String outBatchNo;
+
+    /**
+     * 【微信批次单号】
+     * 微信批次单号,微信商家转账系统返回的唯一标识
+     */
+    @SerializedName(value = "batch_id")
+    private String batchId;
+
+    /**
+     * 【批次状态】
+     * WAIT_PAY: 待付款确认。需要付款出资商户在商家助手小程序或服务商助手小程序进行付款确认
+     * ACCEPTED:已受理。批次已受理成功,若发起批量转账的30分钟后,转账批次单仍处于该状态,可能原因是商户账户余额不足等。商户可查询账户资金流水,若该笔转账批次单的扣款已经发生,则表示批次已经进入转账中,请再次查单确认
+     * PROCESSING:转账中。已开始处理批次内的转账明细单
+     * FINISHED:已完成。批次内的所有转账明细单都已处理完成
+     * CLOSED:已关闭。可查询具体的批次关闭原因确认
+     */
+    @SerializedName(value = "batch_status")
+    private String batchStatus;
+
+    /**
+     * 【批次总笔数】
+     * 转账总笔数。
+     */
+    @SerializedName(value = "total_num")
+    private Integer totalNum;
+
+    /**
+     * 【批次总金额】
+     * 转账总金额,单位为“分”。
+     */
+    @SerializedName(value = "total_amount")
+    private Integer totalAmount;
+
+    /**
+     * 【转账成功金额】
+     * 转账成功的金额,单位为“分”。当批次状态为“PROCESSING”(转账中)时,转账成功金额随时可能变化
+     */
+    @SerializedName(value = "success_amount")
+    private Integer successAmount;
+
+    /**
+     * 【转账成功笔数】
+     * 转账成功的笔数。当批次状态为“PROCESSING”(转账中)时,转账成功笔数随时可能变化
+     */
+    @SerializedName(value = "success_num")
+    private Integer successNum;
+
+    /**
+     * 【转账失败金额】
+     * 转账失败的金额,单位为“分”
+     */
+    @SerializedName(value = "fail_amount")
+    private Integer failAmount;
+
+    /**
+     * 【转账失败笔数】
+     * 转账失败的笔数
+     */
+    @SerializedName(value = "fail_num")
+    private Integer failNum;
+
+    /**
+     * 【批次关闭原因】
+     * 如果批次单状态为“CLOSED”(已关闭),则有关闭原因
+     * 可选取值:
+     * OVERDUE_CLOSE:系统超时关闭,可能原因账户余额不足或其他错误
+     * TRANSFER_SCENE_INVALID:付款确认时,转账场景已不可用,系统做关单处理
+     */
+    @SerializedName(value = "close_reason")
+    private String closeReason;
+
+    /**
+     * 【批次更新时间】
+     * 遵循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(value = "update_time")
+    private String updateTime;
+
+  }
+}
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java
index 9aba25d04..b73029f4e 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java
@@ -980,6 +980,17 @@ public interface WxPayService {
    */
   WxPayRefundNotifyV3Result parseRefundNotifyV3Result(String notifyData, SignatureHeader header) throws WxPayException;
 
+  /**
+   * 解析商家转账批次回调通知
+   * https://pay.weixin.qq.com/docs/merchant/apis/batch-transfer-to-balance/transfer-batch-callback-notice.html
+   *
+   * @param notifyData
+   * @param header
+   * @return
+   * @throws WxPayException
+   */
+  WxPayTransferBatchesNotifyV3Result parseTransferBatchesNotifyV3Result(String notifyData, SignatureHeader header) throws WxPayException;
+
   /**
    * 解析服务商模式退款结果通知
    * 详见https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_1_11.shtml
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java
index 8466a5e91..1187880cb 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java
@@ -432,6 +432,11 @@ public abstract class BaseWxPayServiceImpl implements WxPayService {
     return this.baseParseOrderNotifyV3Result(notifyData, header, WxPayRefundNotifyV3Result.class, WxPayRefundNotifyV3Result.DecryptNotifyResult.class);
   }
 
+  @Override
+  public WxPayTransferBatchesNotifyV3Result parseTransferBatchesNotifyV3Result(String notifyData, SignatureHeader header) throws WxPayException {
+    return this.baseParseOrderNotifyV3Result(notifyData, header, WxPayTransferBatchesNotifyV3Result.class, WxPayTransferBatchesNotifyV3Result.DecryptNotifyResult.class);
+  }
+
   @Override
   public WxPayPartnerRefundNotifyV3Result parsePartnerRefundNotifyV3Result(String notifyData, SignatureHeader header) throws WxPayException {
     return this.baseParseOrderNotifyV3Result(notifyData, header, WxPayPartnerRefundNotifyV3Result.class, WxPayPartnerRefundNotifyV3Result.DecryptNotifyResult.class);
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImplTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImplTest.java
index 3990f5b61..e777d6897 100644
--- a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImplTest.java
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImplTest.java
@@ -841,6 +841,75 @@ public class BaseWxPayServiceImplTest {
     return WxPayNotifyV3Response.success("成功");
   }
 
+  /**
+   * 商家转账批次回调通知
+   * https://pay.weixin.qq.com/docs/merchant/apis/batch-transfer-to-balance/transfer-batch-callback-notice.html
+   *
+   * @return
+   * @throws Exception
+   */
+  @Test
+  public String parseTransferBatchesNotifyV3Result() throws Exception {
+
+    String body = "{\n" +
+      "    \"id\": \"1c8192d8-aba1-5898-a79c-7d3abb72eabe\",\n" +
+      "    \"create_time\": \"2023-08-16T16:43:27+08:00\",\n" +
+      "    \"resource_type\": \"encrypt-resource\",\n" +
+      "    \"event_type\": \"MCHTRANSFER.BATCH.FINISHED\",\n" +
+      "    \"summary\": \"商家转账批次完成通知\",\n" +
+      "    \"resource\": {\n" +
+      "        \"original_type\": \"mch_payment\",\n" +
+      "        \"algorithm\": \"AEAD_AES_256_GCM\",\n" +
+      "        \"ciphertext\": \"zTBf6DDPzZSoIBkoLFkC+ho97QrqnT6UU/ADM0tJP07ITaFPek4vofQjmclLUof78NqrPcJs5OIBl+gnKKJ4xCxcDmDnZZHvev5o1pk4gwtJIFIDxbq3piDr4Wq6cZpvGPPQTYC8YoVRTdVeeN+EcuklRrmaFzv8wCTSdI9wFJ9bsxtLedhq4gpkKqN5fbSguQg9JFsX3OJeT7KPfRd6SD1gu4Lpw5gwxthfOHcYsjM/eY5gaew8zzpN6mMUEJ1HqkNuQgOguHBxFnqFPiMz+Iadw7X38Yz+IgfUkOhN1iuvMhGYKbwKJ7rTiBVvGGpF6Wse1zFKgSiTLH2RnUAMkkHmxqk+JhbQKZpSWr6O8BfhHO1OKg7hpcHZtOJKNMjIF62WYDVf36w1h8h5fg==\",\n" +
+      "        \"associated_data\": \"mch_payment\",\n" +
+      "        \"nonce\": \"DdF3UJVNQaKT\"\n" +
+      "    }\n" +
+      "}";
+    WxPayTransferBatchesNotifyV3Result transferBatchesNotifyV3Body = GSON.fromJson(body, WxPayTransferBatchesNotifyV3Result.class);
+    log.info(GSON.toJson(transferBatchesNotifyV3Body));
+
+    // 处理业务逻辑 ...
+
+    return WxPayNotifyV3Response.success("成功");
+  }
+
+  // 测试
+  public static void main(String[] args){
+    String body = "{\n" +
+      "    \"id\": \"1c8192d8-aba1-5898-a79c-7d3abb72eabe\",\n" +
+      "    \"create_time\": \"2023-08-16T16:43:27+08:00\",\n" +
+      "    \"resource_type\": \"encrypt-resource\",\n" +
+      "    \"event_type\": \"MCHTRANSFER.BATCH.FINISHED\",\n" +
+      "    \"summary\": \"商家转账批次完成通知\",\n" +
+      "    \"resource\": {\n" +
+      "        \"original_type\": \"mch_payment\",\n" +
+      "        \"algorithm\": \"AEAD_AES_256_GCM\",\n" +
+      "        \"ciphertext\": \"zTBf6DDPzZSoIBkoLFkC+ho97QrqnT6UU/ADM0tJP07ITaFPek4vofQjmclLUof78NqrPcJs5OIBl+gnKKJ4xCxcDmDnZZHvev5o1pk4gwtJIFIDxbq3piDr4Wq6cZpvGPPQTYC8YoVRTdVeeN+EcuklRrmaFzv8wCTSdI9wFJ9bsxtLedhq4gpkKqN5fbSguQg9JFsX3OJeT7KPfRd6SD1gu4Lpw5gwxthfOHcYsjM/eY5gaew8zzpN6mMUEJ1HqkNuQgOguHBxFnqFPiMz+Iadw7X38Yz+IgfUkOhN1iuvMhGYKbwKJ7rTiBVvGGpF6Wse1zFKgSiTLH2RnUAMkkHmxqk+JhbQKZpSWr6O8BfhHO1OKg7hpcHZtOJKNMjIF62WYDVf36w1h8h5fg==\",\n" +
+      "        \"associated_data\": \"mch_payment\",\n" +
+      "        \"nonce\": \"DdF3UJVNQaKT\"\n" +
+      "    }\n" +
+      "}";
+    OriginNotifyResponse transferBatchesNotifyV3Body = GSON.fromJson(body, OriginNotifyResponse.class);
+    log.info(GSON.toJson(transferBatchesNotifyV3Body));
+
+    String decryptNotifyResult = "{\n" +
+      "    \"out_batch_no\": \"bfatestnotify000033\",\n" +
+      "    \"batch_id\": \"131000007026709999520922023081519403795655\",\n" +
+      "    \"batch_status\": \"FINISHED\",\n" +
+      "    \"total_num\": 2,\n" +
+      "    \"total_amount\": 200,\n" +
+      "    \"success_amount\": 100,\n" +
+      "    \"success_num\": 1,\n" +
+      "    \"fail_amount\": 100,\n" +
+      "    \"fail_num\": 1,\n" +
+      "    \"mchid\": \"2483775951\",\n" +
+      "    \"update_time\": \"2023-08-15T20:33:22+08:00\"\n" +
+      "}";
+    WxPayTransferBatchesNotifyV3Result.DecryptNotifyResult notifyResult = GSON.fromJson(decryptNotifyResult, WxPayTransferBatchesNotifyV3Result.DecryptNotifyResult.class);
+    log.info(GSON.toJson(notifyResult));
+
+  }
+
   @Test
   public void testWxPayNotifyV3Response() {
     System.out.println(WxPayNotifyV3Response.success("success"));