wip iugu
This commit is contained in:
@@ -34,8 +34,12 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
|||||||
|
|
||||||
|
|
||||||
def ping(url: str):
|
def ping(url: str):
|
||||||
|
# https://requests.readthedocs.io/en/latest/user/advanced/#timeouts
|
||||||
|
connect_timeout = 1
|
||||||
|
read_timeout = 3
|
||||||
|
|
||||||
try:
|
try:
|
||||||
r = requests.get(url, timeout=4)
|
r = requests.get(url, timeout=(connect_timeout, read_timeout))
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
except requests.exceptions.RequestException as exc:
|
except requests.exceptions.RequestException as exc:
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ BUCKET_NAME: str = os.getenv('BUCKET_NAME') # type: ignore
|
|||||||
|
|
||||||
EMAIL_SENDER = ('EDUSEG®', 'noreply@eduseg.com.br')
|
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'
|
PAPERFORGE_API = 'https://paperforge.saladeaula.digital'
|
||||||
CERT_REPORTING_URI = 's3://saladeaula.digital/certs/reporting.html'
|
CERT_REPORTING_URI = 's3://saladeaula.digital/certs/reporting.html'
|
||||||
ESIGN_URI = 's3://saladeaula.digital/esigns/11de2510136adbac.pfx'
|
ESIGN_URI = 's3://saladeaula.digital/esigns/11de2510136adbac.pfx'
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ from config import (
|
|||||||
BUCKET_NAME,
|
BUCKET_NAME,
|
||||||
COURSE_TABLE,
|
COURSE_TABLE,
|
||||||
ENROLLMENT_TABLE,
|
ENROLLMENT_TABLE,
|
||||||
|
HTTP_CONNECT_TIMEOUT,
|
||||||
|
HTTP_READ_TIMEOUT,
|
||||||
PAPERFORGE_API,
|
PAPERFORGE_API,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -149,7 +151,7 @@ def _generate_cert(
|
|||||||
else None,
|
else None,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
timeout=(1, 3),
|
timeout=(HTTP_CONNECT_TIMEOUT, HTTP_READ_TIMEOUT),
|
||||||
)
|
)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
|
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ app = APIGatewayHttpResolver(enable_validation=True)
|
|||||||
dyn = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client)
|
dyn = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client)
|
||||||
|
|
||||||
|
|
||||||
@app.post('/')
|
@app.post('/postback/<order_id>')
|
||||||
@tracer.capture_method
|
@tracer.capture_method
|
||||||
def postback():
|
def postback(order_id: str):
|
||||||
return Response(status_code=HTTPStatus.NO_CONTENT)
|
return Response(status_code=HTTPStatus.NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ ENROLLMENT_TABLE: str = os.getenv('ENROLLMENT_TABLE') # type: ignore
|
|||||||
|
|
||||||
IUGU_ACCOUNT_ID: str = 'AF01CF1B3451459F92666F10589278EE'
|
IUGU_ACCOUNT_ID: str = 'AF01CF1B3451459F92666F10589278EE'
|
||||||
IUGU_API_TOKEN: str = os.getenv('IUGU_API_TOKEN') # type: ignore
|
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
|
BUCKET_NAME: str = os.getenv('BUCKET_NAME') # type: ignore
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,14 @@ from layercake.dateutils import now
|
|||||||
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
|
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
|
||||||
|
|
||||||
from boto3clients import dynamodb_client, s3_client
|
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__)
|
logger = Logger(__name__)
|
||||||
order_layer = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client)
|
order_layer = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client)
|
||||||
@@ -50,7 +57,11 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# Send template URI and data to Paperforge API to generate a PDF
|
# 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()
|
r.raise_for_status()
|
||||||
except requests.exceptions.Timeout:
|
except requests.exceptions.Timeout:
|
||||||
logger.info('The request timed out')
|
logger.info('The request timed out')
|
||||||
|
|||||||
@@ -9,10 +9,18 @@ from layercake.dynamodb import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from boto3clients import dynamodb_client
|
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__)
|
logger = Logger(__name__)
|
||||||
dyn = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client)
|
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)
|
@event_source(data_class=EventBridgeEvent)
|
||||||
|
|||||||
@@ -4,27 +4,77 @@ from aws_lambda_powertools.utilities.data_classes import (
|
|||||||
event_source,
|
event_source,
|
||||||
)
|
)
|
||||||
from aws_lambda_powertools.utilities.typing import LambdaContext
|
from aws_lambda_powertools.utilities.typing import LambdaContext
|
||||||
|
from layercake.dateutils import now
|
||||||
from layercake.dynamodb import (
|
from layercake.dynamodb import (
|
||||||
DynamoDBPersistenceLayer,
|
DynamoDBPersistenceLayer,
|
||||||
|
SortKey,
|
||||||
|
TransactKey,
|
||||||
)
|
)
|
||||||
|
|
||||||
from boto3clients import dynamodb_client
|
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__)
|
logger = Logger(__name__)
|
||||||
dyn = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client)
|
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)
|
@event_source(data_class=EventBridgeEvent)
|
||||||
@logger.inject_lambda_context
|
@logger.inject_lambda_context
|
||||||
def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
||||||
new_image = event.detail['new_image']
|
new_image = event.detail['new_image']
|
||||||
|
now_ = now()
|
||||||
order_id = new_image['id']
|
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 = {
|
payment_method = new_image['payment_method']
|
||||||
'id': order_id,
|
is_pix = payment_method == 'PIX'
|
||||||
'sk': 'IUGU',
|
is_bank_slip = payment_method == 'BANK_SLIP'
|
||||||
'invoice_id': '',
|
|
||||||
}
|
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
|
return True
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ Documentation:
|
|||||||
- https://support.iugu.com/hc/pt-br/articles/212456346-Usar-cart%C3%B5es-de-teste-em-modo-de-teste
|
- 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 dataclasses import dataclass
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
@@ -24,7 +25,10 @@ from urllib.parse import ParseResult, urlparse
|
|||||||
import requests
|
import requests
|
||||||
from aws_lambda_powertools import Logger
|
from aws_lambda_powertools import Logger
|
||||||
from layercake.extra_types import CreditCard
|
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__)
|
logger = Logger(__name__)
|
||||||
|
|
||||||
@@ -71,16 +75,6 @@ class Order(BaseModel):
|
|||||||
cnpj: str | None = None
|
cnpj: str | None = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Invoice: ...
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class BankSlip(BaseModel):
|
|
||||||
digitable_line: str
|
|
||||||
bank_slip_url: HttpUrl
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Credentials:
|
class Credentials:
|
||||||
account_id: str
|
account_id: str
|
||||||
@@ -88,17 +82,6 @@ class Credentials:
|
|||||||
test_mode: bool = True
|
test_mode: bool = True
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Token(str):
|
|
||||||
id: str
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Transaction:
|
|
||||||
status: Status
|
|
||||||
response: dict
|
|
||||||
|
|
||||||
|
|
||||||
class Iugu:
|
class Iugu:
|
||||||
base_url: ParseResult = urlparse('https://api.iugu.com')
|
base_url: ParseResult = urlparse('https://api.iugu.com')
|
||||||
|
|
||||||
@@ -114,7 +97,7 @@ class Iugu:
|
|||||||
self,
|
self,
|
||||||
order: Order,
|
order: Order,
|
||||||
postback_url: ParseResult | str,
|
postback_url: ParseResult | str,
|
||||||
) -> Invoice:
|
) -> dict:
|
||||||
"""
|
"""
|
||||||
O que é uma fatura?
|
O que é uma fatura?
|
||||||
-------------------
|
-------------------
|
||||||
@@ -127,6 +110,8 @@ class Iugu:
|
|||||||
No response dessa chamada é retornado uma url na propriedade secure_url,
|
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
|
onde o cliente final pode acessar e efetuar o pagamento em um checkout da
|
||||||
IUGU.
|
IUGU.
|
||||||
|
|
||||||
|
- https://dev.iugu.com/reference/criar-fatura
|
||||||
"""
|
"""
|
||||||
|
|
||||||
url = self.url(path='/v1/invoices')
|
url = self.url(path='/v1/invoices')
|
||||||
@@ -168,33 +153,25 @@ class Iugu:
|
|||||||
}
|
}
|
||||||
|
|
||||||
try:
|
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()
|
r.raise_for_status()
|
||||||
except requests.HTTPError as err:
|
except requests.HTTPError as err:
|
||||||
logger.exception(err)
|
logger.exception(err)
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
return r.json()
|
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(
|
def payment_token(self, credit_card: CreditCard) -> dict:
|
||||||
# id=response['secure_id'],
|
"""When creating a invoice, it can't make the charge immediately the invoice.
|
||||||
# pdf=bank_slip.bank_slip_url
|
It's necessary make have a token (payment token) to charge.
|
||||||
# if bank_slip
|
|
||||||
# else '%s.pdf' % response['secure_url'],
|
Payment token doesn't depends an invoice, just a credit card to charge later.
|
||||||
# pix=pix,
|
|
||||||
# )
|
- https://dev.iugu.com/reference/criar-token
|
||||||
|
"""
|
||||||
|
|
||||||
def payment_token(self, credit_card: CreditCard) -> Token:
|
|
||||||
url = self.url(path='/v1/payment_token')
|
url = self.url(path='/v1/payment_token')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -213,21 +190,21 @@ class Iugu:
|
|||||||
'year': credit_card.exp_year,
|
'year': credit_card.exp_year,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
timeout=15,
|
timeout=(HTTP_CONNECT_TIMEOUT, HTTP_READ_TIMEOUT),
|
||||||
)
|
)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
except requests.HTTPError as err:
|
except requests.HTTPError as err:
|
||||||
logger.exception(err)
|
logger.exception(err)
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
return Token(r.json()['id'])
|
return r.json()
|
||||||
|
|
||||||
def charge(
|
def charge(
|
||||||
self,
|
self,
|
||||||
invoice_id: str,
|
invoice_id: str,
|
||||||
token: Token,
|
token: str,
|
||||||
installments: int = 1,
|
installments: int = 1,
|
||||||
) -> Transaction:
|
) -> dict:
|
||||||
"""
|
"""
|
||||||
O que é Cobrança Direta
|
O que é Cobrança Direta
|
||||||
-----------------------
|
-----------------------
|
||||||
@@ -240,6 +217,8 @@ class Iugu:
|
|||||||
e o token gerado para o cartão de crédito (iugu js).
|
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
|
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.
|
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')
|
url = self.url(path='/v1/charge')
|
||||||
@@ -250,24 +229,21 @@ class Iugu:
|
|||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = requests.post(url, json=payload, timeout=15)
|
r = requests.post(
|
||||||
response.raise_for_status()
|
url, json=payload, timeout=(HTTP_CONNECT_TIMEOUT, HTTP_READ_TIMEOUT)
|
||||||
|
)
|
||||||
|
r.raise_for_status()
|
||||||
except requests.HTTPError as err:
|
except requests.HTTPError as err:
|
||||||
logger.exception(err)
|
logger.exception(err)
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
success = response.json()['success']
|
return r.json()
|
||||||
|
|
||||||
return Transaction(
|
|
||||||
status=Status.PAID if success else Status.DECLINED,
|
|
||||||
response=response.json(),
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_invoice(self, invoice_id: str) -> dict:
|
def get_invoice(self, invoice_id: str) -> dict:
|
||||||
url = self.url(path=f'/v1/invoices/{format_id(invoice_id)}')
|
url = self.url(path=f'/v1/invoices/{format_id(invoice_id)}')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
r = requests.get(url, timeout=15)
|
r = requests.get(url, timeout=(HTTP_CONNECT_TIMEOUT, HTTP_READ_TIMEOUT))
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
except requests.HTTPError as err:
|
except requests.HTTPError as err:
|
||||||
logger.exception(err)
|
logger.exception(err)
|
||||||
|
|||||||
@@ -78,6 +78,47 @@ Resources:
|
|||||||
Method: POST
|
Method: POST
|
||||||
ApiId: !Ref HttpApi
|
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:
|
EventBillingAppendEnrollmentFunction:
|
||||||
Type: AWS::Serverless::Function
|
Type: AWS::Serverless::Function
|
||||||
Properties:
|
Properties:
|
||||||
@@ -192,6 +233,7 @@ Resources:
|
|||||||
old_image:
|
old_image:
|
||||||
status: [PENDING]
|
status: [PENDING]
|
||||||
|
|
||||||
|
# DEPRECATED
|
||||||
EventAppendOrgIdFunction:
|
EventAppendOrgIdFunction:
|
||||||
Type: AWS::Serverless::Function
|
Type: AWS::Serverless::Function
|
||||||
Properties:
|
Properties:
|
||||||
@@ -219,6 +261,7 @@ Resources:
|
|||||||
tenant_id:
|
tenant_id:
|
||||||
- exists: false
|
- exists: false
|
||||||
|
|
||||||
|
# DEPRECATED
|
||||||
EventAppendUserIdFunction:
|
EventAppendUserIdFunction:
|
||||||
Type: AWS::Serverless::Function
|
Type: AWS::Serverless::Function
|
||||||
Properties:
|
Properties:
|
||||||
|
|||||||
@@ -1,21 +1,91 @@
|
|||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
import requests
|
||||||
from aws_lambda_powertools.utilities.typing.lambda_context import LambdaContext
|
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
|
import events.payments.create_invoice as app
|
||||||
|
|
||||||
|
from ...test_iugu import MockResponse
|
||||||
|
|
||||||
def test_create_invoice(
|
order_id = '121c1140-779d-4664-8d99-4a006a22f547'
|
||||||
dynamodb_seeds,
|
|
||||||
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
|
|
||||||
lambda_context: LambdaContext,
|
def _event(payment_method: str) -> dict:
|
||||||
):
|
return {
|
||||||
event = {
|
|
||||||
'detail': {
|
'detail': {
|
||||||
'new_image': {
|
'new_image': {
|
||||||
'id': '',
|
'id': order_id,
|
||||||
'sk': '0'
|
'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'
|
||||||
|
)
|
||||||
|
|||||||
@@ -134,16 +134,7 @@
|
|||||||
"bank_slip_error_message": null,
|
"bank_slip_error_message": null,
|
||||||
"recipient_cpf_cnpj": "15111975000164"
|
"recipient_cpf_cnpj": "15111975000164"
|
||||||
},
|
},
|
||||||
"pix": {
|
"pix": null,
|
||||||
"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
|
|
||||||
},
|
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"id": "0F30A8B1ED244325BBC6C8A1AE34AE18",
|
"id": "0F30A8B1ED244325BBC6C8A1AE34AE18",
|
||||||
|
|||||||
13
orders-events/tests/samples/iugu_payment_token.json
Normal file
13
orders-events/tests/samples/iugu_payment_token.json
Normal file
@@ -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
|
||||||
|
}
|
||||||
@@ -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": "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"}
|
{"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
|
// User data
|
||||||
{"id": "5OxmMjL-ujoR5IMGegQz", "sk": "0", "name": "Sérgio R Siqueira"}
|
{"id": "5OxmMjL-ujoR5IMGegQz", "sk": "0", "name": "Sérgio R Siqueira"}
|
||||||
{"id": "cnpj", "sk": "15608435000190", "user_id": "cJtK9SsnJhKPyxESe7g3DG"}
|
{"id": "cnpj", "sk": "15608435000190", "user_id": "cJtK9SsnJhKPyxESe7g3DG"}
|
||||||
{"id": "cpf", "sk": "07879819908", "user_id": "5OxmMjL-ujoR5IMGegQz"}
|
{"id": "cpf", "sk": "07879819908", "user_id": "5OxmMjL-ujoR5IMGegQz"}
|
||||||
{"id": "email", "sk": "sergio@somosbeta.com.br", "user_id": "5OxmMjL-ujoR5IMGegQz"}
|
{"id": "email", "sk": "sergio@somosbeta.com.br", "user_id": "5OxmMjL-ujoR5IMGegQz"}
|
||||||
|
|
||||||
|
// @DEPRECATED
|
||||||
// Slots
|
// Slots
|
||||||
{"id": "vacancies#cJtK9SsnJhKPyxESe7g3DG", "sk": "9omWNKymwU5U4aeun6mWzZ#1"}
|
{"id": "vacancies#cJtK9SsnJhKPyxESe7g3DG", "sk": "9omWNKymwU5U4aeun6mWzZ#1"}
|
||||||
{"id": "vacancies#cJtK9SsnJhKPyxESe7g3DG", "sk": "9omWNKymwU5U4aeun6mWzZ#2"}
|
{"id": "vacancies#cJtK9SsnJhKPyxESe7g3DG", "sk": "9omWNKymwU5U4aeun6mWzZ#2"}
|
||||||
|
|||||||
@@ -1,19 +1,42 @@
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from layercake.extra_types import CreditCard
|
from layercake.extra_types import CreditCard
|
||||||
|
|
||||||
from iugu import Credentials, Iugu, Order, Status, Token
|
from iugu import Credentials, Iugu, Order
|
||||||
|
|
||||||
iugu = Iugu(
|
iugu = Iugu(
|
||||||
Credentials(
|
Credentials(
|
||||||
'AF01CF1B3451459F92666F10589278EE',
|
'AF01CF1B3451459F92666F10589278EE',
|
||||||
os.getenv('IUGU_API_TOKEN'),
|
os.getenv('IUGU_API_TOKEN'), # type: ignore
|
||||||
test_mode=True,
|
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:
|
class MockResponse:
|
||||||
def __init__(self, jsonfile: str | None = None) -> None:
|
def __init__(self, jsonfile: str | None = None) -> None:
|
||||||
@@ -31,22 +54,24 @@ class MockResponse:
|
|||||||
|
|
||||||
|
|
||||||
def test_create_invoice_pix(monkeypatch):
|
def test_create_invoice_pix(monkeypatch):
|
||||||
# monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
# requests,
|
requests,
|
||||||
# 'post',
|
'post',
|
||||||
# lambda *args, **kwargs: MockResponse('tests/samples/iugu_invoice_pix.json'),
|
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')
|
invoice = iugu.create_invoice(order, postback_url='http://localhost')
|
||||||
|
|
||||||
print(invoice)
|
assert invoice['id'] == '970CB579C3964E59A323CE61AE04F7BC'
|
||||||
|
assert (
|
||||||
# assert invoice.id == '970cb579-c396-4e59-a323-ce61ae04f7bc-196c'
|
invoice['pix']['qrcode_text']
|
||||||
# assert (
|
== 'http://faturas.iugu.com/iugu_pix/970cb579-c396-4e59-a323-ce61ae04f7bc-196c/test/pay'
|
||||||
# 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):
|
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')
|
invoice = iugu.create_invoice(order, postback_url='http://localhost')
|
||||||
|
|
||||||
|
assert invoice['id'] == '16F7AA3D2E0B41E9987B1DC95B957456'
|
||||||
assert (
|
assert (
|
||||||
invoice.pdf
|
invoice['bank_slip']['bank_slip_url']
|
||||||
== 'https://boletos.iugu.com/v1/public/invoice/16f7aa3d-2e0b-41e9-987b-1dc95b957456-d7a2/bank_slip'
|
== '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(
|
monkeypatch.setattr(
|
||||||
requests,
|
requests,
|
||||||
'post',
|
'post',
|
||||||
lambda *args, **kwargs: MockResponse(),
|
lambda *args, **kwargs: MockResponse('tests/samples/iugu_payment_token.json'),
|
||||||
)
|
)
|
||||||
|
|
||||||
credit_card = CreditCard(
|
credit_card = CreditCard(
|
||||||
@@ -82,7 +112,7 @@ def test_payment_token(monkeypatch):
|
|||||||
exp_year='2029',
|
exp_year='2029',
|
||||||
)
|
)
|
||||||
token = iugu.payment_token(credit_card)
|
token = iugu.payment_token(credit_card)
|
||||||
assert isinstance(token, Token)
|
assert token['id'] == 'ae5204f4-663b-451f-ad65-7c512badb84e'
|
||||||
|
|
||||||
|
|
||||||
def test_charge_paid(monkeypatch):
|
def test_charge_paid(monkeypatch):
|
||||||
@@ -93,10 +123,10 @@ def test_charge_paid(monkeypatch):
|
|||||||
)
|
)
|
||||||
|
|
||||||
charge = iugu.charge(
|
charge = iugu.charge(
|
||||||
invoice_id='970cb579-c396-4e59-a323-ce61ae04f7bc-196c',
|
invoice_id='f92efd60-e6a9-45cf-bd8e-6b15a3d5c3ab-173c',
|
||||||
token=Token('testing'),
|
token='testing',
|
||||||
)
|
)
|
||||||
assert charge.status == Status.PAID
|
assert charge['success'] is True
|
||||||
|
|
||||||
|
|
||||||
def test_charge_declined(monkeypatch):
|
def test_charge_declined(monkeypatch):
|
||||||
@@ -108,10 +138,10 @@ def test_charge_declined(monkeypatch):
|
|||||||
|
|
||||||
charge = iugu.charge(
|
charge = iugu.charge(
|
||||||
invoice_id='970cb579-c396-4e59-a323-ce61ae04f7bc-196c',
|
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):
|
def test_get_invoice(monkeypatch):
|
||||||
@@ -123,28 +153,3 @@ def test_get_invoice(monkeypatch):
|
|||||||
|
|
||||||
invoice = iugu.get_invoice('970cb579-c396-4e59-a323-ce61ae04f7bc-196c')
|
invoice = iugu.get_invoice('970cb579-c396-4e59-a323-ce61ae04f7bc-196c')
|
||||||
assert isinstance(invoice, dict)
|
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,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user