MDL-69166 core_payment: add payment accounts
authorMarina Glancy <marina@moodle.com>
Tue, 29 Sep 2020 17:27:37 +0000 (19:27 +0200)
committerShamim Rezaie <shamim@moodle.com>
Tue, 27 Oct 2020 03:40:49 +0000 (14:40 +1100)
40 files changed:
admin/settings/payment.php [new file with mode: 0644]
admin/settings/top.php
enrol/fee/classes/payment/provider.php
enrol/fee/classes/plugin.php
enrol/fee/templates/payment_region.mustache
lang/en/payment.php
lang/en/plugin.php
lib/classes/form/persistent.php
lib/db/access.php
lib/db/install.xml
lib/db/upgrade.php
payment/accounts.php [new file with mode: 0644]
payment/amd/build/gateways_modal.min.js
payment/amd/build/gateways_modal.min.js.map
payment/amd/build/repository.min.js
payment/amd/build/repository.min.js.map
payment/amd/src/gateways_modal.js
payment/amd/src/repository.js
payment/classes/account.php [new file with mode: 0644]
payment/classes/account_gateway.php [new file with mode: 0644]
payment/classes/external/get_gateways_for_currency.php
payment/classes/form/account.php [new file with mode: 0644]
payment/classes/form/account_gateway.php [new file with mode: 0644]
payment/classes/gateway.php
payment/classes/helper.php
payment/classes/local/callback/provider.php
payment/gateway/paypal/amd/build/gateways_modal.min.js
payment/gateway/paypal/amd/build/gateways_modal.min.js.map
payment/gateway/paypal/amd/build/repository.min.js
payment/gateway/paypal/amd/build/repository.min.js.map
payment/gateway/paypal/amd/src/gateways_modal.js
payment/gateway/paypal/amd/src/repository.js
payment/gateway/paypal/classes/external/get_config_for_js.php
payment/gateway/paypal/classes/external/transaction_complete.php
payment/gateway/paypal/classes/gateway.php
payment/gateway/paypal/db/services.php
payment/gateway/paypal/lang/en/pg_paypal.php
payment/gateway/paypal/settings.php
payment/manage_account.php [new file with mode: 0644]
payment/manage_gateway.php [new file with mode: 0644]

diff --git a/admin/settings/payment.php b/admin/settings/payment.php
new file mode 100644 (file)
index 0000000..d8d8dd5
--- /dev/null
@@ -0,0 +1,9 @@
+<?php
+
+// This file defines settingpages and externalpages under the "Payment" category
+
+$ADMIN->add('payment', new admin_externalpage(
+    'paymentaccounts',
+    new lang_string('paymentaccounts', 'payment'),
+    new moodle_url("/payment/accounts.php"),
+    ['moodle/payment:manageaccounts', 'moodle/payment:viewpayments']));
index 992436b..d567741 100644 (file)
@@ -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')));
index 56e40e2..8dfd227 100644 (file)
@@ -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,
         ];
     }
 
index a089e98..e777703 100644 (file)
@@ -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));
index 0939cf5..458e76d 100644 (file)
@@ -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 }}
index 71a8981..534f722 100644 (file)
  * @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)';
index 74481c1..05e19ff 100644 (file)
@@ -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';
index 0a146cc..2993053 100644 (file)
@@ -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;
index 5078f4d..4d833cb 100644 (file)
@@ -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' => [
+        ]
+    ],
 );
index 6476490..2583608 100755 (executable)
         <INDEX NAME="instance" UNIQUE="false" FIELDS="contextid, contenttype, instanceid"/>
       </INDEXES>
     </TABLE>
+    <TABLE NAME="payment_accounts" COMMENT="Payment accounts">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
+        <FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
+        <FIELD NAME="idnumber" TYPE="char" LENGTH="100" NOTNULL="false" SEQUENCE="false"/>
+        <FIELD NAME="contextid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
+        <FIELD NAME="enabled" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
+        <FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false"/>
+        <FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+      </KEYS>
+    </TABLE>
+    <TABLE NAME="payment_gateways" COMMENT="Configuration for one gateway for one payment account">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
+        <FIELD NAME="accountid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
+        <FIELD NAME="gateway" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false"/>
+        <FIELD NAME="enabled" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1" SEQUENCE="false"/>
+        <FIELD NAME="config" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
+        <FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
+        <FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+        <KEY NAME="accountid" TYPE="foreign" FIELDS="accountid" REFTABLE="payment_accounts" REFFIELDS="id"/>
+      </KEYS>
+    </TABLE>
     <TABLE NAME="payments" COMMENT="Stores information about payments">
       <FIELDS>
         <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
         <FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
         <FIELD NAME="amount" TYPE="char" LENGTH="20" NOTNULL="true" SEQUENCE="false"/>
         <FIELD NAME="currency" TYPE="char" LENGTH="3" NOTNULL="true" SEQUENCE="false"/>
+        <FIELD NAME="accountid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
         <FIELD NAME="gateway" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false"/>
         <FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
         <FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
       <KEYS>
         <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
         <KEY NAME="userid" TYPE="foreign" FIELDS="userid" REFTABLE="user" REFFIELDS="id"/>
+        <KEY NAME="accountid" TYPE="foreign" FIELDS="accountid" REFTABLE="payment_accounts" REFFIELDS="id"/>
       </KEYS>
       <INDEXES>
         <INDEX NAME="component" UNIQUE="false" FIELDS="component"/>
index 575ef24..df3fef8 100644 (file)
@@ -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 (file)
index 0000000..c6d6879
--- /dev/null
@@ -0,0 +1,71 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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();
index e9eb400..ab28fe4 100644 (file)
Binary files a/payment/amd/build/gateways_modal.min.js and b/payment/amd/build/gateways_modal.min.js differ
index ac71fb7..86be958 100644 (file)
Binary files a/payment/amd/build/gateways_modal.min.js.map and b/payment/amd/build/gateways_modal.min.js.map differ
index 8357ed7..f65181d 100644 (file)
Binary files a/payment/amd/build/repository.min.js and b/payment/amd/build/repository.min.js differ
index 00832a1..a5b47c4 100644 (file)
Binary files a/payment/amd/build/repository.min.js.map and b/payment/amd/build/repository.min.js.map differ
index 4160677..6112b2a 100644 (file)
@@ -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
     };
index 758c80a..64a968a 100644 (file)
@@ -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 (file)
index 0000000..c938829
--- /dev/null
@@ -0,0 +1,152 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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 (file)
index 0000000..5d59bcc
--- /dev/null
@@ -0,0 +1,114 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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 : [];
+    }
+}
index 42fe3f0..e17cd66 100644 (file)
@@ -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 (file)
index 0000000..0eac2b5
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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 (file)
index 0000000..b59291d
--- /dev/null
@@ -0,0 +1,150 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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);
+    }
+}
index 32cfb36..0a78e5c 100644 (file)
@@ -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');
+        }
+    }
 }
index 2adf097..5ba6921 100644 (file)
@@ -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);
+    }
 }
index 5d9db19..b94c7bf 100644 (file)
@@ -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;
 
index 947d636..9107c30 100644 (file)
Binary files a/payment/gateway/paypal/amd/build/gateways_modal.min.js and b/payment/gateway/paypal/amd/build/gateways_modal.min.js differ
index 76f9cb4..5075bf4 100644 (file)
Binary files a/payment/gateway/paypal/amd/build/gateways_modal.min.js.map and b/payment/gateway/paypal/amd/build/gateways_modal.min.js.map differ
index 16eb7ec..b4dc13f 100644 (file)
Binary files a/payment/gateway/paypal/amd/build/repository.min.js and b/payment/gateway/paypal/amd/build/repository.min.js differ
index ac239cd..e5b95ef 100644 (file)
Binary files a/payment/gateway/paypal/amd/build/repository.min.js.map and b/payment/gateway/paypal/amd/build/repository.min.js.map differ
index adea363..206d365 100644 (file)
@@ -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, () => {
index 5310b9d..fff839a 100644 (file)
@@ -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];
index a1300aa..f15b481 100644 (file)
@@ -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'],
         ];
     }
 
index 889f13a..0e2a0e3 100644 (file)
@@ -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.
index 864406a..438cef6 100644 (file)
@@ -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');
+        }
+    }
 }
index 38fd89d..9e6d9a7 100644 (file)
@@ -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',
index 761e9ef..3a176b0 100644 (file)
 $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.';
index 2463cfa..92ddb89 100644 (file)
@@ -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 (file)
index 0000000..1a5a0b1
--- /dev/null
@@ -0,0 +1,57 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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 (file)
index 0000000..2833d2c
--- /dev/null
@@ -0,0 +1,63 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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();