templateList;
+
+ /**
+ * From json wx cp tp template list.
+ *
+ * @param json the json
+ * @return the wx cp tp template list
+ */
+ public static WxCpTpTemplateList fromJson(String json) {
+ return WxCpGsonBuilder.create().fromJson(json, WxCpTpTemplateList.class);
+ }
+
+ @Override
+ public String toJson() {
+ return WxCpGsonBuilder.create().toJson(this);
+ }
+
+ /**
+ * 应用模板信息
+ */
+ @Data
+ public static class Template implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 模板id
+ */
+ @SerializedName("template_id")
+ private String templateId;
+
+ /**
+ * 模板类型
+ */
+ @SerializedName("template_type")
+ private Integer templateType;
+
+ /**
+ * 应用名称
+ */
+ @SerializedName("app_name")
+ private String appName;
+
+ /**
+ * 应用logo url
+ */
+ @SerializedName("logo_url")
+ private String logoUrl;
+
+ /**
+ * 应用简介
+ */
+ @SerializedName("app_desc")
+ private String appDesc;
+
+ /**
+ * 应用状态
+ */
+ @SerializedName("status")
+ private Integer status;
+ }
+}
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 82bb811b3..6e2ee866a 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
@@ -920,6 +920,15 @@ interface Tp {
*/
String GET_CUSTOMIZED_AUTH_URL = "/cgi-bin/service/get_customized_auth_url";
+ /**
+ * The constant GET_TEMPLATE_LIST.
+ */
+ String GET_TEMPLATE_LIST = "/cgi-bin/service/get_template_list";
+
+ /**
+ * The constant GET_CUSTOMIZED_APP_DETAIL.
+ */
+ String GET_CUSTOMIZED_APP_DETAIL = "/cgi-bin/service/get_customized_app_detail";
/**
* The constant CONTACT_SEARCH.
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpCustomizedService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpCustomizedService.java
new file mode 100644
index 000000000..99e284885
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpCustomizedService.java
@@ -0,0 +1,41 @@
+package me.chanjar.weixin.cp.tp.service;
+
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.cp.bean.WxCpTpCustomizedAppDetail;
+import me.chanjar.weixin.cp.bean.WxCpTpTemplateList;
+
+/**
+ * 企业微信第三方应用 - 代开发相关接口.
+ *
+ * @author Binary Wang
+ * created on 2026-01-14
+ */
+public interface WxCpTpCustomizedService {
+
+ /**
+ * 获取应用模板列表
+ *
+ * 服务商可通过本接口获取服务商所拥有的应用模板列表
+ * 文档地址:https://developer.work.weixin.qq.com/document/path/97111
+ *
+ *
+ * @return 应用模板列表
+ * @throws WxErrorException 微信错误异常
+ */
+ WxCpTpTemplateList getTemplateList() throws WxErrorException;
+
+ /**
+ * 获取代开发应用详情
+ *
+ * 服务商可通过本接口获取某个授权企业中已经安装的代开发自建应用的详情
+ * 文档地址:https://developer.work.weixin.qq.com/document/path/97111
+ *
+ *
+ * @param authCorpId 授权企业的corpid
+ * @param agentId 应用的agentid,为空时则返回该企业所有的代开发自建应用详情
+ * @return 代开发应用详情
+ * @throws WxErrorException 微信错误异常
+ */
+ WxCpTpCustomizedAppDetail getCustomizedAppDetail(String authCorpId, Integer agentId) throws WxErrorException;
+
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpService.java
index 93855d1a4..92966c1d0 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpService.java
@@ -662,4 +662,18 @@ WxCpTpXmlMessage fromEncryptedXml(String encryptedXml,
void setWxCpTpOAuth2Service(WxCpTpOAuth2Service wxCpTpOAuth2Service);
+ /**
+ * 获取代开发服务
+ *
+ * @return 代开发服务
+ */
+ WxCpTpCustomizedService getWxCpTpCustomizedService();
+
+ /**
+ * 设置代开发服务
+ *
+ * @param wxCpTpCustomizedService 代开发服务
+ */
+ void setWxCpTpCustomizedService(WxCpTpCustomizedService wxCpTpCustomizedService);
+
}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/BaseWxCpTpServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/BaseWxCpTpServiceImpl.java
index f8f554b81..25c1470eb 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/BaseWxCpTpServiceImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/BaseWxCpTpServiceImpl.java
@@ -60,6 +60,7 @@ public abstract class BaseWxCpTpServiceImpl implements WxCpTpService, Requ
private WxCpTpLicenseService wxCpTpLicenseService = new WxCpTpLicenseServiceImpl(this);
private WxCpTpIdConvertService wxCpTpIdConvertService = new WxCpTpIdConvertServiceImpl(this);
private WxCpTpOAuth2Service wxCpTpOAuth2Service = new WxCpTpOAuth2ServiceImpl(this);
+ private WxCpTpCustomizedService wxCpTpCustomizedService = new WxCpTpCustomizedServiceImpl(this);
/**
* 全局的是否正在刷新access token的锁.
*/
@@ -809,6 +810,16 @@ public void setWxCpTpOAuth2Service(WxCpTpOAuth2Service wxCpTpOAuth2Service) {
this.wxCpTpOAuth2Service = wxCpTpOAuth2Service;
}
+ @Override
+ public WxCpTpCustomizedService getWxCpTpCustomizedService() {
+ return wxCpTpCustomizedService;
+ }
+
+ @Override
+ public void setWxCpTpCustomizedService(WxCpTpCustomizedService wxCpTpCustomizedService) {
+ this.wxCpTpCustomizedService = wxCpTpCustomizedService;
+ }
+
@Override
public WxCpTpConfigStorage getWxCpTpConfigStorage() {
return this.configStorage;
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpCustomizedServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpCustomizedServiceImpl.java
new file mode 100644
index 000000000..54ea0fc4b
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpCustomizedServiceImpl.java
@@ -0,0 +1,64 @@
+package me.chanjar.weixin.cp.tp.service.impl;
+
+import com.google.gson.JsonObject;
+import lombok.RequiredArgsConstructor;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.cp.bean.WxCpTpCustomizedAppDetail;
+import me.chanjar.weixin.cp.bean.WxCpTpTemplateList;
+import me.chanjar.weixin.cp.config.WxCpTpConfigStorage;
+import me.chanjar.weixin.cp.tp.service.WxCpTpCustomizedService;
+import me.chanjar.weixin.cp.tp.service.WxCpTpService;
+
+import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Tp.GET_CUSTOMIZED_APP_DETAIL;
+import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Tp.GET_TEMPLATE_LIST;
+
+/**
+ * 企业微信第三方应用 - 代开发相关接口实现.
+ *
+ * @author Binary Wang
+ * created on 2026-01-14
+ */
+@RequiredArgsConstructor
+public class WxCpTpCustomizedServiceImpl implements WxCpTpCustomizedService {
+
+ private final WxCpTpService mainService;
+
+ @Override
+ public WxCpTpTemplateList getTemplateList() throws WxErrorException {
+ String responseText = this.mainService.get(getWxCpTpConfigStorage().getApiUrl(GET_TEMPLATE_LIST)
+ + getProviderAccessToken(), null, true);
+ return WxCpTpTemplateList.fromJson(responseText);
+ }
+
+ @Override
+ public WxCpTpCustomizedAppDetail getCustomizedAppDetail(String authCorpId, Integer agentId) throws WxErrorException {
+ JsonObject jsonObject = new JsonObject();
+ jsonObject.addProperty("auth_corpid", authCorpId);
+ if (agentId != null) {
+ jsonObject.addProperty("agentid", agentId);
+ }
+ String responseText = this.mainService.post(getWxCpTpConfigStorage().getApiUrl(GET_CUSTOMIZED_APP_DETAIL)
+ + getProviderAccessToken(), jsonObject.toString(), true);
+ return WxCpTpCustomizedAppDetail.fromJson(responseText);
+ }
+
+ /**
+ * 获取provider_access_token参数
+ *
+ * @return provider_access_token参数
+ * @throws WxErrorException 微信错误异常
+ */
+ private String getProviderAccessToken() throws WxErrorException {
+ return "?provider_access_token=" + mainService.getWxCpProviderToken();
+ }
+
+ /**
+ * 获取tp参数配置
+ *
+ * @return config
+ */
+ private WxCpTpConfigStorage getWxCpTpConfigStorage() {
+ return mainService.getWxCpTpConfigStorage();
+ }
+
+}
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpCustomizedServiceImplTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpCustomizedServiceImplTest.java
new file mode 100644
index 000000000..b3c9eda6c
--- /dev/null
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpCustomizedServiceImplTest.java
@@ -0,0 +1,178 @@
+package me.chanjar.weixin.cp.tp.service.impl;
+
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.cp.bean.WxCpTpCustomizedAppDetail;
+import me.chanjar.weixin.cp.bean.WxCpTpTemplateList;
+import me.chanjar.weixin.cp.config.WxCpTpConfigStorage;
+import me.chanjar.weixin.cp.config.impl.WxCpTpDefaultConfigImpl;
+import me.chanjar.weixin.cp.tp.service.WxCpTpCustomizedService;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Tp.GET_CUSTOMIZED_APP_DETAIL;
+import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Tp.GET_TEMPLATE_LIST;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
+/**
+ * 代开发相关接口测试
+ *
+ * @author Binary Wang
+ * created on 2026-01-14
+ */
+public class WxCpTpCustomizedServiceImplTest {
+
+ @Mock
+ private WxCpTpServiceApacheHttpClientImpl wxCpTpService;
+
+ private WxCpTpConfigStorage configStorage;
+
+ private WxCpTpCustomizedService wxCpTpCustomizedService;
+
+ /**
+ * Sets up.
+ */
+ @BeforeClass
+ public void setUp() {
+ MockitoAnnotations.openMocks(this);
+ configStorage = new WxCpTpDefaultConfigImpl();
+ when(wxCpTpService.getWxCpTpConfigStorage()).thenReturn(configStorage);
+ wxCpTpCustomizedService = new WxCpTpCustomizedServiceImpl(wxCpTpService);
+ }
+
+ /**
+ * 测试获取应用模板列表
+ *
+ * @throws WxErrorException the wx error exception
+ */
+ @Test
+ public void testGetTemplateList() throws WxErrorException {
+ String result = "{\n" +
+ " \"errcode\": 0,\n" +
+ " \"errmsg\": \"ok\",\n" +
+ " \"template_list\": [\n" +
+ " {\n" +
+ " \"template_id\": \"tpl001\",\n" +
+ " \"template_type\": 1,\n" +
+ " \"app_name\": \"测试应用\",\n" +
+ " \"logo_url\": \"https://example.com/logo.png\",\n" +
+ " \"app_desc\": \"这是一个测试应用\",\n" +
+ " \"status\": 1\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ String url = configStorage.getApiUrl(GET_TEMPLATE_LIST);
+ when(wxCpTpService.getWxCpProviderToken()).thenReturn("mock_provider_token");
+ when(wxCpTpService.get(eq(url + "?provider_access_token=mock_provider_token"), eq(null), eq(true)))
+ .thenReturn(result);
+
+ final WxCpTpTemplateList templateList = wxCpTpCustomizedService.getTemplateList();
+
+ assertNotNull(templateList);
+ assertEquals(templateList.getErrcode(), Long.valueOf(0));
+ assertNotNull(templateList.getTemplateList());
+ assertEquals(templateList.getTemplateList().size(), 1);
+ assertEquals(templateList.getTemplateList().get(0).getTemplateId(), "tpl001");
+ assertEquals(templateList.getTemplateList().get(0).getAppName(), "测试应用");
+ }
+
+ /**
+ * 测试获取代开发应用详情
+ *
+ * @throws WxErrorException the wx error exception
+ */
+ @Test
+ public void testGetCustomizedAppDetail() throws WxErrorException {
+ String authCorpId = "ww1234567890abcdef";
+ Integer agentId = 1000001;
+
+ String result = "{\n" +
+ " \"errcode\": 0,\n" +
+ " \"errmsg\": \"ok\",\n" +
+ " \"auth_corpid\": \"ww1234567890abcdef\",\n" +
+ " \"auth_corp_name\": \"测试企业\",\n" +
+ " \"auth_corp_square_logo_url\": \"https://example.com/square_logo.png\",\n" +
+ " \"auth_corp_round_logo_url\": \"https://example.com/round_logo.png\",\n" +
+ " \"auth_corp_type\": 1,\n" +
+ " \"auth_corp_qrcode_url\": \"https://example.com/qrcode.png\",\n" +
+ " \"auth_corp_user_limit\": 200,\n" +
+ " \"auth_corp_full_name\": \"测试企业有限公司\",\n" +
+ " \"auth_corp_verified_type\": 2,\n" +
+ " \"auth_corp_industry\": \"互联网\",\n" +
+ " \"auth_corp_sub_industry\": \"软件服务\",\n" +
+ " \"auth_corp_location\": \"广东省深圳市\",\n" +
+ " \"customized_app_list\": [\n" +
+ " {\n" +
+ " \"agentid\": 1000001,\n" +
+ " \"template_id\": \"tpl001\",\n" +
+ " \"name\": \"测试应用\",\n" +
+ " \"description\": \"这是一个测试应用\",\n" +
+ " \"logo_url\": \"https://example.com/logo.png\",\n" +
+ " \"allow_userinfos\": {\n" +
+ " \"user\": [\n" +
+ " {\"userid\": \"zhangsan\"}\n" +
+ " ],\n" +
+ " \"department\": [\n" +
+ " {\"id\": 1}\n" +
+ " ]\n" +
+ " },\n" +
+ " \"close\": 0,\n" +
+ " \"home_url\": \"https://example.com/home\",\n" +
+ " \"app_type\": 0\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ String url = configStorage.getApiUrl(GET_CUSTOMIZED_APP_DETAIL);
+ when(wxCpTpService.getWxCpProviderToken()).thenReturn("mock_provider_token");
+ when(wxCpTpService.post(eq(url + "?provider_access_token=mock_provider_token"), any(String.class), eq(true)))
+ .thenReturn(result);
+
+ final WxCpTpCustomizedAppDetail appDetail = wxCpTpCustomizedService.getCustomizedAppDetail(authCorpId, agentId);
+
+ assertNotNull(appDetail);
+ assertEquals(appDetail.getErrcode(), Long.valueOf(0));
+ assertEquals(appDetail.getAuthCorpId(), authCorpId);
+ assertEquals(appDetail.getAuthCorpName(), "测试企业");
+ assertNotNull(appDetail.getCustomizedAppList());
+ assertEquals(appDetail.getCustomizedAppList().size(), 1);
+ assertEquals(appDetail.getCustomizedAppList().get(0).getAgentId(), agentId);
+ assertEquals(appDetail.getCustomizedAppList().get(0).getTemplateId(), "tpl001");
+ assertEquals(appDetail.getCustomizedAppList().get(0).getName(), "测试应用");
+ }
+
+ /**
+ * 测试获取代开发应用详情(不指定agentId)
+ *
+ * @throws WxErrorException the wx error exception
+ */
+ @Test
+ public void testGetCustomizedAppDetailWithoutAgentId() throws WxErrorException {
+ String authCorpId = "ww1234567890abcdef";
+
+ String result = "{\n" +
+ " \"errcode\": 0,\n" +
+ " \"errmsg\": \"ok\",\n" +
+ " \"auth_corpid\": \"ww1234567890abcdef\",\n" +
+ " \"auth_corp_name\": \"测试企业\",\n" +
+ " \"customized_app_list\": []\n" +
+ "}";
+
+ String url = configStorage.getApiUrl(GET_CUSTOMIZED_APP_DETAIL);
+ when(wxCpTpService.getWxCpProviderToken()).thenReturn("mock_provider_token");
+ when(wxCpTpService.post(eq(url + "?provider_access_token=mock_provider_token"), any(String.class), eq(true)))
+ .thenReturn(result);
+
+ final WxCpTpCustomizedAppDetail appDetail = wxCpTpCustomizedService.getCustomizedAppDetail(authCorpId, null);
+
+ assertNotNull(appDetail);
+ assertEquals(appDetail.getErrcode(), Long.valueOf(0));
+ assertEquals(appDetail.getAuthCorpId(), authCorpId);
+ }
+}