[FIX] payment, payment_ogone: bring back the Hosted Payment Page API
With commit 139dd9d, the Hosted Payment Page API of Ogone was entirely replaced by the FlexCheckout API. This new API allows customers to tokenize a payment method for a later use without the need of making a purchase. However, it proved itself to be less convenient for regular purchases as it offers less payment options than the HPP, and payments are no longer cardholder-initiated transactions but merchant-initiated transactions which have a higher chance of triggering an authentication check. Furthermore, the payment flow itself is more complicated than before. This commit brings back the Hosted Payment Page API to work in parallel with the FlexCheckout API and the other APIs that were left untouched. It will be exclusively used for making online payments with a new card. The validation flow is managed by the FlexCheckout API while the DirectLink API manages the payment by token and offline payment flows. task-2494916 closes odoo/odoo#72991 X-original-commit: 4fa7b772c71979f61eb098ff8d005deba834527c Signed-off-by: Toufik Benjaa (tbe) <tbe@odoo.com> Signed-off-by: Antoine Vandevenne (anv) <AntoineVDV@users.noreply.github.com>
This commit is contained in:
@@ -192,6 +192,10 @@ class PaymentAcquirer(models.Model):
|
||||
elif payment_method:
|
||||
payment_method.unlink()
|
||||
|
||||
def _get_default_payment_method(self):
|
||||
self.ensure_one()
|
||||
return self.env.ref('account.account_payment_method_manual_in').id
|
||||
|
||||
#=== CONSTRAINT METHODS ===#
|
||||
|
||||
@api.constrains('fees_dom_var', 'fees_int_var')
|
||||
@@ -389,6 +393,17 @@ class PaymentAcquirer(models.Model):
|
||||
self.ensure_one()
|
||||
return self.journal_id.currency_id or self.company_id.currency_id
|
||||
|
||||
def _get_default_payment_method(self):
|
||||
def _get_redirect_form_view(self, is_validation=False):
|
||||
""" Return the view of the template used to render the redirect form.
|
||||
|
||||
For an acquirer to return a different view depending on whether the operation is a
|
||||
validation, it must override this method and return the appropriate view.
|
||||
|
||||
Note: self.ensure_one()
|
||||
|
||||
:param bool is_validation: Whether the operation is a validation
|
||||
:return: The redirect form template
|
||||
:rtype: record of `ir.ui.view`
|
||||
"""
|
||||
self.ensure_one()
|
||||
return self.env.ref('account.account_payment_method_manual_in').id
|
||||
return self.redirect_form_view_id
|
||||
|
||||
@@ -424,17 +424,18 @@ class PaymentTransaction(models.Model):
|
||||
)
|
||||
|
||||
# Render the html form for the redirect flow if available
|
||||
if self.operation in ('online_redirect', 'validation') \
|
||||
and self.acquirer_id.redirect_form_view_id:
|
||||
if self.operation in ('online_redirect', 'validation'):
|
||||
rendering_values = self._get_specific_rendering_values(processing_values)
|
||||
_logger.info(
|
||||
"acquirer-specific rendering values for transaction with id %s:\n%s",
|
||||
self.id, pprint.pformat(rendering_values)
|
||||
)
|
||||
redirect_form_html = self.acquirer_id.redirect_form_view_id._render(
|
||||
rendering_values, engine='ir.qweb'
|
||||
redirect_form_view = self.acquirer_id._get_redirect_form_view(
|
||||
is_validation=self.operation == 'validation'
|
||||
)
|
||||
processing_values.update(redirect_form_html=redirect_form_html)
|
||||
if redirect_form_view: # Some acquirer don't need a redirect form
|
||||
redirect_form_html = redirect_form_view._render(rendering_values, engine='ir.qweb')
|
||||
processing_values.update(redirect_form_html=redirect_form_html)
|
||||
|
||||
return processing_values
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ _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'
|
||||
_backward_compatibility_urls = [
|
||||
@@ -28,6 +29,25 @@ class OgoneController(http.Controller):
|
||||
'/payment/ogone/validate/exception',
|
||||
] # 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
|
||||
@@ -47,6 +67,15 @@ class OgoneController(http.Controller):
|
||||
|
||||
# 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')
|
||||
@@ -64,15 +93,6 @@ class OgoneController(http.Controller):
|
||||
'payment_ogone.directlink_feedback', {'redirect_html': redirect_html}
|
||||
)
|
||||
else:
|
||||
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)
|
||||
if tx_sudo.state in ('cancel', 'error'):
|
||||
tx_sudo.token_id.active = False # The initial payment failed, archive the token
|
||||
return werkzeug.utils.redirect('/payment/status')
|
||||
@@ -89,6 +109,9 @@ class OgoneController(http.Controller):
|
||||
token as this is the only case where we specifically request the authentication if necessary
|
||||
and handle the redirection request if one is returned.
|
||||
|
||||
This route can also accept S2S notifications from Ogone if it is configured as a webhook in
|
||||
Ogone's backend.
|
||||
|
||||
:param dict feedback_data: The feedback data
|
||||
"""
|
||||
# Check the source and integrity of the data
|
||||
|
||||
@@ -124,6 +124,12 @@ msgstr ""
|
||||
msgid "Saved payment methods cannot be restored once they have been archived."
|
||||
msgstr ""
|
||||
|
||||
#. module: payment_ogone
|
||||
#: code:addons/payment_ogone/models/payment_transaction.py:89
|
||||
#, python-format
|
||||
msgid "Storing your payment details is necessary for future use."
|
||||
msgstr ""
|
||||
|
||||
#. module: payment_ogone
|
||||
#: model:ir.model.fields,help:payment_ogone.field_payment_acquirer__ogone_userid
|
||||
msgid "The ID solely used to identify the API user with Ogone"
|
||||
|
||||
@@ -43,6 +43,19 @@ class PaymentAcquirer(models.Model):
|
||||
|
||||
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')
|
||||
|
||||
def _ogone_get_api_url(self, api_key):
|
||||
""" Return the appropriate URL of the requested API for the acquirer state.
|
||||
|
||||
@@ -56,12 +69,14 @@ 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',
|
||||
|
||||
@@ -62,23 +62,56 @@ class PaymentTransaction(models.Model):
|
||||
return res
|
||||
|
||||
base_url = self.acquirer_id.get_base_url()
|
||||
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'),
|
||||
})
|
||||
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."),
|
||||
})
|
||||
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'),
|
||||
})
|
||||
return rendering_values
|
||||
|
||||
def _send_payment_request(self):
|
||||
@@ -196,58 +229,62 @@ class PaymentTransaction(models.Model):
|
||||
|
||||
feedback_type = data.get('FEEDBACK_TYPE')
|
||||
if feedback_type == 'flexcheckout':
|
||||
self._process_flexcheckout_data(data)
|
||||
elif feedback_type == 'directlink':
|
||||
self._process_directlink_data(data)
|
||||
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']
|
||||
|
||||
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)
|
||||
)
|
||||
|
||||
def _process_flexcheckout_data(self, data):
|
||||
""" Create a token from Flexcheckout feedback data.
|
||||
def _ogone_tokenize_from_feedback_data(self, card_number, acquirer_ref, is_one_shot_payment):
|
||||
""" Create a token from feedback data.
|
||||
|
||||
:param dict data: The feedback data from Flexcheckout
|
||||
: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
|
||||
:return: None
|
||||
"""
|
||||
token_name = data.get('CARDNUMBER') \
|
||||
or payment_utils.build_token_name() # CARD.CARNUMBER not requested in backend
|
||||
token_name = card_number or payment_utils.build_token_name() # Not requested in the backend
|
||||
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': data['ALIASID'],
|
||||
'verified': False, # No payment has been processed through this token yet
|
||||
'active': self.tokenize, # Immediately archive the token if it was not requested
|
||||
'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
|
||||
})
|
||||
self.write({
|
||||
'token_id': token.id,
|
||||
'tokenize': False,
|
||||
})
|
||||
|
||||
def _process_directlink_data(self, data):
|
||||
""" Update the transaction state and the acquirer reference based on the feedback data.
|
||||
|
||||
:param dict data: The feedback data from DirectLink
|
||||
:return: None
|
||||
"""
|
||||
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']:
|
||||
self.token_id.verified = True # The payment has been authorized, the token is valid
|
||||
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 _send_refund_request(self):
|
||||
""" Override of payment to send a refund request to Authorize.
|
||||
|
||||
|
||||
@@ -48,7 +48,56 @@ 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. """
|
||||
""" Test the values of the redirect form inputs for online payments. """
|
||||
return_url = self._build_url(OgoneController._hosted_payment_page_return_url)
|
||||
expected_values = {
|
||||
'PSPID': self.ogone.ogone_pspid,
|
||||
'ORDERID': self.reference,
|
||||
'AMOUNT': str(payment_utils.to_minor_currency_units(self.amount, None, 2)),
|
||||
'CURRENCY': self.currency.name,
|
||||
'LANGUAGE': self.partner.lang,
|
||||
'EMAIL': self.partner.email,
|
||||
'OWNERZIP': self.partner.zip,
|
||||
'OWNERADDRESS': payment_utils.format_partner_address(
|
||||
self.partner.street, self.partner.street2
|
||||
),
|
||||
'OWNERCTY': self.partner.country_id.code,
|
||||
'OWNERTOWN': self.partner.city,
|
||||
'OWNERTELNO': self.partner.phone,
|
||||
'OPERATION': 'SAL', # direct sale
|
||||
'USERID': self.ogone.ogone_userid,
|
||||
'ACCEPTURL': return_url,
|
||||
'DECLINEURL': return_url,
|
||||
'EXCEPTIONURL': return_url,
|
||||
'CANCELURL': return_url,
|
||||
'ALIAS': None,
|
||||
'ALIASUSAGE': None,
|
||||
}
|
||||
expected_values['SHASIGN'] = self.ogone._ogone_generate_signature(
|
||||
expected_values, incoming=False
|
||||
).upper()
|
||||
|
||||
tx = self.create_transaction(flow='redirect')
|
||||
self.assertEqual(tx.tokenize, False)
|
||||
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/ncol/test/orderstandard_utf8.asp')
|
||||
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})"
|
||||
)
|
||||
|
||||
@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,
|
||||
@@ -64,7 +113,7 @@ class OgoneTest(OgoneCommon):
|
||||
expected_values, incoming=False, format_keys=True
|
||||
).upper()
|
||||
|
||||
tx = self.create_transaction(flow='redirect')
|
||||
tx = self.create_transaction(flow='dummy', operation='validation')
|
||||
with mute_logger('odoo.addons.payment.models.payment_transaction'):
|
||||
processing_values = tx._get_processing_values()
|
||||
|
||||
|
||||
@@ -2,6 +2,31 @@
|
||||
<odoo>
|
||||
|
||||
<template id="redirect_form">
|
||||
<form t-att-action="api_url" method="post">
|
||||
<input type="hidden" name="PSPID" t-att-value="PSPID"/>
|
||||
<input type="hidden" name="ORDERID" t-att-value="ORDERID"/>
|
||||
<input type="hidden" name="AMOUNT" t-att-value="AMOUNT"/>
|
||||
<input type="hidden" name="CURRENCY" t-att-value="CURRENCY"/>
|
||||
<input type="hidden" name="LANGUAGE" t-att-value="LANGUAGE"/>
|
||||
<input type="hidden" name="EMAIL" t-att-value="EMAIL"/>
|
||||
<input type="hidden" name="OWNERADDRESS" t-att-value="OWNERADDRESS"/>
|
||||
<input type="hidden" name="OWNERZIP" t-att-value="OWNERZIP"/>
|
||||
<input type="hidden" name="OWNERTOWN" t-att-value="OWNERTOWN"/>
|
||||
<input type="hidden" name="OWNERCTY" t-att-value="OWNERCTY"/>
|
||||
<input type="hidden" name="OWNERTELNO" t-att-value="OWNERTELNO"/>
|
||||
<input type="hidden" name="OPERATION" t-att-value="OPERATION"/>
|
||||
<input type="hidden" name="USERID" t-att-value="USERID"/>
|
||||
<input type="hidden" name="ACCEPTURL" t-att-value="ACCEPTURL"/>
|
||||
<input type="hidden" name="DECLINEURL" t-att-value="DECLINEURL"/>
|
||||
<input type="hidden" name="EXCEPTIONURL" t-att-value="EXCEPTIONURL"/>
|
||||
<input type="hidden" name="CANCELURL" t-att-value="CANCELURL"/>
|
||||
<input type="hidden" name="ALIAS" t-att-value="ALIAS"/>
|
||||
<input type="hidden" name="ALIASUSAGE" t-att-value="ALIASUSAGE"/>
|
||||
<input type="hidden" name="SHASIGN" t-att-value="SHASIGN"/>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<template id="redirect_form_validation">
|
||||
<form t-att-action="api_url" method="post">
|
||||
<input type="hidden" name="ACCOUNT.PSPID" t-att-value="ACCOUNT_PSPID"/>
|
||||
<input type="hidden" name="ALIAS.ALIASID" t-att-value="ALIAS_ALIASID"/>
|
||||
|
||||
Reference in New Issue
Block a user