# SuiteCrmClient — contrato implementado Estado: cliente Laravel implementado e testado com HTTP simulado. Não há SuiteCRM instalado, credenciais criadas, tráfego real ou integração com as manifestações. Não activar apenas por estes testes passarem. ## Ficheiros desta entrega | Ficheiro | Finalidade | | ------------------------------------------------- | ------------------------------------------------- | | `app/Integrations/SuiteCrm/SuiteCrmClient.php` | Cliente OAuth2/JSON:API | | `app/Integrations/SuiteCrm/SuiteCrmException.php` | Erro sanitizado e metadados de recuperação | | `config/suitecrm.php` | Desactivação por defeito, URLs, módulos e limites | | `.env.example` | Variáveis backend sem credenciais reais | | `tests/Feature/SuiteCrm/SuiteCrmClientTest.php` | Testes de contrato local, recuperação e segurança | Sem novas dependências Composer, rotas, migrations ou chamadas desde o frontend. A configuração usa o cache database existente; os testes usam array e não precisam de uma base de dados. Em implantação com vários workers, todos devem partilhar o cache e os locks. ## Contrato HTTP A base configurada é a URL HTTPS da aplicação legacy que contém `Api`; o prefixo real será confirmado na instalação. Todos os caminhos abaixo são relativos a essa base. | Método Laravel | HTTP | Caminho | Contrato | | -------------------- | ----- | -------------------------------------------------------- | ------------------------------------------------------------------------------- | | Autenticação interna | POST | `/Api/access_token` | `grant_type=client_credentials`, client ID/secret; resposta Bearer com validade | | `find` | GET | `/Api/V8/module/{module}/{id}` | Registo autorizado | | `search` | GET | `/Api/V8/module/{module}` | Filtros exactos combinados por AND; página >=1; tamanho 1–100 | | `create` | POST | `/Api/V8/module` | Envelope `data.type` e `data.attributes` | | `update` | PATCH | `/Api/V8/module` | Envelope com tipo, ID e atributos | | `relate` | POST | `/Api/V8/module/{module}/{id}/relationships/{linkField}` | Tipo e ID do registo relacionado | | `fields` | GET | `/Api/V8/meta/fields/{module}` | Metadados para validar o mapeamento | O cliente devolve o documento JSON, preservando paginação. Não segue links de paginação devolvidos pelo servidor; o consumidor pede explicitamente a página seguinte. Módulos são limitados à allowlist de configuração; nomes e IDs não aceitam separadores de caminho. Métodos não são endpoints públicos e não substituem autorização de negócio, validação dos atributos ou permissões da conta técnica. Os formatos foram confrontados com `public/legacy/Api/V8/Config/routes.php` no pacote oficial SuiteCRM 8.10.2 de SHA-256 `ac4693d10ea2d6cd20b3256098781d134599d88878fb24e8dc127038d624f5a8`. Os testes são contratos do consumidor com respostas simuladas; ainda falta provar compatibilidade contra o servidor. ## Tokens e segredos HTTPS e validação do certificado são obrigatórios; redirects são recusados. A URL não pode conter credenciais, query ou fragmento. Tokens são cifrados com a chave Laravel antes de entrar no cache e expiram antecipadamente. Obtenção e invalidação usam locks. Se outra chamada já renovou o token, um 401 atrasado não deve apagar o token novo. Client credentials obtém novo token na expiração; não presume refresh token. Um 401 da API permite uma renovação e repetição. O segundo 401 termina a operação. Alterar o segredo muda o namespace do cache; reiniciar workers/reconstruir o cache de configuração durante rotação. Revogar o cliente antigo no CRM; a mudança local não o revoga remotamente. O segredo deve ser injectado no ambiente do backend por mecanismo privado. Não usar variáveis `VITE_`. O `.env` actual não foi alterado. Não imprimir configuração, payloads ou respostas de autenticação no diagnóstico. ## Retries, circuit breaker e ambiguidade - Consultas: até duas repetições para falhas de rede, 429 e 5xx, com backoff exponencial e jitter. - Retry-After numérico/data é respeitado. Atrasos superiores a cinco segundos são devolvidos ao futuro job, sem bloquear o processo; valor limitado a um dia. - Criação, actualização e relação: não repetir automaticamente após timeout ou 5xx. `outcomeUnknown=true` exige consultar a identidade externa/reconciliar antes de reenviar. - 403, 404, 409 e 422 são erros permanentes neste nível; a lógica de negócio decide a recuperação. - Cinco operações com falha transitória abrem o circuito durante 60 segundos. O contador usa lock. Após o intervalo, operações voltam a ser permitidas; ainda não há controlo de uma única sonda half-open. - Sucesso com JSON inválido é rejeitado; numa escrita, é considerado ambíguo. `retryable` indica natureza transitória, não autorização para repetir uma escrita. O futuro processador da outbox deve respeitar também `outcomeUnknown` e `retryAfter`. O cliente isolado não implementa idempotência distribuída nem fusão de contactos. ## Observabilidade Cada tentativa produz `suitecrm.http` com operação, código HTTP e duração em milissegundos. Não inclui URL, IDs, corpos, atributos ou cabeçalhos. Excepções usam razões enumeradas e não encadeiam a excepção HTTP original. Há atributos SensitiveParameter nos argumentos com dados pessoais/segredos. Esses eventos permitem agregar taxa de falhas/latência; ainda não há exporter, dashboard ou alertas instalados. Instrumentação externa que capture HTTP (debuggers/APM) deve ser configurada para excluir tokens e payloads antes de activar a integração. ## Verificação e limites 15 testes, 62 asserções, sem rede real: desactivação, HTTPS, tokens cifrados, reutilização, expiração, rotação, 401 limitado, formato JSON:API, retry de leitura, escrita ambígua, Retry-After, circuito, módulos/IDs/paginação, sanitização e resposta inválida. PHPStan passou sem erros. A suite completa também passou: 85 testes e 612 asserções. Pint, formatação e lint passaram. Não foi necessário alterar ou testar o frontend visual, pois esta entrega não o modifica. Ainda faltam: testes reais e concorrência entre processos; configuração OAuth; RBAC no CRM; documentos binários; outbox/inbox e jobs; reconciliação; métricas/alertas; relação com consentimento e domínios do portal. Metadados de Documents podem ser enviados por `create` após validação do esquema; não existe upload binário genérico implementado. ## Rollback Manter `SUITECRM_ENABLED=false`. Como nenhum fluxo do portal chama o cliente, esta entrega não altera dados comerciais. Ao remover os ficheiros, retirar também a configuração e variáveis de exemplo. Se o cliente tiver sido activado numa instalação futura, desactivar envios e revogar o OAuth; não considerar isso reversão de registos já escritos no CRM. ## Referências - [Endpoints oficiais](https://docs.suitecrm.com/developer/integration-api/v8/endpoints/) - [Autenticação oficial](https://docs.suitecrm.com/developer/integration-api/v8/authentication/)