diff --git a/admin/settings/payment.php b/admin/settings/payment.php
new file mode 100644
index 00000000000..d8d8dd57007
--- /dev/null
+++ b/admin/settings/payment.php
@@ -0,0 +1,9 @@
+add('payment', new admin_externalpage(
+ 'paymentaccounts',
+ new lang_string('paymentaccounts', 'payment'),
+ new moodle_url("/payment/accounts.php"),
+ ['moodle/payment:manageaccounts', 'moodle/payment:viewpayments']));
diff --git a/admin/settings/top.php b/admin/settings/top.php
index 992436beccf..d567741ad85 100644
--- a/admin/settings/top.php
+++ b/admin/settings/top.php
@@ -39,6 +39,7 @@ $ADMIN->add('root', new admin_category('license', new lang_string('license')));
$ADMIN->add('root', new admin_category('location', new lang_string('location','admin')));
$ADMIN->add('root', new admin_category('language', new lang_string('language')));
$ADMIN->add('root', new admin_category('messaging', new lang_string('messagingcategory', 'admin')));
+$ADMIN->add('root', new admin_category('payment', new lang_string('payments', 'payment')));
$ADMIN->add('root', new admin_category('modules', new lang_string('plugins', 'admin')));
$ADMIN->add('root', new admin_category('security', new lang_string('security','admin')));
$ADMIN->add('root', new admin_category('appearance', new lang_string('appearance','admin')));
diff --git a/enrol/fee/classes/payment/provider.php b/enrol/fee/classes/payment/provider.php
index 56e40e2ff18..8dfd2279c30 100644
--- a/enrol/fee/classes/payment/provider.php
+++ b/enrol/fee/classes/payment/provider.php
@@ -37,7 +37,7 @@ class provider implements \core_payment\local\callback\provider {
* Callback function that returns the enrolment cost for the course that $instanceid enrolment instance belongs to.
*
* @param int $instanceid The enrolment instance id
- * @return array['amount' => float, 'currency' => string]
+ * @return array['amount' => float, 'currency' => string, 'accountid' => int]
*/
public static function get_cost(int $instanceid): array {
global $DB;
@@ -47,6 +47,7 @@ class provider implements \core_payment\local\callback\provider {
return [
'amount' => (float) $instance->cost,
'currency' => $instance->currency,
+ 'accountid' => $instance->customint1,
];
}
diff --git a/enrol/fee/classes/plugin.php b/enrol/fee/classes/plugin.php
index a089e980627..e7777033a00 100644
--- a/enrol/fee/classes/plugin.php
+++ b/enrol/fee/classes/plugin.php
@@ -210,6 +210,7 @@ class enrol_fee_plugin extends enrol_plugin {
'isguestuser' => isguestuser(),
'cost' => $localisedcost,
'currency' => $instance->currency,
+ 'accountid' => $instance->customint1,
'amount' => $cost,
'instanceid' => $instance->id,
'description' => get_string('purchasedescription', 'enrol_fee',
@@ -309,6 +310,10 @@ class enrol_fee_plugin extends enrol_plugin {
$mform->addElement('select', 'status', get_string('status', 'enrol_fee'), $options);
$mform->setDefault('status', $this->get_config('status'));
+ $mform->addElement('select', 'customint1', get_string('paymentaccount', 'payment'),
+ ['' => ''] + \core_payment\helper::get_payment_accounts_menu($context));
+ $mform->addRule('customint1', get_string('required'), 'required', null, 'client');
+
$mform->addElement('text', 'cost', get_string('cost', 'enrol_fee'), array('size' => 4));
$mform->setType('cost', PARAM_RAW);
$mform->setDefault('cost', format_float($this->get_config('cost'), 2, true));
diff --git a/enrol/fee/templates/payment_region.mustache b/enrol/fee/templates/payment_region.mustache
index 0939cf5e0cd..458e76d03a6 100644
--- a/enrol/fee/templates/payment_region.mustache
+++ b/enrol/fee/templates/payment_region.mustache
@@ -41,6 +41,7 @@
"cost": "$108.50",
"amount": 108.50,
"currency": "AUD",
+ "accountid": 1,
"instanceid": 11,
"description": "Enrolment in course Introduction to algorithms",
"isguestuser": false
@@ -64,6 +65,7 @@
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/lang/en/payment.php b/lang/en/payment.php
index 71a8981ec46..534f7229e05 100644
--- a/lang/en/payment.php
+++ b/lang/en/payment.php
@@ -22,11 +22,21 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+$string['accountname'] = 'Account name';
+$string['accountnotavailable'] = 'Not available';
$string['callbacknotimplemented'] = 'The callback is not implemented for component {$a}.';
+$string['createaccount'] = 'Create payment account';
$string['feeincludesurcharge'] = '{$a->fee} (includes {$a->surcharge}% surcharge for using this payment type)';
+$string['gatewaycannotbeenabled'] = 'The payment gateway cannot be enabled because the configuration is incomplete.';
+$string['gatewaydisabled'] = 'Disabled';
+$string['gatewayenabled'] = 'Enabled';
+$string['gatewaynotfound'] = 'Gateway not found';
$string['nocurrencysupported'] = 'No payment in any currency is supported. Please make sure that at least one payment gateway is enabled.';
$string['nogateway'] = 'There is no payment gateway that can be used.';
$string['nogatewayselected'] = 'You first need to select a payment gateway.';
+$string['payments'] = 'Payments';
+$string['paymentaccount'] = 'Payment account';
+$string['paymentaccounts'] = 'Payment accounts';
$string['selectpaymenttype'] = 'Select payment type';
$string['supportedcurrencies'] = 'Supported currencies';
$string['surcharge'] = 'Surcharge (percentage)';
diff --git a/lang/en/plugin.php b/lang/en/plugin.php
index 74481c13e85..05e19ffa855 100644
--- a/lang/en/plugin.php
+++ b/lang/en/plugin.php
@@ -166,6 +166,7 @@ $string['type_mnetservice_plural'] = 'MNet services';
$string['type_mod'] = 'Activity module';
$string['type_mod_plural'] = 'Activity modules';
$string['type_pgmanage'] = 'Manage payment gateways';
+$string['type_pg'] = 'Payment gateway';
$string['type_pg_plural'] = 'Payment gateways';
$string['type_plagiarism'] = 'Plagiarism plugin';
$string['type_plagiarism_plural'] = 'Plagiarism plugins';
diff --git a/lib/classes/form/persistent.php b/lib/classes/form/persistent.php
index 0a146ccedf5..2993053a90c 100644
--- a/lib/classes/form/persistent.php
+++ b/lib/classes/form/persistent.php
@@ -268,7 +268,7 @@ abstract class persistent extends moodleform {
/**
* Return the persistent object associated with this form instance.
*
- * @return core\persistent
+ * @return \core\persistent
*/
final protected function get_persistent() {
return $this->persistent;
diff --git a/lib/db/access.php b/lib/db/access.php
index 5078f4d3daf..4d833cb7514 100644
--- a/lib/db/access.php
+++ b/lib/db/access.php
@@ -2576,4 +2576,22 @@ $capabilities = array(
'editingteacher' => CAP_ALLOW,
]
],
+
+ // Allow to manage payment accounts.
+ 'moodle/payment:manageaccounts' => [
+ 'captype' => 'write',
+ 'riskbitmask' => RISK_PERSONAL | RISK_CONFIG | RISK_DATALOSS,
+ 'contextlevel' => CONTEXT_COURSE,
+ 'archetypes' => [
+ ]
+ ],
+
+ // Allow to view payments.
+ 'moodle/payment:viewpayments' => [
+ 'captype' => 'read',
+ 'riskbitmask' => RISK_PERSONAL,
+ 'contextlevel' => CONTEXT_COURSE,
+ 'archetypes' => [
+ ]
+ ],
);
diff --git a/lib/db/install.xml b/lib/db/install.xml
index 647649055ac..258360850ce 100755
--- a/lib/db/install.xml
+++ b/lib/db/install.xml
@@ -4288,6 +4288,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -4296,6 +4325,7 @@
+
@@ -4303,6 +4333,7 @@
+
diff --git a/lib/db/upgrade.php b/lib/db/upgrade.php
index 575ef24b810..df3fef8d095 100644
--- a/lib/db/upgrade.php
+++ b/lib/db/upgrade.php
@@ -2896,5 +2896,68 @@ function xmldb_main_upgrade($oldversion) {
upgrade_main_savepoint(true, 2021052500.32);
}
+ if ($oldversion < 2021052500.33) {
+
+ // Define table payment_accounts to be created.
+ $table = new xmldb_table('payment_accounts');
+
+ // Adding fields to table payment_accounts.
+ $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+ $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
+ $table->add_field('idnumber', XMLDB_TYPE_CHAR, '100', null, null, null, null);
+ $table->add_field('contextid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+ $table->add_field('enabled', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0');
+ $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, null, null, null);
+ $table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, null, null, null);
+
+ // Adding keys to table payment_accounts.
+ $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
+
+ // Conditionally launch create table for payment_accounts.
+ if (!$dbman->table_exists($table)) {
+ $dbman->create_table($table);
+ }
+
+ // Define table payment_gateways to be created.
+ $table = new xmldb_table('payment_gateways');
+
+ // Adding fields to table payment_gateways.
+ $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+ $table->add_field('accountid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+ $table->add_field('gateway', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null);
+ $table->add_field('enabled', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '1');
+ $table->add_field('config', XMLDB_TYPE_TEXT, null, null, null, null, null);
+ $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+ $table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+
+ // Adding keys to table payment_gateways.
+ $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
+ $table->add_key('accountid', XMLDB_KEY_FOREIGN, ['accountid'], 'payment_accounts', ['id']);
+
+ // Conditionally launch create table for payment_gateways.
+ if (!$dbman->table_exists($table)) {
+ $dbman->create_table($table);
+ }
+
+ // Define field accountid to be added to payments.
+ $table = new xmldb_table('payments');
+ $field = new xmldb_field('accountid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null, 'currency');
+
+ // Conditionally launch add field accountid.
+ if (!$dbman->field_exists($table, $field)) {
+ $dbman->add_field($table, $field);
+ }
+
+ // Define key accountid (foreign) to be added to payments.
+ $table = new xmldb_table('payments');
+ $key = new xmldb_key('accountid', XMLDB_KEY_FOREIGN, ['accountid'], 'payment_accounts', ['id']);
+
+ // Launch add key accountid.
+ $dbman->add_key($table, $key);
+
+ // Main savepoint reached.
+ upgrade_main_savepoint(true, 2021052500.33);
+ }
+
return true;
}
diff --git a/payment/accounts.php b/payment/accounts.php
new file mode 100644
index 00000000000..c6d68791695
--- /dev/null
+++ b/payment/accounts.php
@@ -0,0 +1,71 @@
+.
+
+/**
+ * Management of payment accounts
+ *
+ * @package core_payment
+ * @copyright 2020 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once(__DIR__ . '/../config.php');
+require_once($CFG->libdir . '/adminlib.php');
+
+admin_externalpage_setup('paymentaccounts');
+$PAGE->set_heading(get_string('paymentaccounts', 'payment'));
+
+$enabledplugins = \core\plugininfo\pg::get_enabled_plugins();
+
+echo $OUTPUT->header();
+
+$accounts = \core_payment\helper::get_payment_accounts_to_manage(context_system::instance());
+$table = new html_table();
+$table->head = [get_string('accountname', 'payment'), get_string('type_pg', 'plugin'), ''];
+$table->colclasses = ['', '', 'mdl-right'];
+$table->data = [];
+foreach ($accounts as $account) {
+ $gateways = [];
+ $canmanage = has_capability('moodle/payment:manageaccounts', $account->get_context());
+ foreach ($account->get_gateways() as $gateway) {
+ $status = $gateway->get('enabled') ? $OUTPUT->pix_icon('i/valid', get_string('gatewayenabled', 'payment')) :
+ $OUTPUT->pix_icon('i/invalid', get_string('gatewaydisabled', 'payment'));
+ $gateways[] = $status .
+ ($canmanage ? html_writer::link($gateway->get_edit_url(), $gateway->get_display_name()) : $gateway->get_display_name());
+ }
+ $name = $account->get_formatted_name();
+ if (!$account->is_available()) {
+ $name .= ' ' . html_writer::span(get_string('accountnotavailable', 'payment'), 'badge badge-warning');
+ }
+
+ $menu = new action_menu();
+ $menu->set_alignment(action_menu::TL, action_menu::BL);
+ $menu->set_menu_trigger(get_string('edit'));
+ if ($canmanage) {
+ $menu->add(new action_menu_link_secondary($account->get_edit_url(), null, get_string('edit')));
+ $deleteurl = $account->get_edit_url(['delete' => 1, 'sesskey' => sesskey()]);
+ $deleteaction = new confirm_action(get_string('deleteconfirm', 'tool_recyclebin'));
+ $menu->add(new action_menu_link_secondary($deleteurl, null, get_string('delete')));
+ }
+
+ $table->data[] = [$name, join(', ', $gateways), $OUTPUT->render($menu)];
+}
+
+echo html_writer::table($table);
+
+echo $OUTPUT->single_button(new moodle_url('/payment/manage_account.php'), get_string('createaccount', 'payment'), 'get');
+
+echo $OUTPUT->footer();
diff --git a/payment/amd/build/gateways_modal.min.js b/payment/amd/build/gateways_modal.min.js
index e9eb400f298..ab28fe4d596 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=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 gateways = await getGatewaysSupportingCurrency(currency);\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","parseFloat","dataset","amount","currency","surcharge","parseInt","component","componentid","description","success","message","hide","Notification","addNotification","location","reload","alert","then","matches","elements","gateways","updateCostRegion","accountid","context","renderForPromise","html","js","replaceNodeContents","regions","gatewaysContainer","selectSingleGateway","root","length","checked","locale","localisedCost","toLocaleString","style","fee","costContainer","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 {\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
diff --git a/payment/amd/build/repository.min.js b/payment/amd/build/repository.min.js
index 8357ed7bbaa..f65181de8fb 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){return b.default.call([{methodname:"core_payment_get_gateways_for_currency",args:{currency:a}}])[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:{currency:a,accountid: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 00832a1a84a..a5b47c4af44 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","Ajax","call","methodname","args"],"mappings":"6KAwBA,uDAQO,GAAMA,CAAAA,CAA6B,CAAG,SAAAC,CAAQ,CAAI,CAOrD,MAAOC,WAAKC,IAAL,CAAU,CAND,CACZC,UAAU,CAAE,wCADA,CAEZC,IAAI,CAAE,CACFJ,QAAQ,CAARA,CADE,CAFM,CAMC,CAAV,EAAqB,CAArB,CACV,CARM,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 * @returns {Promise<{shortname: string, name: string, description: String}[]>}\n */\nexport const getGatewaysSupportingCurrency = currency => {\n const request = {\n methodname: 'core_payment_get_gateways_for_currency',\n args: {\n currency\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","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
diff --git a/payment/amd/src/gateways_modal.js b/payment/amd/src/gateways_modal.js
index 41606774ebe..6112b2a9fcb 100644
--- a/payment/amd/src/gateways_modal.js
+++ b/payment/amd/src/gateways_modal.js
@@ -133,7 +133,8 @@ const show = async(rootNode, {
});
const currency = rootNode.dataset.currency;
- const gateways = await getGatewaysSupportingCurrency(currency);
+ const accountid = rootNode.dataset.accountid;
+ const gateways = await getGatewaysSupportingCurrency(currency, accountid);
const context = {
gateways
};
diff --git a/payment/amd/src/repository.js b/payment/amd/src/repository.js
index 758c80aa675..64a968a654f 100644
--- a/payment/amd/src/repository.js
+++ b/payment/amd/src/repository.js
@@ -28,13 +28,15 @@ 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
* @returns {Promise<{shortname: string, name: string, description: String}[]>}
*/
-export const getGatewaysSupportingCurrency = currency => {
+export const getGatewaysSupportingCurrency = (currency, accountid) => {
const request = {
methodname: 'core_payment_get_gateways_for_currency',
args: {
- currency
+ currency,
+ accountid
}
};
return Ajax.call([request])[0];
diff --git a/payment/classes/account.php b/payment/classes/account.php
new file mode 100644
index 00000000000..c938829399d
--- /dev/null
+++ b/payment/classes/account.php
@@ -0,0 +1,152 @@
+.
+
+/**
+ * Class account
+ *
+ * @package core_payment
+ * @copyright 2020 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_payment;
+
+use core\persistent;
+
+/**
+ * Class account
+ *
+ * @package core_payment
+ * @copyright 2020 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class account extends persistent {
+ /**
+ * Database table.
+ */
+ const TABLE = 'payment_accounts';
+
+ /** @var array */
+ protected $gateways;
+
+ /**
+ * Return the definition of the properties of this model.
+ *
+ * @return array
+ */
+ protected static function define_properties() : array {
+ return array(
+ 'name' => [
+ 'type' => PARAM_TEXT,
+ ],
+ 'idnumber' => [
+ 'type' => PARAM_RAW_TRIMMED,
+ ],
+ 'contextid' => [
+ 'type' => PARAM_INT,
+ 'default' => function() {
+ return \context_system::instance()->id;
+ }
+ ],
+ 'enabled' => [
+ 'type' => PARAM_BOOL,
+ 'default' => true
+ ],
+ );
+ }
+
+ /**
+ * Account context
+ *
+ * @return \context
+ * @throws \coding_exception
+ */
+ public function get_context(): \context {
+ return \context::instance_by_id($this->get('contextid'));
+ }
+
+ /**
+ * Account name ready for display
+ *
+ * @return string
+ * @throws \coding_exception
+ */
+ public function get_formatted_name(): string {
+ return format_string($this->get('name'), true, ['context' => $this->get_context(), 'escape' => false]);
+ }
+
+ /**
+ * Manage account url
+ *
+ * @param array $extraparams
+ * @return \moodle_url
+ * @throws \coding_exception
+ * @throws \moodle_exception
+ */
+ public function get_edit_url(array $extraparams = []): \moodle_url {
+ return new \moodle_url('/payment/manage_account.php',
+ ($this->get('id') ? ['id' => $this->get('id')] : []) + $extraparams);
+ }
+
+ /**
+ * List of gateways configured (or possible) for this account
+ *
+ * @param bool $enabledpluginsonly only return payment plugins that are enabled
+ * @return account_gateway[]
+ * @throws \coding_exception
+ */
+ public function get_gateways(bool $enabledpluginsonly = true): array {
+ $id = $this->get('id');
+ if (!$id) {
+ return [];
+ }
+ if ($this->gateways === null) {
+ \core_component::get_plugin_list('pg');
+ $this->gateways = [];
+ foreach (\core_component::get_plugin_list('pg') as $gatewayname => $unused) {
+ $gateway = account_gateway::get_record(['accountid' => $id, 'gateway' => $gatewayname]);
+ if (!$gateway) {
+ $gateway = new account_gateway(0, (object)['accountid' => $id, 'gateway' => $gatewayname,
+ 'enabled' => false, 'config' => null]);
+ }
+ $this->gateways[$gatewayname] = $gateway;
+ }
+ }
+ if ($enabledpluginsonly) {
+ $enabledplugins = \core\plugininfo\pg::get_enabled_plugins();
+ return array_intersect_key($this->gateways, $enabledplugins);
+ }
+ return $this->gateways;
+ }
+
+ /**
+ * Is this account available (used in management interface)
+ *
+ * @return bool
+ * @throws \coding_exception
+ */
+ public function is_available(): bool {
+ if (!$this->get('id') || !$this->get('enabled')) {
+ return false;
+ }
+ foreach ($this->get_gateways() as $gateway) {
+ if ($gateway->get('id') && $gateway->get('enabled')) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/payment/classes/account_gateway.php b/payment/classes/account_gateway.php
new file mode 100644
index 00000000000..5d59bcc0763
--- /dev/null
+++ b/payment/classes/account_gateway.php
@@ -0,0 +1,114 @@
+.
+
+/**
+ * Class account_gateway
+ *
+ * @package core_payment
+ * @copyright 2020 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_payment;
+
+use core\persistent;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Class account_gateway
+ *
+ * @package core_payment
+ * @copyright 2020 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class account_gateway extends persistent {
+ /**
+ * Database table.
+ */
+ const TABLE = 'payment_gateways';
+
+ /**
+ * Return the definition of the properties of this model.
+ *
+ * @return array
+ */
+ protected static function define_properties() : array {
+ return array(
+ 'accountid' => [
+ 'type' => PARAM_INT,
+ ],
+ 'gateway' => [
+ 'type' => PARAM_COMPONENT,
+ // TODO select with options?
+ ],
+ 'enabled' => [
+ 'type' => PARAM_BOOL,
+ 'default' => true
+ ],
+ 'config' => [
+ 'type' => PARAM_RAW,
+ 'optional' => true,
+ 'null' => NULL_ALLOWED,
+ 'default' => null
+ ],
+ );
+ }
+
+ /**
+ * Return the gateway name ready for display
+ *
+ * @return string
+ * @throws \coding_exception
+ */
+ public function get_display_name(): string {
+ return get_string('pluginname', 'pg_' . $this->get('gateway'));
+ }
+
+ /**
+ * Gateway management url
+ *
+ * @return \moodle_url
+ * @throws \coding_exception
+ * @throws \moodle_exception
+ */
+ public function get_edit_url(): \moodle_url {
+ $params = $this->get('id') ? ['id' => $this->get('id')] :
+ ['accountid' => $this->get('accountid'), 'gateway' => $this->get('gateway')];
+ return new \moodle_url('/payment/manage_gateway.php', $params);
+ }
+
+ /**
+ * Get corresponding account
+ *
+ * @return account
+ * @throws \coding_exception
+ */
+ public function get_account(): account {
+ return new account($this->get('accountid'));
+ }
+
+ /**
+ * Parse configuration from the json-encoded stored value
+ *
+ * @return array
+ * @throws \coding_exception
+ */
+ public function get_configuration(): array {
+ $config = @json_decode($this->get('config'), true);
+ return ($config && is_array($config)) ? $config : [];
+ }
+}
diff --git a/payment/classes/external/get_gateways_for_currency.php b/payment/classes/external/get_gateways_for_currency.php
index 42fe3f02211..e17cd66e47a 100644
--- a/payment/classes/external/get_gateways_for_currency.php
+++ b/payment/classes/external/get_gateways_for_currency.php
@@ -43,7 +43,8 @@ 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')]
+ ['currency' => new external_value(PARAM_ALPHA, 'Currency code'),
+ 'accountid' => new external_value(PARAM_INT, 'Account id')]
);
}
@@ -51,16 +52,18 @@ class get_gateways_for_currency extends external_api {
* 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
* @return \stdClass[]
*/
- public static function execute(string $currency): array {
+ public static function execute(string $currency, int $accountid): array {
$params = external_api::validate_parameters(self::execute_parameters(), [
'currency' => $currency,
+ 'accountid' => $accountid,
]);
$list = [];
- $gateways = \core_payment\helper::get_gateways_for_currency($params['currency']);
+ $gateways = \core_payment\helper::get_gateways_for_currency($params['currency'], $params['accountid']);
foreach ($gateways as $gateway) {
$list[] = (object)[
diff --git a/payment/classes/form/account.php b/payment/classes/form/account.php
new file mode 100644
index 00000000000..0eac2b5ab66
--- /dev/null
+++ b/payment/classes/form/account.php
@@ -0,0 +1,64 @@
+.
+
+/**
+ * Class account
+ *
+ * @package core_payment
+ * @copyright 2020 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_payment\form;
+
+use core\form\persistent;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Class account
+ *
+ * @package core_payment
+ * @copyright 2020 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class account extends persistent {
+
+ /** @var string The persistent class. */
+ protected static $persistentclass = 'core_payment\account';
+
+ /**
+ * Define the form - called by parent constructor
+ */
+ public function definition() {
+ $mform = $this->_form;
+
+ $mform->addElement('hidden', 'id');
+ $mform->addElement('hidden', 'contextid');
+
+ $mform->addElement('text', 'name', get_string('accountname', 'payment'), 'maxlength="255"');
+ $mform->setType('name', PARAM_TEXT);
+ $mform->addRule('name', get_string('required'), 'required', null, 'client');
+ $mform->addRule('name', get_string('maximumchars', '', 255), 'maxlength', 255, 'server');
+
+ $mform->addElement('text', 'idnumber', get_string('idnumber'), 'maxlength="100"');
+ $mform->setType('idnumber', PARAM_RAW_TRIMMED);
+ $mform->addRule('idnumber', get_string('maximumchars', '', 100), 'maxlength', 100, 'server');
+
+ $mform->addElement('advcheckbox', 'enabled', get_string('enable'));
+ $this->add_action_buttons();
+ }
+}
diff --git a/payment/classes/form/account_gateway.php b/payment/classes/form/account_gateway.php
new file mode 100644
index 00000000000..b59291db92e
--- /dev/null
+++ b/payment/classes/form/account_gateway.php
@@ -0,0 +1,150 @@
+.
+
+/**
+ * Class account_gateway
+ *
+ * @package core_payment
+ * @copyright 2020 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_payment\form;
+
+use core\form\persistent;
+use core_payment\gateway;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Class account_gateway
+ *
+ * @package core_payment
+ * @copyright 2020 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class account_gateway extends persistent {
+
+ /** @var string The persistent class. */
+ protected static $persistentclass = \core_payment\account_gateway::class;
+
+ protected static $fieldstoremove = ['accountname', 'gatewayname', 'submitbutton'];
+
+ /**
+ * Define the form - called by parent constructor
+ */
+ public function definition() {
+ $mform = $this->_form;
+
+ $mform->addElement('hidden', 'id');
+ $mform->addElement('hidden', 'accountid');
+ $mform->addElement('hidden', 'gateway');
+
+ $mform->addElement('static', 'accountname', get_string('accountname', 'payment'),
+ $this->get_gateway_persistent()->get_account()->get_formatted_name());
+
+ $mform->addElement('static', 'gatewayname', get_string('type_pg', 'plugin'),
+ $this->get_gateway_persistent()->get_display_name());
+
+ $mform->addElement('advcheckbox', 'enabled', get_string('enable'));
+
+ /** @var \core_payment\gateway $classname */
+ $classname = '\pg_' . $this->get_gateway_persistent()->get('gateway') . '\gateway';
+ if (class_exists($classname)) {
+ $classname::add_configuration_to_gateway_form($this);
+ }
+
+ $this->add_action_buttons();
+ }
+
+ /**
+ * Form validation
+ *
+ * @param \stdClass $data
+ * @param array $files
+ * @param array $errors
+ */
+ protected function extra_validation($data, $files, array &$errors) {
+ /** @var \core_payment\gateway $classname */
+ $classname = '\pg_' . $this->get_gateway_persistent()->get('gateway') . '\gateway';
+ if (class_exists($classname)) {
+ $classname::validate_gateway_form($this, $data, $files, $errors);
+ }
+ }
+
+ /**
+ * Exposes the protected attribute to be accessed by the \core_payment\gateway callback
+ *
+ * @return \MoodleQuickForm
+ */
+ public function get_mform(): \MoodleQuickForm {
+ return $this->_form;
+ }
+
+ /**
+ * Exposes the protected attribute to be accessed by the \core_payment\gateway callback
+ *
+ * @return \core_payment\account_gateway
+ */
+ public function get_gateway_persistent(): \core_payment\account_gateway {
+ return $this->get_persistent();
+ }
+
+ /**
+ * Filter out the foreign fields of the persistent.
+ *
+ * This can be overridden to filter out more complex fields.
+ *
+ * @param \stdClass $data The data to filter the fields out of.
+ * @return \stdClass.
+ */
+ protected function filter_data_for_persistent($data) {
+ $data = parent::filter_data_for_persistent($data);
+ return (object) array_intersect_key((array)$data, \core_payment\account_gateway::properties_definition());
+ }
+
+ /**
+ * Overwrite parent method to json encode config
+ *
+ * @return object|\stdClass|null
+ * @throws \coding_exception
+ */
+ public function get_data() {
+ if (!$data = parent::get_data()) {
+ return $data;
+ }
+ // Everything that is not a property of the account_gateway class is a gateway config.
+ $data = (array)$data;
+ $properties = \core_payment\account_gateway::properties_definition() + ['id' => 1];
+ $config = array_diff_key($data, $properties, ['timemodified' => 1, 'timecreated' => 1]);
+ $data = array_intersect_key($data, $properties);
+ $data['config'] = json_encode($config);
+ return (object)$data;
+ }
+
+ /**
+ * Overwrite parent method to json decode config
+ *
+ * @param array|\stdClass $values
+ */
+ public function set_data($values) {
+ if (($config = isset($values->config) ? @json_decode($values->config, true) : null) && is_array($config)) {
+ $values = (object)((array)$values + $config);
+ }
+ unset($values->config);
+ parent::set_data($values);
+ }
+}
diff --git a/payment/classes/gateway.php b/payment/classes/gateway.php
index 32cfb36fb0b..0a78e5c1add 100644
--- a/payment/classes/gateway.php
+++ b/payment/classes/gateway.php
@@ -39,4 +39,30 @@ abstract class gateway {
* @return string[] An array of the currency codes in the three-character ISO-4217 format
*/
public abstract static function get_supported_currencies(): array;
+
+ /**
+ * Configuration form for the gateway instance
+ *
+ * Use $form->get_mform() to access the \MoodleQuickForm instance
+ *
+ * @param \core_payment\form\account_gateway $form
+ */
+ public abstract static function add_configuration_to_gateway_form(\core_payment\form\account_gateway $form): void;
+
+ /**
+ * Validates the gateway configuration form.
+ *
+ * Needs to be overridden to make sure the incomplete configuration can not be enabled.
+ *
+ * @param \core_payment\form\account_gateway $form
+ * @param \stdClass $data
+ * @param array $files
+ * @param array $errors form errors (passed by reference)
+ */
+ public static function validate_gateway_form(\core_payment\form\account_gateway $form,
+ \stdClass $data, array $files, array &$errors): void {
+ if ($data->enabled) {
+ $errors['enabled'] = get_string('gatewaycannotbeenabled', 'payment');
+ }
+ }
}
diff --git a/payment/classes/helper.php b/payment/classes/helper.php
index 2adf0978570..5ba6921377f 100644
--- a/payment/classes/helper.php
+++ b/payment/classes/helper.php
@@ -44,6 +44,7 @@ class helper {
$plugins = \core_plugin_manager::instance()->get_enabled_plugins('pg');
foreach ($plugins as $plugin) {
+ /** @var \pg_paypal\gateway $classname */
$classname = '\pg_' . $plugin . '\gateway';
$currencies += $classname::get_supported_currencies();
@@ -58,13 +59,22 @@ 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
* @return string[]
*/
- public static function get_gateways_for_currency(string $currency): array {
+ public static function get_gateways_for_currency(string $currency, int $accountid): array {
$gateways = [];
- $plugins = \core_plugin_manager::instance()->get_enabled_plugins('pg');
- foreach ($plugins as $plugin) {
+ $account = new account($accountid);
+ if (!$account->get('id') || !$account->get('enabled')) {
+ return $gateways;
+ }
+
+ foreach ($account->get_gateways() as $plugin => $gateway) {
+ if (!$gateway->get('enabled')) {
+ continue;
+ }
+ /** @var gateway $classname */
$classname = '\pg_' . $plugin . '\gateway';
$currencies = $classname::get_supported_currencies();
@@ -114,19 +124,42 @@ class helper {
*
* @param string $component Name of the component that the componentid belongs to
* @param int $componentid An internal identifier that is used by the component
- * @return array['amount' => float, 'currency' => string]
+ * @return array['amount' => float, 'currency' => string, 'accountid' => int]
* @throws \moodle_exception
*/
public static function get_cost(string $component, int $componentid): array {
$cost = component_class_callback("$component\\payment\\provider", 'get_cost', [$componentid]);
- if ($cost === null) {
+ if ($cost === null || !is_array($cost) || !array_key_exists('amount', $cost)
+ || !array_key_exists('currency', $cost) || !array_key_exists('accountid', $cost) ) {
throw new \moodle_exception('callbacknotimplemented', 'core_payment', '', $component);
}
return $cost;
}
+ /**
+ * Returns the gateway configuration for given component and gateway
+ *
+ * @param string $component
+ * @param int $componentid
+ * @param string $gatewayname
+ * @return array
+ * @throws \moodle_exception
+ */
+ public static function get_gateway_configuration(string $component, int $componentid, string $gatewayname): array {
+ $x = self::get_cost($component, $componentid);
+ $gateway = null;
+ $account = new account($x['accountid']);
+ if ($account && $account->get('enabled')) {
+ $gateway = $account->get_gateways()[$gatewayname] ?? null;
+ }
+ if (!$gateway) {
+ throw new \moodle_exception('gatewaynotfound', 'payment');
+ }
+ return $gateway->get_configuration();
+ }
+
/**
* Delivers what the user paid for.
*
@@ -186,4 +219,82 @@ class helper {
get_string('surcharge_desc', 'core_payment'), 0, PARAM_INT));
}
+
+ /**
+ * Save a new or edited payment account (used in management interface)
+ *
+ * @param \stdClass $data
+ */
+ public static function save_payment_account(\stdClass $data) {
+
+ if (empty($data->id)) {
+ $account = new account(0, $data);
+ } else {
+ $account = new account($data->id);
+ $account->from_record($data);
+ }
+
+ $account->save();
+ // TODO trigger event.
+ }
+
+ /**
+ * Delete a payment account (used in management interface)
+ *
+ * @param account $account
+ */
+ public static function delete_payment_account(account $account) {
+ foreach ($account->get_gateways(false) as $gateway) {
+ if ($gateway->get('id')) {
+ $gateway->delete();
+ }
+ }
+ $account->delete();
+ // TODO trigger event.
+ }
+
+ /**
+ * Save a payment gateway linked to an existing account (used in management interface)
+ *
+ * @param \stdClass $data
+ */
+ public static function save_payment_gateway(\stdClass $data) {
+ if (empty($data->id)) {
+ $gateway = new account_gateway(0, $data);
+ } else {
+ $gateway = new account_gateway($data->id);
+ unset($data->accountid, $data->gateway, $data->id);
+ $gateway->from_record($data);
+ }
+
+ $gateway->save();
+ // TODO trigger event.
+ }
+
+ /**
+ * Returns the list of payment accounts in the given context (used in management interface)
+ *
+ * @param \context $context
+ * @return account[]
+ */
+ public static function get_payment_accounts_to_manage(\context $context): array {
+ return account::get_records(['contextid' => $context->id]);
+ }
+
+ /**
+ * Get list of accounts available in the given context
+ *
+ * @param \context $context
+ * @return array
+ */
+ public static function get_payment_accounts_menu(\context $context): array {
+ global $DB;
+ [$sql, $params] = $DB->get_in_or_equal($context->get_parent_context_ids(true));
+ $accounts = array_filter(account::get_records_select('contextid '.$sql, $params), function($account) {
+ return $account->is_available();
+ });
+ return array_map(function($account) {
+ return $account->get_formatted_name();
+ }, $accounts);
+ }
}
diff --git a/payment/classes/local/callback/provider.php b/payment/classes/local/callback/provider.php
index 5d9db19eaab..b94c7bf4624 100644
--- a/payment/classes/local/callback/provider.php
+++ b/payment/classes/local/callback/provider.php
@@ -38,7 +38,7 @@ interface provider {
/**
* @param int $identifier An identifier that is known to the plugin
- * @return array['amount' => float, 'currency' => string]
+ * @return array['amount' => float, 'currency' => string, 'accountid' => int]
*/
public static function get_cost(int $identifier): array;
diff --git a/payment/gateway/paypal/amd/build/gateways_modal.min.js b/payment/gateway/paypal/amd/build/gateways_modal.min.js
index 947d636410d..9107c3036a5 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(),\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","amount","currency","component","componentid","description","callback","Promise","all","Repository","getConfigForJs","paypalConfig","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 {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
diff --git a/payment/gateway/paypal/amd/build/repository.min.js b/payment/gateway/paypal/amd/build/repository.min.js
index 16eb7ec1674..b4dc13f2dbf 100644
--- a/payment/gateway/paypal/amd/build/repository.min.js
+++ b/payment/gateway/paypal/amd/build/repository.min.js
@@ -1,2 +1,2 @@
-define ("pg_paypal/repository",["exports","core/ajax"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.getConfigForJs=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);var c=function(){return b.default.call([{methodname:"pg_paypal_get_config_for_js",args:{}}])[0]};a.getConfigForJs=c});
+define ("pg_paypal/repository",["exports","core/ajax"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.getConfigForJs=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);var c=function(a,c){return b.default.call([{methodname:"pg_paypal_get_config_for_js",args:{component:a,componentid:c}}])[0]};a.getConfigForJs=c});
//# sourceMappingURL=repository.min.js.map
diff --git a/payment/gateway/paypal/amd/build/repository.min.js.map b/payment/gateway/paypal/amd/build/repository.min.js.map
index ac239cda701..e5b95efee8c 100644
--- a/payment/gateway/paypal/amd/build/repository.min.js.map
+++ b/payment/gateway/paypal/amd/build/repository.min.js.map
@@ -1 +1 @@
-{"version":3,"sources":["../src/repository.js"],"names":["getConfigForJs","Ajax","call","methodname","args"],"mappings":"2JAwBA,uDAOO,GAAMA,CAAAA,CAAc,CAAG,UAAM,CAMhC,MAAOC,WAAKC,IAAL,CAAU,CALD,CACZC,UAAU,CAAE,6BADA,CAEZC,IAAI,CAAE,EAFM,CAKC,CAAV,EAAqB,CAArB,CACV,CAPM,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * PayPal repository module to encapsulate all of the AJAX requests that can be sent for PayPal.\n *\n * @module pg_paypal/repository\n * @package pg_paypal\n * @copyright 2020 Shamim Rezaie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\n\n/**\n * Return the PayPal JavaScript SDK URL.\n *\n * @returns {Promise<{clientid: String, brandname: String}>}\n */\nexport const getConfigForJs = () => {\n const request = {\n methodname: 'pg_paypal_get_config_for_js',\n args: {},\n };\n\n return Ajax.call([request])[0];\n};\n"],"file":"repository.min.js"}
\ No newline at end of file
+{"version":3,"sources":["../src/repository.js"],"names":["getConfigForJs","component","componentid","Ajax","call","methodname","args"],"mappings":"2JAwBA,uDAOO,GAAMA,CAAAA,CAAc,CAAG,SAACC,CAAD,CAAYC,CAAZ,CAA4B,CAMtD,MAAOC,WAAKC,IAAL,CAAU,CALD,CACZC,UAAU,CAAE,6BADA,CAEZC,IAAI,CAAE,CAACL,SAAS,CAATA,CAAD,CAAYC,WAAW,CAAXA,CAAZ,CAFM,CAKC,CAAV,EAAqB,CAArB,CACV,CAPM,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * PayPal repository module to encapsulate all of the AJAX requests that can be sent for PayPal.\n *\n * @module pg_paypal/repository\n * @package pg_paypal\n * @copyright 2020 Shamim Rezaie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\n\n/**\n * Return the PayPal JavaScript SDK URL.\n *\n * @returns {Promise<{clientid: String, brandname: String}>}\n */\nexport const getConfigForJs = (component, componentid) => {\n const request = {\n methodname: 'pg_paypal_get_config_for_js',\n args: {component, 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/gateway/paypal/amd/src/gateways_modal.js b/payment/gateway/paypal/amd/src/gateways_modal.js
index adea363931d..206d365948f 100644
--- a/payment/gateway/paypal/amd/src/gateways_modal.js
+++ b/payment/gateway/paypal/amd/src/gateways_modal.js
@@ -60,7 +60,7 @@ export const process = async(amount, currency, component, componentid, descripti
paypalConfig,
] = await Promise.all([
showModalWithPlaceholder(),
- Repository.getConfigForJs(),
+ Repository.getConfigForJs(component, componentid),
]);
modal.getRoot().on(ModalEvents.hidden, () => {
diff --git a/payment/gateway/paypal/amd/src/repository.js b/payment/gateway/paypal/amd/src/repository.js
index 5310b9d255f..fff839a1f2a 100644
--- a/payment/gateway/paypal/amd/src/repository.js
+++ b/payment/gateway/paypal/amd/src/repository.js
@@ -29,10 +29,10 @@ import Ajax from 'core/ajax';
*
* @returns {Promise<{clientid: String, brandname: String}>}
*/
-export const getConfigForJs = () => {
+export const getConfigForJs = (component, componentid) => {
const request = {
methodname: 'pg_paypal_get_config_for_js',
- args: {},
+ args: {component, componentid},
};
return Ajax.call([request])[0];
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 a1300aa028a..f15b481b126 100644
--- a/payment/gateway/paypal/classes/external/get_config_for_js.php
+++ b/payment/gateway/paypal/classes/external/get_config_for_js.php
@@ -26,6 +26,7 @@ declare(strict_types=1);
namespace pg_paypal\external;
+use core_payment\helper;
use external_api;
use external_function_parameters;
use external_value;
@@ -43,7 +44,10 @@ class get_config_for_js extends external_api {
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
- return new external_function_parameters([]);
+ return new external_function_parameters([
+ 'component' => new external_value(PARAM_COMPONENT, ''),
+ 'componentid' => new external_value(PARAM_INT, ''),
+ ]);
}
/**
@@ -51,12 +55,17 @@ class get_config_for_js extends external_api {
*
* @return string[]
*/
- public static function execute(): array {
- $config = get_config('pg_paypal');
+ public static function execute($component, $componentid): array {
+ self::validate_parameters(self::execute_parameters(), [
+ 'component' => $component,
+ 'componentid' => $componentid,
+ ]);
+
+ $config = helper::get_gateway_configuration($component, $componentid, 'paypal');
return [
- 'clientid' => $config->clientid,
- 'brandname' => $config->brandname,
+ 'clientid' => $config['clientid'],
+ 'brandname' => $config['brandname'],
];
}
diff --git a/payment/gateway/paypal/classes/external/transaction_complete.php b/payment/gateway/paypal/classes/external/transaction_complete.php
index 889f13a99c1..0e2a0e3a7ec 100644
--- a/payment/gateway/paypal/classes/external/transaction_complete.php
+++ b/payment/gateway/paypal/classes/external/transaction_complete.php
@@ -26,6 +26,7 @@ declare(strict_types=1);
namespace pg_paypal\external;
+use core_payment\helper;
use external_api;
use external_function_parameters;
use external_value;
@@ -69,12 +70,13 @@ class transaction_complete extends external_api {
'orderid' => $orderid,
]);
- $config = get_config('pg_paypal');
+ $config = (object)helper::get_gateway_configuration($component, $componentid, 'paypal');
$sandbox = $config->environment == 'sandbox';
[
'amount' => $amount,
- 'currency' => $currency
+ 'currency' => $currency,
+ 'accountid' => $accountid,
] = payment_helper::get_cost($component, $componentid);
// Add surcharge if there is any.
diff --git a/payment/gateway/paypal/classes/gateway.php b/payment/gateway/paypal/classes/gateway.php
index 864406aaf2e..438cef6d763 100644
--- a/payment/gateway/paypal/classes/gateway.php
+++ b/payment/gateway/paypal/classes/gateway.php
@@ -41,4 +41,51 @@ class gateway extends \core_payment\gateway {
'MXN', 'MYR', 'NOK', 'NZD', 'PHP', 'PLN', 'RUB', 'SEK', 'SGD', 'THB', 'TRY', 'TWD', 'USD'
];
}
+
+ /**
+ * Configuration form for the gateway instance
+ *
+ * Use $form->get_mform() to access the \MoodleQuickForm instance
+ *
+ * @param \core_payment\form\account_gateway $form
+ */
+ public static function add_configuration_to_gateway_form(\core_payment\form\account_gateway $form): void {
+ $mform = $form->get_mform();
+
+ $mform->addElement('text', 'brandname', get_string('brandname', 'pg_paypal'));
+ $mform->setType('brandname', PARAM_TEXT);
+ $mform->addHelpButton('brandname', 'brandname', 'pg_paypal');
+
+ $mform->addElement('text', 'clientid', get_string('clientid', 'pg_paypal'));
+ $mform->setType('clientid', PARAM_TEXT);
+ $mform->addHelpButton('clientid', 'clientid', 'pg_paypal');
+
+ $mform->addElement('text', 'secret', get_string('secret', 'pg_paypal'));
+ $mform->setType('secret', PARAM_TEXT);
+ $mform->addHelpButton('secret', 'secret', 'pg_paypal');
+
+ $options = [
+ 'live' => get_string('live', 'pg_paypal'),
+ 'sandbox' => get_string('sandbox', 'pg_paypal'),
+ ];
+
+ $mform->addElement('select', 'environment', get_string('environment', 'pg_paypal'), $options);
+ $mform->addHelpButton('environment', 'environment', 'pg_paypal');
+ }
+
+ /**
+ * Validates the gateway configuration form.
+ *
+ * @param \core_payment\form\account_gateway $form
+ * @param \stdClass $data
+ * @param array $files
+ * @param array $errors form errors (passed by reference)
+ */
+ public static function validate_gateway_form(\core_payment\form\account_gateway $form,
+ \stdClass $data, array $files, array &$errors): void {
+ if ($data->enabled &&
+ (empty($data->brandname) || empty($data->clientid) || empty($data->secret))) {
+ $errors['enabled'] = get_string('gatewaycannotbeenabled', 'payment');
+ }
+ }
}
diff --git a/payment/gateway/paypal/db/services.php b/payment/gateway/paypal/db/services.php
index 38fd89d4c5d..9e6d9a74bd8 100644
--- a/payment/gateway/paypal/db/services.php
+++ b/payment/gateway/paypal/db/services.php
@@ -33,14 +33,6 @@ $functions = [
'type' => 'read',
'ajax' => true,
],
- 'pg_paypal_get_sdk_url' => [
- 'classname' => 'pg_paypal\external\get_sdk_url',
- 'methodname' => 'execute',
- 'classpath' => '',
- 'description' => 'Generates and returns the URL of the PayPal JavaScript SDK',
- 'type' => 'read',
- 'ajax' => true,
- ],
'pg_paypal_create_transaction_complete' => [
'classname' => 'pg_paypal\external\transaction_complete',
'methodname' => 'execute',
diff --git a/payment/gateway/paypal/lang/en/pg_paypal.php b/payment/gateway/paypal/lang/en/pg_paypal.php
index 761e9efb278..3a176b05a4d 100644
--- a/payment/gateway/paypal/lang/en/pg_paypal.php
+++ b/payment/gateway/paypal/lang/en/pg_paypal.php
@@ -25,12 +25,12 @@
$string['amountmismatch'] = 'The amount you attempted to pay does not match the required fee. Your account has not been debited.';
$string['authorising'] = 'Authorising the payment. Please wait...';
$string['brandname'] = 'Brand name';
-$string['brandname_desc'] = 'The optional label that overrides the business name in the PayPal account on the PayPal site.';
+$string['brandname_help'] = 'An optional label that overrides the business name for the PayPal account on the PayPal site.';
$string['cannotfetchorderdatails'] = 'Could not fetch payment details from PayPal. Your account has not been debited.';
$string['clientid'] = 'Client ID';
-$string['clientid_desc'] = 'The client ID that PayPal generated for your application.';
+$string['clientid_help'] = 'The client ID that PayPal generated for your application.';
$string['environment'] = 'Environment';
-$string['environment_desc'] = 'You can set this to Sandbox if you are using sandbox accounts (for testing purpose only).';
+$string['environment_help'] = 'You can set this to Sandbox if you are using sandbox accounts (for testing purpose only).';
$string['gatewaydescription'] = 'PayPal is an authorised payment gateway provider for processing credit card transactions.';
$string['gatewayname'] = 'PayPal';
$string['internalerror'] = 'An internal error has occurred. Please contact us.';
@@ -41,4 +41,4 @@ $string['pluginname_desc'] = 'The PayPal plugin allows you to receive payments v
$string['repeatedorder'] = 'This order has already been processed earlier.';
$string['sandbox'] = 'Sandbox';
$string['secret'] = 'Secret';
-$string['secret_desc'] = 'The secret thatPayPal generated for your application.';
+$string['secret_help'] = 'The secret thatPayPal generated for your application.';
diff --git a/payment/gateway/paypal/settings.php b/payment/gateway/paypal/settings.php
index 2463cfaf215..92ddb89d7f5 100644
--- a/payment/gateway/paypal/settings.php
+++ b/payment/gateway/paypal/settings.php
@@ -28,19 +28,5 @@ if ($ADMIN->fulltree) {
$settings->add(new admin_setting_heading('pg_paypal_settings', '', get_string('pluginname_desc', 'pg_paypal')));
- $settings->add(new admin_setting_configtext('pg_paypal/brandname', get_string('brandname', 'pg_paypal'),
- get_string('brandname', 'pg_paypal'), '', PARAM_TEXT));
- $settings->add(new admin_setting_configtext('pg_paypal/clientid', get_string('clientid', 'pg_paypal'),
- get_string('clientid_desc', 'pg_paypal'), '', PARAM_TEXT));
- $settings->add(new admin_setting_configtext('pg_paypal/secret', get_string('secret', 'pg_paypal'),
- get_string('secret_desc', 'pg_paypal'), '', PARAM_TEXT));
-
- $options = [
- 'live' => get_string('live', 'pg_paypal'),
- 'sandbox' => get_string('sandbox', 'pg_paypal'),
- ];
- $settings->add(new admin_setting_configselect('pg_paypal/environment', get_string('environment', 'pg_paypal'),
- get_string('environment_desc', 'pg_paypal'), 'live', $options));
-
\core_payment\helper::add_common_gateway_settings($settings, 'pg_paypal');
}
diff --git a/payment/manage_account.php b/payment/manage_account.php
new file mode 100644
index 00000000000..1a5a0b1e060
--- /dev/null
+++ b/payment/manage_account.php
@@ -0,0 +1,57 @@
+.
+
+/**
+ * Manage one payment accounts
+ *
+ * @package core_payment
+ * @copyright 2020 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once(__DIR__ . '/../config.php');
+require_once($CFG->libdir . '/adminlib.php');
+
+$id = optional_param('id', 0, PARAM_INT);
+$delete = optional_param('delete', false, PARAM_BOOL);
+
+$pageurl = new moodle_url('/payment/manage_account.php');
+admin_externalpage_setup('paymentaccounts', '', [], $pageurl);
+
+$enabledplugins = \core\plugininfo\pg::get_enabled_plugins();
+
+$account = new \core_payment\account($id);
+require_capability('moodle/payment:manageaccounts', $account->get_context());
+
+if ($delete && confirm_sesskey()) {
+ \core_payment\helper::delete_payment_account($account);
+ redirect(new moodle_url('/payment/accounts.php'));
+}
+
+$PAGE->set_heading($id ? format_string($account->get('name')) : get_string('createaccount', 'payment'));
+
+$form = new \core_payment\form\account($pageurl->out(false), ['persistent' => $account]);
+
+if ($form->is_cancelled()) {
+ redirect(new moodle_url('/payment/accounts.php'));
+} else if ($data = $form->get_data()) {
+ \core_payment\helper::save_payment_account($data);
+ redirect(new moodle_url('/payment/accounts.php'));
+}
+
+echo $OUTPUT->header();
+$form->display();
+echo $OUTPUT->footer();
diff --git a/payment/manage_gateway.php b/payment/manage_gateway.php
new file mode 100644
index 00000000000..2833d2cb4d2
--- /dev/null
+++ b/payment/manage_gateway.php
@@ -0,0 +1,63 @@
+.
+
+/**
+ * Manage one payment gateway
+ *
+ * @package core_payment
+ * @copyright 2020 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once(__DIR__ . '/../config.php');
+require_once($CFG->libdir . '/adminlib.php');
+
+$id = optional_param('id', 0, PARAM_INT);
+$accountid = optional_param('accountid', 0, PARAM_INT);
+$gatewayname = optional_param('gateway', null, PARAM_COMPONENT);
+
+$pageurl = new moodle_url('/payment/manage_gateway.php');
+admin_externalpage_setup('paymentaccounts', '', [], $pageurl);
+
+$enabledplugins = \core\plugininfo\pg::get_enabled_plugins();
+
+if ($id) {
+ $gateway = new \core_payment\account_gateway($id);
+ $account = new \core_payment\account($gateway->get('accountid'));
+} else if ($accountid) {
+ $account = new \core_payment\account($accountid);
+ $gateway = $account->get_gateways()[$gatewayname] ?? null;
+}
+
+if (empty($account) || empty($gateway)) {
+ throw new moodle_exception('gatewaynotfound', 'payment');
+}
+require_capability('moodle/payment:manageaccounts', $account->get_context());
+
+$PAGE->set_heading($id ? format_string($account->get('name')) : get_string('createaccount', 'payment'));
+
+$form = new \core_payment\form\account_gateway($pageurl->out(false), ['persistent' => $gateway]);
+
+if ($form->is_cancelled()) {
+ redirect(new moodle_url('/payment/accounts.php'));
+} else if ($data = $form->get_data()) {
+ \core_payment\helper::save_payment_gateway($data);
+ redirect(new moodle_url('/payment/accounts.php'));
+}
+
+echo $OUTPUT->header();
+$form->display();
+echo $OUTPUT->footer();