forsign-sdk 2.0.0
SDK oficial da ForSign para integração com a plataforma de assinaturas eletrônicas.
Requires Python: >=3.8

# Documentação Completa do SDK ForSign para Python (v2)

Bem-vindo à documentação oficial do SDK ForSign para Python! A ForSign oferece a integração mais simples e poderosa do mercado para assinaturas eletrônicas, tornando seus processos mais eficientes, seguros e amigáveis.

Este SDK foi projetado com uma abordagem moderna e assíncrona (`asyncio`), sendo intuitivo e robusto para permitir que você integre um fluxo de assinatura digital completo em sua aplicação Python com o mínimo de esforço.

## ✨ Funcionalidades Principais

- **Autenticação Simplificada:** Conecte-se à nossa API usando apenas uma **API Key**.
- **Múltiplos Tipos de Assinatura:** Suporte para clique, desenho, rubrica, texto e carimbos personalizados.
- **Segurança Robusta:** Valide a identidade dos Signatários com **duplo fator de autenticação (2FA)** via E-mail, SMS e WhatsApp.
- **Workflows Flexíveis:** Defina a ordem de assinatura, datas de expiração, finalização manual e redirecionamento pós-assinatura.
- **Formulários Interativos:** Colete dados estruturados diretamente no documento com campos de texto, checkboxes e listas de seleção.
- **Anexos Seguros:** Solicite documentos adicionais (como RG, CNH) dos Signatários durante o processo e gerencie-os via API.
- **Gerenciamento Completo do Ciclo de Vida:** Tenha controle total sobre as operações: crie, finalize, cancele, baixe documentos e gerencie anexos de forma programática.
- **Posicionamento de Assinatura Inteligente:** Posicione assinaturas com coordenadas precisas ou usando **tags de texto** (ex: `{{assinatura_cliente}}`) diretamente no seu documento PDF para máxima flexibilidade.
- **Totalmente Assíncrono:** Construído sobre `asyncio` e `aiohttp` para alta performance em aplicações modernas.

---

## ⚙️ 1. Começando (Getting Started)

### 1.1. Requisitos

- **Python:** 3.8 ou superior
- **aiohttp:** Será instalado como dependência do SDK.

### 1.2. Instalação

```bash
pip install forsign-sdk
```

### 1.3. Configuração e Autenticação

A autenticação é feita de forma simples através da sua API Key.

```python
import asyncio
from forsign_sdk import ForSignClient, ApiKeyCredential

# 1. Crie uma instância do cliente ForSign.
client = ForSignClient()

# 2. Substitua "SUA_API_KEY_AQUI" pela chave obtida no painel de desenvolvedor.
API_KEY = "SUA_API_KEY_AQUI"

# 3. Crie o objeto de credencial.
credential = ApiKeyCredential(API_KEY)

# 4. Defina a credencial no cliente.
client.set_credential(credential)

print("Cliente ForSign configurado com sucesso!")

# Lembre-se de fechar o cliente ao final do seu programa para liberar recursos.
# Em uma aplicação real, você pode gerenciar o ciclo de vida do cliente de forma mais robusta.
# Exemplo de uso com 'async with':
async def main():
    async with ForSignClient() as client:
        client.set_credential(ApiKeyCredential(API_KEY))
        # ... suas chamadas de API aqui ...

# asyncio.run(main())
```

> **⚠️ Dica de Segurança:** Nunca exponha sua API Key diretamente no código. Utilize variáveis de ambiente ou um sistema de gerenciamento de segredos para armazená-la de forma segura.

---

## 🚀 2. Guia Rápido: Sua Primeira Assinatura

Este guia mostra o fluxo completo para enviar um documento para assinatura em 5 passos simples.

### Passo 1: Upload do Documento

Envie o documento PDF para a plataforma ForSign.

```python
from forsign_sdk.utils import FileRequest
from forsign_sdk.models.common import FileInformation

# Exemplo de upload a partir de uma URL pública
file_request = await FileRequest.from_url(
    "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
    "contrato-exemplo.pdf"
)

# Envia o arquivo para a API
upload_response = await client.upload_file(file_request)

# O objeto 'FileInformation' é essencial para os próximos passos
file_info = FileInformation(
    id=upload_response.data.id,
    description=upload_response.data.file_name
)

print(f"Arquivo '{file_info.description}' enviado! ID: {file_info.id}")
```

### Passo 2: Definir o Signatário

O `Signer` representa uma pessoa que irá assinar o documento.

```python
from forsign_sdk import Signer, EmailNotification, EmailDoubleAuthentication, DefaultSignatureType, SignatureType

signer = Signer(
    name="João da Silva",
    email="joao.silva@example.com",
    # Como o signatário será notificado?
    notification_type=EmailNotification("joao.silva@example.com"),
    # ✨ RECOMENDADO: Adicione um método de Duplo Fator de Autenticação (2FA)
    double_authentication_method=EmailDoubleAuthentication("joao.silva@example.com"),
    # Que tipo de assinatura ele poderá usar?
    signature_type=DefaultSignatureType(SignatureType.USER_CHOICE)
)
```

### Passo 3: Posicionar a Assinatura

Defina onde no documento o signatário deve assinar.

```python
# Adicione um campo de assinatura na página 1, posicionado a 70% da largura e 80% da altura.
signer.add_signature_in_position(file_info, page=1, x="70%", y="80%")
```

### Passo 4: Construir e Criar a Operação

Use o `OperationBuilder` para montar a operação com todas as regras.

```python
from datetime import datetime, timedelta
from forsign_sdk import OperationBuilder

operation_request = (
    OperationBuilder.initialize_with_name("Contrato de Prestação de Serviços")
    .set_expiration_date(datetime.now() + timedelta(days=15))
    .with_external_id("CONTRATO-PY-12345")
    .with_redirect_url("https://seusite.com/obrigado/{operationId}")
    .add_signer(signer)
    .build()
)

# Envia a requisição para a API da ForSign
operation_response = await client.create_operation(operation_request)
```

### Passo 5: Obter e Usar a URL de Assinatura

A resposta da API contém as URLs de assinatura exclusivas para cada participante.

```python
print(f"Operação criada com sucesso! ID: {operation_response.id}")

for member in operation_response.members:
    print(f"--- Assinante: {member.name} ---")
    print(f"ID do Membro: {member.id}")
    print(f"URL de Assinatura: {member.sign_url}")
    print("------------------------------------")
```

---

## 📚 3. Funcionalidades Avançadas

Aprofunde-se nas funcionalidades mais poderosas do SDK.

### 3.1. Configurando Signatários em Detalhes

#### Autenticação Dupla (2FA)

```python
from forsign_sdk import SmsDoubleAuthentication, WhatsappDoubleAuthentication

# 2FA por SMS (o telefone do Signer deve ser informado)
signer.double_authentication_method = SmsDoubleAuthentication("11987654321")

# 2FA por WhatsApp (o telefone do Signer deve ser informado)
signer.double_authentication_method = WhatsappDoubleAuthentication("11987654321")
```

### 3.2. Posicionamento de Assinatura Inteligente

#### a) Por Coordenadas
Posicionamento exato usando percentuais.

```python
# Assinatura na página 1, 50% da largura, 60% da altura
signer.add_signature_in_position(file_info, 1, '50%', '60%')

# Rubrica na página 2, 85% da largura, 95% da altura (canto inferior direito)
signer.add_rubric_in_position(file_info, 2, '85%', '95%')
```

#### b) Por Tag de Texto
Este é o método mais flexível. Adicione um texto padrão (uma "tag") no seu documento PDF, como `{{assinatura_cliente}}`. O SDK irá encontrar essa tag e posicionar a assinatura sobre ela.

```python
from forsign_sdk import TagPosition

# O documento PDF deve conter o texto "{{assinatura_cliente}}"
tag_position = TagPosition(file_info, '{{assinatura_cliente}}')
signer.set_tag_signature_position(tag_position)
```

### 3.3. Formulários Interativos

Colete informações diretamente no documento PDF.

```python
from forsign_sdk import TextFormField, CheckboxFormField, SelectFormField, FormFieldPosition

# 1. Campo de Texto (para CPF, endereço, etc.)
text_field = (
    TextFormField.with_name('CPF')
    .with_instructions('Digite seu CPF')
    .is_required()
    .on_position(FormFieldPosition(file_info, 1, '30%', '40%'))
)

# 2. Caixa de Seleção (para aceite de termos)
checkbox_field = (
    CheckboxFormField.with_name('Aceite dos Termos')
    .with_instructions('Li e concordo com os termos.')
    .is_required()
    .with_options(['Sim, eu aceito'])
    .on_position(FormFieldPosition(file_info, 1, '30%', '50%'))
)

# 3. Lista Suspensa (Select)
select_field = (
    SelectFormField.with_name('Plano Contratado')
    .is_required()
    .with_options(['Básico', 'Intermediário', 'Avançado'])
    .on_position(FormFieldPosition(file_info, 1, '30%', '60%'))
)

# Adicione os campos ao Signatário
signer.add_form_field(text_field)
signer.add_form_field(checkbox_field)
signer.add_form_field(select_field)
```

### 3.4. Solicitando Anexos

Peça ao Signatário para enviar arquivos durante o processo de assinatura.

```python
from forsign_sdk import Attachment, AttachmentFileType, InputAttachmentType

# Define um anexo obrigatório para o documento de identidade
id_attachment = (
    Attachment('Documento de Identidade', 'Envie uma foto do seu RG ou CNH', True)
    # Define os tipos de arquivo permitidos
    .permit_file_type(AttachmentFileType.PNG)
    .permit_file_type(AttachmentFileType.JPEG)
    .permit_file_type(AttachmentFileType.PDF)
    # Define como o usuário pode enviar o anexo
    .permit_attachment_by_input(InputAttachmentType.UPLOAD_FILE)
    .permit_attachment_by_input(InputAttachmentType.CAMERA_SIDE_FRONT)
)

# Adiciona a solicitação de anexo ao Signatário
signer.request_attachment(id_attachment)
```

### 3.5. Personalizando a Operação (OperationBuilder)

```python
from forsign_sdk import Language

operation_request = (
    OperationBuilder.initialize_with_name("Acordo de Confidencialidade")
    # Define uma ordem de assinatura. O segundo signatário só assina após o primeiro.
    .set_signers_order_requirement(True)
    # Define o idioma da interface de assinatura (pt-BR, en, es).
    .set_language(Language.PORTUGUESE)
    # Define que a operação só será finalizada manualmente via API ou painel.
    .set_manual_finish(True)
    # Adiciona um observador que recebe notificações, mas não assina.
    .add_observer("Departamento Jurídico", "juridico@empresa.com")
    .add_signer(signer1)  # Ordem 1
    .add_signer(signer2)  # Ordem 2
    .build()
)
```

---

## 🎛️ 4. Gerenciamento do Ciclo de Vida da Operação

Após a criação, você pode gerenciar a operação usando seu `operationId`.

| Método                               | Descrição                                                                              |
| ------------------------------------ | -------------------------------------------------------------------------------------- |
| `complete_operation(op_id)`          | Finaliza uma operação que está aguardando conclusão manual.                              |
| `cancel_operation(op_id, motivo)`    | Cancela uma operação em andamento.                                                     |
| `download_operation_zip(op_id)`      | Baixa um arquivo .zip contendo os documentos assinados e a trilha de auditoria.        |
| `set_operation_manual_completion(op_id)`  | Muda o modo de finalização de uma operação para manual.                                |
| `set_operation_automatic_completion(op_id, data)` | Define uma data/hora para a finalização automática da operação.                        |

### Exemplo de Gerenciamento

```python
operation_id = 12345  # ID da sua operação

# Exemplo 1: Finalizar uma operação manualmente
try:
    response = await client.complete_operation(operation_id)
    print(response.message)
except ForSignApiException as e:
    print(f"Erro ao finalizar: {e.status_code} - {e.message}")

# Exemplo 2: Baixar o pacote de documentos (ZIP)
try:
    zip_response = await client.download_operation_zip(operation_id)
    
    # O SDK fornece um método auxiliar para salvar o arquivo
    file_path = zip_response.save_to_directory("downloads/")
    print(f"Operação baixada e salva em: {file_path}")
except ForSignApiException as e:
    print(f"Erro ao baixar ZIP: {e.status_code} - {e.message}")
```

---

## 📂 5. Gerenciamento de Anexos dos Signatários

Se sua operação solicita anexos, você pode aprová-los, rejeitá-los ou baixá-los via API.

| Método                       | Descrição                                                                      |
| ---------------------------- | ------------------------------------------------------------------------------ |
| `get_member_attachments(member_id)`  | Lista todos os anexos de um membro (participante) da operação.                 |
| `approve_attachments(member_id, ids)`    | Aprova uma lista de anexos enviados por um membro.                             |
| `reject_attachments(member_id, anexos_rejeitados)`     | Rejeita anexos, fornecendo um motivo para cada um.                             |
| `download_attachment(attachment_id)`    | Baixa o conteúdo de um anexo específico.                                       |

### Exemplo de Gerenciamento de Anexos

```python
from forsign_sdk.models.responses import RejectedAttachment

member_id = 56789  # ID do membro (retornado na criação da operação)

try:
    # 1. Listar anexos de um membro
    attachments = await client.get_member_attachments(member_id)
    uploaded_attachments = [a for a in attachments if a.is_uploaded]

    if not uploaded_attachments:
        print("Nenhum anexo foi enviado por este membro ainda.")
    else:
        # 2. Aprovar o primeiro anexo
        first_attachment_id = uploaded_attachments[0].id
        await client.approve_attachments(member_id, [first_attachment_id])
        print(f"Anexo ID {first_attachment_id} aprovado.")

        # 3. Rejeitar outro anexo (se existir)
        if len(uploaded_attachments) > 1:
            second_attachment_id = uploaded_attachments[1].id
            rejections = [RejectedAttachment(id=second_attachment_id, reason="A imagem está com baixa resolução.")]
            await client.reject_attachments(member_id, rejections)
            print(f"Anexo ID {second_attachment_id} rejeitado.")

        # 4. Baixar um anexo aprovado
        download_response = await client.download_attachment(first_attachment_id)
        with open(f"downloads/{download_response.file_name}", "wb") as f:
            f.write(download_response.content)
        print(f"Anexo '{download_response.file_name}' baixado.")
except ForSignApiException as e:
    print(f"Erro no gerenciamento de anexos: {e.status_code} - {e.message}")
```

---

## 🔌 6. Tratamento de Erros

O SDK utiliza a exceção `ForSignApiException` para sinalizar erros da API. É crucial envolver suas chamadas em blocos `try...except`.

```python
from forsign_sdk import ForSignApiException

try:
    # ... seu código que chama a API ...
    operation_response = await client.create_operation(operation_request)

except ForSignApiException as e:
    print(f"Erro na API: {e.message}")
    print(f"Status Code: {e.status_code}")
    if e.messages:
        print("Detalhes do erro:")
        for msg in e.messages:
            # A API pode retornar uma lista de dicionários com 'key' e 'value' ou apenas uma lista de strings.
            if isinstance(msg, dict):
                print(f"  - Campo '{msg.get('key')}': {msg.get('value')}")
            else:
                print(f"  - {msg}")

except Exception as e:
    print(f"Erro inesperado: {e}")
```

---

## 💡 7. Boas Práticas e Dicas

- **Webhooks:** Para monitorar o status das suas operações em tempo real, recomendamos o uso de **Webhooks**. Esta abordagem é muito superior a consultar o status de uma operação repetidamente (polling), pois é mais eficiente e fornece atualizações instantâneas. Configure sua URL de webhook no painel de desenvolvedor da ForSign.
- **Gerenciamento do Cliente:** Em aplicações web ou de longa duração, instancie o `ForSignClient` uma vez e reutilize-o. O `async with` é uma ótima maneira de garantir que a sessão de rede seja fechada corretamente.
- **Idempotência:** Para operações críticas, considere usar o parâmetro `with_external_id` com um identificador único para evitar a criação de operações duplicadas em caso de falhas de rede.