diff --git a/enrol/fee/classes/payment/provider.php b/enrol/fee/classes/payment/provider.php
index 8dfd2279c30..2a6d43b7357 100644
--- a/enrol/fee/classes/payment/provider.php
+++ b/enrol/fee/classes/payment/provider.php
@@ -55,9 +55,10 @@ class provider implements \core_payment\local\callback\provider {
* Callback function that delivers what the user paid for to them.
*
* @param int $instanceid The enrolment instance id
+ * @param int $paymentid payment id as inserted into the 'payments' table, if needed for reference
* @return bool Whether successful or not
*/
- public static function deliver_order(int $instanceid): bool {
+ public static function deliver_order(int $instanceid, int $paymentid): bool {
global $DB, $USER;
$instance = $DB->get_record('enrol', ['enrol' => 'fee', 'id' => $instanceid], '*', MUST_EXIST);
diff --git a/enrol/fee/classes/plugin.php b/enrol/fee/classes/plugin.php
index e7777033a00..aa5ed3ce2fe 100644
--- a/enrol/fee/classes/plugin.php
+++ b/enrol/fee/classes/plugin.php
@@ -202,16 +202,9 @@ class enrol_fee_plugin extends enrol_plugin {
echo '
'.get_string('nocost', 'enrol_fee').'
';
} else {
- $locale = get_string('localecldr', 'langconfig');
- $fmt = NumberFormatter::create($locale, NumberFormatter::CURRENCY);
- $localisedcost = numfmt_format_currency($fmt, $cost, $instance->currency);
-
$data = [
'isguestuser' => isguestuser(),
- 'cost' => $localisedcost,
- 'currency' => $instance->currency,
- 'accountid' => $instance->customint1,
- 'amount' => $cost,
+ 'cost' => \core_payment\helper::get_cost_as_string($instance->cost, $instance->currency),
'instanceid' => $instance->id,
'description' => get_string('purchasedescription', 'enrol_fee',
format_string($course->fullname, true, ['context' => $context])),
diff --git a/enrol/fee/templates/payment_region.mustache b/enrol/fee/templates/payment_region.mustache
index 458e76d03a6..649ab5a410e 100644
--- a/enrol/fee/templates/payment_region.mustache
+++ b/enrol/fee/templates/payment_region.mustache
@@ -41,7 +41,6 @@
"cost": "$108.50",
"amount": 108.50,
"currency": "AUD",
- "accountid": 1,
"instanceid": 11,
"description": "Enrolment in course Introduction to algorithms",
"isguestuser": false
@@ -63,9 +62,6 @@
class="btn btn-secondary"
type="button"
id="gateways-modal-trigger-{{ uniqid }}"
- data-amount="{{amount}}"
- data-currency="{{currency}}"
- data-accountid="{{accountid}}"
data-component="enrol_fee"
data-componentid="{{instanceid}}"
data-description={{# quote }}{{description}}{{/ quote }}
diff --git a/payment/amd/build/gateways_modal.min.js b/payment/amd/build/gateways_modal.min.js
index ab28fe4d596..4f1e815c126 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,A,B=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 {\n value: parseFloat(rootNode.dataset.amount),\n currency: rootNode.dataset.currency,\n surcharge: parseInt((rootElement.querySelector(Selectors.values.gateway) || {dataset: {surcharge: 0}})\n .dataset.surcharge),\n },\n rootNode.dataset.component,\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, parseFloat(rootNode.dataset.amount), rootNode.dataset.currency);\n }\n });\n\n const currency = rootNode.dataset.currency;\n const accountid = rootNode.dataset.accountid;\n const gateways = await getGatewaysSupportingCurrency(currency, accountid);\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, parseFloat(rootNode.dataset.amount), rootNode.dataset.currency);\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 {number} amount The amount part of cost\n * @param {string} currency The currency part of cost in the 3-letter ISO-4217 format\n * @returns {Promise}\n */\nconst updateCostRegion = async(root, amount, currency) => {\n const locale = await updateCostRegion.locale; // This only takes a bit the first time.\n const surcharge = parseInt((root.querySelector(Selectors.values.gateway) || {dataset: {surcharge: 0}}).dataset.surcharge);\n amount += amount * surcharge / 100;\n const localisedCost = amount.toLocaleString(locale, {style: \"currency\", currency: currency});\n\n const {html, js} = await Templates.renderForPromise('core_payment/fee_breakdown', {fee: localisedCost, surcharge});\n Templates.replaceNodeContents(root.querySelector(Selectors.regions.costContainer), html, js);\n};\nupdateCostRegion.locale = getString(\"localecldr\", \"langconfig\");\n\n/**\n * Process payment using the selected gateway.\n *\n * @param {string} gateway The gateway to be used for payment\n * @param {Object} amount - Amount of payment\n * @param {number} amount.value The numerical part of the amount\n * @param {string} amount.currency The currency part of the amount in the three-character ISO-4217 format\n * @param {number} amount.surcharge The surcharge percentage that should be added to the amount\n * @param {string} component Name of 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, {value, currency, surcharge = 0}, component, componentid, description, callback) => {\n const paymentMethod = await import(`pg_${gateway}/gateways_modal`);\n\n value += value * surcharge / 100;\n paymentMethod.process(value, currency, component, 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","componentid","description","success","message","hide","Notification","addNotification","location","reload","alert","then","matches","elements","gateways","updateCostRegion","context","renderForPromise","html","js","replaceNodeContents","regions","gatewaysContainer","selectSingleGateway","root","length","checked","surcharge","parseInt","cost","fee","costContainer","locale","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 {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.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);\n }\n });\n\n const gateways = await getGatewaysSupportingCurrency(rootNode.dataset.component, 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);\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 {number} amount The amount part of cost\n * @param {string} currency The currency part of cost in the 3-letter ISO-4217 format\n * @returns {Promise}\n */\nconst updateCostRegion = async(root) => {\n const surcharge = parseInt((root.querySelector(Selectors.values.gateway) || {dataset: {surcharge: 0}}).dataset.surcharge);\n const cost = root.querySelector(Selectors.values.gateway).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};\nupdateCostRegion.locale = getString(\"localecldr\", \"langconfig\");\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 {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, componentid, description, callback) => {\n const paymentMethod = await import(`pg_${gateway}/gateways_modal`);\n paymentMethod.process(component, 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 f65181de8fb..707571d6aa6 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){return b.default.call([{methodname:"core_payment_get_gateways_for_currency",args:{currency:a,accountid:c}}])[0]};a.getGatewaysSupportingCurrency=c});
+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){return b.default.call([{methodname:"core_payment_get_gateways_for_currency",args:{component:a,componentid:c}}])[0]};a.getGatewaysSupportingCurrency=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 a5b47c4af44..99951074d96 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","currency","accountid","Ajax","call","methodname","args"],"mappings":"6KAwBA,uDASO,GAAMA,CAAAA,CAA6B,CAAG,SAACC,CAAD,CAAWC,CAAX,CAAyB,CAQlE,MAAOC,WAAKC,IAAL,CAAU,CAPD,CACZC,UAAU,CAAE,wCADA,CAEZC,IAAI,CAAE,CACFL,QAAQ,CAARA,CADE,CAEFC,SAAS,CAATA,CAFE,CAFM,CAOC,CAAV,EAAqB,CAArB,CACV,CATM,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} currency The currency in the three-character ISO-4217 format\n * @param {int} accountid\n * @returns {Promise<{shortname: string, name: string, description: String}[]>}\n */\nexport const getGatewaysSupportingCurrency = (currency, accountid) => {\n const request = {\n methodname: 'core_payment_get_gateways_for_currency',\n args: {\n currency,\n accountid\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":["getGatewaysSupportingCurrency","component","componentid","Ajax","call","methodname","args"],"mappings":"6KAwBA,uDASO,GAAMA,CAAAA,CAA6B,CAAG,SAACC,CAAD,CAAYC,CAAZ,CAA4B,CAQrE,MAAOC,WAAKC,IAAL,CAAU,CAPD,CACZC,UAAU,CAAE,wCADA,CAEZC,IAAI,CAAE,CACFL,SAAS,CAATA,CADE,CAEFC,WAAW,CAAXA,CAFE,CAFM,CAOC,CAAV,EAAqB,CAArB,CACV,CATM,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 {Integer} componentid\n * @returns {Promise<{shortname: string, name: string, description: String}[]>}\n */\nexport const getGatewaysSupportingCurrency = (component, componentid) => {\n const request = {\n methodname: 'core_payment_get_gateways_for_currency',\n args: {\n component,\n 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 6112b2a9fcb..643371a4f36 100644
--- a/payment/amd/src/gateways_modal.js
+++ b/payment/amd/src/gateways_modal.js
@@ -93,12 +93,6 @@ const show = async(rootNode, {
if (gateway) {
processPayment(
gateway,
- {
- value: parseFloat(rootNode.dataset.amount),
- currency: rootNode.dataset.currency,
- surcharge: parseInt((rootElement.querySelector(Selectors.values.gateway) || {dataset: {surcharge: 0}})
- .dataset.surcharge),
- },
rootNode.dataset.component,
rootNode.dataset.componentid,
rootNode.dataset.description,
@@ -128,13 +122,11 @@ const show = async(rootNode, {
// Re-calculate the cost when gateway is changed.
rootElement.addEventListener('change', e => {
if (e.target.matches(Selectors.elements.gateways)) {
- updateCostRegion(rootElement, parseFloat(rootNode.dataset.amount), rootNode.dataset.currency);
+ updateCostRegion(rootElement);
}
});
- const currency = rootNode.dataset.currency;
- const accountid = rootNode.dataset.accountid;
- const gateways = await getGatewaysSupportingCurrency(currency, accountid);
+ const gateways = await getGatewaysSupportingCurrency(rootNode.dataset.component, rootNode.dataset.componentid);
const context = {
gateways
};
@@ -142,7 +134,7 @@ const show = async(rootNode, {
const {html, js} = await Templates.renderForPromise('core_payment/gateways', context);
Templates.replaceNodeContents(rootElement.querySelector(Selectors.regions.gatewaysContainer), html, js);
selectSingleGateway(rootElement);
- await updateCostRegion(rootElement, parseFloat(rootNode.dataset.amount), rootNode.dataset.currency);
+ await updateCostRegion(rootElement);
};
/**
@@ -166,13 +158,11 @@ const selectSingleGateway = root => {
* @param {string} currency The currency part of cost in the 3-letter ISO-4217 format
* @returns {Promise}
*/
-const updateCostRegion = async(root, amount, currency) => {
- const locale = await updateCostRegion.locale; // This only takes a bit the first time.
+const updateCostRegion = async(root) => {
const surcharge = parseInt((root.querySelector(Selectors.values.gateway) || {dataset: {surcharge: 0}}).dataset.surcharge);
- amount += amount * surcharge / 100;
- const localisedCost = amount.toLocaleString(locale, {style: "currency", currency: currency});
+ const cost = root.querySelector(Selectors.values.gateway).dataset.cost;
- const {html, js} = await Templates.renderForPromise('core_payment/fee_breakdown', {fee: localisedCost, surcharge});
+ const {html, js} = await Templates.renderForPromise('core_payment/fee_breakdown', {fee: cost, surcharge});
Templates.replaceNodeContents(root.querySelector(Selectors.regions.costContainer), html, js);
};
updateCostRegion.locale = getString("localecldr", "langconfig");
@@ -181,21 +171,15 @@ updateCostRegion.locale = getString("localecldr", "langconfig");
* Process payment using the selected gateway.
*
* @param {string} gateway The gateway to be used for payment
- * @param {Object} amount - Amount of payment
- * @param {number} amount.value The numerical part of the amount
- * @param {string} amount.currency The currency part of the amount in the three-character ISO-4217 format
- * @param {number} amount.surcharge The surcharge percentage that should be added to the amount
* @param {string} component Name of the component that the componentid belongs to
* @param {number} componentid 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}
*/
-const processPayment = async(gateway, {value, currency, surcharge = 0}, component, componentid, description, callback) => {
+const processPayment = async(gateway, component, componentid, description, callback) => {
const paymentMethod = await import(`pg_${gateway}/gateways_modal`);
-
- value += value * surcharge / 100;
- paymentMethod.process(value, currency, component, componentid, description, callback);
+ paymentMethod.process(component, componentid, description, callback);
};
/**
diff --git a/payment/amd/src/repository.js b/payment/amd/src/repository.js
index 64a968a654f..5199c336a7c 100644
--- a/payment/amd/src/repository.js
+++ b/payment/amd/src/repository.js
@@ -27,16 +27,16 @@ import Ajax from 'core/ajax';
/**
* Returns the list of gateways that can process payments in the given currency.
*
- * @param {string} currency The currency in the three-character ISO-4217 format
- * @param {int} accountid
+ * @param {String} component
+ * @param {Integer} componentid
* @returns {Promise<{shortname: string, name: string, description: String}[]>}
*/
-export const getGatewaysSupportingCurrency = (currency, accountid) => {
+export const getGatewaysSupportingCurrency = (component, componentid) => {
const request = {
methodname: 'core_payment_get_gateways_for_currency',
args: {
- currency,
- accountid
+ component,
+ componentid
}
};
return Ajax.call([request])[0];
diff --git a/payment/classes/external/get_gateways_for_currency.php b/payment/classes/external/get_gateways_for_currency.php
index e17cd66e47a..30df0b990ac 100644
--- a/payment/classes/external/get_gateways_for_currency.php
+++ b/payment/classes/external/get_gateways_for_currency.php
@@ -24,6 +24,7 @@
namespace core_payment\external;
+use core_payment\helper;
use external_api;
use external_function_parameters;
use external_value;
@@ -43,34 +44,40 @@ class get_gateways_for_currency extends external_api {
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters(
- ['currency' => new external_value(PARAM_ALPHA, 'Currency code'),
- 'accountid' => new external_value(PARAM_INT, 'Account id')]
+ ['component' => new external_value(PARAM_COMPONENT, 'Component'),
+ 'componentid' => new external_value(PARAM_INT, 'Component id')]
);
}
/**
* Returns the list of gateways that can process payments in the given currency.
*
- * @param string $currency The currency in the three-character ISO-4217 format.
- * @param int $accountid
+ * @param string $component
+ * @param int $componentid
* @return \stdClass[]
*/
- public static function execute(string $currency, int $accountid): array {
+ public static function execute(string $component, int $componentid): array {
$params = external_api::validate_parameters(self::execute_parameters(), [
- 'currency' => $currency,
- 'accountid' => $accountid,
+ 'component' => $component,
+ 'componentid' => $componentid,
]);
$list = [];
- $gateways = \core_payment\helper::get_gateways_for_currency($params['currency'], $params['accountid']);
+ $gateways = \core_payment\helper::get_gateways_for_currency($params['component'], $params['componentid']);
+ [
+ 'amount' => $amount,
+ 'currency' => $currency
+ ] = \core_payment\helper::get_cost($params['component'], $params['componentid']);
foreach ($gateways as $gateway) {
+ $surcharge = \core_payment\helper::get_gateway_surcharge($gateway);
$list[] = (object)[
'shortname' => $gateway,
'name' => get_string('gatewayname', 'pg_' . $gateway),
'description' => get_string('gatewaydescription', 'pg_' . $gateway),
- 'surcharge' => \core_payment\helper::get_gateway_surcharge($gateway),
+ 'surcharge' => $surcharge,
+ 'cost' => helper::get_cost_as_string(helper::get_cost_with_surcharge($amount, $surcharge, $currency), $currency),
];
}
@@ -89,6 +96,8 @@ class get_gateways_for_currency extends external_api {
'name' => new external_value(PARAM_TEXT, 'Human readable name of the gateway'),
'description' => new external_value(PARAM_TEXT, 'description of the gateway'),
'surcharge' => new external_value(PARAM_INT, 'percentage of surcharge when using the gateway'),
+ 'cost' => new external_value(PARAM_TEXT,
+ 'Cost in human-readable form (amount plus surcharge with currency sign)'),
])
);
}
diff --git a/payment/classes/helper.php b/payment/classes/helper.php
index 5ba6921377f..9e211155cd6 100644
--- a/payment/classes/helper.php
+++ b/payment/classes/helper.php
@@ -47,7 +47,7 @@ class helper {
/** @var \pg_paypal\gateway $classname */
$classname = '\pg_' . $plugin . '\gateway';
- $currencies += $classname::get_supported_currencies();
+ $currencies += component_class_callback($classname, 'get_supported_currencies', [], []);
}
$currencies = array_unique($currencies);
@@ -58,13 +58,18 @@ class helper {
/**
* Returns the list of gateways that can process payments in the given currency.
*
- * @param string $currency The currency in the three-character ISO-4217 format.
- * @param int $accountid
+ * @param string $component
+ * @param int $componentid
* @return string[]
*/
- public static function get_gateways_for_currency(string $currency, int $accountid): array {
+ public static function get_gateways_for_currency(string $component, int $componentid): array {
$gateways = [];
+ [
+ 'amount' => $amount,
+ 'currency' => $currency,
+ 'accountid' => $accountid,
+ ] = self::get_cost($component, $componentid);
$account = new account($accountid);
if (!$account->get('id') || !$account->get('enabled')) {
return $gateways;
@@ -77,7 +82,7 @@ class helper {
/** @var gateway $classname */
$classname = '\pg_' . $plugin . '\gateway';
- $currencies = $classname::get_supported_currencies();
+ $currencies = component_class_callback($classname, 'get_supported_currencies', [], []);
if (in_array($currency, $currencies)) {
$gateways[] = $plugin;
}
@@ -86,33 +91,60 @@ class helper {
return $gateways;
}
+ /**
+ * Calculates the cost with the 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
+ * @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.
+ }
+
+ /**
+ * Returns human-readable amount with fixed number of fractional digits and currency indicator
+ *
+ * @param float $amount
+ * @param string $currency
+ * @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.
+ }
+
+ return $localisedcost;
+ }
+
/**
* Returns the percentage of surcharge that is applied when using a gateway
*
* @param string $gateway Name of the gateway
- * @return int
+ * @return float
*/
- public static function get_gateway_surcharge(string $gateway): int {
- return get_config('pg_' . $gateway, 'surcharge') ?: 0;
+ public static function get_gateway_surcharge(string $gateway): float {
+ return (float)get_config('pg_' . $gateway, 'surcharge');
}
/**
* Returns the attributes to place on a pay button.
*
- * @param float $amount Amount of payment
- * @param string $currency Currency of payment
* @param string $component Name of the component that the componentid belongs to
* @param int $componentid An internal identifier that is used by the component
* @param string $description Description of the payment
* @return array
*/
- public static function gateways_modal_link_params(float $amount, string $currency, string $component, int $componentid,
- string $description): array {
+ public static function gateways_modal_link_params(string $component, int $componentid, string $description): array {
return [
'id' => 'gateways-modal-trigger',
'role' => 'button',
- 'data-amount' => $amount,
- 'data-currency' => $currency,
'data-component' => $component,
'data-componentid' => $componentid,
'data-description' => $description,
@@ -163,13 +195,15 @@ class helper {
/**
* Delivers what the user paid for.
*
+ * @uses \core_payment\local\callback\provider::deliver_order()
+ *
* @param string $component Name of the component that the componentid belongs to
* @param int $componentid An internal identifier that is used by the component
+ * @param int $paymentid payment id as inserted into the 'payments' table, if needed for reference
* @return bool Whether successful or not
- * @throws \moodle_exception
*/
- public static function deliver_order(string $component, int $componentid): bool {
- $result = component_class_callback("$component\\payment\\provider", 'deliver_order', [$componentid]);
+ public static function deliver_order(string $component, int $componentid, int $paymentid): bool {
+ $result = component_class_callback("$component\\payment\\provider", 'deliver_order', [$componentid, $paymentid]);
if ($result === null) {
throw new \moodle_exception('callbacknotimplemented', 'core_payment', '', $component);
@@ -182,6 +216,7 @@ class helper {
* Stores essential information about the payment and returns the "id" field of the payment record in DB.
* Each payment gateway may then store the additional information their way.
*
+ * @param int $accountid Account id
* @param string $component Name of the component that the componentid belongs to
* @param int $componentid An internal identifier that is used by the component
* @param int $userid Id of the user who is paying
@@ -190,7 +225,7 @@ class helper {
* @param string $gateway The gateway that is used for the payment
* @return int
*/
- public static function save_payment(string $component, int $componentid, int $userid, float $amount, string $currency,
+ public static function save_payment(int $accountid, string $component, int $componentid, int $userid, float $amount, string $currency,
string $gateway): int {
global $DB;
@@ -201,6 +236,7 @@ class helper {
$record->amount = $amount;
$record->currency = $currency;
$record->gateway = $gateway;
+ $record->accountid = $accountid;
$record->timecreated = $record->timemodified = time();
$id = $DB->insert_record('payments', $record);
diff --git a/payment/classes/local/callback/provider.php b/payment/classes/local/callback/provider.php
index b94c7bf4624..1e247cea702 100644
--- a/payment/classes/local/callback/provider.php
+++ b/payment/classes/local/callback/provider.php
@@ -43,8 +43,9 @@ interface provider {
public static function get_cost(int $identifier): array;
/**
- * @param int $identifier An identifier that is known to the plugin
+ * @param int $componentid An identifier that is known to the plugin
+ * @param int $paymentid payment id as inserted into the 'payments' table, if needed for reference
* @return bool Whether successful or not
*/
- public static function deliver_order(int $identifier): bool;
+ public static function deliver_order(int $componentid, int $paymentid): bool;
}
diff --git a/payment/gateway/paypal/amd/build/gateways_modal.min.js b/payment/gateway/paypal/amd/build/gateways_modal.min.js
index 9107c3036a5..1993d1f8c74 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 ("pg_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 pg_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('pg_paypal/paypal_button_placeholder', {})\n });\n modal.show();\n return modal;\n};\n\n/**\n * Process the payment.\n *\n * @param {double} amount Amount of payment\n * @param {string} currency The currency in the three-character ISO-4217 format\n * @param {string} component Name of 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 {processCallback} callback The callback function to call when processing is finished\n * @returns {Promise}\n */\nexport const process = async(amount, currency, component, componentid, description, callback) => {\n\n const [\n modal,\n paypalConfig,\n ] = await Promise.all([\n showModalWithPlaceholder(),\n Repository.getConfigForJs(component, componentid),\n ]);\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', 'pg_paypal'));\n\n // Call server to validate and capture payment for order.\n return Ajax.call([{\n methodname: 'pg_paypal_create_transaction_complete',\n args: {\n component,\n componentid,\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","componentid","description","callback","Promise","all","Repository","getConfigForJs","paypalConfig","currency","amount","cost","getRoot","on","ModalEvents","hidden","destroy","paypalScript","clientid","callExternalFunction","setBody","paypal","Buttons","createOrder","data","actions","order","purchase_units","currency_code","value","Truncate","truncate","length","stripTags","application_context","shipping_preference","brand_name","brandname","onApprove","outsideClick","e","preventDefault","Ajax","call","methodname","args","orderid","orderID","then","res","hide","getBody","jsFile","func","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 pg_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('pg_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 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 {processCallback} callback The callback function to call when processing is finished\n * @returns {Promise}\n */\nexport const process = async(component, componentid, description, callback) => {\n\n const [\n modal,\n paypalConfig,\n ] = await Promise.all([\n showModalWithPlaceholder(),\n Repository.getConfigForJs(component, componentid),\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', 'pg_paypal'));\n\n // Call server to validate and capture payment for order.\n return Ajax.call([{\n methodname: 'pg_paypal_create_transaction_complete',\n args: {\n component,\n componentid,\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
diff --git a/payment/gateway/paypal/amd/src/gateways_modal.js b/payment/gateway/paypal/amd/src/gateways_modal.js
index 206d365948f..eab6336f468 100644
--- a/payment/gateway/paypal/amd/src/gateways_modal.js
+++ b/payment/gateway/paypal/amd/src/gateways_modal.js
@@ -45,15 +45,13 @@ const showModalWithPlaceholder = async() => {
/**
* Process the payment.
*
- * @param {double} amount Amount of payment
- * @param {string} currency The currency in the three-character ISO-4217 format
* @param {string} component Name of the component that the componentid belongs to
* @param {number} componentid 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}
*/
-export const process = async(amount, currency, component, componentid, description, callback) => {
+export const process = async(component, componentid, description, callback) => {
const [
modal,
@@ -62,6 +60,8 @@ export const process = async(amount, currency, component, componentid, descripti
showModalWithPlaceholder(),
Repository.getConfigForJs(component, componentid),
]);
+ const currency = paypalConfig.currency;
+ const amount = paypalConfig.cost; // Cost with surcharge.
modal.getRoot().on(ModalEvents.hidden, () => {
// Destroy when hidden.
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 f15b481b126..134784240fa 100644
--- a/payment/gateway/paypal/classes/external/get_config_for_js.php
+++ b/payment/gateway/paypal/classes/external/get_config_for_js.php
@@ -62,10 +62,14 @@ class get_config_for_js extends external_api {
]);
$config = helper::get_gateway_configuration($component, $componentid, 'paypal');
+ $cost = helper::get_cost($component, $componentid);
+ $surcharge = helper::get_gateway_surcharge('paypal');
return [
'clientid' => $config['clientid'],
'brandname' => $config['brandname'],
+ 'cost' => helper::get_cost_with_surcharge($cost['amount'], $surcharge, $cost['currency']),
+ 'currency' => $cost['currency'],
];
}
@@ -78,6 +82,8 @@ class get_config_for_js extends external_api {
return new external_single_structure([
'clientid' => new external_value(PARAM_TEXT, 'PayPal client ID'),
'brandname' => new external_value(PARAM_TEXT, 'Brand name'),
+ 'cost' => new external_value(PARAM_FLOAT, 'Cost with gateway surcharge'),
+ 'currency' => new external_value(PARAM_TEXT, 'Currency'),
]);
}
}
diff --git a/payment/gateway/paypal/classes/external/transaction_complete.php b/payment/gateway/paypal/classes/external/transaction_complete.php
index 0e2a0e3a7ec..56016a8bc09 100644
--- a/payment/gateway/paypal/classes/external/transaction_complete.php
+++ b/payment/gateway/paypal/classes/external/transaction_complete.php
@@ -80,9 +80,8 @@ class transaction_complete extends external_api {
] = payment_helper::get_cost($component, $componentid);
// Add surcharge if there is any.
- if ($config->surcharge) {
- $amount += $amount * $config->surcharge / 100;
- }
+ $surcharge = helper::get_gateway_surcharge('paypal');
+ $amount = helper::get_cost_with_surcharge($amount, $surcharge, $currency);
$paypalhelper = new paypal_helper($config->clientid, $config->secret, $sandbox);
$orderdetails = $paypalhelper->get_order_details($orderid);
@@ -100,10 +99,8 @@ class transaction_complete extends external_api {
$success = true;
// Everything is correct. Let's give them what they paid for.
try {
- payment_helper::deliver_order($component, $componentid);
-
- $paymentid = payment_helper::save_payment($component, $componentid, (int) $USER->id, $amount,
- $currency, 'paypal');
+ $paymentid = payment_helper::save_payment((int)$accountid, $component, $componentid, (int) $USER->id,
+ $amount, $currency, 'paypal');
// Store PayPal extra information.
$record = new \stdClass();
@@ -111,6 +108,8 @@ class transaction_complete extends external_api {
$record->pp_orderid = $orderid;
$DB->insert_record('pg_paypal', $record);
+
+ payment_helper::deliver_order($component, $componentid, $paymentid);
} catch (\Exception $e) {
debugging('Exception while trying to process payment: ' . $e->getMessage(), DEBUG_DEVELOPER);
$success = false;
@@ -148,7 +147,7 @@ class transaction_complete extends external_api {
public static function execute_returns() {
return new external_function_parameters([
'success' => new external_value(PARAM_BOOL, 'Whether everything was successful or not.'),
- 'message' => new external_value(PARAM_TEXT, 'Message (usually the error message).', VALUE_OPTIONAL),
+ 'message' => new external_value(PARAM_RAW, 'Message (usually the error message).'),
]);
}
}
diff --git a/payment/gateway/paypal/classes/paypal_helper.php b/payment/gateway/paypal/classes/paypal_helper.php
index f74984e058e..ff3409a3409 100644
--- a/payment/gateway/paypal/classes/paypal_helper.php
+++ b/payment/gateway/paypal/classes/paypal_helper.php
@@ -162,10 +162,8 @@ class paypal_helper {
],
];
- $command = '{}';
-
$curl = new curl();
- $result = $curl->get($location, $command, $options);
+ $result = $curl->get($location, [], $options);
return json_decode($result, true);
}
diff --git a/payment/templates/gateway.mustache b/payment/templates/gateway.mustache
index 9e2c41aedfa..bc0369496e0 100644
--- a/payment/templates/gateway.mustache
+++ b/payment/templates/gateway.mustache
@@ -42,7 +42,7 @@
}}