e签宝签署电子合同使用手册
参数说明
URL=“smlopenapi” //沙盒模式使用URL
URL=“openapi” //生产模式使用URL
APPID="" //账号的appid
SECRET="" //账号的secret
CACCOUNTID="" //创建企业人的accountid
ORGID="" //企业id
NOTICEDEVELOPERURL="" //接受E签宝的回调接口地址
SEALID="" //企业生成的印章id
CONTENTTYPE=“application/json; charset=UTF-8”
DEFAULT_CHAR_SET=“UTF-8”
GRANTTYPE=“client_credentials”
1.创建账号
详情请看官方文档文档.
2.添加ip白名单
详情请看官方文档文档.
3.E签宝流程介绍
E签宝作为签约中间者存在,首先明确电子签约分为签约文件和签约主体两部分,所谓签约文件,顾名思义就是签约所需要用到的电子合同。签约主体,可以理解为签约的对象,一般是人,又是对这个签约动作负责的个体。每发起一次电子合同签署,算作是一个签署流程,一个流程的生命周期即这份合同的签约状态的生命周期,一个签署流程的开始即从流程中顺序开始执行签署区,所有签署区都执行完毕,执行流程归档,即流程结束。流程正确结束,则合同正式开始生效。
签约文件:签约文件又分为两个部分,需要签约主体签字的称为文档(比如需要签约的合同),不需要签约主体签字的,只作为附属文件留存的称为附件(比如合同中涉及的附加说明项等)。
签约主体:签约主体也分为两个部分,签署平台与签署人,签署平台即账号实际拥有者创建的企业,即为每份合同交钱的主体成为平台。除此以外无论是创建的个人账号还是个人账号下创建的企业账号都算签署人。
签署区:签署区是在签署文件当中,需要留给签署主体签约用的区域。在签署流程完成后,所有签署区会替换成签署过后的样子,最后的合同应该为签署文件+签署主体在签署区的签署+附件。
签约流程:签约流程是由签署主体发起的一个流程操作,其中包含发起人信息,签署文件信息,签署区信息,签署主体信息等。每一个签署动作可看作签署流程所有流程中的一个流程。当全部签署完毕,需要调用流程归档,完成全部签约流程,合同生效。
4.实际操作与代码示例
首先解决校验问题,E签宝校验可采用两种方式,签名和OAuth2.0鉴权,这里我采用了OAuth2.0鉴权,需要注意的是,E签宝每次获取的token有效期为2小时,再次调用获取token接口会使之前的token变为5分钟,测试时为方便起见,每次调用方法时调用一次token,防止token出现过期状况,结尾会对此进行优化,E签宝获取token的接口为
/v1/oauth2/access_token
封装为方法
public String getToken() throws IOException {
URL u = null;
HttpURLConnection con = null;
u = new URL("https://"+URL+".esign.cn/v1/oauth2/access_token?appId=" + APPID + "&secret=" + SECRET + "&grantType=" + GRANTTYPE);
con = (HttpURLConnection) u.openConnection();
con.setRequestMethod("GET");
con.setDoOutput(true);
con.setDoInput(true);
BufferedReader reader = new BufferedReader(new InputStreamReader(
con.getInputStream()));
String line;
String result = "";
while ((line = reader.readLine()) != null) {
result = result + line;
}
JSONObject jsonObject = JSONObject.parseObject(result);//创建jsonObjec对象
JSONObject jsonObject1 = (JSONObject) jsonObject.get("data");
String token = jsonObject1.get("token").toString();
return token;
}
4.1.创建个人用户(创建企业创建人)
此用户将作为企业创建人存在,可以使用创建E签宝账号的人作为此用户或者企业法人都可,值得一提,后面发起合同都是用的此用户和其他企业发起,每份合同签署完成都会发送短信给此用户,此用户还做抄送人用。企业创建人本质上和普通个人用户没有区别。
重要参数
thirdPartyUserId:第三方流水号,这里我用用户原手机号做第三方id
name:用户姓名,务必填写真实姓名,不然后面人脸识别会失效
idType:个人证件类型,这里我选择的是身份证,也可以根据需求做修改
idNumber:证件号码,上面选择的证件类型之后的证件号码
mobile:手机号,后续发送签约信息时会使用这个手机号,还可以添加邮箱做信息接受,这里省略(需要注意的是,我测试的E签宝确定一个人是根据身份证和姓名来确定的,即用A的身份证号和B的姓名和C的手机号,最后是无法人脸识别的,A身份证,A姓名,C手机号的话最后会发送信息到C手机号上,但只有A才能完成人脸认证,根据官方说法,三个权重两个确定一个人)
接口
/v1/accounts/createByThirdPartyUserId
封装成方法
public Map<String, String> addUser(String name, String phone, String idCard) throws IOException, InterruptedException {
HttpRequestBase request = getRquest("POST");
//设置请求url
request.setURI(URI.create("https://" + URL + ".esign.cn/v1/accounts/createByThirdPartyUserId"));
//设置header
request.setHeader("X-Tsign-Open-App-Id", APPID);
request.setHeader("X-Tsign-Open-Token", getToken());
request.setHeader("Content-Type", CONTENTTYPE);
//设置requestbody中的参数
Map<String, Object> map = new HashMap<>();
map.put("thirdPartyUserId", phone);
map.put("name", name);
map.put("idType", "CRED_PSN_CH_IDCARD");
map.put("idNumber", idCard);
map.put("mobile", phone);
String json = JSON.toJSONString(map);
if (StringUtils.isNotBlank(json) && request instanceof HttpEntityEnclosingRequestBase) {
HttpEntity entity = EntityBuilder.create()
.setContentEncoding(DEFAULT_CHAR_SET)
.setContentType(ContentType.APPLICATION_JSON)
.setText(json)
.build();
((HttpEntityEnclosingRequestBase) request).setEntity(entity);
}
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
CloseableHttpResponse response = httpClient.execute(request);
if (null == response || null == response.getStatusLine()) {
throw new RuntimeException("请求结果无法解析!");
}
Map<String, Object> map2 = (Map<String, Object>) JSON.parse(EntityUtils.toString(response.getEntity(), DEFAULT_CHAR_SET));
Map<String, String> data = (Map<String, String>) map2.get("data");
if (response != null) {
response.close();
}
return data;
}
data中可以得到用户的accountId,此为E签宝中的用户id,注意保存
4.2.创建企业
此为合同签约主体不只是人与人了,当需要人与企业进行签署的时候,就需要创建企业了,我们用刚才创建的企业创建人创建企业。
重要参数
thirdPartyUserId:企业第三方流水号id,这里我使用了企业社会统一信用代码
creator:创建者id,我们使用之前创建的企业创建者的accountId
idType:选择社会统一信用代码
idNumber:社会统一信用代码的值
name:企业的全称
orgLegalIdNumber:法人证件号,不传也可以
orgLegalName:法人姓名,不传也可以
接口
/v1/organizations/createByThirdPartyUserId
封装成方法
public Map<String, String> addOrg(String idNumber,String name,String orgLegalIdNumber,String orgLegalName) throws IOException {
HttpRequestBase request = getRquest("POST");
//设置请求url
request.setURI(URI.create("https://"+URL+".esign.cn/v1/organizations/createByThirdPartyUserId"));
//设置header
request.setHeader("X-Tsign-Open-App-Id", APPID);
request.setHeader("X-Tsign-Open-Token", getToken());
request.setHeader("Content-Type", CONTENTTYPE);
//设置requestbody中的参数
Map<String, Object> map = new HashMap<>();
map.put("thirdPartyUserId", idNumber);
map.put("creator", CACCOUNTID);
map.put("idType", "CRED_ORG_USCC");
map.put("idNumber", idNumber);
map.put("name", phone);
map.put("orgLegalIdNumber", orgLegalIdNumber);
map.put("orgLegalName", orgLegalName);
String json = JSON.toJSONString(map);
if (StringUtils.isNotBlank(json) && request instanceof HttpEntityEnclosingRequestBase) {
HttpEntity entity = EntityBuilder.create()
.setContentEncoding(DEFAULT_CHAR_SET)
.setContentType(ContentType.APPLICATION_JSON)
.setText(json)
.build();
((HttpEntityEnclosingRequestBase) request).setEntity(entity);
}
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
CloseableHttpResponse response = httpClient.execute(request);
if (null == response || null == response.getStatusLine()) {
throw new RuntimeException("请求结果无法解析!");
}
Map<String, Object> map2 = (Map<String, Object>) JSON.parse(EntityUtils.toString(response.getEntity(), DEFAULT_CHAR_SET));
Map<String, String> data = (Map<String, String>) map2.get("data");
if (response != null) {
response.close();
}
return data;
}
返回值中的OrgId为E签宝中企业对应id,注意保存
4.3.添加机构印章
添加机构印章,用作后面签署合同时企业盖的印章
接口
/v1/accounts/{accountId}/seals/personaltemplate
封装成方法
public Map<String, String> addSeal() throws IOException {
HttpRequestBase request = getRquest("POST");
//设置请求url
request.setURI(URI.create("https://smlopenapi.esign.cn/v1/organizations/"+ORGID+"/seals/officialtemplate"));
//设置header
request.setHeader("X-Tsign-Open-App-Id", APPID);
request.setHeader("X-Tsign-Open-Token", getToken());
request.setHeader("Content-Type", CONTENTTYPE);
//设置requestbody中的参数
Map<String, Object> map = new HashMap<>();
map.put("alias", "印章别名");
map.put("central", "STAR"); //STAR圆形有五角星 NONE圆形无五角星
map.put("color", "RED");//RED-红色,BLUE-蓝色,BLACK-黑色
map.put("height", 159);//印章高
map.put("width", 159);//印章宽
map.put("htext", "横向文");//横向文,可设置0-8个字,企业名称超出25个字后,不支持设置横向文
map.put("qtext", "下弦文");下弦文,可设置0-20个字,企业企业名称超出25个字后,不支持设置下弦文
map.put("type", orgLegalName);//TEMPLATE_ROUND圆章 TEMPLATE_OVAL
椭圆章
String json = JSON.toJSONString(map);
if (StringUtils.isNotBlank(json) && request instanceof HttpEntityEnclosingRequestBase) {
HttpEntity entity = EntityBuilder.create()
.setContentEncoding(DEFAULT_CHAR_SET)
.setContentType(ContentType.APPLICATION_JSON)
.setText(json)
.build();
((HttpEntityEnclosingRequestBase) request).setEntity(entity);
}
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
CloseableHttpResponse response = httpClient.execute(request);
if (null == response || null == response.getStatusLine()) {
throw new RuntimeException("请求结果无法解析!");
}
Map<String, Object> map2 = (Map<String, Object>) JSON.parse(EntityUtils.toString(response.getEntity(), DEFAULT_CHAR_SET));
Map<String, String> data = (Map<String, String>) map2.get("data");
if (response != null) {
response.close();
}
return data;
}
返回sealId,印章id,保存下来,后面签约需要调用
4.4.签署流程创建
创建一个签署流程,注意,目前流程中还没有签署文件和签署区,需要后续添加
接口
/v1/signflows
封装成方法
public String signatureProcessCreation() throws IOException {
HttpRequestBase request = getRquest("POST");
//设置请求url
request.setURI(URI.create("https://" + URL + ".esign.cn/v1/signflows"));
//设置header
request.setHeader("X-Tsign-Open-App-Id", APPID);
request.setHeader("X-Tsign-Open-Token", getToken());
request.setHeader("Content-Type", CONTENTTYPE);
//设置requestbody中的参数
Map<String, Object> jsonMap = new HashMap<>();
jsonMap.put("autoArchive", false);
//是否自动归档,默认false
jsonMap.put("businessScene", "文件主题");
//文件主题
Map<String, Object> configInfo = new HashMap<>();
configInfo.put("noticeDeveloperUrl", NOTICEDEVELOPERURL);
//回调通知地址
configInfo.put("noticeType", "1");
//通知方式,逗号分割,1-短信,2-邮件 。默认值1,请务必请选择一个通知方式,否则客户将接收不到流程的签署通知和审批通知,如果流程需要审批,将导致审批无法完成;如果客户需要不通知,可以设置noticeType=""注:短信或者邮件获取到的签署链接,有效期默认30天
configInfo.put("redirectUrl", "");
//签署完成重定向地址,默认签署完成停在当前页面
configInfo.put("signPlatform", "");
//签署平台,逗号分割,1-开放服务h5,2-支付宝签 ,默认值1,2
jsonMap.put("configInfo", configInfo);
jsonMap.put("contractRemind", 360);
//文件到期前,提前多少小时回调提醒续签,小时(时间区间:1小时——15天),默认不提醒;若时间到了该参数设置的时间,则会触发【流程文件过期前通知】
jsonMap.put("contractValidity", getTimeMillis());
//文件有效截止日期,毫秒,默认不失效;
jsonMap.put("signValidity", getTimeMillis());
//签署有效截止日期,毫秒,默认不失效
jsonMap.put("initiatorAccountId", CACCOUNTID);
//创始人id
jsonMap.put("initiatorAuthorizedAccountId", ORGID);
//企业id
String json = JSON.toJSONString(jsonMap);
if (StringUtils.isNotBlank(json) && request instanceof HttpEntityEnclosingRequestBase) {
HttpEntity entity = EntityBuilder.create()
.setContentEncoding(DEFAULT_CHAR_SET)
.setContentType(ContentType.APPLICATION_JSON)
.setText(json)
.build();
((HttpEntityEnclosingRequestBase) request).setEntity(entity);
}
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
CloseableHttpResponse response = httpClient.execute(request);
if (null == response || null == response.getStatusLine()) {
throw new RuntimeException("请求结果无法解析!");
}
Map<String, Object> map = (Map<String, Object>) JSON.parse(EntityUtils.toString(response.getEntity(), DEFAULT_CHAR_SET));
Map<String, Object> data = (Map<String, Object>) map.get("data");
response.close();
return (String) data.get("flowId");
}
获取下个月第一天的时间戳
private static Long getTimeMillis(){
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.DAY_OF_MONTH,1);
calendar.add(Calendar.MONTH, 1);
return calendar.getTimeInMillis();
}
该方法返回了flowId,是E签宝的流程id,保存下来,后续添加签署文件和签署对象时使用,合同完成签署回调通过flowId来修改数据库状态
4.5.通过上传方式创建签署文件
要把签署文件提供给E签宝,要先把文件发送给E签宝,这里我使用的是上传
接口
/v1/files/getUploadUrl
封装成方法
public Map<String, String> createSignatureFile(String filePath) throws IOException {
HttpRequestBase request = getRquest("POST");
//设置请求url
request.setURI(URI.create("https://" + URL + ".esign.cn/v1/files/getUploadUrl"));
//设置header
request.setHeader("X-Tsign-Open-App-Id", APPID);
request.setHeader("X-Tsign-Open-Token", getToken());
request.setHeader("Content-Type", CONTENTTYPE);
//设置requestbody中的参数
Long fileSize = new File(filePath).length();
Map<String, Object> map = new HashMap<>();
map.put("contentMd5", getStringContentMD5(filePath));//文件md5值,base64编码
map.put("contentType", "application/octet-stream");//(1)application/octet-stream
ream");//(1)application/octet-stream
(2)application/pdf pdf文件用pdf,其他文件用octet-stream
map.put("convert2Pdf", true);//是否转换成pdf文档,默认false ,我上面传的是.word,所以设置为true
map.put("fileName", filePath);//文件名称,必须带上文件扩展名
map.put("fileSize", fileSize);//文件大小,单位为字节
String json = JSON.toJSONString(map);
if (StringUtils.isNotBlank(json3) && request instanceof HttpEntityEnclosingRequestBase) {
HttpEntity entity = EntityBuilder.create()
.setContentEncoding(DEFAULT_CHAR_SET)
.setContentType(ContentType.APPLICATION_JSON)
.setText(json)
.build();
((HttpEntityEnclosingRequestBase) request).setEntity(entity);
}
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
CloseableHttpResponse response = httpClient.execute(request);
if (null == response || null == response.getStatusLine()) {
throw new RuntimeException("请求结果无法解析!");
}
Map<String, Object> map = (Map<String, Object>) JSON.parse(EntityUtils.toString(response.getEntity(), DEFAULT_CHAR_SET));
Map<String, String> data = (Map<String, String>) map.get("data");
response.close();
return data;
}
计算字符串的MD5
public static String getStringContentMD5(String str) {
// 获取文件MD5的二进制数组(128位)
byte[] bytes = getFileMD5Bytes1282(str);
// 对文件MD5的二进制数组进行base64编码
return new String(Base64.encodeBase64(bytes));
}
获取文件MD5-二进制数组(128位)
public static byte[] getFileMD5Bytes1282(String filePath) {
FileInputStream fis = null;
byte[] md5Bytes = null;
try {
File file = new File(filePath);
fis = new FileInputStream(file);
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] buffer = new byte[1024];
int length = -1;
while ((length = fis.read(buffer, 0, 1024)) != -1) {
md5.update(buffer, 0, length);
}
md5Bytes = md5.digest();
fis.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
log.error(e.getMessage());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
log.error(e.getMessage());
} catch (IOException e) {
e.printStackTrace();
log.error(e.getMessage());
}
return md5Bytes;
}
这里在返回值中会得到文件的上传url
4.6.上传签署合同
使用上一个接口返回的url上传你的文件
public void uploadURL(String filePath) throws IOException {
byte[] imageByte;
File file = new File();
URL u = null;
HttpURLConnection con = null;
u = new URL(UPLOADURL); //上传的url
con = (HttpURLConnection) u.openConnection();
con.setRequestMethod("PUT");
con.setRequestProperty("Content-MD5",getStringContentMD5(filePath));//文件MD5值
con.setRequestProperty("Content-Type","application/octet-stream");
con.setDoOutput(true);
con.setDoInput(true);
OutputStream out = new DataOutputStream(con.getOutputStream());
DataInputStream in = new DataInputStream(new FileInputStream(file));
byte[] bufferOut = new byte[1024];
int bytes = 0;
// 每次读1KB数据,并且将文件数据写入到输出流中
while ((bytes = in.read(bufferOut)) != -1) {
out.write(bufferOut, 0, bytes);
}
// 最后添加换行
in.close();
out.flush();
out.close();
BufferedReader reader = new BufferedReader(new InputStreamReader(
con.getInputStream()));
String line = null;
String result = "";
while ((line = reader.readLine()) != null) {
result = result + line;
JSONObject jsonObject = JSONObject.parseObject(result);//创建jsonObjec对象
String json = jsonObject.toString();//josn格式的字符串
System.out.println(json);
}
}
如果成功,需要等待一段时间等待.word文件转换成.pdf文件才能添加签署文档,如果传输的是.pdf文件,则直接可以添加签署文档
4.7向签署流程中添加签署文档
此处向刚才创建的签署流程中添加添加的签署文档,注意传输不是.pdf文件可能会失败,因为文档未转换成功
接口
/v1/signflows/{flowId}/documents
封装成方法
public void addSignatureDocument(String flowId, String fileId, String fileName) throws IOException, AWTException, InterruptedException {
HttpRequestBase request = getRquest("POST");
//设置请求url
request.setURI(URI.create("https://" + URL + ".esign.cn/v1/signflows/" + flowId + "/documents"));
//设置header
request.setHeader("X-Tsign-Open-App-Id", APPID);
request.setHeader("X-Tsign-Open-Token", getToken());
request.setHeader("Content-Type", CONTENTTYPE);
//设置requestbody中的参数
Map<String, Object> map = new HashMap<>();
map.put("encryption", 0);//是否加密,0-不加密,1-加密,默认0
map.put("fileId", fileId);//文档id
map.put("fileName", fileName);//文件名称,必须带上文件扩展名
String json = JSON.toJSONString(map);
if (StringUtils.isNotBlank(json) && request instanceof HttpEntityEnclosingRequestBase) {
HttpEntity entity = EntityBuilder.create()
.setContentEncoding(DEFAULT_CHAR_SET)
.setContentType(ContentType.APPLICATION_JSON)
.setText(json)
.build();
((HttpEntityEnclosingRequestBase) request).setEntity(entity);
}
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
CloseableHttpResponse response = httpClient.execute(request);
if (null == response || null == response.getStatusLine()) {
throw new RuntimeException("请求结果无法解析!");
}
Map<String, Object> map = (Map<String, Object>) JSON.parse(EntityUtils.toString(response.getEntity(), DEFAULT_CHAR_SET));
}
4.8.平台自动盖章
添加平台自动盖章签署区,使用之前创建的企业印章,设置自动盖章,需要注意的是,只有平台才可以设置平台自动盖章,如果是个人或者个人创建的企业设置就得设置签署方自动盖章,需要再盖章之前调用静默签署接口,判断是不是平台的依据是签署合同的付费主体即平台方,如果觉得难以理解可直接设置签署方自动盖章,静默签署即可。
接口
平台方自动盖章
/v1/signflows/{flowId}/signfields/platformSign
签署方自动盖章
/v1/signflows/{flowId}/signfields/autoSign
签署方静默签署
/v1/signAuth/{accountId}
封装成方法
//静默签署,非平台方设置自动签署前设置
public void signAuth() throws IOException {
HttpRequestBase request = getRquest("POST");
//设置请求url
request.setURI(URI.create("https://"+URL+".esign.cn/v1/signAuth/"+ORGID));
//设置header
request.setHeader("X-Tsign-Open-App-Id", APPID);
request.setHeader("X-Tsign-Open-Token", getToken());
request.setHeader("Content-Type", CONTENTTYPE);
if (StringUtils.isNotBlank(json) && request instanceof HttpEntityEnclosingRequestBase) {
HttpEntity entity = EntityBuilder.create()
.setContentEncoding(DEFAULT_CHAR_SET)
.setContentType(ContentType.APPLICATION_JSON)
.build();
((HttpEntityEnclosingRequestBase) request).setEntity(entity);
}
CloseableHttpResponse response = httpClient.execute(request);
try {
if (null == response || null == response.getStatusLine()) {
throw new RuntimeException("请求结果无法解析!");
}
int statusCode = response.getStatusLine().getStatusCode();
} finally {
if (response != null) {
response.close();
}
}
}
//自动签署
public void automaticSealEnterprise(String flowId, String fileId, String posPage, Integer posX, Integer posY) throws IOException {
HttpRequestBase request = getRquest("POST");
//设置header
request.setHeader("X-Tsign-Open-App-Id", APPID);
request.setHeader("X-Tsign-Open-Token", getToken());
request.setHeader("Content-Type", CONTENTTYPE);
//设置requestbody中的参数
Map<String, Object> map = new HashMap<>();
List<Map<String, Object>> list = new ArrayList<>();
Map<String, Object> map2 = new HashMap<>();
request.setURI(URI.create("https://" + URL + ".esign.cn/v1/signflows/" + flowId + "/signfields/autoSign"));//签署方自动盖章
map2.put("authorizedAccountId",ORGID);//签署方自动盖章
//request.setURI(URI.create("https://" + URL + ".esign.cn/v1/signflows/" + flowId + "/signfields/platformSign"));//平台方自动盖章
map2.put("fileId", fileId);//文件file id
map2.put("order", 1);//签署顺序,默认1,且不小于1,顺序越小越先处理
Map<String, Object> map3 = new HashMap<>();
map3.put("posPage", posPage);//页码信息,当签署区signType为2时, 页码可以'-'分割, 其他情况只能是数字
map3.put("posX", posX);//x坐标,默认空
map3.put("posY", posY);//y坐标
map2.put("posBean", map3);//签署区位置信息, (signType为1时, 页码和XY坐标不能为空, signType为2时, 页码和Y坐标不能为空)
map2.put("sealId", SEALID);//印章id, 仅限企业公章,暂不支持指定企业法定代表人印章,如不传,则采用账号下的默认印章
map2.put("signType", 1);//签署类型, 1-单页签署,2-骑缝签署,默认1
list.add(map2);
map.put("signfields", list);
String json = JSON.toJSONString(map);
if (org.apache.commons.lang3.StringUtils.isNotBlank(json) && request instanceof HttpEntityEnclosingRequestBase) {
HttpEntity entity = EntityBuilder.create()
.setContentEncoding(DEFAULT_CHAR_SET)
.setContentType(ContentType.APPLICATION_JSON)
.setText(json)
.build();
((HttpEntityEnclosingRequestBase) request).setEntity(entity);
}
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
CloseableHttpResponse response = httpClient.execute(request);
response.close();
}
4.9.用户手动签署区添加
用户签署区添加,参数和上平台自动签署相同,注意修改接口,修改签署区位置和签署顺序即可,order为签署顺序字段,可设置多层签署
接口
/v1/signflows/{flowId}/signfields/handSign
封装成方法见上平台自动签署区添加
4.10.开启签署流程
前面签署流程已经完成全部创建准备工作,到现在直接开启该流程即可按签署区顺序开启签署合同流程,直到流程签署完毕或者卡在中间某个签署区知道时间结束。
接口
/v1/signflows/{flowId}/start
封装成方法
public void StartSigningProcess(String flowId) throws IOException {
HttpRequestBase request = getRquest("PUT");
//设置请求url
request.setURI(URI.create("https://" + URL + ".esign.cn/v1/signflows/" + flowId + "/start"));
//设置header
request.setHeader("X-Tsign-Open-App-Id", APPID);
request.setHeader("X-Tsign-Open-Token", getToken());
request.setHeader("Content-Type", CONTENTTYPE);
if (org.apache.commons.lang3.StringUtils.isNotBlank(json) && request instanceof HttpEntityEnclosingRequestBase) {
HttpEntity entity = EntityBuilder.create()
.setContentEncoding(DEFAULT_CHAR_SET)
.setContentType(ContentType.APPLICATION_JSON)
.build();
((HttpEntityEnclosingRequestBase) request).setEntity(entity);
}
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
CloseableHttpResponse response = httpClient.execute(request);
if (null == response || null == response.getStatusLine()) {
throw new RuntimeException("请求结果无法解析!");
}
Map<String, Object> map = (Map<String, Object>) JSON.parse(EntityUtils.toString(response.getEntity(), DEFAULT_CHAR_SET));
response.close();
}
开启签署流程成功之后,签署合同信息会通过你设置的通知方式,手机短信或者邮箱邮件通知签署合同人。
到此,开启签署流程步骤全部完成,接下来就等待签署主体完成签署获取回调进行处理即可。
5.回调
此回调接口为公网可访问接口,接收到回调判断无误后,返回response.code为200即可。注意,返回值类型和官方提供接口类型有出入,可根据修改
@PostMapping("/notify")
public void notify(HttpServletRequest request, HttpServletResponse response) {
String resXml = null;
InputStream inStream = null;
try {
inStream = request.getInputStream();
//InputStream流转换成String字符串
int length = 0;
//定义数组存放数据
byte buffer[] = new byte[2048];
StringBuilder sb = new StringBuilder();
if ((length = inStream.read(buffer)) != -1) {
sb.append(new String(buffer, 0, length));
}
Gson gson = new Gson();
//将存放入数组的数据转为map格式
HashMap map = gson.fromJson(sb.toString(), HashMap.class);
String action = (String) map.get("action");
//1.签署区完成回调,每一个签署区完成都会调用此回调,这里我根据签署区
//的顺序判断是否是最后一个,因为我只创建了平台自动签署区和一个
//签署人签署区,所以当顺序为2的签署区成功签署时,我会调用流程归档,完成手动归档,流程正式结束,合同生效
if (SIGN_FLOW_UPDATE.equals(action)) {
log.info("执行签署回调");
//签署成功
if (2 == (double) map.get(SIGN_RESULT) && 2 == (double) map.get("order")) {
String flowId = (String) map.get(FLOW_ID1);
log.info(FLOW_ID + flowId + "完成签署流程,描述:" + map.get("resultDescription"));
//流程归档
archive(flowId);
response.setStatus(200);
}
//3失败或4拒签
else if (3 == (double) map.get(SIGN_RESULT) || 4 == (double) map.get("signResult")) {
String flowId = (String) map.get(FLOW_ID1);
log.info(FLOW_ID + flowId + "签署流程未完成,签署状态:" + map.get("signResult") + ",描述:" + map.get("resultDescription"));
//合同流程失败
//签署区失败或拒签执行操作
response.setStatus(200);
}
}
//2.流程完成回调,归档后会调用此回调,合同过期,失败,拒签也会调用此回调
else if (SIGN_FLOW_FINISH.equals(action)) {
//流程成功
if ("2".equals(map.get("flowStatus"))) {
String flowId = (String) map.get("flowId");
log.info(FLOW_ID + flowId + "完成签署全流程!,描述:" + map.get("statusDescription"));
//合同流程结束
//合同流程全部成功执行操作,此时合同正式生效
response.setStatus(200);
}
//3撤销5过期7拒签
if ("3".equals(map.get("flowStatus")) || "5".equals(map.get("flowStatus")) || "7".equals(map.get("flowStatus"))) {
String flowId = (String) map.get("flowId");
log.info("flowId:" + flowId + "签署流程未完成!,流程状态:" + map.get("flowStatus") + ",描述:" + map.get("statusDescription"));
//合同流程失败
//合同流程失败执行操作,可自行根据撤销过期拒签执行各自操作
response.setStatus(200);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
}
}
5.1流程归档
应为设置了手动归档,在全部签署区完成签署后,执行流程归档,归档完成,流程结束,合同生效。
接口
/v1/signflows/{flowId}/archive
封装成方法
public void archive(String flowId) throws IOException {
HttpRequestBase request = getRquest("PUT");
//设置请求url
request.setURI(URI.create("https://" + URL + ".esign.cn/v1/signflows/" + flowId + "/archive"));
//设置header
request.setHeader("X-Tsign-Open-App-Id", APPID);
request.setHeader("X-Tsign-Open-Token", getToken());
request.setHeader("Content-Type", CONTENTTYPE);
if (org.apache.commons.lang3.StringUtils.isNotBlank(json) && request instanceof HttpEntityEnclosingRequestBase) {
HttpEntity entity = EntityBuilder.create()
.setContentEncoding(DEFAULT_CHAR_SET)
.setContentType(ContentType.APPLICATION_JSON)
.build();
((HttpEntityEnclosingRequestBase) request).setEntity(entity);
}
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
CloseableHttpResponse response = httpClient.execute(request);
if (null == response || null == response.getStatusLine()) {
throw new RuntimeException("请求结果无法解析!");
}
Map<String, Object> map = (Map<String, Object>) JSON.parse(EntityUtils.toString(response.getEntity(), DEFAULT_CHAR_SET));
response.close();
}
6.总结与优化
使用E签宝的过程中遇到的问题基本都已解决,按照流程走即可完成合同流程签署,特此记录下来,作为参考。
优化,每次调用接口都获取token会使之前的token5分钟内失效,不够合理,特此完成一个定时器,每隔2小时自动获取token,之后存入redis,之后每次只要将获取token的接口换成从redis里获取token即可。回调的方法里可以换成消息队列,将回调进行异步处理,而不用在回调里执行逻辑代码。