[REF] payment_odoo: migrate Odoo Payment to the new payment API

This commit also renames the module `payment_odoo_by_adyen` to
`payment_odoo` as well as the referring strings ("Odoo Payments" instead
of "Odoo Payments by Adyen", ...).
A dedicated [MOV] commit is not used because neither the [MOV] commit
nor the adapted [REF] would be valid on its own.

See the merge commit for more details.

task-2390665
This commit is contained in:
Antoine Vandevenne (anv)
2021-03-30 09:25:51 +02:00
parent 356c93a42d
commit 471a1f39f5
23 changed files with 458 additions and 269 deletions
+11
View File
@@ -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')
@@ -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',
}
+22
View File
@@ -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',),
}
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import main
+81
View File
@@ -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
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="1">
<record id="payment.payment_acquirer_odoo" model="payment.acquirer">
<field name="provider">odoo</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">True</field>
<field name="allow_tokenization">True</field>
</record>
</odoo>
+5
View File
@@ -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
@@ -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}'
@@ -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")
@@ -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
)

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="redirect_form">
<form t-att-action="api_url" method="post">
<input type="hidden" name="data" t-att-value="data"/>
</form>
</template>
</odoo>
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="payment_acquirer_form" model="ir.ui.view">
<field name="name">Odoo Payments Acquirer Form</field>
<field name="model">payment.acquirer</field>
<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', '!=', 'odoo')]}">
<button name="odoo_create_adyen_account"
icon="fa-arrow-right"
type="object"
class="btn-link"
string="Create an account in 1 minute"
attrs="{'invisible': [('odoo_adyen_account_id', '!=', False)]}"/>
<field name="odoo_adyen_account_id"
readonly="1"
attrs="{'invisible': [('odoo_adyen_account_id', '=', False)]}"/>
<field name="odoo_adyen_payout_id"
attrs="{'invisible': [('odoo_adyen_account_id', '=', False)], 'required':[('provider', '=', 'odoo'), ('state', '!=', 'disabled')]}"/>
</group>
</xpath>
</field>
</record>
</odoo>
-5
View File
@@ -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
@@ -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')
@@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="payment.payment_acquirer_odoo_by_adyen" model="payment.acquirer">
<field name="name">Odoo Payments by Adyen</field>
<field name="image_128" type="base64" file="payment_odoo_by_adyen/static/src/img/odoo_icon.png"/>
<field name="provider">odoo_adyen</field>
<field name="company_id" ref="base.main_company"/>
<field name="view_template_id" ref="odoo_by_adyen_form"/>
</record>
</data>
</odoo>
@@ -1,4 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import payment
@@ -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')
@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<template id="odoo_by_adyen_form">
<input type="hidden" name="data_set" t-att-data-action-url="tx_url" data-remove-me=""/>
<input type="hidden" name="data" t-att-value="data"/>
</template>
</data>
</odoo>
@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="acquirer_form_odoo_by_adyen" model="ir.ui.view">
<field name="name">acquirer.form.odoo.by.adyen</field>
<field name="model">payment.acquirer</field>
<field name="inherit_id" ref="payment.acquirer_form"/>
<field name="arch" type="xml">
<xpath expr='//group[@name="acquirer"]' position='inside'>
<group attrs="{'invisible': [('provider', '!=', 'odoo_adyen')]}">
<button name="odoo_adyen_create_account" icon="fa-arrow-right" type="object" class="btn-link" string="Create an account in 1 minute"
attrs="{'invisible': [('odoo_adyen_account_id', '!=', False)]}"/>
<field name="odoo_adyen_account_id" readonly="1" attrs="{'invisible': [('odoo_adyen_account_id', '=', False)]}"/>
<field name="odoo_adyen_payout_id" attrs="{'invisible': [('odoo_adyen_account_id', '=', False)], 'required':[('provider', '=', 'odoo_adyen'), ('state', '!=', 'disabled')]}"/>
</group>
</xpath>
</field>
</record>
</data>
</odoo>