def action_verify_transaction(self): if self.acquirer_id.provider != "apiboletointer": return if not self.acquirer_reference: raise UserError( "Esta transação não foi enviada a nenhum gateway de pagamento") with ArquivoCertificado(self.acquirer_id, "w") as (key, cert): self.api = ApiInter( cert=(cert, key), conta_corrente=( self.acquirer_id.journal_id.bank_account_id.acc_number + self.acquirer_id.journal_id.bank_account_id.acc_number_dig ), ) data = self.api.boleto_recupera(self.acquirer_reference) # EMABERTO, BAIXADO e VENCIDO e PAGO if "errors" in data or not data: raise UserError(data) if data["situacao"] == "EMABERTO" and self.state in ("draft"): self._set_transaction_pending() if data["situacao"] == "PAGO" and self.state not in ("done", "authorized"): self._set_transaction_done() self._post_process_after_done()
def generate_pdf_boleto(self): """ Creates a new attachment with the Boleto PDF """ # _logger.error("my acquirer_reference : %r", self.acquirer_reference) if self.acquirer_reference and self.pdf_boleto_id: return with ArquivoCertificado(self.acquirer_id, "w") as (key, cert): self.api = ApiInter( cert=(cert, key), conta_corrente=( self.acquirer_id.journal_id.bank_account_id.acc_number + self.acquirer_id.journal_id.bank_account_id.acc_number_dig ), ) datas = self.api.boleto_pdf(self.acquirer_reference) if self._isBase64(datas): self.pdf_boleto_id = self.env["ir.attachment"].create({ # "res_model": "payment.transaction", "name": ("Boleto %s.pdf" % self.display_name), "datas": datas, "type": "binary", }) else: raise ValidationError( "Cannot download invalid base64 (.pdf) file")
def generate_pdf_boleto(self): """ Creates a new attachment with the Boleto PDF """ if self.own_number and self.pdf_boleto_id: return order_id = self.payment_line_ids[0].order_id with ArquivoCertificado(order_id.journal_id, 'w') as (key, cert): partner_bank_id = self.journal_id.bank_account_id self.api = ApiInter( cert=(cert, key), conta_corrente=( order_id.company_partner_bank_id.acc_number + order_id.company_partner_bank_id.acc_number_dig)) datas = self.api.boleto_pdf(self.own_number) self.pdf_boleto_id = self.env['ir.attachment'].create({ 'name': ("Boleto %s" % self.bank_payment_line_id.display_name), 'datas': datas, 'datas_fname': ("boleto_%s.pdf" % self.bank_payment_line_id.display_name), 'type': 'binary' })
def action_gerar_boleto(self): journal = self.env['account.journal'].search([('code', '=', 'Inter')]) with ArquivoCertificado(journal, 'w') as (key, cert): self.api = ApiInter( cert=(cert, key), conta_corrente=journal.bank_account_id.acc_number + journal.bank_account_id.acc_number_dig ) if self.pdf_boletos_id: raise exceptions.UserError( 'Já existe um boleto dessa fatura. Você pode utilizar a opção Download ou enviar por email. ' 'Caso deseje de fato gerar um novo boleto, você pode usar a opção Atualizar Boleto, ele fará a baixa' ' no banco Inter (Cancelamento) e emitirá um novo boleto!') if self.state == 'draft': raise exceptions.UserError( 'O status da fatura ainda é provisório. Valide a fatura para que então você ' 'consiga gerar o boleto. Impossível gerar boletos para fatura com status Provisório!') data = self.dados_boleto() for item in data: # Se a fatura ainda não tiver boleto (Nosso numero), então gera. if not self.pdf_boletos_id: if self.date_due < datetime.now().date(): raise exceptions.UserError( 'A data de vencimento da fatura é menor que a data de emissão do boleto' ' (Data atual). Impossível gerar um boleto com a data de vencimento menor' ' que a data de emissão!') # Inclusão do boleto e envio para a API do Inter # os dados do emitente e do pagador estão no dicionarios no inicio do codigo resposta = self.api.boleto_inclui(item._emissao_data()) # Salva o nosso numero do boleto na respectiva fatura self.nossonumero = resposta['nossoNumero'] # Seta a flag para true, indicando que foi emitido boleto dessa fatura self.boleto_emitido = True # Consultar a API do Inter para localizar e baixar o PDF do boleto gerado # A pesquisa é feita pelo nossonumero e o boleto é codificado em base64 boleto_base64 = self.api.boleto_pdf(self.nossonumero) self.pdf_boletos_id = self.env['ir.attachment'].create( { 'name': ( "Boleto %s" % self.display_name.replace('/', '-')), 'datas': boleto_base64, 'datas_fname': ("boleto_%s.pdf" % self.display_name.replace('/', '-')), 'type': 'binary' } )
def cancel_transaction_in_inter(self): if not self.acquirer_reference: raise UserError( "Esta transação não foi enviada a nenhum gateway de pagamento") with ArquivoCertificado(self.acquirer_id, "w") as (key, cert): self.api = ApiInter( cert=(cert, key), conta_corrente=( self.acquirer_id.journal_id.bank_account_id.acc_number + self.acquirer_id.journal_id.bank_account_id.acc_number_dig ), ) data = self.api.boleto_baixa(self.acquirer_reference, "SUBISTITUICAO")
def _generate_bank_inter_boleto(self): payment_provider = self.env['payment.acquirer'].search([('provider', '=', 'apiboletointer')]) with ArquivoCertificado(payment_provider, 'w') as (key, cert): self.api = ApiInter( cert=(cert, key), conta_corrente=(self.payment_journal_id.bank_account_id.acc_number + self.payment_journal_id.bank_account_id.acc_number_dig) ) data = self._generate_bank_inter_boleto_data() for item in data: print(item._emissao_data()) resposta = self.api.boleto_inclui(item._emissao_data()) # o pacote python tem error handle que retorna para a aplicação return resposta
def setUp(self): certificado_cert = os.environ.get('certificado_inter_cert') certificado_key = os.environ.get('certificado_inter_key') self.api = ApiInter(cert=(certificado_cert, certificado_key), conta_corrente='14054310') self.dados = [] myself = User( name='KMEE INFORMATICA LTDA', identifier='23130935000198', bank=UserBank(bankId="341", branchCode="1234", accountNumber="33333", accountVerifier="4", bankName="BANCO ITAU SA"), ) now = datetime.now() for i in range(3): payer = User(name="Sacado Teste", identifier="26103754097", email="*****@*****.**", personType="FISICA", phone="35988763663", address=UserAddress( streetLine1="Rua dos TESTES", district="CENTRO", city="SAO PAULO", stateCode="SP", zipCode="31327130", streetNumber="15", )) slip = BoletoInter(sender=myself, amount_in_cents="100.00", payer=payer, issue_date=now, due_date=now, identifier="456" + str(i), instructions=[ 'TESTE 1', 'TESTE 2', 'TESTE 3', 'TESTE 4', ]) self.dados.append(slip)
class AccountMoveLine(models.Model): _inherit = 'account.move.line' pdf_boleto_id = fields.Many2one(comodel_name='ir.attachment', string='PDF Boleto', ondelete='cascade') own_number = fields.Integer(string='Nosso número', help="Nosso número") def generate_pdf_boleto(self): """ Creates a new attachment with the Boleto PDF """ if self.own_number and self.pdf_boleto_id: return order_id = self.payment_line_ids[0].order_id with ArquivoCertificado(order_id.journal_id, 'w') as (key, cert): partner_bank_id = self.journal_id.bank_account_id self.api = ApiInter( cert=(cert, key), conta_corrente=( order_id.company_partner_bank_id.acc_number + order_id.company_partner_bank_id.acc_number_dig)) datas = self.api.boleto_pdf(self.own_number) self.pdf_boleto_id = self.env['ir.attachment'].create({ 'name': ("Boleto %s" % self.bank_payment_line_id.display_name), 'datas': datas, 'datas_fname': ("boleto_%s.pdf" % self.bank_payment_line_id.display_name), 'type': 'binary' }) def print_pdf_boleto(self): """ Generates and downloads Boletos PDFs :return: actions.act_url """ self.generate_pdf_boleto() if self.own_number: boleto_id = self.pdf_boleto_id base_url = self.env['ir.config_parameter'].get_param( 'web.base.url') download_url = '/web/content/%s/%s?download=True' % (str( boleto_id.id), boleto_id.name) return { "type": "ir.actions.act_url", "url": str(base_url) + str(download_url), "target": "new", }
def _generate_bank_inter_boleto(self): with ArquivoCertificado(self.journal_id, 'w') as (key, cert): self.api = ApiInter( cert=(cert, key), conta_corrente=(self.company_partner_bank_id.acc_number + self.company_partner_bank_id.acc_number_dig) ) data = self._generate_bank_inter_boleto_data() for item in data: resposta = self.api.boleto_inclui(item._emissao_data()) payment_line_id = self.payment_line_ids.filtered( lambda line: line.document_number == item._identifier) if payment_line_id: payment_line_id.move_line_id.own_number = \ resposta['nossoNumero'] # bank_line_id.seu_numero = resposta['seuNumero'] # bank_line_id.linha_digitavel = resposta['linhaDigitavel'] # bank_line_id.barcode = resposta['codigoBarras'] return False, False
def action_atualiza_boleto(self): """ Essa função faz a atualização de boleto. Basicamente ela cancela o boleto atual e gera um novo boleto atualizado. Provavelmente existe formas melhores de realizar essa operação, porém na API do Banco Inter existe apenas 4 endpoint (Emissão, consulta, baixa e PDF), então o que encontrei no momento foi cancelar com a baixa e emitir um novo boleto. Essa operação pode ser cobrada por boletos gerados. """ journal = self.env['account.journal'].search([('code', '=', 'Inter')]) with ArquivoCertificado(journal, 'w') as (key, cert): self.api = ApiInter( cert=(cert, key), conta_corrente=journal.bank_account_id.acc_number + journal.bank_account_id.acc_number_dig ) if not self.pdf_boletos_id: raise exceptions.UserError( 'Ainda não existe um boleto gerado dessa fatura. Impossível atualizar um ' 'boleto inexistente!') if self.date < datetime.now().date() + timedelta(days=2): raise exceptions.UserError( 'O boleto em questão tem menos de dois dias que foi emitido. É provavel que ' 'ainda não esteja registrado. Aguarde pelo menos dois dias para atualizá-lo') # Realiza a baixa do boleto atual resposta_baixa = self.api.boleto_baixa(self.nossonumero, 'SUBISTITUICAO') # Depois que realizo a baixo lá no Inter, eu apago a justificativa do banco de dados, porque la vai ta o nosso numero # do novo boleto, e ele nao foi baixado. Sei que não é a melhor forma, mas futuramente podemos criar uma tabela # somente para armazenar os boletos, inclusive para consultas, podemos fazer isso quando for fazer o painel de boleto. self.boleto_codigo_baixa = None # Na função de de geração de boleto, só é gerado, se não tiver pdf_boletos_id. Esse tratamento existe para # a pessoa não gerar duas vezes. POrém, só nosso caso de atualização, precisaremos, então seto como Null # e abaixo quando a função for chamada, será gerado normalmente. Eu poderia excluir o registro do boleot, # mas futuramente, pode ser que queremos exibi-los em consultas no peinel do boleto. self.pdf_boletos_id = None # Realiza a emissão do novo boleto self.action_gerar_boleto()
def action_baixa_boleto(self): """ Essa função realiza operações de Baixa de boleto na API do banco inter. Ela disponibiliza uma janela com todas as opções de baixa. Ai escolher o codigo da baixa e clicar em salvar, a API do inter é chamada e enviado a requisição. """ journal = self.env['account.journal'].search([('code', '=', 'Inter')]) with ArquivoCertificado(journal, 'w') as (key, cert): self.api = ApiInter( cert=(cert, key), conta_corrente=journal.bank_account_id.acc_number + journal.bank_account_id.acc_number_dig ) if not self.pdf_boletos_id: raise exceptions.UserError( 'Ainda não existe um boleto gerado dessa fatura. Impossível imprimir/baixar pdf ' 'de um boleto inexistente!') if abs((self.date_invoice - datetime.now().date()).days) <= 2: raise exceptions.UserError( 'O boleto em questão tem menos de dois dias que foi emitido. É provavel que ' 'ainda não esteja registrado. Aguarde pelo menos dois dias para realizar a baixa') # resposta = self.api.boleto_baixa(self.nossonumero, self.boleto_codigo_baixa) view_id = self.env.ref('sismais_account_payment_inter_boleto.baixa_boleto_invoice_form').id context = self._context.copy() return { 'name': 'Baixa Boleto Inter', 'view_type': 'form', 'view_mode': 'tree', 'views': [(view_id, 'form')], 'res_model': 'account.invoice', 'view_id': view_id, 'type': 'ir.actions.act_window', 'res_id': self.id, 'target': 'new', 'context': context, }
def baixa_recorrente_boleto(self): """ O Odoo possui um proprio sistema de Cron, usarei ele para acionar essa função diariamente. O cron pode ser feito na propria interface do Odoo ou definida por XML, nesse caso, usuario um XML, Assim que o modulo for instalado, o cron será criado. """ journal = self.env['account.journal'].search([('code', '=', 'Inter')]) with ArquivoCertificado(journal, 'w') as (key, cert): self.api = ApiInter( cert=(cert, key), conta_corrente=journal.bank_account_id.acc_number + journal.bank_account_id.acc_number_dig ) # Data inicial usada na consulta de boleto no no formato string com hifen no lugar de ponto (AAAA-MM-DD) # Uma data bem antiga para que qualquer boleto não pago, independente do tempo, venha no filtro. dt_inicial = '1990-01-01' # Data final usada na consulta de boleto no no formato string com hifen no lugar de ponto (AAAA-MM-DD) dt_final = str(datetime.now().date()).replace('.', '-') resposta = self.api.boleto_consulta('PAGOS', dt_inicial, dt_final) for i in range(len(resposta['content'])): invoice = self.env['account.invoice'].search([('nossonumero', '=', resposta['content'][i]['nossoNumero'])]) # invoice = self.env['account.invoice'].search([('nossonumero', '=', '00663999953')]) # usada para teste if invoice and invoice.state == 'open': Payment = self.env['account.payment'].with_context(default_invoice_ids=[(4, invoice.id, False)]) payment = Payment.create({ 'payment_date': datetime.now(), 'payment_method_id': 1, 'payment_type': 'inbound', 'partner_type': 'customer', 'partner_id': invoice.partner_id.id, 'amount': invoice.amount_total, 'journal_id': journal.id, 'company_id': invoice.company_id, 'currency_id': 6, 'payment_difference_handling': 'reconcile' }) payment.post()
class AccountPaymentOrder(models.Model): _inherit = 'account.payment.order' def _generate_bank_inter_boleto_data(self): dados = [] myself = User( name=self.company_id.legal_name, identifier=misc.punctuation_rm(self.company_id.cnpj_cpf), bank=UserBank( bankId=self.company_partner_bank_id.bank_id.code_bc, branchCode=self.company_partner_bank_id.bra_number, accountNumber=self.company_partner_bank_id.acc_number, accountVerifier=self.company_partner_bank_id.acc_number_dig, bankName=self.company_partner_bank_id.bank_id.name, ), ) for line in self.bank_line_ids: payer = User( name=line.partner_id.legal_name, identifier=misc.punctuation_rm( line.partner_id.cnpj_cpf ), email=line.partner_id.email or '', personType=( "FISICA" if line.partner_id.company_type == 'person' else 'JURIDICA'), phone=misc.punctuation_rm( line.partner_id.phone).replace(" ", ""), address=UserAddress( streetLine1=line.partner_id.street or '', district=line.partner_id.district or '', city=line.partner_id.city_id.name or '', stateCode=line.partner_id.state_id.code or '', zipCode=misc.punctuation_rm(line.partner_id.zip), streetNumber=line.partner_id.street_number, ) ) slip = BoletoInter( sender=myself, amount=line.amount_currency, payer=payer, issue_date=line.create_date, due_date=line.date, identifier=line.document_number, instructions=[ 'TESTE 1', 'TESTE 2', 'TESTE 3', 'TESTE 4', ] ) dados.append(slip) return dados def _generate_bank_inter_boleto(self): with ArquivoCertificado(self.journal_id, 'w') as (key, cert): self.api = ApiInter( cert=(cert, key), conta_corrente=(self.company_partner_bank_id.acc_number + self.company_partner_bank_id.acc_number_dig) ) data = self._generate_bank_inter_boleto_data() for item in data: resposta = self.api.boleto_inclui(item._emissao_data()) payment_line_id = self.payment_line_ids.filtered( lambda line: line.document_number == item._identifier) if payment_line_id: payment_line_id.move_line_id.own_number = \ resposta['nossoNumero'] # bank_line_id.seu_numero = resposta['seuNumero'] # bank_line_id.linha_digitavel = resposta['linhaDigitavel'] # bank_line_id.barcode = resposta['codigoBarras'] return False, False def _gererate_bank_inter_api(self): """ Realiza a conexão com o a API do banco inter""" if self.payment_type == 'inbound': return self._generate_bank_inter_boleto() else: raise NotImplementedError def generate_payment_file(self): self.ensure_one() if (self.company_partner_bank_id.bank_id == self.env.ref('l10n_br_base.res_bank_077') and self.payment_method_id.code == 'electronic'): return self._gererate_bank_inter_api() else: return super().generate_payment_file()
class PaymentTransaction(models.Model): _inherit = "payment.transaction" transaction_url = fields.Char(string="Url de Pagamento", size=256) origin_move_line_id = fields.Many2one("account.move.line") date_maturity = fields.Date(string="Data de Vencimento") pdf_boleto_id = fields.Many2one(comodel_name="ir.attachment", string="PDF Boleto", ondelete="cascade") def _isBase64(self, sb): try: if isinstance(sb, str): # If there's any unicode here, an exception will be thrown and the function will return false sb_bytes = bytes(sb, "ascii") # _logger.error("my sb_bytes : %r", sb_bytes) elif isinstance(sb, bytes): sb_bytes = sb # _logger.error("my sb_bytes : %r", sb_bytes) else: raise ValidationError( "Cannot download invalid base64 (.pdf) file") return base64.b64encode(base64.b64decode(sb_bytes)) == sb_bytes except Exception: raise ValidationError("Cannot download invalid base64 (.pdf) file") return False def generate_pdf_boleto(self): """ Creates a new attachment with the Boleto PDF """ # _logger.error("my acquirer_reference : %r", self.acquirer_reference) if self.acquirer_reference and self.pdf_boleto_id: return with ArquivoCertificado(self.acquirer_id, "w") as (key, cert): self.api = ApiInter( cert=(cert, key), conta_corrente=( self.acquirer_id.journal_id.bank_account_id.acc_number + self.acquirer_id.journal_id.bank_account_id.acc_number_dig ), ) datas = self.api.boleto_pdf(self.acquirer_reference) if self._isBase64(datas): self.pdf_boleto_id = self.env["ir.attachment"].create({ # "res_model": "payment.transaction", "name": ("Boleto %s.pdf" % self.display_name), "datas": datas, "type": "binary", }) else: raise ValidationError( "Cannot download invalid base64 (.pdf) file") def print_pdf_boleto(self): """ Generates and downloads Boletos PDFs :return: actions.act_url """ self.generate_pdf_boleto() if self.acquirer_reference: boleto_id = self.pdf_boleto_id iddoboleto = boleto_id.id base_url = self.env["ir.config_parameter"].get_param( "web.base.url") download_url = "/web/content/%s/%s?download=True" % ( str(boleto_id.id), boleto_id.name.replace("/", "_"), ) return { "type": "ir.actions.act_url", "url": str(base_url) + str(download_url), "target": "new", } def cron_verify_transaction(self): documents = self.search( [ ("state", "in", ["draft", "pending"]), ], limit=50, ) for doc in documents: try: doc.action_verify_transaction() self.env.cr.commit() except Exception as e: self.env.cr.rollback() _logger.exception( "Payment Transaction ID {}: {}.".format(doc.id, str(e)), exc_info=True, ) def action_verify_transaction(self): if self.acquirer_id.provider != "apiboletointer": return if not self.acquirer_reference: raise UserError( "Esta transação não foi enviada a nenhum gateway de pagamento") with ArquivoCertificado(self.acquirer_id, "w") as (key, cert): self.api = ApiInter( cert=(cert, key), conta_corrente=( self.acquirer_id.journal_id.bank_account_id.acc_number + self.acquirer_id.journal_id.bank_account_id.acc_number_dig ), ) data = self.api.boleto_recupera(self.acquirer_reference) # EMABERTO, BAIXADO e VENCIDO e PAGO if "errors" in data or not data: raise UserError(data) if data["situacao"] == "EMABERTO" and self.state in ("draft"): self._set_transaction_pending() if data["situacao"] == "PAGO" and self.state not in ("done", "authorized"): self._set_transaction_done() self._post_process_after_done() # if self.origin_move_line_id: # self.origin_move_line_id._create_bank_tax_move( # (data.get('taxes_paid_cents') or 0) / 100) # else: # self.iugu_status = data['status'] def cancel_transaction_in_inter(self): if not self.acquirer_reference: raise UserError( "Esta transação não foi enviada a nenhum gateway de pagamento") with ArquivoCertificado(self.acquirer_id, "w") as (key, cert): self.api = ApiInter( cert=(cert, key), conta_corrente=( self.acquirer_id.journal_id.bank_account_id.acc_number + self.acquirer_id.journal_id.bank_account_id.acc_number_dig ), ) data = self.api.boleto_baixa(self.acquirer_reference, "SUBISTITUICAO") def action_cancel_transaction(self): self._set_transaction_cancel() if self.acquirer_id.provider == "apiboletointer": self.cancel_transaction_in_inter()
class AccountMove(models.Model): _inherit = 'account.move' @api.depends('line_ids') def _compute_receivables(self): for move in self: move.receivable_move_line_ids = move.line_ids.filtered( lambda m: m.account_id.user_type_id.type == 'receivable' ).sorted(key=lambda m: m.date_maturity) @api.depends('line_ids') def _compute_payables(self): for move in self: move.payable_move_line_ids = move.line_ids.filtered( lambda m: m.account_id.user_type_id.type == 'payable') receivable_move_line_ids = fields.Many2many( 'account.move.line', string='Receivable Move Lines', compute='_compute_receivables') payable_move_line_ids = fields.Many2many( 'account.move.line', string='Payable Move Lines', compute='_compute_payables') payment_journal_id = fields.Many2one( 'account.journal', string='Forma de pagamento') def validate_data_iugu(self): errors = [] for invoice in self: #if not invoice.payment_journal_id.receive_by_iugu: if not invoice.payment_mode_id.display_name == 'BOLETO INTER': continue partner = invoice.partner_id.commercial_partner_id #if not self.env.company.iugu_api_token: #if not self.payment_mode_id.fixed_journal_id.bank_inter_cert: # errors.append('Configure o certificado de API') #if not self.payment_mode_id.fixed_journal_id.bank_inter_key: # errors.append('Configure a chave de API') if partner.is_company and not partner.l10n_br_legal_name: errors.append('Destinatário - Razão Social') if not partner.street: errors.append('Destinatário / Endereço - Rua') if not partner.l10n_br_number: errors.append('Destinatário / Endereço - Número') if not partner.zip or len(re.sub(r"\D", "", partner.zip)) != 8: errors.append('Destinatário / Endereço - CEP') if not partner.state_id: errors.append(u'Destinatário / Endereço - Estado') if not partner.city_id: errors.append(u'Destinatário / Endereço - Município') if not partner.country_id: errors.append(u'Destinatário / Endereço - País') if len(errors) > 0: msg = "\n".join( ["Por favor corrija os erros antes de prosseguir"] + errors) raise ValidationError(msg) def send_information_to_iugu(self): if not self.payment_journal_id.receive_by_boletointer: #if not self.payment_mode_id.display_name == 'BOLETO INTER': return for moveline in self.receivable_move_line_ids: #self.partner_id.action_synchronize_iugu() #iugu_p = self.env['payment.acquirer'].search([('provider', '=', 'iugu')]) #_acquirer = self.env['payment.acquirer'].search([('provider', '=', 'transfer')]) payment_provider = self.env['payment.acquirer'].search([('provider', '=', 'apiboletointer')]) transaction = self.env['payment.transaction'].sudo().create({ 'acquirer_id': payment_provider.id, 'amount': moveline.amount_residual, 'currency_id': moveline.move_id.currency_id.id, 'partner_id': moveline.partner_id.id, 'type': 'server2server', 'date_maturity': moveline.date_maturity, 'origin_move_line_id': moveline.id, 'invoice_ids': [(6, 0, self.ids)] }) #raise UserError(transaction) vals = { 'email': self.partner_id.email, 'due_date': moveline.date_maturity.strftime('%Y-%m-%d'), 'ensure_workday_due_date': True, 'items': [{ 'description': 'Fatura Ref: %s' % moveline.name, 'quantity': 1, 'price_cents': int(moveline.amount_residual * 100), }], #'return_url': '%s/my/invoices/%s' % (base_url, self.id), #'notification_url': '%s/iugu/webhook?id=%s' % (base_url, self.id), 'fines': True, 'late_payment_fine': 2, 'per_day_interest': True, 'customer_id': self.partner_id, 'early_payment_discount': False, 'order_id': transaction.reference, } #data = iugu_invoice_api.create(vals) #data = None #if not data: # data = {} # data['nossoNumero'] = 123 # data['linhaDigitavel'] = 12345 data = self._generate_bank_inter_boleto() #catch error to-do #if "errors" in data: if 0: if isinstance(data['errors'], str): raise UserError('Erro na integração com IUGU:\n%s' % data['errors']) msg = "\n".join( ["A integração com IUGU retornou os seguintes erros"] + ["Field: %s %s" % (x[0], x[1][0]) for x in data['errors'].items()]) raise UserError(msg) transaction.write({ #'acquirer_reference': data['id'], 'acquirer_reference': data['nossoNumero'] or '', #'transaction_url': data['secure_url'], }) #transaction._set_transaction_pending() moveline.write({ #'iugu_id': data['nossoNumero'] or '', #'iugu_secure_payment_url': data['secure_url'], 'boleto_digitable_line': data['linhaDigitavel'] or '', #'iugu_barcode_url': data['bank_slip']['barcode'], #'transaction_ids': transaction.id, }) self.transaction_ids = [(6, 0, [transaction.id])] ### def generate_payment_transactions(self): self.ensure_one() for item in self: item.send_information_to_iugu() def action_post(self): self.validate_data_iugu() result = super(AccountMove, self).action_post() self.generate_payment_transactions() #raise ValidationError('interrupção antes do post') return result ################# inter methods ########### def _generate_bank_inter_boleto_data(self): dados = [] myself = User( name=self.company_id.l10n_br_legal_name, identifier=misc.punctuation_rm(self.company_id.l10n_br_cnpj_cpf), bank=UserBank( bankId=self.payment_mode_id.fixed_journal_id.bank_id.code_bc, branchCode=self.payment_mode_id.fixed_journal_id.bank_account_id.bra_number, accountNumber=self.payment_mode_id.fixed_journal_id.bank_account_id.acc_number, accountVerifier=self.payment_mode_id.fixed_journal_id.bank_account_id.acc_number_dig, bankName=self.payment_mode_id.fixed_journal_id.bank_id.name, ), ) #raise ValidationError(f"{self.company_id.l10n_br_legal_name} {misc.punctuation_rm(self.company_id.l10n_br_cnpj_cpf)} {self.payment_mode_id.fixed_journal_id.bank_id.code_bc} {self.payment_mode_id.fixed_journal_id.bank_account_id.bra_number} {self.payment_mode_id.fixed_journal_id.bank_account_id.acc_number} {self.payment_mode_id.fixed_journal_id.bank_account_id.acc_number_dig} {self.payment_mode_id.fixed_journal_id.bank_id.name} ") for moveline in self.receivable_move_line_ids: #if 1: payer = User( #name=moveline.partner_id.l10n_br_legal_name, name=moveline.partner_id.l10n_br_legal_name or moveline.partner_id.name, identifier=misc.punctuation_rm( moveline.partner_id.l10n_br_cnpj_cpf ), email=moveline.partner_id.email or '', personType=( "FISICA" if moveline.partner_id.company_type == 'person' else 'JURIDICA'), phone=misc.punctuation_rm( moveline.partner_id.phone).replace(" ", "")[2:], address=UserAddress( streetLine1=moveline.partner_id.street or '', district=moveline.partner_id.l10n_br_district or '', city=moveline.partner_id.city_id.name or '', stateCode=moveline.partner_id.state_id.code or '', zipCode=misc.punctuation_rm(moveline.partner_id.zip), streetNumber=moveline.partner_id.l10n_br_number, ) ) #raise ValidationError(f"{moveline.partner_id.l10n_br_legal_name} {moveline.partner_id.l10n_br_cnpj_cpf} {moveline.partner_id.email} {moveline.partner_id.company_type} {moveline.partner_id.phone} {moveline.partner_id.street} {moveline.partner_id.l10n_br_district} {moveline.partner_id.city_id.name} {moveline.partner_id.state_id.code} {moveline.partner_id.zip} {moveline.partner_id.l10n_br_number} ") _instructions = str(self.invoice_payment_term_id.note).split('\n') invoice_payment_term_id = self.invoice_payment_term_id codigoMora = invoice_payment_term_id.interst_mode mora_valor = invoice_payment_term_id.interst_value if codigoMora == 'VALORDIA' else 0 mora_taxa = invoice_payment_term_id.interst_value if codigoMora == 'TAXAMENSAL' else 0 data_mm = (moveline.date_maturity + timedelta(days=1)).strftime('%Y-%m-%d') if not codigoMora == 'ISENTO' else '' codigoMulta = invoice_payment_term_id.fine_mode multa_valor = invoice_payment_term_id.fine_value if codigoMulta == 'VALORFIXO' else 0 multa_taxa = invoice_payment_term_id.fine_value if codigoMulta == 'PERCENTUAL' else 0 mora = dict( codigoMora=codigoMora, valor=mora_valor, taxa=mora_taxa, data=data_mm ) multa = dict( codigoMulta=codigoMulta, valor=multa_valor, taxa=multa_taxa, data=data_mm ) slip = BoletoInter( sender=myself, #amount=moveline.amount_currency, amount=moveline.amount_residual, payer=payer, issue_date=moveline.date, due_date=moveline.date_maturity, identifier=moveline.move_name, mora=mora, multa=multa, #identifier='999999999999999', instructions=_instructions, ) #raise ValidationError(f"{moveline.amount_residual} {moveline.create_date} {moveline.date} {moveline.move_name} ") dados.append(slip) return dados def _generate_bank_inter_boleto(self): payment_provider = self.env['payment.acquirer'].search([('provider', '=', 'apiboletointer')]) with ArquivoCertificado(payment_provider, 'w') as (key, cert): self.api = ApiInter( cert=(cert, key), conta_corrente=(self.payment_journal_id.bank_account_id.acc_number + self.payment_journal_id.bank_account_id.acc_number_dig) ) data = self._generate_bank_inter_boleto_data() for item in data: print(item._emissao_data()) resposta = self.api.boleto_inclui(item._emissao_data()) # o pacote python tem error handle que retorna para a aplicação return resposta
class GenerateBoletoInvoice(models.Model): _inherit = 'account.invoice' nossonumero = fields.Char('Nosso Numero', size=30) # Armazena o nosso numero do boleto boleto_codigo_baixa = fields.Selection([ ('ACERTOS', 'ACERTOS'), ('PROTESTADO', 'PROTESTADO'), ('DEVOLUCAO', 'DEVOLUCAO'), ('PROTESTOAPOSBAIXA', 'PROTESTOAPOSBAIXA'), ('PAGODIRETOAOCLIENTE', 'PAGODIRETOAOCLIENTE'), ('SUBISTITUICAO', 'SUBISTITUICAO'), ('FALTADESOLUCAO', 'FALTADESOLUCAO'), ('APEDIDODOCLIENTE', 'APEDIDODOCLIENTE')], string='Codigo da Baixa') # Em caso de baixas no boleto, armazena o codigo da baixa. def dados_boleto(self): journal = self.env['account.journal'].search([('code', '=', 'Inter')]) dados = [] myself = User( name=self.company_id.legal_name, identifier=misc.punctuation_rm(self.company_id.cnpj_cpf), bank=UserBank( bankId=journal.bank_account_id.bank_id.bic, branchCode=journal.bank_account_id.bra_number, accountNumber=journal.bank_account_id.acc_number, accountVerifier=journal.bank_account_id.acc_number_dig, bankName=journal.bank_account_id.bank_id.name ), ) payer = User( name=self.partner_id.name, identifier=misc.punctuation_rm(self.partner_id.cnpj_cpf), email=self.partner_id.email, personType='FISICA' if self.partner_id.company_type == 'person' else 'JURIDICA', phone=caracteres_rm(self.partner_id.phone), address=UserAddress( streetLine1=self.partner_id.street, district=self.partner_id.district, city=self.partner_id.city_id.name, stateCode=self.partner_id.state_id.code, zipCode=misc.punctuation_rm(self.partner_id.zip), streetNumber=self.partner_id.number, ) ) slip = BoletoInter( sender=myself, amount=self.amount_total, payer=payer, issue_date=datetime.now(), identifier=misc.punctuation_rm(self.partner_id.cnpj_cpf), instructions=[ journal.instrucao1, journal.instrucao2, journal.instrucao3, journal.instrucao4, ], multa=dict( codigoMulta=journal.codigo_multa, data=str(self.date_due + timedelta(days=journal.dia_carencia_multa)), valor=0 if journal.codigo_multa == 'PERCENTUAL' else journal.valor_multa, taxa=0 if journal.codigo_multa == 'VALORFIXO' else journal.taxa_multa ), mora=dict( codigoMora=journal.codigo_mora, data=str(self.date_due + timedelta(days=journal.dia_carencia_mora)), valor=0 if journal.codigo_mora == 'TAXAMENSAL' else journal.valor_mora, taxa=0 if journal.codigo_mora == 'VALORFIXO' else journal.taxa_mora ), due_date=self.date_due, ) dados.append(slip) return dados def action_gerar_boleto(self): journal = self.env['account.journal'].search([('code', '=', 'Inter')]) with ArquivoCertificado(journal, 'w') as (key, cert): self.api = ApiInter( cert=(cert, key), conta_corrente=journal.bank_account_id.acc_number + journal.bank_account_id.acc_number_dig ) if self.pdf_boletos_id: raise exceptions.UserError( 'Já existe um boleto dessa fatura. Você pode utilizar a opção Download ou enviar por email. ' 'Caso deseje de fato gerar um novo boleto, você pode usar a opção Atualizar Boleto, ele fará a baixa' ' no banco Inter (Cancelamento) e emitirá um novo boleto!') if self.state == 'draft': raise exceptions.UserError( 'O status da fatura ainda é provisório. Valide a fatura para que então você ' 'consiga gerar o boleto. Impossível gerar boletos para fatura com status Provisório!') data = self.dados_boleto() for item in data: # Se a fatura ainda não tiver boleto (Nosso numero), então gera. if not self.pdf_boletos_id: if self.date_due < datetime.now().date(): raise exceptions.UserError( 'A data de vencimento da fatura é menor que a data de emissão do boleto' ' (Data atual). Impossível gerar um boleto com a data de vencimento menor' ' que a data de emissão!') # Inclusão do boleto e envio para a API do Inter # os dados do emitente e do pagador estão no dicionarios no inicio do codigo resposta = self.api.boleto_inclui(item._emissao_data()) # Salva o nosso numero do boleto na respectiva fatura self.nossonumero = resposta['nossoNumero'] # Seta a flag para true, indicando que foi emitido boleto dessa fatura self.boleto_emitido = True # Consultar a API do Inter para localizar e baixar o PDF do boleto gerado # A pesquisa é feita pelo nossonumero e o boleto é codificado em base64 boleto_base64 = self.api.boleto_pdf(self.nossonumero) self.pdf_boletos_id = self.env['ir.attachment'].create( { 'name': ( "Boleto %s" % self.display_name.replace('/', '-')), 'datas': boleto_base64, 'datas_fname': ("boleto_%s.pdf" % self.display_name.replace('/', '-')), 'type': 'binary' } ) def action_consulta_boleto(self): """A implementar para ser usado na futura view do painel do boleto""" pass def action_baixa_boleto(self): """ Essa função realiza operações de Baixa de boleto na API do banco inter. Ela disponibiliza uma janela com todas as opções de baixa. Ai escolher o codigo da baixa e clicar em salvar, a API do inter é chamada e enviado a requisição. """ journal = self.env['account.journal'].search([('code', '=', 'Inter')]) with ArquivoCertificado(journal, 'w') as (key, cert): self.api = ApiInter( cert=(cert, key), conta_corrente=journal.bank_account_id.acc_number + journal.bank_account_id.acc_number_dig ) if not self.pdf_boletos_id: raise exceptions.UserError( 'Ainda não existe um boleto gerado dessa fatura. Impossível imprimir/baixar pdf ' 'de um boleto inexistente!') if abs((self.date_invoice - datetime.now().date()).days) <= 2: raise exceptions.UserError( 'O boleto em questão tem menos de dois dias que foi emitido. É provavel que ' 'ainda não esteja registrado. Aguarde pelo menos dois dias para realizar a baixa') # resposta = self.api.boleto_baixa(self.nossonumero, self.boleto_codigo_baixa) view_id = self.env.ref('sismais_account_payment_inter_boleto.baixa_boleto_invoice_form').id context = self._context.copy() return { 'name': 'Baixa Boleto Inter', 'view_type': 'form', 'view_mode': 'tree', 'views': [(view_id, 'form')], 'res_model': 'account.invoice', 'view_id': view_id, 'type': 'ir.actions.act_window', 'res_id': self.id, 'target': 'new', 'context': context, } def action_pdf_boleto(self): if not self.pdf_boletos_id: raise exceptions.UserError( 'Ainda não existe um boleto gerado dessa fatura. Impossível imprimir/baixar pdf ' 'de um boleto inexistente!') boleto_id = self.pdf_boletos_id base_url = self.env['ir.config_parameter'].get_param( 'web.base.url') download_url = '/web/content/%s/%s?download=True' % ( str(boleto_id.id), boleto_id.name) return { "type": "ir.actions.act_url", "url": str(base_url) + str(download_url), "target": "new", } def baixa_recorrente_boleto(self): """ O Odoo possui um proprio sistema de Cron, usarei ele para acionar essa função diariamente. O cron pode ser feito na propria interface do Odoo ou definida por XML, nesse caso, usuario um XML, Assim que o modulo for instalado, o cron será criado. """ journal = self.env['account.journal'].search([('code', '=', 'Inter')]) with ArquivoCertificado(journal, 'w') as (key, cert): self.api = ApiInter( cert=(cert, key), conta_corrente=journal.bank_account_id.acc_number + journal.bank_account_id.acc_number_dig ) # Data inicial usada na consulta de boleto no no formato string com hifen no lugar de ponto (AAAA-MM-DD) # Uma data bem antiga para que qualquer boleto não pago, independente do tempo, venha no filtro. dt_inicial = '1990-01-01' # Data final usada na consulta de boleto no no formato string com hifen no lugar de ponto (AAAA-MM-DD) dt_final = str(datetime.now().date()).replace('.', '-') resposta = self.api.boleto_consulta('PAGOS', dt_inicial, dt_final) for i in range(len(resposta['content'])): invoice = self.env['account.invoice'].search([('nossonumero', '=', resposta['content'][i]['nossoNumero'])]) # invoice = self.env['account.invoice'].search([('nossonumero', '=', '00663999953')]) # usada para teste if invoice and invoice.state == 'open': Payment = self.env['account.payment'].with_context(default_invoice_ids=[(4, invoice.id, False)]) payment = Payment.create({ 'payment_date': datetime.now(), 'payment_method_id': 1, 'payment_type': 'inbound', 'partner_type': 'customer', 'partner_id': invoice.partner_id.id, 'amount': invoice.amount_total, 'journal_id': journal.id, 'company_id': invoice.company_id, 'currency_id': 6, 'payment_difference_handling': 'reconcile' }) payment.post() def action_atualiza_boleto(self): """ Essa função faz a atualização de boleto. Basicamente ela cancela o boleto atual e gera um novo boleto atualizado. Provavelmente existe formas melhores de realizar essa operação, porém na API do Banco Inter existe apenas 4 endpoint (Emissão, consulta, baixa e PDF), então o que encontrei no momento foi cancelar com a baixa e emitir um novo boleto. Essa operação pode ser cobrada por boletos gerados. """ journal = self.env['account.journal'].search([('code', '=', 'Inter')]) with ArquivoCertificado(journal, 'w') as (key, cert): self.api = ApiInter( cert=(cert, key), conta_corrente=journal.bank_account_id.acc_number + journal.bank_account_id.acc_number_dig ) if not self.pdf_boletos_id: raise exceptions.UserError( 'Ainda não existe um boleto gerado dessa fatura. Impossível atualizar um ' 'boleto inexistente!') if self.date < datetime.now().date() + timedelta(days=2): raise exceptions.UserError( 'O boleto em questão tem menos de dois dias que foi emitido. É provavel que ' 'ainda não esteja registrado. Aguarde pelo menos dois dias para atualizá-lo') # Realiza a baixa do boleto atual resposta_baixa = self.api.boleto_baixa(self.nossonumero, 'SUBISTITUICAO') # Depois que realizo a baixo lá no Inter, eu apago a justificativa do banco de dados, porque la vai ta o nosso numero # do novo boleto, e ele nao foi baixado. Sei que não é a melhor forma, mas futuramente podemos criar uma tabela # somente para armazenar os boletos, inclusive para consultas, podemos fazer isso quando for fazer o painel de boleto. self.boleto_codigo_baixa = None # Na função de de geração de boleto, só é gerado, se não tiver pdf_boletos_id. Esse tratamento existe para # a pessoa não gerar duas vezes. POrém, só nosso caso de atualização, precisaremos, então seto como Null # e abaixo quando a função for chamada, será gerado normalmente. Eu poderia excluir o registro do boleot, # mas futuramente, pode ser que queremos exibi-los em consultas no peinel do boleto. self.pdf_boletos_id = None # Realiza a emissão do novo boleto self.action_gerar_boleto()
class TestBancoApiInter(unittest.TestCase): def setUp(self): certificado_cert = os.environ.get('certificado_inter_cert') certificado_key = os.environ.get('certificado_inter_key') self.api = ApiInter(cert=(certificado_cert, certificado_key), conta_corrente='14054310') self.dados = [] myself = User( name='KMEE INFORMATICA LTDA', identifier='23130935000198', bank=UserBank(bankId="341", branchCode="1234", accountNumber="33333", accountVerifier="4", bankName="BANCO ITAU SA"), ) now = datetime.now() for i in range(3): payer = User(name="Sacado Teste", identifier="26103754097", email="*****@*****.**", personType="FISICA", phone="35988763663", address=UserAddress( streetLine1="Rua dos TESTES", district="CENTRO", city="SAO PAULO", stateCode="SP", zipCode="31327130", streetNumber="15", )) slip = BoletoInter(sender=myself, amount_in_cents="100.00", payer=payer, issue_date=now, due_date=now, identifier="456" + str(i), instructions=[ 'TESTE 1', 'TESTE 2', 'TESTE 3', 'TESTE 4', ]) self.dados.append(slip) def test_data(self): for item in self.dados: self.assertTrue(item._emissao_data()) def test_boleto_api(self): for item in self.dados: resposta = self.api.boleto_inclui(item._emissao_data()) item.nosso_numero = resposta['nossoNumero'] item.seu_numero = resposta['seuNumero'] item.linha_digitavel = resposta['linhaDigitavel'] item.barcode = resposta['codigoBarras'] self.assertListEqual( list(resposta.keys()), ['seuNumero', 'nossoNumero', 'codigoBarras', 'linhaDigitavel'], 'Erro ao registrar boleto') resposta = self.api.boleto_consulta(data_inicial='2020-01-01', data_final='2020-12-01', ordenar_por='SEUNUMERO') self.assertTrue(resposta, 'Falha ao consultar boletos') for item in self.dados: resposta = self.api.boleto_pdf(nosso_numero=item.nosso_numero) self.assertTrue(resposta, 'Falha ao imprimir boleto') for item in self.dados: resposta = self.api.boleto_baixa( nosso_numero=item.nosso_numero, codigo_baixa='SUBISTITUICAO', ) self.assertTrue(resposta, 'Falha ao Baixar boletos')