145 lines
6.1 KiB
Python
145 lines
6.1 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import logging
|
|
import pprint
|
|
|
|
from werkzeug import urls
|
|
|
|
from odoo import _, models
|
|
from odoo.exceptions import ValidationError
|
|
|
|
from odoo.addons.payment.const import CURRENCY_MINOR_UNITS
|
|
from odoo.addons.payment_mollie import const
|
|
from odoo.addons.payment_mollie.controllers.main import MollieController
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class PaymentTransaction(models.Model):
|
|
_inherit = 'payment.transaction'
|
|
|
|
def _get_specific_rendering_values(self, processing_values):
|
|
""" Override of payment to return Mollie-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 provider-specific rendering values
|
|
:rtype: dict
|
|
"""
|
|
res = super()._get_specific_rendering_values(processing_values)
|
|
if self.provider_code != 'mollie':
|
|
return res
|
|
|
|
payload = self._mollie_prepare_payment_request_payload()
|
|
_logger.info("sending '/payments' request for link creation:\n%s", pprint.pformat(payload))
|
|
payment_data = self.provider_id._mollie_make_request('/payments', data=payload)
|
|
|
|
# The provider reference is set now to allow fetching the payment status after redirection
|
|
self.provider_reference = payment_data.get('id')
|
|
|
|
# Extract the checkout URL from the payment data and add it with its query parameters to the
|
|
# rendering values. Passing the query parameters separately is necessary to prevent them
|
|
# from being stripped off when redirecting the user to the checkout URL, which can happen
|
|
# when only one payment method is enabled on Mollie and query parameters are provided.
|
|
checkout_url = payment_data['_links']['checkout']['href']
|
|
parsed_url = urls.url_parse(checkout_url)
|
|
url_params = urls.url_decode(parsed_url.query)
|
|
return {'api_url': checkout_url, 'url_params': url_params}
|
|
|
|
def _mollie_prepare_payment_request_payload(self):
|
|
""" Create the payload for the payment request based on the transaction values.
|
|
|
|
:return: The request payload
|
|
:rtype: dict
|
|
"""
|
|
user_lang = self.env.context.get('lang')
|
|
base_url = self.provider_id.get_base_url()
|
|
redirect_url = urls.url_join(base_url, MollieController._return_url)
|
|
webhook_url = urls.url_join(base_url, MollieController._webhook_url)
|
|
decimal_places = CURRENCY_MINOR_UNITS.get(
|
|
self.currency_id.name, self.currency_id.decimal_places
|
|
)
|
|
|
|
return {
|
|
'description': self.reference,
|
|
'amount': {
|
|
'currency': self.currency_id.name,
|
|
'value': f"{self.amount:.{decimal_places}f}",
|
|
},
|
|
'locale': user_lang if user_lang in const.SUPPORTED_LOCALES else 'en_US',
|
|
'method': [const.PAYMENT_METHODS_MAPPING.get(
|
|
self.payment_method_code, self.payment_method_code
|
|
)],
|
|
# Since Mollie does not provide the transaction reference when returning from
|
|
# redirection, we include it in the redirect URL to be able to match the transaction.
|
|
'redirectUrl': f'{redirect_url}?ref={self.reference}',
|
|
'webhookUrl': f'{webhook_url}?ref={self.reference}',
|
|
}
|
|
|
|
def _get_tx_from_notification_data(self, provider_code, notification_data):
|
|
""" Override of payment to find the transaction based on Mollie data.
|
|
|
|
:param str provider_code: The code of the provider that handled the transaction
|
|
:param dict notification_data: The notification data sent by the provider
|
|
:return: The transaction if found
|
|
:rtype: recordset of `payment.transaction`
|
|
:raise: ValidationError if the data match no transaction
|
|
"""
|
|
tx = super()._get_tx_from_notification_data(provider_code, notification_data)
|
|
if provider_code != 'mollie' or len(tx) == 1:
|
|
return tx
|
|
|
|
tx = self.search(
|
|
[('reference', '=', notification_data.get('ref')), ('provider_code', '=', 'mollie')]
|
|
)
|
|
if not tx:
|
|
raise ValidationError("Mollie: " + _(
|
|
"No transaction found matching reference %s.", notification_data.get('ref')
|
|
))
|
|
return tx
|
|
|
|
def _process_notification_data(self, notification_data):
|
|
""" Override of payment to process the transaction based on Mollie data.
|
|
|
|
Note: self.ensure_one()
|
|
|
|
:param dict notification_data: The notification data sent by the provider
|
|
:return: None
|
|
"""
|
|
super()._process_notification_data(notification_data)
|
|
if self.provider_code != 'mollie':
|
|
return
|
|
|
|
payment_data = self.provider_id._mollie_make_request(
|
|
f'/payments/{self.provider_reference}', method="GET"
|
|
)
|
|
|
|
# Update the payment method.
|
|
payment_method_type = payment_data.get('method', '')
|
|
if payment_method_type == 'creditcard':
|
|
payment_method_type = payment_data.get('details', {}).get('cardLabel', '').lower()
|
|
payment_method = self.env['payment.method']._get_from_code(
|
|
payment_method_type, mapping=const.PAYMENT_METHODS_MAPPING
|
|
)
|
|
self.payment_method_id = payment_method or self.payment_method_id
|
|
|
|
# Update the payment state.
|
|
payment_status = payment_data.get('status')
|
|
if payment_status == 'pending':
|
|
self._set_pending()
|
|
elif payment_status == 'authorized':
|
|
self._set_authorized()
|
|
elif payment_status == 'paid':
|
|
self._set_done()
|
|
elif payment_status in ['expired', 'canceled', 'failed']:
|
|
self._set_canceled("Mollie: " + _("Cancelled payment with status: %s", payment_status))
|
|
else:
|
|
_logger.info(
|
|
"received data with invalid payment status (%s) for transaction with reference %s",
|
|
payment_status, self.reference
|
|
)
|
|
self._set_error(
|
|
"Mollie: " + _("Received data with invalid payment status: %s", payment_status)
|
|
)
|