wip payments (iugu)
This commit is contained in:
@@ -82,7 +82,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
|||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.exception(
|
logger.exception(
|
||||||
exc,
|
exc,
|
||||||
keypair={'pk': pk, 'sk': sk},
|
keypair={'id': pk, 'sk': sk},
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
|
|||||||
0
orders-events/app/events/payments/__init__.py
Normal file
0
orders-events/app/events/payments/__init__.py
Normal file
20
orders-events/app/events/payments/append_fee.py
Normal file
20
orders-events/app/events/payments/append_fee.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
from aws_lambda_powertools import Logger
|
||||||
|
from aws_lambda_powertools.utilities.data_classes import (
|
||||||
|
EventBridgeEvent,
|
||||||
|
event_source,
|
||||||
|
)
|
||||||
|
from aws_lambda_powertools.utilities.typing import LambdaContext
|
||||||
|
from layercake.dynamodb import (
|
||||||
|
DynamoDBPersistenceLayer,
|
||||||
|
)
|
||||||
|
|
||||||
|
from boto3clients import dynamodb_client
|
||||||
|
from config import ORDER_TABLE
|
||||||
|
|
||||||
|
logger = Logger(__name__)
|
||||||
|
dyn = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client)
|
||||||
|
|
||||||
|
|
||||||
|
@event_source(data_class=EventBridgeEvent)
|
||||||
|
@logger.inject_lambda_context
|
||||||
|
def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: ...
|
||||||
20
orders-events/app/events/payments/charge_credit_card.py
Normal file
20
orders-events/app/events/payments/charge_credit_card.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
from aws_lambda_powertools import Logger
|
||||||
|
from aws_lambda_powertools.utilities.data_classes import (
|
||||||
|
EventBridgeEvent,
|
||||||
|
event_source,
|
||||||
|
)
|
||||||
|
from aws_lambda_powertools.utilities.typing import LambdaContext
|
||||||
|
from layercake.dynamodb import (
|
||||||
|
DynamoDBPersistenceLayer,
|
||||||
|
)
|
||||||
|
|
||||||
|
from boto3clients import dynamodb_client
|
||||||
|
from config import ORDER_TABLE
|
||||||
|
|
||||||
|
logger = Logger(__name__)
|
||||||
|
dyn = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client)
|
||||||
|
|
||||||
|
|
||||||
|
@event_source(data_class=EventBridgeEvent)
|
||||||
|
@logger.inject_lambda_context
|
||||||
|
def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: ...
|
||||||
30
orders-events/app/events/payments/create_invoice.py
Normal file
30
orders-events/app/events/payments/create_invoice.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
from aws_lambda_powertools import Logger
|
||||||
|
from aws_lambda_powertools.utilities.data_classes import (
|
||||||
|
EventBridgeEvent,
|
||||||
|
event_source,
|
||||||
|
)
|
||||||
|
from aws_lambda_powertools.utilities.typing import LambdaContext
|
||||||
|
from layercake.dynamodb import (
|
||||||
|
DynamoDBPersistenceLayer,
|
||||||
|
)
|
||||||
|
|
||||||
|
from boto3clients import dynamodb_client
|
||||||
|
from config import ORDER_TABLE
|
||||||
|
|
||||||
|
logger = Logger(__name__)
|
||||||
|
dyn = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client)
|
||||||
|
|
||||||
|
|
||||||
|
@event_source(data_class=EventBridgeEvent)
|
||||||
|
@logger.inject_lambda_context
|
||||||
|
def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
||||||
|
new_image = event.detail['new_image']
|
||||||
|
order_id = new_image['id']
|
||||||
|
|
||||||
|
doc = {
|
||||||
|
'id': order_id,
|
||||||
|
'sk': 'IUGU',
|
||||||
|
'invoice_id': '',
|
||||||
|
}
|
||||||
|
|
||||||
|
return True
|
||||||
247
orders-events/app/iugu.py
Normal file
247
orders-events/app/iugu.py
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
"""
|
||||||
|
Notes:
|
||||||
|
-----
|
||||||
|
- `%d` Day of the month as a zero-padded decimal number. Ex: 01, 02, …, 31
|
||||||
|
- `%m` Month as a zero-padded decimal number. Ex: 01, 02, …, 12
|
||||||
|
- `%Y` Year with century as a decimal number. Ex: 0001, 0002, …, 2013, 2014
|
||||||
|
|
||||||
|
- https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes
|
||||||
|
|
||||||
|
Documentation:
|
||||||
|
--------------
|
||||||
|
- https://dev.iugu.com/reference/criar-fatura
|
||||||
|
- https://dev.iugu.com/reference/criar-token
|
||||||
|
- https://dev.iugu.com/reference/cobranca-direta
|
||||||
|
- https://support.iugu.com/hc/pt-br/articles/212456346-Usar-cart%C3%B5es-de-teste-em-modo-de-teste
|
||||||
|
"""
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from enum import Enum
|
||||||
|
from urllib.parse import ParseResult, urlparse
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from aws_lambda_powertools import Logger
|
||||||
|
from pydantic import BaseModel, HttpUrl
|
||||||
|
|
||||||
|
# from data_classes.invoice import Invoice, Pix
|
||||||
|
# from data_classes.order import CreditCard, Order, PaymentMethod, Status
|
||||||
|
|
||||||
|
logger = Logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Status(Enum):
|
||||||
|
PAID = 'PAID'
|
||||||
|
DECLINED = 'DECLINED'
|
||||||
|
|
||||||
|
|
||||||
|
class PaymentMethod(Enum):
|
||||||
|
PIX = 'PIX'
|
||||||
|
BANK_SLIP = 'BANK_SLIP'
|
||||||
|
CREDIT_CARD = 'CREDIT_CARD'
|
||||||
|
|
||||||
|
|
||||||
|
class Order: ...
|
||||||
|
|
||||||
|
|
||||||
|
class CreditCard: ...
|
||||||
|
|
||||||
|
|
||||||
|
class BankSlip(BaseModel):
|
||||||
|
digitable_line: str
|
||||||
|
bank_slip_url: HttpUrl
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Credentials:
|
||||||
|
account_id: str
|
||||||
|
api_token: str
|
||||||
|
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')
|
||||||
|
|
||||||
|
def __init__(self, credentials: Credentials) -> None:
|
||||||
|
self.credentials = credentials
|
||||||
|
|
||||||
|
def url(self, **kwargs) -> str:
|
||||||
|
return self.base_url._replace(
|
||||||
|
query=f'api_token={self.credentials.api_token}', **kwargs
|
||||||
|
).geturl()
|
||||||
|
|
||||||
|
def create_invoice(
|
||||||
|
self,
|
||||||
|
order: Order,
|
||||||
|
postback_url: ParseResult | str,
|
||||||
|
) -> Invoice:
|
||||||
|
"""
|
||||||
|
O que é uma fatura?
|
||||||
|
-------------------
|
||||||
|
A fatura é uma forma de cobrança da iugu que possibilitar efetuar cobranças
|
||||||
|
com os métodos cartão de crédito, boleto e PIX, fora isso nela é possível
|
||||||
|
definir como será realizado o split de pagamentos.
|
||||||
|
|
||||||
|
Por que usar a chamada de fatura?
|
||||||
|
---------------------------------
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
url = self.url(path='/v1/invoices')
|
||||||
|
|
||||||
|
if isinstance(postback_url, str):
|
||||||
|
postback_url = urlparse(postback_url)
|
||||||
|
|
||||||
|
items = [
|
||||||
|
{
|
||||||
|
'description': item.name,
|
||||||
|
'price_cents': int(item.unit_price * 100),
|
||||||
|
'quantity': item.quantity,
|
||||||
|
}
|
||||||
|
for item in order.items
|
||||||
|
]
|
||||||
|
payload = {
|
||||||
|
'order_id': order.id,
|
||||||
|
'external_reference': order.id,
|
||||||
|
'due_date': order.due_date.strftime('%Y-%m-%d'), # type: ignore
|
||||||
|
'items': items,
|
||||||
|
'email': order.email,
|
||||||
|
'payable_with': order.payment_method.lower(),
|
||||||
|
'notification_url': postback_url.geturl(),
|
||||||
|
'payer': {
|
||||||
|
'name': order.name,
|
||||||
|
'email': order.email,
|
||||||
|
'cpf_cnpj': order.cnpj if order.cnpj else order.cpf,
|
||||||
|
'address': {
|
||||||
|
'zip_code': order.address.postcode,
|
||||||
|
'street': order.address.street,
|
||||||
|
'number': order.address.street_number,
|
||||||
|
'district': order.address.neighborhood,
|
||||||
|
'city': order.address.city,
|
||||||
|
'state': order.address.state,
|
||||||
|
'complement': order.address.complement,
|
||||||
|
'country=': 'Brasil',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.post(url, json=payload, timeout=15)
|
||||||
|
response.raise_for_status()
|
||||||
|
except requests.HTTPError as err:
|
||||||
|
logger.exception(err)
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
response = response.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) -> Token:
|
||||||
|
url = self.url(path='/v1/payment_token')
|
||||||
|
payload = {
|
||||||
|
'test': self.credentials.test_mode,
|
||||||
|
'account_id': self.credentials.account_id,
|
||||||
|
'method': 'credit_card',
|
||||||
|
'data': {
|
||||||
|
'number': credit_card.number,
|
||||||
|
'verification_value': credit_card.cvv,
|
||||||
|
'first_name': credit_card.first_name,
|
||||||
|
'last_name': credit_card.last_name,
|
||||||
|
'month': credit_card.exp.strftime('%m'),
|
||||||
|
'year': credit_card.exp.strftime('%Y'),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.post(url, json=payload, timeout=15)
|
||||||
|
response.raise_for_status()
|
||||||
|
except requests.HTTPError as err:
|
||||||
|
logger.exception(err)
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
return Token(response.json()['id'])
|
||||||
|
|
||||||
|
def charge(
|
||||||
|
self,
|
||||||
|
invoice_id: str,
|
||||||
|
token: Token,
|
||||||
|
installments: int = 1,
|
||||||
|
) -> Transaction:
|
||||||
|
"""
|
||||||
|
O que é Cobrança Direta
|
||||||
|
-----------------------
|
||||||
|
Requisição utilizada para realizar uma cobrança simples de forma pontual
|
||||||
|
utilizando os métodos de pagamento cartão de crédito e boleto.
|
||||||
|
|
||||||
|
Por que usar uma cobrança direta?
|
||||||
|
---------------------------------
|
||||||
|
Com ela é possível realizar uma cobrança já inserindo os dados do cliente
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
url = self.url(path='/v1/charge')
|
||||||
|
payload = {
|
||||||
|
'invoice_id': format_id(invoice_id),
|
||||||
|
'token': token,
|
||||||
|
'months': installments,
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.post(url, json=payload, timeout=15)
|
||||||
|
response.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(),
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_invoice(self, invoice_id: str) -> dict:
|
||||||
|
url = self.url(path=f'/v1/invoices/{format_id(invoice_id)}')
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.get(url, timeout=15)
|
||||||
|
response.raise_for_status()
|
||||||
|
except requests.HTTPError as err:
|
||||||
|
logger.exception(err)
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
|
||||||
|
def format_id(invoice_id: str) -> str:
|
||||||
|
return invoice_id.upper().replace('-', '')[:-4]
|
||||||
Reference in New Issue
Block a user