import os import json import requests from time import time from functools import wraps from base64 import b64decode from datetime import datetime, timedelta from flask import Flask, Blueprint, jsonify, request, Response from flask_mysqldb import MySQL from flask_cors import CORS from .auth import token_required from dotenv import load_dotenv from flask import send_file import io load_dotenv() # Configurações da API do Banco Inter CLIENT_ID = os.environ.get("CLIENT_ID") CLIENT_SECRET = os.environ.get("CLIENT_SECRET") CERT_PATH = os.environ.get("CERT_PATH") CERT_KEY_PATH = os.environ.get("CERT_KEY_PATH") ACCOUNT = os.environ.get("ACCOUNT", None) API_BASE_URL = "https://cdpj.partners.bancointer.com.br" boletos = Blueprint('boletos', __name__) app = Flask(__name__) cors = CORS(app, resources={r"/v1/boletos/*": {"origins": "*", "methods": ["GET", "POST", "PUT", "OPTIONS"]}}) app.config["CORS_HEADERS"] = ["Content-Type", "Authorization", "Content-Disposition", "Content-Length"] MYSQL_HOST = os.environ.get('MYSQL_HOST') MYSQL_PORT = os.environ.get('MYSQL_PORT') MYSQL_USER = os.environ.get('MYSQL_USER') MYSQL_PASSWORD = os.environ.get('MYSQL_PASSWORD') MYSQL_DB = os.environ.get('MYSQL_DB') MYSQL_CURSORCLASS = os.environ.get("MYSQL_CURSORCLASS") mysql = MySQL() token_cache = {} def intercept_options(f): @wraps(f) def decorated(*args, **kwargs): if request.method == "OPTIONS": response = jsonify({"status": "ok"}) response.headers.add("Access-Control-Allow-Origin", "*") response.headers.add("Access-Control-Allow-Methods", "GET,POST,PUT,OPTIONS") response.headers.add("Access-Control-Allow-Headers", "Content-Type,Authorization,Content-Disposition,Content-Length") return response, 200 return f(*args, **kwargs) return decorated def obter_token(scopes=["boleto-cobranca.write", "boleto-cobranca.read"]): """Obtem um token de acesso para a API do Banco Inter.""" scopes_param = " ".join(scopes) if token_cache.get(scopes_param): cached_token = token_cache.get(scopes_param) # token is still valid if cached_token.get("expires_at") > int(time()): return cached_token["token"] try: request_string = f"client_id={CLIENT_ID}&client_secret={CLIENT_SECRET}&scope={scopes_param}&grant_type=client_credentials" print("request_string", request_string) response = requests.post(url=f"{API_BASE_URL}/oauth/v2/token", headers={"Content-Type": "application/x-www-form-urlencoded"}, cert=(CERT_PATH, CERT_KEY_PATH), data=request_string) data_json = response.json() token = data_json.get("access_token") token_cache[scopes_param] = { "token": token, "expires_at": data_json.get("expires_in") + int(time()), } return token except Exception as e: print(f"Erro ao obter token de acesso: {e}") return None def emitir_cobranca(token, dados_cobranca): """Emite uma cobrança para o Banco Inter de forma assíncrona.""" headers = { "Authorization": f"Bearer {token}", "Content-Type": "application/json", "Accept": "application/json" } try: response = requests.post( f"{API_BASE_URL}/cobranca/v3/cobrancas", headers=headers, cert=(CERT_PATH, CERT_KEY_PATH), json=dados_cobranca ) # Captura a resposta completa resposta_json = response.json() # Exibe detalhes da resposta para debug print("Código de status:", response.status_code) print("Resposta da API:", json.dumps(resposta_json, indent=4, ensure_ascii=False)) return resposta_json except Exception as e: print(f"Erro ao emitir cobrança: {e}") return None def obter_cobranca(token, codigo_solicitacao): """Obtem uma cobrança.""" headers = { "Authorization": f"Bearer {token}", "Content-Type": "Application/json", } try: response = requests.get(f"{API_BASE_URL}/cobranca/v3/cobrancas/{codigo_solicitacao}", headers=headers, cert=(CERT_PATH, CERT_KEY_PATH)) return response.json() except Exception as e: print(f"Erro ao emitir cobrança:{e}") return None def obter_cobranca_pdf(token, codigo_solicitacao): """Obtem o PDF de uma cobrança.""" headers = { "Authorization": f"Bearer {token}", "Content-Type": "Application/json", } try: # Certifique-se de que o codigo_solicitacao seja inserido corretamente na URL url = f"{API_BASE_URL}/cobranca/v3/cobrancas/{codigo_solicitacao}/pdf" response = requests.get(url, headers=headers, cert=(CERT_PATH, CERT_KEY_PATH)) return response.json() except Exception as e: print(f"Erro ao emitir cobrança:{e}") return None def processar_cobranca_cliente(cliente, token): """ Processa a emissão da cobrança para um cliente específico, retornando os dados relevantes. """ id_empresa = cliente["idempresa"] hoje = datetime.now() vencimento = (hoje + timedelta(days=5)).strftime("%Y-%m-%d") dados_cobranca = { "seuNumero": f"boleto-{id_empresa}", "valorNominal": str(cliente["valor_servico"]), "dataVencimento": vencimento, "numDiasAgenda": 30, "pagador": { "cpfCnpj": cliente["cpf_cnpj"], "tipoPessoa": cliente["tipo_pessoa"], "nome": cliente["nome"], "telefone": cliente["telefone"], "endereco": cliente["endereco"], "cidade": cliente["cidade"], "uf": cliente["uf"], "cep": cliente["cep"], }, "formasRecebimento": ["BOLETO", "PIX"], } emissao_cobranca = emitir_cobranca(token, dados_cobranca) codigo_solicitacao = emissao_cobranca.get("codigoSolicitacao") if emissao_cobranca else None if not codigo_solicitacao: return { "cliente": cliente, "status": "erro", "mensagem": "Falha ao emitir cobrança." } cobranca = obter_cobranca(token, codigo_solicitacao) if not cobranca: return { "cliente": cliente, "status": "erro", "mensagem": "Falha ao obter cobrança." } boleto_data = cobranca.get("cobranca", {}).get("boleto", {}) codigo_barras = boleto_data.get("codigoBarras") linha_digitavel = cobranca.get("boleto", {}).get("linhaDigitavel") pix_data = cobranca.get("pix", {}) pix_copia_e_cola = pix_data.get("pixCopiaECola") cobranca_pdf = obter_cobranca_pdf(token, codigo_solicitacao) if not cobranca_pdf or "pdf" not in cobranca_pdf: return { "cliente": cliente, "status": "erro", "mensagem": "Falha ao obter PDF da cobrança." } return { "cliente": cliente, "status": "sucesso", "codigo_solicitacao": codigo_solicitacao, "codigo_barras": codigo_barras, "linha_digitavel": linha_digitavel, "pix_copia_e_cola": pix_copia_e_cola, "pdf": cobranca_pdf["pdf"], } @boletos.route('/', methods=['POST', 'OPTIONS']) @intercept_options @token_required def gerar_boletos(data): domain = data.get('domain') escopos = data.get('scopes', []) escopos.append("boleto-cobranca.write") escopos.append("boleto-cobranca.read") print("data", data) print(domain) print(escopos) if 'boleto-cobranca.write' not in escopos: return jsonify({"erro": "Permissão necessária: boleto-cobranca.write"}), 403 if not domain: return jsonify({"error": "Domínio não encontrado no token"}), 400 hoje = datetime.now() if hoje.day > 12: return jsonify({"erro": "Boletos só podem ser gerados até o dia 12 de cada mês."}), 403 try: cur = mysql.connection.cursor() cur.execute( "SELECT idempresa, nome, cpf_cnpj, tipo_pessoa, telefone, endereco, cidade, uf, cep, valor_servico FROM empresa WHERE dominio = %s", (domain,) ) clientes = cur.fetchall() cur.close() if not clientes: return jsonify({"erro": "Nenhum cliente encontrado."}), 404 except Exception as e: print(f"Erro ao acessar o banco de dados: {e}") return jsonify({"erro": "Erro ao acessar o banco de dados."}), 500 token = obter_token(escopos) if not token: return jsonify({"erro": "Falha ao obter o token de acesso."}), 401 resultados = [] for cliente in clientes: resultado = processar_cobranca_cliente(cliente, token) resultados.append(resultado) return jsonify({"resultados": resultados}), 200 @boletos.route('/', methods=['GET', 'OPTIONS']) @intercept_options @token_required def recuperar_cobranca(token_data, codigo_solicitacao): print("codigo_solicitacao", codigo_solicitacao) scopes = token_data.get('scopes', []) print("token_data", token_data) if 'boleto-cobranca.read' not in scopes: return jsonify({"erro": "Permissao necessaria: boleto-cobranca.read"}), 403 token = obter_token(scopes) if not token: return jsonify({"erro": "Falha ao obter o token de acesso"}), 401 cobranca = obter_cobranca(token, codigo_solicitacao) if cobranca is None: return jsonify({"erro": "Erro ao obter PDF"}), 404 try: codigo_barras = cobranca.get("boleto").get("codigoBarras") linha_digitavel = cobranca.get("boleto").get("linhaDigitavel") pix_copia_e_cola = cobranca.get("pix").get("pixCopiaECola") return jsonify({ "codigo_solicitacao": codigo_solicitacao, "codigo_barras": codigo_barras, "linha_digitavel": linha_digitavel, "pix_copia_e_cola": pix_copia_e_cola, }), 200 except Exception as e: return jsonify({"erro": f"Erro ao buscar cobranca: {e}"}), 500 @boletos.route('//pdf', methods=['GET', 'OPTIONS']) @intercept_options @token_required def recuperar_boleto(token_data, codigo_solicitacao): print("codigo_solicitacao", codigo_solicitacao) scopes = token_data.get('scopes', []) print("token_data", token_data) if 'boleto-cobranca.read' not in scopes: return jsonify({"erro": "Permissao necessaria: boleto-cobranca.read"}), 403 token = obter_token(scopes) if not token: return jsonify({"erro": "Falha ao obter o token de acesso"}), 401 cobranca_pdf = obter_cobranca_pdf(token, codigo_solicitacao) if cobranca_pdf is None: return jsonify({"erro": "Erro ao obter PDF"}), 404 try: pdf_content_base64 = cobranca_pdf["pdf"] pdf_content_bytes = b64decode(pdf_content_base64, validate=True) return Response(pdf_content_bytes, mimetype='application/pdf', headers={ "Content-Disposition": "inline; filename=boleto.pdf", "Content-Type": "application/pdf", "Content-Length": len(pdf_content_bytes) }) except Exception as e: return jsonify({"erro": f"Erro ao decodificar PDF: {e}"}), 500 if __name__ == "__main__": app.register_blueprint(boletos) app.run(port=5000, debug=True)