Files
odoo_source/addons/payment_adyen/models/payment_transaction.py
T
Antoine Vandevenne (anv) a30aa2aeed [REF] payment_adyen: migrate Adyen to the new payment API
This commits also switches the Adyen implementation from the Hosted
Payment Pages API to the new Checkout API, hence replacing the payment
with redirection flow by a direct payment flow.

See the merge commit for more details.

task-2479832
2021-03-30 09:25:51 +02:00

184 lines
7.0 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
import pprint
from odoo import _, api, fields, models
from odoo.exceptions import UserError, ValidationError
from odoo.addons.payment import utils as payment_utils
from odoo.addons.payment_adyen.const import CURRENCY_DECIMALS, RESULT_CODES_MAPPING
_logger = logging.getLogger(__name__)
class PaymentTransaction(models.Model):
_inherit = 'payment.transaction'
adyen_payment_data = fields.Char(
string="Saved Payment Data",
help="Data that must be passed back to Adyen when returning from redirect", readonly=True,
groups='base.group_system')
#=== BUSINESS METHODS ===#
def _get_specific_processing_values(self, processing_values):
""" Override of payment to return Adyen-specific processing values.
Note: self.ensure_one() from `_get_processing_values`
:param dict processing_values: The generic processing values of the transaction
:return: The dict of acquirer-specific processing values
:rtype: dict
"""
res = super()._get_specific_processing_values(processing_values)
if self.provider != 'adyen':
return res
converted_amount = payment_utils.to_minor_currency_units(
self.amount, self.currency_id, CURRENCY_DECIMALS.get(self.currency_id.name)
)
return {
'converted_amount': converted_amount,
'access_token': payment_utils.generate_access_token(
processing_values['reference'],
converted_amount,
processing_values['partner_id']
)
}
def _send_payment_request(self):
""" Override of payment to send a payment request to Adyen.
Note: self.ensure_one()
:return: None
:raise: UserError if the transaction is not linked to a token
"""
super()._send_payment_request()
if self.provider != 'adyen':
return
# Make the payment request to Adyen
if not self.token_id:
raise UserError("Adyen: " + _("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)
)
data = {
'merchantAccount': self.acquirer_id.adyen_merchant_account,
'amount': {
'value': converted_amount,
'currency': self.currency_id.name,
},
'reference': self.reference,
'paymentMethod': {
'recurringDetailReference': self.token_id.acquirer_ref
}, # Required by Adyen although it is also provided with 'storedPaymentMethodId'
'storedPaymentMethodId': self.token_id.acquirer_ref,
'shopperReference': self.token_id.adyen_shopper_reference,
'recurringProcessingModel': 'Subscription',
'shopperIP': payment_utils.get_customer_ip_address(),
'shopperInteraction': 'ContAuth',
}
response_content = self.acquirer_id._adyen_make_request(
url_field_name='adyen_checkout_api_url',
endpoint='/payments',
payload=data,
method='POST'
)
# Handle the payment request response
_logger.info("payment request response:\n%s", pprint.pformat(response_content))
self._handle_feedback_data('adyen', 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 != 'adyen':
return tx
reference = data.get('merchantReference')
if not reference:
raise ValidationError("Adyen: " + _("Received data with missing merchant reference"))
tx = self.search([('reference', '=', reference), ('provider', '=', 'adyen')])
if not tx:
raise ValidationError(
"Adyen: " + _("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.
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 != 'adyen':
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("Adyen: " + _("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._adyen_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.warning("received data with invalid payment state: %s", payment_state)
self._set_error(
"Adyen: " + _("Received data with invalid payment state: %s", payment_state)
)
def _adyen_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()
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': data['additionalData']['recurring.recurringDetailReference'],
'adyen_shopper_reference': data['additionalData']['recurring.shopperReference'],
'verified': True, # The payment is authorized, so the payment method is valid
})
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
)