261 lines
7.7 KiB
Python
261 lines
7.7 KiB
Python
"""
|
|
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
|
|
"""
|
|
|
|
import os
|
|
from dataclasses import dataclass
|
|
from datetime import datetime
|
|
from decimal import Decimal
|
|
from enum import Enum
|
|
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
|
|
|
|
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__)
|
|
|
|
|
|
class Status(str, Enum):
|
|
PAID = 'PAID'
|
|
DECLINED = 'DECLINED'
|
|
|
|
|
|
class PaymentMethod(str, Enum):
|
|
PIX = 'PIX'
|
|
BANK_SLIP = 'BANK_SLIP'
|
|
CREDIT_CARD = 'CREDIT_CARD'
|
|
|
|
|
|
@dataclass
|
|
class Address(BaseModel):
|
|
postcode: str
|
|
neighborhood: str
|
|
city: str
|
|
state: str
|
|
address1: str
|
|
address2: str | None = None
|
|
|
|
|
|
class Item(BaseModel):
|
|
id: str
|
|
name: str
|
|
quantity: int = 1
|
|
unit_price: Decimal
|
|
|
|
|
|
class Order(BaseModel):
|
|
model_config = ConfigDict(use_enum_values=True)
|
|
|
|
id: str
|
|
email: str
|
|
name: str
|
|
due_date: datetime
|
|
address: Address
|
|
items: tuple[Item, ...]
|
|
payment_method: PaymentMethod
|
|
discount: Decimal = Decimal('0')
|
|
cpf: str | None = None
|
|
cnpj: str | None = None
|
|
|
|
|
|
@dataclass
|
|
class Credentials:
|
|
account_id: str
|
|
api_token: str
|
|
test_mode: bool = True
|
|
|
|
|
|
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,
|
|
) -> dict:
|
|
"""
|
|
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.
|
|
|
|
- https://dev.iugu.com/reference/criar-fatura
|
|
"""
|
|
|
|
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'),
|
|
'items': items,
|
|
'email': order.email,
|
|
'payable_with': order.payment_method.lower(),
|
|
'notification_url': postback_url.geturl(),
|
|
'discount_cents': int(order.discount * -100),
|
|
'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.address1,
|
|
'number': '',
|
|
'district': order.address.neighborhood,
|
|
'city': order.address.city,
|
|
'state': order.address.state,
|
|
'complement': order.address.address2,
|
|
'country=': 'Brasil',
|
|
},
|
|
},
|
|
}
|
|
|
|
try:
|
|
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()
|
|
|
|
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
|
|
"""
|
|
|
|
url = self.url(path='/v1/payment_token')
|
|
|
|
try:
|
|
r = requests.post(
|
|
url,
|
|
json={
|
|
'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_month,
|
|
'year': credit_card.exp_year,
|
|
},
|
|
},
|
|
timeout=(HTTP_CONNECT_TIMEOUT, HTTP_READ_TIMEOUT),
|
|
)
|
|
r.raise_for_status()
|
|
except requests.HTTPError as err:
|
|
logger.exception(err)
|
|
raise
|
|
else:
|
|
return r.json()
|
|
|
|
def charge(
|
|
self,
|
|
invoice_id: str,
|
|
token: str,
|
|
installments: int = 1,
|
|
) -> dict:
|
|
"""
|
|
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.
|
|
|
|
- https://dev.iugu.com/reference/cobranca-direta
|
|
"""
|
|
|
|
url = self.url(path='/v1/charge')
|
|
payload = {
|
|
'invoice_id': format_id(invoice_id),
|
|
'token': token,
|
|
'months': installments,
|
|
}
|
|
|
|
try:
|
|
r = requests.post(
|
|
url, json=payload, timeout=(HTTP_CONNECT_TIMEOUT, HTTP_READ_TIMEOUT)
|
|
)
|
|
r.raise_for_status()
|
|
except requests.HTTPError as err:
|
|
logger.info('Response', err.response)
|
|
logger.exception(err)
|
|
raise
|
|
else:
|
|
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=(HTTP_CONNECT_TIMEOUT, HTTP_READ_TIMEOUT))
|
|
r.raise_for_status()
|
|
except requests.HTTPError as err:
|
|
logger.exception(err)
|
|
raise
|
|
else:
|
|
return r.json()
|
|
|
|
|
|
def format_id(invoice_id: str) -> str:
|
|
return invoice_id.upper().replace('-', '')[:-4]
|