From 3963c6d471c339dbf52f325478eeebd1b4d14539 Mon Sep 17 00:00:00 2001 From: pg Date: Tue, 22 Jun 2021 10:57:32 +0800 Subject: [PATCH] =?UTF-8?q?:new:=20#2150=20=E3=80=90=E4=BC=81=E4=B8=9A?= =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E3=80=91=E8=A1=A5=E5=85=85=E5=AE=8C=E5=96=84?= =?UTF-8?q?=E9=83=A8=E5=88=86=E5=AE=A2=E6=88=B7=E8=81=94=E7=B3=BB=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=EF=BC=8C=E4=BB=A5=E5=8F=8A=E6=9C=8D=E5=8A=A1=E5=95=86?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=E5=A4=96=E9=83=A8=E8=81=94=E7=B3=BB=E4=BA=BA?= =?UTF-8?q?openid=E8=BD=AC=E6=8D=A2=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cp/api/WxCpExternalContactService.java | 113 ++++++++++++++++++ .../impl/WxCpExternalContactServiceImpl.java | 63 ++++++++++ ...WxCpUserExternalGroupChatTransferResp.java | 51 ++++++++ .../external/WxCpUserTransferCustomerReq.java | 49 ++++++++ .../WxCpUserTransferCustomerResp.java | 60 ++++++++++ .../external/WxCpUserTransferResultResp.java | 92 ++++++++++++++ .../weixin/cp/constant/WxCpApiPathConsts.java | 7 ++ .../WxCpExternalContactServiceImplTest.java | 52 +++++++- 8 files changed, 486 insertions(+), 1 deletion(-) create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpUserExternalGroupChatTransferResp.java create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpUserTransferCustomerReq.java create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpUserTransferCustomerResp.java create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpUserTransferResultResp.java diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExternalContactService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExternalContactService.java index 231e0bfa3e..cd65ad3771 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExternalContactService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExternalContactService.java @@ -6,6 +6,7 @@ import me.chanjar.weixin.cp.bean.external.*; import me.chanjar.weixin.cp.bean.external.contact.WxCpExternalContactBatchInfo; import me.chanjar.weixin.cp.bean.external.contact.WxCpExternalContactInfo; +import org.jetbrains.annotations.NotNull; import java.util.Date; import java.util.List; @@ -134,6 +135,14 @@ public interface WxCpExternalContactService { */ WxCpExternalContactInfo getContactDetail(String userId) throws WxErrorException; + /** + * 企业和服务商可通过此接口,将微信外部联系人的userid转为微信openid,用于调用支付相关接口。暂不支持企业微信外部联系人(ExternalUserid为wo开头)的userid转openid。 + * @param externalUserid 微信外部联系人的userid + * @return 该企业的外部联系人openid + * @throws WxErrorException . + */ + String convertToOpenid(String externalUserid) throws WxErrorException; + /** * 批量获取客户详情. *
@@ -225,9 +234,85 @@ WxCpExternalContactBatchInfo getContactDetailBatch(String userId, String cursor,
    * @param takeOverUserid the take over userid
    * @return wx cp base resp
    * @throws WxErrorException the wx error exception
+   * @deprecated 此后续将不再更新维护,建议使用 {@link #transferCustomer(WxCpUserTransferCustomerReq)}
    */
+  @Deprecated
   WxCpBaseResp transferExternalContact(String externalUserid, String handOverUserid, String takeOverUserid) throws WxErrorException;
 
+  /**
+   * 企业可通过此接口,转接在职成员的客户给其他成员。
+   *  
+   * external_userid必须是handover_userid的客户(即配置了客户联系功能的成员所添加的联系人)。
+   * 在职成员的每位客户最多被分配2次。客户被转接成功后,将有90个自然日的服务关系保护期,保护期内的客户无法再次被分配。
+   *
+   * 权限说明:
+   *   * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
+   * 第三方应用需拥有“企业客户权限->客户联系->在职继承”权限
+   * 接替成员必须在此第三方应用或自建应用的可见范围内。
+   * 接替成员需要配置了客户联系功能。
+   * 接替成员需要在企业微信激活且已经过实名认证。
+   *  
+   * @param req 转接在职成员的客户给其他成员请求实体
+   * @return wx cp base resp
+   * @throws WxErrorException the wx error exception
+   */
+  WxCpUserTransferCustomerResp transferCustomer(WxCpUserTransferCustomerReq req) throws WxErrorException;
+
+  /**
+   * 企业和第三方可通过此接口查询在职成员的客户转接情况。
+   * 
+   *   权限说明:
+   *
+   * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
+   * 第三方应用需拥有“企业客户权限->客户联系->在职继承”权限
+   * 接替成员必须在此第三方应用或自建应用的可见范围内。
+   * 
+   * @param handOverUserid 原添加成员的userid
+   * @param takeOverUserid 接替成员的userid
+   * @param cursor 分页查询的cursor,每个分页返回的数据不会超过1000条;不填或为空表示获取第一个分页;
+   * @return 客户转接接口实体
+   * @throws WxErrorException the wx error exception
+   */
+  WxCpUserTransferResultResp transferResult(@NotNull String handOverUserid, @NotNull String takeOverUserid, String cursor)  throws WxErrorException;
+
+  /**
+   * 企业可通过此接口,分配离职成员的客户给其他成员。
+   *  
+   * handover_userid必须是已离职用户。
+   * external_userid必须是handover_userid的客户(即配置了客户联系功能的成员所添加的联系人)。
+   * 在职成员的每位客户最多被分配2次。客户被转接成功后,将有90个自然日的服务关系保护期,保护期内的客户无法再次被分配。
+   *
+   * 权限说明:
+   *
+   * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
+   * 第三方应用需拥有“企业客户权限->客户联系->离职分配”权限
+   * 接替成员必须在此第三方应用或自建应用的可见范围内。
+   * 接替成员需要配置了客户联系功能。
+   * 接替成员需要在企业微信激活且已经过实名认证。
+   *  
+   * @param req 转接在职成员的客户给其他成员请求实体
+   * @return wx cp base resp
+   * @throws WxErrorException the wx error exception
+   */
+  WxCpUserTransferCustomerResp resignedTransferCustomer(WxCpUserTransferCustomerReq req) throws WxErrorException;
+
+  /**
+   * 企业和第三方可通过此接口查询离职成员的客户分配情况。
+   * 
+   * 权限说明:
+   *
+   * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
+   * 第三方应用需拥有“企业客户权限->客户联系->在职继承”权限
+   * 接替成员必须在此第三方应用或自建应用的可见范围内。
+   * 
+   * @param handOverUserid 原添加成员的userid
+   * @param takeOverUserid 接替成员的userid
+   * @param cursor 分页查询的cursor,每个分页返回的数据不会超过1000条;不填或为空表示获取第一个分页;
+   * @return 客户转接接口实体
+   * @throws WxErrorException the wx error exception
+   */
+  WxCpUserTransferResultResp resignedTransferResult(@NotNull String handOverUserid, @NotNull String takeOverUserid, String cursor)  throws WxErrorException;
+
   /**
    * 
    * 该接口用于获取配置过客户群管理的客户群列表。
@@ -260,6 +345,32 @@ WxCpExternalContactBatchInfo getContactDetailBatch(String userId, String cursor,
    */
   WxCpUserExternalGroupChatInfo getGroupChat(String chatId) throws WxErrorException;
 
+  /**
+   *
+   * 企业可通过此接口,将已离职成员为群主的群,分配给另一个客服成员。
+   *
+   * 
+   * 注意::
+   *
+   * 群主离职了的客户群,才可继承
+   * 继承给的新群主,必须是配置了客户联系功能的成员
+   * 继承给的新群主,必须有设置实名
+   * 继承给的新群主,必须有激活企业微信
+   * 同一个人的群,限制每天最多分配300个给新群主
+   *
+   * 权限说明:
+   *
+   * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
+   * 第三方应用需拥有“企业客户权限->客户联系->分配离职成员的客户群”权限
+   * 对于第三方/自建应用,群主必须在应用的可见范围。
+   * 
+   * @param chatIds 需要转群主的客户群ID列表。取值范围: 1 ~ 100
+   * @param newOwner  新群主ID
+   * @return 分配结果,主要是分配失败的群列表
+   * @throws WxErrorException  the wx error exception
+   */
+  WxCpUserExternalGroupChatTransferResp transferGroupChat(String[] chatIds, String newOwner)  throws WxErrorException;
+
   /**
    * 
    * 企业可通过此接口获取成员联系客户的数据,包括发起申请数、新增客户数、聊天数、发送消息数和删除/拉黑成员的客户数等指标。
@@ -397,4 +508,6 @@ WxCpExternalContactBatchInfo getContactDetailBatch(String userId, String cursor,
    * @throws WxErrorException the wx error exception
    */
   WxCpBaseResp markTag(String userid, String externalUserid, String[] addTag, String[] removeTag) throws WxErrorException;
+
+
 }
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImpl.java
index 19e7cdfe79..8065d21980 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImpl.java
@@ -7,6 +7,8 @@
 import me.chanjar.weixin.common.error.WxCpErrorMsgEnum;
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.common.error.WxRuntimeException;
+import me.chanjar.weixin.common.util.BeanUtils;
+import me.chanjar.weixin.common.util.json.GsonParser;
 import me.chanjar.weixin.cp.api.WxCpExternalContactService;
 import me.chanjar.weixin.cp.api.WxCpService;
 import me.chanjar.weixin.cp.bean.WxCpBaseResp;
@@ -15,6 +17,7 @@
 import me.chanjar.weixin.cp.bean.external.contact.WxCpExternalContactInfo;
 import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.jetbrains.annotations.NotNull;
 
 import java.util.Collections;
 import java.util.Date;
@@ -106,6 +109,16 @@ public WxCpExternalContactInfo getContactDetail(String userId) throws WxErrorExc
     return WxCpExternalContactInfo.fromJson(responseContent);
   }
 
+  @Override
+  public String convertToOpenid(@NotNull String externalUserId) throws WxErrorException {
+    JsonObject json = new JsonObject();
+    json.addProperty("external_userid", externalUserId);
+    final String url = this.mainService.getWxCpConfigStorage().getApiUrl(CONVERT_TO_OPENID);
+    String responseContent = this.mainService.post(url, json.toString());
+    JsonObject tmpJson = GsonParser.parse(responseContent);
+    return tmpJson.get("openid").getAsString();
+  }
+
   @Override
   public WxCpExternalContactBatchInfo getContactDetailBatch(String userId,
                                                             String cursor,
@@ -176,6 +189,44 @@ public WxCpBaseResp transferExternalContact(String externalUserid, String handOv
     return WxCpBaseResp.fromJson(result);
   }
 
+  @Override
+  public WxCpUserTransferCustomerResp transferCustomer(WxCpUserTransferCustomerReq req) throws WxErrorException {
+    BeanUtils.checkRequiredFields(req);
+    final String url = this.mainService.getWxCpConfigStorage().getApiUrl(TRANSFER_CUSTOMER);
+    final String result = this.mainService.post(url, req.toJson());
+    return WxCpUserTransferCustomerResp.fromJson(result);
+  }
+
+  @Override
+  public WxCpUserTransferResultResp transferResult(@NotNull String handOverUserid, @NotNull String takeOverUserid, String cursor) throws WxErrorException {
+    JsonObject json = new JsonObject();
+    json.addProperty("cursor", cursor);
+    json.addProperty("handover_userid", handOverUserid);
+    json.addProperty("takeover_userid", takeOverUserid);
+    final String url = this.mainService.getWxCpConfigStorage().getApiUrl(TRANSFER_RESULT);
+    final String result = this.mainService.post(url, json.toString());
+    return WxCpUserTransferResultResp.fromJson(result);
+  }
+
+  @Override
+  public WxCpUserTransferCustomerResp resignedTransferCustomer(WxCpUserTransferCustomerReq req) throws WxErrorException {
+    BeanUtils.checkRequiredFields(req);
+    final String url = this.mainService.getWxCpConfigStorage().getApiUrl(RESIGNED_TRANSFER_CUSTOMER);
+    final String result = this.mainService.post(url, req.toJson());
+    return WxCpUserTransferCustomerResp.fromJson(result);
+  }
+
+  @Override
+  public WxCpUserTransferResultResp resignedTransferResult(@NotNull String handOverUserid, @NotNull String takeOverUserid, String cursor) throws WxErrorException {
+    JsonObject json = new JsonObject();
+    json.addProperty("cursor", cursor);
+    json.addProperty("handover_userid", handOverUserid);
+    json.addProperty("takeover_userid", takeOverUserid);
+    final String url = this.mainService.getWxCpConfigStorage().getApiUrl(RESIGNED_TRANSFER_RESULT);
+    final String result = this.mainService.post(url, json.toString());
+    return WxCpUserTransferResultResp.fromJson(result);
+  }
+
   @Override
   public WxCpUserExternalGroupChatList listGroupChat(Integer pageIndex, Integer pageSize, int status, String[] userIds, String[] partyIds) throws WxErrorException {
     JsonObject json = new JsonObject();
@@ -206,6 +257,18 @@ public WxCpUserExternalGroupChatInfo getGroupChat(String chatId) throws WxErrorE
     return WxCpUserExternalGroupChatInfo.fromJson(result);
   }
 
+  @Override
+  public WxCpUserExternalGroupChatTransferResp transferGroupChat(String[] chatIds, String newOwner) throws WxErrorException {
+    JsonObject json = new JsonObject();
+    if (ArrayUtils.isNotEmpty(chatIds)) {
+      json.add("chat_id_list", new Gson().toJsonTree(chatIds).getAsJsonArray());
+    }
+    json.addProperty("new_owner", newOwner);
+    final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GROUP_CHAT_TRANSFER);
+    final String result = this.mainService.post(url, json.toString());
+    return WxCpUserExternalGroupChatTransferResp.fromJson(result);
+  }
+
   @Override
   public WxCpUserExternalUserBehaviorStatistic getUserBehaviorStatistic(Date startTime, Date endTime, String[] userIds, String[] partyIds) throws WxErrorException {
     JsonObject json = new JsonObject();
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpUserExternalGroupChatTransferResp.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpUserExternalGroupChatTransferResp.java
new file mode 100644
index 0000000000..ff6fb82374
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpUserExternalGroupChatTransferResp.java
@@ -0,0 +1,51 @@
+package me.chanjar.weixin.cp.bean.external;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Getter;
+import lombok.Setter;
+import me.chanjar.weixin.cp.bean.WxCpBaseResp;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+import java.util.List;
+
+/**
+ * 分配离职成员的客户群结果
+ * @author pg
+ * @date 2021年6月21日
+ */
+@Getter
+@Setter
+public class WxCpUserExternalGroupChatTransferResp extends WxCpBaseResp {
+  private static final long serialVersionUID = -943124579487821819L;
+  /**
+   * 没有成功继承的群列表
+   */
+  @SerializedName("failed_chat_list")
+  private List failedChatList;
+
+  public static WxCpUserExternalGroupChatTransferResp fromJson(String json) {
+    return WxCpGsonBuilder.create().fromJson(json, WxCpUserExternalGroupChatTransferResp.class);
+  }
+
+  public String toJson() {
+    return WxCpGsonBuilder.create().toJson(this);
+  }
+
+  @Getter
+  @Setter
+  public static class GroupChatFailedTransfer extends WxCpBaseResp  {
+    private static final long serialVersionUID = -5836775099634587239L;
+    /**
+     * 没能成功继承的群ID
+     */
+    private String chatId;
+
+    public static WxCpUserExternalGroupChatTransferResp.GroupChatFailedTransfer fromJson(String json) {
+      return WxCpGsonBuilder.create().fromJson(json, WxCpUserExternalGroupChatTransferResp.GroupChatFailedTransfer.class);
+    }
+
+    public String toJson() {
+      return WxCpGsonBuilder.create().toJson(this);
+    }
+  }
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpUserTransferCustomerReq.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpUserTransferCustomerReq.java
new file mode 100644
index 0000000000..e8b8142cc6
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpUserTransferCustomerReq.java
@@ -0,0 +1,49 @@
+package me.chanjar.weixin.cp.bean.external;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Getter;
+import lombok.Setter;
+import me.chanjar.weixin.common.annotation.Required;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 转接在职成员的客户给其他成员,请求对象
+ *
+ * @author pg
+ * @date 2021年6月21日
+ */
+@Getter
+@Setter
+public class WxCpUserTransferCustomerReq implements Serializable {
+  private static final long serialVersionUID = -309819538677411801L;
+  /**
+   * 原跟进成员的userid
+   */
+  @SerializedName("handover_userid")
+  @Required
+  private String handOverUserid;
+  /**
+   * 接替成员的userid
+   */
+  @SerializedName("takeover_userid")
+  @Required
+  private String takeOverUserid;
+  /**
+   * 客户的external_userid列表,每次最多分配100个客户
+   */
+  @SerializedName("external_userid")
+  @Required
+  private List externalUserid;
+  /**
+   * 转移成功后发给客户的消息,最多200个字符,不填则使用默认文案
+   */
+  @SerializedName("transfer_success_msg")
+  private String transferMsg;
+
+  public String toJson() {
+    return WxCpGsonBuilder.create().toJson(this);
+  }
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpUserTransferCustomerResp.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpUserTransferCustomerResp.java
new file mode 100644
index 0000000000..27d1c0ad4c
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpUserTransferCustomerResp.java
@@ -0,0 +1,60 @@
+package me.chanjar.weixin.cp.bean.external;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Getter;
+import lombok.Setter;
+import me.chanjar.weixin.cp.bean.WxCpBaseResp;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 转接在职成员的客户给其他成员,返回对象
+ *
+ * @author pg
+ * @date 2021年6月21日
+ */
+@Getter
+@Setter
+public class WxCpUserTransferCustomerResp extends WxCpBaseResp {
+  private static final long serialVersionUID = -8030598756503590089L;
+  /**
+   * 客户转移结果列表
+   */
+  private List customer;
+
+  public static WxCpUserTransferCustomerResp fromJson(String json) {
+    return WxCpGsonBuilder.create().fromJson(json, WxCpUserTransferCustomerResp.class);
+  }
+
+  public String toJson() {
+    return WxCpGsonBuilder.create().toJson(this);
+  }
+
+  /**
+   * 转接客户结果实体
+   */
+  @Getter
+  @Setter
+  public static class TransferCustomer implements Serializable {
+    private static final long serialVersionUID = 8720554208727083338L;
+    /**
+     * 客户的external_userid
+     */
+    @SerializedName("external_userid")
+    private String externalUserid;
+    /**
+     * 对此客户进行分配的结果, 0表示成功发起接替,待24小时后自动接替,并不代表最终接替成功
+     */
+    private Integer errcode;
+
+    public static WxCpUserTransferCustomerResp.TransferCustomer fromJson(String json) {
+      return WxCpGsonBuilder.create().fromJson(json, WxCpUserTransferCustomerResp.TransferCustomer.class);
+    }
+
+    public String toJson() {
+      return WxCpGsonBuilder.create().toJson(this);
+    }
+  }
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpUserTransferResultResp.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpUserTransferResultResp.java
new file mode 100644
index 0000000000..e1b8cc4591
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpUserTransferResultResp.java
@@ -0,0 +1,92 @@
+package me.chanjar.weixin.cp.bean.external;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Getter;
+import lombok.Setter;
+import me.chanjar.weixin.cp.bean.WxCpBaseResp;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+import java.io.Serializable;
+
+/**
+ * 在职成员的客户转接情况
+ * @author pg
+ * @date 2021年6月21日
+ */
+@Getter
+@Setter
+public class WxCpUserTransferResultResp extends WxCpBaseResp {
+  private static final long serialVersionUID = 6897979567174991786L;
+  @SerializedName("next_cursor")
+  private String nextCursor;
+
+  public static WxCpUserTransferResultResp fromJson(String json) {
+    return WxCpGsonBuilder.create().fromJson(json, WxCpUserTransferResultResp.class);
+  }
+
+  public String toJson() {
+    return WxCpGsonBuilder.create().toJson(this);
+  }
+
+  /**
+   * 客户转接结果实体
+   */
+  @Getter
+  @Setter
+  public static class TransferResult implements Serializable {
+    private static final long serialVersionUID = 2847784363733118393L;
+
+    /**
+     * 客户的external_userid
+     */
+    @SerializedName("external_userid")
+    private String externalUserid;
+    /**
+     * 接替状态, 1-接替完毕 2-等待接替 3-客户拒绝 4-接替成员客户达到上限 5-无接替记录
+     */
+    private STATUS status;
+    /**
+     * 接替客户的时间,如果是等待接替状态,则为未来的自动接替时间
+     */
+    @SerializedName("takeover_time")
+    private Long takeOverTime;
+
+    public static WxCpUserTransferResultResp.TransferResult fromJson(String json) {
+      return WxCpGsonBuilder.create().fromJson(json, WxCpUserTransferResultResp.TransferResult.class);
+    }
+
+    public String toJson() {
+      return WxCpGsonBuilder.create().toJson(this);
+    }
+  }
+
+  public enum STATUS {
+
+    /**
+     * 接替完毕
+     */
+    @SerializedName("1")
+    COMPLETE,
+
+    /**
+     * 等待接替
+     */
+    @SerializedName("2")
+    WAITING,
+    /**
+     * 客户拒绝
+     */
+    @SerializedName("3")
+    REFUSED,
+    /**
+     * 接替成员客户达到上限
+     */
+    @SerializedName("4")
+    LIMIT,
+    /**
+     * 无接替记录
+     */
+    @SerializedName("5")
+    NORECORD
+  }
+}
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 bc96269ea8..c60a1bddbd 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
@@ -175,13 +175,20 @@ interface ExternalContact {
     String CLOSE_TEMP_CHAT = "/cgi-bin/externalcontact/close_temp_chat";
     String GET_FOLLOW_USER_LIST = "/cgi-bin/externalcontact/get_follow_user_list";
     String GET_CONTACT_DETAIL = "/cgi-bin/externalcontact/get?external_userid=";
+    String CONVERT_TO_OPENID = "/cgi-bin/externalcontact/convert_to_openid";
     String GET_CONTACT_DETAIL_BATCH = "/cgi-bin/externalcontact/batch/get_by_user?";
     String UPDATE_REMARK = "/cgi-bin/externalcontact/remark";
     String LIST_EXTERNAL_CONTACT = "/cgi-bin/externalcontact/list?userid=";
     String LIST_UNASSIGNED_CONTACT = "/cgi-bin/externalcontact/get_unassigned_list";
+    @Deprecated
     String TRANSFER_UNASSIGNED_CONTACT = "/cgi-bin/externalcontact/transfer";
+    String TRANSFER_CUSTOMER = "/cgi-bin/externalcontact/transfer_customer";
+    String TRANSFER_RESULT = "/cgi-bin/externalcontact/transfer_result";
+    String RESIGNED_TRANSFER_CUSTOMER = "/cgi-bin/externalcontact/resigned/transfer_customer";
+    String RESIGNED_TRANSFER_RESULT = "/cgi-bin/externalcontact/resigned/transfer_result";
     String GROUP_CHAT_LIST = "/cgi-bin/externalcontact/groupchat/list";
     String GROUP_CHAT_INFO = "/cgi-bin/externalcontact/groupchat/get";
+    String GROUP_CHAT_TRANSFER = "/cgi-bin/externalcontact/groupchat/transfer";
     String LIST_USER_BEHAVIOR_DATA = "/cgi-bin/externalcontact/get_user_behavior_data";
     String LIST_GROUP_CHAT_DATA = "/cgi-bin/externalcontact/groupchat/statistic";
     String ADD_MSG_TEMPLATE = "/cgi-bin/externalcontact/add_msg_template";
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImplTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImplTest.java
index 4b6221d175..8869a6a02b 100644
--- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImplTest.java
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImplTest.java
@@ -16,6 +16,7 @@
 import org.testng.annotations.Test;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Date;
 import java.util.List;
 
@@ -196,13 +197,62 @@ public void testTransferExternalContact() {
   }
 
   @Test
-  public void testListGroupChat() {
+  public void testTransferCustomer() throws WxErrorException {
+    WxCpUserTransferCustomerReq req = new WxCpUserTransferCustomerReq();
+    req.setExternalUserid(Collections.emptyList());
+    req.setHandOverUserid("123");
+    req.setTakeOverUserid("234");
+    WxCpBaseResp result = this.wxCpService.getExternalContactService().transferCustomer(req);
+
+    System.out.println(result);
+    assertNotNull(result);
+  }
+
+  @Test
+  public void testTrnsferResult() throws WxErrorException {
+    WxCpUserTransferResultResp result = this.wxCpService.getExternalContactService().transferResult("123", "234", "");
+    System.out.println(result);
+    assertNotNull(result);
+  }
+
+  @Test
+  public void testresignedTransferCustomer() throws WxErrorException {
+    WxCpUserTransferCustomerReq req = new WxCpUserTransferCustomerReq();
+    req.setExternalUserid(Collections.emptyList());
+    req.setHandOverUserid("123");
+    req.setTakeOverUserid("234");
+    WxCpBaseResp result = this.wxCpService.getExternalContactService().resignedTransferCustomer(req);
+
+    System.out.println(result);
+    assertNotNull(result);
+  }
+
+  @Test
+  public void testresignedTrnsferResult() throws WxErrorException {
+    WxCpUserTransferResultResp result = this.wxCpService.getExternalContactService().resignedTransferResult("123", "234", "");
+    System.out.println(result);
+    assertNotNull(result);
+  }
+
+  @Test
+  public void testListGroupChat() throws WxErrorException {
+    WxCpUserExternalGroupChatList result = this.wxCpService.getExternalContactService().listGroupChat(0, 100 ,0,new String[1],new String[1]);
+    System.out.println(result);
+    assertNotNull(result);
   }
 
   @Test
   public void testGetGroupChat() {
   }
 
+  @Test
+  public void testTransferGroupChat() throws WxErrorException {
+    String[] str = {"wri1_QEAAATfnZl_VJ4hlQda0e4Mgf1A"};
+    WxCpUserExternalGroupChatTransferResp result = this.wxCpService.getExternalContactService().transferGroupChat(str, "123");
+    System.out.println(result);
+    assertNotNull(result);
+  }
+
   @Test
   public void testGetUserBehaviorStatistic() {
   }