andreroggeri/pynubank

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

Closed this issue · 13 comments

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

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

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?

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

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

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.

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

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}"
}

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}"}

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.

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'}

@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?

@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 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.