Odoo18-Base/addons/payment_paypal/static/src/js/payment_form.js
2025-01-06 10:57:38 +07:00

177 lines
7.1 KiB
JavaScript

/* global paypal */
import { loadJS } from '@web/core/assets';
import { _t } from '@web/core/l10n/translation';
import { rpc, RPCError } from '@web/core/network/rpc';
import paymentForm from '@payment/js/payment_form';
paymentForm.include({
inlineFormValues: undefined,
paypalColor: 'blue',
selectedOptionId: undefined,
paypalData: undefined,
// #=== DOM MANIPULATION ===#
/**
* Hides paypal button container if the expanded inline form is another provider.
*
* @private
* @param {HTMLInputElement} radio - The radio button linked to the payment option.
* @return {void}
*/
async _expandInlineForm(radio) {
const providerCode = this._getProviderCode(radio);
if (providerCode !== 'paypal') {
document.getElementById('o_paypal_button').classList.add('d-none');
}
this._super(...arguments);
},
/**
* Prepare the inline form of Paypal for direct payment.
* The PayPal sdk creates the payment button based on the client_id
* and the currency of the order.
* The created button is saved and reused when switching between different payment methods,
* to avoid recreating the buttons.
*
* @override method from @payment/js/payment_form
* @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 !== 'paypal') {
this._super(...arguments);
return;
}
this._hideInputs();
this._setPaymentFlow('direct');
document.getElementById('o_paypal_button').classList.remove('d-none');
document.getElementById('o_paypal_loading').classList.remove('d-none');
// Check if instantiation of the component is needed.
this.paypalData ??= {}; // Store the component of each instantiated payment method.
const currentPayPalData = this.paypalData[paymentOptionId]
if (this.selectedOptionId && this.selectedOptionId !== paymentOptionId) {
this.paypalData[this.selectedOptionId]['paypalButton'].hide()
}
if (currentPayPalData && this.selectedOptionId !== paymentOptionId) {
const paypalSDKURL = this.paypalData[paymentOptionId]['sdkURL']
const paypalButton = this.paypalData[paymentOptionId]['paypalButton']
await loadJS(paypalSDKURL);
paypalButton.show();
}
else if (!currentPayPalData) {
this.paypalData[paymentOptionId] = {}
const radio = document.querySelector('input[name="o_payment_radio"]:checked');
if (radio) {
this.inlineFormValues = JSON.parse(radio.dataset['paypalInlineFormValues']);
this.paypalColor = radio.dataset['paypalColor']
}
// https://developer.paypal.com/sdk/js/configuration/#link-queryparameters
const { client_id, currency_code } = this.inlineFormValues
const paypalSDKURL = `https://www.paypal.com/sdk/js?client-id=${
client_id}&components=buttons&currency=${currency_code}&intent=capture`
await loadJS(paypalSDKURL);
const paypalButton = paypal.Buttons({ // https://developer.paypal.com/sdk/js/reference
fundingSource: paypal.FUNDING.PAYPAL,
style: { // https://developer.paypal.com/sdk/js/reference/#link-style
color: this.paypalColor,
label: 'paypal',
disableMaxWidth: true,
borderRadius: 6,
},
createOrder: this._paypalOnClick.bind(this),
onApprove: this._paypalOnApprove.bind(this),
onCancel: this._paypalOnCancel.bind(this),
onError: this._paypalOnError.bind(this),
});
this.paypalData[paymentOptionId]['sdkURL'] = paypalSDKURL;
this.paypalData[paymentOptionId]['paypalButton'] = paypalButton;
paypalButton.render('#o_paypal_button');
}
document.getElementById('o_paypal_loading').classList.add('d-none');
this.selectedOptionId = paymentOptionId;
},
// #=== PAYMENT FLOW ===#
/**
* Handle the click event of the component and initiate the payment.
*
* @private
* @return {void}
*/
async _paypalOnClick() {
await this._submitForm(new Event("PayPalClickEvent"));
return this.paypalData[this.selectedOptionId].paypalOrderId;
},
_processDirectFlow(providerCode, paymentOptionId, paymentMethodCode, processingValues) {
if (providerCode !== 'paypal') {
this._super(...arguments);
return;
}
this.paypalData[paymentOptionId].paypalOrderId = processingValues['order_id'];
this.paypalData[paymentOptionId].paypalTxRef = processingValues['reference'];
},
/**
* Handle the approval event of the component and complete the payment.
*
* @private
* @param {object} data - The data returned by PayPal on approving the order.
* @return {void}
*/
async _paypalOnApprove(data) {
const orderID = data.orderID;
const { provider_id } = this.inlineFormValues
await rpc('/payment/paypal/complete_order', {
'provider_id': provider_id,
'order_id': orderID,
'reference': this.paypalData[this.selectedOptionId].paypalTxRef,
}).then(() => {
// Close the PayPal buttons that were rendered
this.paypalData[this.selectedOptionId]['paypalButton'].close();
window.location = '/payment/status';
}).catch(error => {
if (error instanceof RPCError) {
this._displayErrorDialog(_t("Payment processing failed"), error.data.message);
this._enableButton(); // The button has been disabled before initiating the flow.
}
return Promise.reject(error);
})
},
/**
* Handle the cancel event of the component.
* @private
* @return {void}
*/
_paypalOnCancel() {
this.call('ui', 'unblock');
},
/**
* Handle the error event of the component.
* @private
* @param {object} error - The error in the component.
* @return {void}
*/
_paypalOnError(error) {
const message = error.message
this.call('ui', 'unblock');
// Paypal throws an error if the popup is closed before it can load;
// this case should be treated as an onCancel event.
if (message !== "Detected popup close" && !(error instanceof RPCError)) {
this._displayErrorDialog(_t("Payment processing failed"), message);
}
},
});