# -*- coding: utf-8 -*-
from pagseguro.api.base_payment import BasePaymentRequest
from pagseguro.api.v2 import settings
from pagseguro.api.v2.schemas import item_schema, client_schema, shipping_schema
from pagseguro.exceptions import PagSeguroApiException, \
PagSeguroPaymentException
from pagseguro.validators import Email
from voluptuous import Schema, Object, Required, All, Length, Range, Url
from xml.etree import ElementTree
import dateutil.parser
import logging
import requests
logger = logging.getLogger(__name__)
[docs]class Payment(BasePaymentRequest):
''' Classe que implementa a requisição à API do PagSeguro versão 2
.. todo:: Adicionar suporte à metadata no checkout
Args:
email (str): (obrigatório) O email da sua conta no PagSeguro
token (str): (obrigatório) O seu token de acesso ao PagSeguro
receiver_email (str): (opcional)
currency (str): (opcional) A moeda a ser utilizada. Nesta versão apenas o valor BRL é aceito
e ele é definido por padrão. Não se preocupe com este parâmetro.
reference (str): (opcional) Um identificador para a transação. Você irá utilizar este valor
posteriormente para identificar as transações.
extra_amount (float): (opcional) Um valor extra que deve ser adicionado ou subtraído ao valor
total do pagamento.
redirect_url (str): (opcional) URL para a qual o comprador será redirecionado após o final
do fluxo de pagamento. Tamanho máximo de 255 caracteres.
notification_url (str): (opcional) URL para a qual o PagSeguro enviará os códigos de
notificação relacionados ao pagamento. Toda vez que houver uma mudança no status da
transação e que demandar sua atenção, uma nova notificação será enviada para este endereço.
max_uses (int): Determina o número máximo de vezes que o código de pagamento criado pela chamada
à API de Pagamentos poderá ser usado. Este parâmetro pode ser usado como um controle de segurança.
max_age (int): (opcional) Determina o prazo (em segundos) durante o qual o código de pagamento
criado pela chamada à API de Pagamentos poderá ser usado. Este parâmetro pode ser usado
como um controle de segurança
'''
def __init__(self,
email,
token,
receiver_email=None,
currency='BRL',
reference=None,
extra_amount=None,
redirect_url=None,
notification_url=None,
max_uses=None,
max_age=None, **kwargs):
self.email = email
self.token = token
self.receiver_email = receiver_email
self.currency = currency
self.reference = reference
self.extra_amount = extra_amount
self.redirect_url = redirect_url
self.notification_url = notification_url
self.max_uses = max_uses
self.max_age = max_age
self.items = []
self.client = {}
self.shipping = {}
self.response = None
[docs] def api_version(self):
return u'2.0'
[docs] def add_item(self, item_id, description, amount, quantity, shipping_cost=None, weight=None):
item = {}
item['item_id'] = item_id
item['description'] = description
item['amount'] = float(amount)
item['quantity'] = quantity
if shipping_cost:
item['shipping_cost'] = shipping_cost
if weight:
item['weight'] = weight
# Validar dados
item_schema(item)
self.items.append(item)
[docs] def set_client(self, *args, **kwargs):
''' Se você possui informações cadastradas sobre o comprador você pode utilizar
este método para enviar estas informações para o PagSeguro. É uma boa prática pois
evita que seu cliente tenha que preencher estas informações novamente na página
do PagSeguro.
Args:
name (str): (opcional) Nome do cliente
email (str): (opcional) Email do cliente
phone_area_code (str): (opcional) Código de área do telefone do cliente. Um número com 2 digitos.
phone_number (str): (opcional) O número de telefone do cliente.
cpf: (str): (opcional) Número do cpf do comprador
born_date: (date): Data de nascimento no formato dd/MM/yyyy
Exemplo:
>>> from pagseguro import Payment
>>> from pagseguro import local_settings
>>> payment = Payment(email=local_settings.PAGSEGURO_ACCOUNT_EMAIL, token=local_settings.PAGSEGURO_TOKEN)
>>> payment.set_client(name=u'Adam Yauch', phone_area_code=11)
'''
self.client = {}
for arg, value in kwargs.iteritems():
self.client[arg] = value
client_schema(self.client)
[docs] def set_shipping(self, *args, **kwargs):
''' Define os atributos do frete
Args:
type (int): (opcional) Tipo de frete. Os valores válidos são: 1 para 'Encomenda normal (PAC).',
2 para 'SEDEX' e 3 para 'Tipo de frete não especificado.'
cost (float): (opcional) Valor total do frete. Deve ser maior que 0.00 e menor ou igual a 9999999.00.
street (str): (opcional) Nome da rua do endereço de envio do produto
address_number: (opcional) Número do endereço de envio do produto.
complement: (opcional) Complemento (bloco, apartamento, etc.) do endereço de envio do produto.
district: (opcional) Bairro do endereço de envio do produto.
postal_code: (opcional) CEP do endereço de envio do produto.
city: (opcional) Cidade do endereço de envio do produto.
state: (opcional) Estado do endereço de envio do produto.
country: (opcional) País do endereço de envio do produto. Apenas o valor 'BRA' é aceito.
'''
self.shipping = {}
for arg, value in kwargs.iteritems():
self.shipping[arg] = value
shipping_schema(self.shipping)
[docs] def request(self):
'''
Faz a requisição de pagamento ao servidor do PagSeguro.
'''
payment_v2_schema(self)
params = self._build_params()
req = requests.post(
settings.PAGSEGURO_API_URL,
params=params,
headers={
'Content-Type':
'application/x-www-form-urlencoded; charset=ISO-8859-1'
}
)
if req.status_code == 200:
self.response = self._process_response_xml(req.text)
else:
raise PagSeguroApiException(
u'Erro ao fazer request para a API:' +
' HTTP Status=%s - Response: %s' % (req.status_code, req.text))
return
def _build_params(self):
''' método que constrói o dicionario com os parametros que serão usados
na requisição HTTP Post ao PagSeguro
Returns:
Um dicionário com os parametros definidos no objeto Payment.
'''
params = {}
params['email'] = self.email
params['token'] = self.token
params['currency'] = self.currency
# Atributos opcionais
if self.receiver_email:
params['receiver_email'] = self.receiver_email
if self.reference:
params['reference'] = self.reference
if self.extra_amount:
params['extra_amount'] = self.extra_amount
if self.redirect_url:
params['redirect_url'] = self.redirect_url
if self.notification_url:
params['notification_url'] = self.notification_url
if self.max_uses:
params['max_uses'] = self.max_uses
if self.max_age:
params['max_age'] = self.max_age
#TODO: Incluir metadata aqui
# Itens
for index, item in enumerate(self.items, start=1):
params['itemId%d' % index] = item['item_id']
params['itemDescription%d' % index] = item['description']
params['itemAmount%d' % index] = '%.2f' % item['amount']
params['itemQuantity%s' % index] = item['quantity']
if item.get('shipping_cost'):
params['itemShippingCost%d' % index] = item['shipping_cost']
if item.get('weight'):
params['itemWeight%d' % index] = item['weight']
# Sender
if self.client.get('email'):
params['senderEmail'] = self.client.get('email')
if self.client.get('name'):
params['senderName'] = self.client.get('name')
if self.client.get('phone_area_code'):
params['senderAreaCode'] = self.client.get('phone_area_code')
if self.client.get('phone_number'):
params['senderPhone'] = self.client.get('phone_number')
if self.client.get('cpf'):
params['senderCPF'] = self.client.get('cpf')
if self.client.get('sender_born_date'):
params['senderBornDate'] = self.client.get('sender_born_date')
# Shipping
if self.shipping.get('type'):
params['shippingType'] = self.shipping.get('type')
if self.shipping.get('cost'):
params['shippingCost'] = '%.2f' % self.shipping.get('cost')
if self.shipping.get('country'):
params['shippingAddressCountry'] = self.shipping.get('country')
if self.shipping.get('state'):
params['shippingAddressState'] = self.shipping.get('state')
if self.shipping.get('city'):
params['shippingAddressCity'] = self.shipping.get('city')
if self.shipping.get('postal_code'):
params['shippingAddressPostalCode'] = self.shipping.get('postal_code')
if self.shipping.get('district'):
params['shippingAddressDistrict'] = self.shipping.get('district')
if self.shipping.get('street'):
params['shippingAddressStreet'] = self.shipping.get('street')
if self.shipping.get('number'):
params['shippingAddressNumber'] = self.shipping.get('number')
if self.shipping.get('complement'):
params['shippingAddressComplement'] = self.shipping.get('complement')
return params
def _process_response_xml(self, response_xml):
'''
Processa o xml de resposta e caso não existam erros retorna um
dicionario com o codigo e data.
:return: dictionary
'''
result = {}
xml = ElementTree.fromstring(response_xml)
if xml.tag == 'errors':
logger.error(
u'Erro no pedido de pagamento ao PagSeguro.' +
' O xml de resposta foi: %s' % response_xml)
errors_message = u'Ocorreu algum problema com os dados do pagamento: '
for error in xml.findall('error'):
error_code = error.find('code').text
error_message = error.find('message').text
errors_message += u'\n (code=%s) %s' % (error_code,
error_message)
raise PagSeguroPaymentException(errors_message)
if xml.tag == 'checkout':
result['code'] = xml.find('code').text
try:
xml_date = xml.find('date').text
result['date'] = dateutil.parser.parse(xml_date)
except:
logger.exception(u'O campo date não foi encontrado ou é invalido')
result['date'] = None
else:
raise PagSeguroPaymentException(
u'Erro ao processar resposta do pagamento: tag "checkout" nao encontrada no xml de resposta')
return result
[docs] def payment_url(self):
'''
Retorna a url para onde o cliente deve ser redirecionado para
continuar o fluxo de pagamento.
:return: str, URL de pagamento
'''
if self.response:
return u'%s?code=%s' % (settings.PAGSEGURO_API_URL, self.response['code'])
else:
return None
#: Schema utilizado para validar os atributos da classe Payment da versão 2 da API
#: ..todo:: Verificar porque a validação de URLs não está funcionando
payment_v2_schema = Schema(Object({
Required('email'): All(Email(), Length(max=60)),
'token': All(str, Length(min=32, max=32)),
'receiver_email': All(Email(), Length(max=60)),
'currency': 'BRL',
'reference': All(str, Length(max=200)),
'extra_amount': All(float, Range(min=-9999999.01, max=9999999)),
'redirect_url': Url(),
'notification_url':Url(),
'max_uses': All(int, Range(min=1, max=999)),
'max_age': All(int, Range(min=30,max=999999999)),
'client': client_schema,
'items': [ item_schema, ],
'shipping' : shipping_schema,
'response': str,
} ,cls=Payment)
)