Cobranças

Criar QR Code

Como criar cobranças Pix via API

A criação de cobranças Pix é uma funcionalidade essencial para receber pagamentos instantâneos através da API da Nuvende. Este processo envolve a geração de QR Codes dinâmicos que permitem aos pagadores efetuar pagamentos de forma rápida e segura.

Conceitos Fundamentais

O que são Cobranças Pix?

Cobranças Pix são solicitações de pagamento que geram QR Codes dinâmicos únicos para cada transação. Diferentemente de QR Codes estáticos, os dinâmicos contêm informações específicas como valor, descrição e identificador único da transação.

QR Code Dinâmico vs Estático

QR Code Dinâmico:

  • Contém informações específicas da cobrança
  • Valor e descrição fixos
  • Identificador único (TXID) para cada cobrança
  • Ideal para e-commerce e vendas específicas

QR Code Estático:

  • Mesmo QR Code para todas as transações
  • Valor definido pelo pagador
  • Ideal para doações e pagamentos variáveis

Fluxo de Criação de Cobrança

  1. Geração do TXID: Identificador único da transação
  2. Validação dos Dados: Verificação de chave Pix e valores
  3. Criação da Cobrança: Registro no sistema da Nuvende
  4. Geração do QR Code: Código dinâmico específico
  5. Disponibilização: QR Code pronto para pagamento

Ordem das chamadas

  1. POST /api/v2/auth/login com scopes cob.write e webhooks.write (referência)
  2. PUT /api/v2/cobranca/cob/{txid} para criar a cobrança (referência)
  3. GET /api/v2/cobranca/cob/{txid} para consultar status (referência)
  4. PUT /api/v2/cobranca/webhooks/{chave} com evento recebimento (referência)

Implementação Técnica

Endpoint Oficial

Cria cobrança dinâmica com o txid na URL (PUT criação, não atualização — atualizar usa PATCH quando disponível):

PUT /api/v2/cobranca/cob/{txid}

Scope: cob.write. Alternativa sem txid na URL: Criar sem txid (POST).

Estrutura da requisição

Headers

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

Não envie header de conta — a conta é a associada ao token.

Body

Validação HTTP (422): apenas chave é obrigatória.

Para obter 200 na prática: também envie valor.original (> 0). Omitir valor.original tende a 500, não 422. O {txid} no path deve ter 26–35 caracteres.

{
  "chave": "[email protected]",
  "valor": { "original": 150.00 },
  "calendario": { "expiracao": 3600 }
}
CampoObrigatórioObservação
chaveSim (422 se ausente)Deve pertencer à conta autenticada
valor.originalSim para 200> 0; omitir → erro 500
calendario.expiracaoRecomendadoOpcional no HTTP; não há default 3600 automático na borda se omitido
nomeRecebedorNãoPreenchido com o titular da chave se omitido
devedor.*NãoOpcional; sem regra “CPF ou CNPJ exclusivo”
valor.modalidadeAlteracaoNãoDefault 0
solicitacaoPagadorNãoOpcional

Referência: PUT Cob {txid}.

Parâmetros Detalhados

Chave Pix

  • Tipo: String UUID
  • Descrição: Chave Pix do recebedor
  • Formato: UUID v4
  • Exemplo: e3fe4462-4c8e-42c1-8360-b41b404ae557

Calendário

  • expiracao: Tempo de vida da cobrança em segundos
  • Padrão: 3600 segundos (1 hora)
  • Máximo: 86400 segundos (24 horas)

Solicitação do Pagador

  • Tipo: String opcional
  • Descrição: Mensagem exibida ao pagador
  • Máximo: 140 caracteres

Nome do Recebedor

  • Obrigatório: Não — preenchido com o titular da chave se omitido
  • Máximo: 200 caracteres

Valor

  • original: Necessário para 200 (> 0). Omitir tende a 500, não 422
  • modalidadeAlteracao: Opcional; default 0 (não alterável). 1 = pagador pode alterar

Calendário

  • expiracao: Opcional no HTTP; recomendado enviar (ex.: 3600). Omitir não aplica default 3600 na borda

Devedor

  • Opcional no PUT de criação
  • Sem regra XOR CPF/CNPJ na validação HTTP; se enviados, formato 11 ou 14 dígitos

Os exemplos de cliente abaixo podem aplicar validações mais rígidas que a API (ex.: exigir nomeRecebedor / XOR de documento). Use a tabela de body acima como contrato.

Implementação em Código

Implementação em Javascript

const axios = require('axios');
const { v4: uuidv4 } = require('uuid');

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

  /**
   * Cria uma nova cobrança Pix com QR Code dinâmico
   * @param {Object} dadosCobranca - Dados da cobrança
   * @param {string} dadosCobranca.chave - Chave Pix do recebedor
   * @param {number} dadosCobranca.valor - Valor da cobrança
   * @param {string} dadosCobranca.nomeRecebedor - Nome do recebedor
   * @param {string} dadosCobranca.solicitacaoPagador - Mensagem para o pagador
   * @param {number} dadosCobranca.expiracao - Tempo de expiração em segundos
   * @param {Object} dadosCobranca.devedor - Dados do devedor
   * @param {string} dadosCobranca.devedor.nome - Nome do devedor
   * @param {string} dadosCobranca.devedor.cpf - CPF do devedor (pessoa física)
   * @param {string} dadosCobranca.devedor.cnpj - CNPJ do devedor (pessoa jurídica)
   * @param {number} dadosCobranca.modalidadeAlteracao - Modalidade de alteração do valor (0 ou 1)
   * @param {string} txid - TXID personalizado (opcional)
   * @returns {Promise<Object>} Dados da cobrança criada
   */
  async criarCobranca(dadosCobranca, txid = null) {
    try {
      // Gerar TXID se não fornecido
      const txidFinal = txid || uuidv4();
      
      console.log(`Criando cobrança Pix com TXID: ${txidFinal}`);
      
      const payload = {
        chave: dadosCobranca.chave,
        solicitacaoPagador: dadosCobranca.solicitacaoPagador || null,
        nomeRecebedor: dadosCobranca.nomeRecebedor,
        calendario: {
          expiracao: dadosCobranca.expiracao || 3600
        },
        valor: {
          original: dadosCobranca.valor.toFixed(2),
          modalidadeAlteracao: dadosCobranca.modalidadeAlteracao || 0
        },
        devedor: {
          nome: dadosCobranca.devedor.nome,
          cpf: dadosCobranca.devedor.cpf || null,
          cnpj: dadosCobranca.devedor.cnpj || null
        }
      };

      const response = await axios.put(
        `${this.baseUrl}/api/v2/cobranca/cob/${txidFinal}`,
        payload,
        {
          headers: {
            'Authorization': `Bearer ${this.accessToken}`,
            'Content-Type': 'application/json',
            'Accept': 'application/json',
          }
        }
      );

      console.log('Cobrança Pix criada com sucesso');
      return {
        txid: txidFinal,
        status: response.data.status,
        qrCode: response.data.pixCopiaECola,
        qrCodeImage: response.data.qrCode,
        valor: response.data.valor.original,
        expiracao: response.data.calendario.expiracao,
        criacao: response.data.calendario.criacao
      };
      
    } catch (error) {
      console.error('Erro ao criar cobrança Pix:', error.response?.data || error.message);
      throw this.tratarErroCobranca(error);
    }
  }

  /**
   * Valida os dados da cobrança antes do envio
   * @param {Object} dados - Dados a serem validados
   * @returns {Object} Resultado da validação
   */
  validarDadosCobranca(dados) {
    const erros = [];

    // Validar chave Pix
    if (!dados.chave || !this.validarUUID(dados.chave)) {
      erros.push('Chave Pix deve ser um UUID válido');
    }

    // Validar nome do recebedor
    if (!dados.nomeRecebedor || dados.nomeRecebedor.trim().length === 0) {
      erros.push('Nome do recebedor é obrigatório');
    }

    if (dados.nomeRecebedor && dados.nomeRecebedor.length > 200) {
      erros.push('Nome do recebedor não pode exceder 200 caracteres');
    }

    // Validar valor
    if (!dados.valor || dados.valor <= 0) {
      erros.push('Valor deve ser maior que zero');
    }

    if (dados.valor > 1000000) {
      erros.push('Valor não pode exceder R$ 1.000.000,00');
    }

    // Validar modalidade de alteração
    if (dados.modalidadeAlteracao !== undefined && ![0, 1].includes(dados.modalidadeAlteracao)) {
      erros.push('Modalidade de alteração deve ser 0 ou 1');
    }

    // Validar dados do devedor
    if (!dados.devedor || !dados.devedor.nome || dados.devedor.nome.trim().length === 0) {
      erros.push('Nome do devedor é obrigatório');
    }

    if (dados.devedor && dados.devedor.nome && dados.devedor.nome.length > 200) {
      erros.push('Nome do devedor não pode exceder 200 caracteres');
    }

    // Validar CPF ou CNPJ (deve ter pelo menos um)
    if (dados.devedor) {
      const temCpf = dados.devedor.cpf && dados.devedor.cpf.trim().length > 0;
      const temCnpj = dados.devedor.cnpj && dados.devedor.cnpj.trim().length > 0;
      
      if (!temCpf && !temCnpj) {
        erros.push('Deve ser informado CPF ou CNPJ do devedor');
      }

      if (temCpf && temCnpj) {
        erros.push('Informe apenas CPF ou CNPJ, nunca ambos');
      }

      // Validar formato do CPF
      if (temCpf && !this.validarCPF(dados.devedor.cpf)) {
        erros.push('CPF deve conter apenas números e ter 11 dígitos');
      }

      // Validar formato do CNPJ
      if (temCnpj && !this.validarCNPJ(dados.devedor.cnpj)) {
        erros.push('CNPJ deve conter apenas números e ter 14 dígitos');
      }
    }

    // Validar expiração
    if (dados.expiracao && (dados.expiracao < 1 || dados.expiracao > 86400)) {
      erros.push('Expiração deve estar entre 1 e 86400 segundos');
    }

    // Validar solicitação do pagador
    if (dados.solicitacaoPagador && dados.solicitacaoPagador.length > 140) {
      erros.push('Solicitação do pagador não pode exceder 140 caracteres');
    }

    return {
      valido: erros.length === 0,
      erros: erros
    };
  }

  /**
   * Valida se uma string é um UUID válido
   * @param {string} uuid - String a ser validada
   * @returns {boolean} True se for UUID válido
   */
  validarUUID(uuid) {
    const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
    return uuidRegex.test(uuid);
  }

  /**
   * Valida formato do CPF
   * @param {string} cpf - CPF a ser validado
   * @returns {boolean} True se formato for válido
   */
  validarCPF(cpf) {
    const cpfLimpo = cpf.replace(/\D/g, '');
    return cpfLimpo.length === 11 && /^\d{11}$/.test(cpfLimpo);
  }

  /**
   * Valida formato do CNPJ
   * @param {string} cnpj - CNPJ a ser validado
   * @returns {boolean} True se formato for válido
   */
  validarCNPJ(cnpj) {
    const cnpjLimpo = cnpj.replace(/\D/g, '');
    return cnpjLimpo.length === 14 && /^\d{14}$/.test(cnpjLimpo);
  }

  /**
   * Trata erros específicos da API de cobranças
   * @param {Error} error - Erro capturado
   * @returns {Error} Erro tratado
   */
  tratarErroCobranca(error) {
    const status = error.response?.status;
    const data = error.response?.data;

    switch (status) {
      case 400:
        return new Error(`Dados inválidos: ${data?.detail || 'Verifique os parâmetros enviados'}`);
      case 401:
        return new Error('Token de acesso inválido ou expirado');
      case 403:
        return new Error('Permissões insuficientes para criar cobranças');
      case 404:
        return new Error('Chave Pix não encontrada');
      case 422:
        return new Error(`Dados não processáveis: ${data?.detail || 'Verifique o formato dos dados'}`);
      case 500:
        return new Error('Erro interno do servidor. Tente novamente mais tarde');
      default:
        return new Error(`Erro inesperado: ${error.message}`);
    }
  }
}

// Exemplo de uso prático
async function exemploCriacaoCobranca() {
  const cobrancaService = new CobrancaPixService(
    'https://api-h.nuvende.com.br',
    'seu_access_token');

  try {
    // Dados da cobrança
    const dadosCobranca = {
      chave: 'e3fe4462-4c8e-42c1-8360-b41b404ae557',
      nomeRecebedor: 'João da Silva',
      valor: 100.00,
      modalidadeAlteracao: 0, // Valor não pode ser alterado
      solicitacaoPagador: 'Pagamento de teste via Pix',
      expiracao: 3600,
      devedor: {
        nome: 'Maria Santos',
        cpf: '39053344705'
        // cnpj: '11222333000181' // Use apenas CPF ou CNPJ
      }
    };

    // Validar dados antes de enviar
    const validacao = cobrancaService.validarDadosCobranca(dadosCobranca);
    if (!validacao.valido) {
      console.error('Dados inválidos:', validacao.erros);
      return;
    }

    // Criar cobrança
    const cobranca = await cobrancaService.criarCobranca(dadosCobranca);
    
    console.log('Cobrança criada:', {
      txid: cobranca.txid,
      valor: cobranca.valor,
      qrCode: cobranca.qrCode,
      expiracao: cobranca.expiracao
    });

    // Exibir QR Code para o usuário
    console.log('QR Code para pagamento:', cobranca.qrCodeImage);

  } catch (error) {
    console.error('Erro na criação da cobrança:', error.message);
  }
}

Implementação em Python

import requests
import uuid
import json
from typing import Dict, Optional, List, Union

class CobrancaPixService:
    def __init__(self, base_url: str, access_token: str):
        self.base_url = base_url
        self.access_token = access_token
        self.session = requests.Session()
        
        # Configurar headers padrão
        self.session.headers.update({
            'Authorization': f'Bearer {self.access_token}',
            'Content-Type': 'application/json',
            'Accept': 'application/json',
        })

    def criar_cobranca(self, dados_cobranca: Dict, txid: Optional[str] = None) -> Dict:
        """
        Cria uma nova cobrança Pix com QR Code dinâmico
        
        Args:
            dados_cobranca: Dicionário com os dados da cobrança
            txid: TXID personalizado (opcional)
            
        Returns:
            Dicionário com os dados da cobrança criada
        """
        try:
            # Gerar TXID se não fornecido
            txid_final = txid or str(uuid.uuid4())
            
            print(f"Criando cobrança Pix com TXID: {txid_final}")
            
            # Montar payload
            payload = {
                "chave": dados_cobranca["chave"],
                "solicitacaoPagador": dados_cobranca.get("solicitacaoPagador"),
                "nomeRecebedor": dados_cobranca["nomeRecebedor"],
                "calendario": {
                    "expiracao": dados_cobranca.get("expiracao", 3600)
                },
                "valor": {
                    "original": f"{dados_cobranca['valor']:.2f}",
                    "modalidadeAlteracao": dados_cobranca.get("modalidadeAlteracao", 0)
                },
                "devedor": {
                    "nome": dados_cobranca["devedor"]["nome"],
                    "cpf": dados_cobranca["devedor"].get("cpf"),
                    "cnpj": dados_cobranca["devedor"].get("cnpj")
                }
            }

            # Fazer requisição
            url = f"{self.base_url}/api/v2/cobranca/cob/{txid_final}"
            response = self.session.put(url, json=payload)
            response.raise_for_status()

            print("Cobrança Pix criada com sucesso")
            
            return {
                "txid": txid_final,
                "status": response.json()["status"],
                "qrCode": response.json()["pixCopiaECola"],
                "qrCodeImage": response.json()["qrCode"],
                "valor": response.json()["valor"]["original"],
                "expiracao": response.json()["calendario"]["expiracao"],
                "criacao": response.json()["calendario"]["criacao"]
            }
            
        except requests.exceptions.RequestException as error:
            print(f"Erro ao criar cobrança Pix: {error}")
            raise self._tratar_erro_cobranca(error)

    def validar_dados_cobranca(self, dados: Dict) -> Dict[str, Union[bool, List[str]]]:
        """
        Valida os dados da cobrança antes do envio
        
        Args:
            dados: Dados a serem validados
            
        Returns:
            Dicionário com resultado da validação
        """
        erros = []

        # Validar chave Pix
        if not dados.get("chave") or not self._validar_uuid(dados["chave"]):
            erros.append("Chave Pix deve ser um UUID válido")

        # Validar nome do recebedor
        if not dados.get("nomeRecebedor") or not dados["nomeRecebedor"].strip():
            erros.append("Nome do recebedor é obrigatório")

        if dados.get("nomeRecebedor") and len(dados["nomeRecebedor"]) > 200:
            erros.append("Nome do recebedor não pode exceder 200 caracteres")

        # Validar valor
        if not dados.get("valor") or dados["valor"] <= 0:
            erros.append("Valor deve ser maior que zero")

        if dados.get("valor") and dados["valor"] > 1000000:
            erros.append("Valor não pode exceder R$ 1.000.000,00")

        # Validar modalidade de alteração
        modalidade = dados.get("modalidadeAlteracao")
        if modalidade is not None and modalidade not in [0, 1]:
            erros.append("Modalidade de alteração deve ser 0 ou 1")

        # Validar dados do devedor
        devedor = dados.get("devedor", {})
        if not devedor.get("nome") or not devedor["nome"].strip():
            erros.append("Nome do devedor é obrigatório")

        if devedor.get("nome") and len(devedor["nome"]) > 200:
            erros.append("Nome do devedor não pode exceder 200 caracteres")

        # Validar CPF ou CNPJ
        tem_cpf = devedor.get("cpf") and devedor["cpf"].strip()
        tem_cnpj = devedor.get("cnpj") and devedor["cnpj"].strip()
        
        if not tem_cpf and not tem_cnpj:
            erros.append("Deve ser informado CPF ou CNPJ do devedor")

        if tem_cpf and tem_cnpj:
            erros.append("Informe apenas CPF ou CNPJ, nunca ambos")

        # Validar formato do CPF
        if tem_cpf and not self._validar_cpf(devedor["cpf"]):
            erros.append("CPF deve conter apenas números e ter 11 dígitos")

        # Validar formato do CNPJ
        if tem_cnpj and not self._validar_cnpj(devedor["cnpj"]):
            erros.append("CNPJ deve conter apenas números e ter 14 dígitos")

        # Validar expiração
        expiracao = dados.get("expiracao")
        if expiracao and (expiracao < 1 or expiracao > 86400):
            erros.append("Expiração deve estar entre 1 e 86400 segundos")

        # Validar solicitação do pagador
        solicitacao = dados.get("solicitacaoPagador")
        if solicitacao and len(solicitacao) > 140:
            erros.append("Solicitação do pagador não pode exceder 140 caracteres")

        return {
            "valido": len(erros) == 0,
            "erros": erros
        }

    def _validar_uuid(self, uuid_str: str) -> bool:
        """Valida se uma string é um UUID válido"""
        try:
            uuid.UUID(uuid_str)
            return True
        except ValueError:
            return False

    def _validar_cpf(self, cpf: str) -> bool:
        """Valida formato do CPF"""
        cpf_limpo = ''.join(filter(str.isdigit, cpf))
        return len(cpf_limpo) == 11 and cpf_limpo.isdigit()

    def _validar_cnpj(self, cnpj: str) -> bool:
        """Valida formato do CNPJ"""
        cnpj_limpo = ''.join(filter(str.isdigit, cnpj))
        return len(cnpj_limpo) == 14 and cnpj_limpo.isdigit()

    def _tratar_erro_cobranca(self, error: requests.exceptions.RequestException) -> Exception:
        """Trata erros específicos da API de cobranças"""
        if hasattr(error, 'response') and error.response is not None:
            status_code = error.response.status_code
            try:
                data = error.response.json()
                detail = data.get('detail', 'Erro desconhecido')
            except:
                detail = 'Erro desconhecido'

            if status_code == 400:
                return Exception(f"Dados inválidos: {detail}")
            elif status_code == 401:
                return Exception("Token de acesso inválido ou expirado")
            elif status_code == 403:
                return Exception("Permissões insuficientes para criar cobranças")
            elif status_code == 404:
                return Exception("Chave Pix não encontrada")
            elif status_code == 422:
                return Exception(f"Dados não processáveis: {detail}")
            elif status_code == 500:
                return Exception("Erro interno do servidor. Tente novamente mais tarde")
        
        return Exception(f"Erro inesperado: {str(error)}")


# Exemplo de uso em Python
async def exemplo_criacao_cobranca():
    cobranca_service = CobrancaPixService(
        base_url='https://api-h.nuvende.com.br',
        access_token='seu_access_token')

    try:
        # Dados da cobrança
        dados_cobranca = {
            "chave": "e3fe4462-4c8e-42c1-8360-b41b404ae557",
            "nomeRecebedor": "João da Silva",
            "valor": 100.00,
            "modalidadeAlteracao": 0,  # Valor não pode ser alterado
            "solicitacaoPagador": "Pagamento de teste via Pix",
            "expiracao": 3600,
            "devedor": {
                "nome": "Maria Santos",
                "cpf": "39053344705"
                # "cnpj": "11222333000181"  # Use apenas CPF ou CNPJ
            }
        }

        # Validar dados antes de enviar
        validacao = cobranca_service.validar_dados_cobranca(dados_cobranca)
        if not validacao["valido"]:
            print("Dados inválidos:", validacao["erros"])
            return

        # Criar cobrança
        cobranca = cobranca_service.criar_cobranca(dados_cobranca)
        
        print("Cobrança criada:", {
            "txid": cobranca["txid"],
            "valor": cobranca["valor"],
            "qrCode": cobranca["qrCode"],
            "expiracao": cobranca["expiracao"]
        })

        # Exibir QR Code para o usuário
        print("QR Code para pagamento:", cobranca["qrCodeImage"])

    except Exception as error:
        print("Erro na criação da cobrança:", str(error))


# Executar exemplo
if __name__ == "__main__":
    exemplo_criacao_cobranca()
#!/bin/bash

# Configurações da API
BASE_URL="https://api-h.nuvende.com.br"
ACCESS_TOKEN="seu_access_token"
TXID="$(uuidgen)"  # Gerar TXID único

# Dados da cobrança para pessoa jurídica
CHAVE_PIX="e3fe4462-4c8e-42c1-8360-b41b404ae557"
NOME_RECEBEDOR="Empresa ABC Ltda"
VALOR="500.00"
MODALIDADE_ALTERACAO="1"  # Valor pode ser alterado
SOLICITACAO_PAGADOR="Pagamento de serviços - Empresa XYZ"
EXPIRACAO="7200"  # 2 horas
NOME_DEVEDOR="Empresa Cliente Ltda"
CNPJ_DEVEDOR="12345678000195"

echo "Criando cobrança Pix para pessoa jurídica com TXID: $TXID"

# Fazer requisição PUT para criar cobrança
curl -X PUT \
  "${BASE_URL}/api/v2/cobranca/cob/${TXID}" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d "{
    \"chave\": \"${CHAVE_PIX}\",
    \"solicitacaoPagador\": \"${SOLICITACAO_PAGADOR}\",
    \"nomeRecebedor\": \"${NOME_RECEBEDOR}\",
    \"calendario\": {
      \"expiracao\": ${EXPIRACAO}
    },
    \"valor\": {
      \"original\": \"${VALOR}\",
      \"modalidadeAlteracao\": ${MODALIDADE_ALTERACAO}
    },
    \"devedor\": {
      \"nome\": \"${NOME_DEVEDOR}\",
      \"cnpj\": \"${CNPJ_DEVEDOR}\"
    }
  }" \
  --silent \
  --show-error \
  --fail

echo ""
echo "Cobrança Pix para pessoa jurídica criada com sucesso!"

2025 © Nuvende - CNPJ 38.297.374/0001-93