Impressão de Comprovante

Como imprimir comprovantes via terminal POS

A API da Nuvende permite a criação e impressão de documentos através dos terminais POS, fornecendo uma funcionalidade essencial para o fluxo de pagamentos. Esta funcionalidade garante que tanto o comerciante quanto o cliente tenham evidência física da transação realizada.

Conceitos Fundamentais

O que são Documentos/Comprovantes?

Documentos são elementos que podem ser impressos ou processados por um terminal POS, contendo linhas de texto, QR codes ou imagens. Eles servem como evidência legal da transação e são fundamentais para:

  • Comprovação de Pagamento: Evidência física de que a transação foi realizada
  • Controle Fiscal: Documentos necessários para fins de auditoria e compliance
  • Satisfação do Cliente: Comprovante para o cliente levar consigo
  • Backup de Transações: Respaldo físico das operações realizadas

Por que a Impressão de Documentos é Importante?

A impressão de documentos é crucial no ambiente de pagamentos porque:

  • Atende Requisitos Legais: Muitas regulamentações exigem comprovantes físicos
  • Melhora a Experiência do Cliente: Cliente recebe confirmação imediata da transação
  • Facilita Controle Interno: Comerciante tem controle físico das vendas
  • Reduz Disputas: Comprovante serve como evidência em caso de questionamentos
  • Integra com Sistemas: Pode ser integrado com sistemas de gestão e ERP

Funcionalidades Disponíveis

  • Criar Documento: Criar documento com linhas de texto, QR codes ou imagens
  • Atualizar Documento: Modificar documento existente
  • Imprimir no Terminal: Enviar comando de impressão diretamente para o terminal POS
  • Múltiplos Tipos de Conteúdo: Suporte a texto, QR codes e imagens

Implementação Técnica

Endpoints Disponíveis

Baseado na especificação OpenAPI da Nuvende:

  • POST /api/v2/pagamentos/documents/ - Criar documento
  • PUT /api/v2/pagamentos/documents/{id} - Atualizar documento

Estrutura de Dados

Headers Obrigatórios

Authorization: Bearer {access_token}
Content-Type: application/json
Accept: application/json

Estrutura de Requisição para Criar Documento

{
  "status": "string",
  "merchant_id": "string (UUID)",
  "url": "string (opcional)",
  "terminal_id": "string (UUID)",
  "token": "Bearer",
  "token_type": "Bearer",
  "lines": [
    {
      "content": "string",
      "type": "string",
      "order": "number"
    }
  ]
}

Estrutura de Linha do Documento

{
  "content": "string",
  "type": "text|qrcode|image",
  "order": "number"
}

Explicação Detalhada dos Campos

Campos Obrigatórios:

  • status: Estado inicial do documento (ex: "pending")
  • merchant_id: Identificador único do lojista (UUID)
  • terminal_id: Identificador único do terminal (UUID)
  • token: Valor fixo "Bearer"
  • token_type: Valor fixo "Bearer"
  • lines: Lista de elementos (linhas) a serem incluídos no documento

Campos Opcionais:

  • url: URL para callback/notificação sobre o documento

Estrutura das Linhas:

  • content: Conteúdo da linha (texto, URL para QR code, ou base64 para imagem)
  • type: Tipo de linha ("text", "qrcode", "image")
  • order: Ordem de exibição/impressão da linha no documento

Implementação em Código

Classe de Gerenciamento de Documentos

const axios = require('axios');

class DocumentoService {
  constructor(baseUrl, accessToken) {
    this.baseUrl = baseUrl;
    this.accessToken = accessToken;
  }

  /**
   * Cria um novo documento para impressão
   * @param {Object} dadosDocumento - Dados do documento a ser criado
   * @returns {Promise<Object>} Documento criado com sucesso
   */
  async criarDocumento(dadosDocumento) {
    try {
      console.log('Iniciando criação de documento...');
      console.log('Merchant ID:', dadosDocumento.merchant_id);
      console.log('Terminal ID:', dadosDocumento.terminal_id);
      console.log('Número de linhas:', dadosDocumento.lines.length);
      
      // Validar dados antes de enviar
      const validacao = this.validarDadosDocumento(dadosDocumento);
      if (!validacao.valido) {
        throw new Error(`Dados inválidos: ${validacao.erros.join(', ')}`);
      }
      
      const response = await axios.post(`${this.baseUrl}/api/v2/pagamentos/documents/`, dadosDocumento, {
        headers: {
          'Authorization': `Bearer ${this.accessToken}`,
          'Content-Type': 'application/json',
          'Accept': 'application/json'
        }
      });

      console.log('✅ Documento criado com sucesso');
      console.log('Status:', response.data.status);
      
      return response.data;
      
    } catch (error) {
      console.error('❌ Erro ao criar documento:', error.response?.data || error.message);
      throw this.tratarErro(error);
    }
  }

  /**
   * Atualiza um documento existente
   * @param {string} documentoId - ID do documento a ser atualizado
   * @param {Object} dadosAtualizacao - Novos dados para o documento
   * @returns {Promise<Object>} Documento atualizado
   */
  async atualizarDocumento(documentoId, dadosAtualizacao) {
    try {
      console.log(`Atualizando documento: ${documentoId}`);
      console.log('Dados de atualização:', dadosAtualizacao);
      
      const response = await axios.put(`${this.baseUrl}/api/v2/pagamentos/documents/${documentoId}`, dadosAtualizacao, {
        headers: {
          'Authorization': `Bearer ${this.accessToken}`,
          'Content-Type': 'application/json',
          'Accept': 'application/json'
        }
      });

      console.log('✅ Documento atualizado com sucesso');
      console.log('Novo status:', response.data.status);
      
      return response.data;
      
    } catch (error) {
      console.error('❌ Erro ao atualizar documento:', error.response?.data || error.message);
      throw this.tratarErro(error);
    }
  }

  /**
   * Cria um comprovante de venda simples
   * @param {string} merchantId - ID do comerciante
   * @param {string} terminalId - ID do terminal
   * @param {Object} dadosVenda - Dados da venda
   * @returns {Promise<Object>} Documento criado
   */
  async criarComprovanteVenda(merchantId, terminalId, dadosVenda) {
    try {
      console.log('Criando comprovante de venda...');
      
      const linhas = [
        {
          content: dadosVenda.nomeEstabelecimento || 'Nuvende',
          type: 'text',
          order: 1
        },
        {
          content: '==============================',
          type: 'text',
          order: 2
        },
        {
          content: `Data: ${dadosVenda.data || new Date().toLocaleDateString()}`,
          type: 'text',
          order: 3
        },
        {
          content: `Hora: ${dadosVenda.hora || new Date().toLocaleTimeString()}`,
          type: 'text',
          order: 4
        },
        {
          content: `Valor: R$ ${dadosVenda.valor || '0,00'}`,
          type: 'text',
          order: 5
        },
        {
          content: `Método: ${dadosVenda.metodoPagamento || 'Cartão'}`,
          type: 'text',
          order: 6
        },
        {
          content: '==============================',
          type: 'text',
          order: 7
        },
        {
          content: 'Obrigado pela preferência!',
          type: 'text',
          order: 8
        }
      ];

      // Adicionar QR code se fornecido
      if (dadosVenda.qrCodeUrl) {
        linhas.push({
          content: dadosVenda.qrCodeUrl,
          type: 'qrcode',
          order: 9
        });
      }

      const dadosDocumento = {
        status: 'pending',
        merchant_id: merchantId,
        terminal_id: terminalId,
        token: 'Bearer',
        token_type: 'Bearer',
        lines: linhas
      };

      return await this.criarDocumento(dadosDocumento);
      
    } catch (error) {
      console.error('❌ Erro ao criar comprovante de venda:', error.message);
      throw error;
    }
  }

  /**
   * Cria um documento com QR code
   * @param {string} merchantId - ID do comerciante
   * @param {string} terminalId - ID do terminal
   * @param {string} qrCodeUrl - URL para gerar QR code
   * @param {string} titulo - Título do documento
   * @returns {Promise<Object>} Documento criado
   */
  async criarDocumentoComQRCode(merchantId, terminalId, qrCodeUrl, titulo = 'QR Code') {
    try {
      console.log('Criando documento com QR code...');
      
      const linhas = [
        {
          content: titulo,
          type: 'text',
          order: 1
        },
        {
          content: '==============================',
          type: 'text',
          order: 2
        },
        {
          content: qrCodeUrl,
          type: 'qrcode',
          order: 3
        },
        {
          content: '==============================',
          type: 'text',
          order: 4
        }
      ];

      const dadosDocumento = {
        status: 'pending',
        merchant_id: merchantId,
        terminal_id: terminalId,
        token: 'Bearer',
        token_type: 'Bearer',
        lines: linhas
      };

      return await this.criarDocumento(dadosDocumento);
      
    } catch (error) {
      console.error('❌ Erro ao criar documento com QR code:', error.message);
      throw error;
    }
  }

  /**
   * Cria um documento com imagem
   * @param {string} merchantId - ID do comerciante
   * @param {string} terminalId - ID do terminal
   * @param {string} imagemBase64 - Imagem em base64
   * @param {string} titulo - Título do documento
   * @returns {Promise<Object>} Documento criado
   */
  async criarDocumentoComImagem(merchantId, terminalId, imagemBase64, titulo = 'Imagem') {
    try {
      console.log('Criando documento com imagem...');
      
      const linhas = [
        {
          content: titulo,
          type: 'text',
          order: 1
        },
        {
          content: '==============================',
          type: 'text',
          order: 2
        },
        {
          content: imagemBase64,
          type: 'image',
          order: 3
        },
        {
          content: '==============================',
          type: 'text',
          order: 4
        }
      ];

      const dadosDocumento = {
        status: 'pending',
        merchant_id: merchantId,
        terminal_id: terminalId,
        token: 'Bearer',
        token_type: 'Bearer',
        lines: linhas
      };

      return await this.criarDocumento(dadosDocumento);
      
    } catch (error) {
      console.error('❌ Erro ao criar documento com imagem:', error.message);
      throw error;
    }
  }

  /**
   * Valida dados de documento de forma completa
   * @param {Object} dados - Dados a serem validados
   * @returns {Object} Resultado detalhado da validação
   */
  validarDadosDocumento(dados) {
    const erros = [];

    console.log('Iniciando validação de dados do documento...');

    // Validação do status
    if (!dados.status || dados.status.trim().length === 0) {
      erros.push('Status é obrigatório');
    }

    // Validação do merchant_id
    if (!dados.merchant_id || dados.merchant_id.trim().length === 0) {
      erros.push('ID do comerciante é obrigatório');
    }

    // Validação do terminal_id
    if (!dados.terminal_id || dados.terminal_id.trim().length === 0) {
      erros.push('ID do terminal é obrigatório');
    }

    // Validação do token
    if (!dados.token || dados.token !== 'Bearer') {
      erros.push('Token deve ser "Bearer"');
    }

    // Validação do token_type
    if (!dados.token_type || dados.token_type !== 'Bearer') {
      erros.push('Token type deve ser "Bearer"');
    }

    // Validação das linhas
    if (!dados.lines || !Array.isArray(dados.lines) || dados.lines.length === 0) {
      erros.push('Linhas são obrigatórias e devem ser um array não vazio');
    } else {
      dados.lines.forEach((linha, index) => {
        if (!linha.content || linha.content.trim().length === 0) {
          erros.push(`Conteúdo da linha ${index + 1} é obrigatório`);
        }
        
        if (!linha.type || !['text', 'qrcode', 'image'].includes(linha.type)) {
          erros.push(`Tipo da linha ${index + 1} deve ser "text", "qrcode" ou "image"`);
        }
        
        if (linha.order === undefined || linha.order === null || typeof linha.order !== 'number') {
          erros.push(`Ordem da linha ${index + 1} deve ser um número`);
        }
      });
    }

    const resultado = {
      valido: erros.length === 0,
      erros: erros
    };

    console.log(`Validação concluída: ${erros.length} erros`);
    
    return resultado;
  }

  /**
   * Trata erros da API de forma didática e informativa
   * @param {Error} error - Erro capturado
   * @returns {Error} Erro tratado com informações claras
   */
  tratarErro(error) {
    const status = error.response?.status;
    const data = error.response?.data;

    console.log('Tratando erro da API...');
    console.log('Status HTTP:', status);
    console.log('Dados do erro:', data);

    switch (status) {
      case 400:
        return new Error(`Dados inválidos enviados: ${data?.message || 'Verifique os parâmetros enviados e tente novamente'}`);
      case 401:
        return new Error('Token de acesso inválido ou expirado - faça login novamente');
      case 403:
        return new Error('Permissões insuficientes para criar documentos - verifique suas credenciais');
      case 404:
        return new Error('Documento não encontrado - verifique o ID fornecido');
      case 422:
        return new Error(`Dados não processáveis: ${data?.message || 'Verifique o formato dos dados enviados'}`);
      case 429:
        return new Error('Muitas requisições - aguarde um momento antes de tentar novamente');
      case 500:
        return new Error('Erro interno do servidor - tente novamente mais tarde ou entre em contato com o suporte');
      default:
        return new Error(`Erro inesperado: ${error.message} - entre em contato com o suporte técnico`);
    }
  }
}

// Exemplo de uso prático
async function exemploCompletoImpressaoDocumentos() {
  console.log('🚀 Iniciando exemplo completo de impressão de documentos');
  
  const documentoService = new DocumentoService(
    'https://api-h.nuvende.com.br',
    'seu_access_token_aqui'
  );

  try {
    const merchantId = '9dd1b184-a167-4131-b68a-27be0c20c2b9';
    const terminalId = '32a080cb-eb8a-4a51-9e6b-d1e2d22cbdb9';

    // 1. Criar comprovante de venda
    console.log('\n📄 Passo 1: Criando comprovante de venda...');
    const comprovanteVenda = await documentoService.criarComprovanteVenda(merchantId, terminalId, {
      nomeEstabelecimento: 'Loja Exemplo',
      valor: '150,00',
      metodoPagamento: 'Cartão de Crédito',
      qrCodeUrl: 'https://nuvende.com.br'
    });
    console.log('✅ Comprovante de venda criado');

    // 2. Criar documento com QR code
    console.log('\n📄 Passo 2: Criando documento com QR code...');
    const documentoQR = await documentoService.criarDocumentoComQRCode(
      merchantId, 
      terminalId, 
      'https://nuvende.com.br',
      'Acesse nosso site'
    );
    console.log('✅ Documento com QR code criado');

    // 3. Criar documento personalizado
    console.log('\n📄 Passo 3: Criando documento personalizado...');
    const documentoPersonalizado = await documentoService.criarDocumento({
      status: 'pending',
      merchant_id: merchantId,
      terminal_id: terminalId,
      token: 'Bearer',
      token_type: 'Bearer',
      lines: [
        {
          content: 'Nuvende',
          type: 'text',
          order: 1
        },
        {
          content: 'https://nuvende.com.br',
          type: 'qrcode',
          order: 2
        },
        {
          content: 'Obrigado pela preferência!',
          type: 'text',
          order: 3
        }
      ]
    });
    console.log('✅ Documento personalizado criado');

    console.log('\n🎉 Exemplo completo executado com sucesso!');

  } catch (error) {
    console.error('\n❌ Erro durante execução do exemplo:', error.message);
  }
}

exemploCompletoImpressaoDocumentos();

2025 © Nuvende - CNPJ 38.297.374/0001-93