From 71ccaf200d6047e1b9f7ffb8b1f0925a7e0cc904 Mon Sep 17 00:00:00 2001 From: Marina Glancy Date: Wed, 7 Oct 2020 19:53:01 +1100 Subject: [PATCH] MDL-69166 core_payment: rename some functions --- admin/settings/payment.php | 22 ++++++++++- lib/db/services.php | 6 +-- payment/amd/build/gateways_modal.min.js | 2 +- payment/amd/build/gateways_modal.min.js.map | 2 +- payment/amd/build/repository.min.js | 2 +- payment/amd/build/repository.min.js.map | 2 +- payment/amd/src/gateways_modal.js | 4 +- payment/amd/src/repository.js | 4 +- ...urrency.php => get_available_gateways.php} | 6 +-- payment/classes/helper.php | 38 ++++++++++--------- .../classes/external/get_config_for_js.php | 2 +- .../classes/external/transaction_complete.php | 2 +- payment/tests/accounts_test.php | 16 ++++++++ 13 files changed, 74 insertions(+), 34 deletions(-) rename payment/classes/external/{get_gateways_for_currency.php => get_available_gateways.php} (92%) diff --git a/admin/settings/payment.php b/admin/settings/payment.php index d8d8dd5700764..ab4fe79fc7998 100644 --- a/admin/settings/payment.php +++ b/admin/settings/payment.php @@ -1,6 +1,26 @@ . -// This file defines settingpages and externalpages under the "Payment" category +/** + * Adds payments links to the admin tree + * + * @package core + * @copyright 2020 Marina Glancy + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ $ADMIN->add('payment', new admin_externalpage( 'paymentaccounts', diff --git a/lib/db/services.php b/lib/db/services.php index b8152e313899c..ef653f097c395 100644 --- a/lib/db/services.php +++ b/lib/db/services.php @@ -2720,10 +2720,10 @@ 'ajax' => 'true', 'capabilities' => '', ], - 'core_payment_get_gateways_for_currency' => [ - 'classname' => 'core_payment\external\get_gateways_for_currency', + 'core_payment_get_available_gateways' => [ + 'classname' => 'core_payment\external\get_available_gateways', 'methodname' => 'execute', - 'description' => 'Get the list of payment gateways that support the given currency', + 'description' => 'Get the list of payment gateways that support the given component/area', 'type' => 'read', 'ajax' => true, ], diff --git a/payment/amd/build/gateways_modal.min.js b/payment/amd/build/gateways_modal.min.js index ddfcd0e870d44..8bd0a55dc0c09 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.registerEventListeners=a.registerEventListenersBySelector=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)})}}a.registerEventListenersBySelector=function registerEventListenersBySelector(a){document.querySelectorAll(a).forEach(function(a){p(a)})};var p=function(a){a.addEventListener("click",function(b){b.preventDefault();q(a,{focusOnClose:b.target})})};a.registerEventListeners=p;var 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 {getGatewaysSupportingCurrency} 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 *\n * @param {string} nodeSelector The root to listen to.\n */\nexport const registerEventListenersBySelector = (nodeSelector) => {\n document.querySelectorAll(nodeSelector).forEach((element) => {\n registerEventListeners(element);\n });\n};\n\n/**\n * Register event listeners for the module.\n *\n * @param {HTMLElement} rootNode The root to listen to.\n */\nexport const registerEventListeners = (rootNode) => {\n rootNode.addEventListener('click', (e) => {\n e.preventDefault();\n show(rootNode, {focusOnClose: e.target});\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.componentid,\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 getGatewaysSupportingCurrency(rootNode.dataset.component, rootNode.dataset.paymentarea,\n rootNode.dataset.componentid);\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 componentId belongs to\n * @param {string} paymentArea Name of the area in the component that the componentId belongs to\n * @param {number} componentId 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, componentId, description, callback) => {\n const paymentMethod = await import(`pg_${gateway}/gateways_modal`);\n paymentMethod.process(component, paymentArea, componentId, 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"],"file":"gateways_modal.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/gateways_modal.js"],"names":["registerEventListenersBySelector","nodeSelector","document","querySelectorAll","forEach","element","registerEventListeners","rootNode","addEventListener","e","preventDefault","show","focusOnClose","target","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","componentid","description","success","message","hide","Notification","addNotification","location","reload","alert","then","matches","elements","gateways","updateCostRegion","cost","context","renderForPromise","html","js","replaceNodeContents","regions","gatewaysContainer","selectSingleGateway","root","length","checked","defaultCost","gatewayElement","surcharge","parseInt","fee","costContainer","paymentArea","componentId","callback","paymentMethod","process"],"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 *\n * @param {string} nodeSelector The root to listen to.\n */\nexport const registerEventListenersBySelector = (nodeSelector) => {\n document.querySelectorAll(nodeSelector).forEach((element) => {\n registerEventListeners(element);\n });\n};\n\n/**\n * Register event listeners for the module.\n *\n * @param {HTMLElement} rootNode The root to listen to.\n */\nexport const registerEventListeners = (rootNode) => {\n rootNode.addEventListener('click', (e) => {\n e.preventDefault();\n show(rootNode, {focusOnClose: e.target});\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.componentid,\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,\n rootNode.dataset.componentid);\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 componentId belongs to\n * @param {string} paymentArea Name of the area in the component that the componentId belongs to\n * @param {number} componentId 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, componentId, description, callback) => {\n const paymentMethod = await import(`pg_${gateway}/gateways_modal`);\n paymentMethod.process(component, paymentArea, componentId, 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"],"file":"gateways_modal.min.js"} \ No newline at end of file diff --git a/payment/amd/build/repository.min.js b/payment/amd/build/repository.min.js index 7e70376fa00c3..750638661afa3 100644 --- a/payment/amd/build/repository.min.js +++ b/payment/amd/build/repository.min.js @@ -1,2 +1,2 @@ -define ("core_payment/repository",["exports","core/ajax"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.getGatewaysSupportingCurrency=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);var c=function(a,c,d){return b.default.call([{methodname:"core_payment_get_gateways_for_currency",args:{component:a,paymentarea:c,componentid:d}}])[0]};a.getGatewaysSupportingCurrency=c}); +define ("core_payment/repository",["exports","core/ajax"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.getAvailableGateways=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);var c=function(a,c,d){return b.default.call([{methodname:"core_payment_get_available_gateways",args:{component:a,paymentarea:c,componentid:d}}])[0]};a.getAvailableGateways=c}); //# sourceMappingURL=repository.min.js.map diff --git a/payment/amd/build/repository.min.js.map b/payment/amd/build/repository.min.js.map index b764cebfb3ff8..e36de5baa336e 100644 --- a/payment/amd/build/repository.min.js.map +++ b/payment/amd/build/repository.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/repository.js"],"names":["getGatewaysSupportingCurrency","component","paymentArea","componentId","Ajax","call","methodname","args","paymentarea","componentid"],"mappings":"6KAwBA,uDAUO,GAAMA,CAAAA,CAA6B,CAAG,SAACC,CAAD,CAAYC,CAAZ,CAAyBC,CAAzB,CAAyC,CASlF,MAAOC,WAAKC,IAAL,CAAU,CARD,CACZC,UAAU,CAAE,wCADA,CAEZC,IAAI,CAAE,CACFN,SAAS,CAATA,CADE,CAEFO,WAAW,CAAEN,CAFX,CAGFO,WAAW,CAAEN,CAHX,CAFM,CAQC,CAAV,EAAqB,CAArB,CACV,CAVM,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 * Repository for payment subsystem.\n *\n * @module core_payment/repository\n * @package core_payment\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 * Returns the list of gateways that can process payments in the given currency.\n *\n * @param {string} component\n * @param {string} paymentArea\n * @param {number} componentId\n * @returns {Promise<{shortname: string, name: string, description: String}[]>}\n */\nexport const getGatewaysSupportingCurrency = (component, paymentArea, componentId) => {\n const request = {\n methodname: 'core_payment_get_gateways_for_currency',\n args: {\n component,\n paymentarea: paymentArea,\n componentid: componentId,\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":["getAvailableGateways","component","paymentArea","componentId","Ajax","call","methodname","args","paymentarea","componentid"],"mappings":"oKAwBA,uDAUO,GAAMA,CAAAA,CAAoB,CAAG,SAACC,CAAD,CAAYC,CAAZ,CAAyBC,CAAzB,CAAyC,CASzE,MAAOC,WAAKC,IAAL,CAAU,CARD,CACZC,UAAU,CAAE,qCADA,CAEZC,IAAI,CAAE,CACFN,SAAS,CAATA,CADE,CAEFO,WAAW,CAAEN,CAFX,CAGFO,WAAW,CAAEN,CAHX,CAFM,CAQC,CAAV,EAAqB,CAArB,CACV,CAVM,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 * Repository for payment subsystem.\n *\n * @module core_payment/repository\n * @package core_payment\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 * Returns the list of gateways that can process payments in the given currency.\n *\n * @param {string} component\n * @param {string} paymentArea\n * @param {number} componentId\n * @returns {Promise<{shortname: string, name: string, description: String}[]>}\n */\nexport const getAvailableGateways = (component, paymentArea, componentId) => {\n const request = {\n methodname: 'core_payment_get_available_gateways',\n args: {\n component,\n paymentarea: paymentArea,\n componentid: componentId,\n }\n };\n return Ajax.call([request])[0];\n};\n"],"file":"repository.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 1dacc63f4ec43..ad6484508fb04 100644 --- a/payment/amd/src/gateways_modal.js +++ b/payment/amd/src/gateways_modal.js @@ -25,7 +25,7 @@ import ModalFactory from 'core/modal_factory'; import Templates from 'core/templates'; import {get_string as getString} from 'core/str'; -import {getGatewaysSupportingCurrency} from './repository'; +import {getAvailableGateways} from './repository'; import Selectors from './selectors'; import ModalEvents from 'core/modal_events'; import PaymentEvents from 'core_payment/events'; @@ -127,7 +127,7 @@ const show = async(rootNode, { } }); - const gateways = await getGatewaysSupportingCurrency(rootNode.dataset.component, rootNode.dataset.paymentarea, + const gateways = await getAvailableGateways(rootNode.dataset.component, rootNode.dataset.paymentarea, rootNode.dataset.componentid); const context = { gateways diff --git a/payment/amd/src/repository.js b/payment/amd/src/repository.js index d81c79af2e72c..7b4ccbbff4e52 100644 --- a/payment/amd/src/repository.js +++ b/payment/amd/src/repository.js @@ -32,9 +32,9 @@ import Ajax from 'core/ajax'; * @param {number} componentId * @returns {Promise<{shortname: string, name: string, description: String}[]>} */ -export const getGatewaysSupportingCurrency = (component, paymentArea, componentId) => { +export const getAvailableGateways = (component, paymentArea, componentId) => { const request = { - methodname: 'core_payment_get_gateways_for_currency', + methodname: 'core_payment_get_available_gateways', args: { component, paymentarea: paymentArea, diff --git a/payment/classes/external/get_gateways_for_currency.php b/payment/classes/external/get_available_gateways.php similarity index 92% rename from payment/classes/external/get_gateways_for_currency.php rename to payment/classes/external/get_available_gateways.php index 6a3c822e60e64..9e3d432095a84 100644 --- a/payment/classes/external/get_gateways_for_currency.php +++ b/payment/classes/external/get_available_gateways.php @@ -35,7 +35,7 @@ require_once($CFG->libdir . '/externallib.php'); -class get_gateways_for_currency extends external_api { +class get_available_gateways extends external_api { /** * Returns description of method parameters. @@ -67,7 +67,7 @@ public static function execute(string $component, string $paymentarea, int $comp ]); $list = []; - $gateways = helper::get_gateways_for_currency($params['component'], $params['paymentarea'], $params['componentid']); + $gateways = helper::get_available_gateways($params['component'], $params['paymentarea'], $params['componentid']); [ 'amount' => $amount, 'currency' => $currency @@ -80,7 +80,7 @@ public static function execute(string $component, string $paymentarea, int $comp 'name' => get_string('gatewayname', 'pg_' . $gateway), 'description' => get_string('gatewaydescription', 'pg_' . $gateway), 'surcharge' => $surcharge, - 'cost' => helper::get_cost_as_string(helper::get_cost_with_surcharge($amount, $surcharge, $currency), $currency), + 'cost' => helper::get_cost_as_string($amount, $currency, $surcharge), ]; } diff --git a/payment/classes/helper.php b/payment/classes/helper.php index fe12f8044561a..73de0ccdcca41 100644 --- a/payment/classes/helper.php +++ b/payment/classes/helper.php @@ -67,7 +67,7 @@ public static function get_supported_currencies(): array { * @param int $componentid * @return string[] */ - public static function get_gateways_for_currency(string $component, string $paymentarea, int $componentid): array { + public static function get_available_gateways(string $component, string $paymentarea, int $componentid): array { $gateways = []; [ @@ -97,33 +97,37 @@ public static function get_gateways_for_currency(string $component, string $paym } /** - * Calculates the cost with the surcharge + * Rounds the cost based on the currency fractional digits, can also apply surcharge * * @param float $amount amount in the currency units - * @param float $surcharge surcharge in percents * @param string $currency currency, used for calculating the number of fractional digits + * @param float $surcharge surcharge in percents * @return float */ - public static function get_cost_with_surcharge(float $amount, float $surcharge, string $currency): float { - return round($amount + $amount * $surcharge / 100, 2); // TODO number of digits depends on currency. + public static function get_rounded_cost(float $amount, string $currency, float $surcharge = 0): float { + $amount = $amount * (100 + $surcharge) / 100; + + $locale = get_string('localecldr', 'langconfig'); + $fmt = \NumberFormatter::create($locale, \NumberFormatter::CURRENCY); + $localisedcost = numfmt_format_currency($fmt, $amount, $currency); + + return numfmt_parse_currency($fmt, $localisedcost, $currency); } /** - * Returns human-readable amount with fixed number of fractional digits and currency indicator + * Returns human-readable amount with correct number of fractional digits and currency indicator, can also apply surcharge * - * @param float $amount - * @param string $currency + * @param float $amount amount in the currency units + * @param string $currency The currency + * @param float $surcharge surcharge in percents * @return string - * @throws \coding_exception */ - public static function get_cost_as_string(float $amount, string $currency): string { - if (class_exists('NumberFormatter') && function_exists('numfmt_format_currency')) { - $locale = get_string('localecldr', 'langconfig'); - $fmt = \NumberFormatter::create($locale, \NumberFormatter::CURRENCY); - $localisedcost = numfmt_format_currency($fmt, $amount, $currency); - } else { - $localisedcost = sprintf("%.2f %s", $amount, $currency); // TODO number of digits depends on currency. - } + public static function get_cost_as_string(float $amount, string $currency, float $surcharge = 0): string { + $amount = $amount * (100 + $surcharge) / 100; + + $locale = get_string('localecldr', 'langconfig'); + $fmt = \NumberFormatter::create($locale, \NumberFormatter::CURRENCY); + $localisedcost = numfmt_format_currency($fmt, $amount, $currency); return $localisedcost; } diff --git a/payment/gateway/paypal/classes/external/get_config_for_js.php b/payment/gateway/paypal/classes/external/get_config_for_js.php index a467f9204c3f9..bf6ccc69f8027 100644 --- a/payment/gateway/paypal/classes/external/get_config_for_js.php +++ b/payment/gateway/paypal/classes/external/get_config_for_js.php @@ -73,7 +73,7 @@ public static function execute(string $component, string $paymentarea, int $comp return [ 'clientid' => $config['clientid'], 'brandname' => $config['brandname'], - 'cost' => helper::get_cost_with_surcharge($cost['amount'], $surcharge, $cost['currency']), + 'cost' => helper::get_rounded_cost($cost['amount'], $cost['currency'], $surcharge), 'currency' => $cost['currency'], ]; } diff --git a/payment/gateway/paypal/classes/external/transaction_complete.php b/payment/gateway/paypal/classes/external/transaction_complete.php index 9186b6f41a60c..2a895283cc87e 100644 --- a/payment/gateway/paypal/classes/external/transaction_complete.php +++ b/payment/gateway/paypal/classes/external/transaction_complete.php @@ -84,7 +84,7 @@ public static function execute(string $component, string $paymentarea, int $comp // Add surcharge if there is any. $surcharge = helper::get_gateway_surcharge('paypal'); - $amount = helper::get_cost_with_surcharge($amount, $surcharge, $currency); + $amount = helper::get_rounded_cost($amount, $currency, $surcharge); $paypalhelper = new paypal_helper($config->clientid, $config->secret, $sandbox); $orderdetails = $paypalhelper->get_order_details($orderid); diff --git a/payment/tests/accounts_test.php b/payment/tests/accounts_test.php index d273441fe5561..d023798b206a0 100644 --- a/payment/tests/accounts_test.php +++ b/payment/tests/accounts_test.php @@ -127,4 +127,20 @@ public function test_archive_restore_account() { helper::restore_payment_account(account::get_record(['id' => $account->get('id')])); $this->assertEquals(0, $DB->get_field('payment_accounts', 'archived', ['id' => $account->get('id')])); } + + public function test_format_cost() { + $amount = 5.345; + + $currency = 'IRR'; + $this->assertEquals(5, helper::get_rounded_cost($amount, $currency)); + $this->assertEquals('IRR'."\xc2\xa0".'5', helper::get_cost_as_string($amount, $currency)); + $this->assertEquals(6, helper::get_rounded_cost($amount, $currency, 12)); + $this->assertEquals('IRR'."\xc2\xa0".'6', helper::get_cost_as_string($amount, $currency, 12)); + + $currency = 'USD'; + $this->assertEquals(5.34, helper::get_rounded_cost($amount, $currency)); + $this->assertEquals('USD'."\xc2\xa0".'5.34', helper::get_cost_as_string($amount, $currency)); + $this->assertEquals(5.40, helper::get_rounded_cost($amount, $currency, 1)); + $this->assertEquals('USD'."\xc2\xa0".'5.40', helper::get_cost_as_string($amount, $currency, 1)); + } }