[IMP] payment(_adyen): support multiple payment forms on the same page

This commit addresses an issue with the checkout form displayed on the
portal page of the Subscriptions app: when only one payment provider was
available for checkout, and it required an inline form to be displayed,
the latter would not be shown, and customers were able to hit the "Pay"
button, resulting in a client error. The problem was that Subscriptions
now simultaneously displays two payment forms on the same page, one for
checkout and one for managing payment methods, which was not supported
by the payment engine.

closes odoo/odoo#120482

X-original-commit: 91d701e34a25d2ceada1679f36a9cdbb96c6c019
Signed-off-by: Antoine Vandevenne (anv) <anv@odoo.com>
This commit is contained in:
Antoine Vandevenne (anv)
2023-05-03 22:09:42 +02:00
parent bb6428825b
commit 328f64ab51
6 changed files with 39 additions and 19 deletions
@@ -19,6 +19,7 @@
* @constructor
*/
init: function () {
this.formType = 'checkout';
const preventDoubleClick = handlerMethod => {
return debounce(handlerMethod, 500, true);
};
@@ -62,9 +63,8 @@
// Update the tx context with the value of the "Save my payment details" checkbox
if (flow !== 'token') {
const $tokenizeCheckbox = this.$(
`#o_payment_provider_inline_form_${paymentOptionId}` // Only match provider radios
).find('input[name="o_payment_save_as_token"]');
const $inlineForm = this._getInlineFormFromRadio(checkedRadio);
const $tokenizeCheckbox = $inlineForm.find('input[name="o_payment_save_as_token"]');
this.txContext.tokenizationRequested = $tokenizeCheckbox.length === 1
&& $tokenizeCheckbox[0].checked;
} else {
+2 -1
View File
@@ -24,6 +24,7 @@ import { debounce } from "@web/core/utils/timing";
* @constructor
*/
init: function () {
this.formType = 'manage';
const preventDoubleClick = handlerMethod => {
return debounce(handlerMethod, 500, true);
};
@@ -136,7 +137,7 @@ import { debounce } from "@web/core/utils/timing";
`input[name="o_payment_radio"][data-payment-option-id="${tokenId}"]` +
`[data-payment-option-type="token"]`
).closest('div[name="o_payment_option_card"]');
$tokenCard.siblings(`#o_payment_token_inline_form_${tokenId}`).remove();
$tokenCard.siblings(`#o_payment_token_inline_manage_form_${tokenId}`).remove();
$tokenCard.remove();
this._disableButton(false);
}).guardedCatch(error => {
@@ -116,9 +116,7 @@
// Append error to inline form and center the page on the error
const checkedRadio = $checkedRadios[0];
const paymentOptionId = this._getPaymentOptionIdFromRadio(checkedRadio);
const formType = $(checkedRadio).data('payment-option-type');
const $inlineForm = this.$(`#o_payment_${formType}_inline_form_${paymentOptionId}`);
const $inlineForm = this._getInlineFormFromRadio(checkedRadio);
$inlineForm.removeClass('d-none'); // Show the inline form even if it was empty
$inlineForm.append(errorHtml).find('div[name="o_payment_error"]')[0]
.scrollIntoView({behavior: 'smooth', block: 'center'});
@@ -146,8 +144,7 @@
// Prepare the inline form of the selected payment option and display it if not empty
this._prepareInlineForm(provider, paymentOptionId, flow);
const formType = $(radio).data('payment-option-type');
const $inlineForm = this.$(`#o_payment_${formType}_inline_form_${paymentOptionId}`);
const $inlineForm = this._getInlineFormFromRadio(radio);
if ($inlineForm.children().length > 0) {
$inlineForm.removeClass('d-none');
}
@@ -198,6 +195,22 @@
return true;
},
/**
* Find and return the inline form of the selected payment option.
*
* @private
* @param {HTMLInputElement} radio - The radio button linked to the payment option.
* @return {jQuery} The inline form of the selected payment option.
*/
_getInlineFormFromRadio: function (radio) {
const paymentOptionId = this._getPaymentOptionIdFromRadio(radio);
const paymentOptionType = $(radio).data('payment-option-type');
const $inlineForm = this.$(
`#o_payment_${paymentOptionType}_inline_${this.formType}_form_${paymentOptionId}`
);
return $inlineForm;
},
/**
* Determine and return the online payment flow of the selected payment option.
*
@@ -247,23 +260,23 @@
},
/**
* Remove the error in the provider form.
* Remove the error in the inline form of the current widget.
*
* @private
* @return {jQuery} The removed error
*/
_hideError() {
return $('div[name="o_payment_error"]').remove();
return this.$('div[name="o_payment_error"]').remove();
},
/**
* Collapse all inline forms.
* Collapse all inline forms of the current widget.
*
* @private
* @return {undefined}.
*/
_hideInlineForms() {
return $('[name="o_payment_inline_form"]').addClass('d-none');
return this.$('[name="o_payment_inline_form"]').addClass('d-none');
},
/**
+8 -4
View File
@@ -97,7 +97,7 @@
</label>
</div>
<!-- === Token inline form === -->
<div t-attf-id="o_payment_token_inline_form_{{token.id}}"
<div t-attf-id="o_payment_token_inline_checkout_form_{{token.id}}"
name="o_payment_inline_form"
class="card-footer d-none">
<t t-set="token_inline_form_xml_id"
@@ -105,6 +105,7 @@
<!-- === Inline form content (filled by provider) === -->
<div t-if="token_inline_form_xml_id" class="clearfix">
<t t-call="{{token_inline_form_xml_id}}">
<t t-set="form_type" t-value="'checkout'"/>
<t t-set="token" t-value="token"/>
</t>
</div>
@@ -158,7 +159,7 @@
class="text-muted ms-3"/>
</div>
<!-- === Provider inline form === -->
<div t-attf-id="o_payment_provider_inline_form_{{provider.id}}"
<div t-attf-id="o_payment_provider_inline_checkout_form_{{provider.id}}"
name="o_payment_inline_form"
class="card-footer px-3 d-none">
<t t-if="provider.sudo()._should_build_inline_form(is_validation=False)">
@@ -167,6 +168,7 @@
<!-- === Inline form content (filled by provider) === -->
<div t-if="inline_form_xml_id" class="clearfix">
<t t-call="{{inline_form_xml_id}}">
<t t-set="form_type" t-value="'checkout'"/>
<t t-set="provider_id" t-value="provider.id"/>
</t>
</div>
@@ -264,7 +266,7 @@
</div>
</div>
<!-- === Token inline form === -->
<div t-attf-id="o_payment_token_inline_form_{{token.id}}"
<div t-attf-id="o_payment_token_inline_manage_form_{{token.id}}"
name="o_payment_inline_form"
class="card-footer d-none">
<t t-set="token_inline_form_xml_id"
@@ -272,6 +274,7 @@
<!-- === Inline form content (filled by provider) === -->
<div t-if="token_inline_form_xml_id" class="clearfix">
<t t-call="{{token_inline_form_xml_id}}">
<t t-set="form_type" t-value="'manage'"/>
<t t-set="token" t-value="token"/>
</t>
</div>
@@ -318,7 +321,7 @@
</div>
<!-- === Provider inline form === -->
<t t-if="provider.sudo()._should_build_inline_form(is_validation=True)">
<div t-attf-id="o_payment_provider_inline_form_{{provider.id}}"
<div t-attf-id="o_payment_provider_inline_manage_form_{{provider.id}}"
name="o_payment_inline_form"
class="card-footer d-none">
<t t-set="inline_form_xml_id"
@@ -326,6 +329,7 @@
<!-- === Inline form content (filled by provider) === -->
<div t-if="inline_form_xml_id" class="clearfix">
<t t-call="{{inline_form_xml_id}}">
<t t-set="form_type" t-value="'manage'"/>
<t t-set="provider_id" t-value="provider.id"/>
</t>
</div>
@@ -173,7 +173,7 @@
showPayButton: false,
setStatusAutomatically: true,
},
).mount(`#o_adyen_dropin_container_${paymentOptionId}`);
).mount(`#o_adyen_${this.formType}_dropin_container_${paymentOptionId}`);
this.adyenDropin.providerId = paymentOptionId;
});
}).guardedCatch((error) => {
@@ -2,7 +2,9 @@
<odoo>
<template id="inline_form">
<div t-attf-id="o_adyen_dropin_container_{{provider_id}}" class="o_adyen_dropin"/>
<div t-attf-id="o_adyen_{{form_type}}_dropin_container_{{provider_id}}"
class="o_adyen_dropin"
/>
</template>
</odoo>