[Docusign] Como enviar documentos para assinatura com Docusign e Java, usando eSignature API SDK
Neste artigo mostro como fazer uma integração básica com o Docusign enviando documentos por email para serem assinados digitalmente
O que é o Docusign?
Docusign é um SaaS baseado no envio e assinatura digital de documentos.
Cenário: A empresa onde você trabalha agora possui contratos que precisam ser assinados primeiro pelos clientes, depois pela própria empresa e por fim um e-mail deve ser enviado para ambos com uma cópia do contrato assinado.
Você decidiu que vai então construir uma aplicação que faz tudo o que foi solicitado. Nesse caso você precisa:
- armazenar os documentos
- informar aonde o documento deve ser assinado e uma UI que permita que o usuário assine
- enviar emails
- armazenar o status dos documentos que foi enviado
a lista continua, mas já vimos que o trabalho aqui não será simples.
Para nossa sorte não precisamos fazer tudo isso, o Docusign fará por nós.
O processo de assinatura mais básico do Docusign possui 3 etapas:
- Fazer upload de um documento
- Indicar o email de quem deve assinar o documento
- Indicar aonde o documento deve ser assinado
No painel você poderá acompanhar o progresso das assinaturas até que tudo seja concluído.
Nesse processo o Docusign:
- Armazenou o arquivo
- Enviou os emails com o arquivo para assinatusa indicando onde deveria ser assinado
- Após a assinatura enviou uma cópia em ambos os emails
Dessa forma nós não tivemos que implementar do zero nenhum dos serviços que nos foi solicitado, nem tivemos que nos preocupar com o status desses serviços, os possíveis bugs de implementação, etc.
Mas agora temos um outro dilema: e se a empresa tiver que enviar documentos para 5000 clientes diferentes assinar?
Pra isso o Docusign providencia sua API - eSignature - e que vamos explorar adiante.
Configurando o projeto
Para fazer nossa integração com o Docusign vamos precisar:
- Java 17
- Criar uma conta developer
- Acessar o painel do Docusign
- Criar um projeto Spring Boot com Spring Web aqui
- Postman
- 3 contas de e-mail diferentes para testes
- Adicionar as dependências no pom.xml: exemplo
Configurando um aplicativo no Docusign
Após configurar o projeto vamos acessar o painel da Docusign e configurar nossa aplicação. É nesta etapa que teremos todas informações necessárias para autenticação no Docusign.
Acesse a o painel e clique em Settings
Do lado esquerdo inferior clique em Apps and Keys
Copie o User ID e o API Account ID e preencha no arquivo .properties.
Clique em Add App and Integration Key
Crie um novo App (vamos chamar de playground-app)
Copie a Integration Key e cole no arquivo .properties
Clique em Generate RSA
Salve o conteúdo da Public Key e da Private Key em arquivos nomeados public-key e private-key respectivamente e mova os arquivos para a src/main/resource dentro da sua aplicação Spring
Em Redirect URLs adicione http://localhost:8080/ds/callback
Preencha os métodos GET, POST, PUT, DELETE
e clique em Salvar
Ao final seu arquivo application.properties deve estar preenchido e no diretório resources deve conter os arquivos public-key.txt e private-key.txt
spring.application.name=docusign-playground
docusign.account.id=66ab2a90-9f83-XXXX-XXXX-XXXXXXX
docusign.private.key.file.name=private-key.txt
docusign.client.id=cfedf726-5354-XXXX-XXXX-XXXXXX
docusign.user.id=e1a2d709-6d6d-XXXX-XXXX-XXXXXXXX
docusign.user.scopes=signature,impersonation
Configurando um Template
Podemos resumir o template como sendo o documento que será enviado para os recipients assinarem.
Nosso documento será um arquivo .docx com o conteúdo abaixo
No painel Docusign criaremos o template clicando em Templates > New > Create Template
Preencha o nome do template, adicione uma descrição e faça upload do arquivo .docx.
Clique em Add Recipient
Marque o checkbox Set signing order e preencha os dados de Role para os dois campos signers com owner_signer e stranger_signer respectivamente. Preencha o Name o Email para cada signer (você deve ter acesso aos emails cadastrados)
Clique em Next
Do lado esquerdo clique em Signature e posicione o novo campo aonde deseja que o usuário assine.
Repita o processo para adicionar a assinatura no segundo campo do documento, mas desta vez do lado direito clique em Recipient e selecione a opção stranger_signer
Clique em Save and Close.
Ao finalizar veremos que o template já aparece na lista de templates criados.
Clique no seu template criado depois em Template ID; copie e salve o valor mostrado na tela.
Autenticando no Docusign
O Docusign tem 3 tipos de autenticação; Authorization Code Grant, Implicit Grant e JWT Grant.
Baseado no diagrama que a Docusign disponibiliza, usaremos a autenticação via JWT
Para fazer a autenticação preencha a url abaixo trocando INTEGRATION_KEY pelo valor da sua Integration Key e cole no navegador.
https://account-d.docusign.com/oauth/auth?response_type=code&scope=signature%20impersonation&client_id=<INTEGRATION_KEY>f&redirect_uri=http://localhost:8080/ds/callback
Você será direcionado para uma tela de login novamente, preencha e faça login
Clique em Allow Access
Como a nossa URL de callback aponta para localhost uma tela preta de erro deve aparecer. Isso não indica erro necessariamente. Nosso aplicativo está autenticado!!
Criando Controllers, Models e Services
Vamos ao código.
Nossa aplicação terá 1 endpoint /docusign
com dois métodos: POST
e PUT
. Vamos começar criando 3 novos packages; controller
, model
e service
.
Criando os models
No package model
vamos criar 3 classes; CreateEnvelopeRequestBody
, SignerRequest
e UpdateEnvelopeRequestBody
import lombok.Data;
import java.util.List;
@Data
public class CreateEnvelopeRequestBody {
private String templateId;
private String status;
private List<SignerRequest> signers;
}
import lombok.Data;
@Data
public class SignerRequest{
private String name;
private String email;
private String roleName;
}
import lombok.Data;
@Data
public class UpdateEnvelopeRequestBody {
private String envelopeId;
private String email;
}
Criando os controllers
Agora vamos criar o controller EnvelopeController
retornando duas mensagens sem importância por enquanto, apenas para garantir que tudo está funcionando.
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/docusign")
public class EnvelopeController {
@PostMapping
public ResponseEntity<Object> sendEnvelope(){
return ResponseEntity.ok().body("Hello Post!");
}
@PutMapping
public ResponseEntity<Object> updateEmail(){
return ResponseEntity.ok().body("Hello Put!");
}
}
Criando o service de autenticação
Crie dentro do service
um novo package chamado auth
Dentro de auth
vamos criar a interface DocusignAuthService
import com.docusign.esign.client.ApiClient;
public interface DocusignAuthService {
public ApiClient getDocusignClient();
}
Em seguida vamos implementar a interface criada
/*imports omitidos veja no github*/
@Service
public class DocusignAuthServiceImpl implements DocusignAuthService {
@Autowired
private ApplicationContext ctx;
@Value("${docusign.private.key.file.name}")
private String privateKeyFileName;
@Value("${docusign.client.id}")
private String clientId;
@Value("${docusign.user.id}")
private String userId;
@Value("${docusign.user.scopes}")
private String scope;
private static final long TOKEN_EXPIRATION_IN_SECONDS = 3600;
@Override
public ApiClient getDocusignClient(){
// 1
InputStream privateKey = readFileFromResources(privateKeyFileName);
// 2
List<String> scopes = Arrays.stream(scope.split(",")).toList();
try {
// 3
ApiClient apiClient = new ApiClient();
// 4
byte[] privateKeyBytes = privateKey.readAllBytes();
//5
OAuth.OAuthToken oAuthToken = apiClient.requestJWTUserToken(
clientId,
userId,
scopes,
privateKeyBytes,
TOKEN_EXPIRATION_IN_SECONDS
);
//6
String accessToken = oAuthToken.getAccessToken();
//7
apiClient.addDefaultHeader("Authorization", "Bearer " + accessToken);
return apiClient;
} catch (Exception e) {
return null;
}
}
//8
private InputStream readFileFromResources(String fileName){
/*codigo java para ler arquivo, pode ser visto no github desse projeto*/
}
}
Vamos entender por partes:
1 - Primeiro recuperamos o conteúdo do arquivo private-key.txt.
2 - Recuperamos da property docusign.user.scopes os scopes da nossa autenticação - ou seja, as permissões que aquele token terá no Docusign - e transformarmos em uma lista de Strings.
3 - Criamos uma instância do objeto ApiClient que será usado pra fazer todas nossas chamadas HTTP.
4 - Transformamos o conteúdo do arquivo private-key.txt em bytes.
5 - É gerado um token JWT com base nos parâmetros do arquivo application.properties, necessário para nos comunicarmos com o Docusign.
6 - Pegamos somente o valor do token gerado
7 - Adicionamos no objeto ApiClient um header HTTP chamado Authorization e passamos como valor o token gerado
8 - Esse método que o arquivo private-key.txt com base no nome do arquivo.
Já é possível agora fazer autenticação no Docusign ao chamar o método
authService.getDocusignClient()
Mas antes de seguirmos em frente, vale ressaltar que esse código tem alguns problemas como: um bloco catch que apenas retorna null e não faz mais nada, um método pra recuperar um arquivo local com uma chave privada que deveria estar guardada em algum gerenciador de segredos, o objeto ApiClient poderia ser uma classe singleton, entre outros.
Como nosso objetivo aqui é fazer testes apenas locais, para não adicionar mais complexidade ao código decidi ignorar algumas questões de qualidade. Te encorajo a fazer uma implementação bem melhor do que estamos fazendo aqui.
Criando o service de comunicação com o Docusign
Seguindo adiante vamos criar agora a interface DocusignService
dentro do pacote service
/*imports omitidos veja no github*/
public interface DocusignService {
public EnvelopeSummary sendNewEnvelope(CreateEnvelopeRequestBody requestBody) throws ApiException;
public RecipientsUpdateSummary updateEnvelopeEmail(String email, String envelopeId) throws ApiException;
}
Em seguida vamos criar a classe DocusignServiceImpl
que a implementa
/*imports omitidos veja no github*/
@Service
public class DocusignServiceImpl implements DocusignService {
@Autowired
private DocusignAuthService authService;
@Value("${docusign.account.id}")
private String accountId;
@Override
public EnvelopeSummary sendNewEnvelope(CreateEnvelopeRequestBody requestBody) throws ApiException {
//1
ApiClient apiClient = authService.getDocusignClient();
//2
EnvelopesApi envelopesApi = new EnvelopesApi(apiClient);
//3
Envelope envelope = new Envelope();
//4
List<TemplateRole> roleList = requestBody.getSigners().stream().map(
item -> new TemplateRole()
.roleName(item.getRoleName())
.email(item.getEmail())
.name(item.getName())
).collect(Collectors.toList());
//5
EnvelopeDefinition definition = new EnvelopeDefinition()
.envelopeId(envelope.getEnvelopeId())
.status(requestBody.getStatus())
.templateId(requestBody.getTemplateId())
.templateRoles(roleList);
//6
return envelopesApi.createEnvelope(accountId, definition);
}
@Override
public RecipientsUpdateSummary updateEnvelopeEmail(String email, String envelopeId) throws ApiException {
ApiClient apiClient = authService.getDocusignClient();
EnvelopesApi envelopesApi = new EnvelopesApi(apiClient);
Envelope envelope = new Envelope();
//7
Signer signer = new Signer()
.email(email)
.recipientId("2")
.roleName("stranger_signer");
//8
Recipients recipients = new Recipients()
.addSignersItem(signer);
//9
envelope.setRecipients(recipients);
//10
EnvelopesApi.UpdateRecipientsOptions updateOptions = envelopesApi.new UpdateRecipientsOptions();
updateOptions.setResendEnvelope("true");
//11
return envelopesApi.updateRecipients(accountId, envelopeId, recipients, updateOptions);
}
}
Novamente vamos por partes. Primeiro vamos analisar o método sendNewEnvelope
1 - Dentro do método recuperamos uma nova instância do objeto ApiClient que contém a autenticação do Docusign.
2 - criamos uma instância do objeto EnvelopesApi passando como parâmetro o apiClient. Esse objeto possui os métodos para buscar informações de um envelope existente, criar novos envelopes, deletar documentos dentro de um envelope etc.
3 - Criamos uma nova instância do objeto Envelope.
4 - Criamos uma lista de objetos do tipo TemplateRole. Cada objeto nessa lista representa um signer dentro de um template.
5 - Criamos uma instância do objeto EnvelopeDefinition. Nele preenchemos o ID de um envelope, o status em que queremos criar esse envelope (para enviar email no momento da criação use o status sent), o ID do template que criamos anteriormente.
6 - Enviamos a requisição para o Docusign para criar um envelope. Neste momento o primeiro signer deve receber um e-mail e já devemos ver um novo envelope no painel Docusign.
No segundo método, updateEnvelopeEmail
vamos pular as partes repetidas do primeiro método e vamos direto ao que interessa
7 - Criamos uma nova instância do objeto Signer e indicamos para qual signer vamos querer atualizar o e-mail. Deixamos hardcoded o ID 2 e o roleName indicando que somente podemos atualizar o segundo signer do envelope.
8 - Criamos uma instância de um objeto Recipients e dentro dela colocamos o objeto signer
9 - Injetamos o objeto recipients dentro do objeto envelope já com as informações atualizadas.
10 - Criamos um objeto do tipo UpdateRecipientsOptions para indicar quais informações adicionais de configuração queremos para o nosso recipient. Nesse caso estamos informando que ao atualizar o envelope queremos que um e-mail seja reenviado para o recipient que foi atualizado.
11 - Enviamos a requisição para o Docusign para a atualização do envelope.
Finalizando a criação dos controllers
Por último cvamos corrigir o controller
para chamar o service
criado.
/*imports omitidos veja no github*/
@RestController
@RequestMapping("/docusign")
public class EnvelopeController {
@Autowired
private DocusignService docusignService;
@PostMapping
public ResponseEntity<Object> sendEnvelope(@RequestBody CreateEnvelopeRequestBody requestBody){
try {
return ResponseEntity.ok().body(docusignService.sendNewEnvelope(requestBody));
} catch (ApiException e) {
return ResponseEntity.internalServerError().body(e.getMessage());
}
}
@PutMapping
public ResponseEntity<Object> updateEmail(@RequestBody UpdateEnvelopeRequestBody requestBody){
try {
return ResponseEntity.ok().body(
docusignService.updateEnvelopeEmail(requestBody.getEmail(), requestBody.getEnvelopeId())
);
} catch (ApiException e) {
return ResponseEntity.internalServerError().body(e.getMessage());
}
}
}
Testando a criação e atualização no Docusign
Vamos criar um novo envelope.
Criando um novo envelope
Vamos enviar uma requisição como no exemplo abaixo:
Os campos riscados devem ser substituído pelo Envelope ID que salvamos anteriormente e pelo e-mail da primeira e segunda pessoa que devem assinar o documento.
Após enviar a requisição você deve receber como response o status HTTP 200
e um body
Ao clicar em Manage no painel devemos ver o envelope criado.
Também já podemos visualizar o documento no email cadastrado
Vamos assinar o documento: clique no link recebido no email, adicione sua assinatura clicando no ícone amarelo e depois clique em Finish
Ao voltar para o painel vemos que o status do envelope foi alterado e que agora só falta 1 dos 2 integrantes assinar.
Podemos conferir também que o email foi enviado para a segunda pessoa que deve assinar o documento.
Atualizando um envelope
Vamos imaginar que ocorreu um engano e que na verdade a segunda pessoa deseja receber o documento em e-mail diferente.
Quando fizemos a criação do envelope recebemos como resposta no body do response um campo chamado envelopeId. Copie o valor retornado nesse campo para usar na próxima requisição
Nossa nova requisição será feita com o ID do envelope que desejamos atualizar e o novo email para onde desejamos enviar o documento.
Após enviar a requisição recebemos um HTTP STATUS 200
como resposta.
Ao analisar a caixa de entrada do e-mail cadastrado podemos ver que já recebemos o documento
Ao abrir o documento, assinar e clicar em concluir finalizamos o processo de assinatura do nosso documento.
Documento finalizado no Docusign.
Próximos passos
Repositório no github: docusign-playground
Uma postagem mais completa com imagens está disponível em: https://victor-vn.github.io/java/docusign/esignature/docusign-first-integration/
Bons estudos!