diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java
index 269a69a475..9a715c1e91 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java
@@ -673,4 +673,11 @@ public interface WxCpService extends WxService {
* @return 人事助手服务 hr service
*/
WxCpHrService getHrService();
+
+ /**
+ * 获取待办服务
+ *
+ * @return 待办服务 todo service
+ */
+ WxCpTodoService getTodoService();
}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpTodoService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpTodoService.java
new file mode 100644
index 0000000000..82f17c2303
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpTodoService.java
@@ -0,0 +1,47 @@
+package me.chanjar.weixin.cp.api;
+
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.cp.bean.todo.WxCpTodo;
+
+import java.util.List;
+
+/**
+ * 企业微信待办接口.
+ *
+ * 官方文档:
+ * 获取待办详情,
+ * 更新待办状态
+ *
+ * @author Binary Wang created on 2026-08-11
+ */
+public interface WxCpTodoService {
+ /**
+ * 获取待办详情.
+ *
+ * 该接口用于获取指定的待办详情,请求参数仅包含必填的 todo_id,响应直接返回单个待办对象。
+ *
+ * 请求方式: POST(HTTPS)
+ * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/todo/get?access_token=ACCESS_TOKEN
+ *
+ * @param todoId 待办ID
+ * @return 待办详情 wx cp todo
+ * @throws WxErrorException the wx error exception
+ */
+ WxCpTodo get(String todoId) throws WxErrorException;
+
+ /**
+ * 更新待办状态.
+ *
+ * 该接口用于修改指定的待办信息,支持修改待办整体状态(status 字段)、待办参与人及其状态(attendees[].userid / status 字段)。
+ * 仅允许修改当前应用创建的待办,不允许修改已删除的待办。
+ *
+ * 请求方式: POST(HTTPS)
+ * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/todo/update?access_token=ACCESS_TOKEN
+ *
+ * @param todoId 待办ID
+ * @param status 待办整体状态,可不传:0 - 完成;1 - 进行中。为 null 时不修改整体状态
+ * @param attendees 待办参与人列表,最多支持20个参与人。为 null 或空时不修改参与人列表
+ * @throws WxErrorException the wx error exception
+ */
+ void update(String todoId, Integer status, List attendees) throws WxErrorException;
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java
index a3ec703ca4..e351e58444 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java
@@ -77,6 +77,7 @@ public abstract class BaseWxCpServiceImpl implements WxCpService, RequestH
private final WxCpCorpGroupService corpGroupService = new WxCpCorpGroupServiceImpl(this);
private final WxCpIntelligentRobotService intelligentRobotService = new WxCpIntelligentRobotServiceImpl(this);
private final WxCpHrService hrService = new WxCpHrServiceImpl(this);
+ private final WxCpTodoService todoService = new WxCpTodoServiceImpl(this);
/**
* 全局的是否正在刷新access token的锁.
@@ -753,4 +754,9 @@ public WxCpIntelligentRobotService getIntelligentRobotService() {
public WxCpHrService getHrService() {
return this.hrService;
}
+
+ @Override
+ public WxCpTodoService getTodoService() {
+ return this.todoService;
+ }
}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpTodoServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpTodoServiceImpl.java
new file mode 100644
index 0000000000..627784b36c
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpTodoServiceImpl.java
@@ -0,0 +1,50 @@
+package me.chanjar.weixin.cp.api.impl;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.cp.api.WxCpService;
+import me.chanjar.weixin.cp.api.WxCpTodoService;
+import me.chanjar.weixin.cp.bean.todo.WxCpTodo;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Todo.*;
+
+/**
+ * 企业微信待办接口实现类.
+ *
+ * @author Binary Wang created on 2026-08-11
+ */
+@Slf4j
+@RequiredArgsConstructor
+public class WxCpTodoServiceImpl implements WxCpTodoService {
+ private final WxCpService cpService;
+
+ @Override
+ public WxCpTodo get(String todoId) throws WxErrorException {
+ final Map param = new HashMap<>(1);
+ param.put("todo_id", todoId);
+ final String response = this.cpService.post(this.cpService.getWxCpConfigStorage().getApiUrl(TODO_GET),
+ WxCpGsonBuilder.create().toJson(param));
+ return WxCpGsonBuilder.create().fromJson(response, WxCpTodo.class);
+ }
+
+ @Override
+ public void update(String todoId, Integer status, List attendees) throws WxErrorException {
+ final Map param = new HashMap<>(3);
+ param.put("todo_id", todoId);
+ if (status != null) {
+ param.put("status", status);
+ }
+ if (attendees != null && !attendees.isEmpty()) {
+ param.put("attendees", attendees);
+ }
+
+ this.cpService.post(this.cpService.getWxCpConfigStorage().getApiUrl(TODO_UPDATE),
+ WxCpGsonBuilder.create().toJson(param));
+ }
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/todo/WxCpTodo.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/todo/WxCpTodo.java
new file mode 100644
index 0000000000..a33eb1f4c3
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/todo/WxCpTodo.java
@@ -0,0 +1,110 @@
+package me.chanjar.weixin.cp.bean.todo;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.experimental.Accessors;
+import me.chanjar.weixin.common.bean.ToJson;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 待办信息bean.
+ *
+ * 官方文档:
+ * 获取待办详情
+ *
+ * @author Binary Wang created on 2026-08-11
+ */
+@Data
+@Accessors(chain = true)
+public class WxCpTodo implements Serializable, ToJson {
+ private static final long serialVersionUID = -1L;
+
+ /**
+ * 待办ID
+ */
+ @SerializedName("todo_id")
+ private String todoId;
+ /**
+ * 待办内容
+ */
+ @SerializedName("content")
+ private String content;
+ /**
+ * 待办创建人ID
+ */
+ @SerializedName("creator")
+ private String creator;
+ /**
+ * 待办状态。
+ * 0 - 已完成
+ * 1 - 进行中
+ * 2 - 已删除
+ */
+ @SerializedName("status")
+ private Integer status;
+ /**
+ * 待办创建时间戳(整型秒数)
+ */
+ @SerializedName("create_time")
+ private Long createTime;
+ /**
+ * 待办参与人列表
+ */
+ @SerializedName("attendees")
+ private List attendees;
+ /**
+ * 待办截止时间戳(整型秒数)
+ */
+ @SerializedName("end_time")
+ private Long endTime;
+ /**
+ * 提醒列表
+ */
+ @SerializedName("reminders")
+ private List reminders;
+
+ @Override
+ public String toJson() {
+ return WxCpGsonBuilder.create().toJson(this);
+ }
+
+ /**
+ * 待办参与人.
+ */
+ @Data
+ @Accessors(chain = true)
+ public static class Attendee implements Serializable {
+ private static final long serialVersionUID = -1L;
+
+ /**
+ * 待办参与人ID
+ */
+ @SerializedName("userid")
+ private String userid;
+ /**
+ * 参与人的待办状态。
+ * 0 - 完成
+ * 1 - 进行中
+ */
+ @SerializedName("status")
+ private Integer status;
+ }
+
+ /**
+ * 待办提醒.
+ */
+ @Data
+ @Accessors(chain = true)
+ public static class Reminder implements Serializable {
+ private static final long serialVersionUID = -1L;
+
+ /**
+ * 提醒时间戳(整型秒数)
+ */
+ @SerializedName("remind_time")
+ private Long remindTime;
+ }
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpApiPathConsts.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpApiPathConsts.java
index 642e579870..d95bf0a130 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpApiPathConsts.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpApiPathConsts.java
@@ -1884,4 +1884,22 @@ interface Hr {
*/
String UPDATE_EMPLOYEE_FIELD_INFO = "/cgi-bin/hr/update_staff_info";
}
+
+ /**
+ * 待办相关接口.
+ * 官方文档:https://developer.work.weixin.qq.com/document/path/101524
+ */
+ interface Todo {
+ /**
+ * 获取待办详情
+ * https://developer.work.weixin.qq.com/document/path/101524
+ */
+ String TODO_GET = "/cgi-bin/todo/get";
+
+ /**
+ * 更新待办状态
+ * https://developer.work.weixin.qq.com/document/path/101534
+ */
+ String TODO_UPDATE = "/cgi-bin/todo/update";
+ }
}
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpTodoServiceImplTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpTodoServiceImplTest.java
new file mode 100644
index 0000000000..d8569592ef
--- /dev/null
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpTodoServiceImplTest.java
@@ -0,0 +1,76 @@
+package me.chanjar.weixin.cp.api.impl;
+
+import com.google.inject.Inject;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.cp.api.ApiTestModule;
+import me.chanjar.weixin.cp.api.WxCpService;
+import me.chanjar.weixin.cp.bean.todo.WxCpTodo;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import java.util.Arrays;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
+/**
+ * 单元测试类.
+ *
+ * @author Binary Wang created on 2026-08-11
+ */
+@Guice(modules = ApiTestModule.class)
+public class WxCpTodoServiceImplTest {
+ /**
+ * The Wx service.
+ */
+ @Inject
+ protected WxCpService wxService;
+
+ private static final String TODO_ID = "17c7d2bd9f20d652840f72f59e796AAA";
+
+ /**
+ * Test get.
+ *
+ * @throws WxErrorException the wx error exception
+ */
+ @Test
+ public void testGet() throws WxErrorException {
+ final WxCpTodo todo = this.wxService.getTodoService().get(TODO_ID);
+ assertNotNull(todo, "get() 返回的待办对象不应为 null");
+ assertEquals(todo.getTodoId(), TODO_ID, "返回的 todo_id 应与请求一致");
+ }
+
+ /**
+ * Test update status only.
+ *
+ * @throws WxErrorException the wx error exception
+ */
+ @Test
+ public void testUpdateStatusOnly() throws WxErrorException {
+ this.wxService.getTodoService().update(TODO_ID, 0, null);
+ // 更新成功后通过 get() 回查,验证整体状态确实写入
+ final WxCpTodo todo = this.wxService.getTodoService().get(TODO_ID);
+ assertNotNull(todo, "回查待办不应为 null");
+ assertEquals(todo.getStatus(), Integer.valueOf(0), "待办整体状态应为 0(完成)");
+ }
+
+ /**
+ * Test update with attendees.
+ *
+ * @throws WxErrorException the wx error exception
+ */
+ @Test
+ public void testUpdateWithAttendees() throws WxErrorException {
+ this.wxService.getTodoService().update(TODO_ID, 1,
+ Arrays.asList(
+ new WxCpTodo.Attendee().setUserid("lisi").setStatus(0),
+ new WxCpTodo.Attendee().setUserid("zhangsan").setStatus(1)
+ ));
+ // 更新成功后通过 get() 回查,验证参与人列表确实写入
+ final WxCpTodo todo = this.wxService.getTodoService().get(TODO_ID);
+ assertNotNull(todo, "回查待办不应为 null");
+ assertNotNull(todo.getAttendees(), "attendees 列表不应为 null");
+ assertEquals(todo.getAttendees().size(), 2, "参与人数量应为 2");
+ assertEquals(todo.getStatus(), Integer.valueOf(1), "待办整体状态应为 1(进行中)");
+ }
+}
diff --git a/weixin-java-cp/src/test/resources/testng.xml b/weixin-java-cp/src/test/resources/testng.xml
index f63d3f30f5..cb3b8362e8 100644
--- a/weixin-java-cp/src/test/resources/testng.xml
+++ b/weixin-java-cp/src/test/resources/testng.xml
@@ -24,6 +24,7 @@
+
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 d17c3ae698..8489f80624 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
@@ -368,14 +368,17 @@ public String getConfigKey(String mchId, String appId) {
@Override
public String getPayBaseUrl() {
if (this.getConfig().isUseSandboxEnv()) {
- if (StringUtils.isNotBlank(this.getConfig().getApiV3Key())) {
- throw new WxRuntimeException("微信支付V3 目前不支持沙箱模式!");
- }
return this.getConfig().getApiHostWithPathPrefix() + "/xdc/apiv2sandbox";
}
return this.getConfig().getApiHostWithPathPrefix();
}
+ protected void checkV3SandboxNotSupported() {
+ if (this.getConfig().isUseSandboxEnv()) {
+ throw new WxRuntimeException("微信支付V3 目前不支持沙箱模式!");
+ }
+ }
+
@Override
public WxPayRefundResult refund(WxPayRefundRequest request) throws WxPayException {
request.checkAndSign(this.getConfig());
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceApacheHttpImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceApacheHttpImpl.java
index 12b85670f9..5926bbacfb 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceApacheHttpImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceApacheHttpImpl.java
@@ -128,6 +128,7 @@ public String postV3(String url, String requestStr) throws WxPayException {
}
private String requestV3(String url, String requestStr, HttpRequestBase httpRequestBase) throws WxPayException {
+ this.checkV3SandboxNotSupported();
CloseableHttpClient httpClient = this.createApiV3HttpClient();
try (CloseableHttpResponse response = httpClient.execute(httpRequestBase)) {
//v3已经改为通过状态码判断200 204 成功
@@ -163,6 +164,7 @@ public String patchV3(String url, String requestStr) throws WxPayException {
@Override
public String postV3WithWechatpaySerial(String url, String requestStr) throws WxPayException {
+ this.checkV3SandboxNotSupported();
HttpPost httpPost = this.createHttpPost(url, requestStr);
this.configureRequest(httpPost);
CloseableHttpClient httpClient = this.createApiV3HttpClient();
@@ -199,6 +201,7 @@ public String postV3(String url, HttpPost httpPost) throws WxPayException {
@Override
public String requestV3(String url, HttpRequestBase httpRequest) throws WxPayException {
+ this.checkV3SandboxNotSupported();
this.configureRequest(httpRequest);
CloseableHttpClient httpClient = this.createApiV3HttpClient();
try (CloseableHttpResponse response = httpClient.execute(httpRequest)) {
@@ -243,6 +246,7 @@ public String getV3WithWechatPaySerial(String url) throws WxPayException {
@Override
public InputStream downloadV3(String url) throws WxPayException {
+ this.checkV3SandboxNotSupported();
HttpGet httpGet = new WxPayV3DownloadHttpGet(url);
this.configureRequest(httpGet);
CloseableHttpClient httpClient = this.createApiV3HttpClient();
@@ -285,6 +289,7 @@ public String deleteV3(String url) throws WxPayException {
}
private void configureRequest(HttpRequestBase request) {
+ this.checkV3SandboxNotSupported();
String serialNumber = getWechatPaySerial(getConfig());
String method = request.getMethod();
request.addHeader(ACCEPT, APPLICATION_JSON);
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceHttpComponentsImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceHttpComponentsImpl.java
index cc5423302b..837587e76d 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceHttpComponentsImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceHttpComponentsImpl.java
@@ -125,6 +125,7 @@ public String postV3(String url, String requestStr) throws WxPayException {
}
private String requestV3(String url, String requestStr, HttpRequestBase httpRequestBase) throws WxPayException {
+ this.checkV3SandboxNotSupported();
CloseableHttpClient httpClient = this.createApiV3HttpClient();
try (CloseableHttpResponse response = httpClient.execute(httpRequestBase)) {
//v3已经改为通过状态码判断200 204 成功
@@ -160,6 +161,7 @@ public String patchV3(String url, String requestStr) throws WxPayException {
@Override
public String postV3WithWechatpaySerial(String url, String requestStr) throws WxPayException {
+ this.checkV3SandboxNotSupported();
HttpPost httpPost = this.createHttpPost(url, requestStr);
this.configureRequest(httpPost);
CloseableHttpClient httpClient = this.createApiV3HttpClient();
@@ -196,6 +198,7 @@ public String postV3(String url, HttpPost httpPost) throws WxPayException {
@Override
public String requestV3(String url, HttpRequestBase httpRequest) throws WxPayException {
+ this.checkV3SandboxNotSupported();
this.configureRequest(httpRequest);
CloseableHttpClient httpClient = this.createApiV3HttpClient();
try (CloseableHttpResponse response = httpClient.execute(httpRequest)) {
@@ -240,6 +243,7 @@ public String getV3WithWechatPaySerial(String url) throws WxPayException {
@Override
public InputStream downloadV3(String url) throws WxPayException {
+ this.checkV3SandboxNotSupported();
HttpGet httpGet = new WxPayV3DownloadHttpGet(url);
this.configureRequest(httpGet);
CloseableHttpClient httpClient = this.createApiV3HttpClient();
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/WxPayServiceSandboxTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/WxPayServiceSandboxTest.java
new file mode 100644
index 0000000000..2daa9845a7
--- /dev/null
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/WxPayServiceSandboxTest.java
@@ -0,0 +1,53 @@
+package com.github.binarywang.wxpay.service.impl;
+
+import com.github.binarywang.wxpay.config.WxPayConfig;
+import me.chanjar.weixin.common.error.WxRuntimeException;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.expectThrows;
+
+public class WxPayServiceSandboxTest {
+
+ @Test
+ public void shouldUseV2SandboxUrlWhenV3KeyIsConfigured() {
+ WxPayConfig config = new WxPayConfig();
+ config.setApiHostUrl("https://api.mch.weixin.qq.com");
+ config.setApiHostUrlPath("/payment-proxy");
+ config.setApiV3Key("v3-key");
+ config.setUseSandboxEnv(true);
+
+ WxPayServiceImpl service = new WxPayServiceImpl();
+ service.setConfig(config);
+
+ assertEquals(service.getPayBaseUrl(), "https://api.mch.weixin.qq.com/payment-proxy/xdc/apiv2sandbox");
+ }
+
+ @Test
+ public void shouldRejectV3RequestWhenSandboxIsEnabled() {
+ WxPayConfig config = new WxPayConfig();
+ config.setUseSandboxEnv(true);
+
+ WxPayServiceImpl service = new WxPayServiceImpl();
+ service.setConfig(config);
+
+ WxRuntimeException exception = expectThrows(WxRuntimeException.class,
+ () -> service.postV3("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi", "{}"));
+
+ assertEquals(exception.getMessage(), "微信支付V3 目前不支持沙箱模式!");
+ }
+
+ @Test
+ public void shouldRejectHttpComponentsV3RequestWhenSandboxIsEnabled() {
+ WxPayConfig config = new WxPayConfig();
+ config.setUseSandboxEnv(true);
+
+ WxPayServiceHttpComponentsImpl service = new WxPayServiceHttpComponentsImpl();
+ service.setConfig(config);
+
+ WxRuntimeException exception = expectThrows(WxRuntimeException.class,
+ () -> service.postV3("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi", "{}"));
+
+ assertEquals(exception.getMessage(), "微信支付V3 目前不支持沙箱模式!");
+ }
+}