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