diff --git a/addons/account_payment/static/src/js/payment_form.js b/addons/account_payment/static/src/js/payment_form.js index c15fffb386d..58f808a4976 100644 --- a/addons/account_payment/static/src/js/payment_form.js +++ b/addons/account_payment/static/src/js/payment_form.js @@ -1,33 +1,33 @@ /** @odoo-module **/ - import checkoutForm from "@payment/js/checkout_form"; - import manageForm from "@payment/js/manage_form"; +import checkoutForm from '@payment/js/checkout_form'; +import manageForm from '@payment/js/manage_form'; - const PaymentMixin = { +const PaymentMixin = { - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- - /** - * Add `invoice_id` to the transaction route params if it is provided. - * - * @override method from @payment/js/payment_form_mixin - * @private - * @param {string} code - The provider code of the selected payment option. - * @param {number} paymentOptionId - The id of the selected payment option. - * @param {string} flow - The online payment flow of the selected payment option. - * @return {object} The extended transaction route params. - */ - _prepareTransactionRouteParams: function (code, paymentOptionId, flow) { - const transactionRouteParams = this._super(...arguments); - return { - ...transactionRouteParams, - 'invoice_id': this.txContext.invoiceId ? parseInt(this.txContext.invoiceId) : null, - }; - }, + /** + * Add `invoice_id` to the transaction route params if it is provided. + * + * @override method from @payment/js/payment_form_mixin + * @private + * @param {string} code - The provider code of the selected payment option. + * @param {number} paymentOptionId - The id of the selected payment option. + * @param {string} flow - The online payment flow of the selected payment option. + * @return {object} The extended transaction route params. + */ + _prepareTransactionRouteParams: function (code, paymentOptionId, flow) { + const transactionRouteParams = this._super(...arguments); + return { + ...transactionRouteParams, + 'invoice_id': this.txContext.invoiceId ? parseInt(this.txContext.invoiceId) : null, + }; + }, - }; +}; - checkoutForm.include(PaymentMixin); - manageForm.include(PaymentMixin); +checkoutForm.include(PaymentMixin); +manageForm.include(PaymentMixin); diff --git a/addons/payment/static/src/js/checkout_form.js b/addons/payment/static/src/js/checkout_form.js index a1e89c1e2ae..32302fbc754 100644 --- a/addons/payment/static/src/js/checkout_form.js +++ b/addons/payment/static/src/js/checkout_form.js @@ -1,98 +1,98 @@ /** @odoo-module **/ - import publicWidget from "@web/legacy/js/public/public_widget"; +import publicWidget from '@web/legacy/js/public/public_widget'; +import paymentFormMixin from '@payment/js/payment_form_mixin'; +import { debounce } from '@web/core/utils/timing'; - import paymentFormMixin from "@payment/js/payment_form_mixin"; - import { debounce } from "@web/core/utils/timing"; +publicWidget.registry.PaymentCheckoutForm = publicWidget.Widget.extend(paymentFormMixin, { + selector: 'form[name="o_payment_checkout"]', + events: Object.assign({}, publicWidget.Widget.prototype.events, { + 'click div[name="o_payment_option_card"]': '_onClickPaymentOption', + 'click a[name="o_payment_icon_more"]': '_onClickMorePaymentIcons', + 'click a[name="o_payment_icon_less"]': '_onClickLessPaymentIcons', + 'click button[name="o_payment_submit_button"]': '_onClickPay', + 'submit': '_onSubmit', + }), - publicWidget.registry.PaymentCheckoutForm = publicWidget.Widget.extend(paymentFormMixin, { - selector: 'form[name="o_payment_checkout"]', - events: Object.assign({}, publicWidget.Widget.prototype.events, { - 'click div[name="o_payment_option_card"]': '_onClickPaymentOption', - 'click a[name="o_payment_icon_more"]': '_onClickMorePaymentIcons', - 'click a[name="o_payment_icon_less"]': '_onClickLessPaymentIcons', - 'click button[name="o_payment_submit_button"]': '_onClickPay', - 'submit': '_onSubmit', - }), + /** + * @constructor + */ + init: function () { + this.formType = 'checkout'; + const preventDoubleClick = handlerMethod => { + return debounce(handlerMethod, 500, true); + }; + this._super(...arguments); + // Prevent double-clicks and browser glitches on all inputs + this._onClickLessPaymentIcons = preventDoubleClick(this._onClickLessPaymentIcons); + this._onClickMorePaymentIcons = preventDoubleClick(this._onClickMorePaymentIcons); + this._onClickPay = preventDoubleClick(this._onClickPay); + this._onClickPaymentOption = preventDoubleClick(this._onClickPaymentOption); + this._onSubmit = preventDoubleClick(this._onSubmit); + }, - /** - * @constructor - */ - init: function () { - this.formType = 'checkout'; - const preventDoubleClick = handlerMethod => { - return debounce(handlerMethod, 500, true); - }; - this._super(...arguments); - // Prevent double-clicks and browser glitches on all inputs - this._onClickLessPaymentIcons = preventDoubleClick(this._onClickLessPaymentIcons); - this._onClickMorePaymentIcons = preventDoubleClick(this._onClickMorePaymentIcons); - this._onClickPay = preventDoubleClick(this._onClickPay); - this._onClickPaymentOption = preventDoubleClick(this._onClickPaymentOption); - this._onSubmit = preventDoubleClick(this._onSubmit); - }, + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- + /** + * Handle a direct payment, a payment with redirection, or a payment by token. + * + * Called when clicking on the 'Pay' button or when submitting the form. + * + * @private + * @param {Event} ev + * @return {undefined} + */ + _onClickPay: async function (ev) { + ev.stopPropagation(); + ev.preventDefault(); - /** - * Handle a direct payment, a payment with redirection, or a payment by token. - * - * Called when clicking on the 'Pay' button or when submitting the form. - * - * @private - * @param {Event} ev - * @return {undefined} - */ - _onClickPay: async function (ev) { - ev.stopPropagation(); - ev.preventDefault(); + // Check that the user has selected a payment option + const $checkedRadios = this.$('input[name="o_payment_radio"]:checked'); + if (!this._ensureRadioIsChecked($checkedRadios)) { + return; + } + const checkedRadio = $checkedRadios[0]; - // Check that the user has selected a payment option - const $checkedRadios = this.$('input[name="o_payment_radio"]:checked'); - if (!this._ensureRadioIsChecked($checkedRadios)) { - return; - } - const checkedRadio = $checkedRadios[0]; + // Extract contextual values from the radio button + const provider = this._getProviderFromRadio(checkedRadio); + const paymentOptionId = this._getPaymentOptionIdFromRadio(checkedRadio); + const flow = this._getPaymentFlowFromRadio(checkedRadio); - // Extract contextual values from the radio button - const provider = this._getProviderFromRadio(checkedRadio); - const paymentOptionId = this._getPaymentOptionIdFromRadio(checkedRadio); - const flow = this._getPaymentFlowFromRadio(checkedRadio); + // Update the tx context with the value of the "Save my payment details" checkbox + if (flow !== '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 { + this.txContext.tokenizationRequested = false; + } - // Update the tx context with the value of the "Save my payment details" checkbox - if (flow !== '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 { - this.txContext.tokenizationRequested = false; - } + // Make the payment + this._hideError(); // Don't keep the error displayed if the user is going through 3DS2 + this._disableButton(true); // Disable until it is needed again + this.call('ui', 'block'); + this._processPayment(provider, paymentOptionId, flow); + }, - // Make the payment - this._hideError(); // Don't keep the error displayed if the user is going through 3DS2 - this._disableButton(true); // Disable until it is needed again - this.call("ui", "block"); - this._processPayment(provider, paymentOptionId, flow); - }, + /** + * Delegate the handling of the payment request to `_onClickPay`. + * + * Called when submitting the form (e.g. through the Return key). + * + * @private + * @param {Event} ev + * @return {undefined} + */ + _onSubmit: function (ev) { + ev.stopPropagation(); + ev.preventDefault(); - /** - * Delegate the handling of the payment request to `_onClickPay`. - * - * Called when submitting the form (e.g. through the Return key). - * - * @private - * @param {Event} ev - * @return {undefined} - */ - _onSubmit: function (ev) { - ev.stopPropagation(); - ev.preventDefault(); + this._onClickPay(ev); + }, - this._onClickPay(ev); - }, +}); - }); - export default publicWidget.registry.PaymentCheckoutForm; +export default publicWidget.registry.PaymentCheckoutForm; diff --git a/addons/payment/static/src/js/express_checkout_form.js b/addons/payment/static/src/js/express_checkout_form.js index 028364f94da..7d4e86b03ac 100644 --- a/addons/payment/static/src/js/express_checkout_form.js +++ b/addons/payment/static/src/js/express_checkout_form.js @@ -1,6 +1,6 @@ /** @odoo-module */ -import core from "@web/legacy/js/services/core"; +import core from '@web/legacy/js/services/core'; import publicWidget from '@web/legacy/js/public/public_widget'; publicWidget.registry.PaymentExpressCheckoutForm = publicWidget.Widget.extend({ diff --git a/addons/payment/static/src/js/manage_form.js b/addons/payment/static/src/js/manage_form.js index f3182348cd1..ace737e3a38 100644 --- a/addons/payment/static/src/js/manage_form.js +++ b/addons/payment/static/src/js/manage_form.js @@ -1,224 +1,223 @@ /** @odoo-module **/ - import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog"; - import { renderToMarkup } from "@web/core/utils/render"; - import { _t } from "@web/core/l10n/translation"; - import publicWidget from "@web/legacy/js/public/public_widget"; +import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog"; +import { renderToMarkup } from "@web/core/utils/render"; +import { _t } from '@web/core/l10n/translation'; +import publicWidget from '@web/legacy/js/public/public_widget'; - import paymentFormMixin from "@payment/js/payment_form_mixin"; -import { debounce } from "@web/core/utils/timing"; +import paymentFormMixin from '@payment/js/payment_form_mixin'; +import { debounce } from '@web/core/utils/timing'; - publicWidget.registry.PaymentManageForm = publicWidget.Widget.extend(paymentFormMixin, { - selector: 'form[name="o_payment_manage"]', - events: Object.assign({}, publicWidget.Widget.prototype.events, { - 'click div[name="o_payment_option_card"]': '_onClickPaymentOption', - 'click a[name="o_payment_icon_more"]': '_onClickMorePaymentIcons', - 'click a[name="o_payment_icon_less"]': '_onClickLessPaymentIcons', - 'click button[name="o_payment_submit_button"]': '_onClickSaveToken', - 'click button[name="o_payment_delete_token"]': '_onClickDeleteToken', - 'submit': '_onSubmit', - }), +publicWidget.registry.PaymentManageForm = publicWidget.Widget.extend(paymentFormMixin, { + selector: 'form[name="o_payment_manage"]', + events: Object.assign({}, publicWidget.Widget.prototype.events, { + 'click div[name="o_payment_option_card"]': '_onClickPaymentOption', + 'click a[name="o_payment_icon_more"]': '_onClickMorePaymentIcons', + 'click a[name="o_payment_icon_less"]': '_onClickLessPaymentIcons', + 'click button[name="o_payment_submit_button"]': '_onClickSaveToken', + 'click button[name="o_payment_delete_token"]': '_onClickDeleteToken', + 'submit': '_onSubmit', + }), - /** - * @constructor - */ - init: function () { - this.formType = 'manage'; - const preventDoubleClick = handlerMethod => { - return debounce(handlerMethod, 500, true); - }; - this._super(...arguments); - // Prevent double-clicks and browser glitches on all inputs - this._onClickDeleteToken = preventDoubleClick(this._onClickDeleteToken); - this._onClickLessPaymentIcons = preventDoubleClick(this._onClickLessPaymentIcons); - this._onClickMorePaymentIcons = preventDoubleClick(this._onClickMorePaymentIcons); - this._onClickPaymentOption = preventDoubleClick(this._onClickPaymentOption); - this._onClickSaveToken = preventDoubleClick(this._onClickSaveToken); - this._onSubmit = preventDoubleClick(this._onSubmit); - }, + /** + * @constructor + */ + init: function () { + this.formType = 'manage'; + const preventDoubleClick = handlerMethod => { + return debounce(handlerMethod, 500, true); + }; + this._super(...arguments); + // Prevent double-clicks and browser glitches on all inputs + this._onClickDeleteToken = preventDoubleClick(this._onClickDeleteToken); + this._onClickLessPaymentIcons = preventDoubleClick(this._onClickLessPaymentIcons); + this._onClickMorePaymentIcons = preventDoubleClick(this._onClickMorePaymentIcons); + this._onClickPaymentOption = preventDoubleClick(this._onClickPaymentOption); + this._onClickSaveToken = preventDoubleClick(this._onClickSaveToken); + this._onSubmit = preventDoubleClick(this._onSubmit); + }, - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- - /** - * Assign the token to a record. - * - * @private - * @param {number} tokenId - The id of the token to assign - * @return {undefined} - */ - _assignToken: function (tokenId) { - // Call the assign route to assign the token to a record + /** + * Assign the token to a record. + * + * @private + * @param {number} tokenId - The id of the token to assign + * @return {undefined} + */ + _assignToken: function (tokenId) { + // Call the assign route to assign the token to a record + this._rpc({ + route: this.txContext['assignTokenRoute'], + params: { + 'access_token': this.txContext.accessToken, + 'token_id': tokenId, + } + }).then(() => { + window.location = this.txContext['landingRoute']; + }).guardedCatch(error => { + error.event.preventDefault(); + this._displayError( + _t("Server Error"), + _t("We are not able to save your payment method."), + error.message.data.message, + ); + }); + }, + + /** + * Build and open the confirmation dialog based on the linked records' information. + * + * @private + * @param {Array} linkedRecordsInfo - The list of information about linked records. + * @param confirmCallback - The callback method called when the user clicks on the confirmation + * button. + * @return {void} + */ + _openConfirmationDialog: function (linkedRecordsInfo, confirmCallback) { + const body = renderToMarkup('payment.ManageFormConfirmationDialog', { linkedRecordsInfo }); + this.call('dialog', 'add', ConfirmationDialog, { + title: _t("Warning!"), + body, + confirmLabel: _t("Confirm Deletion"), + confirm: confirmCallback, + cancel: () => {}, + }); + }, + + /** + * Search for documents linked to the token and ask the user for confirmation. + * + * If any such document is found, a confirmation dialog is shown. + * + * @private + * @param {number} tokenId - The id of the token to delete + * @return {undefined} + */ + _deleteToken: function (tokenId) { + const execute = () => { this._rpc({ - route: this.txContext.assignTokenRoute, + route: '/payment/archive_token', params: { - 'access_token': this.txContext.accessToken, 'token_id': tokenId, - } + }, }).then(() => { - window.location = this.txContext.landingRoute; + const $tokenCard = this.$( + `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_manage_form_${tokenId}`).remove(); + $tokenCard.remove(); + this._disableButton(false); }).guardedCatch(error => { error.event.preventDefault(); - this._displayError( - _t("Server Error"), - _t("We are not able to save your payment method."), - error.message.data.message - ); - }); - }, - - /** - * Build and open the confirmation dialog based on the linked records' information. - * - * @private - * @param {Array} linkedRecordsInfo - The list of information about linked records. - * @param confirmCallback - The callback method called when the user clicks on the - * confirmation button. - */ - _openConfirmationDialog: function (linkedRecordsInfo, confirmCallback) { - const body = renderToMarkup( - "payment.ManageFormConfirmationDialog", - { linkedRecordsInfo } - ); - this.call("dialog", "add", ConfirmationDialog, { - title: _t("Warning!"), - body, - confirmLabel: _t("Confirm Deletion"), - confirm: confirmCallback, - cancel: () => {}, - }); - }, - - /** - * Search for documents linked to the token and ask the user for confirmation. - * - * If any such document is found, a confirmation dialog is shown. - * - * @private - * @param {number} tokenId - The id of the token to delete - * @return {undefined} - */ - _deleteToken: function (tokenId) { - const execute = () => { - this._rpc({ - route: '/payment/archive_token', - params: { - 'token_id': tokenId, - }, - }).then(() => { - const $tokenCard = this.$( - `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_manage_form_${tokenId}`).remove(); - $tokenCard.remove(); - this._disableButton(false); - }).guardedCatch(error => { - error.event.preventDefault(); - this._displayError( - _t("Server Error"), - _t("We are not able to delete your payment method."), - error.message.data.message - ); - }); - }; - - // Fetch documents linked to the token - this._rpc({ - model: 'payment.token', - method: 'get_linked_records_info', - args: [tokenId], - }).then(linkedRecordsInfo => { - this._openConfirmationDialog(linkedRecordsInfo, execute); - }).guardedCatch(error => { this._displayError( _t("Server Error"), _t("We are not able to delete your payment method."), - error.message.data.message + error.message.data.message, ); }); - }, + }; - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- + // Fetch documents linked to the token + this._rpc({ + model: 'payment.token', + method: 'get_linked_records_info', + args: [tokenId], + }).then(linkedRecordsInfo => { + this._openConfirmationDialog(linkedRecordsInfo, execute); + }).guardedCatch(error => { + this._displayError( + _t("Server Error"), + _t("We are not able to delete your payment method."), + error.message.data.message, + ); + }); + }, - /** - * Find the radio button linked to the click 'Delete' button and trigger the token deletion. - * - * Let `_onClickPaymentOption` select the radio button and display the inline form. - * - * Called when clicking on the 'Delete' button of a token. - * - * @private - * @param {Event} ev - * @return {undefined} - */ - _onClickDeleteToken: function (ev) { - ev.preventDefault(); + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- - // Extract contextual values from the delete button - const linkedRadio = $(ev.currentTarget).siblings().find('input[name="o_payment_radio"]')[0]; - const tokenId = this._getPaymentOptionIdFromRadio(linkedRadio); + /** + * Find the radio button linked to the click 'Delete' button and trigger the token deletion. + * + * Let `_onClickPaymentOption` select the radio button and display the inline form. + * + * Called when clicking on the 'Delete' button of a token. + * + * @private + * @param {Event} ev + * @return {undefined} + */ + _onClickDeleteToken: function (ev) { + ev.preventDefault(); - // Delete the token - this._deleteToken(tokenId); - }, + // Extract contextual values from the delete button + const linkedRadio = $(ev.currentTarget).siblings().find('input[name="o_payment_radio"]')[0]; + const tokenId = this._getPaymentOptionIdFromRadio(linkedRadio); - /** - * Handle the creation of a new token or the assignation of a token to a record. - * - * Called when clicking on the 'Save Payment Method' button of when submitting the form. - * - * @private - * @param {Event} ev - * @return {undefined} - */ - _onClickSaveToken: async function (ev) { - ev.stopPropagation(); - ev.preventDefault(); + // Delete the token + this._deleteToken(tokenId); + }, - // Check that the user has selected a payment option - const $checkedRadios = this.$('input[name="o_payment_radio"]:checked'); - if (!this._ensureRadioIsChecked($checkedRadios)) { - return; - } - const checkedRadio = $checkedRadios[0]; + /** + * Handle the creation of a new token or the assignation of a token to a record. + * + * Called when clicking on the 'Save Payment Method' button of when submitting the form. + * + * @private + * @param {Event} ev + * @return {undefined} + */ + _onClickSaveToken: async function (ev) { + ev.stopPropagation(); + ev.preventDefault(); - // Extract contextual values from the radio button - const provider = this._getProviderFromRadio(checkedRadio); - const paymentOptionId = this._getPaymentOptionIdFromRadio(checkedRadio); - const flow = this._getPaymentFlowFromRadio(checkedRadio); + // Check that the user has selected a payment option + const $checkedRadios = this.$('input[name="o_payment_radio"]:checked'); + if (!this._ensureRadioIsChecked($checkedRadios)) { + return; + } + const checkedRadio = $checkedRadios[0]; - // Save the payment method - this._hideError(); // Don't keep the error displayed if the user is going through 3DS2 - this._disableButton(true); // Disable until it is needed again - this.call("ui", "block"); - if (flow !== 'token') { // Creation of a new token - this.txContext.tokenizationRequested = true; - this.txContext.isValidation = true; - this._processPayment(provider, paymentOptionId, flow); - } else if (this.txContext.allowTokenSelection) { // Assignation of a token to a record - this._assignToken(paymentOptionId); - } - }, + // Extract contextual values from the radio button + const provider = this._getProviderFromRadio(checkedRadio); + const paymentOptionId = this._getPaymentOptionIdFromRadio(checkedRadio); + const flow = this._getPaymentFlowFromRadio(checkedRadio); - /** - * Delegate the handling of the token to `_onClickSaveToken`. - * - * Called when submitting the form (e.g. through the Return key). - * - * @private - * @param {Event} ev - * @return {undefined} - */ - _onSubmit: function (ev) { - ev.stopPropagation(); - ev.preventDefault(); + // Save the payment method + this._hideError(); // Don't keep the error displayed if the user is going through 3DS2 + this._disableButton(true); // Disable until it is needed again + this.call('ui', 'block'); + if (flow !== 'token') { // Creation of a new token + this.txContext.tokenizationRequested = true; + this.txContext.isValidation = true; + this._processPayment(provider, paymentOptionId, flow); + } else if (this.txContext['allowTokenSelection']) { // Assignation of a token to a record + this._assignToken(paymentOptionId); + } + }, - this._onClickSaveToken(ev); - }, + /** + * Delegate the handling of the token to `_onClickSaveToken`. + * + * Called when submitting the form (e.g. through the Return key). + * + * @private + * @param {Event} ev + * @return {undefined} + */ + _onSubmit: function (ev) { + ev.stopPropagation(); + ev.preventDefault(); - }); - export default publicWidget.registry.PaymentManageForm; + this._onClickSaveToken(ev); + }, + +}); + +export default publicWidget.registry.PaymentManageForm; diff --git a/addons/payment/static/src/js/payment_form_mixin.js b/addons/payment/static/src/js/payment_form_mixin.js index 1a9fd2b4180..a58a50f26c7 100644 --- a/addons/payment/static/src/js/payment_form_mixin.js +++ b/addons/payment/static/src/js/payment_form_mixin.js @@ -1,62 +1,63 @@ /** @odoo-module **/ - import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog"; - import { escape } from "@web/core/utils/strings"; - import core from "@web/legacy/js/services/core"; - import { _t } from "@web/core/l10n/translation"; + +import { ConfirmationDialog } from '@web/core/confirmation_dialog/confirmation_dialog'; +import { escape } from '@web/core/utils/strings'; +import core from '@web/legacy/js/services/core'; +import { _t } from '@web/core/l10n/translation'; export default { - /** - * @override - */ - start: async function () { - this.txContext = {}; // Synchronously initialize txContext before any await. - Object.assign(this.txContext, this.$el.data()); - await this._super(...arguments); - window.addEventListener('pageshow', function (event) { - if (event.persisted) { - window.location.reload(); - } - }); - this.$('[data-bs-toggle="tooltip"]').tooltip(); - const $checkedRadios = this.$('input[name="o_payment_radio"]:checked'); - if ($checkedRadios.length === 1) { - const checkedRadio = $checkedRadios[0]; - this._displayInlineForm(checkedRadio); - this._enableButton(); - } else { - this._setPaymentFlow(); // Initialize the payment flow to let providers overwrite it + /** + * @override + */ + start: async function () { + this.txContext = {}; // Synchronously initialize txContext before any await. + Object.assign(this.txContext, this.$el.data()); + await this._super(...arguments); + window.addEventListener('pageshow', function (event) { + if (event.persisted) { + window.location.reload(); } - // When a module wants to activate the button, - // it must test its conditions and then call this bus. - core.bus.on('enableButton', this, this._enableButton); - }, + }); + this.$('[data-bs-toggle="tooltip"]').tooltip(); + const $checkedRadios = this.$('input[name="o_payment_radio"]:checked'); + if ($checkedRadios.length === 1) { + const checkedRadio = $checkedRadios[0]; + this._displayInlineForm(checkedRadio); + this._enableButton(); + } else { + this._setPaymentFlow(); // Initialize the payment flow to let providers overwrite it + } + // When a module wants to activate the button, + // it must test its conditions and then call this bus. + core.bus.on('enableButton', this, this._enableButton); + }, - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- - /** - * Disable the submit button. - * - * The icons are updated to either show that an action is processing or that the button is - * not ready, depending on the value of `showLoadingAnimation`. - * - * @private - * @param {boolean} showLoadingAnimation - Whether a spinning loader should be shown - * @return {undefined} - */ - _disableButton(showLoadingAnimation = true) { - const $submitButton = $('button[name="o_payment_submit_button"]'); - const iconClass = $submitButton.data('icon-class'); - $submitButton.attr('disabled', true); - if (showLoadingAnimation) { - $submitButton.find('i').removeClass(iconClass); - $submitButton.prepend( - ' ' - ); - } - }, + /** + * Disable the submit button. + * + * The icons are updated to either show that an action is processing or that the button is + * not ready, depending on the value of `showLoadingAnimation`. + * + * @private + * @param {boolean} showLoadingAnimation - Whether a spinning loader should be shown + * @return {undefined} + */ + _disableButton(showLoadingAnimation = true) { + const $submitButton = $('button[name="o_payment_submit_button"]'); + const iconClass = $submitButton.data('icon-class'); + $submitButton.attr('disabled', true); + if (showLoadingAnimation) { + $submitButton.find('i').removeClass(iconClass); + $submitButton.prepend( + ' ' + ); + } + }, /** * Display an error in the payment form. @@ -69,485 +70,486 @@ * @param {string} title - The title of the error * @param {string} description - The description of the error * @param {string} error - The raw error message + * @return {void} */ _displayError: function (title, description = '', error = '') { const $checkedRadios = this.$('input[name="o_payment_radio"]:checked'); if ($checkedRadios.length !== 1) { // Cannot find selected payment option, show dialog - this.call("dialog", "add", ConfirmationDialog, { + this.call('dialog', 'add', ConfirmationDialog, { title: _t("Error: %s", title), - body: description || '', + body: description || "", }); } else { // Show error in inline form this._hideError(); // Remove any previous error - // Build the html for the error - let errorHtml = `
- ${escape(title)}`; - if (description !== '') { - errorHtml += `
${escape(description)}`; - } - if (error !== '') { - errorHtml += `
${escape(error)}`; - } - errorHtml += '
'; - - // Append error to inline form and center the page on the error - const checkedRadio = $checkedRadios[0]; - 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'}); + // Build the html for the error + let errorHtml = `
+ ${escape(title)}`; + if (description !== '') { + errorHtml += `
${escape(description)}`; } - this._enableButton(); // Enable button back after it was disabled before processing - this.call("ui", "unblock"); // The page is blocked at this point, unblock it - }, - - /** - * Display the inline form of the selected payment option and hide others. - * - * @private - * @param {HTMLInputElement} radio - The radio button linked to the payment option - * @return {undefined} - */ - _displayInlineForm: function (radio) { - this._hideInlineForms(); // Collapse previously opened inline forms - this._hideError(); // The error is only relevant until it is hidden with its inline form - this._setPaymentFlow(); // Reset the payment flow to let providers overwrite it - - // Extract contextual values from the radio button - const provider = this._getProviderFromRadio(radio); - const paymentOptionId = this._getPaymentOptionIdFromRadio(radio); - const flow = this._getPaymentFlowFromRadio(radio); - - // Prepare the inline form of the selected payment option and display it if not empty - this._prepareInlineForm(provider, paymentOptionId, flow); - const $inlineForm = this._getInlineFormFromRadio(radio); - if ($inlineForm.children().length > 0) { - $inlineForm.removeClass('d-none'); + if (error !== '') { + errorHtml += `
${escape(error)}`; } - }, + errorHtml += '
'; - /** - * Check if the submit button can be enabled and do it if so. - * - * The icons are updated to show that the button is ready. - * - * @private - * @return {boolean} Whether the button was enabled. - */ - _enableButton: function () { - if (this._isButtonReady()) { - const $submitButton = this.$('button[name="o_payment_submit_button"]'); - const iconClass = $submitButton.data('icon-class'); - $submitButton.attr('disabled', false); - $submitButton.find('i').addClass(iconClass); - $submitButton.find('span.o_loader').remove(); - return true; - } - return false; - }, + // Append error to inline form and center the page on the error + const checkedRadio = $checkedRadios[0]; + 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'}); + } + this._enableButton(); // Enable button back after it was disabled before processing + this.call('ui', 'unblock'); // The page is blocked at this point, unblock it + }, - /** - * Verify that exactly one radio button is checked and display an error otherwise. - * - * @private - * @param {jQuery} $checkedRadios - The currently check radio buttons - * - * @return {boolean} Whether exactly one radio button among the provided radios is checked - */ - _ensureRadioIsChecked: function ($checkedRadios) { - if ($checkedRadios.length === 0) { - this._displayError( - _t("No payment option selected"), - _t("Please select a payment option.") - ); - return false; - } else if ($checkedRadios.length > 1) { - this._displayError( - _t("Multiple payment options selected"), - _t("Please select only one payment option.") - ); - return false; - } + /** + * Display the inline form of the selected payment option and hide others. + * + * @private + * @param {HTMLInputElement} radio - The radio button linked to the payment option + * @return {undefined} + */ + _displayInlineForm: function (radio) { + this._hideInlineForms(); // Collapse previously opened inline forms + this._hideError(); // The error is only relevant until it is hidden with its inline form + this._setPaymentFlow(); // Reset the payment flow to let providers overwrite it + + // Extract contextual values from the radio button + const provider = this._getProviderFromRadio(radio); + const paymentOptionId = this._getPaymentOptionIdFromRadio(radio); + const flow = this._getPaymentFlowFromRadio(radio); + + // Prepare the inline form of the selected payment option and display it if not empty + this._prepareInlineForm(provider, paymentOptionId, flow); + const $inlineForm = this._getInlineFormFromRadio(radio); + if ($inlineForm.children().length > 0) { + $inlineForm.removeClass('d-none'); + } + }, + + /** + * Check if the submit button can be enabled and do it if so. + * + * The icons are updated to show that the button is ready. + * + * @private + * @return {boolean} Whether the button was enabled. + */ + _enableButton: function () { + if (this._isButtonReady()) { + const $submitButton = this.$('button[name="o_payment_submit_button"]'); + const iconClass = $submitButton.data('icon-class'); + $submitButton.attr('disabled', false); + $submitButton.find('i').addClass(iconClass); + $submitButton.find('span.o_loader').remove(); return true; - }, + } + return false; + }, - /** - * 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}` + /** + * Verify that exactly one radio button is checked and display an error otherwise. + * + * @private + * @param {jQuery} $checkedRadios - The currently check radio buttons + * + * @return {boolean} Whether exactly one radio button among the provided radios is checked + */ + _ensureRadioIsChecked: function ($checkedRadios) { + if ($checkedRadios.length === 0) { + this._displayError( + _t("No payment option selected"), + _t("Please select a payment option.") ); - return $inlineForm; - }, - - /** - * Determine and return the online payment flow of the selected payment option. - * - * As some providers implement both the direct payment and the payment with redirection, the - * flow cannot be inferred from the radio button only. The radio button only indicates - * whether the payment option is a token. If not, the transaction context is looked up to - * determine whether the flow is 'direct' or 'redirect'. - * - * @private - * @param {HTMLInputElement} radio - The radio button linked to the payment option - * @return {string} The flow of the selected payment option. redirect, direct or token. - */ - _getPaymentFlowFromRadio: function (radio) { - if ( - $(radio).data('payment-option-type') === 'token' - || this.txContext.flow === 'token' - ) { - return 'token'; - } else if (this.txContext.flow === 'redirect') { - return 'redirect'; - } else { - return 'direct'; - } - }, - - /** - * Determine and return the id of the selected payment option. - * - * @private - * @param {HTMLInputElement} radio - The radio button linked to the payment option - * @return {number} The provider id or the token id or of the payment option linked to the - * radio button. - */ - _getPaymentOptionIdFromRadio(radio) { - return $(radio).data('payment-option-id'); - }, - - /** - * Determine and return the provider of the selected payment option. - * - * @private - * @param {HTMLInputElement} radio - The radio button linked to the payment option - * @return {number} The provider of the payment option linked to the radio button. - */ - _getProviderFromRadio(radio) { - return $(radio).data('provider'); - }, - - /** - * Remove the error in the inline form of the current widget. - * - * @private - * @return {jQuery} The removed error - */ - _hideError() { - return this.$('div[name="o_payment_error"]').remove(); - }, - - /** - * Collapse all inline forms of the current widget. - * - * @private - * @return {undefined}. - */ - _hideInlineForms() { - return this.$('[name="o_payment_inline_form"]').addClass('d-none'); - }, - - /** - * Hide the "Save my payment details" label and checkbox, and the submit button. - * - * The inputs should typically be hidden when the customer has to perform additional actions - * in the inline form. All inputs are automatically shown again when the customer clicks on - * another inline form. - * - * @private - * @return {undefined} - */ - _hideInputs: function () { - const $submitButton = this.$('button[name="o_payment_submit_button"]'); - const $tokenizeCheckboxes = this.$('input[name="o_payment_save_as_token"]'); - $submitButton.addClass('d-none'); - $tokenizeCheckboxes.closest('label').addClass('d-none'); - }, - - /** - * Verify that the submit button is ready to be enabled. - * - * For a module to support a custom behavior for the submit button, it must override this - * method and only return true if the result of this method is true and if nothing prevents - * enabling the submit button for that custom behavior. - * - * @private - * - * @return {boolean} Whether the submit button can be enabled - */ - _isButtonReady: function () { - const $checkedRadios = this.$('input[name="o_payment_radio"]:checked'); - if ($checkedRadios.length === 1) { - const checkedRadio = $checkedRadios[0]; - const flow = this._getPaymentFlowFromRadio(checkedRadio); - return flow !== 'token' || this.txContext.allowTokenSelection; - } else { - return false; - } - }, - - /** - * Prepare the params to send to the transaction route. - * - * For a provider to overwrite generic params or to add provider-specific ones, it must - * override this method and return the extended transaction route params. - * - * @private - * @param {string} code - The code of the selected payment option provider - * @param {number} paymentOptionId - The id of the selected payment option - * @param {string} flow - The online payment flow of the selected payment option - * @return {object} The transaction route params - */ - _prepareTransactionRouteParams: function (code, paymentOptionId, flow) { - return { - 'payment_option_id': paymentOptionId, - 'reference_prefix': this.txContext.referencePrefix !== undefined - ? this.txContext.referencePrefix.toString() : null, - 'amount': this.txContext.amount !== undefined - ? parseFloat(this.txContext.amount) : null, - 'currency_id': this.txContext.currencyId - ? parseInt(this.txContext.currencyId) : null, - 'partner_id': parseInt(this.txContext.partnerId), - 'flow': flow, - 'tokenization_requested': this.txContext.tokenizationRequested, - 'landing_route': this.txContext.landingRoute, - 'is_validation': this.txContext.isValidation, - 'access_token': this.txContext.accessToken - ? this.txContext.accessToken : undefined, - 'csrf_token': odoo.csrf_token, - }; - }, - - /** - * Prepare the provider-specific inline form of the selected payment option. - * - * For a provider to manage an inline form, it must override this method. When the override - * is called, it must lookup the parameters to decide whether it is necessary to prepare its - * inline form. Otherwise, the call must be sent back to the parent method. - * - * @private - * @param {string} code - The code of the selected payment option's provider - * @param {number} paymentOptionId - The id of the selected payment option - * @param {string} flow - The online payment flow of the selected payment option - * @return {Promise} - */ - _prepareInlineForm(code, paymentOptionId, flow) { - return Promise.resolve(); - }, - - /** - * Process the payment. - * - * For a provider to do pre-processing work on the transaction processing flow, or to - * define its entire own flow that requires re-scheduling the RPC to the transaction route, - * it must override this method. - * If only post-processing work is needed, an override of `_processRedirectPayment`, - * `_processDirectPayment` or `_processTokenPayment` might be more appropriate. - * - * @private - * @param {string} code - The code of the payment option's provider - * @param {number} paymentOptionId - The id of the payment option handling the transaction - * @param {string} flow - The online payment flow of the transaction - * @return {Promise} - */ - _processPayment: function (code, paymentOptionId, flow) { - // Call the transaction route to create a tx and retrieve the processing values - return this._rpc({ - route: this.txContext.transactionRoute, - params: this._prepareTransactionRouteParams(code, paymentOptionId, flow), - }).then(processingValues => { - if (flow === 'redirect') { - return this._processRedirectPayment( - code, paymentOptionId, processingValues - ); - } else if (flow === 'direct') { - return this._processDirectPayment(code, paymentOptionId, processingValues); - } else if (flow === 'token') { - return this._processTokenPayment(code, paymentOptionId, processingValues); - } - }).guardedCatch(error => { - error.event.preventDefault(); - this._displayError( - _t("Server Error"), - _t("We are not able to process your payment."), - error.message.data.message - ); - }); - }, - - /** - * Execute the provider-specific implementation of the direct payment flow. - * - * For a provider to redefine the processing of the direct payment flow, it must override - * this method. - * - * @private - * @param {string} code - The code of the provider - * @param {number} providerId - The id of the provider handling the transaction - * @param {object} processingValues - The processing values of the transaction - * @return {Promise} - */ - _processDirectPayment(code, providerId, processingValues) { - return Promise.resolve(); - }, - - /** - * Redirect the customer by submitting the redirect form included in the processing values. - * - * For a provider to redefine the processing of the payment with redirection flow, it must - * override this method. - * - * @private - * @param {string} code - The code of the provider - * @param {number} providerId - The id of the provider handling the transaction - * @param {object} processingValues - The processing values of the transaction - * @return {undefined} - */ - _processRedirectPayment(code, providerId, processingValues) { - // Append the redirect form to the body - const $redirectForm = $(processingValues.redirect_form_html).attr( - 'id', 'o_payment_redirect_form' + return false; + } else if ($checkedRadios.length > 1) { + this._displayError( + _t("Multiple payment options selected"), + _t("Please select only one payment option.") ); - // Ensures external redirections when in an iframe. - $redirectForm[0].setAttribute('target', '_top'); - $(document.getElementsByTagName('body')[0]).append($redirectForm); + return false; + } + return true; + }, - // Submit the form - $redirectForm.submit(); - }, + /** + * 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(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; + }, - /** - * Redirect the customer to the status route. - * - * For a provider to redefine the processing of the payment by token flow, it must override - * this method. - * - * @private - * @param {string} provider_code - The code of the token's provider - * @param {number} tokenId - The id of the token handling the transaction - * @param {object} processingValues - The processing values of the transaction - * @return {undefined} - */ - _processTokenPayment(provider_code, tokenId, processingValues) { - // The flow is already completed as payments by tokens are immediately processed - window.location = '/payment/status'; - }, + /** + * Determine and return the online payment flow of the selected payment option. + * + * As some providers implement both the direct payment and the payment with redirection, the + * flow cannot be inferred from the radio button only. The radio button only indicates + * whether the payment option is a token. If not, the transaction context is looked up to + * determine whether the flow is 'direct' or 'redirect'. + * + * @private + * @param {HTMLInputElement} radio - The radio button linked to the payment option + * @return {string} The flow of the selected payment option. redirect, direct or token. + */ + _getPaymentFlowFromRadio: function (radio) { + if ( + $(radio).data('payment-option-type') === 'token' + || this.txContext.flow === 'token' + ) { + return 'token'; + } else if (this.txContext.flow === 'redirect') { + return 'redirect'; + } else { + return 'direct'; + } + }, - /** - * Set the online payment flow for the selected payment option. - * - * For a provider to manage direct payments, it must call this method from within its - * override of `_prepareInlineForm` to declare its payment flow for the selected payment - * option. - * - * @private - * @param {string} flow - The flow for the selected payment option. Either 'redirect', - * 'direct' or 'token' - * @return {undefined} - */ - _setPaymentFlow: function (flow = 'redirect') { - if (flow !== 'redirect' && flow !== 'direct' && flow !== 'token') { - console.warn( - `payment_form_mixin: method '_setPaymentFlow' was called with invalid flow: - ${flow}. Falling back to 'redirect'.` + /** + * Determine and return the id of the selected payment option. + * + * @private + * @param {HTMLInputElement} radio - The radio button linked to the payment option + * @return {number} The provider id or the token id or of the payment option linked to the + * radio button. + */ + _getPaymentOptionIdFromRadio(radio) { + return $(radio).data('payment-option-id'); + }, + + /** + * Determine and return the provider of the selected payment option. + * + * @private + * @param {HTMLInputElement} radio - The radio button linked to the payment option + * @return {number} The provider of the payment option linked to the radio button. + */ + _getProviderFromRadio(radio) { + return $(radio).data('provider'); + }, + + /** + * Remove the error in the inline form of the current widget. + * + * @private + * @return {jQuery} The removed error + */ + _hideError() { + return this.$('div[name="o_payment_error"]').remove(); + }, + + /** + * Collapse all inline forms of the current widget. + * + * @private + * @return {undefined}. + */ + _hideInlineForms() { + return this.$('[name="o_payment_inline_form"]').addClass('d-none'); + }, + + /** + * Hide the "Save my payment details" label and checkbox, and the submit button. + * + * The inputs should typically be hidden when the customer has to perform additional actions + * in the inline form. All inputs are automatically shown again when the customer clicks on + * another inline form. + * + * @private + * @return {undefined} + */ + _hideInputs: function () { + const $submitButton = this.$('button[name="o_payment_submit_button"]'); + const $tokenizeCheckboxes = this.$('input[name="o_payment_save_as_token"]'); + $submitButton.addClass('d-none'); + $tokenizeCheckboxes.closest('label').addClass('d-none'); + }, + + /** + * Verify that the submit button is ready to be enabled. + * + * For a module to support a custom behavior for the submit button, it must override this + * method and only return true if the result of this method is true and if nothing prevents + * enabling the submit button for that custom behavior. + * + * @private + * + * @return {boolean} Whether the submit button can be enabled + */ + _isButtonReady: function () { + const $checkedRadios = this.$('input[name="o_payment_radio"]:checked'); + if ($checkedRadios.length === 1) { + const checkedRadio = $checkedRadios[0]; + const flow = this._getPaymentFlowFromRadio(checkedRadio); + return flow !== 'token' || this.txContext['allowTokenSelection']; + } else { + return false; + } + }, + + /** + * Prepare the params to send to the transaction route. + * + * For a provider to overwrite generic params or to add provider-specific ones, it must + * override this method and return the extended transaction route params. + * + * @private + * @param {string} code - The code of the selected payment option provider + * @param {number} paymentOptionId - The id of the selected payment option + * @param {string} flow - The online payment flow of the selected payment option + * @return {object} The transaction route params + */ + _prepareTransactionRouteParams: function (code, paymentOptionId, flow) { + return { + 'payment_option_id': paymentOptionId, + 'reference_prefix': this.txContext['referencePrefix'] !== undefined + ? this.txContext['referencePrefix'].toString() : null, + 'amount': this.txContext.amount !== undefined + ? parseFloat(this.txContext.amount) : null, + 'currency_id': this.txContext.currencyId + ? parseInt(this.txContext.currencyId) : null, + 'partner_id': parseInt(this.txContext.partnerId), + 'flow': flow, + 'tokenization_requested': this.txContext.tokenizationRequested, + 'landing_route': this.txContext['landingRoute'], + 'is_validation': this.txContext.isValidation, + 'access_token': this.txContext.accessToken + ? this.txContext.accessToken : undefined, + 'csrf_token': odoo.csrf_token, + }; + }, + + /** + * Prepare the provider-specific inline form of the selected payment option. + * + * For a provider to manage an inline form, it must override this method. When the override + * is called, it must lookup the parameters to decide whether it is necessary to prepare its + * inline form. Otherwise, the call must be sent back to the parent method. + * + * @private + * @param {string} code - The code of the selected payment option's provider + * @param {number} paymentOptionId - The id of the selected payment option + * @param {string} flow - The online payment flow of the selected payment option + * @return {Promise} + */ + _prepareInlineForm(code, paymentOptionId, flow) { + return Promise.resolve(); + }, + + /** + * Process the payment. + * + * For a provider to do pre-processing work on the transaction processing flow, or to + * define its entire own flow that requires re-scheduling the RPC to the transaction route, + * it must override this method. + * If only post-processing work is needed, an override of `_processRedirectPayment`, + * `_processDirectPayment` or `_processTokenPayment` might be more appropriate. + * + * @private + * @param {string} code - The code of the payment option's provider + * @param {number} paymentOptionId - The id of the payment option handling the transaction + * @param {string} flow - The online payment flow of the transaction + * @return {Promise} + */ + _processPayment: function (code, paymentOptionId, flow) { + // Call the transaction route to create a tx and retrieve the processing values + return this._rpc({ + route: this.txContext['transactionRoute'], + params: this._prepareTransactionRouteParams(code, paymentOptionId, flow), + }).then(processingValues => { + if (flow === 'redirect') { + return this._processRedirectPayment( + code, paymentOptionId, processingValues ); - this.txContext.flow = 'redirect'; - } else { - this.txContext.flow = flow; + } else if (flow === 'direct') { + return this._processDirectPayment(code, paymentOptionId, processingValues); + } else if (flow === 'token') { + return this._processTokenPayment(code, paymentOptionId, processingValues); } - }, + }).guardedCatch(error => { + error.event.preventDefault(); + this._displayError( + _t("Server Error"), + _t("We are not able to process your payment."), + error.message.data.message, + ); + }); + }, - /** - * Show the "Save my payment details" label and checkbox, and the submit button. - * - * @private - * @return {undefined}. - */ - _showInputs: function () { - const $submitButton = this.$('button[name="o_payment_submit_button"]'); - const $tokenizeCheckboxes = this.$('input[name="o_payment_save_as_token"]'); - $submitButton.removeClass('d-none'); - $tokenizeCheckboxes.closest('label').removeClass('d-none'); - }, + /** + * Execute the provider-specific implementation of the direct payment flow. + * + * For a provider to redefine the processing of the direct payment flow, it must override + * this method. + * + * @private + * @param {string} code - The code of the provider + * @param {number} providerId - The id of the provider handling the transaction + * @param {object} processingValues - The processing values of the transaction + * @return {Promise} + */ + _processDirectPayment(code, providerId, processingValues) { + return Promise.resolve(); + }, - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- + /** + * Redirect the customer by submitting the redirect form included in the processing values. + * + * For a provider to redefine the processing of the payment with redirection flow, it must + * override this method. + * + * @private + * @param {string} code - The code of the provider + * @param {number} providerId - The id of the provider handling the transaction + * @param {object} processingValues - The processing values of the transaction + * @return {undefined} + */ + _processRedirectPayment(code, providerId, processingValues) { + // Append the redirect form to the body + const $redirectForm = $(processingValues['redirect_form_html']).attr( + 'id', 'o_payment_redirect_form' + ); + // Ensures external redirections when in an iframe. + $redirectForm[0].setAttribute('target', '_top'); + $(document.getElementsByTagName('body')[0]).append($redirectForm); - /** - * Hide all extra payment method icons of the provider linked to the clicked button. - * - * Called when clicking on the "show less" button. - * - * @private - * @param {Event} ev - * @return {undefined} - */ - _onClickLessPaymentIcons(ev) { - ev.preventDefault(); - ev.stopPropagation(); - // Hide the extra payment icons, and the "show less" button - const $itemList = $(ev.currentTarget).parents('ul'); - const maxIconNumber = $itemList.data('max-icons-displayed'); - $itemList.children('li').slice(maxIconNumber).addClass('d-none'); - // Show the "show more" button - $itemList.find('a[name="o_payment_icon_more"]').parents('li').removeClass('d-none'); - }, + // Submit the form + $redirectForm.submit(); + }, - /** - * Display all the payment methods icons of the provider linked to the clicked button. - * - * Called when clicking on the "show more" button. - * - * @private - * @param {Event} ev - * @return {undefined} - */ - _onClickMorePaymentIcons(ev) { - ev.preventDefault(); - ev.stopPropagation(); - // Display all the payment methods icons, and the "show less" button - $(ev.currentTarget).parents('ul').children('li').removeClass('d-none'); - // Hide the "show more" button - $(ev.currentTarget).parents('li').addClass('d-none'); - }, + /** + * Redirect the customer to the status route. + * + * For a provider to redefine the processing of the payment by token flow, it must override + * this method. + * + * @private + * @param {string} provider_code - The code of the token's provider + * @param {number} tokenId - The id of the token handling the transaction + * @param {object} processingValues - The processing values of the transaction + * @return {undefined} + */ + _processTokenPayment(provider_code, tokenId, processingValues) { + // The flow is already completed as payments by tokens are immediately processed + window.location = '/payment/status'; + }, - /** - * Mark the clicked card radio button as checked and open the inline form, if any. - * - * Called when clicking on the card of a payment option. - * - * @private - * @param {Event} ev - * @return {undefined} - */ - _onClickPaymentOption: function (ev) { - // Uncheck all radio buttons - this.$('input[name="o_payment_radio"]').prop('checked', false); - // Check radio button linked to selected payment option - const checkedRadio = $(ev.currentTarget).find('input[name="o_payment_radio"]')[0]; - $(checkedRadio).prop('checked', true); + /** + * Set the online payment flow for the selected payment option. + * + * For a provider to manage direct payments, it must call this method from within its + * override of `_prepareInlineForm` to declare its payment flow for the selected payment + * option. + * + * @private + * @param {string} flow - The flow for the selected payment option. Either 'redirect', + * 'direct' or 'token' + * @return {undefined} + */ + _setPaymentFlow: function (flow = 'redirect') { + if (flow !== 'redirect' && flow !== 'direct' && flow !== 'token') { + console.warn( + `payment_form_mixin: method '_setPaymentFlow' was called with invalid flow: + ${flow}. Falling back to 'redirect'.` + ); + this.txContext.flow = 'redirect'; + } else { + this.txContext.flow = flow; + } + }, - // Show the inputs in case they had been hidden - this._showInputs(); + /** + * Show the "Save my payment details" label and checkbox, and the submit button. + * + * @private + * @return {undefined}. + */ + _showInputs: function () { + const $submitButton = this.$('button[name="o_payment_submit_button"]'); + const $tokenizeCheckboxes = this.$('input[name="o_payment_save_as_token"]'); + $submitButton.removeClass('d-none'); + $tokenizeCheckboxes.closest('label').removeClass('d-none'); + }, - // Disable the submit button while building the content - this._disableButton(false); + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- - // Unfold and prepare the inline form of selected payment option - this._displayInlineForm(checkedRadio); + /** + * Hide all extra payment method icons of the provider linked to the clicked button. + * + * Called when clicking on the "show less" button. + * + * @private + * @param {Event} ev + * @return {undefined} + */ + _onClickLessPaymentIcons(ev) { + ev.preventDefault(); + ev.stopPropagation(); + // Hide the extra payment icons, and the "show less" button + const $itemList = $(ev.currentTarget).parents('ul'); + const maxIconNumber = $itemList.data('max-icons-displayed'); + $itemList.children('li').slice(maxIconNumber).addClass('d-none'); + // Show the "show more" button + $itemList.find('a[name="o_payment_icon_more"]').parents('li').removeClass('d-none'); + }, - // Re-enable the submit button - this._enableButton(); - }, + /** + * Display all the payment methods icons of the provider linked to the clicked button. + * + * Called when clicking on the "show more" button. + * + * @private + * @param {Event} ev + * @return {undefined} + */ + _onClickMorePaymentIcons(ev) { + ev.preventDefault(); + ev.stopPropagation(); + // Display all the payment methods icons, and the "show less" button + $(ev.currentTarget).parents('ul').children('li').removeClass('d-none'); + // Hide the "show more" button + $(ev.currentTarget).parents('li').addClass('d-none'); + }, - }; + /** + * Mark the clicked card radio button as checked and open the inline form, if any. + * + * Called when clicking on the card of a payment option. + * + * @private + * @param {Event} ev + * @return {undefined} + */ + _onClickPaymentOption: function (ev) { + // Uncheck all radio buttons + this.$('input[name="o_payment_radio"]').prop('checked', false); + // Check radio button linked to selected payment option + const checkedRadio = $(ev.currentTarget).find('input[name="o_payment_radio"]')[0]; + $(checkedRadio).prop('checked', true); + + // Show the inputs in case they had been hidden + this._showInputs(); + + // Disable the submit button while building the content + this._disableButton(false); + + // Unfold and prepare the inline form of selected payment option + this._displayInlineForm(checkedRadio); + + // Re-enable the submit button + this._enableButton(); + }, + +}; diff --git a/addons/payment/static/src/js/post_processing.js b/addons/payment/static/src/js/post_processing.js index c32908ac287..4254701cad1 100644 --- a/addons/payment/static/src/js/post_processing.js +++ b/addons/payment/static/src/js/post_processing.js @@ -1,9 +1,9 @@ /** @odoo-module **/ -import publicWidget from "@web/legacy/js/public/public_widget"; -import { renderToElement } from "@web/core/utils/render"; -import { markup } from "@odoo/owl"; -import { _t } from "@web/core/l10n/translation"; + import publicWidget from '@web/legacy/js/public/public_widget'; + import { renderToElement } from '@web/core/utils/render'; + import { markup } from "@odoo/owl"; + import { _t } from '@web/core/l10n/translation'; publicWidget.registry.PaymentPostProcessing = publicWidget.Widget.extend({ selector: 'div[name="o_payment_status"]', diff --git a/addons/payment_adyen/static/src/js/payment_form.js b/addons/payment_adyen/static/src/js/payment_form.js index 9bae59f8f69..4a4160fc993 100644 --- a/addons/payment_adyen/static/src/js/payment_form.js +++ b/addons/payment_adyen/static/src/js/payment_form.js @@ -1,219 +1,218 @@ /** @odoo-module **/ /* global AdyenCheckout */ - import { _t } from "@web/core/l10n/translation"; - import checkoutForm from "@payment/js/checkout_form"; - import manageForm from "@payment/js/manage_form"; +import { _t } from '@web/core/l10n/translation'; +import checkoutForm from '@payment/js/checkout_form'; +import manageForm from '@payment/js/manage_form'; - const adyenMixin = { +const adyenMixin = { - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- - /** - * Handle the additional details event of the Adyen drop-in. - * - * @private - * @param {object} state - The state of the drop-in - * @param {object} dropin - The drop-in - * @return {Promise} - */ - _dropinOnAdditionalDetails: function (state, dropin) { + /** + * Handle the additional details event of the Adyen drop-in. + * + * @private + * @param {object} state - The state of the drop-in + * @param {object} dropin - The drop-in + * @return {Promise} + */ + _dropinOnAdditionalDetails: function (state, dropin) { + return this._rpc({ + route: '/payment/adyen/payment_details', + params: { + 'provider_id': dropin.providerId, + 'reference': this.adyenDropin.reference, + 'payment_details': state.data, + }, + }).then(paymentDetails => { + if (paymentDetails.action) { // Additional action required from the shopper + dropin.handleAction(paymentDetails.action); + } else { // The payment reached a final state, redirect to the status page + window.location = '/payment/status'; + } + }).guardedCatch((error) => { + error.event.preventDefault(); + this._displayError( + _t("Server Error"), + _t("We are not able to process your payment."), + error.message.data.message, + ); + }); + }, + + /** + * Handle the error event of the Adyen drop-in. + * + * @private + * @param {object} error - The error in the drop-in + * @return {undefined} + */ + _dropinOnError: function (error) { + if (!this.$('div[name="o_payment_error"]')) { // Don't replace a specific server error. + this._displayError( + _t("Incorrect Payment Details"), + _t("Please verify your payment details."), + ); + } else { + this._enableButton(); + this.call('ui', 'unblock'); + } + }, + + /** + * Handle the submit event of the Adyen drop-in and initiate the payment. + * + * @private + * @param {object} state - The state of the drop-in + * @param {object} dropin - The drop-in + * @return {Promise} + */ + _dropinOnSubmit: function (state, dropin) { + // Create the transaction and retrieve the processing values + return this._rpc({ + route: this.txContext.transactionRoute, + params: this._prepareTransactionRouteParams('adyen', dropin.providerId, 'direct'), + }).then(processingValues => { + this.adyenDropin.reference = processingValues.reference; // Store final reference + // Initiate the payment return this._rpc({ - route: '/payment/adyen/payment_details', + route: '/payment/adyen/payments', params: { 'provider_id': dropin.providerId, - 'reference': this.adyenDropin.reference, - 'payment_details': state.data, + 'reference': processingValues.reference, + 'converted_amount': processingValues.converted_amount, + 'currency_id': processingValues.currency_id, + 'partner_id': processingValues.partner_id, + 'payment_method': state.data.paymentMethod, + 'access_token': processingValues.access_token, + 'browser_info': state.data.browserInfo, }, - }).then(paymentDetails => { - if (paymentDetails.action) { // Additional action required from the shopper - dropin.handleAction(paymentDetails.action); - } else { // The payment reached a final state, redirect to the status page - window.location = '/payment/status'; - } - }).guardedCatch((error) => { - error.event.preventDefault(); - this._displayError( - _t("Server Error"), - _t("We are not able to process your payment."), - error.message.data.message - ); }); - }, - - /** - * Handle the error event of the Adyen drop-in. - * - * @private - * @param {object} error - The error in the drop-in - * @return {undefined} - */ - _dropinOnError: function (error) { - if (!this.$('div[name="o_payment_error"]')) { // Don't replace a specific server error. - this._displayError( - _t("Incorrect Payment Details"), - _t("Please verify your payment details.") - ); + }).then(paymentResponse => { + if (paymentResponse.action) { // Additional action required from the shopper + this._hideInputs(); // Only the inputs of the inline form should be used + this.call('ui', 'unblock'); // The page is blocked at this point, unblock it + dropin.handleAction(paymentResponse.action); + } else { // The payment reached a final state, redirect to the status page + window.location = '/payment/status'; } - else { - this._enableButton(); - this.call("ui", "unblock"); - } - }, + }).guardedCatch((error) => { + error.event.preventDefault(); + this._displayError( + _t("Server Error"), + _t("We are not able to process your payment."), + error.message.data.message, + ); + }); + }, - /** - * Handle the submit event of the Adyen drop-in and initiate the payment. - * - * @private - * @param {object} state - The state of the drop-in - * @param {object} dropin - The drop-in - * @return {Promise} - */ - _dropinOnSubmit: function (state, dropin) { - // Create the transaction and retrieve the processing values + /** + * Prepare the inline form of Adyen for direct payment. + * + * @override method from @payment/js/payment_form_mixin + * @private + * @param {string} code - The code of the selected payment option's provider + * @param {number} paymentOptionId - The id of the selected payment option + * @param {string} flow - The online payment flow of the selected payment option + * @return {Promise} + */ + _prepareInlineForm: function (code, paymentOptionId, flow) { + if (code !== 'adyen') { + return this._super(...arguments); + } + + // Check if instantiation of the drop-in is needed + if (flow === 'token') { + return Promise.resolve(); // No drop-in for tokens + } else if (this.adyenDropin && this.adyenDropin.providerId === paymentOptionId) { + this._setPaymentFlow('direct'); // Overwrite the flow even if no re-instantiation + return Promise.resolve(); // Don't re-instantiate if already done for this provider + } + + // Overwrite the flow of the select payment option + this._setPaymentFlow('direct'); + + // Get public information on the provider (state, client_key) + return this._rpc({ + route: '/payment/adyen/provider_info', + params: { + 'provider_id': paymentOptionId, + }, + }).then(providerInfo => { + // Get the available payment methods return this._rpc({ - route: this.txContext.transactionRoute, - params: this._prepareTransactionRouteParams('adyen', dropin.providerId, 'direct'), - }).then(processingValues => { - this.adyenDropin.reference = processingValues.reference; // Store final reference - // Initiate the payment - return this._rpc({ - route: '/payment/adyen/payments', - params: { - 'provider_id': dropin.providerId, - 'reference': processingValues.reference, - 'converted_amount': processingValues.converted_amount, - 'currency_id': processingValues.currency_id, - 'partner_id': processingValues.partner_id, - 'payment_method': state.data.paymentMethod, - 'access_token': processingValues.access_token, - 'browser_info': state.data.browserInfo, - }, - }); - }).then(paymentResponse => { - if (paymentResponse.action) { // Additional action required from the shopper - this._hideInputs(); // Only the inputs of the inline form should be used - this.call("ui", "unblock"); // The page is blocked at this point, unblock it - dropin.handleAction(paymentResponse.action); - } else { // The payment reached a final state, redirect to the status page - window.location = '/payment/status'; - } - }).guardedCatch((error) => { - error.event.preventDefault(); - this._displayError( - _t("Server Error"), - _t("We are not able to process your payment."), - error.message.data.message - ); - }); - }, - - /** - * Prepare the inline form of Adyen for direct payment. - * - * @override method from @payment/js/payment_form_mixin - * @private - * @param {string} code - The code of the selected payment option's provider - * @param {number} paymentOptionId - The id of the selected payment option - * @param {string} flow - The online payment flow of the selected payment option - * @return {Promise} - */ - _prepareInlineForm: function (code, paymentOptionId, flow) { - if (code !== 'adyen') { - return this._super(...arguments); - } - - // Check if instantiation of the drop-in is needed - if (flow === 'token') { - return Promise.resolve(); // No drop-in for tokens - } else if (this.adyenDropin && this.adyenDropin.providerId === paymentOptionId) { - this._setPaymentFlow('direct'); // Overwrite the flow even if no re-instantiation - return Promise.resolve(); // Don't re-instantiate if already done for this provider - } - - // Overwrite the flow of the select payment option - this._setPaymentFlow('direct'); - - // Get public information on the provider (state, client_key) - return this._rpc({ - route: '/payment/adyen/provider_info', + route: '/payment/adyen/payment_methods', params: { 'provider_id': paymentOptionId, + 'partner_id': parseInt(this.txContext.partnerId), + 'amount': this.txContext.amount + ? parseFloat(this.txContext.amount) + : undefined, + 'currency_id': this.txContext.currencyId + ? parseInt(this.txContext.currencyId) + : undefined, }, - }).then(providerInfo => { - // Get the available payment methods - return this._rpc({ - route: '/payment/adyen/payment_methods', - params: { - 'provider_id': paymentOptionId, - 'partner_id': parseInt(this.txContext.partnerId), - 'amount': this.txContext.amount - ? parseFloat(this.txContext.amount) - : undefined, - 'currency_id': this.txContext.currencyId - ? parseInt(this.txContext.currencyId) - : undefined, + }).then(paymentMethodsResult => { + // Instantiate the drop-in + const configuration = { + paymentMethodsResponse: paymentMethodsResult, + clientKey: providerInfo.client_key, + locale: (this._getContext().lang || 'en-US').replace('_', '-'), + environment: providerInfo.state === 'enabled' ? 'live' : 'test', + onAdditionalDetails: this._dropinOnAdditionalDetails.bind(this), + onError: this._dropinOnError.bind(this), + onSubmit: this._dropinOnSubmit.bind(this), + }; + const checkout = new AdyenCheckout(configuration); + this.adyenDropin = checkout.create( + 'dropin', { + openFirstPaymentMethod: true, + openFirstStoredPaymentMethod: false, + showStoredPaymentMethods: false, + showPaymentMethods: true, + showPayButton: false, + setStatusAutomatically: true, }, - }).then(paymentMethodsResult => { - // Instantiate the drop-in - const configuration = { - paymentMethodsResponse: paymentMethodsResult, - clientKey: providerInfo.client_key, - locale: (this._getContext().lang || 'en-US').replace('_', '-'), - environment: providerInfo.state === 'enabled' ? 'live' : 'test', - onAdditionalDetails: this._dropinOnAdditionalDetails.bind(this), - onError: this._dropinOnError.bind(this), - onSubmit: this._dropinOnSubmit.bind(this), - }; - const checkout = new AdyenCheckout(configuration); - this.adyenDropin = checkout.create( - 'dropin', { - openFirstPaymentMethod: true, - openFirstStoredPaymentMethod: false, - showStoredPaymentMethods: false, - showPaymentMethods: true, - showPayButton: false, - setStatusAutomatically: true, - }, - ).mount(`#o_adyen_${this.formType}_dropin_container_${paymentOptionId}`); - this.adyenDropin.providerId = paymentOptionId; - }); - }).guardedCatch((error) => { - error.event.preventDefault(); - this._displayError( - _t("Server Error"), - _t("An error occurred when displayed this payment form."), - error.message.data.message - ); + ).mount(`#o_adyen_${this.formType}_dropin_container_${paymentOptionId}`); + this.adyenDropin.providerId = paymentOptionId; }); - }, + }).guardedCatch((error) => { + error.event.preventDefault(); + this._displayError( + _t("Server Error"), + _t("An error occurred when displayed this payment form."), + error.message.data.message, + ); + }); + }, - /** - * Trigger the payment processing by submitting the drop-in. - * - * @override method from @payment/js/payment_form_mixin - * @private - * @param {string} provider - The provider of the payment option's provider - * @param {number} paymentOptionId - The id of the payment option handling the transaction - * @param {string} flow - The online payment flow of the transaction - * @return {Promise} - */ - async _processPayment(provider, paymentOptionId, flow) { - if (provider !== 'adyen' || flow === 'token') { - return this._super(...arguments); // Tokens are handled by the generic flow - } - if (this.adyenDropin === undefined) { // The drop-in has not been properly instantiated - this._displayError( - _t("Server Error"), _t("We are not able to process your payment.") - ); - } else { - return await this.adyenDropin.submit(); - } - }, + /** + * Trigger the payment processing by submitting the drop-in. + * + * @override method from @payment/js/payment_form_mixin + * @private + * @param {string} provider - The provider of the payment option's provider + * @param {number} paymentOptionId - The id of the payment option handling the transaction + * @param {string} flow - The online payment flow of the transaction + * @return {Promise} + */ + async _processPayment(provider, paymentOptionId, flow) { + if (provider !== 'adyen' || flow === 'token') { + return this._super(...arguments); // Tokens are handled by the generic flow + } + if (this.adyenDropin === undefined) { // The drop-in has not been properly instantiated + this._displayError( + _t("Server Error"), _t("We are not able to process your payment.") + ); + } else { + return await this.adyenDropin.submit(); + } + }, - }; +}; - checkoutForm.include(adyenMixin); - manageForm.include(adyenMixin); +checkoutForm.include(adyenMixin); +manageForm.include(adyenMixin); diff --git a/addons/payment_authorize/static/src/js/payment_form.js b/addons/payment_authorize/static/src/js/payment_form.js index e0dcacbf092..d36f53a442b 100644 --- a/addons/payment_authorize/static/src/js/payment_form.js +++ b/addons/payment_authorize/static/src/js/payment_form.js @@ -1,209 +1,209 @@ /** @odoo-module **/ /* global Accept */ - import { _t } from "@web/core/l10n/translation"; - import { loadJS } from "@web/core/assets"; +import { _t } from '@web/core/l10n/translation'; +import { loadJS } from '@web/core/assets'; - import checkoutForm from "@payment/js/checkout_form"; - import manageForm from "@payment/js/manage_form"; +import checkoutForm from '@payment/js/checkout_form'; +import manageForm from '@payment/js/manage_form'; - const authorizeMixin = { +const authorizeMixin = { - /** - * Return all relevant inline form inputs based on the payment method type of the provider. - * - * @private - * @param {number} providerId - The id of the selected provider - * @return {Object} - An object mapping the name of inline form inputs to their DOM element - */ - _getInlineFormInputs: function (providerId) { - if (this.authorizeInfo.payment_method_type === "credit_card") { - return { - card: this.el.querySelector(`#o_authorize_card_${providerId}`), - month: this.el.querySelector(`#o_authorize_month_${providerId}`), - year: this.el.querySelector(`#o_authorize_year_${providerId}`), - code: this.el.querySelector(`#o_authorize_code_${providerId}`), - }; - } else { - return { - accountName: this.el.querySelector(`#o_authorize_account_name_${providerId}`), - accountNumber: this.el.querySelector( - `#o_authorize_account_number_${providerId}` - ), - abaNumber: this.el.querySelector(`#o_authorize_aba_number_${providerId}`), - accountType: this.el.querySelector(`#o_authorize_account_type_${providerId}`), - }; - } - }, - - /** - * Return the credit card or bank data to pass to the Accept.dispatch request. - * - * @private - * @param {number} providerId - The id of the selected provider - * @return {Object} - Data to pass to the Accept.dispatch request - */ - _getPaymentDetails: function (providerId) { - const inputs = this._getInlineFormInputs(providerId); - if (this.authorizeInfo.payment_method_type === 'credit_card') { - return { - cardData: { - cardNumber: inputs.card.value.replace(/ /g, ''), // Remove all spaces - month: inputs.month.value, - year: inputs.year.value, - cardCode: inputs.code.value, - }, - }; - } else { - return { - bankData: { - nameOnAccount: inputs.accountName.value.substring(0, 22), // Max allowed by acceptjs - accountNumber: inputs.accountNumber.value, - routingNumber: inputs.abaNumber.value, - accountType: inputs.accountType.value, - }, - }; - } - }, - - /** - * Prepare the inline form of Authorize.Net for direct payment. - * - * @override method from @payment/js/payment_form_mixin - * @private - * @param {string} provider - The provider of the selected payment option's provider - * @param {number} paymentOptionId - The id of the selected payment option - * @param {string} flow - The online payment flow of the selected payment option - * @return {Promise} - */ - _prepareInlineForm: function (code, paymentOptionId, flow) { - if (code !== 'authorize') { - return this._super(...arguments); - } - - if (flow === 'token') { - return Promise.resolve(); // Don't show the form for tokens - } - - this._setPaymentFlow('direct'); - - let acceptJSUrl = 'https://js.authorize.net/v1/Accept.js'; - return this._rpc({ - route: '/payment/authorize/get_provider_info', - params: { - 'provider_id': paymentOptionId, - }, - }).then(providerInfo => { - if (providerInfo.state !== 'enabled') { - acceptJSUrl = 'https://jstest.authorize.net/v1/Accept.js'; - } - this.authorizeInfo = providerInfo; - }).then(() => { - loadJS(acceptJSUrl); - }).guardedCatch((error) => { - error.event.preventDefault(); - this._displayError( - _t("Server Error"), - _t("An error occurred when displayed this payment form."), - error.message.data.message - ); - }); - }, - - /** - * Dispatch the secure data to Authorize.Net. - * - * @override method from @payment/js/payment_form_mixin - * @private - * @param {string} code - The code of the payment option's provider - * @param {number} paymentOptionId - The id of the payment option handling the transaction - * @param {string} flow - The online payment flow of the transaction - * @return {Promise} - */ - _processPayment: function (code, paymentOptionId, flow) { - if (code !== 'authorize' || flow === 'token') { - return this._super(...arguments); // Tokens are handled by the generic flow - } - - if (!this._validateFormInputs(paymentOptionId)) { - this._enableButton(); // The submit button is disabled at this point, enable it - this.call("ui", "unblock"); // The page is blocked at this point, unblock it - return Promise.resolve(); - } - - // Build the authentication and card data objects to be dispatched to Authorized.Net - const secureData = { - authData: { - apiLoginID: this.authorizeInfo.login_id, - clientKey: this.authorizeInfo.client_key, - }, - ...this._getPaymentDetails(paymentOptionId), + /** + * Return all relevant inline form inputs based on the payment method type of the provider. + * + * @private + * @param {number} providerId - The id of the selected provider + * @return {Object} - An object mapping the name of inline form inputs to their DOM element + */ + _getInlineFormInputs: function (providerId) { + if (this.authorizeInfo['payment_method_type'] === 'credit_card') { + return { + card: this.el.querySelector(`#o_authorize_card_${providerId}`), + month: this.el.querySelector(`#o_authorize_month_${providerId}`), + year: this.el.querySelector(`#o_authorize_year_${providerId}`), + code: this.el.querySelector(`#o_authorize_code_${providerId}`), }; + } else { + return { + accountName: this.el.querySelector(`#o_authorize_account_name_${providerId}`), + accountNumber: this.el.querySelector( + `#o_authorize_account_number_${providerId}` + ), + abaNumber: this.el.querySelector(`#o_authorize_aba_number_${providerId}`), + accountType: this.el.querySelector(`#o_authorize_account_type_${providerId}`), + }; + } + }, - // Dispatch secure data to Authorize.Net to get a payment nonce in return - return Accept.dispatchData( - secureData, response => this._responseHandler(paymentOptionId, response) - ); - }, + /** + * Return the credit card or bank data to pass to the Accept.dispatch request. + * + * @private + * @param {number} providerId - The id of the selected provider + * @return {Object} - Data to pass to the Accept.dispatch request + */ + _getPaymentDetails: function (providerId) { + const inputs = this._getInlineFormInputs(providerId); + if (this.authorizeInfo['payment_method_type'] === 'credit_card') { + return { + cardData: { + cardNumber: inputs.card.value.replace(/ /g, ''), // Remove all spaces + month: inputs.month.value, + year: inputs.year.value, + cardCode: inputs.code.value, + }, + }; + } else { + return { + bankData: { + nameOnAccount: inputs.accountName.value.substring(0, 22), // Max allowed by acceptjs + accountNumber: inputs.accountNumber.value, + routingNumber: inputs.abaNumber.value, + accountType: inputs.accountType.value, + }, + }; + } + }, - /** - * Handle the response from Authorize.Net and initiate the payment. - * - * @private - * @param {number} providerId - The id of the selected provider - * @param {object} response - The payment nonce returned by Authorized.Net - * @return {Promise} - */ - _responseHandler: function (providerId, response) { - if (response.messages.resultCode === 'Error') { - let error = ""; - response.messages.message.forEach(msg => error += `${msg.code}: ${msg.text}\n`); - this._displayError( - _t("Server Error"), - _t("We are not able to process your payment."), - error - ); - return Promise.resolve(); + /** + * Prepare the inline form of Authorize.Net for direct payment. + * + * @override method from @payment/js/payment_form_mixin + * @private + * @param {string} provider - The provider of the selected payment option's provider + * @param {number} paymentOptionId - The id of the selected payment option + * @param {string} flow - The online payment flow of the selected payment option + * @return {Promise} + */ + _prepareInlineForm: function (code, paymentOptionId, flow) { + if (code !== 'authorize') { + return this._super(...arguments); + } + + if (flow === 'token') { + return Promise.resolve(); // Don't show the form for tokens + } + + this._setPaymentFlow('direct'); + + let acceptJSUrl = 'https://js.authorize.net/v1/Accept.js'; + return this._rpc({ + route: '/payment/authorize/get_provider_info', + params: { + 'provider_id': paymentOptionId, + }, + }).then(providerInfo => { + if (providerInfo.state !== 'enabled') { + acceptJSUrl = 'https://jstest.authorize.net/v1/Accept.js'; } + this.authorizeInfo = providerInfo; + }).then(() => { + loadJS(acceptJSUrl); + }).guardedCatch((error) => { + error.event.preventDefault(); + this._displayError( + _t("Server Error"), + _t("An error occurred when displayed this payment form."), + error.message.data.message, + ); + }); + }, - // Create the transaction and retrieve the processing values + /** + * Dispatch the secure data to Authorize.Net. + * + * @override method from @payment/js/payment_form_mixin + * @private + * @param {string} code - The code of the payment option's provider + * @param {number} paymentOptionId - The id of the payment option handling the transaction + * @param {string} flow - The online payment flow of the transaction + * @return {Promise} + */ + _processPayment: function (code, paymentOptionId, flow) { + if (code !== 'authorize' || flow === 'token') { + return this._super(...arguments); // Tokens are handled by the generic flow + } + + if (!this._validateFormInputs(paymentOptionId)) { + this._enableButton(); // The submit button is disabled at this point, enable it + this.call('ui', 'unblock'); // The page is blocked at this point, unblock it + return Promise.resolve(); + } + + // Build the authentication and card data objects to be dispatched to Authorized.Net + const secureData = { + authData: { + apiLoginID: this.authorizeInfo.login_id, + clientKey: this.authorizeInfo.client_key, + }, + ...this._getPaymentDetails(paymentOptionId), + }; + + // Dispatch secure data to Authorize.Net to get a payment nonce in return + return Accept.dispatchData( + secureData, response => this._responseHandler(paymentOptionId, response) + ); + }, + + /** + * Handle the response from Authorize.Net and initiate the payment. + * + * @private + * @param {number} providerId - The id of the selected provider + * @param {object} response - The payment nonce returned by Authorized.Net + * @return {Promise} + */ + _responseHandler: function (providerId, response) { + if (response.messages.resultCode === 'Error') { + let error = ''; + response.messages.message.forEach(msg => error += `${msg.code}: ${msg.text}\n`); + this._displayError( + _t("Server Error"), + _t("We are not able to process your payment."), + error + ); + return Promise.resolve(); + } + + // Create the transaction and retrieve the processing values + return this._rpc({ + route: this.txContext.transactionRoute, + params: this._prepareTransactionRouteParams('authorize', providerId, 'direct'), + }).then(processingValues => { + // Initiate the payment return this._rpc({ - route: this.txContext.transactionRoute, - params: this._prepareTransactionRouteParams('authorize', providerId, 'direct'), - }).then(processingValues => { - // Initiate the payment - return this._rpc({ - route: '/payment/authorize/payment', - params: { - 'reference': processingValues.reference, - 'partner_id': processingValues.partner_id, - 'opaque_data': response.opaqueData, - 'access_token': processingValues.access_token, - } - }).then(() => window.location = '/payment/status'); - }).guardedCatch((error) => { - error.event.preventDefault(); - this._displayError( - _t("Server Error"), - _t("We are not able to process your payment."), - error.message.data.message - ); - }); - }, + route: '/payment/authorize/payment', + params: { + 'reference': processingValues.reference, + 'partner_id': processingValues.partner_id, + 'opaque_data': response.opaqueData, + 'access_token': processingValues.access_token, + } + }).then(() => window.location = '/payment/status'); + }).guardedCatch((error) => { + error.event.preventDefault(); + this._displayError( + _t("Server Error"), + _t("We are not able to process your payment."), + error.message.data.message, + ); + }); + }, - /** - * Checks that all payment inputs adhere to the DOM validation constraints. - * - * @private - * @param {number} providerId - The id of the selected provider - * @return {boolean} - Whether all elements pass the validation constraints - */ - _validateFormInputs: function (providerId) { - const inputs = Object.values(this._getInlineFormInputs(providerId)); - return inputs.every(element => element.reportValidity()); - }, + /** + * Checks that all payment inputs adhere to the DOM validation constraints. + * + * @private + * @param {number} providerId - The id of the selected provider + * @return {boolean} - Whether all elements pass the validation constraints + */ + _validateFormInputs: function (providerId) { + const inputs = Object.values(this._getInlineFormInputs(providerId)); + return inputs.every(element => element.reportValidity()); + }, - }; +}; - checkoutForm.include(authorizeMixin); - manageForm.include(authorizeMixin); +checkoutForm.include(authorizeMixin); +manageForm.include(authorizeMixin); diff --git a/addons/payment_custom/static/src/js/post_processing.js b/addons/payment_custom/static/src/js/post_processing.js index 5f3f33af107..586736aca44 100644 --- a/addons/payment_custom/static/src/js/post_processing.js +++ b/addons/payment_custom/static/src/js/post_processing.js @@ -1,6 +1,6 @@ /** @odoo-module **/ -import paymentPostProcessing from "@payment/js/post_processing"; +import paymentPostProcessing from '@payment/js/post_processing'; paymentPostProcessing.include({ /** diff --git a/addons/payment_demo/static/src/js/payment_form.js b/addons/payment_demo/static/src/js/payment_form.js index 843c406f6cd..e0d15b5a160 100644 --- a/addons/payment_demo/static/src/js/payment_form.js +++ b/addons/payment_demo/static/src/js/payment_form.js @@ -1,62 +1,64 @@ /** @odoo-module **/ - import checkoutForm from "@payment/js/checkout_form"; - import manageForm from "@payment/js/manage_form"; +import checkoutForm from '@payment/js/checkout_form'; +import manageForm from '@payment/js/manage_form'; - const paymentDemoMixin = { +const paymentDemoMixin = { - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- - /** - * Simulate a feedback from a payment provider and redirect the customer to the status page. - * - * @override method from @payment/js/payment_form_mixin - * @private - * @param {string} code - The code of the provider - * @param {number} providerId - The id of the provider handling the transaction - * @param {object} processingValues - The processing values of the transaction - * @return {Promise} - */ - _processDirectPayment: function (code, providerId, processingValues) { - if (code !== 'demo') { - return this._super(...arguments); - } + /** + * Simulate a feedback from a payment provider and redirect the customer to the status page. + * + * @override method from @payment/js/payment_form_mixin + * @private + * @param {string} code - The code of the provider + * @param {number} providerId - The id of the provider handling the transaction + * @param {object} processingValues - The processing values of the transaction + * @return {Promise} + */ + _processDirectPayment: function (code, providerId, processingValues) { + if (code !== 'demo') { + return this._super(...arguments); + } - const customerInput = document.getElementById('customer_input').value; - const simulatedPaymentState = document.getElementById('simulated_payment_state').value; - return this._rpc({ - route: '/payment/demo/simulate_payment', - params: { - 'reference': processingValues.reference, - 'payment_details': customerInput, - 'simulated_state': simulatedPaymentState, - }, - }).then(() => { - window.location = '/payment/status'; - }); - }, + const customerInput = document.getElementById('customer_input').value; + const simulatedPaymentState = document.getElementById('simulated_payment_state').value; + return this._rpc({ + route: '/payment/demo/simulate_payment', + params: { + 'reference': processingValues.reference, + 'payment_details': customerInput, + 'simulated_state': simulatedPaymentState, + }, + }).then(() => { + window.location = '/payment/status'; + }); + }, - /** - * Prepare the inline form of Demo for direct payment. - * - * @override method from @payment/js/payment_form_mixin - * @private - * @param {string} code - The code of the selected payment option's provider - * @param {integer} paymentOptionId - The id of the selected payment option - * @param {string} flow - The online payment flow of the selected payment option - * @return {Promise} - */ - _prepareInlineForm: function (code, paymentOptionId, flow) { - if (code !== 'demo') { - return this._super(...arguments); - } else if (flow === 'token') { - return Promise.resolve(); - } - this._setPaymentFlow('direct'); - return Promise.resolve() - }, - }; - checkoutForm.include(paymentDemoMixin); - manageForm.include(paymentDemoMixin); + /** + * Prepare the inline form of Demo for direct payment. + * + * @override method from @payment/js/payment_form_mixin + * @private + * @param {string} code - The code of the selected payment option's provider + * @param {integer} paymentOptionId - The id of the selected payment option + * @param {string} flow - The online payment flow of the selected payment option + * @return {Promise} + */ + _prepareInlineForm: function (code, paymentOptionId, flow) { + if (code !== 'demo') { + return this._super(...arguments); + } else if (flow === 'token') { + return Promise.resolve(); + } + this._setPaymentFlow('direct'); + return Promise.resolve(); + }, + +}; + +checkoutForm.include(paymentDemoMixin); +manageForm.include(paymentDemoMixin); diff --git a/addons/payment_stripe/static/src/js/checkout_form.js b/addons/payment_stripe/static/src/js/checkout_form.js index a4cc951c892..3e6389939d2 100644 --- a/addons/payment_stripe/static/src/js/checkout_form.js +++ b/addons/payment_stripe/static/src/js/checkout_form.js @@ -1,6 +1,6 @@ /** @odoo-module */ -import checkoutForm from "@payment/js/checkout_form"; +import checkoutForm from '@payment/js/checkout_form'; import stripeMixin from '@payment_stripe/js/stripe_mixin'; checkoutForm.include(stripeMixin); diff --git a/addons/payment_stripe/static/src/js/express_checkout_form.js b/addons/payment_stripe/static/src/js/express_checkout_form.js index 96a4bbeff35..1282ebf95d0 100644 --- a/addons/payment_stripe/static/src/js/express_checkout_form.js +++ b/addons/payment_stripe/static/src/js/express_checkout_form.js @@ -1,7 +1,7 @@ /** @odoo-module **/ /* global Stripe */ -import { _t } from "@web/core/l10n/translation"; +import { _t } from '@web/core/l10n/translation'; import { paymentExpressCheckoutForm } from '@payment/js/express_checkout_form'; import { StripeOptions } from '@payment_stripe/js/stripe_options'; @@ -175,7 +175,7 @@ paymentExpressCheckoutForm.include({ shippingOptions: availableCarriers.map(carrier => ({ id: String(carrier.id), label: carrier.name, - detail: carrier.description ? carrier.description:"", + detail: carrier.description ? carrier.description:'', amount: carrier.minorAmount, })), ...this._getOrderDetails(availableCarriers[0].minorAmount), diff --git a/addons/payment_stripe/static/src/js/manage_form.js b/addons/payment_stripe/static/src/js/manage_form.js index e6b24045271..62164dc58c1 100644 --- a/addons/payment_stripe/static/src/js/manage_form.js +++ b/addons/payment_stripe/static/src/js/manage_form.js @@ -1,6 +1,6 @@ /** @odoo-module */ -import manageForm from "@payment/js/manage_form"; +import manageForm from '@payment/js/manage_form'; import stripeMixin from '@payment_stripe/js/stripe_mixin'; manageForm.include(stripeMixin); diff --git a/addons/payment_stripe/static/src/js/stripe_mixin.js b/addons/payment_stripe/static/src/js/stripe_mixin.js index 404dabe7e12..7f0133df6bb 100644 --- a/addons/payment_stripe/static/src/js/stripe_mixin.js +++ b/addons/payment_stripe/static/src/js/stripe_mixin.js @@ -1,7 +1,7 @@ /** @odoo-module */ /* global Stripe */ -import { _t } from "@web/core/l10n/translation"; +import { _t } from '@web/core/l10n/translation'; import { StripeOptions } from '@payment_stripe/js/stripe_options'; export default { @@ -63,7 +63,7 @@ export default { const tokenizationCheckbox = document.getElementById( `o_payment_provider_inline_${this.formType}_form_${paymentOptionId}` - ).querySelector("input[name='o_payment_save_as_token']"); + ).querySelector('input[name="o_payment_save_as_token"]'); if (this.formType === 'checkout' && tokenizationCheckbox) { // Disable the tokenization checkbox for non-compatible payment methods. paymentElement.addEventListener('change', ev => { diff --git a/addons/sale/static/src/js/payment_form.js b/addons/sale/static/src/js/payment_form.js index a577cee0279..39720d56d23 100644 --- a/addons/sale/static/src/js/payment_form.js +++ b/addons/sale/static/src/js/payment_form.js @@ -1,34 +1,34 @@ /** @odoo-module **/ - import checkoutForm from "@payment/js/checkout_form"; - import manageForm from "@payment/js/manage_form"; +import checkoutForm from '@payment/js/checkout_form'; +import manageForm from '@payment/js/manage_form'; - const salePaymentMixin = { +const salePaymentMixin = { - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- - /** - * Add `sale_order_id` to the transaction route params if it is provided. - * - * @override method from @payment/js/payment_form_mixin - * @private - * @param {string} code - The code of the selected payment option's provider - * @param {number} paymentOptionId - The id of the selected payment option - * @param {string} flow - The online payment flow of the selected payment option - * @return {object} The extended transaction route params - */ - _prepareTransactionRouteParams: function (code, paymentOptionId, flow) { - const transactionRouteParams = this._super(...arguments); - return { - ...transactionRouteParams, - 'sale_order_id': this.txContext.saleOrderId - ? parseInt(this.txContext.saleOrderId) : undefined, - }; - }, + /** + * Add `sale_order_id` to the transaction route params if it is provided. + * + * @override method from @payment/js/payment_form_mixin + * @private + * @param {string} code - The code of the selected payment option's provider + * @param {number} paymentOptionId - The id of the selected payment option + * @param {string} flow - The online payment flow of the selected payment option + * @return {object} The extended transaction route params + */ + _prepareTransactionRouteParams: function (code, paymentOptionId, flow) { + const transactionRouteParams = this._super(...arguments); + return { + ...transactionRouteParams, + 'sale_order_id': this.txContext.saleOrderId + ? parseInt(this.txContext.saleOrderId) : undefined, + }; + }, - }; +}; - checkoutForm.include(salePaymentMixin); - manageForm.include(salePaymentMixin); +checkoutForm.include(salePaymentMixin); +manageForm.include(salePaymentMixin); diff --git a/addons/website_payment/static/src/js/website_payment_form.js b/addons/website_payment/static/src/js/website_payment_form.js index e6df049d076..1ec0f3078ef 100644 --- a/addons/website_payment/static/src/js/website_payment_form.js +++ b/addons/website_payment/static/src/js/website_payment_form.js @@ -1,6 +1,6 @@ /** @odoo-module **/ -import core from "@web/legacy/js/services/core"; +import core, from '@web/legacy/js/services/core'; import checkoutForm from '@payment/js/checkout_form'; import { _t } from "@web/core/l10n/translation"; @@ -61,6 +61,7 @@ checkoutForm.include({ } return this._super(...arguments); }, + /** * Add params used by the donation snippet to the transaction route params. * diff --git a/addons/website_sale/static/src/js/website_sale_payment.js b/addons/website_sale/static/src/js/website_sale_payment.js index ce3eb04186c..b03869a5bee 100644 --- a/addons/website_sale/static/src/js/website_sale_payment.js +++ b/addons/website_sale/static/src/js/website_sale_payment.js @@ -1,17 +1,97 @@ /** @odoo-module **/ - import { debounce } from "@web/core/utils/timing"; -import checkoutForm from "@payment/js/checkout_form"; - import publicWidget from "@web/legacy/js/public/public_widget"; +import { debounce } from '@web/core/utils/timing'; +import checkoutForm from '@payment/js/checkout_form'; +import publicWidget from '@web/legacy/js/public/public_widget'; - const websiteSalePaymentMixin = { +const websiteSalePaymentMixin = { - /** - * @override - */ - init: function () { - this._onClickTCCheckbox = debounce(this._onClickTCCheckbox, 100, true); - this._super(...arguments); + /** + * @override + */ + init: function () { + this._onClickTCCheckbox = debounce(this._onClickTCCheckbox, 100, true); + this._super(...arguments); + }, + + /** + * @override + */ + start: function () { + this.$checkbox = this.$('#checkbox_tc'); + this.$submitButton = this.$('button[name="o_payment_submit_button"]'); + this._adaptConfirmButton(); + return this._super(...arguments); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * Update the data on the submit button with the status of the Terms and Conditions input. + * + * @private + * @return {undefined} + */ + _adaptConfirmButton: function () { + if (this.$checkbox.length > 0) { + const disabledReasons = this.$submitButton.data('disabled_reasons') || {}; + disabledReasons.tc = !this.$checkbox.prop('checked'); + this.$submitButton.data('disabled_reasons', disabledReasons); + } + }, + +}; + +checkoutForm.include(Object.assign({}, websiteSalePaymentMixin, { + events: Object.assign({}, checkoutForm.prototype.events, { + 'change #checkbox_tc': '_onClickTCCheckbox', + }), + + //---------------------------------------------------------------------- + // Private + //---------------------------------------------------------------------- + + /** + * Verify that the Terms and Condition checkbox is checked. + * + * @override method from @payment/js/payment_form_mixin + * @private + * @return {boolean} Whether the submit button can be enabled + */ + _isButtonReady: function () { + const disabledReasonFound = Object.values( + this.$submitButton.data('disabled_reasons') || {} + ).includes(true); + return !disabledReasonFound && this._super(); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * Enable the submit button if it all conditions are met. + * + * @private + * @return {undefined} + */ + _onClickTCCheckbox: function () { + this._adaptConfirmButton(); + + if (!this._enableButton()) { + this._disableButton(false); + } + }, + +})); + +publicWidget.registry.WebsiteSalePayment = publicWidget.Widget.extend( + Object.assign({}, websiteSalePaymentMixin, { + selector: 'div[name="o_website_sale_free_cart"]', + events: { + 'change #checkbox_tc': '_onClickTCCheckbox', }, /** @@ -20,53 +100,10 @@ import checkoutForm from "@payment/js/checkout_form"; start: function () { this.$checkbox = this.$('#checkbox_tc'); this.$submitButton = this.$('button[name="o_payment_submit_button"]'); - this._adaptConfirmButton(); + this._onClickTCCheckbox(); return this._super(...arguments); }, - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * Update the data on the submit button with the status of the Terms and Conditions input. - * - * @private - * @return {undefined} - */ - _adaptConfirmButton: function () { - if (this.$checkbox.length > 0) { - const disabledReasons = this.$submitButton.data('disabled_reasons') || {}; - disabledReasons.tc = !this.$checkbox.prop('checked'); - this.$submitButton.data('disabled_reasons', disabledReasons); - } - }, - - }; - - checkoutForm.include(Object.assign({}, websiteSalePaymentMixin, { - events: Object.assign({}, checkoutForm.prototype.events, { - 'change #checkbox_tc': '_onClickTCCheckbox', - }), - - //---------------------------------------------------------------------- - // Private - //---------------------------------------------------------------------- - - /** - * Verify that the Terms and Condition checkbox is checked. - * - * @override method from @payment/js/payment_form_mixin - * @private - * @return {boolean} Whether the submit button can be enabled - */ - _isButtonReady: function () { - const disabledReasonFound = Object.values( - this.$submitButton.data("disabled_reasons") || {} - ).includes(true); - return !disabledReasonFound && this._super(); - }, - //-------------------------------------------------------------------------- // Handlers //-------------------------------------------------------------------------- @@ -79,46 +116,9 @@ import checkoutForm from "@payment/js/checkout_form"; */ _onClickTCCheckbox: function () { this._adaptConfirmButton(); - - if (!this._enableButton()) { - this._disableButton(false); - } + const disabledReasonFound = Object.values( + this.$submitButton.data('disabled_reasons') || {} + ).includes(true); + this.$submitButton.prop('disabled', disabledReasonFound); }, - })); - - publicWidget.registry.WebsiteSalePayment = publicWidget.Widget.extend( - Object.assign({}, websiteSalePaymentMixin, { - selector: 'div[name="o_website_sale_free_cart"]', - events: { - 'change #checkbox_tc': '_onClickTCCheckbox', - }, - - /** - * @override - */ - start: function () { - this.$checkbox = this.$('#checkbox_tc'); - this.$submitButton = this.$('button[name="o_payment_submit_button"]'); - this._onClickTCCheckbox(); - return this._super(...arguments); - }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * Enable the submit button if it all conditions are met. - * - * @private - * @return {undefined} - */ - _onClickTCCheckbox: function () { - this._adaptConfirmButton(); - const disabledReasonFound = Object.values( - this.$submitButton.data("disabled_reasons") || {} - ).includes(true); - this.$submitButton.prop('disabled', disabledReasonFound); - }, - }));