From 565cb99ec02e6ec5e8480831128a57c6e0c176c2 Mon Sep 17 00:00:00 2001 From: "Lucas Perais (lpe)" Date: Wed, 17 Jul 2019 14:03:40 +0000 Subject: [PATCH 1/3] [FIX] l10n_be_intrastat: weight must be > 0.01 Fine tuning of 06d149a3b1c59594d776e418658e7b5db043a7d2 It appears that according to https://www.nbb.be/doc/dq/f_pdf_ex/nieuwsbriefintrastat_n28_2018_fr.pdf minimum weight is 0.01, and that anything below should be rounded to 0.01 OPW 2031682 closes odoo/odoo#34951 Signed-off-by: Lucas Perais (lpe) --- addons/l10n_be_intrastat/wizard/xml_decl.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/addons/l10n_be_intrastat/wizard/xml_decl.py b/addons/l10n_be_intrastat/wizard/xml_decl.py index d6ca134aa97..170f49e0d68 100644 --- a/addons/l10n_be_intrastat/wizard/xml_decl.py +++ b/addons/l10n_be_intrastat/wizard/xml_decl.py @@ -114,6 +114,11 @@ class XmlDeclaration(models.TransientModel): round_digits = self._get_rounding_digits() _round = partial(float_round, precision_digits=round_digits) + value, weight, supply_units = amounts + # Assuming weight cannot be negative + if weight >= 0 and weight < 0.01: + weight = 0.01 + self._set_Dim(item, 'EXSEQCODE', unicode(numlgn)) self._set_Dim(item, 'EXTRF', unicode(linekey.EXTRF)) self._set_Dim(item, 'EXCNT', unicode(linekey.EXCNT)) @@ -123,9 +128,9 @@ class XmlDeclaration(models.TransientModel): if extendedmode: self._set_Dim(item, 'EXTPC', unicode(linekey.EXTPC)) self._set_Dim(item, 'EXDELTRM', unicode(linekey.EXDELTRM)) - self._set_Dim(item, 'EXTXVAL', unicode(_round(amounts[0])).replace(".", ",")) - self._set_Dim(item, 'EXWEIGHT', unicode(_round(amounts[1])).replace(".", ",")) - self._set_Dim(item, 'EXUNITS', unicode(_round(amounts[2])).replace(".", ",")) + self._set_Dim(item, 'EXTXVAL', unicode(_round(value)).replace(".", ",")) + self._set_Dim(item, 'EXWEIGHT', unicode(_round(weight)).replace(".", ",")) + self._set_Dim(item, 'EXUNITS', unicode(_round(supply_units)).replace(".", ",")) def _get_intrastat_linekey(self, declcode, inv_line, dispatchmode, extendedmode): IntrastatRegion = self.env['l10n_be_intrastat.region'] From f7b6d3b0b81c447e3ec4dab244594717b703686d Mon Sep 17 00:00:00 2001 From: Damien Bouvy Date: Mon, 8 Jul 2019 15:47:32 +0000 Subject: [PATCH 2/3] [FIX] payment_authorize: md5 to sha512 compat This commit extends the changes introduced by 88de93114 to adapt Odoo payment flows to the switch in transaction signature done by Authorize.net. The initial fix was not sufficient for flows that mixed redirection payment flows and server-to-server flows (e.g. paying a quote with a card that gets saved then using the token to pay for a subscription). The problem comes from the fact that the server-to-server API uses the API Transaction Key and API Login ID as credentials to authenticate requests; there is no need for a signature since this data is never publicly exposed on the website and a MITM is mitigated by the fact that it would need to be done between the Odoo server and the Authorize.net servers (both of which use https in a normal deployment) which is admitedly more complex than doing a MITM on a Starbucks wifi. On the other hand, the 'redirection' flow will include all transaction parameters as inputs in an html form, therefore the signature is required to ensure that the values have not been modified by a website user or a mitm. Since both flows can coexist on the same configuration, we cannot use the same field depending on the payment flow configuration - we need both fields to be stored for the provider. This commit therefore has to introduce new fields on payment.acquirer record that can store the signature key for authorize in addition to the usual authorize fields. Instead of adding a new module, this commit uses non-stored computed fields that will generate System Parameters entries for any acquirer of the 'authorize' kind when set through the interface. closes odoo/odoo#34670 Signed-off-by: Damien Bouvy (dbo) --- addons/payment_authorize/models/payment.py | 29 +++++++++++++++++-- .../payment_authorize/views/payment_views.xml | 1 + 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/addons/payment_authorize/models/payment.py b/addons/payment_authorize/models/payment.py index f77504d738e..0e97d630c69 100644 --- a/addons/payment_authorize/models/payment.py +++ b/addons/payment_authorize/models/payment.py @@ -24,6 +24,7 @@ class PaymentAcquirerAuthorize(models.Model): provider = fields.Selection(selection_add=[('authorize', 'Authorize.Net')]) authorize_login = fields.Char(string='API Login Id', required_if_provider='authorize', groups='base.group_user') authorize_transaction_key = fields.Char(string='API Transaction Key', required_if_provider='authorize', groups='base.group_user') + authorize_signature_key = fields.Char(string='API Signature Key', groups='base.group_user', compute="_compute_auth_signature_key", inverse="_inverse_auth_signature_key") def _get_feature_support(self): """Get advanced feature support by provider. @@ -41,6 +42,16 @@ class PaymentAcquirerAuthorize(models.Model): res['tokenize'].append('authorize') return res + def _compute_auth_signature_key(self): + ICP = self.env['ir.config_parameter'].sudo() + for acquirer in self.filtered(lambda a: a.provider == 'authorize'): + acquirer.authorize_signature_key = ICP.get_param('payment_authorize.signature_key_%s' % acquirer.id) + + def _inverse_auth_signature_key(self): + ICP = self.env['ir.config_parameter'].sudo() + for acquirer in self.filtered(lambda a: a.provider == 'authorize'): + ICP.set_param('payment_authorize.signature_key_%s' % acquirer.id, acquirer.authorize_signature_key) + def _get_authorize_urls(self, environment): """ Authorize URLs """ if environment == 'prod': @@ -56,12 +67,24 @@ class PaymentAcquirerAuthorize(models.Model): values['x_amount'], values['x_currency_code']]) - # [BACKWARD COMPATIBILITY] Check that the merchant did update his transaction - # key to signature key (end of MD5 support from Authorize.net) + # [BACKWARD COMPATIBILITY, 2nd edition] # The signature key is now '128-character hexadecimal format', while the # transaction key was only 16-character. - if len(values['x_trans_key']) == 128: + # One of 2 things should have happened: + # 1/ the Transaction Key has been replaced with the Signature Key value (patch from March 2019) + # => Use that to sign, but server-to-server won't work since it uses transaction key + # as its credentials + # 2/ the Signature key is a new field (patch from July 2019) + # => Use that field for the signature + + # FORWARD-PORT NOTE: be careful, hexadecimal decoding in python 3 is done by using bytes.fromhex(str) + # (P2) self.authorize_signature_key.decode("hex") ==> (P3) bytes.fromhex(self.authorize_signature_key) + # FORWARD-PORT NOTE NUMERO DOS: forward part to saas-12.4 but no further + if len(values['x_trans_key']) == 128 and not self.authorize_signature_key: + self.authorize_signature_key = values['x_trans_key'] # store in the correct field return hmac.new(values['x_trans_key'].decode("hex"), data, hashlib.sha512).hexdigest().upper() + elif self.authorize_signature_key: + return hmac.new(self.authorize_signature_key.decode("hex"), data, hashlib.sha512).hexdigest().upper() else: return hmac.new(str(values['x_trans_key']), data, hashlib.md5).hexdigest() diff --git a/addons/payment_authorize/views/payment_views.xml b/addons/payment_authorize/views/payment_views.xml index 7a957e41442..b3f3ed8e535 100644 --- a/addons/payment_authorize/views/payment_views.xml +++ b/addons/payment_authorize/views/payment_views.xml @@ -9,6 +9,7 @@ + How to get paid with Authorize.Net From 36fdb2ff499136b8f5a6c1c1d842251c5e6e2c5b Mon Sep 17 00:00:00 2001 From: Romain Derie Date: Tue, 9 Jul 2019 18:09:10 +0200 Subject: [PATCH 3/3] [FIX] website_sale: restore possibility to remove promo code pricelist Since 5c9cea4ee7e, it was not possible to remove an applied promo code pricelist by adding an empty promo code on checkout (eg removing the one shown in the promo code input). Indeed, when sending an empty promo code, the `search()` done in the controller would not find any pricelist as promo would be en empty string. For the rest, check code on mentionned commit. See `sale_get_order()` method docstring about `code` param: "If empty, it's a special case to reset the pricelist with the first available else the default.". Fixes #34633 --- addons/website_sale/controllers/main.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/addons/website_sale/controllers/main.py b/addons/website_sale/controllers/main.py index 74f779667f1..d58b2447829 100644 --- a/addons/website_sale/controllers/main.py +++ b/addons/website_sale/controllers/main.py @@ -332,9 +332,11 @@ class WebsiteSale(http.Controller): @http.route(['/shop/pricelist'], type='http', auth="public", website=True) def pricelist(self, promo, **post): redirect = post.get('r', '/shop/cart') - pricelist = request.env['product.pricelist'].sudo().search([('code', '=', promo)], limit=1) - if not pricelist or (pricelist and not request.website.is_pricelist_available(pricelist.id)): - return request.redirect("%s?code_not_available=1" % redirect) + # empty promo code is used to reset/remove pricelist (see `sale_get_order()`) + if promo: + pricelist = request.env['product.pricelist'].sudo().search([('code', '=', promo)], limit=1) + if (not pricelist or (pricelist and not request.website.is_pricelist_available(pricelist.id))): + return request.redirect("%s?code_not_available=1" % redirect) request.website.sale_get_order(code=promo) return request.redirect(redirect)