andreroggeri / pynubank

Acesse seus extratos do Nubank pelo Python
MIT License
1.18k stars 179 forks source link

Criação e identificação de transações #226

Closed plutaniano closed 3 years ago

plutaniano commented 3 years ago

Boa tarde, estou tentando automatizar a criação e verificação de pagamento de qr codes que envio aos meus pagadores.

Na parte de criação: Existe alguma maneira de colocar um identificador único para cada transação nos qr codes? No app, existe a opção de incluir um identificador de 12 caracteres no processo de criação do qr code , mas o método create_pix_payment_qrcode não recebe essa informação.

Na parte de verificação: no app, indo nos detalhes da transferencia, é possível verificar o identificador único que eu mencionei anteriormente, mas esse identificador não é disponibilizado no método get_account_feed. Existe alguma maneira de obter esse identificador?

Eu ainda estou me familiarizando com a lib, mas posso tentar implementar essas funcionalidades caso elas ainda não existam. Obrigado

andreroggeri commented 3 years ago

A resposta é sim para as duas perguntas, mas seria necessário implementar.

Na criação teriamos que ver qual o nome desse parametro e passar na query.

Para retornar esse identificador único provavelmente é um campo a mais na query graphql que trás o feed

A parte dificil é descobrir esses campo rs, precisa inspecionar como o app faz o request

plutaniano commented 3 years ago

Fiz umas modificações simples e consegui criar qr codes do pix com identificador e mensagem customizadas:

# pynubank/nubank.py

...

  def create_pix_payment_qrcode(self, 
                              account_id: str,
                              amount: float,
                              pix_key: dict,
                              message: str = '',
                              tx_id: str = ''
      ) -> dict:

      payload = {
          'createPaymentRequestInput': {
              'amount': amount,
              'pixAlias': pix_key.get('value'),
              'savingsAccountId': account_id,
              'message': message,   # mensagem
              'transactionId': tx_id,   # Identificador, truncado em 12 chars
          }
      }

      response = self._make_graphql_request('create_pix_money_request', payload)

      data = response['data']['createPaymentRequest']['paymentRequest']

      qr = QRCode()
      qr.add_data(data['brcode'])

      return {
          'payment_url': data['url'],
          'transactionId': data['transactionId'],
          'message': data['message'],
          'qr_code': qr,
      }

...

Agora, na parte de fazer engenharia reversa na api pra pegar o identificador das transações eu estou com um pouco de dificuldade. Tentei chutar alguns nomes, mas sem sucesso. Também tentei utilizar o mitmproxy mas o app detecta algo de estranho na conexão e se recusa a funcionar.

Qual foi o metodo que vocês utilizaram para obter parametros que já estão na lib?

andreroggeri commented 3 years ago

Também peguei essas informações do app com um proxy, no caso usei o Burp Suite.

Mas o app tem um mecanismo de segurança que não permite o uso de um certificado diferente do deles. Pra burlar isso eu usei o frida, acabei detalhando um pouco do processo em outra issue, o único problema é que é necessário um emulador ou celular com root para utilizar o frida

Ulisses1478 commented 3 years ago

Fiz umas modificações simples e consegui criar qr codes do pix com identificador e mensagem customizadas:

# pynubank/nubank.py

...

  def create_pix_payment_qrcode(self, 
                              account_id: str,
                              amount: float,
                              pix_key: dict,
                              message: str = '',
                              tx_id: str = ''
      ) -> dict:

      payload = {
          'createPaymentRequestInput': {
              'amount': amount,
              'pixAlias': pix_key.get('value'),
              'savingsAccountId': account_id,
              'message': message,   # mensagem
              'transactionId': tx_id,   # Identificador, truncado em 12 chars
          }
      }

      response = self._make_graphql_request('create_pix_money_request', payload)

      data = response['data']['createPaymentRequest']['paymentRequest']

      qr = QRCode()
      qr.add_data(data['brcode'])

      return {
          'payment_url': data['url'],
          'transactionId': data['transactionId'],
          'message': data['message'],
          'qr_code': qr,
      }

...

Agora, na parte de fazer engenharia reversa na api pra pegar o identificador das transações eu estou com um pouco de dificuldade. Tentei chutar alguns nomes, mas sem sucesso. Também tentei utilizar o mitmproxy mas o app detecta algo de estranho na conexão e se recusa a funcionar.

Qual foi o metodo que vocês utilizaram para obter parametros que já estão na lib?

Eu estou com um dispositivo tudo certo aqui só passar o print da página que te passo o request

plutaniano commented 3 years ago

O que eu preciso é das informações que aparecem quando você vai no app > Conta > Histórico, seleciona uma transação pix recebida > ver comprovante. Nessa página tem as informações que eu quero: descrição e identificador. imagem

Se você conseguir isso ai vai resolver o problema. Tentei mexer com os emuladores de android mas como tenho zero experiencia nessa plataforma não cheguei em lugar nenhum. Valeu.

Ulisses1478 commented 3 years ago

Tranquilo te passo ou hoje de noite ou amanhã eu tenho que fazer engenharia reversa em diversos apps aí acabei adquirindo a experiência

Ulisses1478 commented 3 years ago

O request do comprovante seria

{
  "operationName": "get_generic_receipt_screen",
  "variables": {
    "type": "TRANSFER_IN",
    "id": "AQUI SERIA O ID DA TRANSAÇÃO"
  },
  "query": "query get_generic_receipt_screen($type: String!, $id: ID!) {
  viewer {
    savingsAccount {
      getGenericReceiptScreen(type: $type, id: $id) {
        screenShowShareAction
        screenType
        screenPieces {
          __typename
          fallbackMessage
          ... on ReceiptHeaderPiece {
            headerTitle
            headerSubtitle
          }
          ... on ReceiptMessagePiece {
            messageTitle
            messageContent
          }
          ... on ReceiptFooterPiece {
            footerTitle
            footerContent
          }
          ... on ReceiptTablePiece {
            tableHeader {
              icon
              title
              subtitle
              deeplinkWithMeta {
                href
                analytics {
                  key
                  value
                }
              }
            }
            tableItems {
              label
              value
            }
          }
        }
      }
    }
  }
}"
}

O request sem prettify seria

{
  "operationName": "get_generic_receipt_screen",
  "variables": {
    "type": "TRANSFER_IN",
    "id": "AQUI SERIA O ID DA TRANSAÇÃO"
  },
  "query": "query get_generic_receipt_screen($type: String!, $id: ID!) {\n  viewer {\n    savingsAccount {\n      getGenericReceiptScreen(type: $type, id: $id) {\n        screenShowShareAction\n        screenType\n        screenPieces {\n          __typename\n          fallbackMessage\n          ... on ReceiptHeaderPiece {\n            headerTitle\n            headerSubtitle\n          }\n          ... on ReceiptMessagePiece {\n            messageTitle\n            messageContent\n          }\n          ... on ReceiptFooterPiece {\n            footerTitle\n            footerContent\n          }\n          ... on ReceiptTablePiece {\n            tableHeader {\n              icon\n              title\n              subtitle\n              deeplinkWithMeta {\n                href\n                analytics {\n                  key\n                  value\n                }\n              }\n            }\n            tableItems {\n              label\n              value\n            }\n          }\n        }\n      }\n    }\n  }\n}"
}
Ulisses1478 commented 3 years ago

O request de detalhes da transação seria

Prettify

{
  "operationName": "get_generic_transaction_details_screen",
  "variables": {
    "type": "TRANSFER_OUT",
    "id": "AQUI SERIA O ID DA TRANSAÇÃO"
  },
  "query": "fragment deeplinkWithMeta on GenericDeeplink {
  href
  analytics {
    key
    value
  }
}
query get_generic_transaction_details_screen($type: String!, $id: ID!) {
  viewer {
    savingsAccount {
      getGenericTransactionDetailsScreen(type: $type, id: $id) {
        screenTitle
        screenType
        screenPieces {
          __typename
          fallbackMessage
          ... on TransactionDetailsHeaderPiece {
            headerAvatar {
              initials
              icon
              badge
            }
            headerTitle
            headerSubtitle
            headerContent
          }
          ... on TransactionDetailsActionsPiece {
            actions {
              title
              deeplinkWithMeta {
                ...deeplinkWithMeta
              }
              icon
            }
          }
          ... on TransactionDetailsMessagePiece {
            messageTitle
            messageContent
          }
          ... on TransactionDetailsTablePiece {
            tableHeader {
              icon
              title
              subtitle
              deeplinkWithMeta {
                ...deeplinkWithMeta
              }
            }
            tableItems {
              label
              value
            }
          }
        }
      }
    }
  }
}
"
}

Raw

{"operationName":"get_generic_transaction_details_screen","variables":{"type":"TRANSFER_OUT","id":"AQUI SERIA O ID DA TRANSAÇÃO"},"query":"fragment deeplinkWithMeta on GenericDeeplink {\n  href\n  analytics {\n    key\n    value\n  }\n}\n\nquery get_generic_transaction_details_screen($type: String!, $id: ID!) {\n  viewer {\n    savingsAccount {\n      getGenericTransactionDetailsScreen(type: $type, id: $id) {\n        screenTitle\n        screenType\n        screenPieces {\n          __typename\n          fallbackMessage\n          ... on TransactionDetailsHeaderPiece {\n            headerAvatar {\n              initials\n              icon\n              badge\n            }\n            headerTitle\n            headerSubtitle\n            headerContent\n          }\n          ... on TransactionDetailsActionsPiece {\n            actions {\n              title\n              deeplinkWithMeta {\n                ...deeplinkWithMeta\n              }\n              icon\n            }\n          }\n          ... on TransactionDetailsMessagePiece {\n            messageTitle\n            messageContent\n          }\n          ... on TransactionDetailsTablePiece {\n            tableHeader {\n              icon\n              title\n              subtitle\n              deeplinkWithMeta {\n                ...deeplinkWithMeta\n              }\n            }\n            tableItems {\n              label\n              value\n            }\n          }\n        }\n      }\n    }\n  }\n}"}
plutaniano commented 3 years ago

Você sabe qual é o id que tem que passar nessa query? Tentei passar o id que vem com as transações do nu.get_account_feed() e também o id que eu destaquei na foto que eu mandei na outra mensagem, nenhum dos dois funcionou. Também testei com o id da conta, mas sem sucesso também.

Ulisses1478 commented 3 years ago

O id é o id o get_account_feed sim, a diferença é que o type é diferente se usa TRANSFER_IN para entrada de dinheiro e TRANSFER_OUT para saída.

Vou te mandar o código que usei para testar, quando mandar um pull request no pynubank seria bom usar ao invés do txid o objeto da transação inteira e pelos __typenames das transações classificar em [TRANSFER_IN, TRANSFER_OUT]

pynubank/nubank.py

Eu adicionei na class Nubank

def get_generic_receipt_screen(self, txid):
    data = self._make_graphql_request('get_generic_receipt_screen', {
        'type':'TRANSFER_OUT',
        'id':txid
    })
    return data

queries/get_generic_receipt_screen.gql

query get_generic_receipt_screen($type: String!, $id: ID!) {
  viewer {
    savingsAccount {
      getGenericReceiptScreen(type: $type, id: $id) {
        screenShowShareAction
        screenType
        screenPieces {
          __typename
          fallbackMessage
          ... on ReceiptHeaderPiece {
            headerTitle
            headerSubtitle
          }
          ... on ReceiptMessagePiece {
            messageTitle
            messageContent
          }
          ... on ReceiptFooterPiece {
            footerTitle
            footerContent
          }
          ... on ReceiptTablePiece {
            tableHeader {
              icon
              title
              subtitle
              deeplinkWithMeta {
                href
                analytics {
                  key
                  value
                }
              }
            }
            tableItems {
              label
              value
            }
          }
        }
      }
    }
  }
}

A propósito os campos que você queria era essa parte do request

  ... on ReceiptMessagePiece {
    messageTitle
    messageContent # Esse campo aqui mais específicamente
  }

{'__typename': 'ReceiptMessagePiece', 'fallbackMessage': 'Atualize seu app para ver todas as informações!', 'messageTitle': 'Descrição', 'messageContent': 'teste'}
ceelsoin commented 3 years ago

@Ulisses1478 é possível chamar esse objeto de query do comprovante como subquery do objeto feed? A fim de obter todos os comprovantes junto com o feed de TransferIn no mesmo objeto em um único hit na api?

andreroggeri commented 3 years ago

@Ulisses1478 valeu pelos códigos, eu abri um PR com essa alteração e deu certo com o seu código...

Só preciso dar uma melhorada na utilização e documentar, mas no geral até que é "simples". Único problema é que não dá (Pelo menos não vi como) obter essa informação na listagem geral

Ulisses1478 commented 3 years ago

@Ulisses1478 valeu pelos códigos, eu abri um PR com essa alteração e deu certo com o seu código...

Só preciso dar uma melhorada na utilização e documentar, mas no geral até que é "simples". Único problema é que não dá (Pelo menos não vi como) obter essa informação na listagem geral

Pelo que vi não tem como mesmo não na engenharia reversa pelo burp suite.