prhost / systax-sdk
PHP SDK for the Systax API (NCM and CEST lookups)
Requires
- php: ^8.1
- guzzlehttp/guzzle: ^7.0
Requires (Dev)
- phpunit/phpunit: ^10.0
README
⚠️ Aviso: Este SDK é um projeto não oficial e não possui qualquer vínculo, endosso ou suporte da Systax. É fornecido sem garantias de qualquer tipo. Use por sua conta e risco.
SDK PHP para integração com a API Systax, cobrindo os recursos de NCM e CEST (v1 e v2).
- PHP 8.1+
- GuzzleHttp 7.x
- Autenticação JWT automática (com cache e renovação)
- Credenciais de homologação embutidas como padrão
Índice
- Instalação
- Instanciação
- Autenticação
- Recurso: CEST v1
- Recurso: CEST v2
- Recurso: NCM
- DTOs de retorno
- Tratamento de erros
- Testes unitários
- Estrutura de arquivos
- Licença
Instalação
composer require prhost/systax-sdk
Requisitos: PHP 8.1+ e GuzzleHttp 7.x (instalado automaticamente como dependência).
Instanciação
use Prhost\SystaxSdk\SystaxClient; // Credenciais de homologação (padrão — superdemo.ws / systax741) $client = new SystaxClient(); // Credenciais de produção (override) $client = new SystaxClient( username: 'sua_empresa@usuario', password: 'sua_senha_producao', ); // Override completo (incluindo base URL, útil para staging) $client = new SystaxClient( username: 'usuario', password: 'senha', baseUrl: 'https://app.systax.com.br', );
Assinatura do construtor:
new SystaxClient( ?string $username = null, // padrão: 'superdemo.ws' ?string $password = null, // padrão: 'systax741' ?string $baseUrl = null, // padrão: 'https://app.systax.com.br' ?ClientInterface $httpClient = null, // injeção de Guzzle customizado (testes) )
Autenticação
O SDK gerencia o token JWT automaticamente:
- Na primeira chamada a qualquer recurso, obtém o token via
GET /auth/access-tokencom Basic Auth. - O token é cacheado em memória pelo tempo de
expires_in(retornado pela API). - Quando o token está prestes a expirar (≤ 60 segundos), é renovado automaticamente via
GET /auth/refresh-token. - Se a renovação falhar, um novo token é obtido com Basic Auth.
Você não precisa gerenciar tokens manualmente. Se necessário:
// Forçar busca de novo token $client->getTokenManager()->fetchToken(); // Forçar renovação de token existente $client->getTokenManager()->refreshToken(); // Invalidar cache (força nova autenticação na próxima chamada) $client->getTokenManager()->clearToken(); // Inspecionar token atual em cache $token = $client->getTokenManager()->getCachedToken(); // null se não autenticado
Recurso: CEST v1
Endpoint: POST https://app.systax.com.br/cest
Retorna os códigos CEST para um NCM informado.
Criando itens de consulta
use Prhost\SystaxSdk\DTO\CestItem; // Mínimo — apenas NCM (obrigatório) $item = CestItem::make('39181000'); // Com todos os parâmetros opcionais $item = CestItem::make( ncm: '39181000', exTipi: '01', // Código EX da TIPI (opcional) uf: 'SP', // UF de consulta (opcional; vazio = sem filtro de UF) data: '2024-01-01', // Data de vigência (opcional; vazio = data atual) id: '1', // Sequencial (opcional) );
Consultando
$results = $client->cest()->query([ CestItem::make('39181000'), CestItem::make('22089000', uf: 'SP'), ]);
Máximo de 100 itens por requisição.
Retorno: CestResult[]
foreach ($results as $result) { $result->id; // string — sequencial $result->ncm; // string — NCM consultado (ex: '39181000') $result->exTipi; // string — EX TIPI consultado $result->uf; // string — UF consultada $result->data; // string — data consultada $result->ncmTabela; // string — NCM na tabela Systax (ex: '3918') $result->cest; // string — código CEST (ex: '1017400') $result->descricao; // string — descrição do CEST $result->ufTabela; // string — UF na tabela CEST Systax $result->exTipiTabela; // string — EX TIPI na tabela Systax $result->statusCodigo; // int — código de status (0 = sucesso) $result->statusMsg; // string — mensagem de status $result->isSuccess(); // bool — true se statusCodigo === 0 }
Exemplo completo
use Prhost\SystaxSdk\SystaxClient; use Prhost\SystaxSdk\DTO\CestItem; use Prhost\SystaxSdk\Exceptions\ApiException; $client = new SystaxClient(); try { $results = $client->cest()->query([CestItem::make('39181000')]); foreach ($results as $result) { echo "NCM: {$result->ncm} | CEST: {$result->cest} | {$result->descricao}\n"; } } catch (ApiException $e) { // Exemplo: NCM inválida, NCM sem CEST, UF inválida, etc. echo "Erro API [{$e->getApiCode()}]: {$e->getApiMessage()}\n"; }
Recurso: CEST v2
Endpoint: POST https://app.systax.com.br/cestv2
Versão 2 da API CEST. Adiciona:
- Paginação via
offset - Sincronização incremental via
ponteiro - Campos
vigenciaDe,vigenciaAtepor item - Metadados de paginação no retorno (
TotalPaginas,totalItens,ultPonteiro)
Consultando
// Página 1 (padrão) $response = $client->cestV2()->query([ CestItem::make('22089000'), ]); // Página específica $response = $client->cestV2()->query([ CestItem::make('22089000'), ], offset: 3); // Com ponteiro para buscar apenas atualizações desde a última sincronização $response = $client->cestV2()->query([ CestItem::make('22089000', ponteiro: '20231013095117'), ]);
Retorno: CestV2Response
$response->totalPaginas; // int — total de páginas disponíveis $response->totalItens; // int — total de itens encontrados $response->ultPonteiro; // string — último ponteiro da tabela (usar para próxima sync) $response->items; // CestV2Result[]
CestV2Result (estende CestResult)
Todos os campos de CestResult mais:
$item->vigenciaDe; // string — data inicial da vigência (ex: '2016-10-01') $item->vigenciaAte; // string — data final da vigência (vazio = vigente) $item->ponteiro; // string — ponteiro de atualização deste item
Exemplo: sincronização incremental
// Primeira sincronização — salvar ultPonteiro $response = $client->cestV2()->query([CestItem::make('22089000')]); $ponteiro = $response->ultPonteiro; // '20231114020001' // Próxima sincronização — buscar apenas o que mudou $response2 = $client->cestV2()->query([ CestItem::make('22089000', ponteiro: $ponteiro), ]); if (empty($response2->items)) { echo "Nenhuma atualização desde o último ponteiro.\n"; }
Recurso: NCM
Endpoint: GET https://app.systax.com.br/api/ncm
Lista NCMs da tabela TIPI da Systax.
Consultando
// Listar tudo (padrão: 100 por página) $response = $client->ncm()->list(); // Filtrar por prefixo de NCM (2 a 8 dígitos) $response = $client->ncm()->list(ncm: '3926'); $response = $client->ncm()->list(ncm: '39269090'); // Filtrar por EX TIPI $response = $client->ncm()->list(ncm: '39269090', exTipi: '01'); // Filtrar por data de vigência $response = $client->ncm()->list(vigenciaEm: '2024-01-01'); // Paginação $response = $client->ncm()->list(ncm: '39', limit: 50, offset: 2); // Ordenação: campo|direção $response = $client->ncm()->list(order: ['ncm|asc', 'descricao|desc']); // Buscar por ID interno Systax $response = $client->ncm()->findById(1039194);
Assinatura completa:
$client->ncm()->list( ?string $ncm = null, // 2–8 dígitos ?string $exTipi = null, ?string $vigenciaEm = null, // YYYY-MM-DD ?int $id = null, // ID interno Systax int $limit = 100, int $offset = 1, ?array $order = null, // ex: ['ncm|asc'] ): NcmResponse
Retorno: NcmResponse
$response->success; // bool — true se consulta OK $response->message; // string — 'ok' em caso de sucesso $response->recordCount; // int — total de registros retornados $response->data; // NcmResult[]
NcmResult
$item->id; // int — ID interno Systax (ex: 1039194) $item->ncm; // string — código NCM de 8 dígitos (ex: '39269090') $item->exTipi; // string|null — código EX TIPI (null se não houver) $item->vigenciaDe; // string|null — data início de vigência (ex: '2022-04-01') $item->vigenciaAte; // string|null — data fim de vigência (null = vigente) $item->aliquota; // float — alíquota IPI (ex: 14.5) $item->descricao; // string — descrição da NCM $item->nota; // string|null — nota complementar de legislação
Exemplo completo
use Prhost\SystaxSdk\SystaxClient; use Prhost\SystaxSdk\Exceptions\AuthException; $client = new SystaxClient(); try { $response = $client->ncm()->list(ncm: '3926', limit: 10); echo "Total: {$response->recordCount}\n"; foreach ($response->data as $ncm) { echo "{$ncm->ncm} — {$ncm->descricao} | Alíquota: {$ncm->aliquota}%\n"; if ($ncm->exTipi) { echo " EX TIPI: {$ncm->exTipi}\n"; } } } catch (AuthException $e) { echo "Falha de autenticação: {$e->getMessage()}\n"; }
DTOs de retorno
| Classe | Arquivo | Descrição |
|---|---|---|
CestItem |
DTO/CestItem.php |
Input: item de consulta CEST |
CestResult |
DTO/CestResult.php |
Output CEST v1 por item |
CestV2Result |
DTO/CestV2Result.php |
Output CEST v2 por item (estende CestResult) |
CestV2Response |
DTO/CestV2Response.php |
Wrapper paginado CEST v2 |
NcmResult |
DTO/NcmResult.php |
Output NCM por item |
NcmResponse |
DTO/NcmResponse.php |
Wrapper resposta NCM |
TokenResponse |
DTO/TokenResponse.php |
Resposta da API de token |
Tratamento de erros
Todas as exceções estendem Prhost\SystaxSdk\Exceptions\SystaxException (que estende RuntimeException).
| Exceção | Quando é lançada |
|---|---|
AuthException |
HTTP 400/401 nas requisições de token ou recursos; credenciais inválidas; token expirado |
ApiException |
Resposta HTTP 200, mas com codigo != 0 no item (ex: NCM inválida, NCM sem CEST) |
SystaxException |
Base — captura qualquer erro do SDK |
Códigos de erro da API CEST
| Código | Mensagem |
|---|---|
| 0 | Sucesso |
| 1 | NCM inválida |
| 2 | NCM sem CEST |
| 3 | UF inválida |
| 4 | EX TIPI inválida |
| 5 | Data inválida |
| 6 | ID inválido |
| 7 | Ponteiro inválido |
| 8 | Não existem atualizações |
Exemplo de tratamento completo
use Prhost\SystaxSdk\Exceptions\AuthException; use Prhost\SystaxSdk\Exceptions\ApiException; use Prhost\SystaxSdk\Exceptions\SystaxException; try { $results = $client->cest()->query([CestItem::make('99999999')]); } catch (AuthException $e) { // Problema de autenticação — verificar credenciais ou token logger()->error('Systax auth error', ['message' => $e->getMessage()]); } catch (ApiException $e) { // Erro de negócio (NCM inválida, sem CEST, etc.) logger()->warning('Systax API error', [ 'code' => $e->getApiCode(), 'message' => $e->getApiMessage(), ]); } catch (SystaxException $e) { // Qualquer outro erro do SDK logger()->error('Systax SDK error', ['message' => $e->getMessage()]); }
Testes unitários
Os testes usam GuzzleHttp\Handler\MockHandler — nenhuma chamada real à API é feita. São 50 testes cobrindo todos os recursos, casos de sucesso e de erro.
Rodando os testes
composer install ./vendor/bin/phpunit --testdox
Saída esperada:
PHPUnit 10.5.63 by Sebastian Bergmann and contributors.
Runtime: PHP 8.4.x
Cest Resource (Prhost\SystaxSdk\Tests\Unit\Resources\CestResource)
✔ Query returns array of cest results
✔ Query parses ncm and cest correctly
✔ Query returns multiple results for same ncm
✔ Query uses cached token on second call
✔ Cest item make builds array correctly
✔ Cest item omits empty fields
✔ Query throws api exception on ncm invalida
✔ Query throws api exception on ncm sem cest
✔ Query throws auth exception on 400
✔ Query throws auth exception on 401
Cest V2Resource (Prhost\SystaxSdk\Tests\Unit\Resources\CestV2Resource)
✔ Query returns cest v2 response
✔ Query parses pagination metadata
✔ Query parses items as cest v2 results
✔ Query parses cest and vigencia
✔ Query with custom offset
✔ Query with ponteiro item
✔ Query throws auth exception on 400
✔ Query throws auth exception on 401
Ncm Resource (Prhost\SystaxSdk\Tests\Unit\Resources\NcmResource)
✔ List returns ncm response
✔ List parses record count
✔ List parses ncm results
✔ List parses ncm result fields
✔ List parses second result with note and aliquota
✔ Find by id calls list with id param
✔ List without filters returns response
✔ List throws auth exception on 400
✔ List throws auth exception on 401
Systax Client (Prhost\SystaxSdk\Tests\Unit\SystaxClient)
✔ Default credentials are set
✔ Default base url is set
✔ Custom username overrides default
✔ Custom password overrides default
✔ Custom credentials pair overrides both
✔ Custom base url overrides default
✔ Base url trailing slash is stripped
✔ Cest returns cest resource
✔ Cest v2 returns cest v2 resource
✔ Ncm returns ncm resource
✔ Cest returns same instance on repeated calls
✔ Ncm returns same instance on repeated calls
✔ Cest v2 returns same instance on repeated calls
✔ Get credentials returns array with username password
Token Manager (Prhost\SystaxSdk\Tests\Unit\Auth\TokenManager)
✔ Fetch token returns token string
✔ Fetch token populates cache
✔ Fetch token throws auth exception on 401
✔ Fetch token throws auth exception on 400
✔ Fetch token throws auth exception when success false 200
✔ Ensure valid token does not re fetch when cached
✔ Clear token nullifies cache
✔ Refresh token fetches fresh when no cached token
✔ Refresh token throws auth exception on 401
OK (50 tests, 83 assertions)
Localização dos testes
tests/
├── Fixtures/
│ ├── auth_error_response.json — resposta 401 simulada
│ ├── token_response.json — resposta de token válido
│ ├── cest_response.json — resposta CEST v1
│ ├── cestv2_response.json — resposta CEST v2 (com paginação)
│ └── ncm_response.json — resposta NCM
└── Unit/
├── Auth/
│ └── TokenManagerTest.php — 9 testes: fetch, cache, refresh, erros
├── Resources/
│ ├── CestResourceTest.php — 10 testes: parse, erros, cache token
│ ├── CestV2ResourceTest.php — 8 testes: paginação, ponteiro, vigência
│ └── NcmResourceTest.php — 9 testes: list, findById, campos, erros
└── SystaxClientTest.php — 14 testes: credenciais, lazy resources
Adicionando novos testes
Os testes usam GuzzleHttp\Handler\MockHandler para interceptar as requisições HTTP. Padrão a seguir:
use GuzzleHttp\Client; use GuzzleHttp\Handler\MockHandler; use GuzzleHttp\HandlerStack; use GuzzleHttp\Psr7\Response; $mock = new MockHandler([ new Response(200, [], '{"success":true,"status":"OK","token":"jwt...","expires_in":3600}'), new Response(200, [], file_get_contents(__DIR__ . '/../../Fixtures/cest_response.json')), ]); $stack = HandlerStack::create($mock); $http = new Client(['handler' => $stack, 'http_errors' => false]);
A ordem das respostas no array do MockHandler corresponde à ordem das chamadas HTTP feitas pelo SDK (primeiro fetchToken, depois o recurso).
Estrutura de arquivos
├── .gitignore
├── composer.json
├── phpunit.xml
├── src/
│ ├── SystaxClient.php
│ ├── Config.php
│ ├── Auth/
│ │ └── TokenManager.php
│ ├── DTO/
│ │ ├── CestItem.php
│ │ ├── CestResult.php
│ │ ├── CestV2Result.php
│ │ ├── CestV2Response.php
│ │ ├── NcmResult.php
│ │ ├── NcmResponse.php
│ │ └── TokenResponse.php
│ ├── Resources/
│ │ ├── CestResource.php
│ │ ├── CestV2Resource.php
│ │ └── NcmResource.php
│ └── Exceptions/
│ ├── SystaxException.php
│ ├── AuthException.php
│ └── ApiException.php
└── tests/
├── Fixtures/
│ ├── token_response.json
│ ├── auth_error_response.json
│ ├── cest_response.json
│ ├── cestv2_response.json
│ └── ncm_response.json
└── Unit/
├── Auth/
│ └── TokenManagerTest.php
├── Resources/
│ ├── CestResourceTest.php
│ ├── CestV2ResourceTest.php
│ └── NcmResourceTest.php
└── SystaxClientTest.php
Referências
- Swagger Systax Geral
- Documentação API de Token
- Documentação API CEST v1
- Documentação API CEST v2
- Documentação API NCM
Licença
Desenvolvido por Kallef Alexandre