From 50beec7fa0029f6ece11df02a26e333a8430ee95 Mon Sep 17 00:00:00 2001 From: fda-odoo Date: Tue, 12 Dec 2017 11:49:42 +0100 Subject: [PATCH 01/17] [FIX] payment: improve invalid input management on payment form The different fields weren't correctly checked on a payment form. This commit improve the error messages and display it for each field. It adds too a verification on the fields in the case of the field are filled automatically by Firefox on a refresh (F5). --- addons/payment/static/src/js/payment_form.js | 125 ++++++++++-------- .../payment/static/src/js/payment_portal.js | 3 + 2 files changed, 75 insertions(+), 53 deletions(-) diff --git a/addons/payment/static/src/js/payment_form.js b/addons/payment/static/src/js/payment_form.js index dd643840e95..21a105da3fa 100644 --- a/addons/payment/static/src/js/payment_form.js +++ b/addons/payment/static/src/js/payment_form.js @@ -57,25 +57,29 @@ odoo.define('payment.payment_form', function (require) { console.warn('payment_form: unset partner_id when adding new token; things could go wrong'); } var form_data = this.getFormData(inputs_form); - var empty_inputs = false; + var wrong_input = false; inputs_form.toArray().forEach(function (element) { - if (element.dataset.isRequired) { - if (element.value.length === 0) { + //skip the check of non visible inputs + if ($(element).attr('type') == 'hidden') { + return true; + } + $(element).closest('div.form-group').removeClass('has-error'); + $(element).siblings( ".o_invalid_field" ).remove(); + //force check of forms validity (useful for Firefox that refill forms automatically on f5) + $(element).trigger("focusout"); + if (element.dataset.isRequired && element.value.length === 0) { $(element).closest('div.form-group').addClass('has-error'); - empty_inputs = true; - } - else { - $(element).closest('div.form-group').removeClass('has-error'); - } + $(element).closest('div.form-group').append('
' + _.str.escapeHTML("The value is invalid.") + '
'); + wrong_input = true; + } + else if ($(element).closest('div.form-group').hasClass('has-error')) { + wrong_input = true; + $(element).closest('div.form-group').append('
' + _.str.escapeHTML("The value is invalid.") + '
'); } }); - if (empty_inputs) { - this.displayError( - _t('Missing values'), - _t('

Please fill all the inputs required.

') - ); + if (wrong_input) { return; } @@ -106,7 +110,7 @@ odoo.define('payment.payment_form', function (require) { else { self.displayError( _t('Server Error'), - _t("

We are not able to add your payment method at the moment.

")); + _t('e.g. Your credit card details are wrong. Please verify.')); } // here we remove the 'processing' icon from the 'add a new payment' button $(button).attr('disabled', false); @@ -118,10 +122,8 @@ odoo.define('payment.payment_form', function (require) { self.displayError( _t('Server Error'), - _t("

We are not able to add your payment method at the moment.

") + - "

" + - (core.debug ? (data.data.message.replace(/\n/g, "
")): '') + - "

" + _t("We are not able to add your payment method at the moment.") + + data.data.message ); }); } @@ -159,16 +161,14 @@ odoo.define('payment.payment_form', function (require) { else { self.displayError( _t('Server Error'), - _t("

We are not able to redirect you to the payment form.

") + _t("We are not able to redirect you to the payment form.") ); } }).fail(function (message, data) { self.displayError( _t('Server Error'), - _t("

We are not able to redirect you to the payment form.

") + - "

" + - (core.debug ? (data.data.message.replace(/\n/g, "
")): '') + - "

" + _t("We are not able to redirect you to the payment form.<") + + data.data.message ); }); } @@ -176,7 +176,7 @@ odoo.define('payment.payment_form', function (require) { // we append the form to the body and send it. this.displayError( _t("Cannot set-up the payment"), - _t("

We're unable to process your payment.

") + _t("We're unable to process your payment.") ); } } @@ -187,7 +187,7 @@ odoo.define('payment.payment_form', function (require) { else { this.displayError( _t('No payment method selected'), - _t('

Please select a payment method.

') + _t('Please select a payment method.') ); } }, @@ -209,25 +209,31 @@ odoo.define('payment.payment_form', function (require) { var inputs_form = $('input', acquirer_form); var form_data = this.getFormData(inputs_form); var ds = $('input[name="data_set"]', acquirer_form)[0]; - var empty_inputs = false; - + var wrong_input = false; + inputs_form.toArray().forEach(function (element) { - if (element.dataset.isRequired) { - if (element.value.length === 0) { + //skip the check of non visible inputs + if ($(element).attr('type') == 'hidden') { + return true; + } + $(element).closest('div.form-group').removeClass('has-error'); + $(element).siblings( ".o_invalid_field" ).remove(); + //force check of forms validity (useful for Firefox that refill forms automatically on f5) + $(element).trigger("focusout"); + if (element.dataset.isRequired && element.value.length === 0) { $(element).closest('div.form-group').addClass('has-error'); - empty_inputs = true; - } - else { - $(element).closest('div.form-group').removeClass('has-error'); - } + var message = '
' + _.str.escapeHTML("The value is invalid.") + '
'; + $(element).closest('div.form-group').append(message); + wrong_input = true; + } + else if ($(element).closest('div.form-group').hasClass('has-error')) { + wrong_input = true; + var message = '
' + _.str.escapeHTML("The value is invalid.") + '
'; + $(element).closest('div.form-group').append(message); } }); - if (empty_inputs) { - this.displayError( - _t('Missing values'), - _t('

Please fill all the inputs required.

') - ); + if (wrong_input) { return; } // We add a 'processing' icon into the 'add a new payment' button @@ -274,17 +280,15 @@ odoo.define('payment.payment_form', function (require) { self.displayError( _t('Server error'), - _t("

We are not able to add your payment method at the moment.

") + - "

" + - (core.debug ? (data.data.message.replace(/\n/g, "
")): '') + - "

" + _t("We are not able to add your payment method at the moment.

") + + data.data.message ); }); } else { this.displayError( _t('No payment method selected'), - _t('

Please select the option to add a new payment method.

') + _t('Please select the option to add a new payment method.') ); } }, @@ -308,7 +312,7 @@ odoo.define('payment.payment_form', function (require) { }, function () { self.displayError( _t('Server Error'), - _t("

We are not able to delete your payment method at the moment.

") + _t("We are not able to delete your payment method at the moment.") ); }); }; @@ -322,7 +326,7 @@ odoo.define('payment.payment_form', function (require) { // if there's records linked to this payment method var content = ''; result[pm_id].forEach(function (sub) { - content += '

' + _.str.escapeHTML(sub.name) + '

'; + content += '

' + sub.name + '

'; }); content = $('

').html(_t('

This card is currently linked to the following records:

') + content); @@ -343,7 +347,7 @@ odoo.define('payment.payment_form', function (require) { }, function (type, err) { self.displayError( _t('Server Error'), - _t("

We are not able to delete your payment method at the moment.

") + (core.debug ? err.data.message : '') + _t("We are not able to delete your payment method at the moment.") + err.data.message ); }); }, @@ -391,12 +395,27 @@ odoo.define('payment.payment_form', function (require) { return $(element).data('acquirer-id'); }, displayError: function (title, message) { - return new Dialog(null, { - title: _t('Error: ') + title, - size: 'medium', - $content: message || "", - buttons: [ - {text: _t('Ok'), close: true}]}).open(); + var $checkedRadio = this.$('input[type="radio"]:checked'), + acquirerID = this.getAcquirerIdFromRadio($checkedRadio[0]), + $acquirerForm = this.$('#o_payment_add_token_acq_' + acquirerID); + + if ($checkedRadio.length === 0) { + return new Dialog(null, { + title: _t('Error: ') + _.str.escapeHTML(title), + size: 'medium', + $content: "

" + (_.str.escapeHTML(message) || "") + "

" , + buttons: [ + {text: _t('Ok'), close: true}]}).open(); + } else { + // removed if exist error message + this.$('#payment_error').remove(); + var messageResult = '
'; + if (title != '') { + messageResult = messageResult + '' + _.str.escapeHTML(title) + ':
'; + } + messageResult = messageResult + _.str.escapeHTML(message) + '
'; + $acquirerForm.append(messageResult); + } }, getFormData: function ($form) { var unindexed_array = $form.serializeArray(); diff --git a/addons/payment/static/src/js/payment_portal.js b/addons/payment/static/src/js/payment_portal.js index d7397f1b04d..efab5f0a8e0 100644 --- a/addons/payment/static/src/js/payment_portal.js +++ b/addons/payment/static/src/js/payment_portal.js @@ -17,6 +17,7 @@ $(document).ready(function () { if (valid_value) { $(this).parent('.form-group').addClass('has-success'); $(this).parent('.form-group').removeClass('has-error'); + $(this).siblings('.o_invalid_field').remove(); } else { $(this).parent('.form-group').addClass('has-error'); @@ -31,6 +32,7 @@ $(document).ready(function () { if (valid_value) { $(this).parent('.form-group').addClass('has-success'); $(this).parent('.form-group').removeClass('has-error'); + $(this).siblings('.o_invalid_field').remove(); } else { $(this).parent('.form-group').addClass('has-error'); @@ -46,6 +48,7 @@ $(document).ready(function () { if (valid_value) { $(this).parent('.form-group').addClass('has-success'); $(this).parent('.form-group').removeClass('has-error'); + $(this).siblings('.o_invalid_field').remove(); } else { $(this).parent('.form-group').addClass('has-error'); From daf6ab1c872ed9a04517dccd2b32db8dd4855163 Mon Sep 17 00:00:00 2001 From: fda-odoo Date: Wed, 13 Dec 2017 16:07:59 +0100 Subject: [PATCH 02/17] [FIX] Payment, payment_authorize: show an error if customer doesn't have a complete profil on authorize.net payment. If the user has no Zip code, country or city, authorize refuse the payment, but Odoo doens't show any error. The commit invite the user to log in in this case or to fill his missing information --- addons/payment/static/src/js/payment_form.js | 25 +++++++++++++------ addons/payment_authorize/controllers/main.py | 25 +++++++++++++++++-- .../models/authorize_request.py | 5 ++++ 3 files changed, 46 insertions(+), 9 deletions(-) diff --git a/addons/payment/static/src/js/payment_form.js b/addons/payment/static/src/js/payment_form.js index 21a105da3fa..bf555254d5e 100644 --- a/addons/payment/static/src/js/payment_form.js +++ b/addons/payment/static/src/js/payment_form.js @@ -108,9 +108,15 @@ odoo.define('payment.payment_form', function (require) { } // if the server has returned false, we display an error else { - self.displayError( - _t('Server Error'), - _t('e.g. Your credit card details are wrong. Please verify.')); + if (data.error) { + self.displayError( + '', + data.error); + } else { // if the server doesn't provide an error message + self.displayError( + _t('Server Error'), + _t('e.g. Your credit card details are wrong. Please verify.')); + } } // here we remove the 'processing' icon from the 'add a new payment' button $(button).attr('disabled', false); @@ -265,10 +271,15 @@ odoo.define('payment.payment_form', function (require) { } // if the server has returned false, we display an error else { - self.displayError( - _t('Server Error'), - _t("

We are not able to add your payment method at the moment.

") - ); + if (data.error) { + self.displayError( + '', + data.error); + } else { // if the server doesn't provide an error message + self.displayError( + _t('Server Error'), + _t('e.g. Your credit card details are wrong. Please verify.')); + } } // here we remove the 'processing' icon from the 'add a new payment' button $(button).attr('disabled', false); diff --git a/addons/payment_authorize/controllers/main.py b/addons/payment_authorize/controllers/main.py index 6b0aae9bf6a..451a5875b6d 100644 --- a/addons/payment_authorize/controllers/main.py +++ b/addons/payment_authorize/controllers/main.py @@ -3,8 +3,9 @@ import pprint import logging from werkzeug import urls, utils -from odoo import http +from odoo import http, _ from odoo.http import request +from odoo.exceptions import ValidationError _logger = logging.getLogger(__name__) @@ -39,7 +40,27 @@ class AuthorizeController(http.Controller): @http.route(['/payment/authorize/s2s/create_json_3ds'], type='json', auth='public', csrf=False) def authorize_s2s_create_json_3ds(self, verify_validity=False, **kwargs): - token = request.env['payment.acquirer'].browse(int(kwargs.get('acquirer_id'))).s2s_process(kwargs) + token = False + acquirer = request.env['payment.acquirer'].browse(int(kwargs.get('acquirer_id'))) + + try: + token = acquirer.s2s_process(kwargs) + except ValidationError as e: + message = e.args[0] + if isinstance(message, dict) and 'missing_fields' in message: + msg = _("The transaction cannot be processed because some contact details are missing or invalid: ") + message = msg + ', '.join(message['missing_fields']) + '. ' + if request.env.user._is_public(): + message += _("Please sign in to complete your profile.") + # update message if portal mode = b2b + if request.env['ir.config_parameter'].sudo().get_param('auth_signup.allow_uninvited', 'False').lower() == 'false': + message += _("If you don't have any account, please ask your salesperson to update your profile. ") + else: + message += _("Please complete your profile. ") + + return { + 'error': message + } if not token: res = { diff --git a/addons/payment_authorize/models/authorize_request.py b/addons/payment_authorize/models/authorize_request.py index 939abcaf049..5a5f30072f1 100644 --- a/addons/payment_authorize/models/authorize_request.py +++ b/addons/payment_authorize/models/authorize_request.py @@ -139,6 +139,11 @@ class AuthorizeAPI(): etree.SubElement(payment_profile, "customerType").text = 'business' if partner.is_company else 'individual' billTo = etree.SubElement(payment_profile, "billTo") etree.SubElement(billTo, "address").text = (partner.street or '' + (partner.street2 if partner.street2 else '')) or None + + missing_fields = [partner._fields[field].string for field in ['city', 'country_id', 'zip'] if not partner[field]] + if missing_fields: + raise ValidationError({'missing_fields': missing_fields}) + etree.SubElement(billTo, "city").text = partner.city etree.SubElement(billTo, "state").text = partner.state_id.name or None etree.SubElement(billTo, "zip").text = partner.zip From 2ba6301d341400003298370b643f724eec822f06 Mon Sep 17 00:00:00 2001 From: fda-odoo Date: Wed, 13 Dec 2017 16:17:41 +0100 Subject: [PATCH 03/17] [FIX] website_sale: stop the generation of invoices if the option is unchecked in the e-commerce settings An invoice was always generated even if the option was unchecked when we the order is confirmed. --- addons/sale_payment/models/payment.py | 3 +-- .../views/sale_portal_templates.xml | 2 +- addons/website_sale/models/__init__.py | 1 + addons/website_sale/models/payment.py | 25 +++++++++++++++++++ 4 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 addons/website_sale/models/payment.py diff --git a/addons/sale_payment/models/payment.py b/addons/sale_payment/models/payment.py index 410d287b247..758bb94c112 100644 --- a/addons/sale_payment/models/payment.py +++ b/addons/sale_payment/models/payment.py @@ -66,10 +66,9 @@ class PaymentTransaction(models.Model): self.sale_order_id.with_context(send_email=True).action_confirm() if self.state == 'done': - _logger.info('<%s> transaction completed, auto-confirming order %s (ID %s) and generating invoice', self.acquirer_id.provider, self.sale_order_id.name, self.sale_order_id.id) + _logger.info('<%s> transaction completed, auto-confirming order %s (ID %s)', self.acquirer_id.provider, self.sale_order_id.name, self.sale_order_id.id) if self.sale_order_id.state in ('draft', 'sent'): self.sale_order_id.with_context(send_email=True).action_confirm() - self._generate_and_pay_invoice() elif self.state not in ['cancel', 'error'] and self.sale_order_id.state == 'draft': _logger.info('<%s> transaction pending/to confirm manually, sending quote email for order %s (ID %s)', self.acquirer_id.provider, self.sale_order_id.name, self.sale_order_id.id) self.sale_order_id.force_quotation_send() diff --git a/addons/sale_payment/views/sale_portal_templates.xml b/addons/sale_payment/views/sale_portal_templates.xml index df86ce75998..5ca7511fbec 100644 --- a/addons/sale_payment/views/sale_portal_templates.xml +++ b/addons/sale_payment/views/sale_portal_templates.xml @@ -13,7 +13,7 @@ -
+

Pay with

diff --git a/addons/website_sale/models/__init__.py b/addons/website_sale/models/__init__.py index 8cc73414b40..3a394e73151 100644 --- a/addons/website_sale/models/__init__.py +++ b/addons/website_sale/models/__init__.py @@ -7,6 +7,7 @@ from . import mail_compose_message from . import product from . import res_country from . import res_partner +from . import payment from . import sale_order from . import ir_model_fields from . import website diff --git a/addons/website_sale/models/payment.py b/addons/website_sale/models/payment.py new file mode 100644 index 00000000000..3bebb9dfff8 --- /dev/null +++ b/addons/website_sale/models/payment.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import logging + +from odoo import models + +_logger = logging.getLogger(__name__) + + +class PaymentTransaction(models.Model): + _inherit = 'payment.transaction' + + # -------------------------------------------------- + # Sale management + # -------------------------------------------------- + + def _confirm_so(self): + """Generate the invoice automatically when option is enabled from ecommerce setting""" + result = super(PaymentTransaction, self)._confirm_so() + IrConfigParameter = self.env['ir.config_parameter'].sudo() + if self.state == 'done' and IrConfigParameter.get_param('website_sale.automatic_invoice', default=False): + _logger.info('<%s> transaction completed, generating invoice for order %s (ID %s)', self.acquirer_id.provider, self.sale_order_id.name, self.sale_order_id.id) + self._generate_and_pay_invoice() + return result From f62b31caf3011e50e357db7fa6c93612fa412de5 Mon Sep 17 00:00:00 2001 From: fda-odoo Date: Mon, 18 Dec 2017 13:47:39 +0100 Subject: [PATCH 04/17] [FIX/IMP] account,account_payment,sale,sale_payment: add a payment notification message for user who are coming back in Odoo after a payment (like in online quotations & e-commerce) WARNING: To avoid breaking any configuration in stable (11.0), the template payment_confirmation_status has been replicated in each module. They must be removed before a forward-port in master --- .../views/account_portal_templates.xml | 10 ++-- .../views/account_portal_templates.xml | 40 +++++++++++++++- .../views/payment_portal_templates.xml | 26 ++++++++++ addons/sale/views/sale_portal_templates.xml | 10 ++-- .../views/sale_portal_templates.xml | 39 ++++++++++++++- addons/website_quote/controllers/main.py | 1 + .../views/website_quote_templates.xml | 47 +++++++++++++------ 7 files changed, 146 insertions(+), 27 deletions(-) diff --git a/addons/account/views/account_portal_templates.xml b/addons/account/views/account_portal_templates.xml index 2ff82b4bc3a..5d62cc9481a 100644 --- a/addons/account/views/account_portal_templates.xml +++ b/addons/account/views/account_portal_templates.xml @@ -95,7 +95,7 @@
- +
@@ -253,8 +253,8 @@ + + + + + diff --git a/addons/payment/views/payment_portal_templates.xml b/addons/payment/views/payment_portal_templates.xml index f599329e5e1..16776b880d1 100644 --- a/addons/payment/views/payment_portal_templates.xml +++ b/addons/payment/views/payment_portal_templates.xml @@ -122,5 +122,31 @@ + + diff --git a/addons/sale/views/sale_portal_templates.xml b/addons/sale/views/sale_portal_templates.xml index d38b93cb821..1d724064c7b 100644 --- a/addons/sale/views/sale_portal_templates.xml +++ b/addons/sale/views/sale_portal_templates.xml @@ -172,7 +172,7 @@
- +
Date:
@@ -335,8 +335,8 @@ + + +