Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement new types of key #1

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions pixqrcode/service/generate_code.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from pixqrcode.model.merchant_account import MerchantAccount
from pixqrcode.model.pix import Pix
from pixqrcode.utils.crc16 import crc16

from pixqrcode.service.validate_pix import ValidatePix
import re

class GenerateCode:
def __init__(self):
Expand All @@ -11,7 +12,11 @@ def left_zero(self, text: str):
return str(len(text)).zfill(2)

def generate(self, pix: Pix):
return f"00020126360014{self.merchant.globally_unique_identifier}0114{pix.mobile}520400005303986540" \
valPix = ValidatePix(pix)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aqui seguir o padrão de ter lowercase (snake_case) para as variáveis dentro de funções

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

O ValidatePix é chamado no init.py dentro do metodo is_valid()
O code tu pode passar ele utilizando a DTO Pix o parametro que a classe recebe

detect_type = valPix.detect_type_key
code = detect_type()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tu pode instanciar a classe direto e já puxar o método não precisaria passar para dentro de outra variável.
Mesma coisa o code = detect_type() tu pode passar o método já direto dentro da string, ficaria igualmente legível.


return f"00020126360014{self.merchant.globally_unique_identifier}{code}{pix.mobile}520400005303986540" \
f"{len(pix.amount.__str__())}{pix.amount.__str__()}5802{pix.country_code}59{self.left_zero(pix.name)}" \
f"{pix.name}60{self.left_zero(pix.city)}{pix.city}" \
f"62{str(len(pix.reference_label) + 4).zfill(2)}05{self.left_zero(pix.reference_label)}" \
Expand All @@ -22,4 +27,4 @@ def crc16code(self, query: str) -> str:

def format_code(self, pix: Pix):
hash_payment = self.generate(pix)
return f"{hash_payment}{self.crc16code(hash_payment).upper()[2:]}".strip()
return f"{hash_payment}{self.crc16code(hash_payment).upper()[2:]}".strip()
89 changes: 77 additions & 12 deletions pixqrcode/service/validate_pix.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,20 +47,85 @@ def reference_label(self):
else:
self.pix.reference_label = FormatValues.texts_no_space(self.pix.reference_label)

def mobile(self):
if not self.pix.mobile:
raise PixError("telefone nao informado")
def validateCPF(self):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

o padrão para o nome dos métodos também segue como lowercase (snake_case). Mesmo CPF sendo "nome" o aconselhável é seguir como os demais métodos.

cpf = self.pix.mobile
cpf = re.sub('\D', '', cpf)

numbers = [int(digit) for digit in cpf if digit.isdigit()]
if len(numbers) != 11 or len(set(numbers)) == 1:
return False

sum_of_products = sum(a*b for a, b in zip(numbers[0:9], range(10, 1, -1)))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A logica de validação de CPF ficou um pouco confusa sobre qual digito é. Coloque um comentário para ajudar a ler no futuro sobre qual é qual mais precisamente, vai ajudar bastante quando tu tem blocos de códigos parecidos

expected_digit = (sum_of_products * 10 % 11) % 10
if numbers[9] != expected_digit:
return False

sum_of_products = sum(a*b for a, b in zip(numbers[0:10], range(11, 1, -1)))
expected_digit = (sum_of_products * 10 % 11) % 10
if numbers[10] != expected_digit:
return False

return True

"""
Valida o tipo de chave e retorna o codigo de acordo com o BC
"""
def detect_type_key(self):
key = self.pix.mobile
regex = re.search(r'^[a-zA-Z0-9._-]+@[a-zA-Z0-9]+\.[a-zA-Z\.a-zA-Z]{1,10}$', key)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

o nome da variável pode deixar confuso durante o uso ao longo do código, talvez ser mais especifico regex_email = re....

# 0111 - CPF
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Números mágicos nunca são bem vindos em códigos de produção. Nesse caso aqui tu poderia utilizar uma classe do tipo Enum para definir os nomes e valores Ex: EMAIL = 0125

Enum Python

# 0111 - CNPJ
# 0125 - EMAIL
# 0114 - Telefone

code = ""
if regex is None:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No fluxo de validação da chave ele acaba realizando uma dupla função, Descobrir qual tipo de chave é se é email, cpf, cnpj ou telefone e validar a chave, isso torna o fluxo mais difícil de entender e tb com if um dentro do outro como é esse o caso aqui.

Ai o que é interessante de se fazer, ter fluxos distintos para isso. Em um você Descobrir qual tipo de chave é e o segundo validar a chave com base no tipo se houver um match.

Podendo assim ter vários métodos um para cada tipo de chave, e ai chamar um método que identifica o tipo de chave no validade (linha 14) e chama o método corresponde para aquele tipo de chave.

key_numbers = re.sub('\D', '', key)
if len(key_numbers) == 10:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

O pix não utiliza de números fixos para chaves (Até onde eu saiba), apenas telefones celulares por costume

#valida se é um telefone fixo
code = '0114'

self.pix.mobile = FormatValues.mobile(self.pix.mobile)
if not re.match(r'^.55[\d]{3}', self.pix.mobile):
if not re.match(r'^55[\d]{3}', self.pix.mobile):
self.pix.mobile = f"+55{self.pix.mobile}"
if len(key_numbers) == 12:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Varios if verificam o mesmo valor len(key_numbers) == porém retornam o mesmo valor para code. Tu pode ter condições and para tal, e ao contrario de utilizar numeros magicos (numeros fixos) você passa a ter uma faixa: Ex 12 <= len(key_numbers) <= 13

#valida se é um telefone com DDI
code = '0114'

if len(key_numbers) == 13:
#valida se é um celular com DDI
code = '0114'

elif self.validateCPF():
#valida se é um cpf com base nos calculos de digitos
code = '0111'
elif len(key_numbers) == 11:
#se na ultima validacao nao retornou true é bem provavel que seja celular

code = '0114'
elif len(key_numbers) == 14:
#verifica se é cnpj
code = '0111' #creio que o codigo esteja errado, nao achei nada sobre
else:
self.pix.mobile = f"+{self.pix.mobile}"
raise Exception("A chave informada não foi identificada")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Voltar o Exception aqui vai disparar um erro que não está padronizado para tratamento. Utilizar a classe PixError, ai quando houver um erro que precise ser reportado tu não corre o risco de parar o código simulando uma Exception genérica.

else:
# É email
code = '0125'

return code

def mobile(self):
if not self.pix.mobile:
raise PixError("Chave nao informado")
code = self.detect_type_key()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Em um debug do código, o método de detect_type_key executou duas vezes. Eu deduzo que isso não seja o comportamento espero

if code == '0114':
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Evitar a utilização de números mágicos como esse ao longo do código.

self.pix.mobile = FormatValues.mobile(self.pix.mobile)
if not re.match(r'^.55[\d]{3}', self.pix.mobile):
if not re.match(r'^55[\d]{3}', self.pix.mobile):
self.pix.mobile = f"+55{self.pix.mobile}"
else:
self.pix.mobile = f"+{self.pix.mobile}"

if 14 > len(self.pix.mobile) < 14:
raise PixError("telefone curto ou longo")
if 14 > len(self.pix.mobile) < 14:
raise PixError("telefone curto ou longo")

if not re.match(r'^.+55[\d]{3}', self.pix.mobile):
raise PixError("telefone sem o DDD")
if not re.match(r'^.+55[\d]{3}', self.pix.mobile):
raise PixError("telefone sem o DDD")
return True