authenticate method

Future<void> authenticate({
  1. required String consumerKey,
  2. required String consumerSecret,
  3. required String contratanteNumero,
  4. required String autorPedidoDadosNumero,
  5. String? certificadoDigitalBase64,
  6. String? certificadoDigitalPath,
  7. String? senhaCertificado,
  8. String ambiente = 'trial',
})

Autentica o cliente com a API do SERPRO usando OAuth2 e mTLS

Parâmetros

  • consumerKey: Consumer Key fornecido pelo SERPRO (obrigatório)
  • consumerSecret: Consumer Secret fornecido pelo SERPRO (obrigatório)
  • contratanteNumero: CNPJ da empresa contratante (obrigatório)
  • autorPedidoDadosNumero: CPF/CNPJ do autor da requisição (obrigatório)
  • certificadoDigitalBase64: Certificado P12/PFX em Base64 (produção)
  • certificadoDigitalPath: Caminho do arquivo P12/PFX (produção)
  • senhaCertificado: Senha do certificado (produção)
  • ambiente: 'trial' ou 'producao' (padrão: 'trial')

Exemplos

Trial (sem certificado):

await apiClient.authenticate(
  consumerKey: '06aef429-a981-3ec5-a1f8-71d38d86481e',
  consumerSecret: '06aef429-a981-3ec5-a1f8-71d38d86481e',
  contratanteNumero: '00000000000191',
  autorPedidoDadosNumero: '00000000191',
  ambiente: 'trial',
);

Produção (com certificado em Base64):

await apiClient.authenticate(
  consumerKey: 'sua_key',
  consumerSecret: 'seu_secret',
  contratanteNumero: '12345678000100',
  autorPedidoDadosNumero: '11122233344',
  certificadoDigitalBase64: 'MIIJqQIBAzCCCW8GCSqGSIb3...',
  senhaCertificado: 'senha123',
  ambiente: 'producao',
);

Produção (com certificado em arquivo):

await apiClient.authenticate(
  consumerKey: 'sua_key',
  consumerSecret: 'seu_secret',
  contratanteNumero: '12345678000100',
  autorPedidoDadosNumero: '11122233344',
  certificadoDigitalPath: '/caminho/certificado.pfx',
  senhaCertificado: 'senha123',
  ambiente: 'producao',
);

Erros Retornados (formato JSON)

{
  "mensagem": "Consumer Secret não informado ou inválido",
  "status": 400,
  "resposta": "Campo 'consumerSecret' é obrigatório"
}

Implementation

Future<void> authenticate({
  required String consumerKey,
  required String consumerSecret,
  required String contratanteNumero,
  required String autorPedidoDadosNumero,
  String? certificadoDigitalBase64,
  String? certificadoDigitalPath,
  String? senhaCertificado,
  String ambiente = 'trial',
}) async {
  try {
    // Usar autenticação via Cloud Function se URL estiver configurada
    if (_urlAutenticacao != null && _urlAutenticacao!.isNotEmpty) {
      // Armazenar certificado para uso no proxy
      _cloudFunctionCertBase64 = certificadoDigitalBase64;
      _cloudFunctionCertPassword = senhaCertificado;

      await _authenticateViaCloudFunction(
        consumerKey: consumerKey,
        consumerSecret: consumerSecret,
        contratanteNumero: contratanteNumero,
        autorPedidoDadosNumero: autorPedidoDadosNumero,
        ambiente: ambiente,
      );
      return;
    }

    // 0. Verificar se é uma nova autenticação com dados diferentes
    final novoContratante = contratanteNumero.trim();
    final novoAutor = autorPedidoDadosNumero.trim();

    if (_storedCredentials != null &&
        (_storedCredentials!.contratanteNumero != novoContratante ||
            _storedCredentials!.autorPedidoDadosNumero != novoAutor)) {
      // Limpar dados da autenticação anterior para evitar conflitos
      clearAuthentication();
    }

    // 1. Validar ambiente
    if (ambiente != 'trial' && ambiente != 'producao') {
      throw _buildErrorResponse(
        mensagem: 'Ambiente inválido',
        status: 400,
        resposta:
            'Ambiente deve ser "trial" ou "producao". Recebido: "$ambiente"',
      );
    }
    _ambiente = ambiente;

    // 2. Validar credenciais obrigatórias
    if (consumerKey.trim().isEmpty) {
      throw _buildErrorResponse(
        mensagem: 'Consumer Key não informado ou inválido',
        status: 400,
        resposta: 'Campo "consumerKey" é obrigatório e não pode ser vazio',
      );
    }

    if (consumerSecret.trim().isEmpty) {
      throw _buildErrorResponse(
        mensagem: 'Consumer Secret não informado ou inválido',
        status: 400,
        resposta: 'Campo "consumerSecret" é obrigatório e não pode ser vazio',
      );
    }

    if (contratanteNumero.trim().isEmpty) {
      throw _buildErrorResponse(
        mensagem: 'Número do contratante não informado',
        status: 400,
        resposta: 'Campo "contratanteNumero" é obrigatório (CNPJ da empresa)',
      );
    }

    if (autorPedidoDadosNumero.trim().isEmpty) {
      throw _buildErrorResponse(
        mensagem: 'Número do autor não informado',
        status: 400,
        resposta:
            'Campo "autorPedidoDadosNumero" é obrigatório (CPF/CNPJ do autor)',
      );
    }

    // 3. Validar certificado em produção
    if (ambiente == 'producao') {
      final temCertificadoBase64 =
          certificadoDigitalBase64 != null &&
          certificadoDigitalBase64.trim().isNotEmpty;
      final temCertificadoPath =
          certificadoDigitalPath != null &&
          certificadoDigitalPath.trim().isNotEmpty;

      if (!temCertificadoBase64 && !temCertificadoPath) {
        throw _buildErrorResponse(
          mensagem: 'Certificado digital obrigatório em produção',
          status: 400,
          resposta:
              'Para ambiente de produção é necessário informar o certificado digital. '
              'Use "certificadoDigitalBase64" (recomendado) ou "certificadoDigitalPath".',
        );
      }

      if (senhaCertificado == null) {
        throw _buildErrorResponse(
          mensagem: 'Senha do certificado não informada',
          status: 400,
          resposta:
              'Para ambiente de produção é necessário informar a senha do certificado digital (use string vazia "" para certificados sem senha).',
        );
      }
    }

    // 4. Criar credenciais
    final credentials = AuthCredentials(
      consumerKey: consumerKey.trim(),
      consumerSecret: consumerSecret.trim(),
      certPath: certificadoDigitalPath?.trim(),
      certBase64: certificadoDigitalBase64?.trim(),
      certPassword: senhaCertificado?.trim(),
      contratanteNumero: contratanteNumero.trim(),
      autorPedidoDadosNumero: autorPedidoDadosNumero.trim(),
      ambiente: ambiente,
    );
    credentials.validate();
    _storedCredentials = credentials;

    // 5. Inicializar HTTP adapter com mTLS (aceita Base64 diretamente - sem arquivo temporário)
    _httpAdapter = HttpClientAdapter();

    await _httpAdapter!.configureMtlsUnified(
      certBase64: certificadoDigitalBase64,
      certPath: certificadoDigitalPath,
      certPassword: senhaCertificado,
      isProduction: ambiente == 'producao',
    );

    // 6. Inicializar serviço de autenticação
    _authService = AuthService(_httpAdapter!, ambiente);

    // 7. Executar autenticação
    _authModel = await _authService!.authenticate(credentials);

    // Token já está armazenado em _authModel
  } on InvalidCredentialsException catch (e) {
    throw _buildErrorResponse(
      mensagem: e.message,
      status: 400,
      resposta: 'Credenciais inválidas',
    );
  } on CertificateException catch (e) {
    throw _buildErrorResponse(
      mensagem: 'Erro no certificado digital',
      status: 400,
      resposta: e.message,
    );
  } on AuthenticationFailedException catch (e) {
    throw _buildErrorResponse(
      mensagem: 'Falha na autenticação',
      status: e.statusCode,
      resposta: e.responseBody ?? e.message,
    );
  } on NetworkAuthException catch (e) {
    throw _buildErrorResponse(
      mensagem: 'Erro de rede durante autenticação',
      status: 0,
      resposta: e.message,
    );
  } catch (e) {
    throw _buildErrorResponse(
      mensagem: 'Erro inesperado durante autenticação',
      status: 500,
      resposta: e.toString(),
    );
  }
}