first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
+99
View File
@@ -0,0 +1,99 @@
<?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');
$showarchived = optional_param('showarchived', false, PARAM_BOOL);
admin_externalpage_setup('paymentaccounts');
$PAGE->set_heading(get_string('paymentaccounts', 'payment'));
$PAGE->set_primary_active_tab('siteadminnode');
$enabledplugins = \core\plugininfo\paygw::get_enabled_plugins();
echo $OUTPUT->header();
$accounts = \core_payment\helper::get_payment_accounts_to_manage(context_system::instance(), $showarchived);
$table = new html_table();
$table->head = [get_string('accountname', 'payment'), get_string('type_paygw_plural', '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 bg-warning text-dark');
}
if ($account->get('archived')) {
$name .= ' ' . html_writer::span(get_string('accountarchived', 'payment'), 'badge bg-secondary text-dark');
}
$menu = new action_menu();
$menu->set_menu_trigger(get_string('edit'));
$menu->set_boundary('window');
if ($canmanage) {
$menu->add(new action_menu_link_secondary($account->get_edit_url(), null, get_string('edit')));
if (!$account->get('archived')) {
$deleteurl = $account->get_edit_url(['delete' => 1, 'sesskey' => sesskey()]);
$menu->add(new action_menu_link_secondary($deleteurl, null, get_string('deleteorarchive', 'payment'), [
'data-modal' => 'confirmation',
'data-modal-type' => 'delete',
'data-modal-content-str' => json_encode(['accountdeleteconfirm', 'payment']),
]));
} else {
$restoreurl = $account->get_edit_url(['restore' => 1, 'sesskey' => sesskey()]);
$menu->add(new action_menu_link_secondary($restoreurl, null, get_string('restoreaccount', 'payment')));
}
}
$table->data[] = [$name, join(', ', $gateways), $OUTPUT->render($menu)];
}
echo html_writer::div(get_string('paymentaccountsexplained', 'payment'), 'pb-2');
if (has_capability('moodle/site:config', context_system::instance())) {
// For administrators add a link to "Manage payment gateways" page.
$link = html_writer::link(new moodle_url('/admin/settings.php', ['section' => 'managepaymentgateways']),
get_string('type_paygwmanage', 'plugin'));
$text = get_string('gotomanageplugins', 'payment', $link);
echo html_writer::div($text, 'pb-2');
}
echo html_writer::div(html_writer::table($table), 'position-relative');
echo html_writer::div(html_writer::link(new moodle_url($PAGE->url, ['showarchived' => !$showarchived]),
$showarchived ? get_string('hidearchived', 'payment') : get_string('showarchived', 'payment')), 'mdl-right');
echo $OUTPUT->single_button(new moodle_url('/payment/manage_account.php'), get_string('createaccount', 'payment'), 'get');
echo $OUTPUT->footer();
+3
View File
@@ -0,0 +1,3 @@
define("core_payment/events",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default={proceed:"core_payment-modal_gateways:proceed"},_exports.default}));
//# sourceMappingURL=events.min.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"events.min.js","sources":["../src/events.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Contain the events the payment modal can fire.\n *\n * @module core_payment/events\n * @copyright 2020 Shamim Rezaie <shamim@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport default {\n proceed: 'core_payment-modal_gateways:proceed',\n};\n"],"names":["proceed"],"mappings":"qKAuBe,CACXA,QAAS"}
+3
View File
@@ -0,0 +1,3 @@
define("core_payment/gateways_modal",["exports","core/templates","core/str","./repository","./selectors","core/modal_events","core_payment/events","core/toast","core/notification","./modal_gateways"],(function(_exports,_templates,_str,_repository,_selectors,_modal_events,_events,_toast,_notification,_modal_gateways){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_templates=_interopRequireDefault(_templates),_selectors=_interopRequireDefault(_selectors),_modal_events=_interopRequireDefault(_modal_events),_events=_interopRequireDefault(_events),_notification=_interopRequireDefault(_notification),_modal_gateways=_interopRequireDefault(_modal_gateways);var _systemImportTransformerGlobalIdentifier="undefined"!=typeof window?window:"undefined"!=typeof self?self:"undefined"!=typeof global?global:{};function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}const show=async function(rootNode){let{focusOnClose:focusOnClose=null}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const body=await _templates.default.render("core_payment/gateways_modal",{}),modal=await _modal_gateways.default.create({title:(0,_str.getString)("selectpaymenttype","core_payment"),body:body,show:!0,removeOnClose:!0}),rootElement=modal.getRoot()[0];(0,_toast.addToastRegion)(rootElement),modal.getRoot().on(_modal_events.default.hidden,(()=>{null==focusOnClose||focusOnClose.focus()})),modal.getRoot().on(_events.default.proceed,(async e=>{e.preventDefault();const gateway=(rootElement.querySelector(_selectors.default.values.gateway)||{value:""}).value;gateway?processPayment(gateway,rootNode.dataset.component,rootNode.dataset.paymentarea,rootNode.dataset.itemid,rootNode.dataset.description).then((message=>{modal.hide(),_notification.default.addNotification({message:message,type:"success"}),location.href=rootNode.dataset.successurl})).catch((message=>_notification.default.alert("",message))):(0,_toast.add)((0,_str.getString)("nogatewayselected","core_payment"),{type:"warning"})})),rootElement.addEventListener("change",(e=>{e.target.matches(_selectors.default.elements.gateways)&&updateCostRegion(rootElement,rootNode.dataset.cost)}));const gateways=await(0,_repository.getAvailableGateways)(rootNode.dataset.component,rootNode.dataset.paymentarea,rootNode.dataset.itemid),context={gateways:gateways},{html:html,js:js}=await _templates.default.renderForPromise("core_payment/gateways",context);_templates.default.replaceNodeContents(rootElement.querySelector(_selectors.default.regions.gatewaysContainer),html,js),selectSingleGateway(rootElement),await updateCostRegion(rootElement,rootNode.dataset.cost)},selectSingleGateway=root=>{const gateways=root.querySelectorAll(_selectors.default.elements.gateways);1==gateways.length&&(gateways[0].checked=!0)},updateCostRegion=async function(root){let defaultCost=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";const gatewayElement=root.querySelector(_selectors.default.values.gateway),surcharge=parseInt((gatewayElement||{dataset:{surcharge:0}}).dataset.surcharge),cost=(gatewayElement||{dataset:{cost:defaultCost}}).dataset.cost,valueStr=surcharge?await(0,_str.getString)("feeincludesurcharge","core_payment",{fee:cost,surcharge:surcharge}):cost,surchargeStr=await(0,_str.getString)("labelvalue","core",{label:await(0,_str.getString)("cost","core"),value:valueStr}),{html:html,js:js}=await _templates.default.renderForPromise("core_payment/fee_breakdown",{surchargestr:surchargeStr});_templates.default.replaceNodeContents(root.querySelector(_selectors.default.regions.costContainer),html,js)},processPayment=async(gateway,component,paymentArea,itemId,description)=>(await("function"==typeof _systemImportTransformerGlobalIdentifier.define&&_systemImportTransformerGlobalIdentifier.define.amd?new Promise((function(resolve,reject){_systemImportTransformerGlobalIdentifier.require(["paygw_".concat(gateway,"/gateways_modal")],resolve,reject)})):"undefined"!=typeof module&&module.exports&&"undefined"!=typeof require||"undefined"!=typeof module&&module.component&&_systemImportTransformerGlobalIdentifier.require&&"component"===_systemImportTransformerGlobalIdentifier.require.loader?Promise.resolve(require("paygw_".concat(gateway,"/gateways_modal"))):Promise.resolve(_systemImportTransformerGlobalIdentifier["paygw_".concat(gateway,"/gateways_modal")]))).process(component,paymentArea,itemId,description),init=()=>{init.initialised||(init.initialised=!0,document.addEventListener("click",(e=>{const gatewayTrigger=e.target.closest('[data-action="core_payment/triggerPayment"]');gatewayTrigger&&(e.preventDefault(),show(gatewayTrigger,{focusOnClose:e.target}))})))};_exports.init=init,init.initialised=!1}));
//# sourceMappingURL=gateways_modal.min.js.map
File diff suppressed because one or more lines are too long
+3
View File
@@ -0,0 +1,3 @@
define("core_payment/modal_gateways",["exports","jquery","core/custom_interaction_events","core/modal","core/modal_events","core_payment/events"],(function(_exports,_jquery,_custom_interaction_events,_modal,_modal_events,_events){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_jquery=_interopRequireDefault(_jquery),_custom_interaction_events=_interopRequireDefault(_custom_interaction_events),_modal=_interopRequireDefault(_modal),_modal_events=_interopRequireDefault(_modal_events),_events=_interopRequireDefault(_events);const SELECTORS_PROCEED_BUTTON='[data-action="proceed"]',SELECTORS_CANCEL_BUTTON='[data-action="cancel"]';class ModalGateways extends _modal.default{constructor(root){super(root)}registerEventListeners(){super.registerEventListeners(),this.getModal().on(_custom_interaction_events.default.events.activate,SELECTORS_PROCEED_BUTTON,((e,data)=>{var proceedEvent=_jquery.default.Event(_events.default.proceed);this.getRoot().trigger(proceedEvent,this),proceedEvent.isDefaultPrevented()||(this.hide(),data.originalEvent.preventDefault())})),this.getModal().on(_custom_interaction_events.default.events.activate,SELECTORS_CANCEL_BUTTON,((e,data)=>{var cancelEvent=_jquery.default.Event(_modal_events.default.cancel);this.getRoot().trigger(cancelEvent,this),cancelEvent.isDefaultPrevented()||(this.hide(),data.originalEvent.preventDefault())}))}}return _exports.default=ModalGateways,_defineProperty(ModalGateways,"TYPE","core_payment-modal_gateways"),_defineProperty(ModalGateways,"TEMPLATE","core_payment/modal_gateways"),ModalGateways.registerModalType(),_exports.default}));
//# sourceMappingURL=modal_gateways.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"modal_gateways.min.js","sources":["../src/modal_gateways.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Contain the logic for the gateways modal: A modal with proceed and cancel buttons.\n *\n * @module core_payment/modal_gateways\n * @copyright 2020 Shamim Rezaie <shamim@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport CustomEvents from 'core/custom_interaction_events';\nimport Modal from 'core/modal';\nimport ModalEvents from 'core/modal_events';\nimport PaymentEvents from 'core_payment/events';\n\nconst SELECTORS = {\n PROCEED_BUTTON: '[data-action=\"proceed\"]',\n CANCEL_BUTTON: '[data-action=\"cancel\"]',\n};\n\nexport default class ModalGateways extends Modal {\n static TYPE = 'core_payment-modal_gateways';\n static TEMPLATE = 'core_payment/modal_gateways';\n\n /**\n * Constructor for the Modal.\n *\n * @param {object} root The root jQuery element for the modal\n */\n constructor(root) {\n super(root);\n }\n\n /**\n * Set up all of the event handling for the modal.\n *\n * @method registerEventListeners\n */\n registerEventListeners() {\n // Apply parent event listeners.\n super.registerEventListeners();\n\n this.getModal().on(CustomEvents.events.activate, SELECTORS.PROCEED_BUTTON, (e, data) => {\n var proceedEvent = $.Event(PaymentEvents.proceed);\n this.getRoot().trigger(proceedEvent, this);\n\n if (!proceedEvent.isDefaultPrevented()) {\n this.hide();\n data.originalEvent.preventDefault();\n }\n });\n\n this.getModal().on(CustomEvents.events.activate, SELECTORS.CANCEL_BUTTON, (e, data) => {\n var cancelEvent = $.Event(ModalEvents.cancel);\n this.getRoot().trigger(cancelEvent, this);\n\n if (!cancelEvent.isDefaultPrevented()) {\n this.hide();\n data.originalEvent.preventDefault();\n }\n });\n }\n}\n\nModalGateways.registerModalType();\n"],"names":["SELECTORS","ModalGateways","Modal","constructor","root","registerEventListeners","getModal","on","CustomEvents","events","activate","e","data","proceedEvent","$","Event","PaymentEvents","proceed","getRoot","trigger","this","isDefaultPrevented","hide","originalEvent","preventDefault","cancelEvent","ModalEvents","cancel","registerModalType"],"mappings":"0yBA6BMA,yBACc,0BADdA,wBAEa,+BAGEC,sBAAsBC,eASvCC,YAAYC,YACFA,MAQVC,+BAEUA,8BAEDC,WAAWC,GAAGC,mCAAaC,OAAOC,SAAUV,0BAA0B,CAACW,EAAGC,YACvEC,aAAeC,gBAAEC,MAAMC,gBAAcC,cACpCC,UAAUC,QAAQN,aAAcO,MAEhCP,aAAaQ,4BACTC,OACLV,KAAKW,cAAcC,0BAItBlB,WAAWC,GAAGC,mCAAaC,OAAOC,SAAUV,yBAAyB,CAACW,EAAGC,YACtEa,YAAcX,gBAAEC,MAAMW,sBAAYC,aACjCT,UAAUC,QAAQM,YAAaL,MAE/BK,YAAYJ,4BACRC,OACLV,KAAKW,cAAcC,4EAtCdvB,qBACH,+CADGA,yBAEC,+BA0CtBA,cAAc2B"}
+10
View File
@@ -0,0 +1,10 @@
define("core_payment/repository",["exports","core/ajax"],(function(_exports,_ajax){var obj;
/**
* Repository for payment subsystem.
*
* @module core_payment/repository
* @copyright 2020 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.getAvailableGateways=void 0,_ajax=(obj=_ajax)&&obj.__esModule?obj:{default:obj};_exports.getAvailableGateways=(component,paymentArea,itemId)=>{const request={methodname:"core_payment_get_available_gateways",args:{component:component,paymentarea:paymentArea,itemid:itemId}};return _ajax.default.call([request])[0]}}));
//# sourceMappingURL=repository.min.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"repository.min.js","sources":["../src/repository.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Repository for payment subsystem.\n *\n * @module core_payment/repository\n * @copyright 2020 Shamim Rezaie <shamim@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\n\n/**\n * @typedef {Object} PaymentGateway A Payment Gateway\n * @property {string} shortname\n * @property {string} name\n * @property {string} description\n */\n\n/**\n * Returns the list of gateways that can process payments in the given currency.\n *\n * @method getAvailableGateways\n * @param {string} component\n * @param {string} paymentArea\n * @param {number} itemId\n * @returns {Promise<PaymentGateway[]>}\n */\nexport const getAvailableGateways = (component, paymentArea, itemId) => {\n const request = {\n methodname: 'core_payment_get_available_gateways',\n args: {\n component,\n paymentarea: paymentArea,\n itemid: itemId,\n }\n };\n return Ajax.call([request])[0];\n};\n"],"names":["component","paymentArea","itemId","request","methodname","args","paymentarea","itemid","Ajax","call"],"mappings":";;;;;;;oLAyCoC,CAACA,UAAWC,YAAaC,gBACnDC,QAAU,CACZC,WAAY,sCACZC,KAAM,CACFL,UAAAA,UACAM,YAAaL,YACbM,OAAQL,gBAGTM,cAAKC,KAAK,CAACN,UAAU"}
+3
View File
@@ -0,0 +1,3 @@
define("core_payment/selectors",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default={elements:{gateways:'[data-region="gateways-container"] input[type="radio"]'},regions:{gatewaysContainer:'[data-region="gateways-container"]',costContainer:'[data-region="fee-breakdown-container"]'},values:{gateway:'[data-region="gateways-container"] input[type="radio"]:checked'}},_exports.default}));
//# sourceMappingURL=selectors.min.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"selectors.min.js","sources":["../src/selectors.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Define all of the selectors we will be using on the payment interface.\n *\n * @module core_payment/selectors\n * @copyright 2019 Shamim Rezaie <shamim@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport default {\n elements: {\n gateways: '[data-region=\"gateways-container\"] input[type=\"radio\"]',\n },\n regions: {\n gatewaysContainer: '[data-region=\"gateways-container\"]',\n costContainer: '[data-region=\"fee-breakdown-container\"]',\n },\n values: {\n gateway: '[data-region=\"gateways-container\"] input[type=\"radio\"]:checked',\n },\n};\n"],"names":["elements","gateways","regions","gatewaysContainer","costContainer","values","gateway"],"mappings":"wKAuBe,CACXA,SAAU,CACNC,SAAU,0DAEdC,QAAS,CACLC,kBAAmB,qCACnBC,cAAe,2CAEnBC,OAAQ,CACJC,QAAS"}
+26
View File
@@ -0,0 +1,26 @@
// 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/>.
/**
* Contain the events the payment modal can fire.
*
* @module core_payment/events
* @copyright 2020 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
export default {
proceed: 'core_payment-modal_gateways:proceed',
};
+194
View File
@@ -0,0 +1,194 @@
// 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/>.
/**
* Contain the logic for the gateways modal.
*
* @module core_payment/gateways_modal
* @copyright 2019 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import Templates from 'core/templates';
import {getString} from 'core/str';
import {getAvailableGateways} from './repository';
import Selectors from './selectors';
import ModalEvents from 'core/modal_events';
import PaymentEvents from 'core_payment/events';
import {add as addToast, addToastRegion} from 'core/toast';
import Notification from 'core/notification';
import ModalGateways from './modal_gateways';
/**
* Register event listeners for the module.
*/
const registerEventListeners = () => {
document.addEventListener('click', e => {
const gatewayTrigger = e.target.closest('[data-action="core_payment/triggerPayment"]');
if (gatewayTrigger) {
e.preventDefault();
show(gatewayTrigger, {focusOnClose: e.target});
}
});
};
/**
* Shows the gateway selector modal.
*
* @param {HTMLElement} rootNode
* @param {Object} options - Additional options
* @param {HTMLElement} options.focusOnClose The element to focus on when the modal is closed.
*/
const show = async(rootNode, {
focusOnClose = null,
} = {}) => {
// Load upfront, so we don't try to inject the internal content into a possibly-not-yet-resolved promise.
const body = await Templates.render('core_payment/gateways_modal', {});
const modal = await ModalGateways.create({
title: getString('selectpaymenttype', 'core_payment'),
body: body,
show: true,
removeOnClose: true,
});
const rootElement = modal.getRoot()[0];
addToastRegion(rootElement);
modal.getRoot().on(ModalEvents.hidden, () => {
focusOnClose?.focus();
});
modal.getRoot().on(PaymentEvents.proceed, async(e) => {
e.preventDefault();
const gateway = (rootElement.querySelector(Selectors.values.gateway) || {value: ''}).value;
if (gateway) {
processPayment(
gateway,
rootNode.dataset.component,
rootNode.dataset.paymentarea,
rootNode.dataset.itemid,
rootNode.dataset.description
).then((message) => {
modal.hide();
Notification.addNotification({
message,
type: 'success',
});
location.href = rootNode.dataset.successurl;
return;
}).catch(message => Notification.alert('', message));
} else {
// We cannot use await in the following line.
// The reason is that we are preventing the default action of the save event being triggered,
// therefore we cannot define the event handler function asynchronous.
addToast(getString('nogatewayselected', 'core_payment'), {
type: 'warning',
});
}
});
// Re-calculate the cost when gateway is changed.
rootElement.addEventListener('change', e => {
if (e.target.matches(Selectors.elements.gateways)) {
updateCostRegion(rootElement, rootNode.dataset.cost);
}
});
const gateways = await getAvailableGateways(rootNode.dataset.component, rootNode.dataset.paymentarea, rootNode.dataset.itemid);
const context = {
gateways
};
const {html, js} = await Templates.renderForPromise('core_payment/gateways', context);
Templates.replaceNodeContents(rootElement.querySelector(Selectors.regions.gatewaysContainer), html, js);
selectSingleGateway(rootElement);
await updateCostRegion(rootElement, rootNode.dataset.cost);
};
/**
* Auto-select the gateway if there is only one gateway.
*
* @param {HTMLElement} root An HTMLElement that contains the cost region
*/
const selectSingleGateway = root => {
const gateways = root.querySelectorAll(Selectors.elements.gateways);
if (gateways.length == 1) {
gateways[0].checked = true;
}
};
/**
* Shows the cost of the item the user is purchasing in the cost region.
*
* @param {HTMLElement} root An HTMLElement that contains the cost region
* @param {string} defaultCost The default cost that is going to be displayed if no gateway is selected
* @returns {Promise<void>}
*/
const updateCostRegion = async(root, defaultCost = '') => {
const gatewayElement = root.querySelector(Selectors.values.gateway);
const surcharge = parseInt((gatewayElement || {dataset: {surcharge: 0}}).dataset.surcharge);
const cost = (gatewayElement || {dataset: {cost: defaultCost}}).dataset.cost;
const valueStr = surcharge ? await getString('feeincludesurcharge', 'core_payment', {fee: cost, surcharge: surcharge}) : cost;
const surchargeStr = await getString('labelvalue', 'core',
{
label: await getString('cost', 'core'),
value: valueStr
}
);
const {html, js} = await Templates.renderForPromise('core_payment/fee_breakdown', {surchargestr: surchargeStr});
Templates.replaceNodeContents(root.querySelector(Selectors.regions.costContainer), html, js);
};
/**
* Process payment using the selected gateway.
*
* @param {string} gateway The gateway to be used for payment
* @param {string} component Name of the component that the itemId belongs to
* @param {string} paymentArea Name of the area in the component that the itemId belongs to
* @param {number} itemId An internal identifier that is used by the component
* @param {string} description Description of the payment
* @returns {Promise<string>}
*/
const processPayment = async(gateway, component, paymentArea, itemId, description) => {
const paymentMethod = await import(`paygw_${gateway}/gateways_modal`);
return paymentMethod.process(component, paymentArea, itemId, description);
};
/**
* Set up the payment actions.
*/
export const init = () => {
if (!init.initialised) {
// Event listeners should only be registered once.
init.initialised = true;
registerEventListeners();
}
};
/**
* Whether the init function was called before.
*
* @static
* @type {boolean}
*/
init.initialised = false;
+79
View File
@@ -0,0 +1,79 @@
// 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/>.
/**
* Contain the logic for the gateways modal: A modal with proceed and cancel buttons.
*
* @module core_payment/modal_gateways
* @copyright 2020 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import $ from 'jquery';
import CustomEvents from 'core/custom_interaction_events';
import Modal from 'core/modal';
import ModalEvents from 'core/modal_events';
import PaymentEvents from 'core_payment/events';
const SELECTORS = {
PROCEED_BUTTON: '[data-action="proceed"]',
CANCEL_BUTTON: '[data-action="cancel"]',
};
export default class ModalGateways extends Modal {
static TYPE = 'core_payment-modal_gateways';
static TEMPLATE = 'core_payment/modal_gateways';
/**
* Constructor for the Modal.
*
* @param {object} root The root jQuery element for the modal
*/
constructor(root) {
super(root);
}
/**
* Set up all of the event handling for the modal.
*
* @method registerEventListeners
*/
registerEventListeners() {
// Apply parent event listeners.
super.registerEventListeners();
this.getModal().on(CustomEvents.events.activate, SELECTORS.PROCEED_BUTTON, (e, data) => {
var proceedEvent = $.Event(PaymentEvents.proceed);
this.getRoot().trigger(proceedEvent, this);
if (!proceedEvent.isDefaultPrevented()) {
this.hide();
data.originalEvent.preventDefault();
}
});
this.getModal().on(CustomEvents.events.activate, SELECTORS.CANCEL_BUTTON, (e, data) => {
var cancelEvent = $.Event(ModalEvents.cancel);
this.getRoot().trigger(cancelEvent, this);
if (!cancelEvent.isDefaultPrevented()) {
this.hide();
data.originalEvent.preventDefault();
}
});
}
}
ModalGateways.registerModalType();
+52
View File
@@ -0,0 +1,52 @@
// 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/>.
/**
* Repository for payment subsystem.
*
* @module core_payment/repository
* @copyright 2020 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import Ajax from 'core/ajax';
/**
* @typedef {Object} PaymentGateway A Payment Gateway
* @property {string} shortname
* @property {string} name
* @property {string} description
*/
/**
* Returns the list of gateways that can process payments in the given currency.
*
* @method getAvailableGateways
* @param {string} component
* @param {string} paymentArea
* @param {number} itemId
* @returns {Promise<PaymentGateway[]>}
*/
export const getAvailableGateways = (component, paymentArea, itemId) => {
const request = {
methodname: 'core_payment_get_available_gateways',
args: {
component,
paymentarea: paymentArea,
itemid: itemId,
}
};
return Ajax.call([request])[0];
};
+35
View File
@@ -0,0 +1,35 @@
// 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/>.
/**
* Define all of the selectors we will be using on the payment interface.
*
* @module core_payment/selectors
* @copyright 2019 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
export default {
elements: {
gateways: '[data-region="gateways-container"] input[type="radio"]',
},
regions: {
gatewaysContainer: '[data-region="gateways-container"]',
costContainer: '[data-region="fee-breakdown-container"]',
},
values: {
gateway: '[data-region="gateways-container"] input[type="radio"]:checked',
},
};
+156
View File
@@ -0,0 +1,156 @@
<?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
],
'archived' => [
'type' => PARAM_BOOL,
'default' => false
],
);
}
/**
* 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('paygw');
$this->gateways = [];
foreach (\core_component::get_plugin_list('paygw') 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\paygw::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;
}
}
+106
View File
@@ -0,0 +1,106 @@
<?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;
/**
* 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,
],
'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
*/
public function get_display_name(): string {
return get_string('pluginname', 'paygw_' . $this->get('gateway'));
}
/**
* Gateway management url
*
* @return \moodle_url
*/
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
*/
public function get_account(): account {
return new account($this->get('accountid'));
}
/**
* Parse configuration from the json-encoded stored value
*
* @return array
*/
public function get_configuration(): array {
$config = @json_decode($this->get('config'), true);
return ($config && is_array($config)) ? $config : [];
}
}
+94
View File
@@ -0,0 +1,94 @@
<?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/>.
namespace core_payment\event;
use core\event\base;
use core_payment\account;
/**
* Class account_created
*
* @package core_payment
* @copyright 2020 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Class account_created
*
* @package core_payment
* @copyright 2020 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class account_created extends base {
/**
* Initialise event parameters.
*/
protected function init() {
$this->data['objecttable'] = 'payment_accounts';
$this->data['crud'] = 'c';
$this->data['edulevel'] = self::LEVEL_OTHER;
}
/**
* Create an instance of the event and add a record snapshot
*
* @param account $account
* @return base
* @throws \coding_exception
*/
public static function create_from_account(account $account) {
$eventparams = [
'objectid' => $account->get('id'),
'context' => $account->get_context(),
'other' => ['name' => $account->get('name')]
];
$event = self::create($eventparams);
$event->add_record_snapshot($event->objecttable, $account->to_record());
return $event;
}
/**
* Returns localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventaccountcreated', 'payment');
}
/**
* Returns non-localised event description with id's for admin use only.
*
* @return string
*/
public function get_description() {
$name = s($this->other['name']);
return "The user with id '$this->userid' created payment account with id '$this->objectid' and the name '{$name}'.";
}
/**
* Returns relevant URL.
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/payment/accounts.php');
}
}
+94
View File
@@ -0,0 +1,94 @@
<?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/>.
namespace core_payment\event;
use core\event\base;
use core_payment\account;
/**
* Class account_deleted
*
* @package core_payment
* @copyright 2020 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Class account_deleted
*
* @package core_payment
* @copyright 2020 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class account_deleted extends base {
/**
* Initialise event parameters.
*/
protected function init() {
$this->data['objecttable'] = 'payment_accounts';
$this->data['crud'] = 'd';
$this->data['edulevel'] = self::LEVEL_OTHER;
}
/**
* Create an instance of the event and add a record snapshot
*
* @param account $account
* @return base
* @throws \coding_exception
*/
public static function create_from_account(account $account) {
$eventparams = [
'objectid' => $account->get('id'),
'context' => $account->get_context(),
'other' => ['name' => $account->get('name')]
];
$event = self::create($eventparams);
$event->add_record_snapshot($event->objecttable, $account->to_record());
return $event;
}
/**
* Returns localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventaccountdeleted', 'payment');
}
/**
* Returns non-localised event description with id's for admin use only.
*
* @return string
*/
public function get_description() {
$name = s($this->other['name']);
return "The user with id '$this->userid' deleted payment account with id '$this->objectid' and the name '{$name}'.";
}
/**
* Returns relevant URL.
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/payment/accounts.php');
}
}
+101
View File
@@ -0,0 +1,101 @@
<?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/>.
namespace core_payment\event;
use core\event\base;
use core_payment\account;
/**
* Class account_updated
*
* @package core_payment
* @copyright 2020 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Class account_updated
*
* @package core_payment
* @copyright 2020 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class account_updated extends base {
/**
* Initialise event parameters.
*/
protected function init() {
$this->data['objecttable'] = 'payment_accounts';
$this->data['crud'] = 'u';
$this->data['edulevel'] = self::LEVEL_OTHER;
}
/**
* Create an instance of the event and add a record snapshot
*
* @param account $account
* @param array $other
* @return base
*/
public static function create_from_account(account $account, array $other = []) {
$eventparams = [
'objectid' => $account->get('id'),
'context' => $account->get_context(),
'other' => ['name' => $account->get('name')] + $other
];
$event = self::create($eventparams);
$event->add_record_snapshot($event->objecttable, $account->to_record());
return $event;
}
/**
* Returns localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventaccountupdated', 'payment');
}
/**
* Returns non-localised event description with id's for admin use only.
*
* @return string
*/
public function get_description() {
$name = s($this->other['name']);
if (!empty($this->other['archived'])) {
$verb = 'archived';
} else if (!empty($this->other['restored'])) {
$verb = 'restored';
} else {
$verb = 'updated';
}
return "The user with id '$this->userid' $verb payment account with id '$this->objectid' and the name '{$name}'.";
}
/**
* Returns relevant URL.
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/payment/accounts.php');
}
}
+102
View File
@@ -0,0 +1,102 @@
<?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/>.
/**
* This is the external API for this component.
*
* @package core_payment
* @copyright 2019 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_payment\external;
use core_payment\helper;
use core_external\external_api;
use core_external\external_value;
use core_external\external_single_structure;
use core_external\external_multiple_structure;
use core_external\external_function_parameters;
class get_available_gateways extends external_api {
/**
* Returns description of method parameters.
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'component' => new external_value(PARAM_COMPONENT, 'Component'),
'paymentarea' => new external_value(PARAM_AREA, 'Payment area in the component'),
'itemid' => new external_value(PARAM_INT, 'An identifier for payment area in the component')
]);
}
/**
* Returns the list of gateways that can process payments in the given currency.
*
* @param string $component
* @param string $paymentarea
* @param int $itemid
* @return \stdClass[]
*/
public static function execute(string $component, string $paymentarea, int $itemid): array {
$params = external_api::validate_parameters(self::execute_parameters(), [
'component' => $component,
'paymentarea' => $paymentarea,
'itemid' => $itemid,
]);
$list = [];
$gateways = helper::get_available_gateways($params['component'], $params['paymentarea'], $params['itemid']);
$payable = helper::get_payable($params['component'], $params['paymentarea'], $params['itemid']);
$amount = $payable->get_amount();
$currency = $payable->get_currency();
foreach ($gateways as $gateway) {
$surcharge = helper::get_gateway_surcharge($gateway);
$list[] = (object)[
'shortname' => $gateway,
'name' => get_string('gatewayname', 'paygw_' . $gateway),
'description' => get_string('gatewaydescription', 'paygw_' . $gateway),
'surcharge' => $surcharge,
'cost' => helper::get_cost_as_string($amount, $currency, $surcharge),
];
}
return $list;
}
/**
* Returns description of method result value.
*
* @return external_multiple_structure
*/
public static function execute_returns(): external_multiple_structure {
return new external_multiple_structure(
new external_single_structure([
'shortname' => new external_value(PARAM_PLUGIN, 'Name of the plugin'),
'name' => new external_value(PARAM_TEXT, 'Human readable name of the gateway'),
'description' => new external_value(PARAM_RAW, 'description of the gateway'),
'surcharge' => new external_value(PARAM_INT, 'percentage of surcharge when using the gateway'),
'cost' => new external_value(PARAM_TEXT,
'Cost in human-readable form (amount plus surcharge with currency sign)'),
])
);
}
}
+66
View File
@@ -0,0 +1,66 @@
<?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;
/**
* 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->addHelpButton('name', 'accountname', 'payment');
$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('accountidnumber', 'payment'), 'maxlength="100"');
$mform->addHelpButton('idnumber', 'accountidnumber', 'payment');
$mform->setType('idnumber', PARAM_RAW_TRIMMED);
$mform->addRule('idnumber', get_string('maximumchars', '', 100), 'maxlength', 100, 'server');
$mform->addElement('static', 'staticinfo', '', get_string('accountconfignote', 'payment'));
$mform->addElement('advcheckbox', 'enabled', get_string('enable'));
$this->add_action_buttons();
}
}
+147
View File
@@ -0,0 +1,147 @@
<?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;
/**
* 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_paygw', 'plugin'),
$this->get_gateway_persistent()->get_display_name());
$mform->addElement('advcheckbox', 'enabled', get_string('enable'));
/** @var \core_payment\gateway $classname */
$classname = '\paygw_' . $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 = '\paygw_' . $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);
}
}
+66
View File
@@ -0,0 +1,66 @@
<?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/>.
/**
* Contains base class for payment gateways.
*
* @package core_payment
* @copyright 2019 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_payment;
/**
* Base class for payment gateways.
*
* @copyright 2019 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class gateway {
/**
* Returns the list of currencies that the payment gateway supports.
*
* @return string[] An array of the currency codes in the three-character ISO-4217 format
*/
abstract public 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
*/
abstract public 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');
}
}
}
+416
View File
@@ -0,0 +1,416 @@
<?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/>.
/**
* Contains helper class for the payment subsystem.
*
* @package core_payment
* @copyright 2019 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_payment;
use core_payment\event\account_created;
use core_payment\event\account_deleted;
use core_payment\event\account_updated;
/**
* Helper class for the payment subsystem.
*
* @copyright 2019 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class helper {
/**
* Returns an accumulated list of supported currencies by all payment gateways.
*
* @return string[] An array of the currency codes in the three-character ISO-4217 format
*/
public static function get_supported_currencies(): array {
$currencies = [];
$plugins = \core_plugin_manager::instance()->get_enabled_plugins('paygw');
foreach ($plugins as $plugin) {
/** @var \paygw_paypal\gateway $classname */
$classname = '\paygw_' . $plugin . '\gateway';
$currencies = array_merge($currencies, component_class_callback($classname, 'get_supported_currencies', [], []));
}
$currencies = array_unique($currencies);
return $currencies;
}
/**
* Returns the list of gateways that can process payments in the given currency.
*
* @param string $component Name of the component that the paymentarea and itemid belong to
* @param string $paymentarea Payment area
* @param int $itemid An identifier that is known to the component
* @return string[]
*/
public static function get_available_gateways(string $component, string $paymentarea, int $itemid): array {
$gateways = [];
$payable = static::get_payable($component, $paymentarea, $itemid);
$account = new account($payable->get_account_id());
if (!$account->get('id') || !$account->get('enabled')) {
return $gateways;
}
$currency = $payable->get_currency();
foreach ($account->get_gateways() as $plugin => $gateway) {
if (!$gateway->get('enabled')) {
continue;
}
/** @var gateway $classname */
$classname = '\paygw_' . $plugin . '\gateway';
$currencies = component_class_callback($classname, 'get_supported_currencies', [], []);
if (in_array($currency, $currencies)) {
$gateways[] = $plugin;
}
}
return $gateways;
}
/**
* Rounds the cost based on the currency fractional digits, can also apply surcharge
*
* @param float $amount amount in the currency units
* @param string $currency currency, used for calculating the number of fractional digits
* @param float $surcharge surcharge in percents
* @return float
*/
public static function get_rounded_cost(float $amount, string $currency, float $surcharge = 0): float {
$amount = $amount * (100 + $surcharge) / 100;
$locale = get_string('localecldr', 'langconfig');
$fmt = \NumberFormatter::create($locale, \NumberFormatter::CURRENCY);
$localisedcost = numfmt_format_currency($fmt, $amount, $currency);
return numfmt_parse_currency($fmt, $localisedcost, $currency);
}
/**
* Returns human-readable amount with correct number of fractional digits and currency indicator, can also apply surcharge
*
* @param float $amount amount in the currency units
* @param string $currency The currency
* @param float $surcharge surcharge in percents
* @return string
*/
public static function get_cost_as_string(float $amount, string $currency, float $surcharge = 0): string {
$amount = $amount * (100 + $surcharge) / 100;
$locale = get_string('localecldr', 'langconfig');
$fmt = \NumberFormatter::create($locale, \NumberFormatter::CURRENCY);
$localisedcost = numfmt_format_currency($fmt, $amount, $currency);
return $localisedcost;
}
/**
* Returns the percentage of surcharge that is applied when using a gateway
*
* @param string $gateway Name of the gateway
* @return float
*/
public static function get_gateway_surcharge(string $gateway): float {
return (float)get_config('paygw_' . $gateway, 'surcharge');
}
/**
* Returns the attributes to place on a pay button.
*
* @param string $component Name of the component that the paymentarea and itemid belong to
* @param string $paymentarea Payment area
* @param int $itemid An internal identifier that is used by the component
* @param string $description Description of the payment
* @return array
*/
public static function gateways_modal_link_params(string $component, string $paymentarea, int $itemid,
string $description): array {
$payable = static::get_payable($component, $paymentarea, $itemid);
$successurl = static::get_success_url($component, $paymentarea, $itemid);
return [
'id' => 'gateways-modal-trigger',
'role' => 'button',
'data-action' => 'core_payment/triggerPayment',
'data-component' => $component,
'data-paymentarea' => $paymentarea,
'data-itemid' => $itemid,
'data-cost' => static::get_cost_as_string($payable->get_amount(), $payable->get_currency()),
'data-description' => $description,
'data-successurl' => $successurl->out(false),
];
}
/**
* Get the name of the service provider class
*
* @param string $component The component
* @return string
* @throws \coding_exception
*/
private static function get_service_provider_classname(string $component) {
$providerclass = "$component\\payment\\service_provider";
if (class_exists($providerclass)) {
$rc = new \ReflectionClass($providerclass);
if ($rc->implementsInterface(local\callback\service_provider::class)) {
return $providerclass;
}
}
throw new \coding_exception("$component does not have an eligible implementation of payment service_provider.");
}
/**
* Asks the payable from the related component.
*
* @param string $component Name of the component that the paymentarea and itemid belong to
* @param string $paymentarea Payment area
* @param int $itemid An internal identifier that is used by the component
* @return local\entities\payable
*/
public static function get_payable(string $component, string $paymentarea, int $itemid): local\entities\payable {
$providerclass = static::get_service_provider_classname($component);
return component_class_callback($providerclass, 'get_payable', [$paymentarea, $itemid]);
}
/**
* Fetches the URL of the page the user should be redirected to from the related component
*
* @param string $component Name of the component that the paymentarea and itemid belong to
* @param string $paymentarea Payment area
* @param int $itemid An identifier that is known to the component
* @return \moodle_url
*/
public static function get_success_url(string $component, string $paymentarea, int $itemid): \moodle_url {
$providerclass = static::get_service_provider_classname($component);
return component_class_callback($providerclass, 'get_success_url', [$paymentarea, $itemid]);
}
/**
* Returns the gateway configuration for given component and gateway
*
* @param string $component Name of the component that the paymentarea and itemid belong to
* @param string $paymentarea Payment area
* @param int $itemid An identifier that is known to the component
* @param string $gatewayname The gateway name
* @return array
* @throws \moodle_exception
*/
public static function get_gateway_configuration(string $component, string $paymentarea, int $itemid,
string $gatewayname): array {
$payable = self::get_payable($component, $paymentarea, $itemid);
$gateway = null;
$account = new account($payable->get_account_id());
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.
*
* @uses \core_payment\local\callback\service_provider::deliver_order()
*
* @param string $component Name of the component that the paymentarea and itemid belong to
* @param string $paymentarea Payment area
* @param int $itemid An internal identifier that is used by the component
* @param int $paymentid payment id as inserted into the 'payments' table, if needed for reference
* @param int $userid The userid the order is going to deliver to
* @return bool Whether successful or not
*/
public static function deliver_order(string $component, string $paymentarea, int $itemid, int $paymentid, int $userid): bool {
$providerclass = static::get_service_provider_classname($component);
$result = component_class_callback($providerclass, 'deliver_order', [$paymentarea, $itemid, $paymentid, $userid]);
return $result;
}
/**
* Stores essential information about the payment and returns the "id" field of the payment record in DB.
* Each payment gateway may then store the additional information their way.
*
* @param int $accountid Account id
* @param string $component Name of the component that the paymentarea and itemid belong to
* @param string $paymentarea Payment area
* @param int $itemid An internal identifier that is used by the component
* @param int $userid Id of the user who is paying
* @param float $amount Amount of payment
* @param string $currency Currency of payment
* @param string $gateway The gateway that is used for the payment
* @return int
*/
public static function save_payment(int $accountid, string $component, string $paymentarea, int $itemid, int $userid,
float $amount, string $currency, string $gateway): int {
global $DB;
$record = new \stdClass();
$record->component = $component;
$record->paymentarea = $paymentarea;
$record->itemid = $itemid;
$record->userid = $userid;
$record->amount = $amount;
$record->currency = $currency;
$record->gateway = $gateway;
$record->accountid = $accountid;
$record->timecreated = $record->timemodified = time();
$id = $DB->insert_record('payments', $record);
return $id;
}
/**
* This functions adds the settings that are common for all payment gateways.
*
* @param \admin_settingpage $settings The settings object
* @param string $gateway The gateway name prefixed with paygw_
*/
public static function add_common_gateway_settings(\admin_settingpage $settings, string $gateway): void {
$settings->add(new \admin_setting_configtext($gateway . '/surcharge', get_string('surcharge', 'core_payment'),
get_string('surcharge_desc', 'core_payment'), 0, PARAM_INT));
}
/**
* Save a new or edited payment account (used in management interface)
*
* @param \stdClass $data
* @return account
*/
public static function save_payment_account(\stdClass $data): account {
if (empty($data->id)) {
$account = new account(0, $data);
$account->save();
account_created::create_from_account($account)->trigger();
} else {
$account = new account($data->id);
$account->from_record($data);
$account->save();
account_updated::create_from_account($account)->trigger();
}
return $account;
}
/**
* Delete a payment account (used in management interface)
*
* @param account $account
*/
public static function delete_payment_account(account $account): void {
global $DB;
if ($DB->record_exists('payments', ['accountid' => $account->get('id')])) {
$account->set('archived', 1);
$account->save();
account_updated::create_from_account($account, ['archived' => 1])->trigger();
return;
}
foreach ($account->get_gateways(false) as $gateway) {
if ($gateway->get('id')) {
$gateway->delete();
}
}
$event = account_deleted::create_from_account($account);
$account->delete();
$event->trigger();
}
/**
* Restore archived payment account (used in management interface)
*
* @param account $account
*/
public static function restore_payment_account(account $account): void {
$account->set('archived', 0);
$account->save();
account_updated::create_from_account($account, ['restored' => 1])->trigger();
}
/**
* Save a payment gateway linked to an existing account (used in management interface)
*
* @param \stdClass $data
* @return account_gateway
*/
public static function save_payment_gateway(\stdClass $data): account_gateway {
if (empty($data->id)) {
$records = account_gateway::get_records(['accountid' => $data->accountid, 'gateway' => $data->gateway]);
if ($records) {
$gateway = reset($records);
} else {
$gateway = new account_gateway(0, $data);
}
} else {
$gateway = new account_gateway($data->id);
}
unset($data->accountid, $data->gateway, $data->id);
$gateway->from_record($data);
$account = $gateway->get_account();
$gateway->save();
account_updated::create_from_account($account)->trigger();
return $gateway;
}
/**
* 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, bool $showarchived = false): array {
$records = account::get_records(['contextid' => $context->id] + ($showarchived ? [] : ['archived' => 0]));
\core_collator::asort_objects_by_method($records, 'get_formatted_name');
return $records;
}
/**
* 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() && !$account->get('archived');
});
return array_map(function($account) {
return $account->get_formatted_name();
}, $accounts);
}
}
@@ -0,0 +1,67 @@
<?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/>.
/**
* This file contains the \core_payment\local\local\callback\service_provider interface.
*
* Plugins should implement this if they use payment subsystem.
*
* @package core_payment
* @copyright 2020 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_payment\local\callback;
/**
* The service_provider interface for plugins to provide callbacks which are needed by the payment subsystem.
*
* @copyright 2020 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface service_provider {
/**
* Callback function that returns the cost of the given item in the specified payment area,
* along with the accountid that payments are paid to.
*
* @param string $paymentarea Payment area
* @param int $itemid An identifier that is known to the plugin
* @return \core_payment\local\entities\payable
*/
public static function get_payable(string $paymentarea, int $itemid): \core_payment\local\entities\payable;
/**
* Callback function that returns the URL of the page the user should be redirected to in the case of a successful payment.
*
* @param string $paymentarea Payment area
* @param int $itemid An identifier that is known to the plugin
* @return \moodle_url
*/
public static function get_success_url(string $paymentarea, int $itemid): \moodle_url;
/**
* Callback function that delivers what the user paid for to them.
*
* @param string $paymentarea Payment area
* @param int $itemid An identifier that is known to the plugin
* @param int $paymentid payment id as inserted into the 'payments' table, if needed for reference
* @param int $userid The userid the order is going to deliver to
*
* @return bool Whether successful or not
*/
public static function deliver_order(string $paymentarea, int $itemid, int $paymentid, int $userid): bool;
}
@@ -0,0 +1,70 @@
<?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/>.
/**
* The payable class.
*
* @package core_payment
* @copyright 2020 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_payment\local\entities;
/**
* The payable class.
*
* @copyright 2020 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class payable {
private $amount;
private $currency;
private $accountid;
public function __construct(float $amount, string $currency, int $accountid) {
$this->amount = $amount;
$this->currency = $currency;
$this->accountid = $accountid;
}
/**
* Get the amount of the payable cost.
*
* @return float
*/
public function get_amount(): float {
return $this->amount;
}
/**
* Get the currency of the payable cost.
*
* @return string
*/
public function get_currency(): string {
return $this->currency;
}
/**
* Get the id of the payment account the cost is payable to.
*
* @return int
*/
public function get_account_id(): int {
return $this->accountid;
}
}
@@ -0,0 +1,69 @@
<?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/>.
namespace core_payment\privacy;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\approved_userlist;
use core_privacy\local\request\userlist;
use context;
interface consumer_provider {
/**
* Return contextid for the provided payment data
*
* @param string $paymentarea Payment area
* @param int $itemid The item id
* @return int|null
*/
public static function get_contextid_for_payment(string $paymentarea, int $itemid): ?int;
/**
* Get the list of users who have data within a context.
*
* @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
*/
public static function get_users_in_context(userlist $userlist);
/**
* Export all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts to export information for.
*/
public static function export_user_data(approved_contextlist $contextlist);
/**
* Delete all data for all users in the specified context.
*
* @param context $context The specific context to delete data for.
*/
public static function delete_data_for_all_users_in_context(context $context);
/**
* Delete all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
*/
public static function delete_data_for_user(approved_contextlist $contextlist);
/**
* Delete multiple users within a single context.
*
* @param approved_userlist $userlist The approved context and user information to delete information for.
*/
public static function delete_data_for_users(approved_userlist $userlist);
}
@@ -0,0 +1,37 @@
<?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/>.
namespace core_payment\privacy;
interface paygw_provider {
/**
* Export all user data for the specified payment record, and the given context.
*
* @param \context $context Context
* @param array $subcontext The location within the current context that the payment data belongs
* @param \stdClass $payment The payment record
*/
public static function export_payment_data(\context $context, array $subcontext, \stdClass $payment);
/**
* Delete all user data related to the given payments.
*
* @param string $paymentsql SQL query that selects payment.id field for the payments
* @param array $paymentparams Array of parameters for $paymentsql
*/
public static function delete_data_for_payment_sql(string $paymentsql, array $paymentparams);
}
+338
View File
@@ -0,0 +1,338 @@
<?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/>.
/**
* Privacy Subsystem implementation for core_payment.
*
* @package core_payment
* @category privacy
* @copyright 2020 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_payment\privacy;
use core_privacy\local\metadata\collection;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\approved_userlist;
use core_privacy\local\request\contextlist;
use core_privacy\local\request\transform;
use core_privacy\local\request\userlist;
use core_privacy\local\request\writer;
use core_payment\helper as payment_helper;
/**
* Privacy Subsystem implementation for core_payment.
*
* @copyright 2020 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
// This component has data.
// We need to return all payment information where the user is
// listed in the payment.userid field.
// We may also need to fetch this informtion from individual plugins in some cases.
// e.g. to fetch the full and other gateway-specific meta-data.
\core_privacy\local\metadata\provider,
// This is a subsysytem which provides information to core.
\core_privacy\local\request\subsystem\provider,
// This is a subsysytem which provides information to plugins.
\core_privacy\local\request\subsystem\plugin_provider,
// This plugin is capable of determining which users have data within it.
\core_privacy\local\request\core_userlist_provider,
// This plugin is capable of determining which users have data within it for the plugins it provides data to.
\core_privacy\local\request\shared_userlist_provider
{
/**
* Returns meta data about this system.
*
* @param collection $collection The initialised collection to add items to.
* @return collection A listing of user data stored through this system.
*/
public static function get_metadata(collection $collection): collection {
// The 'payments' table contains data about payments.
$collection->add_database_table('payments', [
'userid' => 'privacy:metadata:database:payments:userid',
'amount' => 'privacy:metadata:database:payments:amount',
'currency' => 'privacy:metadata:database:payments:currency',
'gateway' => 'privacy:metadata:database:payments:gateway',
'timecreated' => 'privacy:metadata:database:payments:timecreated',
'timemodified' => 'privacy:metadata:database:payments:timemodified',
], 'privacy:metadata:database:payments');
return $collection;
}
/**
* Get the list of users who have data within a context.
*
* @param int $userid The user to search.
* @return contextlist The contextlist containing the list of contexts used in this plugin.
*/
public static function get_contexts_for_userid(int $userid): contextlist {
global $DB;
$contextids = [];
$payments = $DB->get_recordset('payments', ['userid' => $userid]);
foreach ($payments as $payment) {
$contextids[] = \core_privacy\manager::component_class_callback(
$payment->component,
consumer_provider::class,
'get_contextid_for_payment',
[$payment->paymentarea, $payment->itemid]
) ?: SYSCONTEXTID;
}
$payments->close();
$contextlist = new contextlist();
if (!empty($contextids)) {
[$insql, $inparams] = $DB->get_in_or_equal(array_unique($contextids), SQL_PARAMS_NAMED);
$contextlist->add_from_sql("SELECT id FROM {context} WHERE id {$insql}", $inparams);
}
return $contextlist;
}
/**
* Get the list of users who have data within a context.
*
* @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
*/
public static function get_users_in_context(userlist $userlist) {
global $DB;
$providers = static::get_consumer_providers();
foreach ($providers as $provider) {
$provider::get_users_in_context($userlist);
}
// Orphaned payments.
$context = $userlist->get_context();
if ($context instanceof \context_system) {
[$notinsql, $notinparams] = $DB->get_in_or_equal($providers, SQL_PARAMS_NAMED, 'param', false);
$sql = "SELECT p.userid
FROM {payments} p
WHERE component $notinsql";
$userlist->add_from_sql('userid', $sql, $notinparams);
}
}
/**
* Export all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts to export information for.
*/
public static function export_user_data(approved_contextlist $contextlist) {
global $DB;
$providers = static::get_consumer_providers();
foreach ($providers as $provider) {
$provider::export_user_data($contextlist);
}
// Orphaned payments.
if (in_array(SYSCONTEXTID, $contextlist->get_contextids())) {
[$notinsql, $notinparams] = $DB->get_in_or_equal($providers, SQL_PARAMS_NAMED, 'param', false);
$params = ['userid' => $contextlist->get_user()->id] + $notinparams;
$orphanedpayments = $DB->get_records_sql(
"SELECT *
FROM {payments}
WHERE userid = :userid AND component $notinsql",
$params
);
foreach ($orphanedpayments as $payment) {
static::export_payment_data_for_user_in_context(
\context_system::instance(),
[''],
$payment->userid,
$payment->component,
$payment->paymentarea,
$payment->itemid
);
}
}
}
/**
* Delete all data for all users in the specified context.
*
* @param context $context The specific context to delete data for.
*/
public static function delete_data_for_all_users_in_context(\context $context) {
global $DB;
$providers = static::get_consumer_providers();
foreach ($providers as $provider) {
$provider::delete_data_for_all_users_in_context($context);
}
// Orphaned payments.
if ($context instanceof \context_system) {
[$notinsql, $params] = $DB->get_in_or_equal($providers, SQL_PARAMS_NAMED, 'param', false);
$paymentsql = "SELECT id FROM {payments} WHERE component $notinsql";
static::delete_data_for_payment_sql($paymentsql, $params);
}
}
/**
* Delete all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
*/
public static function delete_data_for_user(approved_contextlist $contextlist) {
global $DB;
$providers = static::get_consumer_providers();
foreach ($providers as $provider) {
$provider::delete_data_for_user($contextlist);
}
// Orphaned payments.
if (in_array(SYSCONTEXTID, $contextlist->get_contextids())) {
[$notinsql, $notinparams] = $DB->get_in_or_equal($providers, SQL_PARAMS_NAMED, 'param', false);
$paymentsql = "SELECT id
FROM {payments}
WHERE userid = :userid AND component $notinsql";
$paymentparams = ['userid' => $contextlist->get_user()->id] + $notinparams;
static::delete_data_for_payment_sql($paymentsql, $paymentparams);
}
}
/**
* Delete multiple users within a single context.
*
* @param approved_userlist $userlist The approved context and user information to delete information for.
*/
public static function delete_data_for_users(approved_userlist $userlist) {
global $DB;
$providers = static::get_consumer_providers();
foreach ($providers as $provider) {
$provider::delete_data_for_users($userlist);
}
// Orphaned payments.
if ($userlist->get_context() instanceof \context_system) {
[$notinsql, $notinparams] = $DB->get_in_or_equal($providers, SQL_PARAMS_NAMED, 'param', false);
[$usersql, $userparams] = $DB->get_in_or_equal($userlist->get_userids(), SQL_PARAMS_NAMED);
$paymentsql = "SELECT id
FROM {payments}
WHERE component $notinsql AND userid $usersql";
$paymentparams = $notinparams + $userparams;
static::delete_data_for_payment_sql($paymentsql, $paymentparams);
}
}
/**
* Returns the list of plugins that use the payment subsystem and implement the consumer_provider interface.
*
* @return string[] provider class names
*/
private static function get_consumer_providers(): array {
$providers = [];
foreach (array_keys(\core_component::get_plugin_types()) as $plugintype) {
$potentialproviders = \core_component::get_plugin_list_with_class($plugintype, 'privacy\provider');
foreach ($potentialproviders as $potentialprovider) {
if (is_a($potentialprovider, consumer_provider::class, true)) {
$providers[] = $potentialprovider;
}
}
}
return $providers;
}
/**
* Export all user data for the specified user, in the specified context.
*
* @param \context $context The context that the payment belongs to
* @param string[] $subpath Sub-path to be used during export
* @param int $userid User id
* @param string $component Component name
* @param string $paymentarea Payment area
* @param int $itemid An internal identifier that is used by the component
*/
public static function export_payment_data_for_user_in_context(\context $context, array $subpath, int $userid,
string $component, string $paymentarea, int $itemid) {
global $DB;
$payments = $DB->get_records('payments', [
'component' => $component,
'paymentarea' => $paymentarea,
'itemid' => $itemid,
'userid' => $userid,
]);
foreach ($payments as $payment) {
$data = (object) [
'userid' => transform::user($payment->userid),
'amount' => payment_helper::get_cost_as_string($payment->amount, $payment->currency),
'timecreated' => transform::datetime($payment->timecreated),
'timemodified' => transform::datetime($payment->timemodified),
];
$subcontext = array_merge(
[get_string('payments', 'payment')],
$subpath,
['payment-' . $payment->id]
);
writer::with_context($context)->export_data(
$subcontext,
$data
);
\core_privacy\manager::component_class_callback(
'paygw_' . $payment->gateway,
paygw_provider::class,
'export_payment_data',
[$context, $subcontext, $payment]
);
}
}
/**
* Delete all user data related to the given payments.
*
* @param string $paymentsql SQL query that selects payment.id field for the payments
* @param array $paymentparams Array of parameters for $paymentsql
*/
public static function delete_data_for_payment_sql(string $paymentsql, array $paymentparams) {
global $DB;
\core_privacy\manager::plugintype_class_callback(
'paygw',
paygw_provider::class,
'delete_data_for_payment_sql',
[$paymentsql, $paymentparams]
);
$DB->delete_records_subquery('payments', 'id', 'id', $paymentsql, $paymentparams);
}
}
+10
View File
@@ -0,0 +1,10 @@
define("paygw_paypal/gateways_modal",["exports","./repository","core/templates","core/truncate","core/modal","core/modal_events","core/str"],(function(_exports,Repository,_templates,_truncate,_modal,_modal_events,_str){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.process=void 0,Repository=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}
/**
* This module is responsible for PayPal content in the gateways modal.
*
* @module paygw_paypal/gateways_modal
* @copyright 2020 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/(Repository),_templates=_interopRequireDefault(_templates),_truncate=_interopRequireDefault(_truncate),_modal=_interopRequireDefault(_modal),_modal_events=_interopRequireDefault(_modal_events);const showModalWithPlaceholder=async()=>await _modal.default.create({body:await _templates.default.render("paygw_paypal/paypal_button_placeholder",{}),show:!0,removeOnClose:!0});_exports.process=(component,paymentArea,itemId,description)=>Promise.all([showModalWithPlaceholder(),Repository.getConfigForJs(component,paymentArea,itemId)]).then((_ref=>{let[modal,paypalConfig]=_ref;return Promise.all([modal,paypalConfig,switchSdk(paypalConfig.clientid,paypalConfig.currency)])})).then((_ref2=>{let[modal,paypalConfig]=_ref2;return modal.setBody(""),new Promise((resolve=>{window.paypal.Buttons({createOrder:function(data,actions){return actions.order.create({purchase_units:[{amount:{currency_code:paypalConfig.currency_code,value:paypalConfig.cost},description:_truncate.default.truncate(description,{length:127,stripTags:!0})}],application_context:{shipping_preference:"NO_SHIPPING",brand_name:_truncate.default.truncate(paypalConfig.brandname,{length:127,stripTags:!0})}})},onApprove:function(data){modal.getRoot().on(_modal_events.default.outsideClick,(e=>{e.preventDefault()})),modal.setBody((0,_str.getString)("authorising","paygw_paypal")),Repository.markTransactionComplete(component,paymentArea,itemId,data.orderID).then((res=>(modal.hide(),res))).then(resolve)}}).render(modal.getBody()[0])}))})).then((res=>res.success?Promise.resolve(res.message):Promise.reject(res.message)));const switchSdk=(clientId,currency)=>{const sdkUrl="https://www.paypal.com/sdk/js?client-id=".concat(clientId,"&currency=").concat(currency);if(switchSdk.currentlyloaded===sdkUrl)return Promise.resolve();if(switchSdk.currentlyloaded){const suspectedScript=document.querySelector('script[src="'.concat(switchSdk.currentlyloaded,'"]'));suspectedScript&&suspectedScript.parentNode.removeChild(suspectedScript)}const script=document.createElement("script");return new Promise((resolve=>{script.readyState?script.onreadystatechange=function(){"complete"!=this.readyState&&"loaded"!=this.readyState||(this.onreadystatechange=null,resolve())}:script.onload=function(){resolve()},script.setAttribute("src",sdkUrl),document.head.appendChild(script),switchSdk.currentlyloaded=sdkUrl}))};switchSdk.currentlyloaded=""}));
//# sourceMappingURL=gateways_modal.min.js.map
File diff suppressed because one or more lines are too long
+10
View File
@@ -0,0 +1,10 @@
define("paygw_paypal/repository",["exports","core/ajax"],(function(_exports,_ajax){var obj;
/**
* PayPal repository module to encapsulate all of the AJAX requests that can be sent for PayPal.
*
* @module paygw_paypal/repository
* @copyright 2020 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.markTransactionComplete=_exports.getConfigForJs=void 0,_ajax=(obj=_ajax)&&obj.__esModule?obj:{default:obj};_exports.getConfigForJs=(component,paymentArea,itemId)=>{const request={methodname:"paygw_paypal_get_config_for_js",args:{component:component,paymentarea:paymentArea,itemid:itemId}};return _ajax.default.call([request])[0]};_exports.markTransactionComplete=(component,paymentArea,itemId,orderId)=>{const request={methodname:"paygw_paypal_create_transaction_complete",args:{component:component,paymentarea:paymentArea,itemid:itemId,orderid:orderId}};return _ajax.default.call([request])[0]}}));
//# sourceMappingURL=repository.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"repository.min.js","sources":["../src/repository.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * PayPal repository module to encapsulate all of the AJAX requests that can be sent for PayPal.\n *\n * @module paygw_paypal/repository\n * @copyright 2020 Shamim Rezaie <shamim@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\n\n/**\n * Return the PayPal JavaScript SDK URL.\n *\n * @param {string} component Name of the component that the itemId belongs to\n * @param {string} paymentArea The area of the component that the itemId belongs to\n * @param {number} itemId An internal identifier that is used by the component\n * @returns {Promise<{clientid: string, brandname: string, cost: number, currency: string}>}\n */\nexport const getConfigForJs = (component, paymentArea, itemId) => {\n const request = {\n methodname: 'paygw_paypal_get_config_for_js',\n args: {\n component,\n paymentarea: paymentArea,\n itemid: itemId,\n },\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Call server to validate and capture payment for order.\n *\n * @param {string} component Name of the component that the itemId belongs to\n * @param {string} paymentArea The area of the component that the itemId belongs to\n * @param {number} itemId An internal identifier that is used by the component\n * @param {string} orderId The order id coming back from PayPal\n * @returns {*}\n */\nexport const markTransactionComplete = (component, paymentArea, itemId, orderId) => {\n const request = {\n methodname: 'paygw_paypal_create_transaction_complete',\n args: {\n component,\n paymentarea: paymentArea,\n itemid: itemId,\n orderid: orderId,\n },\n };\n\n return Ajax.call([request])[0];\n};\n"],"names":["component","paymentArea","itemId","request","methodname","args","paymentarea","itemid","Ajax","call","orderId","orderid"],"mappings":";;;;;;;yMAiC8B,CAACA,UAAWC,YAAaC,gBAC7CC,QAAU,CACZC,WAAY,iCACZC,KAAM,CACFL,UAAAA,UACAM,YAAaL,YACbM,OAAQL,gBAITM,cAAKC,KAAK,CAACN,UAAU,qCAYO,CAACH,UAAWC,YAAaC,OAAQQ,iBAC9DP,QAAU,CACZC,WAAY,2CACZC,KAAM,CACFL,UAAAA,UACAM,YAAaL,YACbM,OAAQL,OACRS,QAASD,iBAIVF,cAAKC,KAAK,CAACN,UAAU"}
@@ -0,0 +1,167 @@
// 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/>.
/**
* This module is responsible for PayPal content in the gateways modal.
*
* @module paygw_paypal/gateways_modal
* @copyright 2020 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import * as Repository from './repository';
import Templates from 'core/templates';
import Truncate from 'core/truncate';
import Modal from 'core/modal';
import ModalEvents from 'core/modal_events';
import {getString} from 'core/str';
/**
* Creates and shows a modal that contains a placeholder.
*
* @returns {Promise<Modal>}
*/
const showModalWithPlaceholder = async() => await Modal.create({
body: await Templates.render('paygw_paypal/paypal_button_placeholder', {}),
show: true,
removeOnClose: true,
});
/**
* Process the payment.
*
* @param {string} component Name of the component that the itemId belongs to
* @param {string} paymentArea The area of the component that the itemId belongs to
* @param {number} itemId An internal identifier that is used by the component
* @param {string} description Description of the payment
* @returns {Promise<string>}
*/
export const process = (component, paymentArea, itemId, description) => {
return Promise.all([
showModalWithPlaceholder(),
Repository.getConfigForJs(component, paymentArea, itemId),
])
.then(([modal, paypalConfig]) => {
return Promise.all([
modal,
paypalConfig,
switchSdk(paypalConfig.clientid, paypalConfig.currency),
]);
})
.then(([modal, paypalConfig]) => {
// We have to clear the body. The render method in paypal.Buttons will render everything.
modal.setBody('');
return new Promise(resolve => {
window.paypal.Buttons({
// Set up the transaction.
createOrder: function(data, actions) {
return actions.order.create({
purchase_units: [{ // eslint-disable-line
amount: {
currency_code: paypalConfig.currency_code, // eslint-disable-line
value: paypalConfig.cost,
},
description: Truncate.truncate(description, {length: 127, stripTags: true}),
}],
application_context: { // eslint-disable-line
shipping_preference: 'NO_SHIPPING', // eslint-disable-line
brand_name: Truncate.truncate(paypalConfig.brandname, {length: 127, stripTags: true}), // eslint-disable-line
},
});
},
// Finalise the transaction.
onApprove: function(data) {
modal.getRoot().on(ModalEvents.outsideClick, (e) => {
// Prevent closing the modal when clicking outside of it.
e.preventDefault();
});
modal.setBody(getString('authorising', 'paygw_paypal'));
Repository.markTransactionComplete(component, paymentArea, itemId, data.orderID)
.then(res => {
modal.hide();
return res;
})
.then(resolve);
}
}).render(modal.getBody()[0]);
});
})
.then(res => {
if (res.success) {
return Promise.resolve(res.message);
}
return Promise.reject(res.message);
});
};
/**
* Unloads the previously loaded PayPal JavaScript SDK, and loads a new one.
*
* @param {string} clientId PayPal client ID
* @param {string} currency The currency
* @returns {Promise}
*/
const switchSdk = (clientId, currency) => {
const sdkUrl = `https://www.paypal.com/sdk/js?client-id=${clientId}&currency=${currency}`;
// Check to see if this file has already been loaded. If so just go straight to the func.
if (switchSdk.currentlyloaded === sdkUrl) {
return Promise.resolve();
}
// PayPal can only work with one currency at the same time. We have to unload the previously loaded script
// if it was loaded for a different currency. Weird way indeed, but the only way.
// See: https://github.com/paypal/paypal-checkout-components/issues/1180
if (switchSdk.currentlyloaded) {
const suspectedScript = document.querySelector(`script[src="${switchSdk.currentlyloaded}"]`);
if (suspectedScript) {
suspectedScript.parentNode.removeChild(suspectedScript);
}
}
const script = document.createElement('script');
return new Promise(resolve => {
if (script.readyState) {
script.onreadystatechange = function() {
if (this.readyState == 'complete' || this.readyState == 'loaded') {
this.onreadystatechange = null;
resolve();
}
};
} else {
script.onload = function() {
resolve();
};
}
script.setAttribute('src', sdkUrl);
document.head.appendChild(script);
switchSdk.currentlyloaded = sdkUrl;
});
};
/**
* Holds the full url of loaded PayPal JavaScript SDK.
*
* @static
* @type {string}
*/
switchSdk.currentlyloaded = '';
@@ -0,0 +1,68 @@
// 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/>.
/**
* PayPal repository module to encapsulate all of the AJAX requests that can be sent for PayPal.
*
* @module paygw_paypal/repository
* @copyright 2020 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import Ajax from 'core/ajax';
/**
* Return the PayPal JavaScript SDK URL.
*
* @param {string} component Name of the component that the itemId belongs to
* @param {string} paymentArea The area of the component that the itemId belongs to
* @param {number} itemId An internal identifier that is used by the component
* @returns {Promise<{clientid: string, brandname: string, cost: number, currency: string}>}
*/
export const getConfigForJs = (component, paymentArea, itemId) => {
const request = {
methodname: 'paygw_paypal_get_config_for_js',
args: {
component,
paymentarea: paymentArea,
itemid: itemId,
},
};
return Ajax.call([request])[0];
};
/**
* Call server to validate and capture payment for order.
*
* @param {string} component Name of the component that the itemId belongs to
* @param {string} paymentArea The area of the component that the itemId belongs to
* @param {number} itemId An internal identifier that is used by the component
* @param {string} orderId The order id coming back from PayPal
* @returns {*}
*/
export const markTransactionComplete = (component, paymentArea, itemId, orderId) => {
const request = {
methodname: 'paygw_paypal_create_transaction_complete',
args: {
component,
paymentarea: paymentArea,
itemid: itemId,
orderid: orderId,
},
};
return Ajax.call([request])[0];
};
@@ -0,0 +1,90 @@
<?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/>.
/**
* This class contains a list of webservice functions related to the PayPal payment gateway.
*
* @package paygw_paypal
* @copyright 2020 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
declare(strict_types=1);
namespace paygw_paypal\external;
use core_payment\helper;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_value;
use core_external\external_single_structure;
class get_config_for_js extends external_api {
/**
* Returns description of method parameters.
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'component' => new external_value(PARAM_COMPONENT, 'Component'),
'paymentarea' => new external_value(PARAM_AREA, 'Payment area in the component'),
'itemid' => new external_value(PARAM_INT, 'An identifier for payment area in the component'),
]);
}
/**
* Returns the config values required by the PayPal JavaScript SDK.
*
* @param string $component
* @param string $paymentarea
* @param int $itemid
* @return string[]
*/
public static function execute(string $component, string $paymentarea, int $itemid): array {
self::validate_parameters(self::execute_parameters(), [
'component' => $component,
'paymentarea' => $paymentarea,
'itemid' => $itemid,
]);
$config = helper::get_gateway_configuration($component, $paymentarea, $itemid, 'paypal');
$payable = helper::get_payable($component, $paymentarea, $itemid);
$surcharge = helper::get_gateway_surcharge('paypal');
return [
'clientid' => $config['clientid'],
'brandname' => $config['brandname'],
'cost' => helper::get_rounded_cost($payable->get_amount(), $payable->get_currency(), $surcharge),
'currency' => $payable->get_currency(),
];
}
/**
* Returns description of method result value.
*
* @return external_single_structure
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([
'clientid' => new external_value(PARAM_TEXT, 'PayPal client ID'),
'brandname' => new external_value(PARAM_TEXT, 'Brand name'),
'cost' => new external_value(PARAM_FLOAT, 'Cost with gateway surcharge'),
'currency' => new external_value(PARAM_TEXT, 'Currency'),
]);
}
}
@@ -0,0 +1,149 @@
<?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/>.
/**
* This class contains a list of webservice functions related to the PayPal payment gateway.
*
* @package paygw_paypal
* @copyright 2020 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
declare(strict_types=1);
namespace paygw_paypal\external;
use core_external\external_api;
use core_external\external_value;
use core_external\external_function_parameters;
use core_payment\helper;
use core_payment\helper as payment_helper;
use paygw_paypal\paypal_helper;
class transaction_complete extends external_api {
/**
* Returns description of method parameters.
*
* @return external_function_parameters
*/
public static function execute_parameters() {
return new external_function_parameters([
'component' => new external_value(PARAM_COMPONENT, 'The component name'),
'paymentarea' => new external_value(PARAM_AREA, 'Payment area in the component'),
'itemid' => new external_value(PARAM_INT, 'The item id in the context of the component area'),
'orderid' => new external_value(PARAM_TEXT, 'The order id coming back from PayPal'),
]);
}
/**
* Perform what needs to be done when a transaction is reported to be complete.
* This function does not take cost as a parameter as we cannot rely on any provided value.
*
* @param string $component Name of the component that the itemid belongs to
* @param string $paymentarea
* @param int $itemid An internal identifier that is used by the component
* @param string $orderid PayPal order ID
* @return array
*/
public static function execute(string $component, string $paymentarea, int $itemid, string $orderid): array {
global $USER, $DB;
self::validate_parameters(self::execute_parameters(), [
'component' => $component,
'paymentarea' => $paymentarea,
'itemid' => $itemid,
'orderid' => $orderid,
]);
$config = (object)helper::get_gateway_configuration($component, $paymentarea, $itemid, 'paypal');
$sandbox = $config->environment == 'sandbox';
$payable = payment_helper::get_payable($component, $paymentarea, $itemid);
$currency = $payable->get_currency();
// Add surcharge if there is any.
$surcharge = helper::get_gateway_surcharge('paypal');
$amount = helper::get_rounded_cost($payable->get_amount(), $currency, $surcharge);
$paypalhelper = new paypal_helper($config->clientid, $config->secret, $sandbox);
$orderdetails = $paypalhelper->get_order_details($orderid);
$success = false;
$message = '';
if ($orderdetails) {
if ($orderdetails['status'] == paypal_helper::ORDER_STATUS_APPROVED &&
$orderdetails['intent'] == paypal_helper::ORDER_INTENT_CAPTURE) {
$item = $orderdetails['purchase_units'][0];
if ($item['amount']['value'] == $amount && $item['amount']['currency_code'] == $currency) {
$capture = $paypalhelper->capture_order($orderid);
if ($capture && $capture['status'] == paypal_helper::CAPTURE_STATUS_COMPLETED) {
$success = true;
// Everything is correct. Let's give them what they paid for.
try {
$paymentid = payment_helper::save_payment($payable->get_account_id(), $component, $paymentarea,
$itemid, (int) $USER->id, $amount, $currency, 'paypal');
// Store PayPal extra information.
$record = new \stdClass();
$record->paymentid = $paymentid;
$record->pp_orderid = $orderid;
$DB->insert_record('paygw_paypal', $record);
payment_helper::deliver_order($component, $paymentarea, $itemid, $paymentid, (int) $USER->id);
} catch (\Exception $e) {
debugging('Exception while trying to process payment: ' . $e->getMessage(), DEBUG_DEVELOPER);
$success = false;
$message = get_string('internalerror', 'paygw_paypal');
}
} else {
$success = false;
$message = get_string('paymentnotcleared', 'paygw_paypal');
}
} else {
$success = false;
$message = get_string('amountmismatch', 'paygw_paypal');
}
} else {
$success = false;
$message = get_string('paymentnotcleared', 'paygw_paypal');
}
} else {
// Could not capture authorization!
$success = false;
$message = get_string('cannotfetchorderdatails', 'paygw_paypal');
}
return [
'success' => $success,
'message' => $message,
];
}
/**
* Returns description of method result value.
*
* @return external_function_parameters
*/
public static function execute_returns() {
return new external_function_parameters([
'success' => new external_value(PARAM_BOOL, 'Whether everything was successful or not.'),
'message' => new external_value(PARAM_RAW, 'Message (usually the error message).'),
]);
}
}
@@ -0,0 +1,89 @@
<?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/>.
/**
* Contains class for PayPal payment gateway.
*
* @package paygw_paypal
* @copyright 2019 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace paygw_paypal;
/**
* The gateway class for PayPal payment gateway.
*
* @copyright 2019 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class gateway extends \core_payment\gateway {
public static function get_supported_currencies(): array {
// See https://developer.paypal.com/docs/api/reference/currency-codes/,
// 3-character ISO-4217: https://en.wikipedia.org/wiki/ISO_4217#Active_codes.
return [
'AUD', 'BRL', 'CAD', 'CHF', 'CZK', 'DKK', 'EUR', 'GBP', 'HKD', 'HUF', 'ILS', 'INR', 'JPY',
'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', 'paygw_paypal'));
$mform->setType('brandname', PARAM_TEXT);
$mform->addHelpButton('brandname', 'brandname', 'paygw_paypal');
$mform->addElement('text', 'clientid', get_string('clientid', 'paygw_paypal'));
$mform->setType('clientid', PARAM_TEXT);
$mform->addHelpButton('clientid', 'clientid', 'paygw_paypal');
$mform->addElement('text', 'secret', get_string('secret', 'paygw_paypal'));
$mform->setType('secret', PARAM_TEXT);
$mform->addHelpButton('secret', 'secret', 'paygw_paypal');
$options = [
'live' => get_string('live', 'paygw_paypal'),
'sandbox' => get_string('sandbox', 'paygw_paypal'),
];
$mform->addElement('select', 'environment', get_string('environment', 'paygw_paypal'), $options);
$mform->addHelpButton('environment', 'environment', 'paygw_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');
}
}
}
@@ -0,0 +1,196 @@
<?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/>.
/**
* Contains helper class to work with PayPal REST API.
*
* @package core_payment
* @copyright 2020 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace paygw_paypal;
use curl;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/filelib.php');
class paypal_helper {
/**
* @var string The payment was authorized or the authorized payment was captured for the order.
*/
public const CAPTURE_STATUS_COMPLETED = 'COMPLETED';
/**
* @var string The merchant intends to capture payment immediately after the customer makes a payment.
*/
public const ORDER_INTENT_CAPTURE = 'CAPTURE';
/**
* @var string The customer approved the payment.
*/
public const ORDER_STATUS_APPROVED = 'APPROVED';
/**
* @var string The base API URL
*/
private $baseurl;
/**
* @var string Client ID
*/
private $clientid;
/**
* @var string PayPal App secret
*/
private $secret;
/**
* @var string The oath bearer token
*/
private $token;
/**
* helper constructor.
*
* @param string $clientid The client id.
* @param string $secret PayPal secret.
* @param bool $sandbox Whether we are working with the sandbox environment or not.
*/
public function __construct(string $clientid, string $secret, bool $sandbox) {
$this->clientid = $clientid;
$this->secret = $secret;
$this->baseurl = $sandbox ? 'https://api.sandbox.paypal.com' : 'https://api.paypal.com';
$this->token = $this->get_token();
}
/**
* Captures an authorized payment, by ID.
*
* @param string $authorizationid The PayPal-generated ID for the authorized payment to capture.
* @param float $amount The amount to capture.
* @param string $currency The currency code for the amount.
* @param bool $final Indicates whether this is the final captures against the authorized payment.
* @return array|null Formatted API response.
*/
public function capture_authorization(string $authorizationid, float $amount, string $currency, bool $final = true): ?array {
$location = "{$this->baseurl}/v2/payments/authorizations/{$authorizationid}/capture";
$options = [
'CURLOPT_RETURNTRANSFER' => true,
'CURLOPT_TIMEOUT' => 30,
'CURLOPT_HTTP_VERSION' => CURL_HTTP_VERSION_1_1,
'CURLOPT_SSLVERSION' => CURL_SSLVERSION_TLSv1_2,
'CURLOPT_HTTPHEADER' => [
'Content-Type: application/json',
"Authorization: Bearer {$this->token}",
],
];
$command = [
'amount' => [
'value' => (string) $amount,
'currency_code' => $currency,
],
'final_capture' => $final,
];
$command = json_encode($command);
$curl = new curl();
$result = $curl->post($location, $command, $options);
return json_decode($result, true);
}
/**
* Captures order details from PayPal.
*
* @param string $orderid The order we want to capture.
* @return array|null Formatted API response.
*/
public function capture_order(string $orderid): ?array {
$location = "{$this->baseurl}/v2/checkout/orders/{$orderid}/capture";
$options = [
'CURLOPT_RETURNTRANSFER' => true,
'CURLOPT_TIMEOUT' => 30,
'CURLOPT_HTTP_VERSION' => CURL_HTTP_VERSION_1_1,
'CURLOPT_SSLVERSION' => CURL_SSLVERSION_TLSv1_2,
'CURLOPT_HTTPHEADER' => [
'Content-Type: application/json',
"Authorization: Bearer {$this->token}",
],
];
$command = '{}';
$curl = new curl();
$result = $curl->post($location, $command, $options);
return json_decode($result, true);
}
public function get_order_details(string $orderid): ?array {
$location = "{$this->baseurl}/v2/checkout/orders/{$orderid}";
$options = [
'CURLOPT_RETURNTRANSFER' => true,
'CURLOPT_TIMEOUT' => 30,
'CURLOPT_HTTP_VERSION' => CURL_HTTP_VERSION_1_1,
'CURLOPT_SSLVERSION' => CURL_SSLVERSION_TLSv1_2,
'CURLOPT_HTTPHEADER' => [
'Content-Type: application/json',
"Authorization: Bearer {$this->token}",
],
];
$curl = new curl();
$result = $curl->get($location, [], $options);
return json_decode($result, true);
}
/**
* Request for PayPal REST oath bearer token.
*
* @return string
*/
private function get_token(): string {
$location = "{$this->baseurl}/v1/oauth2/token";
$options = [
'CURLOPT_RETURNTRANSFER' => true,
'CURLOPT_TIMEOUT' => 30,
'CURLOPT_HTTP_VERSION' => CURL_HTTP_VERSION_1_1,
'CURLOPT_SSLVERSION' => CURL_SSLVERSION_TLSv1_2,
'CURLOPT_USERPWD' => "{$this->clientid}:{$this->secret}",
];
$command = 'grant_type=client_credentials';
$curl = new curl();
$result = $curl->post($location, $command, $options);
$result = json_decode($result, true);
return $result['access_token'];
}
}
@@ -0,0 +1,82 @@
<?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/>.
/**
* Privacy Subsystem implementation for paygw_paypal.
*
* @package paygw_paypal
* @category privacy
* @copyright 2020 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace paygw_paypal\privacy;
use core_payment\privacy\paygw_provider;
use core_privacy\local\request\writer;
/**
* Privacy Subsystem implementation for paygw_paypal.
*
* @copyright 2020 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider, paygw_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason(): string {
return 'privacy:metadata';
}
/**
* Export all user data for the specified payment record, and the given context.
*
* @param \context $context Context
* @param array $subcontext The location within the current context that the payment data belongs
* @param \stdClass $payment The payment record
*/
public static function export_payment_data(\context $context, array $subcontext, \stdClass $payment) {
global $DB;
$subcontext[] = get_string('gatewayname', 'paygw_paypal');
$record = $DB->get_record('paygw_paypal', ['paymentid' => $payment->id]);
$data = (object) [
'orderid' => $record->pp_orderid,
];
writer::with_context($context)->export_data(
$subcontext,
$data
);
}
/**
* Delete all user data related to the given payments.
*
* @param string $paymentsql SQL query that selects payment.id field for the payments
* @param array $paymentparams Array of parameters for $paymentsql
*/
public static function delete_data_for_payment_sql(string $paymentsql, array $paymentparams) {
global $DB;
$DB->delete_records_select('paygw_paypal', "paymentid IN ({$paymentsql})", $paymentparams);
}
}
+31
View File
@@ -0,0 +1,31 @@
<?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/>.
/**
* paygw_paypal installer script.
*
* @package paygw_paypal
* @copyright 2020 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
function xmldb_paygw_paypal_install() {
global $CFG;
// Enable the Paypal payment gateway on installation. It still needs to be configured and enabled for accounts.
$order = (!empty($CFG->paygw_plugins_sortorder)) ? explode(',', $CFG->paygw_plugins_sortorder) : [];
set_config('paygw_plugins_sortorder', join(',', array_merge($order, ['paypal'])));
}
+19
View File
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="payment/gateway/paypal/db" VERSION="20201216" COMMENT="XMLDB file for PayPal payment gateway plugin"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../../lib/xmldb/xmldb.xsd"
>
<TABLES>
<TABLE NAME="paygw_paypal" COMMENT="Stores PayPal related information">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="paymentid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="pp_orderid" TYPE="char" LENGTH="255" NOTNULL="true" DEFAULT="The ID of the order in PayPal" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="paymentid" TYPE="foreign-unique" FIELDS="paymentid" REFTABLE="payments" REFFIELDS="id"/>
</KEYS>
</TABLE>
</TABLES>
</XMLDB>
+43
View File
@@ -0,0 +1,43 @@
<?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/>.
/**
* External functions and service definitions for the PayPal payment gateway plugin.
*
* @package paygw_paypal
* @copyright 2020 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$functions = [
'paygw_paypal_get_config_for_js' => [
'classname' => 'paygw_paypal\external\get_config_for_js',
'classpath' => '',
'description' => 'Returns the configuration settings to be used in js',
'type' => 'read',
'ajax' => true,
],
'paygw_paypal_create_transaction_complete' => [
'classname' => 'paygw_paypal\external\transaction_complete',
'classpath' => '',
'description' => 'Takes care of what needs to be done when a PayPal transaction comes back as complete.',
'type' => 'write',
'ajax' => true,
'loginrequired' => false,
],
];
+58
View File
@@ -0,0 +1,58 @@
<?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/>.
/**
* Upgrade script for paygw_paypal.
*
* @package paygw_paypal
* @copyright 2021 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Upgrade the plugin.
*
* @param int $oldversion the version we are upgrading from
* @return bool always true
*/
function xmldb_paygw_paypal_upgrade(int $oldversion): bool {
global $DB;
$dbman = $DB->get_manager();
if ($oldversion < 2021052501) {
// Define key paymentid (foreign-unique) to be added to paygw_paypal.
$table = new xmldb_table('paygw_paypal');
$key = new xmldb_key('paymentid', XMLDB_KEY_FOREIGN_UNIQUE, ['paymentid'], 'payments', ['id']);
// Launch add key paymentid.
$dbman->add_key($table, $key);
// Paypal savepoint reached.
upgrade_plugin_savepoint(true, 2021052501, 'paygw', 'paypal');
}
// Automatically generated Moodle v4.2.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.3.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.4.0 release upgrade line.
// Put any upgrade step following this.
return true;
}
@@ -0,0 +1,45 @@
<?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/>.
/**
* Strings for component 'paygw_paypal', language 'en'
*
* @package paygw_paypal
* @copyright 2019 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$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_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_help'] = 'The client ID that PayPal generated for your application.';
$string['environment'] = 'Environment';
$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.';
$string['live'] = 'Live';
$string['paymentnotcleared'] = 'payment not cleared by PayPal.';
$string['pluginname'] = 'PayPal';
$string['pluginname_desc'] = 'The PayPal plugin allows you to receive payments via PayPal.';
$string['privacy:metadata'] = 'The PayPal plugin does not store any personal data.';
$string['repeatedorder'] = 'This order has already been processed earlier.';
$string['sandbox'] = 'Sandbox';
$string['secret'] = 'Secret';
$string['secret_help'] = 'The secret that PayPal generated for your application.';
+330
View File
@@ -0,0 +1,330 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 320 125" style="enable-background:new 0 0 320 125;" xml:space="preserve" preserveAspectRatio="xMinYMid meet">
<style type="text/css">
.st0{fill:url(#SVGID_1_);}
.st1{fill:#222562;}
.st2{fill-rule:evenodd;clip-rule:evenodd;fill:#FF8000;}
.st3{fill-rule:evenodd;clip-rule:evenodd;fill:#FF0000;}
.st4{fill-rule:evenodd;clip-rule:evenodd;fill:#FFFFFF;stroke:#FFFFFF;stroke-width:0.1212;stroke-linecap:square;stroke-miterlimit:10;}
.st5{fill-rule:evenodd;clip-rule:evenodd;fill:#FFFFFF;}
.st6{fill:none;stroke:#FFFFFF;stroke-width:0.1212;stroke-linecap:square;stroke-miterlimit:10;}
.st7{fill-rule:evenodd;clip-rule:evenodd;fill:#FFFFFF;stroke:#999999;}
.st8{fill-rule:evenodd;clip-rule:evenodd;fill:#E6E7E8;stroke:#000000;}
.st9{fill:#263F6B;stroke:#263F6B;stroke-width:0.25;}
.st10{fill-rule:evenodd;clip-rule:evenodd;fill:#009DE2;}
.st11{fill-rule:evenodd;clip-rule:evenodd;fill:#113984;}
.st12{fill-rule:evenodd;clip-rule:evenodd;fill:#172C70;}
.st13{fill:#00589F;}
.st14{fill:#F9A51A;}
.st15{fill:#F79739;stroke:#F79739;stroke-width:6;}
.st16{fill:none;stroke:#F79739;stroke-width:2;}
</style>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="159.3737" y1="31.3265" x2="159.3737" y2="121.832">
<stop offset="0" style="stop-color:#FFFFFF"/>
<stop offset="0.2032" style="stop-color:#FFFEFC"/>
<stop offset="0.35" style="stop-color:#FFFAF3"/>
<stop offset="0.479" style="stop-color:#FEF4E3"/>
<stop offset="0.5978" style="stop-color:#FEEBCD"/>
<stop offset="0.7097" style="stop-color:#FDDFB0"/>
<stop offset="0.8164" style="stop-color:#FDD18C"/>
<stop offset="0.917" style="stop-color:#FCC063"/>
<stop offset="1" style="stop-color:#FBB03B"/>
</linearGradient>
<rect x="4.6" y="31.3" class="st0" width="309.5" height="90.5"/>
<g>
<rect x="120.8" y="62" class="st1" width="78.6" height="47.7"/>
<g>
<polygon class="st2" points="174.1,64.3 175.2,64.3 176.3,64.4 178.4,64.8 180.5,65.3 182.5,66 184.4,66.9 186.2,68 187.8,69.3
189.4,70.6 190.8,72.2 192,73.8 193.1,75.6 194,77.5 194.7,79.4 195.3,81.5 195.6,83.7 195.7,85.8 195.7,86.9 195.6,88
195.3,90.1 194.7,92.2 194,94.2 193.1,96.1 192,97.8 190.8,99.5 189.4,101 187.8,102.4 186.2,103.7 184.4,104.7 182.5,105.7
180.5,106.4 178.4,106.9 176.3,107.2 174.1,107.3 173,107.3 171.9,107.2 169.8,106.9 167.8,106.4 165.8,105.7 163.9,104.8
162.2,103.7 160.5,102.4 159,101 157.6,99.5 156.4,97.9 155.4,96.1 154.5,94.2 153.8,92.3 153.2,90.2 152.9,88.1 152.8,85.9
152.8,84.8 152.9,83.7 153.2,81.6 153.8,79.5 154.5,77.5 155.4,75.7 156.4,73.9 157.6,72.2 159,70.7 160.5,69.3 162.2,68
163.9,66.9 165.8,66 167.8,65.3 169.8,64.8 171.9,64.4 174.1,64.3 "/>
<polygon class="st3" points="160.8,100.8 160.7,100.9 160.6,101 160.5,101.1 160.4,101.2 160.3,101.3 160.2,101.3 160.1,101.4
160,101.5 159.9,101.6 159.9,101.7 159.8,101.8 159.7,101.8 159.6,101.9 159.5,102 159.4,102.1 159.3,102.2 158.9,102.5
158.5,102.7 157.8,103.3 157,103.8 156.3,104.2 155.4,104.7 154.6,105.1 153.8,105.5 152.9,105.8 152.1,106.1 151.2,106.3
150.3,106.5 149.4,106.7 148.5,106.9 147.6,107 146.7,107 145.8,107.1 144.7,107 143.6,106.9 141.5,106.6 139.5,106.1
137.5,105.4 135.7,104.5 133.9,103.4 132.3,102.2 130.8,100.8 129.4,99.3 128.2,97.7 127.1,95.9 126.2,94.1 125.5,92.1 125,90.1
124.6,88 124.5,85.9 124.5,84.8 124.6,83.7 125,81.6 125.5,79.6 126.3,77.7 127.2,75.8 128.3,74.1 129.5,72.4 130.9,70.9
132.4,69.6 134.1,68.4 135.7,67.3 137.5,66.4 139.4,65.7 141.3,65.2 143.2,64.9 145.1,64.8 145.7,64.8 146.2,64.8 147.3,64.9
148.3,65 149.3,65.2 150.3,65.4 151.2,65.6 152.1,65.8 153,66.1 153.9,66.5 154.7,66.8 155.5,67.2 156.2,67.6 157,68.1
157.7,68.5 158.4,69 159,69.5 159.7,70.2 160.5,70.9 161.3,71.8 158,71.8 157.1,72.9 162.5,72.9 162.7,73.2 163.3,74 163.5,74.3
156.2,74.3 155.5,75.4 164.2,75.4 164.4,75.8 165,76.8 154.8,76.8 154.3,78 165.4,78 165.7,78.6 166,79.4 153.8,79.4 153.5,80.5
166.3,80.5 166.6,81.6 166.7,81.9 153.2,81.9 153,83 166.8,83 166.9,83.7 167,84.8 167,85.9 167,87 163.5,87 163.5,88.1
166.9,88.1 166.8,89.1 166.7,89.5 163.2,89.5 163.1,90.7 166.5,90.7 166.4,91.1 166.1,92.1 153.7,92.1 154.1,93.2 165.7,93.2
165.4,94.1 165.1,94.6 154.7,94.6 155.2,95.7 164.5,95.7 164.4,95.9 163.9,96.8 163.7,97.1 156,97.1 156.7,98.3 163,98.3
162.8,98.5 162.1,99.3 161.8,99.7 157.7,99.7 158.8,100.8 160.8,100.8 "/>
<polygon class="st2" points="154.7,87 152,87 152.1,88 154.7,88 154.7,87 "/>
<polygon class="st4" points="173.1,83.9 173.1,83.9 172.9,83.8 172.8,83.8 172.6,83.7 172.5,83.7 172.4,83.6 172.2,83.6
172.1,83.6 171.9,83.5 171.8,83.5 171.7,83.5 171.5,83.5 171.3,83.5 171,83.5 170.9,83.5 170.7,83.5 170.6,83.5 170.3,83.6
170.1,83.7 169.8,83.8 169.6,83.9 169.4,84.1 169.2,84.3 169,84.5 168.9,84.7 168.7,85 168.6,85.2 168.5,85.5 168.4,85.8
168.3,86.1 168.2,86.4 168.2,86.6 168.1,86.8 168.1,86.9 168.1,87.2 168.2,87.5 168.2,87.8 168.3,88 168.4,88.2 168.6,88.5
168.7,88.7 168.9,88.8 169.1,89 169.3,89.1 169.5,89.3 169.7,89.3 170,89.4 170.2,89.4 170.5,89.4 170.6,89.4 170.7,89.4
170.9,89.4 171,89.4 171.2,89.4 171.3,89.3 171.5,89.3 171.6,89.3 171.7,89.3 171.9,89.2 172,89.2 172.1,89.1 172.1,89.1
172.2,89.1 172.3,89 172.4,89 172.5,89.1 172.4,89.2 172.4,89.3 172.4,89.4 172.3,89.4 172.3,89.5 172.3,89.6 172.2,89.7
172.2,89.8 172.2,89.9 172.1,90 172.1,90.1 172.1,90.2 172.1,90.3 172.1,90.4 172.1,90.5 172.1,90.7 172.1,90.8 172.1,90.9
172.1,90.9 172,91 171.9,91 171.8,91 171.7,91.1 171.6,91.1 171.4,91.1 171.3,91.1 171.2,91.2 171.1,91.2 171,91.2 170.9,91.2
170.8,91.2 170.6,91.2 170.1,91.2 169.8,91.2 169.3,91.1 168.9,91 168.4,90.9 168,90.7 167.7,90.5 167.4,90.3 167.1,90
166.8,89.7 166.6,89.4 166.5,89 166.3,88.6 166.2,88.2 166.2,87.7 166.2,87.3 166.2,86.8 166.2,86.4 166.2,86.1 166.3,85.5
166.5,85 166.7,84.5 166.9,84 167.2,83.6 167.4,83.2 167.8,82.9 168.1,82.6 168.5,82.3 168.9,82.1 169.3,81.9 169.7,81.8
170.2,81.7 170.6,81.6 171.1,81.6 171.2,81.7 171.3,81.7 171.5,81.7 171.7,81.7 171.8,81.7 172,81.7 172.2,81.8 172.3,81.8
172.5,81.8 172.6,81.8 172.8,81.9 172.9,81.9 173,81.9 173.2,82 173.3,82 173.4,82.1 173.5,82.1 173.5,82.2 173.5,82.3
173.5,82.4 173.5,82.5 173.5,82.6 173.5,82.6 173.4,82.7 173.4,82.8 173.4,82.8 173.4,82.9 173.4,83 173.4,83 173.4,83.2
173.3,83.3 173.3,83.3 173.3,83.4 173.3,83.5 173.3,83.6 173.3,83.7 173.3,83.7 173.3,83.8 173.3,83.9 173.2,83.9 173.1,83.9
"/>
<polygon class="st4" points="162.3,83.5 164.2,83.5 164.1,84.4 164.1,84.5 164.1,84.6 164.2,84.6 164.3,84.6 164.3,84.5
164.4,84.4 164.5,84.3 164.6,84.2 164.7,84 164.8,83.9 165,83.8 165.1,83.8 165.2,83.7 165.4,83.6 165.5,83.6 165.7,83.6
165.8,83.5 166,83.5 166.1,83.5 166.3,83.5 166.5,83.5 165.9,84.7 165.7,85.6 165.6,85.5 165.5,85.5 165.4,85.5 165.2,85.5
165.1,85.5 164.9,85.6 164.8,85.6 164.6,85.7 164.5,85.8 164.4,85.9 164.3,86 164.2,86.1 164.1,86.3 164,86.5 163.9,86.7
163.8,86.9 163.8,87.1 163.2,90.1 163.1,91.1 160.9,91.1 161.2,90 162.2,84.2 162.3,83.5 "/>
<polygon class="st4" points="144.2,91 144.3,90.1 144.4,89.4 144.4,89.4 144.5,89.4 144.6,89.5 144.8,89.5 145,89.5 145.1,89.6
145.4,89.6 145.5,89.6 145.8,89.6 145.9,89.6 146.1,89.6 146.3,89.6 146.5,89.6 147.1,89.6 147.1,89.6 147.3,89.6 147.4,89.6
147.5,89.5 147.6,89.4 147.7,89.3 147.8,89.2 147.8,89.1 147.9,89 147.9,88.8 147.8,88.7 147.8,88.6 147.7,88.5 147.6,88.4
147.4,88.2 147.2,88.1 147.1,88.1 147,88 146.8,88 146.6,87.9 146.4,87.8 146.2,87.7 146,87.6 145.8,87.5 145.6,87.3 145.5,87.2
145.3,87 145.2,86.9 145.1,86.7 145,86.5 145,86.2 145,86 145,85.7 145,85.6 145,85.5 145,85.3 145.1,85 145.2,84.9 145.3,84.7
145.4,84.5 145.5,84.3 145.6,84.1 145.8,84 146,83.8 146.2,83.7 146.5,83.6 146.7,83.5 147,83.5 147.3,83.4 147.7,83.4 148,83.4
148.2,83.4 148.4,83.4 148.6,83.4 148.7,83.4 148.9,83.4 149,83.5 149.2,83.5 149.4,83.5 149.5,83.5 149.7,83.5 149.8,83.5
150,83.5 150.2,83.5 150.3,83.5 150,84.6 149.9,85.1 149.8,85.1 149.8,85.1 149.6,85 149.5,85 149.3,85 149.2,85 149,85 148.8,85
148.7,85 148.5,84.9 147.9,84.9 147.8,85 147.7,85 147.6,85 147.6,85 147.4,85.1 147.4,85.1 147.3,85.2 147.2,85.3 147.2,85.4
147.1,85.4 147.1,85.5 147.1,85.6 147.1,85.7 147.2,85.8 147.2,85.9 147.3,86 147.4,86.1 147.5,86.2 147.7,86.3 147.8,86.4
147.9,86.4 148.1,86.5 148.3,86.6 148.5,86.7 148.7,86.8 148.9,86.9 149.1,87 149.3,87.2 149.4,87.3 149.5,87.5 149.7,87.7
149.8,87.9 149.8,88.1 149.9,88.4 149.9,88.7 149.8,89 149.8,89.2 149.8,89.3 149.7,89.6 149.6,89.9 149.5,90.1 149.4,90.3
149.3,90.4 149.1,90.6 148.9,90.7 148.7,90.8 148.5,90.9 148.3,91 148,91 147.8,91.1 147.5,91.1 147.2,91.1 146.9,91.2
146.8,91.2 146.6,91.2 146.5,91.2 146.3,91.2 146,91.2 145.8,91.2 145.6,91.2 145.5,91.2 145.3,91.2 145.1,91.1 144.9,91.1
144.7,91.1 144.6,91.1 144.4,91 144.2,91 "/>
<polygon class="st4" points="128.4,81.7 131.7,81.7 131.8,87.8 132,87.8 134.5,81.7 137.9,81.7 137.6,82.9 136.3,90.1 136.3,91.1
134.2,91.1 134.5,90 135.6,83.5 135.4,83.5 132.2,91.1 130.5,91.1 129.9,83.5 129.8,83.5 128.6,90 128.5,91.1 126.6,91.1
126.9,90.1 128.3,82.5 128.4,81.7 "/>
<polygon class="st5" points="191,81.7 190.6,84.3 190.5,84.4 190.4,84.5 190.3,84.4 190.3,84.3 190.2,84.2 190.1,84.1 190,84
189.9,83.9 189.8,83.9 189.7,83.8 189.7,83.8 189.6,83.8 189.5,83.7 189.4,83.7 189.3,83.6 189.2,83.6 189.2,83.6 189.1,83.5
189,83.5 188.9,83.5 188.9,83.5 188.8,83.5 188.6,83.4 188.5,83.4 188.2,83.4 187.8,83.5 187.5,83.5 187.2,83.6 186.9,83.8
186.6,83.9 186.3,84.1 186.1,84.3 185.8,84.6 185.6,84.9 185.4,85.1 185.2,85.5 185,85.8 184.9,86.2 184.8,86.6 184.8,86.9
184.7,87.1 184.7,87.6 184.6,88 184.7,88.4 184.7,88.8 184.8,89.1 184.9,89.5 185,89.8 185.2,90 185.4,90.3 185.6,90.5
185.8,90.7 186.1,90.8 186.4,91 186.7,91.1 187,91.1 187.1,91.2 187.3,91.2 187.5,91.2 187.7,91.1 187.9,91.1 188.1,91 188.2,91
188.4,90.9 188.6,90.8 188.7,90.8 188.9,90.7 189,90.6 189.1,90.5 189.2,90.4 189.3,90.3 189.4,90.2 189.4,90.2 189.5,90.3
189.4,91.1 191.5,91.1 191.6,90.3 192.5,85.1 188.5,85.1 188.8,85.1 188.9,85.2 189,85.2 189.3,85.3 189.5,85.4 189.6,85.5
189.7,85.6 189.8,85.8 189.9,85.9 190,86.1 190,86.3 190,86.5 190,86.7 190,86.9 190,87.1 189.9,87.3 189.9,87.5 189.9,87.7
189.8,87.8 189.8,87.9 189.7,88.1 189.6,88.3 189.5,88.5 189.5,88.7 189.3,88.8 189.2,88.9 189.1,89.1 189,89.2 188.9,89.3
188.7,89.3 188.6,89.4 188.4,89.4 188.2,89.4 188.1,89.5 187.8,89.5 187.7,89.4 187.6,89.4 187.4,89.3 187.3,89.3 187.2,89.2
187.1,89.1 187,88.9 186.9,88.8 186.8,88.6 186.8,88.5 186.7,88.3 186.7,88.1 186.7,87.9 186.7,87.7 186.7,87.4 186.7,87.2
186.8,87.1 186.8,87 186.8,86.8 186.9,86.6 187,86.4 187.1,86.2 187.2,86.1 187.3,85.9 187.4,85.7 187.5,85.6 187.7,85.5
187.8,85.4 188,85.3 188.2,85.2 188.4,85.2 188.5,85.1 192.5,85.1 192.9,82.9 193.2,81.7 191,81.7 "/>
<polygon class="st5" points="181.2,83.5 183.2,83.5 183,84.4 183,84.5 183,84.6 183.1,84.6 183.2,84.6 183.2,84.5 183.3,84.4
183.4,84.3 183.5,84.2 183.7,84.1 183.8,84 183.9,83.9 184,83.8 184.2,83.8 184.3,83.7 184.5,83.6 184.6,83.6 184.8,83.5
184.9,83.5 185.1,83.5 185.3,83.5 185.4,83.5 184.9,84.7 184.7,85.6 184.6,85.5 184.6,85.5 184.4,85.5 184.2,85.5 184.1,85.5
183.9,85.6 183.8,85.6 183.6,85.7 183.5,85.8 183.4,85.9 183.2,86 183.1,86.1 183,86.3 182.9,86.5 182.8,86.7 182.8,86.9
182.7,87.1 182.2,90.1 182,91.1 179.9,91.1 180.1,90 181.2,84.2 181.2,83.5 "/>
<polyline class="st6" points="191,81.7 190.6,84.3 190.5,84.4 190.4,84.5 190.3,84.4 190.3,84.3 190.2,84.2 190.1,84.1 190,84
189.9,83.9 189.8,83.9 189.7,83.8 189.7,83.8 189.6,83.8 189.5,83.7 189.4,83.7 189.3,83.6 189.2,83.6 189.2,83.6 189.1,83.5
189,83.5 188.9,83.5 188.9,83.5 188.8,83.5 188.6,83.4 188.5,83.4 188.2,83.4 187.8,83.5 187.5,83.5 187.2,83.6 186.9,83.8
186.6,83.9 186.3,84.1 186.1,84.3 185.8,84.6 185.6,84.9 185.4,85.1 185.2,85.5 185,85.8 184.9,86.2 184.8,86.6 184.8,86.9
184.7,87.1 184.7,87.6 184.6,88 184.7,88.4 184.7,88.8 184.8,89.1 184.9,89.5 185,89.8 185.2,90 185.4,90.3 185.6,90.5
185.8,90.7 186.1,90.8 186.4,91 186.7,91.1 187,91.1 187.1,91.2 187.3,91.2 "/>
<polyline class="st6" points="187.3,91.2 187.5,91.2 187.7,91.1 187.9,91.1 188.1,91 188.2,91 188.4,90.9 188.6,90.8 188.7,90.8
188.9,90.7 189,90.6 189.1,90.5 189.2,90.4 189.3,90.3 189.4,90.2 189.4,90.2 189.5,90.3 189.4,91.1 191.5,91.1 191.6,90.3
192.9,82.9 193.2,81.7 191,81.7 "/>
<polyline class="st6" points="181.2,83.5 183.2,83.5 183,84.4 183,84.5 183,84.6 183.1,84.6 183.2,84.6 183.2,84.5 183.3,84.4
183.4,84.3 183.5,84.2 183.7,84.1 183.8,84 183.9,83.9 184,83.8 184.2,83.8 184.3,83.7 184.5,83.6 184.6,83.6 184.8,83.5
184.9,83.5 185.1,83.5 185.3,83.5 185.4,83.5 184.9,84.7 184.7,85.6 184.6,85.5 184.6,85.5 184.4,85.5 184.2,85.5 184.1,85.5
183.9,85.6 183.8,85.6 183.6,85.7 183.5,85.8 183.4,85.9 183.2,86 183.1,86.1 183,86.3 182.9,86.5 182.8,86.7 182.8,86.9
182.7,87.1 182.2,90.1 182,91.1 179.9,91.1 180.1,90 181.2,84.2 181.2,83.5 "/>
<polyline class="st6" points="189.9,87.7 189.8,87.8 189.8,87.9 189.7,88.1 189.6,88.3 189.5,88.5 189.5,88.7 189.3,88.8
189.2,88.9 189.1,89.1 189,89.2 188.9,89.3 188.7,89.3 188.6,89.4 188.4,89.4 188.2,89.4 188.1,89.5 187.8,89.5 187.7,89.4
187.6,89.4 187.4,89.3 187.3,89.3 187.2,89.2 187.1,89.1 187,88.9 186.9,88.8 186.8,88.6 186.8,88.5 186.7,88.3 186.7,88.1
186.7,87.9 186.7,87.7 186.7,87.4 186.7,87.2 186.8,87.1 186.8,87 186.8,86.8 186.9,86.6 187,86.4 187.1,86.2 187.2,86.1
187.3,85.9 187.4,85.7 187.5,85.6 187.7,85.5 187.8,85.4 188,85.3 188.2,85.2 188.4,85.2 188.5,85.1 188.8,85.1 188.9,85.2
189,85.2 189.3,85.3 189.5,85.4 189.6,85.5 189.7,85.6 189.8,85.8 189.9,85.9 190,86.1 "/>
<polyline class="st6" points="190,86.1 190,86.3 190,86.5 190,86.7 190,86.9 190,87.1 189.9,87.3 189.9,87.5 189.9,87.7 "/>
<polygon class="st5" points="141.5,90.3 141.5,90.3 141.3,90.5 141.2,90.6 141.1,90.6 141,90.7 140.9,90.8 140.8,90.9 140.7,90.9
140.5,91 140.4,91 140.2,91.1 140.1,91.1 139.9,91.2 139.7,91.2 139.6,91.2 139.4,91.2 139.2,91.2 139.1,91.2 138.8,91.2
138.5,91.1 138.3,91 138.1,90.8 137.9,90.7 137.7,90.5 137.6,90.4 137.5,90.2 137.4,90 137.3,89.8 137.3,89.5 137.2,89.3
137.2,89.1 137.2,88.9 137.3,88.7 137.3,88.5 137.3,88.3 137.5,88 137.6,87.7 137.8,87.5 138,87.2 138.2,87 138.4,86.8
138.8,86.7 139.1,86.5 139.4,86.4 139.8,86.3 140.3,86.3 140.7,86.2 141.2,86.2 141.8,86.2 142.4,86.2 142.4,86.1 142.4,86
142.4,85.9 142.4,85.7 142.3,85.6 142.3,85.5 142.2,85.4 142.2,85.3 142.1,85.2 142,85.2 141.9,85.1 141.7,85 141.6,85
141.5,84.9 141.4,84.9 141.2,84.9 141.1,84.9 140.7,84.9 140.6,84.9 140.4,84.9 140.2,84.9 140,84.9 139.9,84.9 139.7,85
139.5,85 139.3,85 139.1,85.1 138.9,85.1 138.8,85.1 138.6,85.2 138.4,85.3 138.5,84.8 138.8,83.8 138.8,83.7 138.9,83.7
139.1,83.6 139.2,83.6 139.3,83.6 139.5,83.5 139.6,83.5 139.8,83.5 140,83.5 140.2,83.4 140.4,83.4 140.7,83.4 140.9,83.4
141.2,83.4 141.4,83.4 141.9,83.4 142,83.4 142.3,83.4 142.5,83.5 142.7,83.5 143,83.6 143.2,83.7 143.4,83.8 143.6,84
143.8,84.1 143.9,84.3 144.1,84.5 144.2,84.7 144.2,84.9 144.3,85.1 144.3,85.4 144.3,85.6 143.9,87.5 141.3,87.5 141.2,87.5
141,87.5 140.7,87.6 140.5,87.6 140.4,87.7 140.1,87.7 140,87.8 139.8,87.9 139.6,88 139.5,88.1 139.4,88.2 139.4,88.3
139.3,88.4 139.3,88.5 139.2,88.7 139.2,88.8 139.2,89 139.3,89.1 139.3,89.2 139.4,89.3 139.5,89.4 139.6,89.5 139.7,89.6
139.9,89.6 140.1,89.6 140.3,89.6 140.5,89.6 140.7,89.6 140.8,89.6 140.9,89.5 141.1,89.5 141.2,89.3 141.4,89.2 141.5,89.1
141.6,88.9 141.7,88.8 141.8,88.6 141.9,88.5 141.9,88.3 141.9,88.1 142,88 142,87.9 142,87.7 142,87.6 141.9,87.6 141.8,87.6
141.7,87.5 141.5,87.5 141.3,87.5 143.9,87.5 143.6,89.3 143.6,89.4 143.6,89.5 143.6,89.5 143.6,89.6 143.6,89.7 143.6,89.8
143.5,90 143.5,90 143.5,90.2 143.5,90.3 143.5,90.4 143.5,90.5 143.5,90.8 143.5,90.9 143.5,91.1 141.6,91.1 141.7,90.3
141.6,90.2 141.5,90.2 141.5,90.3 "/>
<polyline class="st6" points="141.5,90.3 141.5,90.3 141.3,90.5 141.2,90.6 141.1,90.6 141,90.7 140.9,90.8 140.8,90.9
140.7,90.9 140.5,91 140.4,91 140.2,91.1 140.1,91.1 139.9,91.2 139.7,91.2 139.6,91.2 139.4,91.2 139.2,91.2 139.1,91.2
138.8,91.2 138.5,91.1 138.3,91 138.1,90.8 137.9,90.7 137.7,90.5 137.6,90.4 137.5,90.2 137.4,90 137.3,89.8 137.3,89.5
137.2,89.3 137.2,89.1 137.2,88.9 137.3,88.7 137.3,88.5 137.3,88.3 137.5,88 137.6,87.7 137.8,87.5 138,87.2 138.2,87
138.4,86.8 138.8,86.7 139.1,86.5 139.4,86.4 139.8,86.3 140.3,86.3 140.7,86.2 141.2,86.2 141.8,86.2 142.4,86.2 142.4,86.1
142.4,86 142.4,85.9 142.4,85.7 142.3,85.6 142.3,85.5 142.2,85.4 142.2,85.3 142.1,85.2 "/>
<polyline class="st6" points="142.1,85.2 142,85.2 141.9,85.1 141.7,85 141.6,85 141.5,84.9 141.4,84.9 141.2,84.9 141.1,84.9
140.7,84.9 140.6,84.9 140.4,84.9 140.2,84.9 140,84.9 139.9,84.9 139.7,85 139.5,85 139.3,85 139.1,85.1 138.9,85.1 138.8,85.1
138.6,85.2 138.4,85.3 138.5,84.8 138.8,83.8 138.8,83.7 138.9,83.7 139.1,83.6 139.2,83.6 139.3,83.6 139.5,83.5 139.6,83.5
139.8,83.5 140,83.5 140.2,83.4 140.4,83.4 140.7,83.4 140.9,83.4 141.2,83.4 141.4,83.4 141.9,83.4 142,83.4 142.3,83.4
142.5,83.5 142.7,83.5 143,83.6 143.2,83.7 143.4,83.8 143.6,84 143.8,84.1 143.9,84.3 144.1,84.5 144.2,84.7 144.2,84.9
144.3,85.1 144.3,85.4 144.3,85.6 143.6,89.3 143.6,89.4 143.6,89.5 "/>
<polyline class="st6" points="143.6,89.5 143.6,89.5 143.6,89.6 143.6,89.7 143.6,89.8 143.5,90 143.5,90 143.5,90.2 143.5,90.3
143.5,90.4 143.5,90.5 143.5,90.8 143.5,90.9 143.5,91.1 141.6,91.1 141.7,90.3 141.6,90.2 141.5,90.2 141.5,90.3 "/>
<polyline class="st6" points="142,87.6 141.9,87.6 141.8,87.6 141.7,87.5 141.5,87.5 141.3,87.5 141.2,87.5 141,87.5 140.7,87.6
140.5,87.6 140.4,87.7 140.1,87.7 140,87.8 139.8,87.9 139.6,88 139.5,88.1 139.4,88.2 139.4,88.3 139.3,88.4 139.3,88.5
139.2,88.7 139.2,88.8 139.2,89 139.3,89.1 139.3,89.2 139.4,89.3 139.5,89.4 139.6,89.5 139.7,89.6 139.9,89.6 140.1,89.6
140.3,89.6 140.5,89.6 140.7,89.6 140.8,89.6 140.9,89.5 141.1,89.5 141.2,89.3 141.4,89.2 141.5,89.1 141.6,88.9 141.7,88.8
141.8,88.6 141.9,88.5 141.9,88.3 141.9,88.1 142,88 142,87.9 142,87.7 142,87.6 "/>
<polygon class="st5" points="177.2,90.4 177.1,90.4 177,90.6 176.8,90.7 176.8,90.8 176.6,90.8 176.5,90.9 176.4,91 176.3,91
176.1,91.1 176,91.1 175.8,91.2 175.7,91.2 175.5,91.2 175.3,91.2 175.2,91.2 175,91.2 174.8,91.2 174.7,91.2 174.4,91.2
174.1,91.1 173.9,91 173.7,90.9 173.5,90.7 173.3,90.6 173.2,90.4 173.1,90.2 173,90 172.9,89.8 172.9,89.6 172.9,89.3
172.8,89.1 172.9,88.9 172.9,88.7 172.9,88.5 173,88.3 173.1,88 173.3,87.7 173.5,87.5 173.7,87.3 173.9,87.1 174.2,86.9
174.5,86.8 174.8,86.7 175.2,86.6 175.6,86.5 176,86.5 176.4,86.4 176.8,86.4 177.3,86.4 177.8,86.4 177.9,86.3 177.9,86.2
177.9,86.1 177.9,86 177.9,85.9 177.9,85.8 177.8,85.7 177.8,85.5 177.7,85.4 177.6,85.3 177.4,85.2 177.2,85.1 177,85.1
176.8,85 176.7,85 176.3,85 176.2,85 176,85 175.8,85 175.6,85.1 175.4,85.1 175.2,85.1 175,85.1 174.8,85.2 174.6,85.2
174.5,85.2 174.3,85.3 174.2,85.3 174,85.4 174.1,84.9 174.4,83.8 174.5,83.8 174.6,83.7 174.7,83.7 174.9,83.7 175,83.6
175.2,83.6 175.3,83.6 175.5,83.5 175.7,83.5 175.9,83.5 176.1,83.5 176.3,83.4 176.5,83.4 176.8,83.4 177.1,83.4 177.5,83.4
177.6,83.4 177.9,83.5 178.1,83.5 178.4,83.6 178.6,83.7 178.8,83.8 179,83.9 179.2,84.1 179.4,84.3 179.5,84.5 179.7,84.6
179.8,84.9 179.9,85.1 179.9,85.3 179.9,85.5 179.9,85.8 179.6,87.7 177.1,87.7 177,87.7 176.9,87.7 176.8,87.7 176.7,87.7
176.6,87.7 176.5,87.7 176.4,87.7 176.3,87.8 176.2,87.8 176.1,87.8 176,87.8 175.9,87.8 175.8,87.8 175.7,87.9 175.6,87.9
175.5,87.9 175.4,88 175.3,88 175.3,88.1 175.2,88.1 175.1,88.2 175,88.3 175,88.4 174.9,88.5 174.9,88.5 174.9,88.7 174.8,88.8
174.8,89 174.8,89.1 174.9,89.3 174.9,89.3 175,89.5 175.1,89.5 175.2,89.6 175.3,89.7 175.5,89.7 175.6,89.8 175.8,89.8
176,89.8 176.3,89.7 176.4,89.7 176.4,89.7 176.6,89.6 176.8,89.5 176.9,89.3 177.1,89.2 177.2,89.1 177.3,88.9 177.3,88.7
177.4,88.6 177.5,88.4 177.5,88.3 177.5,88.1 177.6,88 177.6,87.9 177.6,87.8 177.5,87.8 177.5,87.7 177.4,87.7 177.3,87.7
177.2,87.7 179.6,87.7 179.3,89.3 179.3,89.4 179.2,89.5 179.2,89.6 179.2,89.7 179.2,89.8 179.2,89.9 179.2,90 179.1,90.2
179.1,90.3 179.1,90.4 179.1,90.5 179.1,90.7 179.1,90.8 179.1,90.9 179.1,91.1 177.3,91.1 177.3,90.4 177.2,90.4 177.2,90.4
177.2,90.4 "/>
<polyline class="st6" points="177.2,90.4 177.1,90.4 177,90.6 176.8,90.7 176.8,90.8 176.6,90.8 176.5,90.9 176.4,91 176.3,91
176.1,91.1 176,91.1 175.8,91.2 175.7,91.2 175.5,91.2 175.3,91.2 175.2,91.2 175,91.2 174.8,91.2 174.7,91.2 174.4,91.2
174.1,91.1 173.9,91 173.7,90.9 173.5,90.7 173.3,90.6 173.2,90.4 173.1,90.2 173,90 172.9,89.8 172.9,89.6 172.9,89.3
172.8,89.1 172.9,88.9 172.9,88.7 172.9,88.5 173,88.3 173.1,88 173.3,87.7 173.5,87.5 173.7,87.3 173.9,87.1 174.2,86.9
174.5,86.8 174.8,86.7 175.2,86.6 175.6,86.5 176,86.5 176.4,86.4 176.8,86.4 177.3,86.4 177.8,86.4 177.9,86.3 177.9,86.2
177.9,86.1 177.9,86 177.9,85.9 177.9,85.8 177.8,85.7 177.8,85.5 177.7,85.4 "/>
<polyline class="st6" points="177.7,85.4 177.6,85.3 177.4,85.2 177.2,85.1 177,85.1 176.8,85 176.7,85 176.3,85 176.2,85 176,85
175.8,85 175.6,85.1 175.4,85.1 175.2,85.1 175,85.1 174.8,85.2 174.6,85.2 174.5,85.2 174.3,85.3 174.2,85.3 174,85.4
174.1,84.9 174.4,83.8 174.5,83.8 174.6,83.7 174.7,83.7 174.9,83.7 175,83.6 175.2,83.6 175.3,83.6 175.5,83.5 175.7,83.5
175.9,83.5 176.1,83.5 176.3,83.4 176.5,83.4 176.8,83.4 177.1,83.4 177.5,83.4 177.6,83.4 177.9,83.5 178.1,83.5 178.4,83.6
178.6,83.7 178.8,83.8 179,83.9 179.2,84.1 179.4,84.3 179.5,84.5 179.7,84.6 179.8,84.9 179.9,85.1 179.9,85.3 179.9,85.5
179.9,85.8 179.3,89.3 179.3,89.4 179.2,89.5 179.2,89.6 179.2,89.7 "/>
<polyline class="st6" points="179.2,89.7 179.2,89.8 179.2,89.9 179.2,90 179.1,90.2 179.1,90.3 179.1,90.4 179.1,90.5
179.1,90.7 179.1,90.8 179.1,90.9 179.1,91.1 177.3,91.1 177.3,90.4 177.2,90.4 177.2,90.4 177.2,90.4 "/>
<polyline class="st6" points="177.5,87.8 177.5,87.7 177.4,87.7 177.3,87.7 177.2,87.7 177.1,87.7 177,87.7 176.9,87.7
176.8,87.7 176.7,87.7 176.6,87.7 176.5,87.7 176.4,87.7 176.3,87.8 176.2,87.8 176.1,87.8 176,87.8 175.9,87.8 175.8,87.8
175.7,87.9 175.6,87.9 175.5,87.9 175.4,88 175.3,88 175.3,88.1 175.2,88.1 175.1,88.2 175,88.3 175,88.4 174.9,88.5 174.9,88.5
174.9,88.7 174.8,88.8 174.8,89 174.8,89.1 174.9,89.3 174.9,89.3 175,89.5 175.1,89.5 175.2,89.6 175.3,89.7 175.5,89.7
175.6,89.8 175.8,89.8 176,89.8 176.3,89.7 176.4,89.7 176.4,89.7 176.6,89.6 176.8,89.5 176.9,89.3 177.1,89.2 177.2,89.1
177.3,88.9 177.3,88.7 177.4,88.6 177.5,88.4 177.5,88.3 177.5,88.1 177.6,88 "/>
<polyline class="st6" points="177.6,88 177.6,87.9 177.6,87.8 177.5,87.8 "/>
<polygon class="st4" points="151.7,81.8 153.7,81.8 153.3,83.5 154.5,83.5 154.2,85.2 153,85.2 152.5,88.5 152.4,88.5 152.4,88.6
152.4,88.8 152.4,88.9 152.4,89.1 152.5,89.2 152.5,89.3 152.6,89.3 152.7,89.4 152.8,89.4 152.9,89.4 153,89.4 153.3,89.4
153.5,89.4 153.7,89.4 153.9,89.4 153.5,91.1 153.2,91.1 152.9,91.2 152.4,91.2 152,91.2 151.6,91.2 151.3,91.1 151,91
150.8,90.9 150.6,90.8 150.5,90.6 150.4,90.4 150.3,90.2 150.3,90 150.3,89.8 150.3,89.5 150.3,89.2 150.4,88.9 151.5,82.8
151.7,81.8 "/>
<polygon class="st5" points="160.6,89.1 160.3,90.8 160.2,90.8 160.1,90.9 160,90.9 159.9,91 159.8,91 159.7,91 159.6,91.1
159.4,91.1 159.3,91.1 159.1,91.1 158.9,91.2 158.7,91.2 158.5,91.2 158.3,91.2 158,91.2 157.5,91.2 157.3,91.2 157,91.1
156.6,91.1 156.3,91 155.9,90.8 155.6,90.7 155.3,90.5 155.1,90.3 154.9,90 154.7,89.7 154.5,89.4 154.4,89.1 154.3,88.7
154.2,88.3 154.2,87.8 154.2,87.3 154.2,87.1 154.3,86.9 154.4,86.5 154.5,86.1 154.6,85.7 154.8,85.3 155,85 155.2,84.7
155.5,84.4 155.7,84.2 156,84 156.3,83.8 156.6,83.7 156.9,83.5 157.2,83.4 157.6,83.4 157.9,83.4 158.1,83.4 158.4,83.4
158.8,83.4 159.2,83.5 159.5,83.7 159.9,83.8 160.1,84 160.4,84.2 160.6,84.5 160.8,84.8 160.8,84.9 157.7,84.9 157.5,84.9
157.3,85 157.1,85.1 157,85.2 156.8,85.4 156.7,85.5 156.6,85.6 156.5,85.8 156.4,85.9 156.4,86.1 156.4,86.2 156.4,86.3
156.4,86.4 156.5,86.4 159,86.4 159.1,86.3 159.1,86.2 159.1,86.1 159.1,86 159,85.8 159,85.7 158.9,85.6 158.9,85.5 158.8,85.4
158.7,85.2 158.5,85.1 158.3,85 158.1,84.9 157.9,84.9 157.8,84.9 160.8,84.9 160.9,85.1 161,85.5 161.1,85.9 161.1,86.3
161,86.8 160.9,87.3 160.8,87.9 156.2,87.9 156.1,87.9 156.1,88 156.1,88.1 156.1,88.3 156.1,88.4 156.1,88.5 156.2,88.7
156.3,88.8 156.4,89 156.5,89.1 156.7,89.3 156.9,89.4 157.1,89.5 157.3,89.5 157.6,89.6 157.6,89.6 157.8,89.6 157.9,89.6
158.1,89.6 158.3,89.6 158.4,89.6 158.6,89.6 158.8,89.6 159,89.6 159.2,89.5 159.4,89.5 159.6,89.4 159.7,89.4 159.9,89.3
160.1,89.3 160.4,89.2 160.6,89.1 "/>
<polyline class="st6" points="160.6,89.1 160.3,90.8 160.2,90.8 160.1,90.9 160,90.9 159.9,91 159.8,91 159.7,91 159.6,91.1
159.4,91.1 159.3,91.1 159.1,91.1 158.9,91.2 158.7,91.2 158.5,91.2 158.3,91.2 158,91.2 157.5,91.2 157.3,91.2 157,91.1
156.6,91.1 156.3,91 155.9,90.8 155.6,90.7 155.3,90.5 155.1,90.3 154.9,90 154.7,89.7 154.5,89.4 154.4,89.1 154.3,88.7
154.2,88.3 154.2,87.8 154.2,87.3 154.2,87.1 154.3,86.9 154.4,86.5 154.5,86.1 154.6,85.7 154.8,85.3 155,85 155.2,84.7
155.5,84.4 155.7,84.2 156,84 156.3,83.8 156.6,83.7 156.9,83.5 157.2,83.4 157.6,83.4 157.9,83.4 158.1,83.4 158.4,83.4
158.8,83.4 159.2,83.5 159.5,83.7 159.9,83.8 160.1,84 160.4,84.2 160.6,84.5 "/>
<polyline class="st6" points="160.6,84.5 160.8,84.8 160.9,85.1 161,85.5 161.1,85.9 161.1,86.3 161,86.8 160.9,87.3 160.8,87.9
156.2,87.9 156.1,87.9 156.1,88 156.1,88.1 156.1,88.3 156.1,88.4 156.1,88.5 156.2,88.7 156.3,88.8 156.4,89 156.5,89.1
156.7,89.3 156.9,89.4 157.1,89.5 157.3,89.5 157.6,89.6 157.6,89.6 157.8,89.6 157.9,89.6 158.1,89.6 158.3,89.6 158.4,89.6
158.6,89.6 158.8,89.6 159,89.6 159.2,89.5 159.4,89.5 159.6,89.4 159.7,89.4 159.9,89.3 160.1,89.3 160.4,89.2 160.6,89.1 "/>
<polyline class="st6" points="159,86.4 159.1,86.3 159.1,86.2 159.1,86.1 159.1,86 159,85.8 159,85.7 158.9,85.6 158.9,85.5
158.8,85.4 158.7,85.2 158.5,85.1 158.3,85 158.1,84.9 157.9,84.9 157.7,84.9 157.5,84.9 157.3,85 157.1,85.1 157,85.2
156.8,85.4 156.7,85.5 156.6,85.6 156.5,85.8 156.4,85.9 156.4,86.1 156.4,86.2 156.4,86.3 156.4,86.4 156.5,86.4 159,86.4 "/>
<polygon class="st5" points="193.1,91 193.1,89.8 193.6,89.8 193.7,89.8 193.8,89.9 193.8,89.9 193.9,89.9 193.9,89.9 194,90
194,90 194,90 194,90.1 194,90.2 194,90.2 194,90.3 194,90.4 193.8,90.4 193.8,90.3 193.8,90.3 193.9,90.3 193.9,90.2 193.9,90.2
193.9,90.1 193.9,90.1 193.8,90 193.8,90 193.7,90 193.7,90 193.6,90 193.3,90 193.3,90.4 193.7,90.4 193.8,90.4 194,90.4
193.9,90.4 193.9,90.4 193.8,90.5 193.8,90.5 194.1,91 193.9,91 193.6,90.5 193.3,90.5 193.3,91 193.1,91 "/>
<polygon class="st5" points="193.5,91.3 193.6,91.3 193.7,91.3 193.8,91.3 193.8,91.3 193.9,91.2 194,91.2 194,91.2 194.1,91.1
194.2,91.1 194.2,91 194.3,90.9 194.3,90.9 194.3,90.8 194.4,90.7 194.4,90.7 194.4,90.6 194.4,90.5 194.4,90.4 194.4,90.4
194.4,90.3 194.4,90.2 194.4,90.1 194.3,90 194.3,90 194.3,89.9 194.2,89.9 194.2,89.8 194.1,89.7 194,89.7 194,89.6 193.9,89.6
193.8,89.6 193.8,89.6 193.7,89.6 193.6,89.5 193.5,89.5 193.5,89.5 193.4,89.6 193.3,89.6 193.2,89.6 193.2,89.6 193.1,89.6
193,89.7 193,89.7 192.9,89.8 192.9,89.9 192.8,89.9 192.8,90 192.7,90 192.7,90.1 192.7,90.2 192.7,90.3 192.7,90.4 192.7,90.5
192.7,90.6 192.7,90.7 192.7,90.7 192.7,90.8 192.8,90.9 192.8,90.9 192.9,91 192.9,91.1 193,91.1 193,91.2 193.1,91.2
193.2,91.2 193.2,91.3 193.3,91.3 193.4,91.3 193.5,91.3 193.5,91.3 193.5,91.2 193.5,91.2 193.4,91.2 193.3,91.2 193.2,91.2
193.2,91.1 193.1,91.1 193,91 193,91 192.9,90.9 192.9,90.9 192.8,90.8 192.8,90.7 192.8,90.7 192.8,90.6 192.7,90.5 192.7,90.4
192.8,90.3 192.8,90.2 192.8,90.1 192.8,90 192.9,90 192.9,89.9 193,89.9 193,89.8 193.1,89.8 193.2,89.7 193.2,89.7 193.3,89.6
193.4,89.6 193.5,89.6 193.6,89.6 193.7,89.6 193.8,89.6 193.8,89.7 193.9,89.7 194,89.8 194,89.8 194.1,89.9 194.1,89.9
194.2,90 194.2,90 194.3,90.1 194.3,90.2 194.3,90.3 194.3,90.4 194.3,90.4 194.3,90.5 194.3,90.6 194.3,90.7 194.3,90.7
194.2,90.8 194.2,90.9 194.1,90.9 194.1,91 194,91 194,91.1 193.9,91.1 193.8,91.2 193.8,91.2 193.7,91.2 193.6,91.2 193.5,91.2
193.5,91.3 "/>
</g>
</g>
<polygon class="st7" points="24.1,63.5 106.3,63.5 106.3,112.2 24.1,112.2 24.1,63.5 "/>
<g>
<polygon class="st8" points="216.5,62.2 291.7,62.2 291.7,109.4 216.5,109.4 216.5,62.2 "/>
<g>
<g>
<path class="st9" d="M226.6,78.2c0.9-0.2,2.4-0.3,3.8-0.3c2.1,0,3.4,0.4,4.4,1.1c0.8,0.6,1.3,1.5,1.3,2.8c0,1.5-1,2.8-2.7,3.4v0
c1.5,0.4,3.3,1.6,3.3,3.9c0,1.4-0.6,2.4-1.4,3.1c-1.1,1-3,1.5-5.6,1.5c-1.4,0-2.5-0.1-3.2-0.2V78.2z M228.7,84.6h1.9
c2.2,0,3.5-1.1,3.5-2.6c0-1.8-1.4-2.6-3.6-2.6c-1,0-1.5,0.1-1.9,0.1V84.6z M228.7,92.1c0.4,0.1,1,0.1,1.8,0.1
c2.2,0,4.2-0.8,4.2-3.1c0-2.1-1.9-3-4.2-3h-1.7V92.1z"/>
<path class="st9" d="M242.2,88.7l-1.7,4.9h-2.2l5.5-15.7h2.5l5.5,15.7h-2.2l-1.7-4.9H242.2z M247.6,87.1l-1.6-4.5
c-0.4-1-0.6-2-0.8-2.9h-0.1c-0.2,0.9-0.5,1.9-0.8,2.8l-1.6,4.5H247.6z"/>
<path class="st9" d="M254.3,93.7V78h2.3l5.2,8c1.2,1.8,2.1,3.5,2.9,5.1l0,0c-0.2-2.1-0.2-4-0.2-6.5V78h2v15.7h-2.1l-5.1-8
c-1.1-1.7-2.2-3.5-3-5.2l-0.1,0c0.1,2,0.2,3.9,0.2,6.5v6.7H254.3z"/>
<path class="st9" d="M270.2,78h2.1v7.6h0.1c0.4-0.6,0.9-1.2,1.3-1.7l5-5.9h2.6l-5.9,6.7l6.3,9h-2.5l-5.3-7.7l-1.5,1.7v6h-2.1V78z
"/>
</g>
</g>
</g>
<g>
<path class="st10" d="M100.1,15.7h9.9c5.3,0,7.3,2.7,7,6.7c-0.5,6.5-4.5,10.2-9.7,10.2h-2.6c-0.7,0-1.2,0.5-1.4,1.8l-1.1,7.5
c-0.1,0.5-0.3,0.8-0.7,0.8h-6.2c-0.6,0-0.8-0.4-0.6-1.4l3.8-24C98.5,16.1,99,15.7,100.1,15.7z"/>
<path class="st11" d="M143.2,15.2c3.3,0,6.4,1.8,6,6.3c-0.5,5.4-3.4,8.4-7.9,8.4h-4c-0.6,0-0.8,0.5-1,1.4l-0.8,4.9
c-0.1,0.7-0.5,1.1-1.1,1.1h-3.7c-0.6,0-0.8-0.4-0.7-1.2l3.1-19.6c0.1-1,0.5-1.3,1.2-1.3H143.2L143.2,15.2z M137.1,25.7h3
c1.9-0.1,3.1-1.4,3.3-3.7c0.1-1.5-0.9-2.5-2.5-2.5l-2.8,0L137.1,25.7L137.1,25.7z M159.2,35.8c0.3-0.3,0.7-0.5,0.6-0.1l-0.1,0.9
c-0.1,0.5,0.1,0.7,0.6,0.7h3.3c0.6,0,0.8-0.2,1-1.1l2-12.7c0.1-0.6-0.1-0.9-0.5-0.9h-3.6c-0.3,0-0.5,0.2-0.6,0.7l-0.1,0.8
c-0.1,0.4-0.3,0.5-0.4,0.1c-0.6-1.5-2.2-2.1-4.4-2.1c-5.1,0.1-8.5,3.9-8.8,8.9c-0.3,3.8,2.4,6.8,6,6.8
C156.7,37.8,157.9,37,159.2,35.8L159.2,35.8z M156.5,33.9c-2.2,0-3.7-1.7-3.4-3.9c0.3-2.1,2.4-3.9,4.5-3.9c2.2,0,3.7,1.7,3.4,3.9
C160.7,32.1,158.6,33.9,156.5,33.9L156.5,33.9z M173,22.6h-3.3c-0.7,0-1,0.5-0.7,1.1l4.1,12.1l-4.1,5.8c-0.3,0.5-0.1,0.9,0.4,0.9
h3.7c0.6,0,0.8-0.1,1.1-0.5l12.7-18.2c0.4-0.6,0.2-1.1-0.4-1.2l-3.5,0c-0.6,0-0.9,0.2-1.2,0.7l-5.3,7.7l-2.4-7.7
C174,22.9,173.6,22.6,173,22.6z"/>
<path class="st10" d="M200.2,15.2c3.3,0,6.4,1.8,6,6.3c-0.5,5.4-3.4,8.4-7.9,8.4h-4c-0.6,0-0.8,0.5-1,1.4l-0.8,4.9
c-0.1,0.7-0.5,1.1-1.1,1.1h-3.7c-0.6,0-0.8-0.4-0.7-1.2l3.1-19.6c0.1-1,0.5-1.3,1.2-1.3H200.2L200.2,15.2z M194.1,25.7h3
c1.9-0.1,3.1-1.4,3.3-3.7c0.1-1.5-0.9-2.5-2.5-2.5l-2.8,0L194.1,25.7L194.1,25.7z M216.2,35.8c0.3-0.3,0.7-0.5,0.6-0.1l-0.1,0.9
c-0.1,0.5,0.1,0.7,0.6,0.7h3.3c0.6,0,0.8-0.2,1-1.1l2-12.7c0.1-0.6-0.1-0.9-0.5-0.9h-3.6c-0.3,0-0.5,0.2-0.6,0.7l-0.1,0.8
c-0.1,0.4-0.3,0.5-0.4,0.1c-0.6-1.5-2.2-2.1-4.4-2.1c-5.1,0.1-8.5,3.9-8.8,8.9c-0.3,3.8,2.4,6.8,6,6.8
C213.7,37.8,214.9,37,216.2,35.8L216.2,35.8z M213.5,33.9c-2.2,0-3.7-1.7-3.4-3.9c0.3-2.1,2.4-3.9,4.5-3.9c2.2,0,3.7,1.7,3.4,3.9
C217.7,32.1,215.6,33.9,213.5,33.9L213.5,33.9z M228.6,37.4h-3.8c-0.3,0-0.5-0.2-0.5-0.5l3.3-21.1c0-0.3,0.3-0.5,0.6-0.5h3.8
c0.3,0,0.5,0.2,0.5,0.5l-3.3,21.1C229.2,37.2,228.9,37.4,228.6,37.4z"/>
<path class="st11" d="M93.9,7.4h9.9c2.8,0,6.1,0.1,8.3,2c1.5,1.3,2.3,3.4,2.1,5.6c-0.6,7.6-5.1,11.8-11.2,11.8h-4.9
c-0.8,0-1.4,0.6-1.6,2l-1.4,8.7c-0.1,0.6-0.3,0.9-0.8,0.9h-6.1c-0.7,0-0.9-0.5-0.7-1.6L91.9,9C92.1,7.9,92.7,7.4,93.9,7.4z"/>
<path class="st12" d="M96.6,28.1l1.7-11c0.2-1,0.7-1.4,1.7-1.4h9.9c1.6,0,3,0.3,4,0.7c-1,6.7-5.4,10.5-11.1,10.5h-4.9
C97.4,26.9,96.9,27.2,96.6,28.1z"/>
</g>
<g>
<polygon class="st13" points="54.8,98.3 58.4,77.4 64.1,77.4 60.5,98.3 "/>
<path class="st13" d="M81.2,77.9C80,77.5,78.3,77,76.1,77c-5.6,0-9.6,2.8-9.6,6.9c0,3,2.8,4.7,5,5.7c2.2,1,3,1.7,3,2.6
c0,1.4-1.8,2-3.4,2c-2.3,0-3.5-0.3-5.4-1.1l-0.7-0.3l-0.8,4.7c1.3,0.6,3.8,1.1,6.3,1.1c6,0,9.9-2.8,9.9-7.2c0-2.4-1.5-4.2-4.8-5.7
c-2-1-3.2-1.6-3.2-2.6c0-0.9,1-1.8,3.3-1.8c1.9,0,3.2,0.4,4.3,0.8l0.5,0.2L81.2,77.9z"/>
<path class="st13" d="M95.8,77.4h-4.4c-1.4,0-2.4,0.4-3,1.7l-8.5,19.2h6c0,0,1-2.6,1.2-3.1c0.7,0,6.5,0,7.3,0
c0.2,0.7,0.7,3.1,0.7,3.1h5.3L95.8,77.4z M88.7,90.9c0.5-1.2,2.3-5.9,2.3-5.9c0,0.1,0.5-1.2,0.8-2l0.4,1.8c0,0,1.1,5,1.3,6.1H88.7z
"/>
<path class="st13" d="M50,77.4l-5.6,14.3l-0.6-2.9c-1-3.3-4.3-7-7.9-8.8L41,98.3l6,0l9-20.9H50z"/>
<path class="st14" d="M39.3,77.4h-9.2L30,77.8c7.1,1.7,11.9,5.9,13.8,11l-2-9.6C41.5,77.8,40.5,77.4,39.3,77.4"/>
</g>
<g>
<line class="st15" x1="315.6" y1="29.5" x2="243.4" y2="29.5"/>
<line class="st16" x1="4.6" y1="27" x2="4.6" y2="121.8"/>
<line class="st16" x1="315.6" y1="27" x2="315.6" y2="121.8"/>
<line class="st16" x1="3.6" y1="121.8" x2="315.6" y2="121.8"/>
<line class="st15" x1="75.8" y1="29.5" x2="3.6" y2="29.5"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 33 KiB

+32
View File
@@ -0,0 +1,32 @@
<?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/>.
/**
* Settings for the PayPal payment gateway
*
* @package paygw_paypal
* @copyright 2019 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
if ($ADMIN->fulltree) {
$settings->add(new admin_setting_heading('paygw_paypal_settings', '', get_string('pluginname_desc', 'paygw_paypal')));
\core_payment\helper::add_common_gateway_settings($settings, 'paygw_paypal');
}
+4
View File
@@ -0,0 +1,4 @@
.core_payment_gateways_modal .paypal .icon {
height: 40px;
width: auto;
}
@@ -0,0 +1,35 @@
{{!
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/>.
}}
{{!
@template paygw_paypal/paypal_button_placeholder
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* none
Example context (json):
{}
}}
<div class="bg-pulse-grey rounded" style="width: 100%; height: 45px; margin-bottom: 14px;"></div>
<div class="bg-pulse-grey rounded" style="width: 100%; height: 45px; margin-bottom: 14px;"></div>
<div class="bg-pulse-grey" style="width: 110px; height: 16px; margin: 10px auto; position: relative; bottom: 3px;"></div>
+29
View File
@@ -0,0 +1,29 @@
<?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/>.
/**
* Version information
*
* @package paygw_paypal
* @copyright 2019 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2024042200; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2024041600; // Requires this Moodle version.
$plugin->component = 'paygw_paypal'; // Full name of the plugin (used for diagnostics).
+70
View File
@@ -0,0 +1,70 @@
<?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);
$restore = optional_param('restore', false, PARAM_BOOL);
$pageurl = new moodle_url('/payment/manage_account.php');
admin_externalpage_setup('paymentaccounts', '', [], $pageurl);
$enabledplugins = \core\plugininfo\paygw::get_enabled_plugins();
$account = new \core_payment\account($id);
require_capability('moodle/payment:manageaccounts', $account->get_context());
if ($delete && !$account->get('archived') && confirm_sesskey()) {
\core_payment\helper::delete_payment_account($account);
redirect(new moodle_url('/payment/accounts.php'));
}
if ($restore && $account->get('archived') && confirm_sesskey()) {
\core_payment\helper::restore_payment_account($account);
redirect(new moodle_url('/payment/accounts.php'));
}
$PAGE->set_secondary_active_tab('siteadminnode');
$PAGE->set_primary_active_tab('siteadminnode');
if ($id == 0) {
$PAGE->navbar->add(get_string('createaccount', 'payment'), $PAGE->url);
} else {
$PAGE->navbar->add(get_string('editpaymentaccount', 'payment'), $PAGE->url);
}
$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();
+67
View File
@@ -0,0 +1,67 @@
<?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\paygw::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_secondary_active_tab('siteadminnode');
$PAGE->set_primary_active_tab('siteadminnode');
$PAGE->navbar->add(get_string('createaccount', 'payment'), $PAGE->url);
$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();
+37
View File
@@ -0,0 +1,37 @@
{{!
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/>.
}}
{{!
@template core_payment/fee_breakdown
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* surchargestr
Example context (json):
{
"surchargestr": "Cost: $10.00"
}
}}
<div class="core_payment_fee_breakdown">
{{surchargestr}}
</div>
+51
View File
@@ -0,0 +1,51 @@
{{!
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/>.
}}
{{!
@template core_payment/gateway
This template will render the gateway option in the gateway selector modal.
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* shortname
* name
* description
* surcharge
* image
Example context (json):
{
"shortname": "paypal",
"name": "PayPal",
"description": "A description for PayPal.",
"surcharge": "3"
}
}}
<div class="custom-control custom-radio {{shortname}}">
<input class="custom-control-input" type="radio" name="payby" id="id-payby-{{uniqid}}-{{shortname}}" data-cost="{{cost}}" data-surcharge="{{surcharge}}" value="{{shortname}}" {{#checked}} checked="checked" {{/checked}} />
<label class="custom-control-label bg-light border p-3 my-3" for="id-payby-{{uniqid}}-{{shortname}}">
<p class="h3">{{name}}</p>
<p class="content mb-2">{{{description}}}</p>
{{#pix}} img, paygw_{{shortname}} {{/pix}}
</label>
</div>
+55
View File
@@ -0,0 +1,55 @@
{{!
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/>.
}}
{{!
@template core_payment/gateways
This template will render the list of gateways in the gateway selector modal.
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* gateways
Example context (json):
{
"gateways" : [
{
"shortname": "paypal",
"name": "PayPal",
"description": "A description for PayPal.",
"image": "../../../pix/help.svg"
},
{
"shortname": "stripe",
"name": "Stripe",
"description": "A description for Stripe.",
"image": "../../../pix/help.svg"
}
]
}
}}
{{#gateways}}
{{> core_payment/gateway }}
{{/gateways}}
{{^gateways}}
<p>{{#str}}nogateway, core_payment{{/str}}</p>
{{/gateways}}
+42
View File
@@ -0,0 +1,42 @@
{{!
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/>.
}}
{{!
@template core_payment/gateways_modal
This template will render the gateway selector modal.
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* none
Example context (json):
{}
}}
<div class="core_payment_gateways_modal">
<div data-region="gateways-container">
{{> core_payment/gateways_placeholder }}
</div>
<div data-region="fee-breakdown-container">
<div class="bg-pulse-grey" style="height: 22px; width: 90px"></div>
</div>
</div>
@@ -0,0 +1,41 @@
{{!
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/>.
}}
{{!
@template core_payment/gateways_placeholder
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* none
Example context (json):
{}
}}
<div class="custom-control custom-radio">
<input class="custom-control-input" type="radio" disabled />
<label class="custom-control-label bg-light border p-3 my-3 w-100">
<div class="bg-pulse-grey mb-2" style="height: 31px; width: 18%"></div>
<div class="bg-pulse-grey mb-1" style="height: 20px; width: 95%"></div>
<div class="bg-pulse-grey mb-2" style="height: 20px; width: 25%"></div>
<div class="bg-pulse-grey" style="height: 40px; width: 30%"></div>
</label>
</div>
+45
View File
@@ -0,0 +1,45 @@
{{!
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/>.
}}
{{!
@template core_payment/modal_gateways
Moodle modal template with save and cancel buttons.
The purpose of this template is to render a modal.
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* title A cleaned string (use clean_text()) to display.
* body HTML content for the boday
Example context (json):
{
"title": "Example gateways modal",
"body": "Some example content for the body"
}
}}
{{< core/modal }}
{{$footer}}
<button type="button" class="btn btn-primary" data-action="proceed">{{#str}} proceed {{/str}}</button>
<button type="button" class="btn btn-secondary" data-action="cancel">{{#str}} cancel {{/str}}</button>
{{/footer}}
{{/ core/modal }}
+81
View File
@@ -0,0 +1,81 @@
@core @core_payment
Feature: Manage payment accounts
@javascript
Scenario: Creating and editing payment account
When I log in as "admin"
And I navigate to "Payments > Payment accounts" in site administration
And I follow "Manage payment gateways"
Then "Australian Dollar" "text" should exist in the "PayPal" "table_row"
And I follow "Payment accounts"
And I press "Create payment account"
And I set the field "Account name" to "TestAccount"
And I press "Save changes"
And I should see "PayPal" in the "TestAccount" "table_row"
And I open the action menu in "TestAccount" "table_row"
And I choose "Edit" in the open action menu
And I set the field "Account name" to "NewName"
And I press "Save changes"
And I should not see "TestAccount"
And I should see "PayPal" in the "NewName" "table_row"
@javascript
Scenario: Configuring gateways on payment accounts
Given the following "core_payment > payment accounts" exist:
| name |
| Account1 |
| Account2 |
When I log in as "admin"
And I navigate to "Payments > Payment accounts" in site administration
Then I should see "Not available" in the "Account1" "table_row"
And I click on "PayPal" "link" in the "Account1" "table_row"
And I set the field "Brand name" to "Test paypal"
And I set the following fields to these values:
| Brand name | Test paypal |
| Client ID | Test |
| Secret | Test |
| Enable | 1 |
And I press "Save changes"
And I should not see "Not available" in the "Account1" "table_row"
And I should see "PayPal" in the "Account1" "table_row"
@javascript
Scenario: Deleting payment accounts
Given the following "core_payment > payment accounts" exist:
| name |
| Account1 |
| Account2 |
When I log in as "admin"
And I navigate to "Payments > Payment accounts" in site administration
And I open the action menu in "Account1" "table_row"
And I choose "Delete or archive" in the open action menu
And I click on "Yes" "button" in the "Confirm" "dialogue"
Then I should not see "Account1"
And I should see "Account2"
@javascript
Scenario: Archiving and restoring accounts
Given the following "users" exist:
| username |
| user1 |
And the following "core_payment > payment accounts" exist:
| name |
| Account1 |
| Account2 |
And the following "core_payment > payments" exist:
| account | component | amount | user |
| Account1 | test | 10 | user1 |
| Account1 | test | 15 | user1 |
When I log in as "admin"
And I navigate to "Payments > Payment accounts" in site administration
And I open the action menu in "Account1" "table_row"
And I choose "Delete or archive" in the open action menu
And I click on "Yes" "button" in the "Confirm" "dialogue"
Then I should not see "Account1"
And I should see "Account2"
And I follow "Show archived"
And I should see "Archived" in the "Account1" "table_row"
And I open the action menu in "Account1" "table_row"
And I choose "Restore" in the open action menu
And I should not see "Archived" in the "Account1" "table_row"
And I log out
@@ -0,0 +1,19 @@
@core @core_payment @javascript
Feature: Verify the breadcrumbs in payment accounts site administration pages
Whenever I navigate to payment account page in site administration
As an admin
The breadcrumbs should be visible
Background:
Given I log in as "admin"
Scenario: Verify the breadcrumbs in payment account page as an admin
Given I navigate to "Payments > Payment accounts" in site administration
And I click on "Create payment account" "button"
And "Create payment account" "text" should exist in the ".breadcrumb" "css_element"
And "Payment accounts" "link" should exist in the ".breadcrumb" "css_element"
And "Payments" "link" should exist in the ".breadcrumb" "css_element"
And I press "Cancel"
When I click on "Show archived" "link"
Then "Payment accounts" "text" should exist in the ".breadcrumb" "css_element"
And "Payments" "link" should exist in the ".breadcrumb" "css_element"
@@ -0,0 +1,72 @@
<?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/>.
/**
* Behat data generator for core_payment.
*
* @package core_payment
* @category test
* @copyright 2020 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Behat data generator for core_payment.
*
* @package core_payment
* @category test
* @copyright 2020 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_core_payment_generator extends behat_generator_base {
/**
* Get a list of the entities that can be created.
*
* @return array entity name => information about how to generate.
*/
protected function get_creatable_entities(): array {
return [
'payment accounts' => [
'singular' => 'payment account',
'datagenerator' => 'payment_account',
'required' => ['name'],
],
'payments' => [
'singular' => 'payment',
'datagenerator' => 'payment',
'required' => ['account', 'amount', 'user'],
'switchids' => ['account' => 'accountid', 'user' => 'userid'],
],
];
}
/**
* Look up the id of a account from its name.
*
* @param string $accountname
* @return int corresponding id.
*/
protected function get_account_id(string $accountname): int {
global $DB;
if (!$id = $DB->get_field('payment_accounts', 'id', ['name' => $accountname])) {
throw new Exception('There is no account with name "' . $accountname . '".');
}
return $id;
}
}
+82
View File
@@ -0,0 +1,82 @@
<?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/>.
/**
* Payment module test data generator class
*
* @package core_payment
* @category test
* @copyright 2020 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_payment_generator extends component_generator_base {
/** @var int */
protected $accountcounter = 0;
/**
* Create a payment account
*
* @param array $data account data (name, idnumber, enabled) and additionally field 'gateways' that can include
* a list of gateways that should be mock-enabled for this account.
*/
public function create_payment_account(array $data = []): \core_payment\account {
$this->accountcounter++;
$gateways = [];
if (!empty($data['gateways'])) {
$gateways = preg_split('/,/', $data['gateways']);
}
unset($data['gateways']);
$account = \core_payment\helper::save_payment_account(
(object)($data + ['name' => 'Test '.$this->accountcounter, 'idnumber' => '', 'enabled' => 1]));
foreach ($gateways as $gateway) {
\core_payment\helper::save_payment_gateway(
(object)['accountid' => $account->get('id'), 'gateway' => $gateway, 'enabled' => 1]);
}
return $account;
}
/**
* Create a payment account
*
* @param array $data
*/
public function create_payment(array $data): int {
global $DB;
if (empty($data['accountid']) || !\core_payment\account::get_record(['id' => $data['accountid']])) {
throw new coding_exception('Account id is not specified or does not exist');
}
if (empty($data['amount'])) {
throw new coding_exception('Amount must be specified');
}
$gateways = \core\plugininfo\paygw::get_enabled_plugins();
if (empty($data['gateway'])) {
$data['gateway'] = reset($gateways);
}
$id = $DB->insert_record('payments', $data +
[
'component' => 'testcomponent',
'paymentarea' => 'teatarea',
'itemid' => 0,
'currency' => 'AUD',
]);
return $id;
}
}
+72
View File
@@ -0,0 +1,72 @@
<?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/>.
/**
* Testing generator in payments API
*
* @package core_payment
* @category test
* @copyright 2020 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_payment;
/**
* Testing generator in payments API
*
* @package core_payment
* @category test
* @copyright 2020 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class generator_test extends \advanced_testcase {
public function test_create_account(): void {
global $DB;
$this->resetAfterTest();
/** @var \core_payment_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_payment');
$this->assertTrue($generator instanceof \core_payment_generator);
$account1 = $generator->create_payment_account();
$account2 = $generator->create_payment_account(['name' => 'My name', 'gateways' => 'paypal']);
$record1 = $DB->get_record('payment_accounts', ['id' => $account1->get('id')]);
$record2 = $DB->get_record('payment_accounts', ['id' => $account2->get('id')]);
$this->assertEquals(1, $record1->enabled);
$this->assertEquals('My name', $record2->name);
// First account does not have gateways configurations.
$this->assertEmpty($DB->get_records('payment_gateways', ['accountid' => $account1->get('id')]));
// Second account has.
$this->assertCount(1, $DB->get_records('payment_gateways', ['accountid' => $account2->get('id')]));
}
public function test_create_payment(): void {
global $DB;
$this->resetAfterTest();
/** @var \core_payment_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_payment');
$account = $generator->create_payment_account(['gateways' => 'paypal']);
$user = $this->getDataGenerator()->create_user();
$paymentid = $generator->create_payment(['accountid' => $account->get('id'), 'amount' => 10, 'userid' => $user->id]);
$this->assertEquals('testcomponent', $DB->get_field('payments', 'component', ['id' => $paymentid]));
}
}
+202
View File
@@ -0,0 +1,202 @@
<?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/>.
/**
* Testing helper class methods in payments API
*
* @package core_payment
* @category test
* @copyright 2020 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_payment;
use advanced_testcase;
use core\plugininfo\paygw;
/**
* Testing helper class methods in payments API
*
* @package core_payment
* @category test
* @copyright 2020 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class helper_test extends advanced_testcase {
protected function enable_paypal_gateway(): bool {
if (!array_key_exists('paypal', \core_component::get_plugin_list('paygw'))) {
return false;
}
return true;
}
public function test_create_account(): void {
global $DB;
$this->resetAfterTest();
$account = helper::save_payment_account((object)['name' => 'Test 1', 'idnumber' => '']);
$this->assertNotEmpty($account->get('id'));
$this->assertEquals('Test 1', $DB->get_field('payment_accounts', 'name', ['id' => $account->get('id')]));
}
public function test_update_account_details(): void {
global $DB;
$this->resetAfterTest();
$account = helper::save_payment_account((object)['name' => 'Test 1', 'idnumber' => '']);
$record = $account->to_record();
$record->name = 'Edited name';
$editedaccount = helper::save_payment_account($record);
$this->assertEquals($account->get('id'), $editedaccount->get('id'));
$this->assertEquals('Edited name', $DB->get_field('payment_accounts', 'name', ['id' => $account->get('id')]));
}
public function test_update_account_gateways(): void {
global $DB;
if (!$this->enable_paypal_gateway()) {
$this->markTestSkipped('Paypal payment gateway plugin not found');
}
$this->resetAfterTest();
$account = helper::save_payment_account((object)['name' => 'Test 1', 'idnumber' => '']);
$gateway = helper::save_payment_gateway(
(object)['accountid' => $account->get('id'), 'gateway' => 'paypal', 'config' => 'T1']);
$this->assertNotEmpty($gateway->get('id'));
$this->assertEquals('T1', $DB->get_field('payment_gateways', 'config', ['id' => $gateway->get('id')]));
// Update by id.
$editedgateway = helper::save_payment_gateway(
(object)['id' => $gateway->get('id'), 'accountid' => $account->get('id'), 'gateway' => 'paypal', 'config' => 'T2']);
$this->assertEquals($gateway->get('id'), $editedgateway->get('id'));
$this->assertEquals('T2', $DB->get_field('payment_gateways', 'config', ['id' => $gateway->get('id')]));
// Update by account/gateway.
$editedgateway = helper::save_payment_gateway(
(object)['accountid' => $account->get('id'), 'gateway' => 'paypal', 'config' => 'T3']);
$this->assertEquals($gateway->get('id'), $editedgateway->get('id'));
$this->assertEquals('T3', $DB->get_field('payment_gateways', 'config', ['id' => $gateway->get('id')]));
}
public function test_delete_account(): void {
global $DB;
if (!$this->enable_paypal_gateway()) {
$this->markTestSkipped('Paypal payment gateway plugin not found');
}
$this->resetAfterTest();
// Delete account without payments, it will be deleted, gateways will also be deleted.
$account = helper::save_payment_account((object)['name' => 'Test 1', 'idnumber' => '']);
$gateway = helper::save_payment_gateway(
(object)['accountid' => $account->get('id'), 'gateway' => 'paypal', 'config' => 'T1']);
helper::delete_payment_account(account::get_record(['id' => $account->get('id')]));
$this->assertEmpty($DB->get_records('payment_accounts', ['id' => $account->get('id')]));
$this->assertEmpty($DB->get_records('payment_gateways', ['id' => $gateway->get('id')]));
}
public function test_archive_restore_account(): void {
global $DB, $USER;
$this->resetAfterTest();
// Delete account with payments - it will be archived.
$this->setAdminUser();
$account = helper::save_payment_account((object)['name' => 'Test 1', 'idnumber' => '']);
$DB->insert_record('payments', [
'accountid' => $account->get('id'),
'component' => 'test',
'paymentarea' => 'test',
'itemid' => 1,
'userid' => $USER->id,
]);
helper::delete_payment_account(account::get_record(['id' => $account->get('id')]));
$this->assertEquals(1, $DB->get_field('payment_accounts', 'archived', ['id' => $account->get('id')]));
// Restore account.
helper::restore_payment_account(account::get_record(['id' => $account->get('id')]));
$this->assertEquals(0, $DB->get_field('payment_accounts', 'archived', ['id' => $account->get('id')]));
}
/**
* Provider for format_cost test
*
* @return array
*/
public function get_rounded_cost_provider(): array {
return [
'IRR 0 surcharge' => [5.345, 'IRR', 0, 5],
'IRR 12% surcharge' => [5.345, 'IRR', 12, 6],
'USD 0 surcharge' => [5.345, 'USD', 0, 5.34],
'USD 1% surcharge' => [5.345, 'USD', 1, 5.4],
];
}
/**
* Provider for test_get_cost_as_string
*
* @return array[]
*/
public function get_cost_as_string_provider(): array {
return [
'IRR 0 surcharge' => [5.345, 'IRR', 0, 'IRR'."\xc2\xa0".'5'],
'IRR 12% surcharge' => [5.345, 'IRR', 12, 'IRR'."\xc2\xa0".'6'],
'USD 0 surcharge' => [5.345, 'USD', 0, 'USD'."\xc2\xa0".'5.34'],
'USD 1% surcharge' => [5.345, 'USD', 1, 'USD'."\xc2\xa0".'5.40'],
];
}
/**
* Test for test_format_cost function
*
* @dataProvider get_rounded_cost_provider
* @param float $amount
* @param string $currency
* @param float $surcharge
* @param string $expected
*/
public function test_get_rounded_cost(float $amount, string $currency, float $surcharge, float $expected): void {
$this->assertEquals($expected, helper::get_rounded_cost($amount, $currency, $surcharge));
}
/**
* Test for get_cost_as_string function
*
* @dataProvider get_cost_as_string_provider
* @param float $amount
* @param string $currency
* @param float $surcharge
* @param string $expected
*/
public function test_get_cost_as_string(float $amount, string $currency, float $surcharge, string $expected): void {
// Some old ICU versions have a bug, where they don't follow the CLDR and they are
// missing the non-breaking-space between the currency abbreviation and the value.
// i.e. it returns AUD50 instead of AU\xc2\xa050). See the following issues @ ICU:
// - https://unicode-org.atlassian.net/browse/ICU-6560
// - https://unicode-org.atlassian.net/browse/ICU-8853
// - https://unicode-org.atlassian.net/browse/ICU-8840
// It has been detected that versions prior to ICU-61.1 / ICU-62.1 come with this
// problem. Noticeably some CI images (as of December 2021) use buggy ICU-60.1.
// So, here, we are going to dynamically verify the behaviour and skip the
// test when buggy one is found. No need to apply this to code as dar as the real
// formatting is not critical for the functionality (just small glitch).
if ('IRR5' === (new \NumberFormatter('en-AU', \NumberFormatter::CURRENCY))->formatCurrency(5, 'IRR')) {
$this->markTestSkipped('Old ICU libraries behavior (ICU < 62), skipping this tests');
}
$this->assertEquals($expected, helper::get_cost_as_string($amount, $currency, $surcharge));
}
}
+5
View File
@@ -0,0 +1,5 @@
This files describes API changes in /payment/*,
information provided here is intended especially for developers.
=== 3.11 ===
* Service provider plugins using the payment subsystem are now required to implement the get_success_url method.