Repository: andreroggeri/pynubank

Issue:
Necessidades PIX
Excelentíssimos, sou novo aqui nessa busca pelas apis loucas do nubank, eu tenho a necessidade hoje de gerar qr code de pagamento PIX, eu dei uma pesquisada com Insomnia nas rotas "discovery" e consegui Bearer token, mas ainda está muito confuso como prosseguir nessa empreitada, alguém pode me apontar caminho para melhorar minha vida  na busca dessas rotas, geração de certificado etc... ou se possível quem tiver interessado podemos fazer uma call ou grupo do discord para montarmos uma documentação e ficarmos no mesmo nível, evoluímos mais rápido trabalhando juntos!!! Tmj, Muitíssimo Grato.

OBS: Tanto Astrocoders/nubank-api quanto pynubank são api para consultar apis, acho que seria mais produtivo, focarmos na utilização das apis do nubank em si e deixar backend a cargo do desenvolvedor.... só uma opinião para quem tiver interessado.

Discussion:
coveralls: 
[![Coverage Status](https://coveralls.io/builds/35747769/badge)](https://coveralls.io/builds/35747769)

Coverage increased (+0.01%) to 99.706% when pulling **30d4ee16bc911b6c7f5745bcaeab5d3ed6a72ae9 on add-pix-support** into **35e126464a861fbecc21a80066ee04192154b561 on master**.


Patch:
diff --git a/.gitignore b/.gitignore
index 93dbd95..b1b618a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -108,4 +108,4 @@ ENV/
 .DS_Store
 
 *.p12
-*.json
\ No newline at end of file
+./*.json
\ No newline at end of file
diff --git a/examples/README.md b/examples/README.md
index da1a6b0..8cd3bcb 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -7,4 +7,7 @@
  
 ## Analisando os dados 
  - [Distribuição de gastos por categoria](./analysis.md#Gastos-por-categoria)
- - [Evolução de saldo da NuConta](./analysis.md#Evolução-do-Saldo-da-Nuconta)
\ No newline at end of file
+ - [Evolução de saldo da NuConta](./analysis.md#Evolução-do-Saldo-da-Nuconta)
+ 
+## Pix
+ - [Solicitando pagamento](./pix.md)
diff --git a/examples/pix.md b/examples/pix.md
new file mode 100644
index 0000000..477ce21
--- /dev/null
+++ b/examples/pix.md
@@ -0,0 +1,31 @@
+# Pix
+Está implementado o suporte a consulta de chaves do Pix e solicitação de cobrança (QRCode).
+
+## Solicitando um pagamento
+```python
+from pynubank import Nubank, MockHttpClient
+
+nu = Nubank(MockHttpClient())
+
+data = nu.get_available_pix_keys()
+
+print(data['keys']) # Retorna lista de chaves cadastradas no Pix
+
+print(data['account_id']) # Retorna id da sua conta
+
+# No exemplo abaixo solicitamos uma cobrança de R$ 50,25 utilizando a primeira chave cadastrada
+money_request = nu.create_pix_payment_qrcode(data['account_id'], 50.25, data['keys'][0])
+
+# Irá printar o QRCode no terminal
+money_request['qr_code'].print_ascii()
+
+# Também é possível gerar uma imagem para ser enviada através de algum sistema
+# Nesse caso irá salvar um arquivo qr_code.png que pode ser escaneado pelo app do banco para ser pago
+qr = money_request['qr_ocde']
+img = qr.make_image()
+img.save('qr_code.png')
+
+# Além do QRCode também há uma URL para pagamento
+print(money_request['payment_url'])
+
+```
\ No newline at end of file
diff --git a/pynubank/nubank.py b/pynubank/nubank.py
index db0f953..da72127 100644
--- a/pynubank/nubank.py
+++ b/pynubank/nubank.py
@@ -179,3 +179,29 @@ class Nubank:
         money_request_response = self._make_graphql_request('create_money_request', payload)
 
         return money_request_response['data']['createMoneyRequest']['moneyRequest']['url']
+
+    def get_available_pix_keys(self):
+        response = self._make_graphql_request('get_pix_keys')
+        savings_acount = response['data']['viewer']['savingsAccount']
+
+        return {'keys': savings_acount['dict']['keys'], 'account_id': savings_acount['id']}
+
+    def create_pix_payment_qrcode(self, account_id: str, amount: float, pix_key: dict) -> dict:
+        payload = {
+            'createPaymentRequestInput': {
+                'amount': amount,
+                'pixAlias': pix_key.get('value'),
+                "savingsAccountId": account_id
+            }
+        }
+
+        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'],
+            'qr_code': qr
+        }
diff --git a/pynubank/queries/create_pix_money_request.gql b/pynubank/queries/create_pix_money_request.gql
new file mode 100644
index 0000000..1b27e88
--- /dev/null
+++ b/pynubank/queries/create_pix_money_request.gql
@@ -0,0 +1,13 @@
+mutation createPaymentRequest($createPaymentRequestInput: CreatePaymentRequestInput) {
+  createPaymentRequest(input: $createPaymentRequestInput) {
+    paymentRequest {
+      id
+      amount
+      message
+      url
+      transactionId
+      pixAlias
+      brcode
+    }
+  }
+}
\ No newline at end of file
diff --git a/pynubank/queries/get_pix_keys.gql b/pynubank/queries/get_pix_keys.gql
new file mode 100644
index 0000000..5e16097
--- /dev/null
+++ b/pynubank/queries/get_pix_keys.gql
@@ -0,0 +1,19 @@
+query customerKeys {
+  viewer {
+    name
+    maskedTaxId
+    savingsAccount {
+      id
+      dict {
+        keys(onlyActive: true) {
+          id
+          kind
+          value
+          formattedValue
+          itemDeepLink
+          badge
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/pynubank/utils/mock_http.py b/pynubank/utils/mock_http.py
index 6ba3030..675e576 100644
--- a/pynubank/utils/mock_http.py
+++ b/pynubank/utils/mock_http.py
@@ -33,6 +33,10 @@ class MockHttpClient(HttpClient):
         self._results[('https://mocked-proxy-url/api/proxy/ghostflame_123',
                        str(prepare_request_body('create_money_request')))] = self._read_data('money')
         self._results[('https://mocked-proxy-url/api/proxy/customer_123', '')] = self._read_data('customer')
+        self._results[('https://mocked-proxy-url/api/proxy/ghostflame_123',
+                       str(prepare_request_body('get_pix_keys')))] = self._read_data('pix_keys')
+        self._results[('https://mocked-proxy-url/api/proxy/ghostflame_123',
+                       str(prepare_request_body('create_pix_money_request')))] = self._read_data('pix_money_request')
 
     def add_mock_url(self, url: str, graphql_object: str, response_json_name: str):
         self._results[(url, graphql_object)] = self._read_data(response_json_name)
diff --git a/pynubank/utils/mocked_responses/customer.json b/pynubank/utils/mocked_responses/customer.json
index b7b8c5d..99b0c58 100644
--- a/pynubank/utils/mocked_responses/customer.json
+++ b/pynubank/utils/mocked_responses/customer.json
@@ -22,7 +22,7 @@
         "mgm_wait",
         "mgm_other_wait"
       ],
-      "billing_address_city": "São Paulo",
+      "billing_address_city": "Sao Paulo",
       "phone": "1122334455678",
       "billing_address_locality": "Bebedouro",
       "name": "John Doe Mary Doe",
diff --git a/pynubank/utils/mocked_responses/pix_keys.json b/pynubank/utils/mocked_responses/pix_keys.json
new file mode 100644
index 0000000..2851ff2
--- /dev/null
+++ b/pynubank/utils/mocked_responses/pix_keys.json
@@ -0,0 +1,43 @@
+{
+  "data": {
+    "viewer": {
+      "name": "Some Joe Doe",
+      "maskedTaxId": "•••.456.789-••",
+      "savingsAccount": {
+        "id": "xxxxxxxxxxxxxxxxxxxxxxxx",
+        "dict": {
+          "maxKeys": 5,
+          "keysAvailableForRegistration": [
+            {
+              "kind": "PHONE"
+            },
+            {
+              "kind": "EMAIL"
+            },
+            {
+              "kind": "EVP"
+            }
+          ],
+          "keys": [
+            {
+              "id": "zzzzzzzzzzzzzz",
+              "kind": "CPF",
+              "value": "12345678912",
+              "formattedValue": "123.456.789-10",
+              "itemDeepLink": "nuapp://pix/dict/key-details/zzzzzzzzzzzzzz",
+              "badge": null
+            },
+            {
+              "id": "yyyyyyyyyyyyyyyyyyyyyyy",
+              "kind": "EMAIL",
+              "value": "john@doe",
+              "formattedValue": "john@doe",
+              "itemDeepLink": "nuapp://pix/dict/key-details/yyyyyyyyyyyyyyyyyyyyyyy",
+              "badge": null
+            }
+          ]
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/pynubank/utils/mocked_responses/pix_money_request.json b/pynubank/utils/mocked_responses/pix_money_request.json
new file mode 100644
index 0000000..7bde329
--- /dev/null
+++ b/pynubank/utils/mocked_responses/pix_money_request.json
@@ -0,0 +1,15 @@
+{
+  "data": {
+    "createPaymentRequest": {
+      "paymentRequest": {
+        "id": "zzzzzzzzzzzzzzzzzz",
+        "amount": 500.0,
+        "message": null,
+        "url": "https://nubank.com.br/pagar/tttttt/yyyyyyy",
+        "transactionId": "iiiiiiii",
+        "pixAlias": "12345678910",
+        "brcode": "12464565442165BR.GOV.BCB.PIX42136542416542146542165.005802BR5920John Doe6009SAO PAULOSf5ASF56sf654aA65sa4f6S56fs"
+      }
+    }
+  }
+}
\ No newline at end of file


Test patch:
diff --git a/tests/test_nubank_client.py b/tests/test_nubank_client.py
index 78ee227..54890d6 100644
--- a/tests/test_nubank_client.py
+++ b/tests/test_nubank_client.py
@@ -314,7 +314,7 @@ def test_get_customer():
     assert customer['name'] == 'John Doe Mary Doe'
     assert customer['billing_address_number'] == '123'
     assert customer['billing_address_line1'] == "Paulista Avenue"
-    assert customer['billing_address_city'] == "São Paulo"
+    assert customer['billing_address_city'] == "Sao Paulo"
     assert customer['billing_address_locality'] == "Bebedouro"
     assert customer['billing_address_state'] == "SP"
     assert customer['billing_address_postcode'] == "01234567"
@@ -341,3 +341,27 @@ def test_should_create_money_request():
     nubank_client.authenticate_with_qr_code('12345678912', 'hunter12', 'some-uuid')
 
     assert nubank_client.create_money_request(200) == 'https://some.tld/path1/path2'
+
+
+def test_should_fetch_pix_keys():
+    nubank_client = Nubank(client=MockHttpClient())
+    nubank_client.authenticate_with_qr_code('12345678912', 'hunter12', 'some-uuid')
+
+    data = nubank_client.get_available_pix_keys()
+
+    keys = data['keys']
+    account_id = data['account_id']
+
+    assert len(keys) == 2
+    assert keys[0]['value'] == '12345678912'
+    assert account_id == 'xxxxxxxxxxxxxxxxxxxxxxxx'
+
+def test_should_create_pix_money_request():
+    nubank_client = Nubank(client=MockHttpClient())
+    nubank_client.authenticate_with_qr_code('12345678912', 'hunter12', 'some-uuid')
+
+    keys_data = nubank_client.get_available_pix_keys()
+    request = nubank_client.create_pix_payment_qrcode('1231231232', 1232213.23, keys_data['keys'][0])
+
+    assert request['qr_code'] is not None
+    assert request['payment_url'] == 'https://nubank.com.br/pagar/tttttt/yyyyyyy'
\ No newline at end of file