[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:

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

documento .docx que sera o arquivo de template

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:

enviando requisicao para criar envelope para o docusign via postman

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.

visualizando body da requisicao

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.

visualizando status concluido do envelope 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!