From: Shamim Rezaie Date: Sun, 18 Oct 2020 15:31:44 +0000 (+1100) Subject: MDL-69166 core_payment: Use promises instead of callbacks X-Git-Tag: v3.10.0-beta~6^2~1 X-Git-Url: http://git.moodle.org/gw?p=moodle.git;a=commitdiff_plain;h=0fb7847e09a7697a8d0a0a95932d853c7d3781e6 MDL-69166 core_payment: Use promises instead of callbacks --- diff --git a/payment/amd/build/gateways_modal.min.js b/payment/amd/build/gateways_modal.min.js index d177a3d55ed..3cda8102180 100644 Binary files a/payment/amd/build/gateways_modal.min.js and b/payment/amd/build/gateways_modal.min.js differ diff --git a/payment/amd/build/gateways_modal.min.js.map b/payment/amd/build/gateways_modal.min.js.map index cc38c7626c3..d215e534c80 100644 Binary files a/payment/amd/build/gateways_modal.min.js.map and b/payment/amd/build/gateways_modal.min.js.map differ diff --git a/payment/amd/src/gateways_modal.js b/payment/amd/src/gateways_modal.js index 3ae9f66453b..5d07448a030 100644 --- a/payment/amd/src/gateways_modal.js +++ b/payment/amd/src/gateways_modal.js @@ -87,25 +87,25 @@ const show = async(rootNode, { rootNode.dataset.component, rootNode.dataset.paymentarea, rootNode.dataset.itemid, - rootNode.dataset.description, - ({success, message = ''}) => { - modal.hide(); - if (success) { - Notification.addNotification({ - message: message, - type: 'success', - }); - location.reload(); - } else { - Notification.alert('', message); - } - }, - ); + rootNode.dataset.description + ) + .then(message => { + modal.hide(); + Notification.addNotification({ + message: message, + type: 'success', + }); + location.reload(); + + // The following return statement is never reached. It is put here just to make eslint happy. + return message; + }) + .catch(message => Notification.alert('', message)); } else { // We cannot use await in the following line. // The reason is that we are preventing the default action of the save event being triggered, // therefore we cannot define the event handler function asynchronous. - getString('nogatewayselected', 'core_payment').then(message => addToast(message)); + getString('nogatewayselected', 'core_payment').then(message => addToast(message)).catch(); } e.preventDefault(); @@ -166,22 +166,13 @@ const updateCostRegion = async(root, defaultCost = '') => { * @param {string} paymentArea Name of the area in the component that the itemId belongs to * @param {number} itemId An internal identifier that is used by the component * @param {string} description Description of the payment - * @param {processPaymentCallback} callback The callback function to call when processing is finished - * @returns {Promise} + * @returns {Promise} */ -const processPayment = async(gateway, component, paymentArea, itemId, description, callback) => { +const processPayment = async(gateway, component, paymentArea, itemId, description) => { const paymentMethod = await import(`paygw_${gateway}/gateways_modal`); - paymentMethod.process(component, paymentArea, itemId, description, callback); + return paymentMethod.process(component, paymentArea, itemId, description); }; -/** - * The callback definition for processPayment. - * - * @callback processPaymentCallback - * @param {bool} success - * @param {string} message - */ - /** * Set up the payment actions. */ diff --git a/payment/gateway/paypal/amd/build/gateways_modal.min.js b/payment/gateway/paypal/amd/build/gateways_modal.min.js index 8faa42b473c..21236a04fed 100644 Binary files a/payment/gateway/paypal/amd/build/gateways_modal.min.js and b/payment/gateway/paypal/amd/build/gateways_modal.min.js differ diff --git a/payment/gateway/paypal/amd/build/gateways_modal.min.js.map b/payment/gateway/paypal/amd/build/gateways_modal.min.js.map index e3a4b5ebcf6..62f32280592 100644 Binary files a/payment/gateway/paypal/amd/build/gateways_modal.min.js.map and b/payment/gateway/paypal/amd/build/gateways_modal.min.js.map differ diff --git a/payment/gateway/paypal/amd/build/repository.min.js b/payment/gateway/paypal/amd/build/repository.min.js index 83d0ead3479..72cdeb92862 100644 Binary files a/payment/gateway/paypal/amd/build/repository.min.js and b/payment/gateway/paypal/amd/build/repository.min.js differ diff --git a/payment/gateway/paypal/amd/build/repository.min.js.map b/payment/gateway/paypal/amd/build/repository.min.js.map index 9f0f3835069..792cd9b26fc 100644 Binary files a/payment/gateway/paypal/amd/build/repository.min.js.map and b/payment/gateway/paypal/amd/build/repository.min.js.map differ diff --git a/payment/gateway/paypal/amd/src/gateways_modal.js b/payment/gateway/paypal/amd/src/gateways_modal.js index 4d68227e4b5..04f8a96ec82 100644 --- a/payment/gateway/paypal/amd/src/gateways_modal.js +++ b/payment/gateway/paypal/amd/src/gateways_modal.js @@ -24,7 +24,6 @@ import * as Repository from './repository'; import Templates from 'core/templates'; import Truncate from 'core/truncate'; -import Ajax from 'core/ajax'; import ModalFactory from 'core/modal_factory'; import ModalEvents from 'core/modal_events'; import {get_string as getString} from 'core/str'; @@ -49,102 +48,95 @@ const showModalWithPlaceholder = async() => { * @param {string} paymentArea The area of the component that the itemId belongs to * @param {number} itemId An internal identifier that is used by the component * @param {string} description Description of the payment - * @param {processCallback} callback The callback function to call when processing is finished - * @returns {Promise} + * @returns {Promise} */ -export const process = async(component, paymentArea, itemId, description, callback) => { - - const [ - modal, - paypalConfig, - ] = await Promise.all([ +export const process = (component, paymentArea, itemId, description) => { + return Promise.all([ showModalWithPlaceholder(), Repository.getConfigForJs(component, paymentArea, itemId), - ]); - const currency = paypalConfig.currency; - const amount = paypalConfig.cost; // Cost with surcharge. - - modal.getRoot().on(ModalEvents.hidden, () => { - // Destroy when hidden. - modal.destroy(); - }); - - const paypalScript = `https://www.paypal.com/sdk/js?client-id=${paypalConfig.clientid}¤cy=${currency}`; - - callExternalFunction(paypalScript, () => { - modal.setBody(''); // We have to clear the body. The render method in paypal.Buttons will render everything. - - paypal.Buttons({ // eslint-disable-line - // Set up the transaction. - createOrder: function(data, actions) { - return actions.order.create({ - purchase_units: [{ // eslint-disable-line - amount: { - currency_code: currency, // eslint-disable-line - value: amount + ]) + .then(([modal, paypalConfig]) => { + modal.getRoot().on(ModalEvents.hidden, () => { + // Destroy when hidden. + modal.destroy(); + }); + + return Promise.all([ + modal, + paypalConfig, + switchSdk(paypalConfig.clientid, paypalConfig.currency), + ]); + }) + .then(([modal, paypalConfig]) => { + // We have to clear the body. The render method in paypal.Buttons will render everything. + modal.setBody(''); + + return new Promise(resolve => { + window.paypal.Buttons({ + // Set up the transaction. + createOrder: function(data, actions) { + return actions.order.create({ + purchase_units: [{ // eslint-disable-line + amount: { + currency_code: paypalConfig.currency_code, // eslint-disable-line + value: paypalConfig.cost, + }, + description: Truncate.truncate(description, {length: 127, stripTags: true}), + }], + application_context: { // eslint-disable-line + shipping_preference: 'NO_SHIPPING', // eslint-disable-line + brand_name: Truncate.truncate(paypalConfig.brandname, {length: 127, stripTags: true}), // eslint-disable-line }, - description: Truncate.truncate(description, {length: 127, stripTags: true}), - }], - application_context: { // eslint-disable-line - shipping_preference: 'NO_SHIPPING', // eslint-disable-line - brand_name: Truncate.truncate(paypalConfig.brandname, {length: 127, stripTags: true}), // eslint-disable-line - }, - }); - }, - // Finalise the transaction. - onApprove: function(data) { - modal.getRoot().on(ModalEvents.outsideClick, (e) => { - // Prevent closing the modal when clicking outside of it. - e.preventDefault(); - }); - - modal.setBody(getString('authorising', 'paygw_paypal')); - - // Call server to validate and capture payment for order. - return Ajax.call([{ - methodname: 'paygw_paypal_create_transaction_complete', - args: { - component, - paymentarea: paymentArea, - itemid: itemId, - orderid: data.orderID, - }, - }])[0] - .then(function(res) { - modal.hide(); - return callback(res); - }); - } - }).render(modal.getBody()[0]); + }); + }, + // Finalise the transaction. + onApprove: function(data) { + modal.getRoot().on(ModalEvents.outsideClick, (e) => { + // Prevent closing the modal when clicking outside of it. + e.preventDefault(); + }); + + modal.setBody(getString('authorising', 'paygw_paypal')); + + Repository.markTransactionComplete(component, paymentArea, itemId, data.orderID) + .then(res => { + modal.hide(); + return res; + }) + .then(resolve); + } + }).render(modal.getBody()[0]); + }); + }) + .then(res => { + if (res.success) { + return Promise.resolve(res.message); + } + + return Promise.reject(res.message); }); }; /** - * The callback definition for process. + * Unloads the previously loaded PayPal JavaScript SDK, and loads a new one. * - * @callback processCallback - * @param {bool} success - * @param {string} message + * @param {string} clientId PayPal client ID + * @param {string} currency The currency + * @returns {Promise} */ +const switchSdk = (clientId, currency) => { + const sdkUrl = `https://www.paypal.com/sdk/js?client-id=${clientId}¤cy=${currency}`; -/** - * Calls a function from an external javascript file. - * - * @param {string} jsFile URL of the external JavaScript file - * @param {function} func The function to call - */ -const callExternalFunction = (jsFile, func) => { // Check to see if this file has already been loaded. If so just go straight to the func. - if (callExternalFunction.currentlyloaded == jsFile) { - func(); - return; + if (switchSdk.currentlyloaded === sdkUrl) { + return Promise.resolve(); } // PayPal can only work with one currency at the same time. We have to unload the previously loaded script // if it was loaded for a different currency. Weird way indeed, but the only way. // See: https://github.com/paypal/paypal-checkout-components/issues/1180 - if (callExternalFunction.currentlyloaded) { - const suspectedScript = document.querySelector(`script[src="${callExternalFunction.currentlyloaded}"]`); + if (switchSdk.currentlyloaded) { + const suspectedScript = document.querySelector(`script[src="${switchSdk.currentlyloaded}"]`); if (suspectedScript) { suspectedScript.parentNode.removeChild(suspectedScript); } @@ -152,29 +144,31 @@ const callExternalFunction = (jsFile, func) => { const script = document.createElement('script'); - if (script.readyState) { - script.onreadystatechange = function() { - if (this.readyState == 'complete' || this.readyState == 'loaded') { - this.onreadystatechange = null; - func(); - } - }; - } else { - script.onload = function() { - func(); - }; - } + return new Promise(resolve => { + if (script.readyState) { + script.onreadystatechange = function() { + if (this.readyState == 'complete' || this.readyState == 'loaded') { + this.onreadystatechange = null; + resolve(); + } + }; + } else { + script.onload = function() { + resolve(); + }; + } - script.setAttribute('src', jsFile); - document.head.appendChild(script); + script.setAttribute('src', sdkUrl); + document.head.appendChild(script); - callExternalFunction.currentlyloaded = jsFile; + switchSdk.currentlyloaded = sdkUrl; + }); }; /** - * Holds the full url of loaded external JavaScript file. + * Holds the full url of loaded PayPal JavaScript SDK. * * @static * @type {string} */ -callExternalFunction.currentlyloaded = ''; +switchSdk.currentlyloaded = ''; diff --git a/payment/gateway/paypal/amd/src/repository.js b/payment/gateway/paypal/amd/src/repository.js index 627753e6956..facec049fb0 100644 --- a/payment/gateway/paypal/amd/src/repository.js +++ b/payment/gateway/paypal/amd/src/repository.js @@ -30,7 +30,7 @@ import Ajax from 'core/ajax'; * @param {string} component Name of the component that the itemId belongs to * @param {string} paymentArea The area of the component that the itemId belongs to * @param {number} itemId An internal identifier that is used by the component - * @returns {Promise<{clientid: string, brandname: string}>} + * @returns {Promise<{clientid: string, brandname: string, cost: number, currency: string}>} */ export const getConfigForJs = (component, paymentArea, itemId) => { const request = { @@ -44,3 +44,26 @@ export const getConfigForJs = (component, paymentArea, itemId) => { return Ajax.call([request])[0]; }; + +/** + * Call server to validate and capture payment for order. + * + * @param {string} component Name of the component that the itemId belongs to + * @param {string} paymentArea The area of the component that the itemId belongs to + * @param {number} itemId An internal identifier that is used by the component + * @param {string} orderId The order id coming back from PayPal + * @returns {*} + */ +export const markTransactionComplete = (component, paymentArea, itemId, orderId) => { + const request = { + methodname: 'paygw_paypal_create_transaction_complete', + args: { + component, + paymentarea: paymentArea, + itemid: itemId, + orderid: orderId, + }, + }; + + return Ajax.call([request])[0]; +};