prhost/systax-sdk

PHP SDK for the Systax API (NCM and CEST lookups)

Maintainers

Package info

github.com/prhost/systax-sdk

pkg:composer/prhost/systax-sdk

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

v1.0.0 2026-02-22 23:18 UTC

This package is auto-updated.

Last update: 2026-02-22 23:19:56 UTC


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

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:

  1. Na primeira chamada a qualquer recurso, obtém o token via GET /auth/access-token com Basic Auth.
  2. O token é cacheado em memória pelo tempo de expires_in (retornado pela API).
  3. Quando o token está prestes a expirar (≤ 60 segundos), é renovado automaticamente via GET /auth/refresh-token.
  4. 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, vigenciaAte por 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\MockHandlernenhuma 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

Licença

MIT

Desenvolvido por Kallef Alexandre