[REM] payment_ogone: remove the FlexCheckout API

Before this commit, the FlexCheckout API was used to process validation
operations only. It proved itself to be:
- unusable without making small payments with immediate refunds
- badly suited to PSD2 due to its Merchant Initiated Transactions (MIT)
- not worth the maintenance cost tied to its complexity given the simple
  flow that it implements
- inconvenient to integrate to standard payment flows
- poorly customizable in regard to the hosted tokenization page

This commit thus removes it entirely and drops Ogone's support for
validation operations with it.

task-2494916
This commit is contained in:
Antoine Vandevenne (anv)
2021-07-26 13:40:59 +00:00
parent 6df3cdaaa7
commit a5bdad89f2
5 changed files with 83 additions and 338 deletions
+5 -86
View File
@@ -1,25 +1,20 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
import logging
import pprint
import re
import markupsafe
import werkzeug
from odoo import _, http
from odoo.exceptions import ValidationError
from odoo.http import request
from odoo.tools import ustr
_logger = logging.getLogger(__name__)
class OgoneController(http.Controller):
_hosted_payment_page_return_url = '/payment/ogone/hostedpaymentpage'
_flexcheckout_return_url = '/payment/ogone/flexcheckout'
_directlink_return_url = '/payment/ogone/directlink'
_return_url = '/payment/ogone/return'
_backward_compatibility_urls = [
'/payment/ogone/accept', '/payment/ogone/test/accept',
'/payment/ogone/decline', '/payment/ogone/test/decline',
@@ -31,84 +26,11 @@ class OgoneController(http.Controller):
] # Facilitates the migration of users who registered the URLs in Ogone's backend prior to 14.3
@http.route(
_hosted_payment_page_return_url, type='http', auth='public', methods=['GET', 'POST'],
csrf=False
) # 'GET' or 'POST' depending on the configuration in Ogone backend
def ogone_return_from_hosted_payment_page(self, **feedback_data):
""" Process the data returned by Ogone after redirection to the Hosted Payment Page.
:param dict feedback_data: The feedback data
"""
# Check the source and integrity of the data
data = self._homogenize_data(feedback_data)
self._verify_signature(feedback_data, data)
# Handle the feedback data
data['FEEDBACK_TYPE'] = 'hosted_payment_page'
_logger.info("entering _handle_feedback_data with data:\n%s", pprint.pformat(data))
request.env['payment.transaction'].sudo()._handle_feedback_data('ogone', data)
return werkzeug.utils.redirect('/payment/status')
@http.route(
_flexcheckout_return_url, type='http', auth='public', methods=['GET', 'POST'], csrf=False
) # 'GET' or 'POST' depending on the configuration in Ogone backend
def ogone_return_from_flexcheckout(self, **feedback_data):
""" Process the data returned by Ogone after redirection to Flexcheckout.
:param dict feedback_data: The feedback data
"""
# Check the source and integrity of the data
data = self._homogenize_data(feedback_data)
self._verify_signature(feedback_data, data)
# Create a token from the feedback data
data['FEEDBACK_TYPE'] = 'flexcheckout'
_logger.info("entering _handle_feedback_data with data:\n%s", pprint.pformat(data))
tx_sudo = request.env['payment.transaction'].sudo()._handle_feedback_data('ogone', data)
# Process the payment through the token
tree = tx_sudo._ogone_send_order_request(request_3ds_authentication=True)
feedback_data = {
'FEEDBACK_TYPE': 'directlink',
'ORDERID': tree.get('orderID'),
'tree': tree,
}
_logger.info(
"entering _handle_feedback_data with data:\n%s", pprint.pformat(feedback_data)
)
request.env['payment.transaction'].sudo()._handle_feedback_data('ogone', feedback_data)
# Handle the response
redirect_html_element = tree.find('HTML_ANSWER')
if redirect_html_element:
# Ogone has inserted an HTML_ANSWER element in its response XML tree. This means that a
# redirection to DirectLink's authentication page is required, as FlexCheckout is not
# capable of handling authentications...
# As per the documentation, the HTML must be inserted as-is into the current page, and
# consists of a <form> bundled with a script to auto-submit the form once its inserted.
# The content of the HTML is not sanitized to preserve the redirection in the script.
# After redirection, the customer comes back to the directlink return URL and we proceed
# with the (now authenticated) payment request.
redirect_html = markupsafe.Markup(ustr(base64.b64decode(redirect_html_element.text)))
return request.render(
'payment_ogone.directlink_feedback', {'redirect_html': redirect_html}
)
else:
if tx_sudo.state in ('cancel', 'error'):
tx_sudo.token_id.active = False # The initial payment failed, archive the token
return request.redirect('/payment/status')
@http.route(
[_directlink_return_url] + _backward_compatibility_urls, type='http', auth='public',
[_return_url] + _backward_compatibility_urls, type='http', auth='public',
methods=['GET', 'POST'], csrf=False
) # 'GET' or 'POST' depending on the configuration in Ogone backend
def ogone_return_from_directlink(self, **feedback_data):
""" Process the data returned by Ogone after redirection to Directlink authentication page.
A redirection to Directlink can happen if a 3DS1 authentication is requested when sending
the request for a new order. This should normally only happen for the first payment of a
token as this is the only case where we specifically request the authentication if necessary
and handle the redirection request if one is returned.
def ogone_return_from_redirect(self, **feedback_data):
""" Process the data returned by Ogone after redirection to the Hosted Payment Page.
This route can also accept S2S notifications from Ogone if it is configured as a webhook in
Ogone's backend.
@@ -120,11 +42,8 @@ class OgoneController(http.Controller):
self._verify_signature(feedback_data, data)
# Handle the feedback data
data['FEEDBACK_TYPE'] = 'directlink'
_logger.info("entering _handle_feedback_data with data:\n%s", pprint.pformat(data))
tx_sudo = request.env['payment.transaction'].sudo()._handle_feedback_data('ogone', data)
if tx_sudo.state in ('cancel', 'error'):
tx_sudo.token_id.active = False # The initial payment failed, archive the token
request.env['payment.transaction'].sudo()._handle_feedback_data('ogone', data)
return request.redirect('/payment/status')
def _homogenize_data(self, data):
+1 -21
View File
@@ -1,27 +1,8 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
# See https://epayments-support.ingenico.com/en/integration/all-sales-channels/flexcheckout/guide#flexcheckout_integration_guides_sha_out
FLEXCHECKOUT_KEYS = [
'ALIAS.ALIASID',
'ALIAS.NCERROR',
'ALIAS.NCERRORCARDNO',
'ALIAS.NCERRORCN',
'ALIAS.NCERRORCVC',
'ALIAS.NCERRORED',
'ALIAS.ORDERID',
'ALIAS.STATUS',
'ALIAS.STOREPERMANENTLY',
'CARD.BIC',
'CARD.BIN',
'CARD.BRAND',
'CARD.CARDHOLDERNAME',
'CARD.CARDNUMBER',
'CARD.CVC',
'CARD.EXPIRYDATE'
]
# See https://epayments-support.ingenico.com/en/integration-solutions/integrations/directlink#directlink_integration_guides_request_a_new_order
# See https://epayments-support.ingenico.com/en/integration-solutions/integrations/directlink#directlink_integration_guides_order_response
DIRECTLINK_KEYS = [
VALID_KEYS = [
'AAVADDRESS',
'AAVCHECK',
'AAVMAIL',
@@ -86,7 +67,6 @@ DIRECTLINK_KEYS = [
'TRXDATE',
'VC',
]
VALID_KEYS = DIRECTLINK_KEYS + FLEXCHECKOUT_KEYS
# See https://epayments-support.ingenico.com/en/get-started/transaction-status-full/
+11 -31
View File
@@ -5,7 +5,7 @@ from hashlib import sha1
import requests
from odoo import _, fields, models
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
from .const import VALID_KEYS
@@ -31,37 +31,22 @@ class PaymentAcquirer(models.Model):
ogone_shakey_out = fields.Char(
string="SHA Key OUT", size=32, required_if_provider='ogone', groups='base.group_system')
def _get_validation_amount(self):
""" Override of payment to return the amount for Ogone validation operations.
@api.model
def _get_compatible_acquirers(self, *args, is_validation=False, **kwargs):
""" Override of payment to unlist Ogone acquirers for validation operations. """
acquirers = super()._get_compatible_acquirers(*args, is_validation=is_validation, **kwargs)
:return: The validation amount
:rtype: float
"""
res = super()._get_validation_amount()
if self.provider != 'ogone':
return res
if is_validation:
acquirers = acquirers.filtered(lambda a: a.provider != 'ogone')
return 1.0
def _get_redirect_form_view(self, is_validation=False):
""" Override of payment to return the FlexCheckout form for validation operations.
:param bool is_validation: Whether the operation is a validation
:return: The redirect form template
:rtype: record of `ir.ui.view`
"""
res = super()._get_redirect_form_view()
if self.provider != 'ogone' or not is_validation:
return res # self.redirect_form_view_id
return self.env.ref('payment_ogone.redirect_form_validation')
return acquirers
def _ogone_get_api_url(self, api_key):
""" Return the appropriate URL of the requested API for the acquirer state.
Note: self.ensure_one()
:param str api_key: The API whose URL to get: 'flexcheckout' or 'directlink'
:param str api_key: The API whose URL to get: 'hosted_payment_page' or 'directlink'
:return: The API URL
:rtype: str
"""
@@ -70,16 +55,12 @@ class PaymentAcquirer(models.Model):
if self.state == 'enabled':
api_urls = {
'hosted_payment_page': 'https://secure.ogone.com/ncol/prod/orderstandard_utf8.asp',
'flexcheckout': 'https://secure.ogone.com/Tokenization/HostedPage',
'directlink': 'https://secure.ogone.com/ncol/prod/orderdirect_utf8.asp',
'maintenancedirect': 'https://secure.ogone.com/ncol/prod/maintenancedirect_utf8.asp',
}
else: # 'test'
api_urls = {
'hosted_payment_page': 'https://ogone.test.v-psp.com/ncol/test/orderstandard_utf8.asp',
'flexcheckout': 'https://ogone.test.v-psp.com/Tokenization/HostedPage',
'directlink': 'https://ogone.test.v-psp.com/ncol/test/orderdirect_utf8.asp',
'maintenancedirect': 'https://ogone.test.v-psp.com/ncol/test/maintenancedirect_utf8.asp',
}
return api_urls.get(api_key)
@@ -110,12 +91,11 @@ class PaymentAcquirer(models.Model):
shasign = sha1(signing_string.encode()).hexdigest()
return shasign
def _ogone_make_request(self, api_key, payload=None, method='POST'):
def _ogone_make_request(self, payload=None, method='POST'):
""" Make a request to one of Ogone APIs.
Note: self.ensure_one()
:param str api_key: The API to which the request is made: 'flexcheckout' or 'directlink'
:param dict payload: The payload of the request
:param str method: The HTTP method of the request
:return The content of the response
@@ -124,7 +104,7 @@ class PaymentAcquirer(models.Model):
"""
self.ensure_one()
url = self._ogone_get_api_url(api_key)
url = self._ogone_get_api_url('directlink')
try:
response = requests.request(method, url, data=payload, timeout=60)
response.raise_for_status()
@@ -61,57 +61,37 @@ class PaymentTransaction(models.Model):
if self.acquirer_id.provider != 'ogone':
return res
base_url = self.acquirer_id.get_base_url()
if self.operation == 'online_redirect':
return_url = urls.url_join(base_url, OgoneController._hosted_payment_page_return_url)
rendering_values = {
'PSPID': self.acquirer_id.ogone_pspid,
'ORDERID': self.reference,
'AMOUNT': payment_utils.to_minor_currency_units(self.amount, None, 2),
'CURRENCY': self.currency_id.name,
'LANGUAGE': self.partner_lang or 'en_US',
'EMAIL': self.partner_email or '',
'OWNERADDRESS': self.partner_address or '',
'OWNERZIP': self.partner_zip or '',
'OWNERTOWN': self.partner_city or '',
'OWNERCTY': self.partner_country_id.code or '',
'OWNERTELNO': self.partner_phone or '',
'OPERATION': 'SAL', # direct sale
'USERID': self.acquirer_id.ogone_userid,
'ACCEPTURL': return_url,
'DECLINEURL': return_url,
'EXCEPTIONURL': return_url,
'CANCELURL': return_url,
}
if self.tokenize:
rendering_values.update({
'ALIAS': payment_utils.singularize_reference_prefix(prefix='ODOO-ALIAS'),
'ALIASUSAGE': _("Storing your payment details is necessary for future use."),
})
return_url = urls.url_join(self.acquirer_id.get_base_url(), OgoneController._return_url)
rendering_values = {
'PSPID': self.acquirer_id.ogone_pspid,
'ORDERID': self.reference,
'AMOUNT': payment_utils.to_minor_currency_units(self.amount, None, 2),
'CURRENCY': self.currency_id.name,
'LANGUAGE': self.partner_lang or 'en_US',
'EMAIL': self.partner_email or '',
'OWNERADDRESS': self.partner_address or '',
'OWNERZIP': self.partner_zip or '',
'OWNERTOWN': self.partner_city or '',
'OWNERCTY': self.partner_country_id.code or '',
'OWNERTELNO': self.partner_phone or '',
'OPERATION': 'SAL', # direct sale
'USERID': self.acquirer_id.ogone_userid,
'ACCEPTURL': return_url,
'DECLINEURL': return_url,
'EXCEPTIONURL': return_url,
'CANCELURL': return_url,
}
if self.tokenize:
rendering_values.update({
'SHASIGN': self.acquirer_id._ogone_generate_signature(
rendering_values, incoming=False
).upper(),
'api_url': self.acquirer_id._ogone_get_api_url('hosted_payment_page'),
})
else: # validation
return_url = urls.url_join(base_url, OgoneController._flexcheckout_return_url)
rendering_values = {
'ACCOUNT_PSPID': self.acquirer_id.ogone_pspid,
'ALIAS_ALIASID': payment_utils.singularize_reference_prefix(prefix='ODOO-ALIAS'),
'ALIAS_ORDERID': self.reference,
'ALIAS_STOREPERMANENTLY': 'Y' if self.tokenize else 'N',
'CARD_PAYMENTMETHOD': 'CreditCard',
'LAYOUT_LANGUAGE': self.partner_lang,
'PARAMETERS_ACCEPTURL': return_url,
'PARAMETERS_EXCEPTIONURL': return_url,
}
rendering_values.update({
'SHASIGNATURE_SHASIGN': self.acquirer_id._ogone_generate_signature(
rendering_values, incoming=False, format_keys=True
).upper(),
'api_url': self.acquirer_id._ogone_get_api_url('flexcheckout'),
'ALIAS': payment_utils.singularize_reference_prefix(prefix='ODOO-ALIAS'),
'ALIASUSAGE': _("Storing your payment details is necessary for future use."),
})
rendering_values.update({
'SHASIGN': self.acquirer_id._ogone_generate_signature(
rendering_values, incoming=False
).upper(),
'api_url': self.acquirer_id._ogone_get_api_url('hosted_payment_page'),
})
return rendering_values
def _send_payment_request(self):
@@ -129,25 +109,8 @@ class PaymentTransaction(models.Model):
if not self.token_id:
raise UserError("Ogone: " + _("The transaction is not linked to a token."))
tree = self._ogone_send_order_request()
feedback_data = {
'FEEDBACK_TYPE': 'directlink',
'ORDERID': tree.get('orderID'),
'tree': tree,
}
_logger.info("entering _handle_feedback_data with data:\n%s", pprint.pformat(feedback_data))
self._handle_feedback_data('ogone', feedback_data)
def _ogone_send_order_request(self, request_3ds_authentication=False):
""" Make a new order request to Ogone and return the lxml etree parsed from the response.
:param bool request_3ds_authentication: Whether a 3DS authentication should be requested if
necessary to process the payment
:return: The lxml etree
:raise: ValidationError if the response can not be parsed to an lxml etree
"""
# Make the payment request
base_url = self.acquirer_id.get_base_url()
return_url = urls.url_join(base_url, OgoneController._directlink_return_url)
data = {
# DirectLink parameters
'PSPID': self.acquirer_id.ogone_pspid,
@@ -166,14 +129,8 @@ class PaymentTransaction(models.Model):
'OPERATION': 'SAL', # direct sale
# Alias Manager parameters
'ALIAS': self.token_id.acquirer_ref,
'ALIASPERSISTEDAFTERUSE': 'Y' if self.token_id.active else 'N',
'ALIASPERSISTEDAFTERUSE': 'Y',
'ECI': 9, # Recurring (from eCommerce)
# 3DS parameters
'ACCEPTURL': return_url,
'DECLINEURL': return_url,
'EXCEPTIONURL': return_url,
'LANGUAGE': self.partner_lang or 'en_US',
'FLAG3D': 'Y' if request_3ds_authentication else 'N',
}
data['SHASIGN'] = self.acquirer_id._ogone_generate_signature(data, incoming=False)
@@ -181,16 +138,20 @@ class PaymentTransaction(models.Model):
"making payment request:\n%s",
pprint.pformat({k: v for k, v in data.items() if k != 'PSWD'})
) # Log the payment request data without the password
response_content = self.acquirer_id._ogone_make_request('directlink', data)
response_content = self.acquirer_id._ogone_make_request(data)
try:
tree = objectify.fromstring(response_content)
except etree.XMLSyntaxError:
raise ValidationError("Ogone: " + "Received badly structured response from the API.")
# Handle the feedback data
_logger.info(
"received payment request response as an etree:\n%s",
etree.tostring(tree, pretty_print=True, encoding='utf-8')
)
return tree
feedback_data = {'ORDERID': tree.get('orderID'), 'tree': tree}
_logger.info("entering _handle_feedback_data with data:\n%s", pprint.pformat(feedback_data))
self._handle_feedback_data('ogone', feedback_data)
@api.model
def _get_tx_from_feedback_data(self, provider, data):
@@ -221,111 +182,49 @@ class PaymentTransaction(models.Model):
: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 != 'ogone':
return
feedback_type = data.get('FEEDBACK_TYPE')
if feedback_type == 'flexcheckout':
self._ogone_tokenize_from_feedback_data(
data.get('CARDNUMBER'),
data['ALIASID'],
not self.tokenize, # Immediately archive the token if it was not requested
)
elif feedback_type in ('hosted_payment_page', 'directlink'):
if 'tree' in data:
data = data['tree']
if 'tree' in data:
data = data['tree']
self.acquirer_reference = data.get('PAYID')
payment_status = int(data.get('STATUS', '0'))
if payment_status in const.PAYMENT_STATUS_MAPPING['pending']:
self._set_pending()
elif payment_status in const.PAYMENT_STATUS_MAPPING['done']:
has_token_data = 'ALIAS' in data
if self.tokenize and has_token_data:
self._ogone_tokenize_from_feedback_data(
data.get('CARDNO'), data['ALIAS'], False
)
if self.token_id:
self.token_id.verified = True # Validity of the token has been confirmed
self._set_done()
elif payment_status in const.PAYMENT_STATUS_MAPPING['cancel']:
self._set_canceled()
else: # Classify unknown payment statuses as `error` tx state
_logger.info("received data with invalid payment status: %s", payment_status)
self._set_error(
"Ogone: " + _("Received data with invalid payment status: %s", payment_status)
)
else:
raise ValidationError(
"Ogone: " + _("Received feedback data with unknown type: %s", feedback_type)
self.acquirer_reference = data.get('PAYID')
payment_status = int(data.get('STATUS', '0'))
if payment_status in const.PAYMENT_STATUS_MAPPING['pending']:
self._set_pending()
elif payment_status in const.PAYMENT_STATUS_MAPPING['done']:
has_token_data = 'ALIAS' in data
if self.tokenize and has_token_data:
self._ogone_tokenize_from_feedback_data(data)
self._set_done()
elif payment_status in const.PAYMENT_STATUS_MAPPING['cancel']:
self._set_canceled()
else: # Classify unknown payment statuses as `error` tx state
_logger.info("received data with invalid payment status: %s", payment_status)
self._set_error(
"Ogone: " + _("Received data with invalid payment status: %s", payment_status)
)
def _ogone_tokenize_from_feedback_data(self, card_number, acquirer_ref, is_one_shot_payment):
def _ogone_tokenize_from_feedback_data(self, data):
""" Create a token from feedback data.
:param str card_number: The obfuscated number of the card
:param str acquirer_ref: The acquirer reference of the payment
:param bool is_one_shot_payment: Whether the token was created for a one-shot payment
:param dict data: The feedback data sent by the provider
:return: None
"""
token_name = card_number or payment_utils.build_token_name() # Not requested in the backend
token_name = data.get('CARDNO') or payment_utils.build_token_name()
token = self.env['payment.token'].create({
'acquirer_id': self.acquirer_id.id,
'name': token_name, # Already padded with 'X's
'partner_id': self.partner_id.id,
'acquirer_ref': acquirer_ref,
'verified': False, # Set to True as soon as a payment is authorized
'active': not is_one_shot_payment, # Archive immediately if used for one-shot payment
'acquirer_ref': data['ALIAS'],
'verified': True, # The payment is authorized, so the payment method is valid
})
self.write({
'token_id': token.id,
'tokenize': False,
})
def _send_refund_request(self):
""" Override of payment to send a refund request to Authorize.
Note: self.ensure_one()
:return: None
:raise: ValidationError if a badly structured response is received
"""
super()._send_refund_request()
if self.provider != 'ogone':
return
data = {
'PSPID': self.acquirer_id.ogone_pspid,
'ORDERID': self.reference,
'PAYID': self.acquirer_reference,
'USERID': self.acquirer_id.ogone_userid,
'PSWD': self.acquirer_id.ogone_password,
'AMOUNT': payment_utils.to_minor_currency_units(self.amount, None, 2),
'CURRENCY': self.currency_id.name,
'OPERATION': 'RFS', # refund
}
data['SHASIGN'] = self.acquirer_id._ogone_generate_signature(data, incoming=False)
_logger.info(
"making refund request:\n%s",
pprint.pformat({k: v for k, v in data.items() if k != 'PSWD'})
) # Log the refund request data without the password
response_content = self.acquirer_id._ogone_make_request('maintenancedirect', data)
try:
tree = objectify.fromstring(response_content)
except etree.XMLSyntaxError:
raise ValidationError("Ogone: " + "Received badly structured response from the API.")
_logger.info(
"received refund request response as an etree:\n%s",
etree.tostring(tree, pretty_print=True, encoding='utf-8')
"created token with id %s for partner with id %s", token.id, self.partner_id.id
)
feedback_data = {
'FEEDBACK_TYPE': 'directlink',
'ORDERID': tree.get('orderID'),
'tree': tree,
}
_logger.info("entering _handle_feedback_data with data:\n%s", pprint.pformat(feedback_data))
self._handle_feedback_data('ogone', feedback_data)
+6 -39
View File
@@ -15,9 +15,11 @@ from ..controllers.main import OgoneController
@tagged('post_install', '-at_install')
class OgoneTest(OgoneCommon):
def test_validation_amount(self):
""" Test the value of the validation amount. """
self.assertEqual(self.ogone._get_validation_amount(), 1.0)
def test_incompatibility_with_validation_operation(self):
acquirers = self.env['payment.acquirer']._get_compatible_acquirers(
self.company.id, self.partner.id, is_validation=True
)
self.assertNotIn(self.ogone, acquirers)
@freeze_time('2011-11-02 12:00:21') # Freeze time for consistent singularization behavior
def test_reference_is_singularized(self):
@@ -49,7 +51,7 @@ class OgoneTest(OgoneCommon):
@freeze_time('2011-11-02 12:00:21') # Freeze time for consistent singularization behavior
def test_redirect_form_values(self):
""" Test the values of the redirect form inputs for online payments. """
return_url = self._build_url(OgoneController._hosted_payment_page_return_url)
return_url = self._build_url(OgoneController._return_url)
expected_values = {
'PSPID': self.ogone.ogone_pspid,
'ORDERID': self.reference,
@@ -94,38 +96,3 @@ class OgoneTest(OgoneCommon):
value,
f"received value {inputs[form_key]} for input {form_key} (expected {value})"
)
@freeze_time('2011-11-02 12:00:21') # Freeze time for consistent singularization behavior
def test_redirect_form_validation_values(self):
""" Test the values of the redirect form inputs for validation. """
return_url = self._build_url(OgoneController._flexcheckout_return_url)
expected_values = {
'ACCOUNT_PSPID': self.ogone.ogone_pspid,
'ALIAS_ALIASID': payment_utils.singularize_reference_prefix(prefix='ODOO-ALIAS'),
'ALIAS_ORDERID': self.reference,
'ALIAS_STOREPERMANENTLY': 'N', # 'Y' if self.tokenize
'CARD_PAYMENTMETHOD': 'CreditCard',
'LAYOUT_LANGUAGE': self.partner.lang,
'PARAMETERS_ACCEPTURL': return_url,
'PARAMETERS_EXCEPTIONURL': return_url,
}
expected_values['SHASIGNATURE_SHASIGN'] = self.ogone._ogone_generate_signature(
expected_values, incoming=False, format_keys=True
).upper()
tx = self.create_transaction(flow='dummy', operation='validation')
with mute_logger('odoo.addons.payment.models.payment_transaction'):
processing_values = tx._get_processing_values()
form_info = self._extract_values_from_html_form(processing_values['redirect_form_html'])
self.assertEqual(form_info['action'], 'https://ogone.test.v-psp.com/Tokenization/HostedPage')
inputs = form_info['inputs']
self.assertEqual(len(expected_values), len(inputs))
for rendering_key, value in expected_values.items():
form_key = rendering_key.replace('_', '.')
self.assertEqual(
inputs[form_key],
value,
f"received value {inputs[form_key]} for input {form_key} (expected {value})"
)