diff --git a/addons/payment_odoo/__init__.py b/addons/payment_odoo/__init__.py
new file mode 100644
index 00000000000..9fe1f558a00
--- /dev/null
+++ b/addons/payment_odoo/__init__.py
@@ -0,0 +1,11 @@
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import controllers
+from . import models
+
+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, 'odoo')
diff --git a/addons/payment_odoo_by_adyen/__manifest__.py b/addons/payment_odoo/__manifest__.py
similarity index 52%
rename from addons/payment_odoo_by_adyen/__manifest__.py
rename to addons/payment_odoo/__manifest__.py
index b3d7b76a9ea..299c15f287d 100644
--- a/addons/payment_odoo_by_adyen/__manifest__.py
+++ b/addons/payment_odoo/__manifest__.py
@@ -1,19 +1,19 @@
-# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
- 'name': 'Odoo Payments by Adyen Payment Acquirer',
+ 'name': 'Odoo Payments Payment Acquirer',
+ 'version': '2.0',
'category': 'Accounting/Payment Acquirers',
'sequence': 330,
- 'summary': 'Payment Acquirer: Odoo Payments by Adyen',
- 'version': '1.0',
- 'description': """Odoo Payments by Adyen""",
+ 'summary': 'Payment Acquirer: Odoo Payments',
+ 'description': """Odoo Payments""",
'depends': ['payment', 'adyen_platforms'],
'data': [
'views/payment_views.xml',
- 'views/payment_odoo_by_adyen_templates.xml',
+ 'views/payment_odoo_templates.xml',
'data/payment_acquirer_data.xml',
],
- 'installable': True,
'application': True,
+ 'post_init_hook': 'create_missing_journals',
+ 'uninstall_hook': 'uninstall_hook',
}
diff --git a/addons/payment_odoo/const.py b/addons/payment_odoo/const.py
new file mode 100644
index 00000000000..73db0f01799
--- /dev/null
+++ b/addons/payment_odoo/const.py
@@ -0,0 +1,22 @@
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+# Adyen-specific mapping of currency codes in ISO 4217 format to the number of decimals.
+# Only currencies for which Adyen does not follow the ISO 4217 norm are listed here.
+# See https://docs.adyen.com/development-resources/currency-codes
+CURRENCY_DECIMALS = {
+ 'CLP': 2,
+ 'CVE': 0,
+ 'IDR': 0,
+ 'ISK': 2,
+}
+
+# Mapping of transaction states to Adyen result codes.
+# See https://docs.adyen.com/checkout/payment-result-codes for the exhaustive list of result codes.
+RESULT_CODES_MAPPING = {
+ 'pending': (
+ 'ChallengeShopper', 'IdentifyShopper', 'Pending', 'PresentToShopper', 'Received',
+ 'RedirectShopper'
+ ),
+ 'done': ('Authorised',),
+ 'cancel': ('Cancelled',),
+}
diff --git a/addons/payment_odoo_by_adyen/controllers/__init__.py b/addons/payment_odoo/controllers/__init__.py
similarity index 79%
rename from addons/payment_odoo_by_adyen/controllers/__init__.py
rename to addons/payment_odoo/controllers/__init__.py
index 5d4b25db9c0..80ee4da1c5e 100644
--- a/addons/payment_odoo_by_adyen/controllers/__init__.py
+++ b/addons/payment_odoo/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_odoo/controllers/main.py b/addons/payment_odoo/controllers/main.py
new file mode 100644
index 00000000000..063eda29fab
--- /dev/null
+++ b/addons/payment_odoo/controllers/main.py
@@ -0,0 +1,81 @@
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import json
+import logging
+import pprint
+
+from odoo import http
+from odoo.http import request
+
+from odoo.addons.payment import utils as payment_utils
+from odoo.addons.payment_odoo.const import CURRENCY_DECIMALS
+
+_logger = logging.getLogger(__name__)
+
+
+class OdooController(http.Controller):
+ _notification_url = '/payment/odoo/notification'
+
+ @http.route(_notification_url, type='json', auth='public')
+ def odoo_notification(self):
+ """ Process the data sent by Adyen to the webhook based on the event code.
+
+ See https://docs.adyen.com/development-resources/webhooks/understand-notifications for the
+ exhaustive list of event codes.
+
+ :return: None
+ """
+ # Payload data represent a single notification's data. Because two notifications of a same
+ # batch can be related to different sub-merchants, the proxy splits the batches and send
+ # individual notifications one by one to this endpoint.
+ notification_data = json.loads(request.httprequest.data)
+
+ # Check the source and integrity of the notification
+ received_signature = notification_data.get('additionalData', {}).get(
+ 'metadata.merchant_signature'
+ )
+ tx_sudo = request.env['payment.transaction'].sudo()._get_tx_from_feedback_data(
+ 'odoo', notification_data
+ )
+ if not self._verify_notification_signature(received_signature, tx_sudo):
+ return
+
+ _logger.info("notification received:\n%s", pprint.pformat(notification_data))
+ if notification_data['success'] != 'true':
+ return # Don't handle failed events
+
+ # Reshape the notification data for parsing
+ event_code = notification_data['eventCode']
+ if event_code == 'AUTHORISATION':
+ notification_data['resultCode'] = 'Authorised'
+ elif event_code == 'CANCELLATION':
+ notification_data['resultCode'] = 'Cancelled'
+ else:
+ return # Don't handle unsupported event codes
+
+ # Handle the notification data as a regular feedback
+ request.env['payment.transaction'].sudo()._handle_feedback_data('odoo', notification_data)
+
+ def _verify_notification_signature(self, received_signature, tx):
+ """ Check that the signature computed from the transaction values matches the received one.
+
+ :param str received_signature: The signature sent with the notification
+ :param recordset tx: The transaction of the notification, as a `payment.transaction` record
+ :return: Whether the signatures match
+ :rtype: str
+ """
+
+ if not received_signature:
+ _logger.warning("ignored notification with missing signature")
+ return False
+
+ converted_amount = payment_utils.to_minor_currency_units(
+ tx.amount, tx.currency_id, CURRENCY_DECIMALS.get(tx.currency_id.name)
+ )
+ if not payment_utils.check_access_token(
+ received_signature, converted_amount, tx.currency_id.name, tx.reference
+ ):
+ _logger.warning("ignored notification with invalid signature")
+ return False
+
+ return True
diff --git a/addons/payment_odoo/data/payment_acquirer_data.xml b/addons/payment_odoo/data/payment_acquirer_data.xml
new file mode 100644
index 00000000000..ac585037e32
--- /dev/null
+++ b/addons/payment_odoo/data/payment_acquirer_data.xml
@@ -0,0 +1,13 @@
+
+
+
+
+ odoo
+
+ False
+ False
+ True
+ True
+
+
+
diff --git a/addons/payment_odoo_by_adyen/i18n/payment_odoo_by_adyen.pot b/addons/payment_odoo/i18n/payment_odoo_by_adyen.pot
similarity index 100%
rename from addons/payment_odoo_by_adyen/i18n/payment_odoo_by_adyen.pot
rename to addons/payment_odoo/i18n/payment_odoo_by_adyen.pot
diff --git a/addons/payment_odoo/models/__init__.py b/addons/payment_odoo/models/__init__.py
new file mode 100644
index 00000000000..22eb69117db
--- /dev/null
+++ b/addons/payment_odoo/models/__init__.py
@@ -0,0 +1,5 @@
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import payment_acquirer
+from . import payment_token
+from . import payment_transaction
diff --git a/addons/payment_odoo/models/payment_acquirer.py b/addons/payment_odoo/models/payment_acquirer.py
new file mode 100644
index 00000000000..57dd6e68522
--- /dev/null
+++ b/addons/payment_odoo/models/payment_acquirer.py
@@ -0,0 +1,42 @@
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from werkzeug import urls
+
+from odoo import _, api, fields, models
+from odoo.exceptions import ValidationError
+
+
+class PaymentAcquirer(models.Model):
+ _inherit = 'payment.acquirer'
+
+ provider = fields.Selection(
+ selection_add=[('odoo', "Odoo Payments")], ondelete={'odoo': 'set default'})
+ odoo_adyen_account_id = fields.Many2one(
+ related='company_id.adyen_account_id', required_if_provider='odoo')
+ odoo_adyen_payout_id = fields.Many2one(
+ string="Adyen Payout", comodel_name='adyen.payout', required_if_provider='odoo',
+ domain='[("adyen_account_id", "=", odoo_adyen_account_id)]')
+
+ @api.constrains('provider', 'state')
+ def _check_state_is_not_test(self):
+ if any(a.provider == 'odoo' and a.state == 'test' for a in self):
+ raise ValidationError(_("Odoo Payments is not available in test mode."))
+
+ def odoo_create_adyen_account(self):
+ return self.env['adyen.account'].action_create_redirect()
+
+ def _odoo_get_api_url(self):
+ self.ensure_one()
+ proxy_url = self.env['ir.config_parameter'].sudo().get_param('adyen_platforms.proxy_url')
+ return urls.url_join(proxy_url, 'pay_by_link')
+
+ def _odoo_compute_shopper_reference(self, partner_id):
+ """ Compute a unique reference of the partner for Adyen.
+
+ This is used for the `shopperReference` field in communications with Adyen.
+
+ :param recordset partner_id: The partner making the transaction, as a `res.partner` id
+ :return: The unique reference for the partner
+ :rtype: str
+ """
+ return f'{self.odoo_adyen_account_id.adyen_uuid}_{partner_id}'
diff --git a/addons/payment_odoo/models/payment_token.py b/addons/payment_odoo/models/payment_token.py
new file mode 100644
index 00000000000..d8c5a13b558
--- /dev/null
+++ b/addons/payment_odoo/models/payment_token.py
@@ -0,0 +1,9 @@
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import fields, models
+
+
+class PaymentToken(models.Model):
+ _inherit = 'payment.token'
+
+ odoo_payment_method_type = fields.Char(string="Payment Method Type")
diff --git a/addons/payment_odoo/models/payment_transaction.py b/addons/payment_odoo/models/payment_transaction.py
new file mode 100644
index 00000000000..36ba4ed44df
--- /dev/null
+++ b/addons/payment_odoo/models/payment_transaction.py
@@ -0,0 +1,231 @@
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import json
+import logging
+import pprint
+
+from werkzeug import urls
+
+from odoo import _, api, models
+from odoo.exceptions import UserError, ValidationError
+
+from odoo.addons.payment import utils as payment_utils
+from odoo.addons.payment_odoo.const import CURRENCY_DECIMALS, RESULT_CODES_MAPPING
+from odoo.addons.payment_odoo.controllers.main import OdooController
+
+_logger = logging.getLogger(__name__)
+
+
+class PaymentTransaction(models.Model):
+ _inherit = 'payment.transaction'
+
+ def _get_specific_rendering_values(self, processing_values):
+ """ Override of payment to return Odoo-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 != 'odoo':
+ return res
+
+ converted_amount = payment_utils.to_minor_currency_units(
+ self.amount, self.currency_id, CURRENCY_DECIMALS.get(self.currency_id.name)
+ )
+ # The lang is taken from the context rather than from the partner because it is not required
+ # to be logged to make a payment and because the lang is not always set on the partner.
+ # Adyen only supports a reduced set of languages but, instead of looking for the closest
+ # match in https://docs.adyen.com/checkout/components-web/localization-components, we simply
+ # provide the lang string as is (after adapting the format) and let Adyen find the best fit.
+ lang_code = (self._context.get('lang') or 'en-US').replace('_', '-')
+ base_url = self.acquirer_id._get_base_url()
+ signature = payment_utils.generate_access_token(
+ converted_amount, self.currency_id.name, self.reference
+ )
+ data = {
+ 'adyen_uuid': self.acquirer_id.odoo_adyen_account_id.adyen_uuid,
+ 'payout': self.acquirer_id.odoo_adyen_payout_id.code,
+ 'amount': {
+ 'value': converted_amount,
+ 'currency': self.currency_id.name,
+ },
+ 'reference': self.reference,
+ 'shopperLocale': lang_code,
+ 'shopperReference': self.acquirer_id._odoo_compute_shopper_reference(
+ self.partner_id.id
+ ),
+ 'recurringProcessingModel': 'CardOnFile',
+ 'storePaymentMethod': self.tokenize, # True by default on Adyen side
+ # Since the Pay by Link API redirects the customer without any payload, we use the
+ # /payment/status route directly as return url.
+ 'returnUrl': urls.url_join(base_url, '/payment/status'),
+ 'metadata': {
+ 'merchant_signature': signature,
+ 'notification_url': urls.url_join(base_url, OdooController._notification_url),
+ }, # Proxy-specific data
+ }
+ return {
+ 'data': json.dumps(data),
+ 'api_url': self.acquirer_id._odoo_get_api_url(),
+ }
+
+ def _send_payment_request(self):
+ """ Override of payment to send a payment request to Adyen through the Odoo proxy.
+
+ Note: self.ensure_one()
+
+ :return: None
+ :raise: UserError if the transaction is not linked to a token
+ """
+ super()._send_payment_request()
+ if self.provider != 'odoo':
+ return
+
+ # Make the payment request
+ if not self.token_id:
+ raise UserError("Odoo Payments: " + _("The transaction is not linked to a token."))
+
+ converted_amount = payment_utils.to_minor_currency_units(
+ self.amount, self.currency_id, CURRENCY_DECIMALS.get(self.currency_id.name)
+ )
+ base_url = self.acquirer_id._get_base_url()
+ signature = payment_utils.generate_access_token(
+ converted_amount, self.currency_id.name, self.reference
+ )
+ data = {
+ 'payout': self.acquirer_id.odoo_adyen_payout_id.code,
+ 'amount': {
+ 'value': converted_amount,
+ 'currency': self.currency_id.name,
+ },
+ 'reference': self.reference,
+ 'paymentMethod': {
+ 'type': self.token_id.odoo_payment_method_type,
+ 'storedPaymentMethodId': self.token_id.acquirer_ref,
+ },
+ 'shopperReference': self.acquirer_id._odoo_compute_shopper_reference(
+ self.partner_id.id
+ ),
+ 'recurringProcessingModel': 'Subscription',
+ 'shopperInteraction': 'ContAuth',
+ 'metadata': {
+ 'merchant_signature': signature,
+ 'notification_url': urls.url_join(base_url, OdooController._notification_url),
+ }, # Proxy-specific data
+ }
+ response_content = self.acquirer_id.odoo_adyen_account_id._adyen_rpc('payments', data)
+
+ # Handle the payment request response
+ _logger.info("payment request response:\n%s", pprint.pformat(response_content))
+ self._handle_feedback_data('odoo', response_content)
+
+ @api.model
+ def _get_tx_from_feedback_data(self, provider, data):
+ """ Override of payment to find the transaction based on Adyen 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
+ """
+ tx = super()._get_tx_from_feedback_data(provider, data)
+ if provider != 'odoo':
+ return tx
+
+ reference = data.get('merchantReference')
+ if not reference:
+ raise ValidationError(
+ "Odoo Payments: " + _("Received data with missing merchant reference")
+ )
+
+ tx = self.search([('reference', '=', reference), ('provider', '=', 'odoo')])
+ if not tx:
+ raise ValidationError(
+ "Odoo Payments: " + _("No transaction found matching reference %s.", reference)
+ )
+ return tx
+
+ def _process_feedback_data(self, data):
+ """ Override of payment to process the transaction based on Adyen data.
+
+ Since only webhook notifications send data, the parsing is exclusively done according to the
+ structure of NotificationRequestItem object.
+ See https://docs.adyen.com/development-resources/webhooks/notifications-api
+
+ Note: self.ensure_one()
+
+ :param dict data: The feedback data sent by the provider
+ :return: None
+ :raise: ValidationError if inconsistent data were received
+ """
+ super()._process_feedback_data(data)
+ if self.provider != 'odoo':
+ return
+
+ # Handle the acquirer reference
+ if 'pspReference' in data:
+ self.acquirer_reference = data.get('pspReference')
+
+ # Handle the payment state
+ payment_state = data.get('resultCode')
+ if not payment_state:
+ raise ValidationError(
+ "Odoo Payments: " + _("Received data with missing payment state.")
+ )
+
+ if payment_state in RESULT_CODES_MAPPING['pending']:
+ self._set_pending()
+ elif payment_state in RESULT_CODES_MAPPING['done']:
+ has_token_data = 'recurring.recurringDetailReference' in data.get('additionalData', {})
+ if self.tokenize and has_token_data:
+ self._odoo_tokenize_from_feedback_data(data)
+ self._set_done()
+ elif payment_state in RESULT_CODES_MAPPING['cancel']:
+ self._set_canceled()
+ else: # Classify unsupported payment state as `error` tx state
+ _logger.info("received data with invalid payment state: %s", payment_state)
+ self._set_error(
+ "Odoo Payments: " + _("Received data with invalid payment state: %s", payment_state)
+ )
+
+ def _odoo_tokenize_from_feedback_data(self, data):
+ """ Create a new token based on the feedback data.
+
+ Note: self.ensure_one()
+
+ :param dict data: The feedback data sent by the provider
+ :return: None
+ """
+ self.ensure_one()
+
+ # Retrieve all stored payment methods for the customer from the API and match them with the
+ # acquirer reference of the transaction to find its payment method
+ response_content = self.acquirer_id.odoo_adyen_account_id._adyen_rpc(
+ 'payment_methods',
+ dict(shopperReference=data['additionalData']['recurring.shopperReference']),
+ )
+ payment_methods = response_content['storedPaymentMethods']
+ acquirer_ref = data['additionalData']['recurring.recurringDetailReference']
+ payment_method_type = next(pm['type'] for pm in payment_methods if pm['id'] == acquirer_ref)
+
+ # Create the token
+ token = self.env['payment.token'].create({
+ 'acquirer_id': self.acquirer_id.id,
+ 'name': payment_utils.build_token_name(data['additionalData'].get('cardSummary')),
+ 'partner_id': self.partner_id.id,
+ 'acquirer_ref': acquirer_ref,
+ 'verified': True, # The payment is authorized, so the payment method is valid
+ 'odoo_payment_method_type': payment_method_type,
+ })
+ self.write({
+ 'token_id': token,
+ 'tokenize': False,
+ })
+ _logger.info(
+ "created token with id %s for partner with id %s", token.id, self.partner_id.id
+ )
diff --git a/addons/payment_odoo_by_adyen/static/description/icon.png b/addons/payment_odoo/static/description/icon.png
similarity index 100%
rename from addons/payment_odoo_by_adyen/static/description/icon.png
rename to addons/payment_odoo/static/description/icon.png
diff --git a/addons/payment_odoo_by_adyen/static/description/icon.svg b/addons/payment_odoo/static/description/icon.svg
similarity index 100%
rename from addons/payment_odoo_by_adyen/static/description/icon.svg
rename to addons/payment_odoo/static/description/icon.svg
diff --git a/addons/payment_odoo_by_adyen/static/src/img/odoo_icon.png b/addons/payment_odoo/static/src/img/odoo_icon.png
similarity index 100%
rename from addons/payment_odoo_by_adyen/static/src/img/odoo_icon.png
rename to addons/payment_odoo/static/src/img/odoo_icon.png
diff --git a/addons/payment_odoo/views/payment_odoo_templates.xml b/addons/payment_odoo/views/payment_odoo_templates.xml
new file mode 100644
index 00000000000..c9ae5def1dd
--- /dev/null
+++ b/addons/payment_odoo/views/payment_odoo_templates.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
diff --git a/addons/payment_odoo/views/payment_views.xml b/addons/payment_odoo/views/payment_views.xml
new file mode 100644
index 00000000000..4bbc4184ea9
--- /dev/null
+++ b/addons/payment_odoo/views/payment_views.xml
@@ -0,0 +1,27 @@
+
+
+
+
+ Odoo Payments Acquirer Form
+ payment.acquirer
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/payment_odoo_by_adyen/__init__.py b/addons/payment_odoo_by_adyen/__init__.py
deleted file mode 100644
index 7d34c7c054a..00000000000
--- a/addons/payment_odoo_by_adyen/__init__.py
+++ /dev/null
@@ -1,5 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-from . import controllers
-from . import models
diff --git a/addons/payment_odoo_by_adyen/controllers/main.py b/addons/payment_odoo_by_adyen/controllers/main.py
deleted file mode 100644
index cb2182e81b8..00000000000
--- a/addons/payment_odoo_by_adyen/controllers/main.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-import json
-import logging
-import pprint
-
-from odoo import http
-from odoo.http import request
-
-_logger = logging.getLogger(__name__)
-
-
-class OdooByAdyenController(http.Controller):
- _notification_url = '/payment/odoo_adyen/notification'
-
- @http.route('/payment/odoo_adyen/notification', type='json', auth='public', csrf=False)
- def odoo_adyen_notification(self):
- data = json.loads(request.httprequest.data)
- _logger.info('Beginning Odoo by Adyen form_feedback with data %s', pprint.pformat(data))
- if data.get('authResult') not in ['CANCELLED']:
- request.env['payment.transaction'].sudo().form_feedback(data, 'odoo_adyen')
diff --git a/addons/payment_odoo_by_adyen/data/payment_acquirer_data.xml b/addons/payment_odoo_by_adyen/data/payment_acquirer_data.xml
deleted file mode 100644
index f6572c102c6..00000000000
--- a/addons/payment_odoo_by_adyen/data/payment_acquirer_data.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
- Odoo Payments by Adyen
-
- odoo_adyen
-
-
-
-
-
-
diff --git a/addons/payment_odoo_by_adyen/models/__init__.py b/addons/payment_odoo_by_adyen/models/__init__.py
deleted file mode 100644
index 2ec5b9cd2f6..00000000000
--- a/addons/payment_odoo_by_adyen/models/__init__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-from . import payment
diff --git a/addons/payment_odoo_by_adyen/models/payment.py b/addons/payment_odoo_by_adyen/models/payment.py
deleted file mode 100644
index cf44d248400..00000000000
--- a/addons/payment_odoo_by_adyen/models/payment.py
+++ /dev/null
@@ -1,185 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-import hashlib
-import hmac
-import json
-import logging
-from werkzeug import urls
-
-from odoo import _, api, fields, models
-from odoo.exceptions import ValidationError
-from odoo.addons.payment_odoo_by_adyen.controllers.main import OdooByAdyenController
-
-_logger = logging.getLogger(__name__)
-
-
-class AcquirerOdooByAdyen(models.Model):
- _inherit = 'payment.acquirer'
-
- provider = fields.Selection(selection_add=[
- ('odoo_adyen', 'Odoo Payments by Adyen')
- ], ondelete={'odoo_adyen': 'set default'})
- odoo_adyen_account_id = fields.Many2one('adyen.account', required_if_provider='odoo_adyen', related='company_id.adyen_account_id')
- odoo_adyen_payout_id = fields.Many2one('adyen.payout', required_if_provider='odoo_adyen', string='Adyen Payout', domain="[('adyen_account_id', '=', odoo_adyen_account_id)]")
-
- @api.constrains('provider', 'state')
- def _check_odoo_adyen_test(self):
- for payment_acquirer in self:
- if payment_acquirer.provider == 'odoo_adyen' and payment_acquirer.state == 'test':
- raise ValidationError(_('Odoo Payments by Adyen is not available in test mode.'))
-
- def _get_feature_support(self):
- res = super(AcquirerOdooByAdyen, self)._get_feature_support()
- res['tokenize'].append('odoo_adyen')
- return res
-
- @api.model
- def _odoo_adyen_format_amount(self, amount, currency_id):
- return {
- 'value': int(amount * (10 ** currency_id.decimal_places)),
- 'currency': currency_id.name,
- }
-
- @api.model
- def _odoo_adyen_compute_signature(self, amount, currency_id, reference):
- secret = self.env['ir.config_parameter'].sudo().get_param('database.secret')
- token_str = '%s%s%s' % (
- int(amount * (10 ** currency_id.decimal_places)),
- currency_id.name,
- reference
- )
- return hmac.new(secret.encode('utf-8'), token_str.encode('utf-8'), hashlib.sha256).hexdigest()
-
- def odoo_adyen_form_generate_values(self, values):
- # Don't use the value returned by `self.get_base_url` for the notification_url as
- # `request.httprequest.url_root` could be forged to retrieve the signature and
- # fake a payment update
- base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
- data = {
- 'adyen_uuid': self.odoo_adyen_account_id.adyen_uuid,
- 'payout': self.odoo_adyen_payout_id.code,
- 'amount': self._odoo_adyen_format_amount(values['amount'], values['currency']),
- 'reference': values['reference'],
- 'shopperLocale': values.get('partner_lang'),
- 'metadata': {
- 'merchant_signature': self._odoo_adyen_compute_signature(values['amount'],values['currency'],values['reference']),
- 'notification_url': urls.url_join(base_url, OdooByAdyenController._notification_url),
- },
- 'returnUrl': urls.url_join(self.get_base_url(), '/payment/process'),
- }
-
- if self.save_token in ['ask', 'always']:
- data.update({
- 'shopperReference': '%s_%s' % (self.odoo_adyen_account_id.adyen_uuid, values['partner_id']),
- 'storePaymentMethod': True,
- 'recurringProcessingModel': 'CardOnFile',
- })
-
- values.update({
- 'data': json.dumps(data),
- })
- return values
-
- def odoo_adyen_get_form_action_url(self):
- self.ensure_one()
- proxy_url = self.env['ir.config_parameter'].sudo().get_param('adyen_platforms.proxy_url')
- return urls.url_join(proxy_url, 'pay_by_link')
-
- def odoo_adyen_create_account(self):
- return self.env['adyen.account'].action_create_redirect()
-
-class TxOdooByAdyen(models.Model):
- _inherit = 'payment.transaction'
-
- def odoo_adyen_s2s_do_transaction(self, **kwargs):
- self.ensure_one()
- # Don't use the value returned by `self.get_base_url` for the notification_url as
- # `request.httprequest.url_root` could be forged to retrieve the signature and
- # fake a payment update
- base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
- data = {
- 'payout': self.acquirer_id.odoo_adyen_payout_id.code,
- 'amount': self.acquirer_id._odoo_adyen_format_amount(self.amount, self.currency_id),
- 'reference': self.reference,
- 'paymentMethod': {
- 'type': self.payment_token_id.odoo_adyen_payment_method_type,
- 'storedPaymentMethodId': self.payment_token_id.acquirer_ref,
- },
- 'shopperReference': '%s_%s' % (self.acquirer_id.odoo_adyen_account_id.adyen_uuid, self.partner_id.id),
- 'shopperInteraction': 'ContAuth',
- 'metadata': {
- 'merchant_signature': self.acquirer_id._odoo_adyen_compute_signature(self.amount, self.currency_id, self.reference),
- 'notification_url': urls.url_join(base_url, OdooByAdyenController._notification_url),
- },
- 'returnUrl': urls.url_join(self.get_base_url(), '/payment/process'),
- }
- self.acquirer_id.odoo_adyen_account_id._adyen_rpc('payments', data)
-
- @api.model
- def _odoo_adyen_form_get_tx_from_data(self, data):
- reference = data.get('merchantReference')
- if not reference:
- error_msg = _('Odoo Payments by Adyen: received data with missing reference (%s)', reference)
- _logger.info(error_msg)
- raise ValidationError(error_msg)
-
- tx = self.env['payment.transaction'].search([('reference', '=', reference)])
- if not tx or len(tx) > 1:
- error_msg = _('Odoo Payments by Adyen: received data for reference %s') % (reference)
- if not tx:
- error_msg += _('; no order found')
- else:
- error_msg += _('; multiple order found')
- _logger.info(error_msg)
- raise ValidationError(error_msg)
-
- return tx
-
- def _odoo_adyen_form_get_invalid_parameters(self, data):
- invalid_parameters = []
-
- if self.acquirer_reference and data.get('pspReference') != self.acquirer_reference:
- invalid_parameters.append(('pspReference', data.get('pspReference'), self.acquirer_reference))
-
- return invalid_parameters
-
- def _odoo_adyen_form_validate(self, data):
- merchant_signature = self.acquirer_id._odoo_adyen_compute_signature(self.amount, self.currency_id, self.reference)
- if merchant_signature != data['additionalData']['metadata.merchant_signature']:
- return False
-
- # Save token
- if self.partner_id and not self.payment_token_id and \
- (self.type == 'form_save' or self.acquirer_id.save_token == 'always') \
- and 'recurring.shopperReference' in data['additionalData']:
- res = self.acquirer_id.odoo_adyen_account_id._adyen_rpc('payment_methods', {
- 'shopperReference': data['additionalData']['recurring.shopperReference']
- })
- stored_payment_methods = res['storedPaymentMethods']
- pm_id = data['additionalData']['recurring.recurringDetailReference']
- token_id = self.env['payment.token'].create({
- 'name': _("Card No XXXXXXXXXXXX%s", data['additionalData']['cardSummary']),
- 'acquirer_ref': pm_id,
- 'acquirer_id': self.acquirer_id.id,
- 'partner_id': self.partner_id.id,
- 'odoo_adyen_payment_method_type': next(pm['type'] for pm in stored_payment_methods if pm['id'] == pm_id)
- })
- self.payment_token_id = token_id
-
- # Update status
- if data['success']:
- self.write({'acquirer_reference': data.get('pspReference')})
- self._set_transaction_done()
- return True
- else:
- error = _('Odoo Payment by Adyen: feedback error')
- _logger.info(error)
- self.write({'state_message': error})
- self._set_transaction_cancel()
- return False
-
-class PaymentToken(models.Model):
- _inherit = 'payment.token'
-
- odoo_adyen_payment_method_type = fields.Char(string='PaymentMethod Type')
diff --git a/addons/payment_odoo_by_adyen/views/payment_odoo_by_adyen_templates.xml b/addons/payment_odoo_by_adyen/views/payment_odoo_by_adyen_templates.xml
deleted file mode 100644
index 1aaa50f40ba..00000000000
--- a/addons/payment_odoo_by_adyen/views/payment_odoo_by_adyen_templates.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/addons/payment_odoo_by_adyen/views/payment_views.xml b/addons/payment_odoo_by_adyen/views/payment_views.xml
deleted file mode 100644
index 715aefac291..00000000000
--- a/addons/payment_odoo_by_adyen/views/payment_views.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
- acquirer.form.odoo.by.adyen
- payment.acquirer
-
-
-
-
-
-
-
-
-
-
-
-
-
-