Browse files

[ADD] payment_payulatam: Add new payment acquirer for Latin America

TaskID 1826303
  • Loading branch information...
nla-odoo authored and tivisse committed May 10, 2018
1 parent 50a41c9 commit b0eb01bc11ecf3f68791802691e5b397b2eb2a33
@@ -243,4 +243,26 @@

<record id="payment_acquirer_payulatam" model="payment.acquirer">
<field name="name">PayUlatam</field>
<field name="image" type="base64" file="payment_payulatam/static/src/img/payulatam_icon.png"/>
<field name="view_template_id" ref="default_acquirer_button"/>
<field name="module_id" ref="base.module_payment_payulatam"/>
<field name="description" type="html">
PayU is a leading financial services provider in Colombia, Argentina, Brazil, Chile, Mexico, Panama, and Peru. It allows merchant to accept local payments with just one account and integration.
<ul class="list-inline">
<li><i class="fa fa-check"/>Online Payment</li>
<li><i class="fa fa-check"/>Payment Status Tracking</li>
<!-- -->
<field name="payment_icon_ids" eval='[(6, 0, [ref("payment.payment_icon_cc_diners_club_intl"),

@@ -49,4 +49,9 @@
<field name="name">Bancontact</field>
<field name="image" type="base64" file="payment/static/img/bancontact.png"/>

<record id="payment_icon_cc_codensa_easy_credit" model="payment.icon">
<field name="name">Codensa Easy Credit</field>
<field name="image" type="base64" file="payment/static/img/codensa_easy_credit.png"/>
Binary file not shown.
@@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from . import controllers
from . import models
from odoo.addons.payment.models.payment_acquirer import create_missing_journal_for_acquirers
@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

'name': 'PayuLatam Payment Acquirer',
'category': 'Payment Acquirer',
'summary': 'Payment Acquirer: PayuLatam Implementation',
'description': """Payulatam payment acquirer""",
'depends': ['payment'],
'data': [
'post_init_hook': 'create_missing_journal_for_acquirers',
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from . import main
@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

import logging
import pprint
import werkzeug

from odoo import http
from odoo.http import request

_logger = logging.getLogger(__name__)

class PayuLatamController(http.Controller):

@http.route('/payment/payulatam/response', type='http', auth='public', csrf=False)
def payulatam_response(self, **post):
""" PayUlatam."""'PayUlatam: entering form_feedback with post response data %s', pprint.pformat(post))
if post:
request.env['payment.transaction'].sudo().form_feedback(post, 'payulatam')
return werkzeug.utils.redirect('/payment/process')
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<data noupdate="1">
<record id="payment.payment_acquirer_payulatam" model="payment.acquirer">
<field name="provider">payulatam</field>
<field name="company_id" ref="base.main_company"/>
<field name="view_template_id" ref="payulatam_form"/>
<field name="environment">test</field>
<field name="pre_msg" type="html">
<p>You will be redirected to the PayUlatam website after clicking on the payment button.</p>
<field name="payulatam_account_id">dummy</field>
<field name="payulatam_merchant_id">dummy</field>
<field name="payulatam_api_key">dummy</field>
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from . import payment
@@ -0,0 +1,144 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

import logging
import uuid

from hashlib import md5
from werkzeug import urls

from odoo import api, fields, models, _
from odoo.addons.payment.models.payment_acquirer import ValidationError
from import float_compare

_logger = logging.getLogger(__name__)

class PaymentAcquirerPayulatam(models.Model):
_inherit = 'payment.acquirer'

provider = fields.Selection(selection_add=[('payulatam', 'PayUlatam')])
payulatam_merchant_id = fields.Char(string="PayUlatam Merchant ID", required_if_provider='payulatam', groups='base.group_user')
payulatam_account_id = fields.Char(string="PayUlatam Account ID", required_if_provider='payulatam', groups='base.group_user')
payulatam_api_key = fields.Char(string="PayUlatam API Key", required_if_provider='payulatam', groups='base.group_user')

def _get_payulatam_urls(self, environment):
""" PayUlatam URLs"""
if environment == 'prod':
return ''
return ''

def _payulatam_generate_sign(self, inout, values):
if inout not in ('in', 'out'):
raise Exception("Type must be 'in' or 'out'")

if inout == 'in':
data_string = ('~').join((self.payulatam_api_key, self.payulatam_merchant_id, values['referenceCode'],
str(values['amount']), values['currency']))
data_string = ('~').join((self.payulatam_api_key, self.payulatam_merchant_id, values['referenceCode'],
str(float(values.get('TX_VALUE'))), values['currency'], values.get('transactionState')))
return md5(data_string.encode('utf-8')).hexdigest()

def payulatam_form_generate_values(self, values):
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
tx = self.env['payment.transaction'].search([('reference', '=', values.get('reference'))])
# payulatam will not allow any payment twise even if payment was failed last time.
# so, replace reference code if payment is not done or pending.
if tx.state not in ['done', 'pending']:
tx.acquirer_reference = str(uuid.uuid4())
payulatam_values = dict(
tax='0', # This is the transaction VAT. If VAT zero is sent the system, 19% will be applied automatically. It can contain two decimals. Eg 19000.00. In the where you do not charge VAT, it should should be set as 0.
responseUrl=urls.url_join(base_url, '/payment/payulatam/response'),
payulatam_values['signature'] = self._payulatam_generate_sign("in", payulatam_values)
return payulatam_values

def payulatam_get_form_action_url(self):
return self._get_payulatam_urls(self.environment)

class PaymentTransactionPayulatam(models.Model):
_inherit = 'payment.transaction'

def _payulatam_form_get_tx_from_data(self, data):
""" Given a data dict coming from payulatam, verify it and find the related
transaction record. """
reference, txnid, sign = data.get('referenceCode'), data.get('transactionId'), data.get('signature')
if not reference or not txnid or not sign:
raise ValidationError(_('PayUlatam: received data with missing reference (%s) or transaction id (%s) or sign (%s)') % (reference, txnid, sign))

transaction =[('acquirer_reference', '=', reference)])

if not transaction:
error_msg = (_('PayUlatam: received data for reference %s; no order found') % (reference))
raise ValidationError(error_msg)
elif len(transaction) > 1:
error_msg = (_('PayUlatam: received data for reference %s; multiple orders found') % (reference))
raise ValidationError(error_msg)

# verify shasign
sign_check = transaction.acquirer_id._payulatam_generate_sign('out', data)
if sign_check.upper() != sign.upper():
raise ValidationError(('PayUlatam: invalid sign, received %s, computed %s, for data %s') % (sign, sign_check, data))
return transaction

def _payulatam_form_get_invalid_parameters(self, data):
invalid_parameters = []

if self.acquirer_reference and data.get('referenceCode') != self.acquirer_reference:
invalid_parameters.append(('Reference code', data.get('referenceCode'), self.acquirer_reference))
if float_compare(float(data.get('TX_VALUE', '0.0')), self.amount, 2) != 0:
invalid_parameters.append(('Amount', data.get('TX_VALUE'), '%.2f' % self.amount))
if data.get('merchantId') != self.acquirer_id.payulatam_merchant_id:
invalid_parameters.append(('Merchant Id', data.get('merchantId'), self.acquirer_id.payulatam_merchant_id))
return invalid_parameters

def _payulatam_form_validate(self, data):

status = data.get('lapTransactionState') or data.find('transactionResponse').find('state').text
res = {
'acquirer_reference': data.get('transactionId') or data.find('transactionResponse').find('transactionId').text,
'state_message': data.get('message') or ""

if status == 'APPROVED':'Validated PayUlatam payment for tx %s: set as done' % (self.reference))
return True
elif status == 'PENDING':'Received notification for PayUlatam payment %s: set as pending' % (self.reference))
return self.write(res)
elif status in ['EXPIRED', 'DECLINED']:'Received notification for PayUlatam payment %s: set as Cancel' % (self.reference))
return self.write(res)
error = 'Received unrecognized status for PayUlatam payment %s: %s, set as error' % (self.reference, status)
res.update(state='cancel', state_message=error)
return self.write(res)
Binary file not shown.
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from . import test_payulatam
Oops, something went wrong.

0 comments on commit b0eb01b

Please sign in to comment.