Repository: andreroggeri/pynubank

Issue:
Transferências utilizando Pix
Olá, as transferências por Pix estão vindo dessa maneira, caso possa interessar a alguém.
Elas não aparece porque estão filtradas na biblioteca. Acredito que no futuro eles mudem isso.

'__typename': 'GenericFeedEvent'

Comments:

Patch:
diff --git a/pynubank/nubank.py b/pynubank/nubank.py
index b0c1be9..931ad44 100644
--- a/pynubank/nubank.py
+++ b/pynubank/nubank.py
@@ -1,6 +1,5 @@
 import calendar
 import datetime
-import re
 import uuid
 from typing import Tuple
 
@@ -10,6 +9,7 @@ from pynubank.exception import NuMissingCreditCard
 from pynubank.utils.discovery import Discovery
 from pynubank.utils.graphql import prepare_request_body
 from pynubank.utils.http import HttpClient
+from pynubank.utils.parsing import parse_float, parse_pix_transaction
 
 PAYMENT_EVENT_TYPES = (
     'TransferOutEvent',
@@ -20,7 +20,11 @@ PAYMENT_EVENT_TYPES = (
     'DebitPurchaseReversalEvent',
     'BillPaymentEvent',
     'DebitWithdrawalFeeEvent',
-    'DebitWithdrawalEvent'
+    'DebitWithdrawalEvent',
+    'PixTransferOutEvent',
+    'PixTransferInEvent',
+    'PixTransferOutReversalEvent',
+    'PixTransferFailedEvent',
 )
 
 
@@ -157,6 +161,7 @@ class Nubank:
 
     def get_account_statements(self):
         feed = self.get_account_feed()
+        feed = map(parse_pix_transaction, feed)
         return list(filter(lambda x: x['__typename'] in PAYMENT_EVENT_TYPES, feed))
 
     def get_account_balance(self):
@@ -179,7 +184,7 @@ class Nubank:
 
         value = data['data']['viewer']['productFeatures']['savings']['screens']['detailedBalance']['monthBalanceSection']['yieldSection']['semantics']['label']
 
-        return float(re.search(r'\d*,\d\d', value).group().replace(',', '.'))
+        return parse_float(value)
 
     def create_boleto(self, amount: float) -> str:
         customer_id_response = self._make_graphql_request('account_id')
diff --git a/pynubank/utils/parsing.py b/pynubank/utils/parsing.py
new file mode 100644
index 0000000..6dab3b4
--- /dev/null
+++ b/pynubank/utils/parsing.py
@@ -0,0 +1,28 @@
+import re
+
+TITLE_INFLOW_PIX = 'Transferência recebida'
+TITLE_OUTFLOW_PIX = 'Transferência enviada'
+TITLE_REVERSAL_PIX = 'Reembolso enviado'
+TITLE_FAILED_PIX = 'Transferência falhou'
+
+PIX_TRANSACTION_MAP = {
+    TITLE_INFLOW_PIX: 'PixTransferInEvent',
+    TITLE_OUTFLOW_PIX: 'PixTransferOutEvent',
+    TITLE_REVERSAL_PIX: 'PixTransferOutReversalEvent',
+    TITLE_FAILED_PIX: 'PixTransferFailedEvent',
+}
+
+
+def parse_float(value: str):
+    return float(re.search(r'(?:\d.)?\d+,\d*', value).group().replace('.', '').replace(',', '.'))
+
+
+def parse_pix_transaction(transaction: dict) -> dict:
+    if transaction['__typename'] != 'GenericFeedEvent':
+        return transaction
+
+    if transaction['title'] in PIX_TRANSACTION_MAP.keys():
+        transaction['__typename'] = PIX_TRANSACTION_MAP[transaction['title']]
+        transaction['amount'] = parse_float(transaction['detail'])
+
+    return transaction


Test patch:
diff --git a/tests/test_nubank_client.py b/tests/test_nubank_client.py
index 688d5ec..d122511 100644
--- a/tests/test_nubank_client.py
+++ b/tests/test_nubank_client.py
@@ -251,7 +251,7 @@ def test_get_account_statements():
 
     statements = nubank_client.get_account_statements()
 
-    assert len(statements) == 12
+    assert len(statements) == 23
     assert statements[0]['id'] == 'e409e495-4a16-4bad-9ddb-5c447c84fdcb'
     assert statements[0]['__typename'] == 'TransferOutEvent'
     assert statements[0]['title'] == 'Transferência enviada'
@@ -259,12 +259,12 @@ def test_get_account_statements():
     assert statements[0]['postDate'] == '2021-04-14'
     assert statements[0]['amount'] == 4496.9
 
-    assert statements[11]['id'] == 'a9f96774-37f2-431e-9e6f-a081defacf25'
-    assert statements[11]['__typename'] == 'BarcodePaymentEvent'
-    assert statements[11]['title'] == 'Pagamento efetuado'
-    assert statements[11]['detail'] == 'CONFIDENCE CORRETORA DE CAMBIO S A'
-    assert statements[11]['postDate'] == '2020-12-08'
-    assert statements[11]['amount'] == 4245.1
+    assert statements[22]['id'] == 'a9f96774-37f2-431e-9e6f-a081defacf25'
+    assert statements[22]['__typename'] == 'BarcodePaymentEvent'
+    assert statements[22]['title'] == 'Pagamento efetuado'
+    assert statements[22]['detail'] == 'CONFIDENCE CORRETORA DE CAMBIO S A'
+    assert statements[22]['postDate'] == '2020-12-08'
+    assert statements[22]['amount'] == 4245.1
 
 
 def test_get_account_investments_details():
diff --git a/tests/test_parsing.py b/tests/test_parsing.py
new file mode 100644
index 0000000..f7c38c7
--- /dev/null
+++ b/tests/test_parsing.py
@@ -0,0 +1,60 @@
+from pynubank.utils.parsing import parse_pix_transaction
+
+base_transaction = {
+    "id": "12c77a49-21c2-427d-8662-beba354e8356",
+    "__typename": "GenericFeedEvent",
+    "title": "Transferência enviada",
+    "detail": "Waldisney da Silva\nR$ 3.668,40",
+    "postDate": "2021-03-24"
+}
+
+
+def test_should_do_nothing_with_transactions_that_arent_pix():
+    transaction = base_transaction.copy()
+    transaction['__typename'] = 'TransferInEvent'
+    transaction['amount'] = 3429
+
+    parsed = parse_pix_transaction(transaction)
+
+    assert parsed['__typename'] == transaction['__typename']
+    assert parsed['amount'] == transaction['amount']
+
+
+def test_should_parse_inflow_pix_transaction():
+    transaction = base_transaction.copy()
+    transaction['title'] = 'Transferência recebida'
+
+    parsed = parse_pix_transaction(transaction)
+
+    assert parsed['__typename'] == 'PixTransferInEvent'
+    assert parsed['amount'] == 3668.40
+
+
+def test_should_parse_outflow_pix_transaction():
+    transaction = base_transaction.copy()
+    transaction['title'] = 'Transferência enviada'
+
+    parsed = parse_pix_transaction(transaction)
+
+    assert parsed['__typename'] == 'PixTransferOutEvent'
+    assert parsed['amount'] == 3668.40
+
+
+def test_should_parse_reversal_pix_transaction():
+    transaction = base_transaction.copy()
+    transaction['title'] = 'Reembolso enviado'
+
+    parsed = parse_pix_transaction(transaction)
+
+    assert parsed['__typename'] == 'PixTransferOutReversalEvent'
+    assert parsed['amount'] == 3668.40
+
+
+def test_should_parse_failed_pix_transaction():
+    transaction = base_transaction.copy()
+    transaction['title'] = 'Transferência falhou'
+
+    parsed = parse_pix_transaction(transaction)
+
+    assert parsed['__typename'] == 'PixTransferFailedEvent'
+    assert parsed['amount'] == 3668.40