From 0ecce652b15836cd509cd046d999982c9371141e Mon Sep 17 00:00:00 2001 From: Shamim Rezaie Date: Mon, 19 Oct 2020 02:31:44 +1100 Subject: [PATCH] MDL-69166 core_payment: Use promises instead of callbacks --- payment/amd/build/gateways_modal.min.js | 2 +- payment/amd/build/gateways_modal.min.js.map | 2 +- payment/amd/src/gateways_modal.js | 45 ++--- .../paypal/amd/build/gateways_modal.min.js | 2 +- .../amd/build/gateways_modal.min.js.map | 2 +- .../paypal/amd/build/repository.min.js | 2 +- .../paypal/amd/build/repository.min.js.map | 2 +- .../gateway/paypal/amd/src/gateways_modal.js | 180 +++++++++--------- payment/gateway/paypal/amd/src/repository.js | 25 ++- 9 files changed, 135 insertions(+), 127 deletions(-) diff --git a/payment/amd/build/gateways_modal.min.js b/payment/amd/build/gateways_modal.min.js index d177a3d55ed..3cda8102180 100644 --- a/payment/amd/build/gateways_modal.min.js +++ b/payment/amd/build/gateways_modal.min.js @@ -1,2 +1,2 @@ -define ("core_payment/gateways_modal",["exports","core/modal_factory","core/templates","core/str","./repository","./selectors","core/modal_events","core_payment/events","core/toast","core/notification","./modal_gateways"],function(a,b,c,d,e,f,g,h,i,j,k){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=l(b);c=l(c);f=l(f);g=l(g);h=l(h);j=l(j);k=l(k);var o="undefined"!=typeof window?window:"undefined"!=typeof self?self:"undefined"!=typeof global?global:{};function l(a){return a&&a.__esModule?a:{default:a}}function m(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function n(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var h=a.apply(b,c);function f(a){m(h,d,e,f,g,"next",a)}function g(a){m(h,d,e,f,g,"throw",a)}f(void 0)})}}var p=function(){document.addEventListener("click",function(a){var b=a.target.closest("[data-action=\"core_payment/triggerPayment\"]");if(b){a.preventDefault();q(b,{focusOnClose:a.target})}})},q=function(){var a=n(regeneratorRuntime.mark(function a(l){var m,n,o,p,q,u,v,w,x,y,z=arguments;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:m=1.\n\n/**\n * Contain the logic for the gateways modal.\n *\n * @module core_payment/gateways_modal\n * @package core_payment\n * @copyright 2019 Shamim Rezaie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport ModalFactory from 'core/modal_factory';\nimport Templates from 'core/templates';\nimport {get_string as getString} from 'core/str';\nimport {getAvailableGateways} from './repository';\nimport Selectors from './selectors';\nimport ModalEvents from 'core/modal_events';\nimport PaymentEvents from 'core_payment/events';\nimport {add as addToast, addToastRegion} from 'core/toast';\nimport Notification from 'core/notification';\nimport ModalGateways from './modal_gateways';\n\n/**\n * Register event listeners for the module.\n */\nconst registerEventListeners = () => {\n document.addEventListener('click', e => {\n const gatewayTrigger = e.target.closest('[data-action=\"core_payment/triggerPayment\"]');\n if (gatewayTrigger) {\n e.preventDefault();\n\n show(gatewayTrigger, {focusOnClose: e.target});\n }\n });\n};\n\n/**\n * Shows the gateway selector modal.\n *\n * @param {HTMLElement} rootNode\n * @param {Object} options - Additional options\n * @param {HTMLElement} options.focusOnClose The element to focus on when the modal is closed.\n */\nconst show = async(rootNode, {\n focusOnClose = null,\n} = {}) => {\n const modal = await ModalFactory.create({\n type: ModalGateways.TYPE,\n title: await getString('selectpaymenttype', 'core_payment'),\n body: await Templates.render('core_payment/gateways_modal', {}),\n });\n\n const rootElement = modal.getRoot()[0];\n addToastRegion(rootElement);\n\n modal.show();\n\n modal.getRoot().on(ModalEvents.hidden, () => {\n // Destroy when hidden.\n modal.destroy();\n try {\n focusOnClose.focus();\n } catch (e) {\n // eslint-disable-line\n }\n });\n\n modal.getRoot().on(PaymentEvents.proceed, (e) => {\n const gateway = (rootElement.querySelector(Selectors.values.gateway) || {value: ''}).value;\n\n if (gateway) {\n processPayment(\n gateway,\n rootNode.dataset.component,\n rootNode.dataset.paymentarea,\n rootNode.dataset.itemid,\n rootNode.dataset.description,\n ({success, message = ''}) => {\n modal.hide();\n if (success) {\n Notification.addNotification({\n message: message,\n type: 'success',\n });\n location.reload();\n } else {\n Notification.alert('', message);\n }\n },\n );\n } else {\n // We cannot use await in the following line.\n // The reason is that we are preventing the default action of the save event being triggered,\n // therefore we cannot define the event handler function asynchronous.\n getString('nogatewayselected', 'core_payment').then(message => addToast(message));\n }\n\n e.preventDefault();\n });\n\n // Re-calculate the cost when gateway is changed.\n rootElement.addEventListener('change', e => {\n if (e.target.matches(Selectors.elements.gateways)) {\n updateCostRegion(rootElement, rootNode.dataset.cost);\n }\n });\n\n const gateways = await getAvailableGateways(rootNode.dataset.component, rootNode.dataset.paymentarea, rootNode.dataset.itemid);\n const context = {\n gateways\n };\n\n const {html, js} = await Templates.renderForPromise('core_payment/gateways', context);\n Templates.replaceNodeContents(rootElement.querySelector(Selectors.regions.gatewaysContainer), html, js);\n selectSingleGateway(rootElement);\n await updateCostRegion(rootElement, rootNode.dataset.cost);\n};\n\n/**\n * Auto-select the gateway if there is only one gateway.\n *\n * @param {HTMLElement} root An HTMLElement that contains the cost region\n */\nconst selectSingleGateway = root => {\n const gateways = root.querySelectorAll(Selectors.elements.gateways);\n\n if (gateways.length == 1) {\n gateways[0].checked = true;\n }\n};\n\n/**\n * Shows the cost of the item the user is purchasing in the cost region.\n *\n * @param {HTMLElement} root An HTMLElement that contains the cost region\n * @param {string} defaultCost The default cost that is going to be displayed if no gateway is selected\n * @returns {Promise}\n */\nconst updateCostRegion = async(root, defaultCost = '') => {\n const gatewayElement = root.querySelector(Selectors.values.gateway);\n const surcharge = parseInt((gatewayElement || {dataset: {surcharge: 0}}).dataset.surcharge);\n const cost = (gatewayElement || {dataset: {cost: defaultCost}}).dataset.cost;\n\n const {html, js} = await Templates.renderForPromise('core_payment/fee_breakdown', {fee: cost, surcharge});\n Templates.replaceNodeContents(root.querySelector(Selectors.regions.costContainer), html, js);\n};\n\n/**\n * Process payment using the selected gateway.\n *\n * @param {string} gateway The gateway to be used for payment\n * @param {string} component Name of the component that the itemId belongs to\n * @param {string} paymentArea Name of the area in the component that the itemId belongs to\n * @param {number} itemId An internal identifier that is used by the component\n * @param {string} description Description of the payment\n * @param {processPaymentCallback} callback The callback function to call when processing is finished\n * @returns {Promise}\n */\nconst processPayment = async(gateway, component, paymentArea, itemId, description, callback) => {\n const paymentMethod = await import(`paygw_${gateway}/gateways_modal`);\n paymentMethod.process(component, paymentArea, itemId, description, callback);\n};\n\n/**\n * The callback definition for processPayment.\n *\n * @callback processPaymentCallback\n * @param {bool} success\n * @param {string} message\n */\n\n/**\n * Set up the payment actions.\n */\nexport const init = () => {\n if (!init.initialised) {\n // Event listeners should only be registered once.\n init.initialised = true;\n registerEventListeners();\n }\n};\n\n/**\n * Whether the init function was called before.\n *\n * @static\n * @type {boolean}\n */\ninit.initialised = false;\n"],"file":"gateways_modal.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/gateways_modal.js"],"names":["registerEventListeners","document","addEventListener","e","gatewayTrigger","target","closest","preventDefault","show","focusOnClose","rootNode","ModalFactory","ModalGateways","TYPE","Templates","render","type","title","body","create","modal","rootElement","getRoot","on","ModalEvents","hidden","destroy","focus","PaymentEvents","proceed","gateway","querySelector","Selectors","values","value","processPayment","dataset","component","paymentarea","itemid","description","then","message","hide","Notification","addNotification","location","reload","catch","alert","matches","elements","gateways","updateCostRegion","cost","context","renderForPromise","html","js","replaceNodeContents","regions","gatewaysContainer","selectSingleGateway","root","querySelectorAll","length","checked","defaultCost","gatewayElement","surcharge","parseInt","fee","costContainer","paymentArea","itemId","paymentMethod","process","init","initialised"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Contain the logic for the gateways modal.\n *\n * @module core_payment/gateways_modal\n * @package core_payment\n * @copyright 2019 Shamim Rezaie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport ModalFactory from 'core/modal_factory';\nimport Templates from 'core/templates';\nimport {get_string as getString} from 'core/str';\nimport {getAvailableGateways} from './repository';\nimport Selectors from './selectors';\nimport ModalEvents from 'core/modal_events';\nimport PaymentEvents from 'core_payment/events';\nimport {add as addToast, addToastRegion} from 'core/toast';\nimport Notification from 'core/notification';\nimport ModalGateways from './modal_gateways';\n\n/**\n * Register event listeners for the module.\n */\nconst registerEventListeners = () => {\n document.addEventListener('click', e => {\n const gatewayTrigger = e.target.closest('[data-action=\"core_payment/triggerPayment\"]');\n if (gatewayTrigger) {\n e.preventDefault();\n\n show(gatewayTrigger, {focusOnClose: e.target});\n }\n });\n};\n\n/**\n * Shows the gateway selector modal.\n *\n * @param {HTMLElement} rootNode\n * @param {Object} options - Additional options\n * @param {HTMLElement} options.focusOnClose The element to focus on when the modal is closed.\n */\nconst show = async(rootNode, {\n focusOnClose = null,\n} = {}) => {\n const modal = await ModalFactory.create({\n type: ModalGateways.TYPE,\n title: await getString('selectpaymenttype', 'core_payment'),\n body: await Templates.render('core_payment/gateways_modal', {}),\n });\n\n const rootElement = modal.getRoot()[0];\n addToastRegion(rootElement);\n\n modal.show();\n\n modal.getRoot().on(ModalEvents.hidden, () => {\n // Destroy when hidden.\n modal.destroy();\n try {\n focusOnClose.focus();\n } catch (e) {\n // eslint-disable-line\n }\n });\n\n modal.getRoot().on(PaymentEvents.proceed, (e) => {\n const gateway = (rootElement.querySelector(Selectors.values.gateway) || {value: ''}).value;\n\n if (gateway) {\n processPayment(\n gateway,\n rootNode.dataset.component,\n rootNode.dataset.paymentarea,\n rootNode.dataset.itemid,\n rootNode.dataset.description\n )\n .then(message => {\n modal.hide();\n Notification.addNotification({\n message: message,\n type: 'success',\n });\n location.reload();\n\n // The following return statement is never reached. It is put here just to make eslint happy.\n return message;\n })\n .catch(message => Notification.alert('', message));\n } else {\n // We cannot use await in the following line.\n // The reason is that we are preventing the default action of the save event being triggered,\n // therefore we cannot define the event handler function asynchronous.\n getString('nogatewayselected', 'core_payment').then(message => addToast(message)).catch();\n }\n\n e.preventDefault();\n });\n\n // Re-calculate the cost when gateway is changed.\n rootElement.addEventListener('change', e => {\n if (e.target.matches(Selectors.elements.gateways)) {\n updateCostRegion(rootElement, rootNode.dataset.cost);\n }\n });\n\n const gateways = await getAvailableGateways(rootNode.dataset.component, rootNode.dataset.paymentarea, rootNode.dataset.itemid);\n const context = {\n gateways\n };\n\n const {html, js} = await Templates.renderForPromise('core_payment/gateways', context);\n Templates.replaceNodeContents(rootElement.querySelector(Selectors.regions.gatewaysContainer), html, js);\n selectSingleGateway(rootElement);\n await updateCostRegion(rootElement, rootNode.dataset.cost);\n};\n\n/**\n * Auto-select the gateway if there is only one gateway.\n *\n * @param {HTMLElement} root An HTMLElement that contains the cost region\n */\nconst selectSingleGateway = root => {\n const gateways = root.querySelectorAll(Selectors.elements.gateways);\n\n if (gateways.length == 1) {\n gateways[0].checked = true;\n }\n};\n\n/**\n * Shows the cost of the item the user is purchasing in the cost region.\n *\n * @param {HTMLElement} root An HTMLElement that contains the cost region\n * @param {string} defaultCost The default cost that is going to be displayed if no gateway is selected\n * @returns {Promise}\n */\nconst updateCostRegion = async(root, defaultCost = '') => {\n const gatewayElement = root.querySelector(Selectors.values.gateway);\n const surcharge = parseInt((gatewayElement || {dataset: {surcharge: 0}}).dataset.surcharge);\n const cost = (gatewayElement || {dataset: {cost: defaultCost}}).dataset.cost;\n\n const {html, js} = await Templates.renderForPromise('core_payment/fee_breakdown', {fee: cost, surcharge});\n Templates.replaceNodeContents(root.querySelector(Selectors.regions.costContainer), html, js);\n};\n\n/**\n * Process payment using the selected gateway.\n *\n * @param {string} gateway The gateway to be used for payment\n * @param {string} component Name of the component that the itemId belongs to\n * @param {string} paymentArea Name of the area in the component that the itemId belongs to\n * @param {number} itemId An internal identifier that is used by the component\n * @param {string} description Description of the payment\n * @returns {Promise}\n */\nconst processPayment = async(gateway, component, paymentArea, itemId, description) => {\n const paymentMethod = await import(`paygw_${gateway}/gateways_modal`);\n return paymentMethod.process(component, paymentArea, itemId, description);\n};\n\n/**\n * Set up the payment actions.\n */\nexport const init = () => {\n if (!init.initialised) {\n // Event listeners should only be registered once.\n init.initialised = true;\n registerEventListeners();\n }\n};\n\n/**\n * Whether the init function was called before.\n *\n * @static\n * @type {boolean}\n */\ninit.initialised = false;\n"],"file":"gateways_modal.min.js"} \ No newline at end of file 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 --- a/payment/gateway/paypal/amd/build/gateways_modal.min.js +++ b/payment/gateway/paypal/amd/build/gateways_modal.min.js @@ -1,2 +1,2 @@ -function _typeof(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){_typeof=function(a){return typeof a}}else{_typeof=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return _typeof(a)}define ("paygw_paypal/gateways_modal",["exports","./repository","core/templates","core/truncate","core/ajax","core/modal_factory","core/modal_events","core/str"],function(a,b,c,d,e,f,g,h){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.process=void 0;b=k(b);c=i(c);d=i(d);e=i(e);f=i(f);g=i(g);function i(a){return a&&a.__esModule?a:{default:a}}function j(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;j=function(){return a};return a}function k(a){if(a&&a.__esModule){return a}if(null===a||"object"!==_typeof(a)&&"function"!=typeof a){return{default:a}}var b=j();if(b&&b.has(a)){return b.get(a)}var c={},d=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var e in a){if(Object.prototype.hasOwnProperty.call(a,e)){var f=d?Object.getOwnPropertyDescriptor(a,e):null;if(f&&(f.get||f.set)){Object.defineProperty(c,e,f)}else{c[e]=a[e]}}}c.default=a;if(b){b.set(a,c)}return c}function l(a,b){return r(a)||q(a,b)||n(a,b)||m()}function m(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function n(a,b){if(!a)return;if("string"==typeof a)return p(a,b);var c=Object.prototype.toString.call(a).slice(8,-1);if("Object"===c&&a.constructor)c=a.constructor.name;if("Map"===c||"Set"===c)return Array.from(c);if("Arguments"===c||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(c))return p(a,b)}function p(a,b){if(null==b||b>a.length)b=a.length;for(var c=0,d=Array(b);ca.length)b=a.length;for(var c=0,d=Array(b);c.\n\n/**\n * This module is responsible for PayPal content in the gateways modal.\n *\n * @module paygw_paypal/gateway_modal\n * @copyright 2020 Shamim Rezaie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport * as Repository from './repository';\nimport Templates from 'core/templates';\nimport Truncate from 'core/truncate';\nimport Ajax from 'core/ajax';\nimport ModalFactory from 'core/modal_factory';\nimport ModalEvents from 'core/modal_events';\nimport {get_string as getString} from 'core/str';\n\n/**\n * Creates and shows a modal that contains a placeholder.\n *\n * @returns {Promise}\n */\nconst showModalWithPlaceholder = async() => {\n const modal = await ModalFactory.create({\n body: await Templates.render('paygw_paypal/paypal_button_placeholder', {})\n });\n modal.show();\n return modal;\n};\n\n/**\n * Process the payment.\n *\n * @param {string} component Name of the component that the itemId belongs to\n * @param {string} paymentArea The area of the component that the itemId belongs to\n * @param {number} itemId An internal identifier that is used by the component\n * @param {string} description Description of the payment\n * @param {processCallback} callback The callback function to call when processing is finished\n * @returns {Promise}\n */\nexport const process = async(component, paymentArea, itemId, description, callback) => {\n\n const [\n modal,\n paypalConfig,\n ] = await Promise.all([\n showModalWithPlaceholder(),\n Repository.getConfigForJs(component, paymentArea, itemId),\n ]);\n const currency = paypalConfig.currency;\n const amount = paypalConfig.cost; // Cost with surcharge.\n\n modal.getRoot().on(ModalEvents.hidden, () => {\n // Destroy when hidden.\n modal.destroy();\n });\n\n const paypalScript = `https://www.paypal.com/sdk/js?client-id=${paypalConfig.clientid}¤cy=${currency}`;\n\n callExternalFunction(paypalScript, () => {\n modal.setBody(''); // We have to clear the body. The render method in paypal.Buttons will render everything.\n\n paypal.Buttons({ // eslint-disable-line\n // Set up the transaction.\n createOrder: function(data, actions) {\n return actions.order.create({\n purchase_units: [{ // eslint-disable-line\n amount: {\n currency_code: currency, // eslint-disable-line\n value: amount\n },\n description: Truncate.truncate(description, {length: 127, stripTags: true}),\n }],\n application_context: { // eslint-disable-line\n shipping_preference: 'NO_SHIPPING', // eslint-disable-line\n brand_name: Truncate.truncate(paypalConfig.brandname, {length: 127, stripTags: true}), // eslint-disable-line\n },\n });\n },\n // Finalise the transaction.\n onApprove: function(data) {\n modal.getRoot().on(ModalEvents.outsideClick, (e) => {\n // Prevent closing the modal when clicking outside of it.\n e.preventDefault();\n });\n\n modal.setBody(getString('authorising', 'paygw_paypal'));\n\n // Call server to validate and capture payment for order.\n return Ajax.call([{\n methodname: 'paygw_paypal_create_transaction_complete',\n args: {\n component,\n paymentarea: paymentArea,\n itemid: itemId,\n orderid: data.orderID,\n },\n }])[0]\n .then(function(res) {\n modal.hide();\n return callback(res);\n });\n }\n }).render(modal.getBody()[0]);\n });\n};\n\n/**\n * The callback definition for process.\n *\n * @callback processCallback\n * @param {bool} success\n * @param {string} message\n */\n\n/**\n * Calls a function from an external javascript file.\n *\n * @param {string} jsFile URL of the external JavaScript file\n * @param {function} func The function to call\n */\nconst callExternalFunction = (jsFile, func) => {\n // Check to see if this file has already been loaded. If so just go straight to the func.\n if (callExternalFunction.currentlyloaded == jsFile) {\n func();\n return;\n }\n\n // PayPal can only work with one currency at the same time. We have to unload the previously loaded script\n // if it was loaded for a different currency. Weird way indeed, but the only way.\n // See: https://github.com/paypal/paypal-checkout-components/issues/1180\n if (callExternalFunction.currentlyloaded) {\n const suspectedScript = document.querySelector(`script[src=\"${callExternalFunction.currentlyloaded}\"]`);\n if (suspectedScript) {\n suspectedScript.parentNode.removeChild(suspectedScript);\n }\n }\n\n const script = document.createElement('script');\n\n if (script.readyState) {\n script.onreadystatechange = function() {\n if (this.readyState == 'complete' || this.readyState == 'loaded') {\n this.onreadystatechange = null;\n func();\n }\n };\n } else {\n script.onload = function() {\n func();\n };\n }\n\n script.setAttribute('src', jsFile);\n document.head.appendChild(script);\n\n callExternalFunction.currentlyloaded = jsFile;\n};\n\n/**\n * Holds the full url of loaded external JavaScript file.\n *\n * @static\n * @type {string}\n */\ncallExternalFunction.currentlyloaded = '';\n"],"file":"gateways_modal.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/gateways_modal.js"],"names":["showModalWithPlaceholder","ModalFactory","Templates","render","body","create","modal","show","process","component","paymentArea","itemId","description","Promise","all","Repository","getConfigForJs","then","paypalConfig","getRoot","on","ModalEvents","hidden","destroy","switchSdk","clientid","currency","setBody","resolve","window","paypal","Buttons","createOrder","data","actions","order","purchase_units","amount","currency_code","value","cost","Truncate","truncate","length","stripTags","application_context","shipping_preference","brand_name","brandname","onApprove","outsideClick","e","preventDefault","markTransactionComplete","orderID","res","hide","getBody","success","message","reject","clientId","sdkUrl","currentlyloaded","suspectedScript","document","querySelector","parentNode","removeChild","script","createElement","readyState","onreadystatechange","onload","setAttribute","head","appendChild"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * This module is responsible for PayPal content in the gateways modal.\n *\n * @module paygw_paypal/gateway_modal\n * @copyright 2020 Shamim Rezaie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport * as Repository from './repository';\nimport Templates from 'core/templates';\nimport Truncate from 'core/truncate';\nimport ModalFactory from 'core/modal_factory';\nimport ModalEvents from 'core/modal_events';\nimport {get_string as getString} from 'core/str';\n\n/**\n * Creates and shows a modal that contains a placeholder.\n *\n * @returns {Promise}\n */\nconst showModalWithPlaceholder = async() => {\n const modal = await ModalFactory.create({\n body: await Templates.render('paygw_paypal/paypal_button_placeholder', {})\n });\n modal.show();\n return modal;\n};\n\n/**\n * Process the payment.\n *\n * @param {string} component Name of the component that the itemId belongs to\n * @param {string} paymentArea The area of the component that the itemId belongs to\n * @param {number} itemId An internal identifier that is used by the component\n * @param {string} description Description of the payment\n * @returns {Promise}\n */\nexport const process = (component, paymentArea, itemId, description) => {\n return Promise.all([\n showModalWithPlaceholder(),\n Repository.getConfigForJs(component, paymentArea, itemId),\n ])\n .then(([modal, paypalConfig]) => {\n modal.getRoot().on(ModalEvents.hidden, () => {\n // Destroy when hidden.\n modal.destroy();\n });\n\n return Promise.all([\n modal,\n paypalConfig,\n switchSdk(paypalConfig.clientid, paypalConfig.currency),\n ]);\n })\n .then(([modal, paypalConfig]) => {\n // We have to clear the body. The render method in paypal.Buttons will render everything.\n modal.setBody('');\n\n return new Promise(resolve => {\n window.paypal.Buttons({\n // Set up the transaction.\n createOrder: function(data, actions) {\n return actions.order.create({\n purchase_units: [{ // eslint-disable-line\n amount: {\n currency_code: paypalConfig.currency_code, // eslint-disable-line\n value: paypalConfig.cost,\n },\n description: Truncate.truncate(description, {length: 127, stripTags: true}),\n }],\n application_context: { // eslint-disable-line\n shipping_preference: 'NO_SHIPPING', // eslint-disable-line\n brand_name: Truncate.truncate(paypalConfig.brandname, {length: 127, stripTags: true}), // eslint-disable-line\n },\n });\n },\n // Finalise the transaction.\n onApprove: function(data) {\n modal.getRoot().on(ModalEvents.outsideClick, (e) => {\n // Prevent closing the modal when clicking outside of it.\n e.preventDefault();\n });\n\n modal.setBody(getString('authorising', 'paygw_paypal'));\n\n Repository.markTransactionComplete(component, paymentArea, itemId, data.orderID)\n .then(res => {\n modal.hide();\n return res;\n })\n .then(resolve);\n }\n }).render(modal.getBody()[0]);\n });\n })\n .then(res => {\n if (res.success) {\n return Promise.resolve(res.message);\n }\n\n return Promise.reject(res.message);\n });\n};\n\n/**\n * Unloads the previously loaded PayPal JavaScript SDK, and loads a new one.\n *\n * @param {string} clientId PayPal client ID\n * @param {string} currency The currency\n * @returns {Promise}\n */\nconst switchSdk = (clientId, currency) => {\n const sdkUrl = `https://www.paypal.com/sdk/js?client-id=${clientId}¤cy=${currency}`;\n\n // Check to see if this file has already been loaded. If so just go straight to the func.\n if (switchSdk.currentlyloaded === sdkUrl) {\n return Promise.resolve();\n }\n\n // PayPal can only work with one currency at the same time. We have to unload the previously loaded script\n // if it was loaded for a different currency. Weird way indeed, but the only way.\n // See: https://github.com/paypal/paypal-checkout-components/issues/1180\n if (switchSdk.currentlyloaded) {\n const suspectedScript = document.querySelector(`script[src=\"${switchSdk.currentlyloaded}\"]`);\n if (suspectedScript) {\n suspectedScript.parentNode.removeChild(suspectedScript);\n }\n }\n\n const script = document.createElement('script');\n\n return new Promise(resolve => {\n if (script.readyState) {\n script.onreadystatechange = function() {\n if (this.readyState == 'complete' || this.readyState == 'loaded') {\n this.onreadystatechange = null;\n resolve();\n }\n };\n } else {\n script.onload = function() {\n resolve();\n };\n }\n\n script.setAttribute('src', sdkUrl);\n document.head.appendChild(script);\n\n switchSdk.currentlyloaded = sdkUrl;\n });\n};\n\n/**\n * Holds the full url of loaded PayPal JavaScript SDK.\n *\n * @static\n * @type {string}\n */\nswitchSdk.currentlyloaded = '';\n"],"file":"gateways_modal.min.js"} \ No newline at end of file diff --git a/payment/gateway/paypal/amd/build/repository.min.js b/payment/gateway/paypal/amd/build/repository.min.js index 83d0ead3479..72cdeb92862 100644 --- a/payment/gateway/paypal/amd/build/repository.min.js +++ b/payment/gateway/paypal/amd/build/repository.min.js @@ -1,2 +1,2 @@ -define ("paygw_paypal/repository",["exports","core/ajax"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.getConfigForJs=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);var c=function(a,c,d){return b.default.call([{methodname:"paygw_paypal_get_config_for_js",args:{component:a,paymentarea:c,itemid:d}}])[0]};a.getConfigForJs=c}); +define ("paygw_paypal/repository",["exports","core/ajax"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.markTransactionComplete=a.getConfigForJs=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);var c=function(a,c,d){return b.default.call([{methodname:"paygw_paypal_get_config_for_js",args:{component:a,paymentarea:c,itemid:d}}])[0]};a.getConfigForJs=c;var d=function(a,c,d,e){return b.default.call([{methodname:"paygw_paypal_create_transaction_complete",args:{component:a,paymentarea:c,itemid:d,orderid:e}}])[0]};a.markTransactionComplete=d}); //# sourceMappingURL=repository.min.js.map 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 --- a/payment/gateway/paypal/amd/build/repository.min.js.map +++ b/payment/gateway/paypal/amd/build/repository.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/repository.js"],"names":["getConfigForJs","component","paymentArea","itemId","Ajax","call","methodname","args","paymentarea","itemid"],"mappings":"8JAwBA,uDAUO,GAAMA,CAAAA,CAAc,CAAG,SAACC,CAAD,CAAYC,CAAZ,CAAyBC,CAAzB,CAAoC,CAU9D,MAAOC,WAAKC,IAAL,CAAU,CATD,CACZC,UAAU,CAAE,gCADA,CAEZC,IAAI,CAAE,CACFN,SAAS,CAATA,CADE,CAEFO,WAAW,CAAEN,CAFX,CAGFO,MAAM,CAAEN,CAHN,CAFM,CASC,CAAV,EAAqB,CAArB,CACV,CAXM,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * PayPal repository module to encapsulate all of the AJAX requests that can be sent for PayPal.\n *\n * @module paygw_paypal/repository\n * @package paygw_paypal\n * @copyright 2020 Shamim Rezaie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\n\n/**\n * Return the PayPal JavaScript SDK URL.\n *\n * @param {string} component Name of the component that the itemId belongs to\n * @param {string} paymentArea The area of the component that the itemId belongs to\n * @param {number} itemId An internal identifier that is used by the component\n * @returns {Promise<{clientid: string, brandname: string}>}\n */\nexport const getConfigForJs = (component, paymentArea, itemId) => {\n const request = {\n methodname: 'paygw_paypal_get_config_for_js',\n args: {\n component,\n paymentarea: paymentArea,\n itemid: itemId,\n },\n };\n\n return Ajax.call([request])[0];\n};\n"],"file":"repository.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/repository.js"],"names":["getConfigForJs","component","paymentArea","itemId","Ajax","call","methodname","args","paymentarea","itemid","markTransactionComplete","orderId","orderid"],"mappings":"wLAwBA,uDAUO,GAAMA,CAAAA,CAAc,CAAG,SAACC,CAAD,CAAYC,CAAZ,CAAyBC,CAAzB,CAAoC,CAU9D,MAAOC,WAAKC,IAAL,CAAU,CATD,CACZC,UAAU,CAAE,gCADA,CAEZC,IAAI,CAAE,CACFN,SAAS,CAATA,CADE,CAEFO,WAAW,CAAEN,CAFX,CAGFO,MAAM,CAAEN,CAHN,CAFM,CASC,CAAV,EAAqB,CAArB,CACV,CAXM,C,mBAsBA,GAAMO,CAAAA,CAAuB,CAAG,SAACT,CAAD,CAAYC,CAAZ,CAAyBC,CAAzB,CAAiCQ,CAAjC,CAA6C,CAWhF,MAAOP,WAAKC,IAAL,CAAU,CAVD,CACZC,UAAU,CAAE,0CADA,CAEZC,IAAI,CAAE,CACFN,SAAS,CAATA,CADE,CAEFO,WAAW,CAAEN,CAFX,CAGFO,MAAM,CAAEN,CAHN,CAIFS,OAAO,CAAED,CAJP,CAFM,CAUC,CAAV,EAAqB,CAArB,CACV,CAZM,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * PayPal repository module to encapsulate all of the AJAX requests that can be sent for PayPal.\n *\n * @module paygw_paypal/repository\n * @package paygw_paypal\n * @copyright 2020 Shamim Rezaie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\n\n/**\n * Return the PayPal JavaScript SDK URL.\n *\n * @param {string} component Name of the component that the itemId belongs to\n * @param {string} paymentArea The area of the component that the itemId belongs to\n * @param {number} itemId An internal identifier that is used by the component\n * @returns {Promise<{clientid: string, brandname: string, cost: number, currency: string}>}\n */\nexport const getConfigForJs = (component, paymentArea, itemId) => {\n const request = {\n methodname: 'paygw_paypal_get_config_for_js',\n args: {\n component,\n paymentarea: paymentArea,\n itemid: itemId,\n },\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Call server to validate and capture payment for order.\n *\n * @param {string} component Name of the component that the itemId belongs to\n * @param {string} paymentArea The area of the component that the itemId belongs to\n * @param {number} itemId An internal identifier that is used by the component\n * @param {string} orderId The order id coming back from PayPal\n * @returns {*}\n */\nexport const markTransactionComplete = (component, paymentArea, itemId, orderId) => {\n const request = {\n methodname: 'paygw_paypal_create_transaction_complete',\n args: {\n component,\n paymentarea: paymentArea,\n itemid: itemId,\n orderid: orderId,\n },\n };\n\n return Ajax.call([request])[0];\n};\n"],"file":"repository.min.js"} \ No newline at end of file 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. + ]) + .then(([modal, paypalConfig]) => { + modal.getRoot().on(ModalEvents.hidden, () => { + // Destroy when hidden. + modal.destroy(); + }); - 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(''); - 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 + 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(); - }); + }); + }, + // 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')); + 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]); + 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]; +};