Source code for braspag.core

# -*- encoding: utf-8 -*-

from __future__ import absolute_import

import uuid
import httplib
import logging

import jinja2

from .utils import spaceless, is_valid_guid
from xml.dom import minidom
from xml.etree import ElementTree
from decimal import Decimal, InvalidOperation


class BraspagRequest(object):
    """Implements Braspag Pagador API (manual version 1.9).

Boleto generation is not yet implemented.

    """

    _PAYMENT_METHODS = {
        'Cielo': {
            'Visa Electron': 123,
            'Visa': 500,
            'MasterCard': 501,
            'Amex': 502,
            'Diners': 503,
            'Elo': 504,
        },
        'Banorte': {
            'Visa': 505,
            'MasterCard': 506,
            'Diners': 507,
            'Amex': 508,
        },
        'Redecard': {
            'Visa': 509,
            'MasterCard': 510,
            'Diners':511,
        },
        'PagosOnLine': {
            'Visa': 512,
            'MasterCard': 513,
            'Amex': 514,
            'Diners': 515,
        },
        'Payvision': {
            'Visa': 516,
            'MasterCard': 517,
            'Diners': 518,
            'Amex': 519,
        },
        'Banorte Cargos Automaticos': {
            'Visa': 520,
            'MasterCard': 521,
            'Diners': 522,
        },
        'Amex': {
            '2P': 523,
        },
        'SITEF': {
            'Visa': 524,
            'MasterCard': 525,
            'Amex': 526,
            'Diners': 527,
            'HiperCard': 528,
            'Leader': 529,
            'Aura': 530,
            'Santander Visa': 531,
            'Santander MasterCard': 532,
        },
        'Simulated': {
            'USD': 995,
            'EUR': 996,
            'BRL': 997,
        }
    }

    def __init__(self, merchant_id=None, homologation=False):
        if homologation:
            self.url = 'homologacao.pagador.com.br'
        else:
            self.url = 'www.pagador.com.br'

        self.merchant_id = merchant_id

        self.jinja_env = jinja2.Environment(
            autoescape=True,
            loader=jinja2.PackageLoader('braspag'),
        )

    @staticmethod
    def _webservice_request(xml, url):
        WSDL = '/webservice/pagadorTransaction.asmx?WSDL'

        if isinstance(xml, unicode):
            xml = xml.encode('utf-8')

        http = httplib.HTTPSConnection(url)
        http.request("POST", WSDL, body=xml, headers = {
            "Host": "localhost",
            "Content-Type": "text/xml; charset=UTF-8",
        })
        return BraspagResponse(http.getresponse())

    def _request(self, xml_request):
        return BraspagRequest._webservice_request(spaceless(xml_request),
                                                 self.url)

[docs] def authorize(self, **kwargs): """All arguments supplied to this method must be keyword arguments. :arg order_id: Order id. It will be used to indentify the order later in Braspag. :arg customer_id: Must be user's CPF/CNPJ. :arg customer_name: User's full name. :arg customer_email: User's email address. :arg amount: Amount to charge. :arg card_holder: Name printed on card. :arg card_number: Card number. :arg card_security_code: Card security code. :arg card_exp_date: Card expiration date. :arg save_card: Flag that tell to Braspag to store card number. If set to True Reponse will return a card token. *Default: False*. :arg card_token: Card token returned by Braspag. When used it should replace *card_holder*, *card_exp_date*, *card_number* and *card_security_code*. :arg number_of_payments: Number of payments that the amount will be devided (number of months). *Default: 1*. :arg currency: Currency of the given amount. *Default: BRL*. :arg country: User's country. *Default: BRA*. :arg transaction_type: An integer representing one of the :ref:`transaction_types`. *Default: 2*. :arg payment_plan: An integer representing how multiple payments should be handled. *Default: 0*. See :ref:`payment_plans`. :arg payment_method: Integer representing one of the available :ref:`payment_methods`. :returns: :class:`~braspag.BraspagResponse` """ assert any((kwargs.get('card_number'), kwargs.get('card_token'))),\ 'card_number ou card_token devem ser fornecidos' if kwargs.get('card_number'): card_keys = ( 'card_holder', 'card_security_code', 'card_exp_date', 'card_number', ) assert all(kwargs.has_key(key) for key in card_keys), \ (u'Transações com Cartão de Crédito exigem os ' u'parametros: {0}'.format(' ,'.join(card_keys))) if not kwargs.get('number_of_payments'): kwargs['number_of_payments'] = 1 try: number_of_payments = int(kwargs.get('number_of_payments')) except ValueError: raise BraspagException('Number of payments must be int.') if not kwargs.get('payment_plan'): if number_of_payments > 1: # 2 = parcelado pelo emissor do cartão kwargs['payment_plan'] = 2 else: # 0 = a vista kwargs['payment_plan'] = 0 if not kwargs.get('currency'): kwargs['currency'] = 'BRL' if not kwargs.get('country'): kwargs['country'] = 'BRA' if not kwargs.get('transaction_type'): # 2 = captura automatica kwargs['transaction_type'] = 2 if kwargs.get('save_card', False): kwargs['save_card'] = 'true' xml_request = self._render_template('authorize.xml', kwargs) return self._request(xml_request)
def _base_transaction(self, transaction_id, amount, type=None): assert type in ('Refund', 'Void', 'Capture') assert is_valid_guid(transaction_id), 'Transaction ID invalido' data_dict = { 'amount': amount, 'type': type, 'transaction_id': transaction_id, } xml_request = self._render_template('base.xml', data_dict) return self._request(xml_request) def void(self, transaction_id, amount=0): """Void the given amount for the given transaction_id. This method should be used to return funds to customers for transactions that happened within less than 23h and 59 minutes ago. For other transactions use :meth:`~braspag.BraspagRequest.refund`. If the amount is 0 (zero) the full transaction will be voided. :returns: :class:`~braspag.BraspagResponse` """ return self._base_transaction(transaction_id, amount, 'Void') def refund(self, transaction_id, amount=0): """Refund the given amount for the given transaction_id. This method should be used to return funds to customers for transactions that happened at least 24 hours ago. For transactions that happended within 24 hours use :meth:`~braspag.BraspagRequest.void`. If the amount is 0 (zero) the full transaction will be refunded. :returns: :class:`~braspag.BraspagResponse` """ return self._base_transaction(transaction_id, amount, 'Refund') def capture(self, transaction_id, amount=0): """Capture the given `amount` from the given transaction_id. This method should only be called after pre-authorizing the transaction by calling :meth:`~braspag.BraspagRequest.authorize` with `transaction_types` 1 or 3. :returns: :class:`~braspag.BraspagResponse` """ return self._base_transaction(transaction_id, amount, 'Capture') def _render_template(self, template_name, data_dict): if self.merchant_id: data_dict['merchant_id'] = self.merchant_id if not data_dict.has_key('request_id'): data_dict['request_id'] = unicode(uuid.uuid4()) template = self.jinja_env.get_template(template_name) xml_request = template.render(data_dict) logging.debug(xml_request) return xml_request class BraspagResponse(object): """ TODO: .. attribute:: correlation_id .. attribute:: errors .. attribute:: success .. attribute:: order_id .. attribute:: braspag_order_id .. attribute:: transaction_id .. attribute:: payment_method .. attribute:: amount .. attribute:: acquirer_transaction_id .. attribute:: authorization_code .. attribute:: return_code .. attribute:: return_message .. attribute:: card_token .. attribute:: status """ _STATUS = [ (0, 'Captured'), (1, 'Authorized'), (2, 'Not Authorized'), (3, 'Disqualifying Error'), (4, 'Waiting for Answer'), ] _NAMESPACE = 'https://www.pagador.com.br/webservice/pagador' def __init__(self, http_reponse): if http_reponse.status != 200: raise BraspagHttpResponseException(http_reponse.status, http_reponse.reason) # Ensure all attributes are set before returning self.order_id = None self.braspag_order_id = None self.transaction_id = None self.payment_method = None self.amount = None self.acquirer_transaction_id = None self.authorization_code = None self.return_code = None self.return_message = None self.card_token = None self.status = None xml_response = http_reponse.read() self.root = ElementTree.fromstring(xml_response) logging.debug(minidom.parseString(xml_response).\ toprettyxml(indent=' ')) self.correlation_id = self._get_text('CorrelationId') self.errors = self._get_errors() if self._get_text('Success') == 'true': self.success = True else: self.success = False return self.order_id = self._get_text('OrderId') self.braspag_order_id = self._get_text('BraspagOrderId') self.transaction_id = self._get_text('BraspagTransactionId') self.payment_method = self._get_int('PaymentMethod') self.amount = self._get_amount('Amount') self.acquirer_transaction_id = self._get_text('AcquirerTransactionId') self.authorization_code = self._get_text('AuthorizationCode') self.return_code = self._get_int('ReturnCode') self.return_message = self._get_text('ReturnMessage') self.card_token = self._get_text('CreditCardToken') self.status = BraspagResponse._STATUS[self._get_int('Status')] def _get_text(self, field, node=None): if node is None: node = self.root xml_tag = node.find('.//{{{0}}}{1}'.format( BraspagResponse._NAMESPACE, field)) if xml_tag is not None: if xml_tag.text is not None: return xml_tag.text.strip() return '' def _get_int(self, field): try: return int(self._get_text(field)) except ValueError: return 0 def _get_amount(self, field): try: amount = Decimal(self._get_text(field)) except InvalidOperation: return Decimal('0.00') return (amount/100).quantize(Decimal('1.00')) def _get_errors(self): errors = [] xml_errors = self.root.findall('.//{{{0}}}ErrorReportDataResponse'.\ format(BraspagResponse._NAMESPACE)) for error_node in xml_errors: code = self._get_text('ErrorCode', error_node) msg = self._get_text('ErrorMessage', error_node) errors.append((int(code), msg)) return errors

Project Versions