From ffee5a914b3944214089728ee114fd4f54d597ca Mon Sep 17 00:00:00 2001 From: Kevin Baptiste Date: Fri, 26 Mar 2021 18:30:52 +0100 Subject: [PATCH] [REF] payment_payulatam: migrate PayULatam to the new payment API See the merge commit for more details. task-2333038 --- addons/payment_payulatam/__init__.py | 9 +- addons/payment_payulatam/__manifest__.py | 5 +- .../payment_payulatam/controllers/__init__.py | 1 - addons/payment_payulatam/controllers/main.py | 15 +- .../data/payment_acquirer_data.xml | 18 +- addons/payment_payulatam/models/__init__.py | 4 +- addons/payment_payulatam/models/payment.py | 143 --------------- .../models/payment_acquirer.py | 68 ++++++++ .../models/payment_transaction.py | 148 ++++++++++++++++ addons/payment_payulatam/tests/__init__.py | 2 +- addons/payment_payulatam/tests/common.py | 19 ++ .../payment_payulatam/tests/test_payulatam.py | 165 ++++++++---------- .../views/payment_payulatam_templates.xml | 36 ++-- .../payment_payulatam/views/payment_views.xml | 17 +- 14 files changed, 368 insertions(+), 282 deletions(-) delete mode 100644 addons/payment_payulatam/models/payment.py create mode 100644 addons/payment_payulatam/models/payment_acquirer.py create mode 100644 addons/payment_payulatam/models/payment_transaction.py create mode 100644 addons/payment_payulatam/tests/common.py diff --git a/addons/payment_payulatam/__init__.py b/addons/payment_payulatam/__init__.py index e52ad6f83e3..c56ff5159ac 100644 --- a/addons/payment_payulatam/__init__.py +++ b/addons/payment_payulatam/__init__.py @@ -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') diff --git a/addons/payment_payulatam/__manifest__.py b/addons/payment_payulatam/__manifest__.py index 89a94a98efd..5c04525a204 100644 --- a/addons/payment_payulatam/__manifest__.py +++ b/addons/payment_payulatam/__manifest__.py @@ -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', } diff --git a/addons/payment_payulatam/controllers/__init__.py b/addons/payment_payulatam/controllers/__init__.py index 5d4b25db9c0..80ee4da1c5e 100644 --- a/addons/payment_payulatam/controllers/__init__.py +++ b/addons/payment_payulatam/controllers/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. from . import main diff --git a/addons/payment_payulatam/controllers/main.py b/addons/payment_payulatam/controllers/main.py index 6d56715803a..4e2dff4c0be 100644 --- a/addons/payment_payulatam/controllers/main.py +++ b/addons/payment_payulatam/controllers/main.py @@ -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') diff --git a/addons/payment_payulatam/data/payment_acquirer_data.xml b/addons/payment_payulatam/data/payment_acquirer_data.xml index 93d0468aaf7..892bd5a2f0e 100644 --- a/addons/payment_payulatam/data/payment_acquirer_data.xml +++ b/addons/payment_payulatam/data/payment_acquirer_data.xml @@ -1,10 +1,12 @@ - - - - payulatam - - - - + + + + payulatam + + False + False + False + + diff --git a/addons/payment_payulatam/models/__init__.py b/addons/payment_payulatam/models/__init__.py index 2ec5b9cd2f6..8e04237423a 100644 --- a/addons/payment_payulatam/models/__init__.py +++ b/addons/payment_payulatam/models/__init__.py @@ -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 diff --git a/addons/payment_payulatam/models/payment.py b/addons/payment_payulatam/models/payment.py deleted file mode 100644 index 958a977ee1b..00000000000 --- a/addons/payment_payulatam/models/payment.py +++ /dev/null @@ -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) diff --git a/addons/payment_payulatam/models/payment_acquirer.py b/addons/payment_payulatam/models/payment_acquirer.py new file mode 100644 index 00000000000..3347de49879 --- /dev/null +++ b/addons/payment_payulatam/models/payment_acquirer.py @@ -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() diff --git a/addons/payment_payulatam/models/payment_transaction.py b/addons/payment_payulatam/models/payment_transaction.py new file mode 100644 index 00000000000..a6d7811ab27 --- /dev/null +++ b/addons/payment_payulatam/models/payment_transaction.py @@ -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.")) diff --git a/addons/payment_payulatam/tests/__init__.py b/addons/payment_payulatam/tests/__init__.py index 09671a17b96..42eed48541f 100644 --- a/addons/payment_payulatam/tests/__init__.py +++ b/addons/payment_payulatam/tests/__init__.py @@ -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 diff --git a/addons/payment_payulatam/tests/common.py b/addons/payment_payulatam/tests/common.py new file mode 100644 index 00000000000..3965200de18 --- /dev/null +++ b/addons/payment_payulatam/tests/common.py @@ -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 diff --git a/addons/payment_payulatam/tests/test_payulatam.py b/addons/payment_payulatam/tests/test_payulatam.py index 710d7dabe41..752e10da6ba 100644 --- a/addons/payment_payulatam/tests/test_payulatam.py +++ b/addons/payment_payulatam/tests/test_payulatam.py @@ -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') diff --git a/addons/payment_payulatam/views/payment_payulatam_templates.xml b/addons/payment_payulatam/views/payment_payulatam_templates.xml index 793b1e0e38d..2a34ae4520e 100644 --- a/addons/payment_payulatam/views/payment_payulatam_templates.xml +++ b/addons/payment_payulatam/views/payment_payulatam_templates.xml @@ -1,20 +1,24 @@ -