[IMP] payment, *: replace providers with payment methods in payment forms
Before this commit, the payment providers (e.g., Stripe, Adyen...) available for payment were displayed on the payment forms. The customer had to select one to process their payment. After that, the customer had to select their preferred payment method (e.g., Credit Card, Bancontact...) from a list of payment methods supported by the selected provider over which the website administrator had close to no control. This was making the payment forms confusing because the payment methods were displayed sometimes more than once, if at all, in a non-controlled order, and behind the selection of a payment provider that customers should not have to deal with. As the payment method was selected in an iframe or directly on the provider's website, the information on the selection payment method was not available in Odoo. This posed many problems, among which were the impossibility of assessing whether a specific feature (e.g., tokenization, refunds, manual capture...) was available, not being able to easily identify payment tokens through the payment method logo, listing available payment methods on the website, sorting and fine-grained configuration of the available payment method, subpar payment method-specific display on the payment form (e.g., PayPal that requires displaying a "Pay with PayPal" button), etc. In this commit, the payment providers are thus replaced by the payment methods on the payment forms. All contextually available (depending on the country, currency, requested feature...) payment methods are displayed one after the other on a single-level list and in the order configured by the website administrator. Each payment method is "powered by" (i.e., linked) to a single payment provider: the first one, by model order, to support it. This allows, for example, offering the PayPal payment method through Mollie, which charges low processing fees, while also offering Klarna through Stripe, which supports more payment methods but charges higher processing fees. While doing so, the two different payment forms, "Checkout" and "Manage", are also merged together in a new, configurable case-by-case, payment form that is entirely redesigned to offer a better user experience. After payment, the information on the selected payment method is saved on the transaction and eventual payment record and updated with the information received from the provider. task-2882677 closes odoo/odoo#120446 Related: odoo/upgrade#5103 Related: odoo/documentation#5717 Related: odoo/enterprise#40666 Signed-off-by: Antoine Vandevenne (anv) <anv@odoo.com> Co-authored-by: Anita (anko) <anko@odoo.com> Co-authored-by: Brieuc-brd <brd@odoo.com> Co-authored-by: Valeriya (vchu) <vchu@odoo.com>
This commit is contained in:
committed by
Valeriya(vchu)
co-authored by
Anita
Brieuc-brd
Valeriya
parent
ec1d70617f
commit
7e012dd544
@@ -1,5 +1,12 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
# Mapping of payment method codes to Authorize codes.
|
||||
PAYMENT_METHODS_MAPPING = {
|
||||
'amex': 'americanexpress',
|
||||
'diners': 'dinersclub',
|
||||
'card': 'creditcard'
|
||||
}
|
||||
|
||||
# Mapping of payment status on Authorize side to transaction statuses.
|
||||
# See https://developer.authorize.net/api/reference/index.html#transaction-reporting-get-transaction-details.
|
||||
TRANSACTION_STATUS_MAPPING = {
|
||||
|
||||
@@ -14,25 +14,6 @@ _logger = logging.getLogger(__name__)
|
||||
|
||||
class AuthorizeController(http.Controller):
|
||||
|
||||
@http.route('/payment/authorize/get_provider_info', type='json', auth='public')
|
||||
def authorize_get_provider_info(self, provider_id):
|
||||
""" Return public information on the provider.
|
||||
|
||||
:param int provider_id: The provider handling the transaction, as a `payment.provider` id
|
||||
:return: Information on the provider, namely: the state, payment method type, login ID, and
|
||||
public client key
|
||||
:rtype: dict
|
||||
"""
|
||||
provider_sudo = request.env['payment.provider'].sudo().browse(provider_id).exists()
|
||||
return {
|
||||
'state': provider_sudo.state,
|
||||
'payment_method_type': provider_sudo.authorize_payment_method_type,
|
||||
# The public API key solely used to identify the seller account with Authorize.Net
|
||||
'login_id': provider_sudo.authorize_login,
|
||||
# The public client key solely used to identify requests from the Accept.js suite
|
||||
'client_key': provider_sudo.authorize_client_key,
|
||||
}
|
||||
|
||||
@http.route('/payment/authorize/payment', type='json', auth='public')
|
||||
def authorize_payment(self, reference, partner_id, access_token, opaque_data):
|
||||
""" Make a payment request and handle the response.
|
||||
|
||||
@@ -37,7 +37,6 @@ class AuthorizeAPI:
|
||||
self.state = provider.state
|
||||
self.name = provider.authorize_login
|
||||
self.transaction_key = provider.authorize_transaction_key
|
||||
self.payment_method_type = provider.authorize_payment_method_type
|
||||
|
||||
def _make_request(self, operation, data=None):
|
||||
request = {
|
||||
@@ -80,10 +79,12 @@ class AuthorizeAPI:
|
||||
'x_response_reason_text': response.get('err_msg')
|
||||
}
|
||||
else:
|
||||
tx_response = response.get('transactionResponse', {})
|
||||
return {
|
||||
'x_response_code': response.get('transactionResponse', {}).get('responseCode'),
|
||||
'x_trans_id': response.get('transactionResponse', {}).get('transId'),
|
||||
'x_response_code': tx_response.get('responseCode'),
|
||||
'x_trans_id': tx_response.get('transId'),
|
||||
'x_type': operation,
|
||||
'payment_method_code': tx_response.get('accountType'),
|
||||
}
|
||||
|
||||
# Customer profiles
|
||||
@@ -137,7 +138,7 @@ class AuthorizeAPI:
|
||||
})
|
||||
|
||||
payment = response.get('paymentProfile', {}).get('payment', {})
|
||||
if self.payment_method_type == 'credit_card':
|
||||
if 'creditCard' in payment:
|
||||
# Authorize.net pads the card and account numbers with X's.
|
||||
res['payment_details'] = payment.get('creditCard', {}).get('cardNumber')[-4:]
|
||||
else:
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import json
|
||||
import logging
|
||||
import pprint
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.fields import Command
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo.fields import Command
|
||||
|
||||
from .authorize_request import AuthorizeAPI
|
||||
from odoo.addons.payment_authorize.models.authorize_request import AuthorizeAPI
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -27,26 +28,9 @@ class PaymentProvider(models.Model):
|
||||
authorize_client_key = fields.Char(
|
||||
string="API Client Key",
|
||||
help="The public client key. To generate directly from Odoo or from Authorize.Net backend.")
|
||||
authorize_payment_method_type = fields.Selection(
|
||||
string="Allow Payments From",
|
||||
help="Determines with what payment method the customer can pay.",
|
||||
selection=[('credit_card', "Credit Card"), ('bank_account', "Bank Account (USA Only)")],
|
||||
default='credit_card',
|
||||
required_if_provider='authorize',
|
||||
)
|
||||
|
||||
# === CONSTRAINT METHODS ===#
|
||||
|
||||
@api.constrains('authorize_payment_method_type')
|
||||
def _check_payment_method_type(self):
|
||||
for provider in self.filtered(lambda p: p.code == "authorize"):
|
||||
if self.env['payment.token'].search([('provider_id', '=', provider.id)], limit=1):
|
||||
raise ValidationError(_(
|
||||
"There are active tokens linked to this provider. To change the payment method "
|
||||
"type, please disable the provider and duplicate it. Then, change the payment "
|
||||
"method type on the duplicated provider."
|
||||
))
|
||||
|
||||
# Authorize.Net supports only one currency: "One gateway account is required for each currency"
|
||||
# See https://community.developer.authorize.net/t5/The-Authorize-Net-Developer-Blog/Authorize-Net-UK-Europe-Update/ba-p/35957
|
||||
@api.constrains('available_currency_ids', 'state')
|
||||
@@ -68,24 +52,6 @@ class PaymentProvider(models.Model):
|
||||
'support_tokenization': True,
|
||||
})
|
||||
|
||||
# === ONCHANGE METHODS ===#
|
||||
|
||||
@api.onchange('authorize_payment_method_type')
|
||||
def _onchange_authorize_payment_method_type(self):
|
||||
if self.authorize_payment_method_type == 'bank_account':
|
||||
self.display_as = _("Bank (powered by Authorize)")
|
||||
self.payment_method_ids = [Command.clear()]
|
||||
else:
|
||||
self.display_as = _("Credit Card (powered by Authorize)")
|
||||
self.payment_method_ids = [Command.set([self.env.ref(pm_xml_id).id for pm_xml_id in (
|
||||
'payment.payment_method_maestro',
|
||||
'payment.payment_method_mastercard',
|
||||
'payment.payment_method_discover',
|
||||
'payment.payment_method_diners_club_intl',
|
||||
'payment.payment_method_jcb',
|
||||
'payment.payment_method_visa',
|
||||
)])]
|
||||
|
||||
# === ACTION METHODS ===#
|
||||
|
||||
def action_update_merchant_details(self):
|
||||
@@ -138,3 +104,20 @@ class PaymentProvider(models.Model):
|
||||
return res
|
||||
|
||||
return self.available_currency_ids[0]
|
||||
|
||||
def _authorize_get_inline_form_values(self):
|
||||
""" Return a serialized JSON of the required values to render the inline form.
|
||||
|
||||
Note: `self.ensure_one()`
|
||||
|
||||
:return: The JSON serial of the required values to render the inline form.
|
||||
:rtype: str
|
||||
"""
|
||||
self.ensure_one()
|
||||
|
||||
inline_form_values = {
|
||||
'state': self.state,
|
||||
'login_id': self.authorize_login,
|
||||
'client_key': self.authorize_client_key,
|
||||
}
|
||||
return json.dumps(inline_form_values)
|
||||
|
||||
@@ -17,8 +17,3 @@ class PaymentToken(models.Model):
|
||||
authorize_profile = fields.Char(
|
||||
string="Authorize.Net Profile ID",
|
||||
help="The unique reference for the partner/token combination in the Authorize.net backend.")
|
||||
authorize_payment_method_type = fields.Selection(
|
||||
string="Authorize.Net Payment Type",
|
||||
help="The type of payment method this token is linked to.",
|
||||
selection=[("credit_card", "Credit Card"), ("bank_account", "Bank Account (USA Only)")],
|
||||
)
|
||||
|
||||
@@ -8,7 +8,7 @@ from odoo.exceptions import UserError, ValidationError
|
||||
|
||||
from odoo.addons.payment import utils as payment_utils
|
||||
from odoo.addons.payment_authorize.models.authorize_request import AuthorizeAPI
|
||||
from odoo.addons.payment_authorize.const import TRANSACTION_STATUS_MAPPING
|
||||
from odoo.addons.payment_authorize.const import PAYMENT_METHODS_MAPPING, TRANSACTION_STATUS_MAPPING
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
@@ -211,7 +211,17 @@ class PaymentTransaction(models.Model):
|
||||
|
||||
response_content = notification_data.get('response')
|
||||
|
||||
# Update the provider reference.
|
||||
self.provider_reference = response_content.get('x_trans_id')
|
||||
|
||||
# Update the payment method.
|
||||
payment_method_code = response_content.get('payment_method_code', '').lower()
|
||||
payment_method = self.env['payment.method']._get_from_code(
|
||||
payment_method_code, mapping=PAYMENT_METHODS_MAPPING
|
||||
)
|
||||
self.payment_method_id = payment_method or self.payment_method_id
|
||||
|
||||
# Update the payment state.
|
||||
status_code = response_content.get('x_response_code', '3')
|
||||
if status_code == '1': # Approved
|
||||
status_type = response_content.get('x_type').lower()
|
||||
@@ -277,12 +287,11 @@ class PaymentTransaction(models.Model):
|
||||
if cust_profile:
|
||||
token = self.env['payment.token'].create({
|
||||
'provider_id': self.provider_id.id,
|
||||
'payment_method_id': self.payment_method_id.id,
|
||||
'payment_details': cust_profile.get('payment_details'),
|
||||
'partner_id': self.partner_id.id,
|
||||
'provider_ref': cust_profile.get('payment_profile_id'),
|
||||
'authorize_profile': cust_profile.get('profile_id'),
|
||||
'authorize_payment_method_type': self.provider_id.authorize_payment_method_type,
|
||||
'verified': True,
|
||||
})
|
||||
self.write({
|
||||
'token_id': token.id,
|
||||
|
||||
@@ -4,34 +4,182 @@
|
||||
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 paymentForm from '@payment/js/payment_form';
|
||||
|
||||
const authorizeMixin = {
|
||||
paymentForm.include({
|
||||
|
||||
authorizeData: undefined,
|
||||
|
||||
// #=== DOM MANIPULATION ===#
|
||||
|
||||
/**
|
||||
* Prepare the inline form of Authorize.net for direct payment.
|
||||
*
|
||||
* @private
|
||||
* @param {number} providerId - The id of the selected payment option's provider.
|
||||
* @param {string} providerCode - The code of the selected payment option's provider.
|
||||
* @param {number} paymentOptionId - The id of the selected payment option.
|
||||
* @param {string} paymentMethodCode - The code of the selected payment method, if any.
|
||||
* @param {string} flow - The online payment flow of the selected payment option.
|
||||
* @return {void}
|
||||
*/
|
||||
async _prepareInlineForm(providerId, providerCode, paymentOptionId, paymentMethodCode, flow) {
|
||||
if (providerCode !== 'authorize') {
|
||||
this._super(...arguments);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the inline form values were already extracted.
|
||||
this.authorizeData ??= {}; // Store the form data of each instantiated payment method.
|
||||
if (flow === 'token') {
|
||||
return; // Don't show the form for tokens.
|
||||
} else if (this.authorizeData[paymentOptionId]) {
|
||||
this._setPaymentFlow('direct'); // Overwrite the flow even if no re-instantiation.
|
||||
loadJS(this.authorizeData[paymentOptionId]['acceptJSUrl']); // Reload the SDK.
|
||||
return; // Don't re-extract the data if already done for this payment method.
|
||||
}
|
||||
|
||||
// Overwrite the flow of the selected payment method.
|
||||
this._setPaymentFlow('direct');
|
||||
|
||||
// Extract and deserialize the inline form values.
|
||||
const radio = document.querySelector('input[name="o_payment_radio"]:checked');
|
||||
const inlineForm = this._getInlineForm(radio);
|
||||
const authorizeForm = inlineForm.querySelector('[name="o_authorize_form"]');
|
||||
this.authorizeData[paymentOptionId] = JSON.parse(authorizeForm.dataset['inlineFormValues']);
|
||||
let acceptJSUrl = 'https://js.authorize.net/v1/Accept.js';
|
||||
if (this.authorizeData[paymentOptionId].state !== 'enabled') {
|
||||
acceptJSUrl = 'https://jstest.authorize.net/v1/Accept.js';
|
||||
}
|
||||
this.authorizeData[paymentOptionId].form = authorizeForm;
|
||||
this.authorizeData[paymentOptionId].acceptJSUrl = acceptJSUrl;
|
||||
|
||||
// Load the SDK.
|
||||
loadJS(acceptJSUrl);
|
||||
},
|
||||
|
||||
// #=== PAYMENT FLOW ===#
|
||||
|
||||
/**
|
||||
* Trigger the payment processing by submitting the data.
|
||||
*
|
||||
* @override method from payment.payment_form
|
||||
* @private
|
||||
* @param {string} providerCode - The code of the selected payment option's provider.
|
||||
* @param {number} paymentOptionId - The id of the selected payment option.
|
||||
* @param {string} paymentMethodCode - The code of the selected payment method, if any.
|
||||
* @param {string} flow - The payment flow of the selected payment option.
|
||||
* @return {void}
|
||||
*/
|
||||
async _initiatePaymentFlow(providerCode, paymentOptionId, paymentMethodCode, flow) {
|
||||
if (providerCode !== 'authorize' || flow === 'token') {
|
||||
this._super(...arguments); // Tokens are handled by the generic flow
|
||||
return;
|
||||
}
|
||||
|
||||
const inputs = Object.values(
|
||||
this._authorizeGetInlineFormInputs(paymentOptionId, paymentMethodCode)
|
||||
);
|
||||
if (!inputs.every(element => element.reportValidity())) {
|
||||
this._enableButton(); // The submit button is disabled at this point, enable it
|
||||
return;
|
||||
}
|
||||
|
||||
await this._super(...arguments);
|
||||
},
|
||||
|
||||
/**
|
||||
* Process the direct payment flow.
|
||||
*
|
||||
* @override method from payment.payment_form
|
||||
* @private
|
||||
* @param {string} providerCode - The code of the selected payment option's provider.
|
||||
* @param {number} paymentOptionId - The id of the selected payment option.
|
||||
* @param {string} paymentMethodCode - The code of the selected payment method, if any.
|
||||
* @param {object} processingValues - The processing values of the transaction.
|
||||
* @return {void}
|
||||
*/
|
||||
async _processDirectFlow(providerCode, paymentOptionId, paymentMethodCode, processingValues) {
|
||||
if (providerCode !== 'authorize') {
|
||||
this._super(...arguments);
|
||||
return;
|
||||
}
|
||||
|
||||
// Build the authentication and card data objects to be dispatched to Authorized.Net
|
||||
const secureData = {
|
||||
authData: {
|
||||
apiLoginID: this.authorizeData[paymentOptionId]['login_id'],
|
||||
clientKey: this.authorizeData[paymentOptionId]['client_key'],
|
||||
},
|
||||
...this._authorizeGetPaymentDetails(paymentOptionId, paymentMethodCode),
|
||||
};
|
||||
|
||||
// Dispatch secure data to Authorize.Net to get a payment nonce in return
|
||||
Accept.dispatchData(
|
||||
secureData, response => this._authorizeHandleResponse(response, processingValues)
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle the response from Authorize.Net and initiate the payment.
|
||||
*
|
||||
* @private
|
||||
* @param {object} response - The payment nonce returned by Authorized.Net
|
||||
* @param {object} processingValues - The processing values of the transaction.
|
||||
* @return {void}
|
||||
*/
|
||||
_authorizeHandleResponse(response, processingValues) {
|
||||
if (response.messages.resultCode === 'Error') {
|
||||
let error = '';
|
||||
response.messages.message.forEach(msg => error += `${msg.code}: ${msg.text}\n`);
|
||||
this._displayErrorDialog(_t("Payment processing failed"), error);
|
||||
this._enableButton();
|
||||
return;
|
||||
}
|
||||
|
||||
// Initiate the payment
|
||||
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._displayErrorDialog(_t("Payment processing failed"), error.message.data.message);
|
||||
this._enableButton();
|
||||
});
|
||||
},
|
||||
|
||||
// #=== GETTERS ===#
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param {number} paymentOptionId - The id of the selected payment option.
|
||||
* @param {string} paymentMethodCode - The code of the selected payment method, if any.
|
||||
* @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') {
|
||||
_authorizeGetInlineFormInputs(paymentOptionId, paymentMethodCode) {
|
||||
const form = this.authorizeData[paymentOptionId]['form'];
|
||||
if (paymentMethodCode === '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}`),
|
||||
card: form.querySelector('#o_authorize_card'),
|
||||
month: form.querySelector('#o_authorize_month'),
|
||||
year: form.querySelector('#o_authorize_year'),
|
||||
code: form.querySelector('#o_authorize_code'),
|
||||
};
|
||||
} 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}`),
|
||||
accountName: form.querySelector('#o_authorize_account_name'),
|
||||
accountNumber: form.querySelector('#o_authorize_account_number'),
|
||||
abaNumber: form.querySelector('#o_authorize_aba_number'),
|
||||
accountType: form.querySelector('#o_authorize_account_type'),
|
||||
};
|
||||
}
|
||||
},
|
||||
@@ -40,12 +188,13 @@ const authorizeMixin = {
|
||||
* Return the credit card or bank data to pass to the Accept.dispatch request.
|
||||
*
|
||||
* @private
|
||||
* @param {number} providerId - The id of the selected provider
|
||||
* @param {number} paymentOptionId - The id of the selected payment option.
|
||||
* @param {string} paymentMethodCode - The code of the selected payment method, if any.
|
||||
* @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') {
|
||||
_authorizeGetPaymentDetails(paymentOptionId, paymentMethodCode) {
|
||||
const inputs = this._authorizeGetInlineFormInputs(paymentOptionId, paymentMethodCode);
|
||||
if (paymentMethodCode === 'card') {
|
||||
return {
|
||||
cardData: {
|
||||
cardNumber: inputs.card.value.replace(/ /g, ''), // Remove all spaces
|
||||
@@ -66,148 +215,4 @@ const authorizeMixin = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Prepare the inline form of Authorize.Net 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 {void}
|
||||
*/
|
||||
_prepareInlineForm: function (code, paymentOptionId, flow) {
|
||||
if (code !== 'authorize') {
|
||||
this._super(...arguments);
|
||||
return;
|
||||
}
|
||||
|
||||
if (flow === 'token') {
|
||||
return; // Don't show the form for tokens
|
||||
}
|
||||
|
||||
this._setPaymentFlow('direct');
|
||||
|
||||
let acceptJSUrl = 'https://js.authorize.net/v1/Accept.js';
|
||||
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 {void}
|
||||
*/
|
||||
_processPayment: function (code, paymentOptionId, flow) {
|
||||
if (code !== 'authorize' || flow === 'token') {
|
||||
this._super(...arguments); // Tokens are handled by the generic flow
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 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
|
||||
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 {void}
|
||||
*/
|
||||
_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;
|
||||
}
|
||||
|
||||
// Create the transaction and retrieve the processing values
|
||||
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,
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 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);
|
||||
});
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
/* Disable the number input arrows. */
|
||||
.o_authorize_form {
|
||||
label {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
/* Chrome, Safari, Edge, Opera */
|
||||
input[type=number]::-webkit-outer-spin-button,
|
||||
input[type=number]::-webkit-inner-spin-button {
|
||||
appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
input[type=number] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,50 +2,55 @@
|
||||
<odoo>
|
||||
|
||||
<template id="inline_form">
|
||||
<div t-if="provider.authorize_payment_method_type == 'credit_card'" t-attf-id="o_authorize_form_{{provider_id}}" class="o_authorize_form">
|
||||
<div class="mb-3">
|
||||
<label t-attf-for="o_authorize_card_{{provider_id}}" class="col-form-label">Card Number</label>
|
||||
<input type="text" t-attf-id="o_authorize_card_{{provider_id}}" required="" maxlength="19" class="form-control"/>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-8 mb-3">
|
||||
<label t-attf-for="o_authorize_month_{{provider_id}}">Expiration</label>
|
||||
<div class="input-group">
|
||||
<input type="number" t-attf-id="o_authorize_month_{{provider_id}}" placeholder="MM" min="1" max="12" required="" class="form-control"/>
|
||||
<input type="number" t-attf-id="o_authorize_year_{{provider_id}}" placeholder="YY" min="00" max="99" required="" class="form-control"/>
|
||||
<div name="o_authorize_form"
|
||||
class="o_authorize_form"
|
||||
t-att-data-inline-form-values="provider_sudo._authorize_get_inline_form_values()"
|
||||
>
|
||||
<t t-if="pm_sudo.code == 'card'">
|
||||
<div class="mb-3">
|
||||
<label for="o_authorize_card" class="col-form-label">Card Number</label>
|
||||
<input type="text" id="o_authorize_card" required="" maxlength="19" class="form-control"/>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-8 mb-3">
|
||||
<label for="o_authorize_month">Expiration</label>
|
||||
<div class="input-group">
|
||||
<input type="number" id="o_authorize_month" placeholder="MM" min="1" max="12" required="" class="form-control"/>
|
||||
<input type="number" id="o_authorize_year" placeholder="YY" min="00" max="99" required="" class="form-control"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4 mb-3">
|
||||
<label for="o_authorize_code">Card Code</label>
|
||||
<input type="number" id="o_authorize_code" max="9999" class="form-control"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4 mb-3">
|
||||
<label t-attf-for="o_authorize_code_{{provider_id}}">Card Code</label>
|
||||
<input type="number" t-attf-id="o_authorize_code_{{provider_id}}" max="9999" class="form-control"/>
|
||||
</t>
|
||||
<t t-else="" >
|
||||
<div class="mb-3">
|
||||
<label for="o_authorize_bank_name" class="col-form-label">Bank Name</label>
|
||||
<input type="text" id="o_authorize_bank_name" required="" class="form-control"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div t-else="" t-attf-id="o_authorize_form_{{provider_id}}" class="o_authorize_form">
|
||||
<div class="mb-3">
|
||||
<label t-attf-for="o_authorize_bank_name_{{provider_id}}" class="col-form-label">Bank Name</label>
|
||||
<input type="text" t-attf-id="o_authorize_bank_name_{{provider_id}}" required="" class="form-control"/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label t-attf-for="o_authorize_account_name_{{provider_id}}" class="col-form-label">Name On Account</label>
|
||||
<input type="text" t-attf-id="o_authorize_account_name_{{provider_id}}" required="" class="form-control"/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label t-attf-for="o_authorize_account_number_{{provider_id}}" class="col-form-label">Account Number</label>
|
||||
<input type="text" t-attf-id="o_authorize_account_number_{{provider_id}}" required="" class="form-control"/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label t-attf-for="o_authorize_aba_number_{{provider_id}}" class="col-form-label">ABA Routing Number</label>
|
||||
<input type="text" t-attf-id="o_authorize_aba_number_{{provider_id}}" required="" class="form-control"/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label t-attf-for="o_authorize_account_type_{{provider_id}}" class="col-form-label">Bank Account Type</label>
|
||||
<select t-attf-id="o_authorize_account_type_{{provider_id}}" required="" class="form-select">
|
||||
<option value="checking">Personal Checking</option>
|
||||
<option value="savings">Personal Savings</option>
|
||||
<option value="businessChecking">Business Checking</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="o_authorize_account_name" class="col-form-label">Name On Account</label>
|
||||
<input type="text" id="o_authorize_account_name" required="" class="form-control"/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="o_authorize_account_number" class="col-form-label">Account Number</label>
|
||||
<input type="text" id="o_authorize_account_number" required="" class="form-control"/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="o_authorize_aba_number" class="col-form-label">ABA Routing Number</label>
|
||||
<input type="text" id="o_authorize_aba_number" required="" class="form-control"/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="o_authorize_account_type" class="col-form-label">Bank Account Type</label>
|
||||
<select id="o_authorize_account_type" required="" class="form-select">
|
||||
<option value="checking">Personal Checking</option>
|
||||
<option value="savings">Personal Savings</option>
|
||||
<option value="businessChecking">Business Checking</option>
|
||||
</select>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -23,11 +23,6 @@
|
||||
</a>
|
||||
</group>
|
||||
</group>
|
||||
<field name="display_as" position="before">
|
||||
<field name="authorize_payment_method_type"
|
||||
invisible="code != 'authorize'"
|
||||
required="code == 'authorize' and state != 'disabled'"/>
|
||||
</field>
|
||||
<div name="available_currencies" position="inside">
|
||||
<button string="Set Account Currency"
|
||||
type="object"
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
<field name="provider_ref" position="after">
|
||||
<field name="provider_code" invisible="1"/>
|
||||
<field name="authorize_profile" invisible="provider_code != 'authorize'"/>
|
||||
<field name="authorize_payment_method_type" invisible="provider_code != 'authorize'"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
Reference in New Issue
Block a user