[REF] payment_payulatam: migrate PayULatam to the new payment API
See the merge commit for more details. task-2333038
This commit is contained in:
committed by
Antoine Vandevenne (anv)
parent
7b165cd555
commit
ffee5a914b
@@ -1,6 +1,11 @@
|
||||
# -*- 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
|
||||
|
||||
from odoo.addons.payment import reset_payment_acquirer
|
||||
from odoo.addons.payment.models.payment_acquirer import create_missing_journals # post-init hook
|
||||
|
||||
|
||||
def uninstall_hook(cr, registry):
|
||||
reset_payment_acquirer(cr, registry, 'payulatam')
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
{
|
||||
'name': 'PayuLatam Payment Acquirer',
|
||||
'version': '2.0',
|
||||
'category': 'Accounting/Payment Acquirers',
|
||||
'sequence': 370,
|
||||
'summary': 'Payment Acquirer: PayuLatam Implementation',
|
||||
@@ -14,5 +14,6 @@
|
||||
'data/payment_acquirer_data.xml',
|
||||
],
|
||||
'application': True,
|
||||
'post_init_hook': 'create_missing_journal_for_acquirers',
|
||||
'post_init_hook': 'create_missing_journals',
|
||||
'uninstall_hook': 'uninstall_hook',
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import main
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# -*- 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
|
||||
@@ -12,11 +12,10 @@ _logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PayuLatamController(http.Controller):
|
||||
_return_url = '/payment/payulatam/return'
|
||||
|
||||
@http.route('/payment/payulatam/response', type='http', auth='public', csrf=False)
|
||||
def payulatam_response(self, **post):
|
||||
""" PayUlatam."""
|
||||
_logger.info('PayU Latam: 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')
|
||||
@http.route(_return_url, type='http', auth='public', methods=['GET'])
|
||||
def payulatam_return(self, **data):
|
||||
_logger.info("entering _handle_feedback_data with data:\n%s", pprint.pformat(data))
|
||||
request.env['payment.transaction'].sudo()._handle_feedback_data('payulatam', data)
|
||||
return werkzeug.utils.redirect('/payment/status')
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<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"/>
|
||||
</record>
|
||||
</data>
|
||||
<odoo noupdate="1">
|
||||
|
||||
<record id="payment.payment_acquirer_payulatam" model="payment.acquirer">
|
||||
<field name="provider">payulatam</field>
|
||||
<field name="redirect_form_view_id" ref="redirect_form"/>
|
||||
<field name="support_authorization">False</field>
|
||||
<field name="support_fees_computation">False</field>
|
||||
<field name="support_tokenization">False</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import payment
|
||||
from . import payment_acquirer
|
||||
from . import payment_transaction
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
# -*- 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 odoo.tools.float_utils import float_compare
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PaymentAcquirerPayulatam(models.Model):
|
||||
_inherit = 'payment.acquirer'
|
||||
|
||||
provider = fields.Selection(selection_add=[
|
||||
('payulatam', 'PayU Latam')
|
||||
], ondelete={'payulatam': 'set default'})
|
||||
payulatam_merchant_id = fields.Char(string="PayU Latam Merchant ID", required_if_provider='payulatam', groups='base.group_user')
|
||||
payulatam_account_id = fields.Char(string="PayU Latam Account ID", required_if_provider='payulatam', groups='base.group_user')
|
||||
payulatam_api_key = fields.Char(string="PayU Latam API Key", required_if_provider='payulatam', groups='base.group_user')
|
||||
|
||||
def _get_payulatam_urls(self, environment):
|
||||
""" PayUlatam URLs"""
|
||||
if environment == 'prod':
|
||||
return 'https://checkout.payulatam.com/ppp-web-gateway-payu/'
|
||||
return 'https://sandbox.checkout.payulatam.com/ppp-web-gateway-payu/'
|
||||
|
||||
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']))
|
||||
else:
|
||||
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.reference = str(uuid.uuid4())
|
||||
payulatam_values = dict(
|
||||
values,
|
||||
merchantId=self.payulatam_merchant_id,
|
||||
accountId=self.payulatam_account_id,
|
||||
description=values.get('reference'),
|
||||
referenceCode=tx.reference,
|
||||
amount=values['amount'],
|
||||
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.
|
||||
taxReturnBase='0',
|
||||
currency=values['currency'].name,
|
||||
buyerEmail=values['partner_email'],
|
||||
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):
|
||||
self.ensure_one()
|
||||
environment = 'prod' if self.state == 'enabled' else 'test'
|
||||
return self._get_payulatam_urls(environment)
|
||||
|
||||
|
||||
class PaymentTransactionPayulatam(models.Model):
|
||||
_inherit = 'payment.transaction'
|
||||
|
||||
@api.model
|
||||
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(_('PayU Latam: received data with missing reference (%s) or transaction id (%s) or sign (%s)') % (reference, txnid, sign))
|
||||
|
||||
transaction = self.search([('reference', '=', reference)])
|
||||
|
||||
if not transaction:
|
||||
error_msg = (_('PayU Latam: received data for reference %s; no order found') % (reference))
|
||||
raise ValidationError(error_msg)
|
||||
elif len(transaction) > 1:
|
||||
error_msg = (_('PayU Latam: 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(('PayU Latam: 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('transactionId') != self.acquirer_reference:
|
||||
invalid_parameters.append(('Reference code', data.get('transactionId'), 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):
|
||||
self.ensure_one()
|
||||
|
||||
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':
|
||||
_logger.info('Validated PayU Latam payment for tx %s: set as done' % (self.reference))
|
||||
res.update(state='done', date=fields.Datetime.now())
|
||||
self._set_transaction_done()
|
||||
self.write(res)
|
||||
self.execute_callback()
|
||||
return True
|
||||
elif status == 'PENDING':
|
||||
_logger.info('Received notification for PayU Latam payment %s: set as pending' % (self.reference))
|
||||
res.update(state='pending')
|
||||
self._set_transaction_pending()
|
||||
return self.write(res)
|
||||
elif status in ['EXPIRED', 'DECLINED']:
|
||||
_logger.info('Received notification for PayU Latam payment %s: set as Cancel' % (self.reference))
|
||||
res.update(state='cancel')
|
||||
self._set_transaction_cancel()
|
||||
return self.write(res)
|
||||
else:
|
||||
error = 'Received unrecognized status for PayU Latam payment %s: %s, set as error' % (self.reference, status)
|
||||
_logger.info(error)
|
||||
res.update(state='cancel', state_message=error)
|
||||
self._set_transaction_cancel()
|
||||
return self.write(res)
|
||||
@@ -0,0 +1,68 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from hashlib import md5
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo.tools.float_utils import float_repr
|
||||
|
||||
SUPPORTED_CURRENCIES = ('ARS', 'BRL', 'CLP', 'COP', 'MXN', 'PEN', 'USD')
|
||||
|
||||
|
||||
class PaymentAcquirer(models.Model):
|
||||
_inherit = 'payment.acquirer'
|
||||
|
||||
provider = fields.Selection(
|
||||
selection_add=[('payulatam', 'PayU Latam')], ondelete={'payulatam': 'set default'})
|
||||
payulatam_merchant_id = fields.Char(
|
||||
string="PayU Latam Merchant ID",
|
||||
help="The ID solely used to identify the account with PayULatam",
|
||||
required_if_provider='payulatam')
|
||||
payulatam_account_id = fields.Char(
|
||||
string="PayU Latam Account ID",
|
||||
help="The ID solely used to identify the country-dependent shop with PayULatam",
|
||||
required_if_provider='payulatam')
|
||||
payulatam_api_key = fields.Char(
|
||||
string="PayU Latam API Key", required_if_provider='payulatam',
|
||||
groups='base.group_system')
|
||||
|
||||
@api.model
|
||||
def _get_compatible_acquirers(self, *args, currency_id=None, **kwargs):
|
||||
""" Override of payment to unlist PayU Latam acquirers for unsupported currencies. """
|
||||
acquirers = super()._get_compatible_acquirers(*args, currency_id=currency_id, **kwargs)
|
||||
|
||||
currency = self.env['res.currency'].browse(currency_id).exists()
|
||||
if currency and currency.name not in SUPPORTED_CURRENCIES:
|
||||
acquirers = acquirers.filtered(lambda a: a.provider != 'payulatam')
|
||||
|
||||
return acquirers
|
||||
|
||||
def _payulatam_generate_sign(self, values, incoming=True):
|
||||
""" Generate the signature for incoming or outgoing communications.
|
||||
|
||||
:param dict values: The values used to generate the signature
|
||||
:param bool incoming: Whether the signature must be generated for an incoming (PayU Latam to
|
||||
Odoo) or outgoing (Odoo to PayU Latam) communication.
|
||||
:return: The signature
|
||||
:rtype: str
|
||||
"""
|
||||
if incoming:
|
||||
data_string = '~'.join([
|
||||
self.payulatam_api_key,
|
||||
self.payulatam_merchant_id,
|
||||
values['referenceCode'],
|
||||
# Observation from the sandbox: PayU Latam is using a rounded value to generate
|
||||
# their signature despite saying otherwise in the doc:
|
||||
# http://developers.payulatam.com/en/web_checkout/integration.html
|
||||
float_repr(float(values.get('TX_VALUE')), 1),
|
||||
values['currency'],
|
||||
values.get('transactionState'),
|
||||
])
|
||||
else:
|
||||
data_string = '~'.join([
|
||||
self.payulatam_api_key,
|
||||
self.payulatam_merchant_id,
|
||||
values['referenceCode'],
|
||||
float_repr(float(values['amount']), 1),
|
||||
values['currency'],
|
||||
])
|
||||
return md5(data_string.encode('utf-8')).hexdigest()
|
||||
@@ -0,0 +1,148 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import logging
|
||||
|
||||
from werkzeug import urls
|
||||
|
||||
from odoo import _, api, models
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.tools.float_utils import float_repr
|
||||
|
||||
from odoo.addons.payment import utils as payment_utils
|
||||
from odoo.addons.payment_payulatam.controllers.main import PayuLatamController
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PaymentTransaction(models.Model):
|
||||
_inherit = 'payment.transaction'
|
||||
|
||||
@api.model
|
||||
def _compute_reference(self, provider, prefix=None, separator='-', **kwargs):
|
||||
""" Override of payment to ensure that PayU Latam requirements for references are satisfied.
|
||||
|
||||
PayU Latam requirements for transaction are as follows:
|
||||
- References must be unique at provider level for a given merchant account.
|
||||
This is satisfied by singularizing the prefix with the current datetime. If two
|
||||
transactions are created simultaneously, `_compute_reference` ensures the uniqueness of
|
||||
references by suffixing a sequence number.
|
||||
|
||||
:param str provider: The provider of the acquirer handling the transaction
|
||||
:param str prefix: The custom prefix used to compute the full reference
|
||||
:param str separator: The custom separator used to separate the prefix from the suffix
|
||||
:return: The unique reference for the transaction
|
||||
:rtype: str
|
||||
"""
|
||||
if provider == 'payulatam':
|
||||
prefix = payment_utils.singularize_reference_prefix(prefix=prefix, separator=separator)
|
||||
return super()._compute_reference(provider, prefix=prefix, separator=separator, **kwargs)
|
||||
|
||||
def _get_specific_rendering_values(self, processing_values):
|
||||
""" Override of payment to return Payulatam-specific rendering values.
|
||||
|
||||
Note: self.ensure_one() from `_get_processing_values`
|
||||
|
||||
:param dict processing_values: The generic and specific processing values of the transaction
|
||||
:return: The dict of acquirer-specific processing values
|
||||
:rtype: dict
|
||||
"""
|
||||
res = super()._get_specific_rendering_values(processing_values)
|
||||
if self.provider != 'payulatam':
|
||||
return res
|
||||
|
||||
api_url = 'https://checkout.payulatam.com/ppp-web-gateway-payu/' \
|
||||
if self.acquirer_id.state == 'enabled' \
|
||||
else 'https://sandbox.checkout.payulatam.com/ppp-web-gateway-payu/'
|
||||
payulatam_values = {
|
||||
'merchantId': self.acquirer_id.payulatam_merchant_id,
|
||||
'referenceCode': self.reference,
|
||||
'description': self.reference,
|
||||
'amount': float_repr(processing_values['amount'], self.currency_id.decimal_places or 2),
|
||||
'tax': 0,
|
||||
'taxReturnBase': 0,
|
||||
'currency': self.currency_id.name,
|
||||
'accountId': self.acquirer_id.payulatam_account_id,
|
||||
'buyerFullName': self.partner_name,
|
||||
'buyerEmail': self.partner_email,
|
||||
'responseUrl': urls.url_join(self.get_base_url(), PayuLatamController._return_url),
|
||||
'api_url': api_url,
|
||||
}
|
||||
if self.acquirer_id.state != 'enabled':
|
||||
payulatam_values['test'] = 1
|
||||
payulatam_values['signature'] = self.acquirer_id._payulatam_generate_sign(
|
||||
payulatam_values, incoming=False
|
||||
)
|
||||
return payulatam_values
|
||||
|
||||
@api.model
|
||||
def _get_tx_from_feedback_data(self, provider, data):
|
||||
""" Override of payment to find the transaction based on Payulatam data.
|
||||
|
||||
:param str provider: The provider of the acquirer that handled the transaction
|
||||
:param dict data: The feedback data sent by the provider
|
||||
:return: The transaction if found
|
||||
:rtype: recordset of `payment.transaction`
|
||||
:raise: ValidationError if inconsistent data were received
|
||||
:raise: ValidationError if the data match no transaction
|
||||
:raise: ValidationError if the signature can not be verified
|
||||
"""
|
||||
tx = super()._get_tx_from_feedback_data(provider, data)
|
||||
if provider != 'payulatam':
|
||||
return tx
|
||||
|
||||
reference = data.get('referenceCode')
|
||||
sign = data.get('signature')
|
||||
if not reference or not sign:
|
||||
raise ValidationError(
|
||||
"PayU Latam: " + _(
|
||||
"Received data with missing reference (%(ref)s) or sign (%(sign)s).",
|
||||
ref=reference, sign=sign
|
||||
)
|
||||
)
|
||||
|
||||
tx = self.search([('reference', '=', reference), ('provider', '=', 'payulatam')])
|
||||
if not tx:
|
||||
raise ValidationError(
|
||||
"PayU Latam: " + _("No transaction found matching reference %s.", reference)
|
||||
)
|
||||
|
||||
# Verify signature
|
||||
sign_check = tx.acquirer_id._payulatam_generate_sign(data, incoming=True)
|
||||
if sign_check != sign:
|
||||
raise ValidationError(
|
||||
"PayU Latam: " + _(
|
||||
"Invalid sign: received %(sign)s, computed %(check)s.",
|
||||
sign=sign, check=sign_check
|
||||
)
|
||||
)
|
||||
|
||||
return tx
|
||||
|
||||
def _process_feedback_data(self, data):
|
||||
""" Override of payment to process the transaction based on Payulatam data.
|
||||
|
||||
Note: self.ensure_one()
|
||||
|
||||
:param dict data: The feedback data sent by the provider
|
||||
:return: None
|
||||
"""
|
||||
super()._process_feedback_data(data)
|
||||
if self.provider != 'payulatam':
|
||||
return
|
||||
|
||||
self.acquirer_reference = data.get('transactionId')
|
||||
|
||||
status = data.get('lapTransactionState')
|
||||
state_message = data.get('message')
|
||||
if status == 'PENDING':
|
||||
self._set_pending(state_message=state_message)
|
||||
elif status == 'APPROVED':
|
||||
self._set_done(state_message=state_message)
|
||||
elif status in ('EXPIRED', 'DECLINED'):
|
||||
self._set_canceled(state_message=state_message)
|
||||
else:
|
||||
_logger.warning(
|
||||
"received unrecognized payment state %s for transaction with reference %s",
|
||||
status, self.reference
|
||||
)
|
||||
self._set_error("PayU Latam: " + _("Invalid payment status."))
|
||||
@@ -1,4 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import common
|
||||
from . import test_payulatam
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from odoo.addons.payment.tests.common import PaymentCommon
|
||||
|
||||
|
||||
class PayulatamCommon(PaymentCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.payulatam = cls._prepare_acquirer('payulatam', update_values={
|
||||
'payulatam_account_id': 'dummy',
|
||||
'payulatam_merchant_id': 'dummy',
|
||||
'payulatam_api_key': 'dummy',
|
||||
})
|
||||
|
||||
# Override default values
|
||||
cls.acquirer = cls.payulatam
|
||||
cls.currency = cls.currency_euro
|
||||
@@ -1,107 +1,93 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from lxml import objectify
|
||||
from werkzeug import urls
|
||||
|
||||
from odoo.addons.payment.tests.common import PaymentAcquirerCommon
|
||||
from freezegun import freeze_time
|
||||
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.tests import tagged
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
from .common import PayulatamCommon
|
||||
from ..controllers.main import PayuLatamController
|
||||
from ..models.payment_acquirer import SUPPORTED_CURRENCIES
|
||||
|
||||
|
||||
class PayUlatamCommon(PaymentAcquirerCommon):
|
||||
@tagged('post_install', '-at_install')
|
||||
class PayUlatamTest(PayulatamCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls, chart_template_ref=None):
|
||||
super().setUpClass(chart_template_ref=chart_template_ref)
|
||||
cls.payulatam = cls.env.ref('payment.payment_acquirer_payulatam')
|
||||
cls.payulatam.write({
|
||||
'payulatam_account_id': 'dummy',
|
||||
'payulatam_merchant_id': 'dummy',
|
||||
'payulatam_api_key': 'dummy',
|
||||
'state': 'test',
|
||||
})
|
||||
def test_compatible_acquirers(self):
|
||||
for curr in SUPPORTED_CURRENCIES:
|
||||
currency = self._prepare_currency(curr)
|
||||
acquirers = self.env['payment.acquirer']._get_compatible_acquirers(
|
||||
partner_id=self.partner.id,
|
||||
company_id=self.company.id,
|
||||
currency_id=currency.id,
|
||||
)
|
||||
self.assertIn(self.payulatam, acquirers)
|
||||
|
||||
acquirers = self.env['payment.acquirer']._get_compatible_acquirers(
|
||||
partner_id=self.partner.id,
|
||||
company_id=self.company.id,
|
||||
currency_id=self.currency_euro.id,
|
||||
)
|
||||
self.assertNotIn(self.payulatam, acquirers)
|
||||
|
||||
@tagged('post_install', '-at_install', 'external', '-standard')
|
||||
class PayUlatamForm(PayUlatamCommon):
|
||||
# freeze time for consistent singularize_prefix behavior during the test
|
||||
@freeze_time("2011-11-02 12:00:21")
|
||||
def test_reference(self):
|
||||
tx = self.create_transaction(flow="redirect", reference="")
|
||||
self.assertEqual(tx.reference, "tx-20111102120021",
|
||||
"Payulatam: transaction reference wasn't correctly singularized.")
|
||||
|
||||
def test_10_payulatam_form_render(self):
|
||||
base_url = self.env['ir.config_parameter'].get_param('web.base.url')
|
||||
self.assertEqual(self.payulatam.state, 'test', 'test without test environment')
|
||||
self.payulatam.write({
|
||||
'payulatam_merchant_id': 'dummy',
|
||||
'payulatam_account_id': 'dummy',
|
||||
'payulatam_api_key': 'dummy',
|
||||
})
|
||||
def test_redirect_form_values(self):
|
||||
tx = self.create_transaction(flow='redirect')
|
||||
with mute_logger('odoo.addons.payment.models.payment_transaction'):
|
||||
processing_values = tx._get_processing_values()
|
||||
|
||||
# ----------------------------------------
|
||||
# Test: button direct rendering
|
||||
# ----------------------------------------
|
||||
self.env['payment.transaction'].create({
|
||||
'reference': 'test_ref0',
|
||||
'amount': 0.001,
|
||||
'currency_id': self.currency_euro.id,
|
||||
'acquirer_id': self.payulatam.id,
|
||||
'partner_id': self.buyer_id
|
||||
})
|
||||
|
||||
# render the button
|
||||
res = self.payulatam.render(
|
||||
'test_ref0', 0.01, self.currency_euro.id,
|
||||
values=self.buyer_values)
|
||||
|
||||
form_values = {
|
||||
form_info = self._extract_values_from_html_form(processing_values['redirect_form_html'])
|
||||
expected_values = {
|
||||
'merchantId': 'dummy',
|
||||
'accountId': 'dummy',
|
||||
'description': 'test_ref0',
|
||||
'referenceCode': 'test',
|
||||
'amount': '0.01',
|
||||
'currency': 'EUR',
|
||||
'tax': '0',
|
||||
'taxReturnBase': '0',
|
||||
'buyerEmail': 'norbert.buyer@example.com',
|
||||
'responseUrl': urls.url_join(base_url, '/payment/payulatam/response'),
|
||||
'extra1': None
|
||||
'description': self.reference,
|
||||
'referenceCode': self.reference,
|
||||
'amount': str(self.amount),
|
||||
'currency': self.currency.name,
|
||||
'tax': str(0),
|
||||
'taxReturnBase': str(0),
|
||||
'buyerEmail': self.partner.email,
|
||||
'buyerFullName': self.partner.name,
|
||||
'responseUrl': self._build_url(PayuLatamController._return_url),
|
||||
'test': str(1), # test is always done in test mode.
|
||||
}
|
||||
# check form result
|
||||
tree = objectify.fromstring(res)
|
||||
expected_values['signature'] = self.payulatam._payulatam_generate_sign(
|
||||
expected_values, incoming=False)
|
||||
|
||||
data_set = tree.xpath("//input[@name='data_set']")
|
||||
self.assertEqual(len(data_set), 1, 'payulatam: Found %d "data_set" input instead of 1' % len(data_set))
|
||||
self.assertEqual(data_set[0].get('data-action-url'), 'https://sandbox.checkout.payulatam.com/ppp-web-gateway-payu/', 'payulatam: wrong form POST url')
|
||||
for form_input in tree.input:
|
||||
if form_input.get('name') in ['submit', 'data_set', 'signature', 'referenceCode']:
|
||||
continue
|
||||
self.assertEqual(
|
||||
form_input.get('value'),
|
||||
form_values[form_input.get('name')],
|
||||
'payulatam: wrong value for input %s: received %s instead of %s' % (form_input.get('name'), form_input.get('value'), form_values[form_input.get('name')])
|
||||
)
|
||||
|
||||
def test_20_payulatam_form_management(self):
|
||||
self.assertEqual(self.payulatam.state, 'test', 'test without test environment')
|
||||
self.assertEqual(form_info['action'],
|
||||
'https://sandbox.checkout.payulatam.com/ppp-web-gateway-payu/')
|
||||
self.assertDictEqual(form_info['inputs'], expected_values)
|
||||
|
||||
def test_feedback_processing(self):
|
||||
# typical data posted by payulatam after client has successfully paid
|
||||
payulatam_post_data = {
|
||||
'installmentsNumber': '1',
|
||||
'lapPaymentMethod': 'VISA',
|
||||
'description': 'test_ref0',
|
||||
'currency': 'EUR',
|
||||
'description': self.reference,
|
||||
'currency': self.currency.name,
|
||||
'extra2': '',
|
||||
'lng': 'es',
|
||||
'transactionState': '7',
|
||||
'polPaymentMethod': '211',
|
||||
'pseCycle': '',
|
||||
'pseBank': '',
|
||||
'referenceCode': 'test_ref_10',
|
||||
'referenceCode': self.reference,
|
||||
'reference_pol': '844164756',
|
||||
'signature': '88f11d693d3551419f86850948d731ba',
|
||||
'signature': 'f3ea3a7414a56d8153c425ab7e2f69d7', # Update me
|
||||
'pseReference3': '',
|
||||
'buyerEmail': 'admin@yourcompany.example.com',
|
||||
'lapResponseCode': 'PENDING_TRANSACTION_CONFIRMATION',
|
||||
'pseReference2': '',
|
||||
'cus': '',
|
||||
'orderLanguage': 'es',
|
||||
'TX_VALUE': '0.01',
|
||||
'TX_VALUE': str(self.amount),
|
||||
'risk': '',
|
||||
'trazabilityCode': '',
|
||||
'extra3': '',
|
||||
@@ -123,32 +109,25 @@ class PayUlatamForm(PayUlatamCommon):
|
||||
'merchant_address': 'Av 123 Calle 12'
|
||||
}
|
||||
|
||||
# create tx
|
||||
tx = self.env['payment.transaction'].create({
|
||||
'amount': 0.01,
|
||||
'acquirer_id': self.payulatam.id,
|
||||
'currency_id': self.currency_euro.id,
|
||||
'reference': 'test_ref_10',
|
||||
'partner_name': 'Norbert Buyer',
|
||||
'partner_country_id': self.country_france.id,
|
||||
'partner_id': self.buyer_id})
|
||||
# should raise error about unknown tx
|
||||
with self.assertRaises(ValidationError):
|
||||
self.env['payment.transaction']._handle_feedback_data('payulatam', payulatam_post_data)
|
||||
|
||||
# validate transaction
|
||||
tx.form_feedback(payulatam_post_data, 'payulatam')
|
||||
# check
|
||||
tx = self.create_transaction(flow='redirect')
|
||||
|
||||
# Validate the transaction ('pending' state)
|
||||
self.env['payment.transaction']._handle_feedback_data('payulatam', payulatam_post_data)
|
||||
self.assertEqual(tx.state, 'pending', 'Payulatam: wrong state after receiving a valid pending notification')
|
||||
self.assertEqual(tx.state_message, 'PENDING', 'Payulatam: wrong state message after receiving a valid pending notification')
|
||||
self.assertEqual(tx.acquirer_reference, 'b232989a-4aa8-42d1-bace-153236eee791', 'PayU Latam: wrong txn_id after receiving a valid pending notification')
|
||||
self.assertEqual(tx.state_message, payulatam_post_data['message'], 'Payulatam: wrong state message after receiving a valid pending notification')
|
||||
self.assertEqual(tx.acquirer_reference, 'b232989a-4aa8-42d1-bace-153236eee791', 'Payulatam: wrong txn_id after receiving a valid pending notification')
|
||||
|
||||
# update transaction
|
||||
# Reset the transaction
|
||||
tx.write({
|
||||
'state': 'draft',
|
||||
'acquirer_reference': False})
|
||||
|
||||
# update notification from payulatam
|
||||
# Validate the transaction ('approved' state)
|
||||
payulatam_post_data['lapTransactionState'] = 'APPROVED'
|
||||
# validate transaction
|
||||
tx.form_feedback(payulatam_post_data, 'payulatam')
|
||||
# check transaction
|
||||
self.assertEqual(tx.state, 'done', 'payulatam: wrong state after receiving a valid pending notification')
|
||||
self.assertEqual(tx.acquirer_reference, 'b232989a-4aa8-42d1-bace-153236eee791', 'payulatam: wrong txn_id after receiving a valid pending notification')
|
||||
self.env['payment.transaction']._handle_feedback_data('payulatam', payulatam_post_data)
|
||||
self.assertEqual(tx.state, 'done', 'Payulatam: wrong state after receiving a valid pending notification')
|
||||
self.assertEqual(tx.acquirer_reference, 'b232989a-4aa8-42d1-bace-153236eee791', 'Payulatam: wrong txn_id after receiving a valid pending notification')
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<template id="payulatam_form">
|
||||
<div>
|
||||
<input type="hidden" name="data_set" t-att-data-action-url="tx_url" data-remove-me=""/>
|
||||
<input type="hidden" name="merchantId" t-att-value='merchantId'/>
|
||||
<input type="hidden" name="accountId" t-att-value='accountId'/>
|
||||
<input type="hidden" name="description" t-att-value='description'/>
|
||||
<input type="hidden" name="referenceCode" t-att-value='referenceCode'/>
|
||||
<input type="hidden" name="amount" t-att-value='amount'/>
|
||||
<input type="hidden" name="currency" t-att-value='currency'/>
|
||||
<input type="hidden" name="signature" t-att-value='signature'/>
|
||||
<input type="hidden" name="tax" t-att-value='tax'/>
|
||||
<input type="hidden" name="taxReturnBase" t-att-value="taxReturnBase"/>
|
||||
<input type="hidden" name="buyerEmail" t-att-value='buyerEmail'/>
|
||||
<input type="hidden" name="responseUrl" t-att-value='responseUrl'/>
|
||||
<input type="hidden" name="extra1" t-att-value="extra1"/>
|
||||
</div>
|
||||
|
||||
<template id="redirect_form">
|
||||
<form t-att-action="api_url" method="post">
|
||||
<input type="hidden" name="merchantId" t-att-value="merchantId"/>
|
||||
<input type="hidden" name="referenceCode" t-att-value="referenceCode"/>
|
||||
<input type="hidden" name="description" t-att-value="description"/>
|
||||
<input type="hidden" name="amount" t-att-value="amount"/>
|
||||
<!-- Use t-attf to set O, otherwise the value attribute is not included in the input -->
|
||||
<input type="hidden" name="tax" t-attf-value="{{tax}}"/>
|
||||
<!-- Use t-attf to set O, otherwise the value attribute is not included in the input -->
|
||||
<input type="hidden" name="taxReturnBase" t-attf-value="{{taxReturnBase}}"/>
|
||||
<input type="hidden" name="signature" t-att-value="signature"/>
|
||||
<input type="hidden" name="currency" t-att-value="currency"/>
|
||||
<input type="hidden" name="test" t-att-value="test"/>
|
||||
<input type="hidden" name="accountId" t-att-value="accountId"/>
|
||||
<input type="hidden" name="buyerFullName" t-att-value="buyerFullName"/>
|
||||
<input type="hidden" name="buyerEmail" t-att-value="buyerEmail"/>
|
||||
<input type="hidden" name="responseUrl" t-att-value="responseUrl"/>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="payment_acquirer_form_inherit_payment_payulatam" model="ir.ui.view">
|
||||
<field name="name">payment.acquirer.form.inherit.payment.payulatam</field>
|
||||
|
||||
<record id="payment_acquirer_form" model="ir.ui.view">
|
||||
<field name="name">PayU latam Acquirer Form</field>
|
||||
<field name="model">payment.acquirer</field>
|
||||
<field name="inherit_id" ref="payment.acquirer_form"/>
|
||||
<field name="inherit_id" ref="payment.payment_acquirer_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr='//group[@name="acquirer"]' position='inside'>
|
||||
<group attrs="{'invisible': [('provider', '!=', 'payulatam')]}">
|
||||
<field name="payulatam_account_id" options="{'no_create': True}"/>
|
||||
<field name="payulatam_merchant_id"/>
|
||||
<field name="payulatam_api_key"/>
|
||||
<field name="payulatam_merchant_id"
|
||||
attrs="{'required':[('provider', '=', 'payulatam'), ('state', '!=', 'disabled')]}"/>
|
||||
<field name="payulatam_account_id"
|
||||
attrs="{'required':[('provider', '=', 'payulatam'), ('state', '!=', 'disabled')]}"/>
|
||||
<field name="payulatam_api_key"
|
||||
attrs="{'required':[('provider', '=', 'payulatam'), ('state', '!=', 'disabled')]}"/>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
||||
Reference in New Issue
Block a user