diff --git a/api.saladeaula.digital/app/keep_warm.py b/api.saladeaula.digital/app/keep_warm.py index 40dd622..809faac 100644 --- a/api.saladeaula.digital/app/keep_warm.py +++ b/api.saladeaula.digital/app/keep_warm.py @@ -34,8 +34,12 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: def ping(url: str): + # https://requests.readthedocs.io/en/latest/user/advanced/#timeouts + connect_timeout = 1 + read_timeout = 3 + try: - r = requests.get(url, timeout=4) + r = requests.get(url, timeout=(connect_timeout, read_timeout)) r.raise_for_status() except requests.exceptions.RequestException as exc: return { diff --git a/enrollments-events/app/config.py b/enrollments-events/app/config.py index 9a11744..ff012e2 100644 --- a/enrollments-events/app/config.py +++ b/enrollments-events/app/config.py @@ -11,6 +11,9 @@ BUCKET_NAME: str = os.getenv('BUCKET_NAME') # type: ignore EMAIL_SENDER = ('EDUSEG®', 'noreply@eduseg.com.br') +HTTP_CONNECT_TIMEOUT = int(os.environ.get('HTTP_CONNECT_TIMEOUT', 1)) +HTTP_READ_TIMEOUT = int(os.environ.get('HTTP_READ_TIMEOUT', 3)) + PAPERFORGE_API = 'https://paperforge.saladeaula.digital' CERT_REPORTING_URI = 's3://saladeaula.digital/certs/reporting.html' ESIGN_URI = 's3://saladeaula.digital/esigns/11de2510136adbac.pfx' diff --git a/enrollments-events/app/events/issue_cert.py b/enrollments-events/app/events/issue_cert.py index 1ff082c..c3ef1fa 100644 --- a/enrollments-events/app/events/issue_cert.py +++ b/enrollments-events/app/events/issue_cert.py @@ -16,6 +16,8 @@ from config import ( BUCKET_NAME, COURSE_TABLE, ENROLLMENT_TABLE, + HTTP_CONNECT_TIMEOUT, + HTTP_READ_TIMEOUT, PAPERFORGE_API, ) @@ -149,7 +151,7 @@ def _generate_cert( else None, }, }, - timeout=(1, 3), + timeout=(HTTP_CONNECT_TIMEOUT, HTTP_READ_TIMEOUT), ) r.raise_for_status() diff --git a/orders-events/app/app.py b/orders-events/app/app.py index bbe41ec..97478b1 100644 --- a/orders-events/app/app.py +++ b/orders-events/app/app.py @@ -19,9 +19,9 @@ app = APIGatewayHttpResolver(enable_validation=True) dyn = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client) -@app.post('/') +@app.post('/postback/') @tracer.capture_method -def postback(): +def postback(order_id: str): return Response(status_code=HTTPStatus.NO_CONTENT) diff --git a/orders-events/app/config.py b/orders-events/app/config.py index 0619787..e4ac2f8 100644 --- a/orders-events/app/config.py +++ b/orders-events/app/config.py @@ -7,6 +7,11 @@ ENROLLMENT_TABLE: str = os.getenv('ENROLLMENT_TABLE') # type: ignore IUGU_ACCOUNT_ID: str = 'AF01CF1B3451459F92666F10589278EE' IUGU_API_TOKEN: str = os.getenv('IUGU_API_TOKEN') # type: ignore +IUGU_TEST_MODE: bool = os.getenv('AWS_LAMBDA_FUNCTION_NAME') is None +IUGU_POSTBACK_URL = 'https://zjg09ppxq8.execute-api.sa-east-1.amazonaws.com' + +HTTP_CONNECT_TIMEOUT = int(os.environ.get('HTTP_CONNECT_TIMEOUT', 1)) +HTTP_READ_TIMEOUT = int(os.environ.get('HTTP_READ_TIMEOUT', 3)) BUCKET_NAME: str = os.getenv('BUCKET_NAME') # type: ignore diff --git a/orders-events/app/events/billing/close_window.py b/orders-events/app/events/billing/close_window.py index 215c23a..c298c2d 100644 --- a/orders-events/app/events/billing/close_window.py +++ b/orders-events/app/events/billing/close_window.py @@ -12,7 +12,14 @@ from layercake.dateutils import now from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair from boto3clients import dynamodb_client, s3_client -from config import BILLING_TEMPLATE_URI, BUCKET_NAME, ORDER_TABLE, PAPERFORGE_API +from config import ( + BILLING_TEMPLATE_URI, + BUCKET_NAME, + HTTP_CONNECT_TIMEOUT, + HTTP_READ_TIMEOUT, + ORDER_TABLE, + PAPERFORGE_API, +) logger = Logger(__name__) order_layer = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client) @@ -50,7 +57,11 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: try: # Send template URI and data to Paperforge API to generate a PDF - r = requests.post(PAPERFORGE_API, data=json_data, timeout=6) + r = requests.post( + PAPERFORGE_API, + data=json_data, + timeout=(HTTP_CONNECT_TIMEOUT, HTTP_READ_TIMEOUT), + ) r.raise_for_status() except requests.exceptions.Timeout: logger.info('The request timed out') diff --git a/orders-events/app/events/payments/charge_credit_card.py b/orders-events/app/events/payments/charge_credit_card.py index a1939d3..62f8cee 100644 --- a/orders-events/app/events/payments/charge_credit_card.py +++ b/orders-events/app/events/payments/charge_credit_card.py @@ -9,10 +9,18 @@ from layercake.dynamodb import ( ) from boto3clients import dynamodb_client -from config import ORDER_TABLE +from config import IUGU_ACCOUNT_ID, IUGU_API_TOKEN, IUGU_TEST_MODE, ORDER_TABLE +from iugu import Credentials, Iugu logger = Logger(__name__) dyn = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client) +iugu = Iugu( + Credentials( + IUGU_ACCOUNT_ID, + IUGU_API_TOKEN, + test_mode=IUGU_TEST_MODE, + ) +) @event_source(data_class=EventBridgeEvent) diff --git a/orders-events/app/events/payments/create_invoice.py b/orders-events/app/events/payments/create_invoice.py index 524fd01..a7a047b 100644 --- a/orders-events/app/events/payments/create_invoice.py +++ b/orders-events/app/events/payments/create_invoice.py @@ -4,27 +4,77 @@ from aws_lambda_powertools.utilities.data_classes import ( event_source, ) from aws_lambda_powertools.utilities.typing import LambdaContext +from layercake.dateutils import now from layercake.dynamodb import ( DynamoDBPersistenceLayer, + SortKey, + TransactKey, ) from boto3clients import dynamodb_client -from config import ORDER_TABLE +from config import ( + IUGU_ACCOUNT_ID, + IUGU_API_TOKEN, + IUGU_POSTBACK_URL, + IUGU_TEST_MODE, + ORDER_TABLE, +) +from iugu import Credentials, Iugu, Order logger = Logger(__name__) dyn = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client) +iugu = Iugu( + Credentials( + IUGU_ACCOUNT_ID, + IUGU_API_TOKEN, + test_mode=IUGU_TEST_MODE, + ) +) @event_source(data_class=EventBridgeEvent) @logger.inject_lambda_context def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: new_image = event.detail['new_image'] + now_ = now() order_id = new_image['id'] + r = dyn.collection.get_items( + TransactKey(order_id) + + SortKey('ADDRESS', rename_key='address') + + SortKey('ITEMS', path_spec='items', rename_key='items'), + flatten_top=False, + ) - doc = { - 'id': order_id, - 'sk': 'IUGU', - 'invoice_id': '', - } + payment_method = new_image['payment_method'] + is_pix = payment_method == 'PIX' + is_bank_slip = payment_method == 'BANK_SLIP' + + invoice = iugu.create_invoice( + order=Order( + address=r.get('address', {}), + items=r.get('items', []), + **new_image, + ), + postback_url=f'{IUGU_POSTBACK_URL}/postback/{order_id}', + ) + + try: + dyn.put_item( + item={ + 'id': order_id, + 'sk': 'INVOICE', + 'payment_method': payment_method, + 'secure_id': invoice['secure_id'], + 'secure_url': invoice['secure_url'], + 'created_at': now_, + # Uncomment this when adding for multiple payment providers + # 'payment_provider': 'iugu', + } + | ({'bank_slip': invoice['bank_slip']} if is_bank_slip else {}) + | ({'pix': invoice['pix']} if is_pix else {}), + cond_expr='attribute_not_exists(sk)', + ) + except Exception: + pass return True diff --git a/orders-events/app/iugu.py b/orders-events/app/iugu.py index 3c20c60..6b4568a 100644 --- a/orders-events/app/iugu.py +++ b/orders-events/app/iugu.py @@ -15,6 +15,7 @@ Documentation: - https://support.iugu.com/hc/pt-br/articles/212456346-Usar-cart%C3%B5es-de-teste-em-modo-de-teste """ +import os from dataclasses import dataclass from datetime import datetime from decimal import Decimal @@ -24,7 +25,10 @@ from urllib.parse import ParseResult, urlparse import requests from aws_lambda_powertools import Logger from layercake.extra_types import CreditCard -from pydantic import BaseModel, ConfigDict, HttpUrl +from pydantic import BaseModel, ConfigDict + +HTTP_CONNECT_TIMEOUT = int(os.environ.get('HTTP_CONNECT_TIMEOUT', 1)) +HTTP_READ_TIMEOUT = int(os.environ.get('HTTP_READ_TIMEOUT', 3)) logger = Logger(__name__) @@ -71,16 +75,6 @@ class Order(BaseModel): cnpj: str | None = None -@dataclass -class Invoice: ... - - -@dataclass -class BankSlip(BaseModel): - digitable_line: str - bank_slip_url: HttpUrl - - @dataclass class Credentials: account_id: str @@ -88,17 +82,6 @@ class Credentials: test_mode: bool = True -@dataclass -class Token(str): - id: str - - -@dataclass -class Transaction: - status: Status - response: dict - - class Iugu: base_url: ParseResult = urlparse('https://api.iugu.com') @@ -114,7 +97,7 @@ class Iugu: self, order: Order, postback_url: ParseResult | str, - ) -> Invoice: + ) -> dict: """ O que é uma fatura? ------------------- @@ -127,6 +110,8 @@ class Iugu: No response dessa chamada é retornado uma url na propriedade secure_url, onde o cliente final pode acessar e efetuar o pagamento em um checkout da IUGU. + + - https://dev.iugu.com/reference/criar-fatura """ url = self.url(path='/v1/invoices') @@ -168,33 +153,25 @@ class Iugu: } try: - r = requests.post(url, json=payload, timeout=15) + r = requests.post( + url, json=payload, timeout=(HTTP_CONNECT_TIMEOUT, HTTP_READ_TIMEOUT) + ) r.raise_for_status() except requests.HTTPError as err: logger.exception(err) raise else: return r.json() - # pix = ( - # Pix(**response['pix']) - # if order.payment_method == PaymentMethod.PIX - # else None - # ) - # bank_slip = ( - # BankSlip(**response['bank_slip']) - # if order.payment_method == PaymentMethod.BANK_SLIP - # else None - # ) - # return Invoice( - # id=response['secure_id'], - # pdf=bank_slip.bank_slip_url - # if bank_slip - # else '%s.pdf' % response['secure_url'], - # pix=pix, - # ) + def payment_token(self, credit_card: CreditCard) -> dict: + """When creating a invoice, it can't make the charge immediately the invoice. + It's necessary make have a token (payment token) to charge. + + Payment token doesn't depends an invoice, just a credit card to charge later. + + - https://dev.iugu.com/reference/criar-token + """ - def payment_token(self, credit_card: CreditCard) -> Token: url = self.url(path='/v1/payment_token') try: @@ -213,21 +190,21 @@ class Iugu: 'year': credit_card.exp_year, }, }, - timeout=15, + timeout=(HTTP_CONNECT_TIMEOUT, HTTP_READ_TIMEOUT), ) r.raise_for_status() except requests.HTTPError as err: logger.exception(err) raise else: - return Token(r.json()['id']) + return r.json() def charge( self, invoice_id: str, - token: Token, + token: str, installments: int = 1, - ) -> Transaction: + ) -> dict: """ O que é Cobrança Direta ----------------------- @@ -240,6 +217,8 @@ class Iugu: e o token gerado para o cartão de crédito (iugu js). Se o seu intuito é gerar apenas um boleto, essa chamada também retorna o PDF do boleto e o link de pagamento para efetuar o pagamento. + + - https://dev.iugu.com/reference/cobranca-direta """ url = self.url(path='/v1/charge') @@ -250,24 +229,21 @@ class Iugu: } try: - response = requests.post(url, json=payload, timeout=15) - response.raise_for_status() + r = requests.post( + url, json=payload, timeout=(HTTP_CONNECT_TIMEOUT, HTTP_READ_TIMEOUT) + ) + r.raise_for_status() except requests.HTTPError as err: logger.exception(err) raise else: - success = response.json()['success'] - - return Transaction( - status=Status.PAID if success else Status.DECLINED, - response=response.json(), - ) + return r.json() def get_invoice(self, invoice_id: str) -> dict: url = self.url(path=f'/v1/invoices/{format_id(invoice_id)}') try: - r = requests.get(url, timeout=15) + r = requests.get(url, timeout=(HTTP_CONNECT_TIMEOUT, HTTP_READ_TIMEOUT)) r.raise_for_status() except requests.HTTPError as err: logger.exception(err) diff --git a/orders-events/template.yaml b/orders-events/template.yaml index 11acae1..45d4a58 100644 --- a/orders-events/template.yaml +++ b/orders-events/template.yaml @@ -78,6 +78,47 @@ Resources: Method: POST ApiId: !Ref HttpApi + EventPaymentsCreateInvoiceFunction: + Type: AWS::Serverless::Function + Properties: + Handler: events.payments.create_invoice.lambda_handler + LoggingConfig: + LogGroup: !Ref EventLog + Policies: + - DynamoDBCrudPolicy: + TableName: !Ref OrderTable + Events: + Event: + Type: EventBridgeRule + Properties: + Pattern: + resources: [!Ref OrderTable] + detail-type: [INSERT] + detail: + new_image: + sk: ['0'] + + EventPaymentsChargeCreditCardFunction: + Type: AWS::Serverless::Function + Properties: + Handler: events.payments.charge_credit_card.lambda_handler + LoggingConfig: + LogGroup: !Ref EventLog + Policies: + - DynamoDBCrudPolicy: + TableName: !Ref OrderTable + Events: + Event: + Type: EventBridgeRule + Properties: + Pattern: + resources: [!Ref OrderTable] + detail-type: [INSERT] + detail: + new_image: + sk: ['INVOICE'] + payment_method: ['CREDIT_CARD'] + EventBillingAppendEnrollmentFunction: Type: AWS::Serverless::Function Properties: @@ -192,6 +233,7 @@ Resources: old_image: status: [PENDING] + # DEPRECATED EventAppendOrgIdFunction: Type: AWS::Serverless::Function Properties: @@ -219,6 +261,7 @@ Resources: tenant_id: - exists: false + # DEPRECATED EventAppendUserIdFunction: Type: AWS::Serverless::Function Properties: diff --git a/orders-events/tests/events/payments/test_create_invoice.py b/orders-events/tests/events/payments/test_create_invoice.py index 1de3510..210a139 100644 --- a/orders-events/tests/events/payments/test_create_invoice.py +++ b/orders-events/tests/events/payments/test_create_invoice.py @@ -1,21 +1,91 @@ +from decimal import Decimal + +import requests from aws_lambda_powertools.utilities.typing.lambda_context import LambdaContext -from layercake.dynamodb import DynamoDBPersistenceLayer +from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair import events.payments.create_invoice as app +from ...test_iugu import MockResponse -def test_create_invoice( - dynamodb_seeds, - dynamodb_persistence_layer: DynamoDBPersistenceLayer, - lambda_context: LambdaContext, -): - event = { +order_id = '121c1140-779d-4664-8d99-4a006a22f547' + + +def _event(payment_method: str) -> dict: + return { 'detail': { 'new_image': { - 'id': '', - 'sk': '0' + 'id': order_id, + 'sk': '0', + 'total': Decimal(' 267.3'), + 'name': 'Beta Educação', + 'payment_method': payment_method, + 'create_date': '2026-01-07T19:07:49.272967-03:00', + 'due_date': '2026-01-12T00:35:44.897447-03:00', + 'coupon': '10OFF', + 'discount': Decimal('-29.7'), + 'updated_at': '2026-01-07T19:07:51.512605-03:00', + 'tenant_id': 'cJtK9SsnJhKPyxESe7g3DG', + 'email': 'org+15608435000190@users.noreply.saladeaula.digital', + 'org_id': 'cJtK9SsnJhKPyxESe7g3DG', + 'cnpj': '15608435000190', + 'status': 'PENDING', + 'subtotal': Decimal('297'), } } } - assert app.lambda_handler(event, lambda_context) # type: ignore + +def test_create_bank_slip_invoice( + monkeypatch, + dynamodb_seeds, + dynamodb_persistence_layer: DynamoDBPersistenceLayer, + lambda_context: LambdaContext, +): + monkeypatch.setattr( + requests, + 'post', + lambda *args, **kwargs: MockResponse( + 'tests/samples/iugu_invoice_bank_slip.json' + ), + ) + + assert app.lambda_handler(_event('BANK_SLIP'), lambda_context) # type: ignore + + invoice = dynamodb_persistence_layer.get_item(KeyPair(order_id, 'INVOICE')) + + assert ( + invoice['secure_url'] + == 'https://checkout.iugu.com/invoices/16f7aa3d-2e0b-41e9-987b-1dc95b957456-d7a2' + ) + assert invoice['secure_id'] == '16f7aa3d-2e0b-41e9-987b-1dc95b957456-d7a2' + assert ( + invoice['bank_slip']['bank_slip_url'] + == 'https://boletos.iugu.com/v1/public/invoice/16f7aa3d-2e0b-41e9-987b-1dc95b957456-d7a2/bank_slip' + ) + + +def test_create_bank_slip_pix( + monkeypatch, + dynamodb_seeds, + dynamodb_persistence_layer: DynamoDBPersistenceLayer, + lambda_context: LambdaContext, +): + monkeypatch.setattr( + requests, + 'post', + lambda *args, **kwargs: MockResponse('tests/samples/iugu_invoice_pix.json'), + ) + + assert app.lambda_handler(_event('PIX'), lambda_context) # type: ignore + + invoice = dynamodb_persistence_layer.get_item(KeyPair(order_id, 'INVOICE')) + + assert ( + invoice['pix']['qrcode_text'] + == 'http://faturas.iugu.com/iugu_pix/970cb579-c396-4e59-a323-ce61ae04f7bc-196c/test/pay' + ) + assert ( + invoice['pix']['qrcode_text'] + == 'http://faturas.iugu.com/iugu_pix/970cb579-c396-4e59-a323-ce61ae04f7bc-196c/test/pay' + ) diff --git a/orders-events/tests/samples/iugu_invoice_bank_slip.json b/orders-events/tests/samples/iugu_invoice_bank_slip.json index 2f71cf7..7102fd1 100644 --- a/orders-events/tests/samples/iugu_invoice_bank_slip.json +++ b/orders-events/tests/samples/iugu_invoice_bank_slip.json @@ -134,16 +134,7 @@ "bank_slip_error_message": null, "recipient_cpf_cnpj": "15111975000164" }, - "pix": { - "qrcode": "https://faturas.iugu.com/qr_code/16f7aa3d-2e0b-41e9-987b-1dc95b957456-d7a2", - "qrcode_text": "00020101021226840014br.gov.bcb.pix2562qr.iugu.com/public/payload/v2/16F7AA3D2E0B41E9987B1DC95B957456520400005303986540530.005802BR5925IUGU INSTITUICAO DE PAGAM6009SAO PAULO62070503***630446D8", - "status": "qr_code_created", - "payer_cpf_cnpj": null, - "payer_name": null, - "end_to_end_id": null, - "end_to_end_refund_id": null, - "account_number_last_digits": null - }, + "pix": null, "items": [ { "id": "0F30A8B1ED244325BBC6C8A1AE34AE18", diff --git a/orders-events/tests/samples/iugu_payment_token.json b/orders-events/tests/samples/iugu_payment_token.json new file mode 100644 index 0000000..249edc9 --- /dev/null +++ b/orders-events/tests/samples/iugu_payment_token.json @@ -0,0 +1,13 @@ +{ + "id": "ae5204f4-663b-451f-ad65-7c512badb84e", + "method": "credit_card", + "extra_info": { + "bin": "411111", + "year": 2029, + "month": 12, + "brand": "VISA", + "holder_name": "S\u00e9rgio R Siqueira", + "display_number": "XXXX-XXXX-XXXX-1111" + }, + "test": true +} diff --git a/orders-events/tests/seeds.jsonl b/orders-events/tests/seeds.jsonl index 19aab55..7912ba0 100644 --- a/orders-events/tests/seeds.jsonl +++ b/orders-events/tests/seeds.jsonl @@ -14,12 +14,20 @@ {"id": "2849f1d5-f4f1-411e-8497-ec3a40afc0ab", "sk": "ITEMS", "items": [ { "name": "CIPA Grau de Risco 1", "id": "3c27ea9c-9464-46a1-9717-8c1441793186", "quantity": 1, "unit_price": 99 }, { "name": "CIPA Grau de Risco 2", "id": "99bb3b60-4ded-4a8e-937c-ba2d78ec6454", "quantity": 1, "unit_price": 99 } ], "created_at": "2026-01-07T19:09:54.193859-03:00"} {"id": "2849f1d5-f4f1-411e-8497-ec3a40afc0ab", "sk": "ADDRESS", "city": "São José", "postcode": "88101001", "state": "SC", "created_at": "2026-01-07T19:09:54.193859-03:00", "address1": "Avenida Presidente Kennedy" "address2": "", "neighborhood": "Campinas"} +// Seeds for Iugu +// file: tests/events/payments/test_create_invoice.py +{"id": "121c1140-779d-4664-8d99-4a006a22f547", "sk": "0", "total": "267.3", "name": "Beta Educação", "payment_method": "BANK_SLIP", "create_date": "2026-01-07T19:07:49.272967-03:00", "due_date": "2026-01-12T00:35:44.897447-03:00", "coupon": "10OFF", "discount": "-29.7", "updated_at": "2026-01-07T19:07:51.512605-03:00", "tenant_id": "cJtK9SsnJhKPyxESe7g3DG", "email": "org+15608435000190@users.noreply.saladeaula.digital", "org_id": "cJtK9SsnJhKPyxESe7g3DG", "cnpj": "15608435000190", "status": "PENDING", "subtotal": 297} +{"id": "121c1140-779d-4664-8d99-4a006a22f547", "sk": "ADDRESS", "city": "São José", "neighborhood": "Campinas", "address2": "", "postcode": "88101001", "state": "SC", "address1": "Avenida Presidente Kennedy", "created_at": "2026-01-07T19:07:49.272967-03:00"} +{"id": "121c1140-779d-4664-8d99-4a006a22f547", "sk": "ITEMS", "items": [{"name": "CIPA Grau de Risco 2", "id": "99bb3b60-4ded-4a8e-937c-ba2d78ec6454", "quantity": 3, "unit_price": 99}], "created_at": "2026-01-07T19:07:49.272967-03:00"} + + // User data {"id": "5OxmMjL-ujoR5IMGegQz", "sk": "0", "name": "Sérgio R Siqueira"} {"id": "cnpj", "sk": "15608435000190", "user_id": "cJtK9SsnJhKPyxESe7g3DG"} {"id": "cpf", "sk": "07879819908", "user_id": "5OxmMjL-ujoR5IMGegQz"} {"id": "email", "sk": "sergio@somosbeta.com.br", "user_id": "5OxmMjL-ujoR5IMGegQz"} +// @DEPRECATED // Slots {"id": "vacancies#cJtK9SsnJhKPyxESe7g3DG", "sk": "9omWNKymwU5U4aeun6mWzZ#1"} {"id": "vacancies#cJtK9SsnJhKPyxESe7g3DG", "sk": "9omWNKymwU5U4aeun6mWzZ#2"} diff --git a/orders-events/tests/test_iugu.py b/orders-events/tests/test_iugu.py index 1a292cb..5fb996b 100644 --- a/orders-events/tests/test_iugu.py +++ b/orders-events/tests/test_iugu.py @@ -1,19 +1,42 @@ import json import os +from uuid import uuid4 import requests from layercake.extra_types import CreditCard -from iugu import Credentials, Iugu, Order, Status, Token +from iugu import Credentials, Iugu, Order iugu = Iugu( Credentials( 'AF01CF1B3451459F92666F10589278EE', - os.getenv('IUGU_API_TOKEN'), + os.getenv('IUGU_API_TOKEN'), # type: ignore test_mode=True, ), ) +event = { + 'name': 'Sérgio Siqueira', + 'email': 'sergio@somosbeta.com.br', + 'due_date': '2026-11-12', + 'cpf': '07879819908', + 'address': { + 'postcode': '82100410', + 'address1': 'Rua Manoel José Pereira', + 'address2': '202', + 'neighborhood': 'Pilarzinho', + 'city': 'Curitiba', + 'state': 'PR', + }, + 'items': [ + { + 'id': '1', + 'name': 'Pen', + 'unit_price': 100, + }, + ], +} + class MockResponse: def __init__(self, jsonfile: str | None = None) -> None: @@ -31,22 +54,24 @@ class MockResponse: def test_create_invoice_pix(monkeypatch): - # monkeypatch.setattr( - # requests, - # 'post', - # lambda *args, **kwargs: MockResponse('tests/samples/iugu_invoice_pix.json'), - # ) + monkeypatch.setattr( + requests, + 'post', + lambda *args, **kwargs: MockResponse('tests/samples/iugu_invoice_pix.json'), + ) - order = Order(**event) + order = Order( + id=str(uuid4()), + payment_method='PIX', # type: ignore + **event, + ) invoice = iugu.create_invoice(order, postback_url='http://localhost') - print(invoice) - - # assert invoice.id == '970cb579-c396-4e59-a323-ce61ae04f7bc-196c' - # assert ( - # invoice.pix.qrcode_text - # == 'http://faturas.iugu.com/iugu_pix/970cb579-c396-4e59-a323-ce61ae04f7bc-196c/test/pay' - # ) + assert invoice['id'] == '970CB579C3964E59A323CE61AE04F7BC' + assert ( + invoice['pix']['qrcode_text'] + == 'http://faturas.iugu.com/iugu_pix/970cb579-c396-4e59-a323-ce61ae04f7bc-196c/test/pay' + ) def test_create_invoice_bank_slip(monkeypatch): @@ -58,11 +83,16 @@ def test_create_invoice_bank_slip(monkeypatch): ), ) - order = Order(**event | {'payment_method': 'BANK_SLIP'}) + order = Order( + id=str(uuid4()), + payment_method='BANK_SLIP', # type: ignore + **event, + ) invoice = iugu.create_invoice(order, postback_url='http://localhost') + assert invoice['id'] == '16F7AA3D2E0B41E9987B1DC95B957456' assert ( - invoice.pdf + invoice['bank_slip']['bank_slip_url'] == 'https://boletos.iugu.com/v1/public/invoice/16f7aa3d-2e0b-41e9-987b-1dc95b957456-d7a2/bank_slip' ) @@ -71,7 +101,7 @@ def test_payment_token(monkeypatch): monkeypatch.setattr( requests, 'post', - lambda *args, **kwargs: MockResponse(), + lambda *args, **kwargs: MockResponse('tests/samples/iugu_payment_token.json'), ) credit_card = CreditCard( @@ -82,7 +112,7 @@ def test_payment_token(monkeypatch): exp_year='2029', ) token = iugu.payment_token(credit_card) - assert isinstance(token, Token) + assert token['id'] == 'ae5204f4-663b-451f-ad65-7c512badb84e' def test_charge_paid(monkeypatch): @@ -93,10 +123,10 @@ def test_charge_paid(monkeypatch): ) charge = iugu.charge( - invoice_id='970cb579-c396-4e59-a323-ce61ae04f7bc-196c', - token=Token('testing'), + invoice_id='f92efd60-e6a9-45cf-bd8e-6b15a3d5c3ab-173c', + token='testing', ) - assert charge.status == Status.PAID + assert charge['success'] is True def test_charge_declined(monkeypatch): @@ -108,10 +138,10 @@ def test_charge_declined(monkeypatch): charge = iugu.charge( invoice_id='970cb579-c396-4e59-a323-ce61ae04f7bc-196c', - token=Token('testing'), + token='testing', ) - assert charge.status == Status.DECLINED - assert charge.response['status'] == 'unauthorized' + + assert charge['success'] is False def test_get_invoice(monkeypatch): @@ -123,28 +153,3 @@ def test_get_invoice(monkeypatch): invoice = iugu.get_invoice('970cb579-c396-4e59-a323-ce61ae04f7bc-196c') assert isinstance(invoice, dict) - - -event = { - 'id': 'testing', - 'name': 'Sérgio Siqueira', - 'email': 'sergio@somosbeta.com.br', - 'due_date': '2026-11-12', - 'cpf': '07879819908', - 'payment_method': 'PIX', - 'address': { - 'postcode': '82100410', - 'address1': 'Rua Manoel José Pereira', - 'address2': '202', - 'neighborhood': 'Pilarzinho', - 'city': 'Curitiba', - 'state': 'PR', - }, - 'items': [ - { - 'id': '1', - 'name': 'Pen', - 'unit_price': 100, - }, - ], -}