on-demand release 4.0dev+
[moodle.git] / payment / amd / src / gateways_modal.js
CommitLineData
e9de4309
SR
1// This file is part of Moodle - http://moodle.org/
2//
3// Moodle is free software: you can redistribute it and/or modify
4// it under the terms of the GNU General Public License as published by
5// the Free Software Foundation, either version 3 of the License, or
6// (at your option) any later version.
7//
8// Moodle is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11// GNU General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
15
16/**
17 * Contain the logic for the gateways modal.
18 *
19 * @module core_payment/gateways_modal
e9de4309
SR
20 * @copyright 2019 Shamim Rezaie <shamim@moodle.com>
21 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22 */
23
24import ModalFactory from 'core/modal_factory';
25import Templates from 'core/templates';
26import {get_string as getString} from 'core/str';
71ccaf20 27import {getAvailableGateways} from './repository';
e9de4309 28import Selectors from './selectors';
ad6df317 29import ModalEvents from 'core/modal_events';
b23dcc37 30import PaymentEvents from 'core_payment/events';
8ef156b7 31import {add as addToast, addToastRegion} from 'core/toast';
b5507b86 32import Notification from 'core/notification';
b23dcc37 33import ModalGateways from './modal_gateways';
e9de4309
SR
34
35/**
36 * Register event listeners for the module.
e9de4309 37 */
f5d94d63
SR
38const registerEventListeners = () => {
39 document.addEventListener('click', e => {
40 const gatewayTrigger = e.target.closest('[data-action="core_payment/triggerPayment"]');
41 if (gatewayTrigger) {
42 e.preventDefault();
e9de4309 43
f5d94d63
SR
44 show(gatewayTrigger, {focusOnClose: e.target});
45 }
e9de4309
SR
46 });
47};
48
49/**
50 * Shows the gateway selector modal.
51 *
52 * @param {HTMLElement} rootNode
53 * @param {Object} options - Additional options
54 * @param {HTMLElement} options.focusOnClose The element to focus on when the modal is closed.
55 */
ad6df317 56const show = async(rootNode, {
e9de4309
SR
57 focusOnClose = null,
58} = {}) => {
ad6df317 59 const modal = await ModalFactory.create({
b23dcc37 60 type: ModalGateways.TYPE,
ad6df317
SR
61 title: await getString('selectpaymenttype', 'core_payment'),
62 body: await Templates.render('core_payment/gateways_modal', {}),
63 });
e9de4309 64
e3e83185
SR
65 const rootElement = modal.getRoot()[0];
66 addToastRegion(rootElement);
8ef156b7 67
ad6df317 68 modal.show();
e9de4309 69
ad6df317
SR
70 modal.getRoot().on(ModalEvents.hidden, () => {
71 // Destroy when hidden.
72 modal.destroy();
73 try {
74 focusOnClose.focus();
75 } catch (e) {
76 // eslint-disable-line
77 }
78 });
e9de4309 79
b23dcc37 80 modal.getRoot().on(PaymentEvents.proceed, (e) => {
e3e83185 81 const gateway = (rootElement.querySelector(Selectors.values.gateway) || {value: ''}).value;
8ef156b7 82
ad6df317
SR
83 if (gateway) {
84 processPayment(
85 gateway,
ad6df317 86 rootNode.dataset.component,
61766b3d 87 rootNode.dataset.paymentarea,
d5a9d6e5 88 rootNode.dataset.itemid,
0ecce652
SR
89 rootNode.dataset.description
90 )
91 .then(message => {
92 modal.hide();
93 Notification.addNotification({
94 message: message,
95 type: 'success',
96 });
93178492 97 location.href = rootNode.dataset.successurl;
0ecce652
SR
98
99 // The following return statement is never reached. It is put here just to make eslint happy.
100 return message;
101 })
102 .catch(message => Notification.alert('', message));
ad6df317
SR
103 } else {
104 // We cannot use await in the following line.
105 // The reason is that we are preventing the default action of the save event being triggered,
106 // therefore we cannot define the event handler function asynchronous.
e8d7817f 107 getString('nogatewayselected', 'core_payment').then(message => addToast(message, {type: 'warning'})).catch();
ad6df317 108 }
8ef156b7 109
ad6df317 110 e.preventDefault();
8ef156b7 111 });
ad6df317 112
e3e83185
SR
113 // Re-calculate the cost when gateway is changed.
114 rootElement.addEventListener('change', e => {
115 if (e.target.matches(Selectors.elements.gateways)) {
73527fa2 116 updateCostRegion(rootElement, rootNode.dataset.cost);
e3e83185
SR
117 }
118 });
119
d5a9d6e5 120 const gateways = await getAvailableGateways(rootNode.dataset.component, rootNode.dataset.paymentarea, rootNode.dataset.itemid);
ad6df317
SR
121 const context = {
122 gateways
123 };
124
125 const {html, js} = await Templates.renderForPromise('core_payment/gateways', context);
e3e83185 126 Templates.replaceNodeContents(rootElement.querySelector(Selectors.regions.gatewaysContainer), html, js);
e25eb2c0 127 selectSingleGateway(rootElement);
73527fa2 128 await updateCostRegion(rootElement, rootNode.dataset.cost);
8ef156b7 129};
11b2d9e9 130
e25eb2c0
SR
131/**
132 * Auto-select the gateway if there is only one gateway.
133 *
134 * @param {HTMLElement} root An HTMLElement that contains the cost region
135 */
136const selectSingleGateway = root => {
137 const gateways = root.querySelectorAll(Selectors.elements.gateways);
138
139 if (gateways.length == 1) {
140 gateways[0].checked = true;
141 }
142};
143
11b2d9e9
SR
144/**
145 * Shows the cost of the item the user is purchasing in the cost region.
146 *
147 * @param {HTMLElement} root An HTMLElement that contains the cost region
73527fa2 148 * @param {string} defaultCost The default cost that is going to be displayed if no gateway is selected
11b2d9e9
SR
149 * @returns {Promise<void>}
150 */
73527fa2
SR
151const updateCostRegion = async(root, defaultCost = '') => {
152 const gatewayElement = root.querySelector(Selectors.values.gateway);
153 const surcharge = parseInt((gatewayElement || {dataset: {surcharge: 0}}).dataset.surcharge);
154 const cost = (gatewayElement || {dataset: {cost: defaultCost}}).dataset.cost;
11b2d9e9 155
1d479dc0 156 const {html, js} = await Templates.renderForPromise('core_payment/fee_breakdown', {fee: cost, surcharge});
11b2d9e9
SR
157 Templates.replaceNodeContents(root.querySelector(Selectors.regions.costContainer), html, js);
158};
8ef156b7
SR
159
160/**
161 * Process payment using the selected gateway.
162 *
8ef156b7 163 * @param {string} gateway The gateway to be used for payment
d5a9d6e5
SR
164 * @param {string} component Name of the component that the itemId belongs to
165 * @param {string} paymentArea Name of the area in the component that the itemId belongs to
166 * @param {number} itemId An internal identifier that is used by the component
03f20edb 167 * @param {string} description Description of the payment
0ecce652 168 * @returns {Promise<string>}
8ef156b7 169 */
0ecce652 170const processPayment = async(gateway, component, paymentArea, itemId, description) => {
2d7feb75 171 const paymentMethod = await import(`paygw_${gateway}/gateways_modal`);
0ecce652 172 return paymentMethod.process(component, paymentArea, itemId, description);
e9de4309 173};
b5507b86 174
f5d94d63
SR
175/**
176 * Set up the payment actions.
177 */
178export const init = () => {
179 if (!init.initialised) {
180 // Event listeners should only be registered once.
181 init.initialised = true;
182 registerEventListeners();
183 }
184};
185
186/**
187 * Whether the init function was called before.
188 *
189 * @static
190 * @type {boolean}
191 */
192init.initialised = false;