MDL-69166 core_payment: improvements to api, small fixes

This commit is contained in:
Marina Glancy 2020-09-30 19:21:30 +02:00 committed by Shamim Rezaie
parent 6ff3087fe6
commit 1d479dc0cf
19 changed files with 115 additions and 92 deletions

View File

@ -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);

View File

@ -202,16 +202,9 @@ class enrol_fee_plugin extends enrol_plugin {
echo '<p>'.get_string('nocost', 'enrol_fee').'</p>';
} 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])),

View File

@ -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 }}

View File

@ -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<B.length&&B[1]!==void 0?B[1]:{},n=m.focusOnClose,o=void 0===n?null:n;a.t0=b.default;a.t1=k.default.TYPE;a.next=5;return(0,d.get_string)("selectpaymenttype","core_payment");case 5:a.t2=a.sent;a.next=8;return c.default.render("core_payment/gateways_modal",{});case 8:a.t3=a.sent;a.t4={type:a.t1,title:a.t2,body:a.t3};a.next=12;return a.t0.create.call(a.t0,a.t4);case 12:p=a.sent;q=p.getRoot()[0];(0,i.addToastRegion)(q);p.show();p.getRoot().on(g.default.hidden,function(){p.destroy();try{o.focus()}catch(a){}});p.getRoot().on(h.default.proceed,function(a){var b=(q.querySelector(f.default.values.gateway)||{value:""}).value;if(b){t(b,{value:parseFloat(l.dataset.amount),currency:l.dataset.currency,surcharge:parseInt((q.querySelector(f.default.values.gateway)||{dataset:{surcharge:0}}).dataset.surcharge)},l.dataset.component,l.dataset.componentid,l.dataset.description,function(a){var b=a.success,c=a.message,d=void 0===c?"":c;p.hide();if(b){j.default.addNotification({message:d,type:"success"});location.reload()}else{j.default.alert("",d)}})}else{(0,d.get_string)("nogatewayselected","core_payment").then(function(a){return(0,i.add)(a)})}a.preventDefault()});q.addEventListener("change",function(a){if(a.target.matches(f.default.elements.gateways)){s(q,parseFloat(l.dataset.amount),l.dataset.currency)}});u=l.dataset.currency;v=l.dataset.accountid;a.next=23;return(0,e.getGatewaysSupportingCurrency)(u,v);case 23:w=a.sent;x={gateways:w};a.next=27;return c.default.renderForPromise("core_payment/gateways",x);case 27:y=a.sent;z=y.html;A=y.js;c.default.replaceNodeContents(q.querySelector(f.default.regions.gatewaysContainer),z,A);r(q);a.next=34;return s(q,parseFloat(l.dataset.amount),l.dataset.currency);case 34:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}(),r=function(a){var b=a.querySelectorAll(f.default.elements.gateways);if(1==b.length){b[0].checked=!0}},s=function(){var a=n(regeneratorRuntime.mark(function a(b,d,e){var g,h,i,j,k,l;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:a.next=2;return s.locale;case 2:g=a.sent;h=parseInt((b.querySelector(f.default.values.gateway)||{dataset:{surcharge:0}}).dataset.surcharge);d+=d*h/100;i=d.toLocaleString(g,{style:"currency",currency:e});a.next=8;return c.default.renderForPromise("core_payment/fee_breakdown",{fee:i,surcharge:h});case 8:j=a.sent;k=j.html;l=j.js;c.default.replaceNodeContents(b.querySelector(f.default.regions.costContainer),k,l);case 12:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}();s.locale=(0,d.get_string)("localecldr","langconfig");var t=function(){var a=n(regeneratorRuntime.mark(function a(b,c,d,e,f,g){var h,i,j,k,l;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:h=c.value,i=c.currency,j=c.surcharge,k=void 0===j?0:j;a.next=3;return"function"==typeof o.define&&o.define.amd?new Promise(function(a,c){o.require(["pg_".concat(b,"/gateways_modal")],a,c)}):"undefined"!=typeof module&&module.exports&&"undefined"!=typeof require||"undefined"!=typeof module&&module.component&&o.require&&"component"===o.require.loader?Promise.resolve(require(("pg_".concat(b,"/gateways_modal")))):Promise.resolve(o["pg_".concat(b,"/gateways_modal")]);case 3:l=a.sent;h+=h*k/100;l.process(h,i,d,e,f,g);case 6:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}()});
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<z.length&&z[1]!==void 0?z[1]:{},n=m.focusOnClose,o=void 0===n?null:n;a.t0=b.default;a.t1=k.default.TYPE;a.next=5;return(0,d.get_string)("selectpaymenttype","core_payment");case 5:a.t2=a.sent;a.next=8;return c.default.render("core_payment/gateways_modal",{});case 8:a.t3=a.sent;a.t4={type:a.t1,title:a.t2,body:a.t3};a.next=12;return a.t0.create.call(a.t0,a.t4);case 12:p=a.sent;q=p.getRoot()[0];(0,i.addToastRegion)(q);p.show();p.getRoot().on(g.default.hidden,function(){p.destroy();try{o.focus()}catch(a){}});p.getRoot().on(h.default.proceed,function(a){var b=(q.querySelector(f.default.values.gateway)||{value:""}).value;if(b){t(b,l.dataset.component,l.dataset.componentid,l.dataset.description,function(a){var b=a.success,c=a.message,d=void 0===c?"":c;p.hide();if(b){j.default.addNotification({message:d,type:"success"});location.reload()}else{j.default.alert("",d)}})}else{(0,d.get_string)("nogatewayselected","core_payment").then(function(a){return(0,i.add)(a)})}a.preventDefault()});q.addEventListener("change",function(a){if(a.target.matches(f.default.elements.gateways)){s(q)}});a.next=21;return(0,e.getGatewaysSupportingCurrency)(l.dataset.component,l.dataset.componentid);case 21:u=a.sent;v={gateways:u};a.next=25;return c.default.renderForPromise("core_payment/gateways",v);case 25:w=a.sent;x=w.html;y=w.js;c.default.replaceNodeContents(q.querySelector(f.default.regions.gatewaysContainer),x,y);r(q);a.next=32;return s(q);case 32:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}(),r=function(a){var b=a.querySelectorAll(f.default.elements.gateways);if(1==b.length){b[0].checked=!0}},s=function(){var a=n(regeneratorRuntime.mark(function a(b){var d,e,g,h,i;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:d=parseInt((b.querySelector(f.default.values.gateway)||{dataset:{surcharge:0}}).dataset.surcharge);e=b.querySelector(f.default.values.gateway).dataset.cost;a.next=4;return c.default.renderForPromise("core_payment/fee_breakdown",{fee:e,surcharge:d});case 4:g=a.sent;h=g.html;i=g.js;c.default.replaceNodeContents(b.querySelector(f.default.regions.costContainer),h,i);case 8:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}();s.locale=(0,d.get_string)("localecldr","langconfig");var t=function(){var a=n(regeneratorRuntime.mark(function a(b,c,d,e,f){var g;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:a.next=2;return"function"==typeof o.define&&o.define.amd?new Promise(function(a,c){o.require(["pg_".concat(b,"/gateways_modal")],a,c)}):"undefined"!=typeof module&&module.exports&&"undefined"!=typeof require||"undefined"!=typeof module&&module.component&&o.require&&"component"===o.require.loader?Promise.resolve(require(("pg_".concat(b,"/gateways_modal")))):Promise.resolve(o["pg_".concat(b,"/gateways_modal")]);case 2:g=a.sent;g.process(c,d,e,f);case 4:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}()});
//# sourceMappingURL=gateways_modal.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -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

View File

@ -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 <http://www.gnu.org/licenses/>.\n\n/**\n * Repository for payment subsystem.\n *\n * @module core_payment/repository\n * @package core_payment\n * @copyright 2020 Shamim Rezaie <shamim@moodle.com>\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"}
{"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 <http://www.gnu.org/licenses/>.\n\n/**\n * Repository for payment subsystem.\n *\n * @module core_payment/repository\n * @package core_payment\n * @copyright 2020 Shamim Rezaie <shamim@moodle.com>\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"}

View File

@ -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<void>}
*/
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<void>}
*/
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);
};
/**

View File

@ -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];

View File

@ -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)'),
])
);
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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);c<b;c++){d[c]=a[c]}return d}function q(a,b){if("undefined"==typeof Symbol||!(Symbol.iterator in Object(a)))return;var c=[],d=!0,e=!1,f=void 0;try{for(var g=a[Symbol.iterator](),h;!(d=(h=g.next()).done);d=!0){c.push(h.value);if(b&&c.length===b)break}}catch(a){e=!0;f=a}finally{try{if(!d&&null!=g["return"])g["return"]()}finally{if(e)throw f}}return c}function r(a){if(Array.isArray(a))return a}function s(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 t(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var h=a.apply(b,c);function f(a){s(h,d,e,f,g,"next",a)}function g(a){s(h,d,e,f,g,"throw",a)}f(void 0)})}}var u=function(){var a=t(regeneratorRuntime.mark(function a(){var b;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:a.t0=f.default;a.next=3;return c.default.render("pg_paypal/paypal_button_placeholder",{});case 3:a.t1=a.sent;a.t2={body:a.t1};a.next=7;return a.t0.create.call(a.t0,a.t2);case 7:b=a.sent;b.show();return a.abrupt("return",b);case 10:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}(),v=function(){var a=t(regeneratorRuntime.mark(function a(c,f,i,j,k,m){var n,o,p,q,r;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:a.next=2;return Promise.all([u(),b.getConfigForJs(i,j)]);case 2:n=a.sent;o=l(n,2);p=o[0];q=o[1];p.getRoot().on(g.default.hidden,function(){p.destroy()});r="https://www.paypal.com/sdk/js?client-id=".concat(q.clientid,"&currency=").concat(f);w(r,function(){p.setBody("");paypal.Buttons({createOrder:function createOrder(a,b){return b.order.create({purchase_units:[{amount:{currency_code:f,value:c},description:d.default.truncate(k,{length:127,stripTags:!0})}],application_context:{shipping_preference:"NO_SHIPPING",brand_name:d.default.truncate(q.brandname,{length:127,stripTags:!0})}})},onApprove:function onApprove(a){p.getRoot().on(g.default.outsideClick,function(a){a.preventDefault()});p.setBody((0,h.get_string)("authorising","pg_paypal"));return e.default.call([{methodname:"pg_paypal_create_transaction_complete",args:{component:i,componentid:j,orderid:a.orderID}}])[0].then(function(a){p.hide();return m(a)})}}).render(p.getBody()[0])});case 9:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}();a.process=v;var w=function(a,b){if(w.currentlyloaded==a){b();return}if(w.currentlyloaded){var d=document.querySelector("script[src=\"".concat(w.currentlyloaded,"\"]"));if(d){d.parentNode.removeChild(d)}}var c=document.createElement("script");if(c.readyState){c.onreadystatechange=function(){if("complete"==this.readyState||"loaded"==this.readyState){this.onreadystatechange=null;b()}}}else{c.onload=function(){b()}}c.setAttribute("src",a);document.head.appendChild(c);w.currentlyloaded=a};w.currentlyloaded=""});
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);c<b;c++){d[c]=a[c]}return d}function q(a,b){if("undefined"==typeof Symbol||!(Symbol.iterator in Object(a)))return;var c=[],d=!0,e=!1,f=void 0;try{for(var g=a[Symbol.iterator](),h;!(d=(h=g.next()).done);d=!0){c.push(h.value);if(b&&c.length===b)break}}catch(a){e=!0;f=a}finally{try{if(!d&&null!=g["return"])g["return"]()}finally{if(e)throw f}}return c}function r(a){if(Array.isArray(a))return a}function s(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 t(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var h=a.apply(b,c);function f(a){s(h,d,e,f,g,"next",a)}function g(a){s(h,d,e,f,g,"throw",a)}f(void 0)})}}var u=function(){var a=t(regeneratorRuntime.mark(function a(){var b;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:a.t0=f.default;a.next=3;return c.default.render("pg_paypal/paypal_button_placeholder",{});case 3:a.t1=a.sent;a.t2={body:a.t1};a.next=7;return a.t0.create.call(a.t0,a.t2);case 7:b=a.sent;b.show();return a.abrupt("return",b);case 10:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}(),v=function(){var a=t(regeneratorRuntime.mark(function a(c,f,i,j){var k,m,n,o,p,q,r;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:a.next=2;return Promise.all([u(),b.getConfigForJs(c,f)]);case 2:k=a.sent;m=l(k,2);n=m[0];o=m[1];p=o.currency;q=o.cost;n.getRoot().on(g.default.hidden,function(){n.destroy()});r="https://www.paypal.com/sdk/js?client-id=".concat(o.clientid,"&currency=").concat(p);w(r,function(){n.setBody("");paypal.Buttons({createOrder:function createOrder(a,b){return b.order.create({purchase_units:[{amount:{currency_code:p,value:q},description:d.default.truncate(i,{length:127,stripTags:!0})}],application_context:{shipping_preference:"NO_SHIPPING",brand_name:d.default.truncate(o.brandname,{length:127,stripTags:!0})}})},onApprove:function onApprove(a){n.getRoot().on(g.default.outsideClick,function(a){a.preventDefault()});n.setBody((0,h.get_string)("authorising","pg_paypal"));return e.default.call([{methodname:"pg_paypal_create_transaction_complete",args:{component:c,componentid:f,orderid:a.orderID}}])[0].then(function(a){n.hide();return j(a)})}}).render(n.getBody()[0])});case 11:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}();a.process=v;var w=function(a,b){if(w.currentlyloaded==a){b();return}if(w.currentlyloaded){var d=document.querySelector("script[src=\"".concat(w.currentlyloaded,"\"]"));if(d){d.parentNode.removeChild(d)}}var c=document.createElement("script");if(c.readyState){c.onreadystatechange=function(){if("complete"==this.readyState||"loaded"==this.readyState){this.onreadystatechange=null;b()}}}else{c.onload=function(){b()}}c.setAttribute("src",a);document.head.appendChild(c);w.currentlyloaded=a};w.currentlyloaded=""});
//# sourceMappingURL=gateways_modal.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -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<void>}
*/
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.

View File

@ -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'),
]);
}
}

View File

@ -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).'),
]);
}
}

View File

@ -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);
}

View File

@ -42,7 +42,7 @@
}}
<div class="custom-control custom-radio {{shortname}}">
<input class="custom-control-input" type="radio" name="payby" id="id-payby-{{uniqid}}-{{shortname}}" data-surcharge="{{surcharge}}" value="{{shortname}}" {{#checked}} checked="checked" {{/checked}} />
<input class="custom-control-input" type="radio" name="payby" id="id-payby-{{uniqid}}-{{shortname}}" data-cost="{{cost}}" data-surcharge="{{surcharge}}" value="{{shortname}}" {{#checked}} checked="checked" {{/checked}} />
<label class="custom-control-label bg-light border p-3 my-3" for="id-payby-{{uniqid}}-{{shortname}}">
<p class="h3">{{name}}</p>
<p class="content mb-2">{{description}}</p>