MDL-69166 core_payment: Add paymentarea to the payment subsystem
[moodle.git] / payment / gateway / paypal / amd / src / gateways_modal.js
CommitLineData
149e530c
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 * This module is responsible for PayPal content in the gateways modal.
18 *
19 * @module pg_paypal/gateway_modal
20 * @copyright 2020 Shamim Rezaie <shamim@moodle.com>
21 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22 */
23
24import * as Repository from './repository';
25import Templates from 'core/templates';
149e530c 26import Truncate from 'core/truncate';
e1151bf6 27import Ajax from 'core/ajax';
ed1cc025 28import ModalFactory from 'core/modal_factory';
d00c00dd 29import ModalEvents from 'core/modal_events';
048a6521 30import {get_string as getString} from 'core/str';
149e530c
SR
31
32/**
ed1cc025 33 * Creates and shows a modal that contains a placeholder.
149e530c 34 *
ed1cc025 35 * @returns {Promise<Modal>}
149e530c 36 */
048a6521 37const showModalWithPlaceholder = async() => {
ed1cc025 38 const modal = await ModalFactory.create({
ed1cc025
SR
39 body: await Templates.render('pg_paypal/paypal_button_placeholder', {})
40 });
41 modal.show();
42 return modal;
149e530c
SR
43};
44
ed1cc025
SR
45/**
46 * Process the payment.
47 *
7d10f352
SR
48 * @param {string} component Name of the component that the componentId belongs to
49 * @param {string} paymentArea The area of the component that the componentId belongs to
50 * @param {number} componentId An internal identifier that is used by the component
ed1cc025 51 * @param {string} description Description of the payment
d00c00dd 52 * @param {processCallback} callback The callback function to call when processing is finished
ed1cc025
SR
53 * @returns {Promise<void>}
54 */
7d10f352 55export const process = async(component, paymentArea, componentId, description, callback) => {
149e530c
SR
56
57 const [
ed1cc025 58 modal,
149e530c
SR
59 paypalConfig,
60 ] = await Promise.all([
048a6521 61 showModalWithPlaceholder(),
7d10f352 62 Repository.getConfigForJs(component, paymentArea, componentId),
149e530c 63 ]);
15a4e4c8
MG
64 const currency = paypalConfig.currency;
65 const amount = paypalConfig.cost; // Cost with surcharge.
149e530c 66
d00c00dd
SR
67 modal.getRoot().on(ModalEvents.hidden, () => {
68 // Destroy when hidden.
69 modal.destroy();
70 });
71
048a6521 72 const paypalScript = `https://www.paypal.com/sdk/js?client-id=${paypalConfig.clientid}&currency=${currency}`;
149e530c
SR
73
74 callExternalFunction(paypalScript, () => {
048a6521
SR
75 modal.setBody(''); // We have to clear the body. The render method in paypal.Buttons will render everything.
76
149e530c 77 paypal.Buttons({ // eslint-disable-line
048a6521 78 // Set up the transaction.
149e530c 79 createOrder: function(data, actions) {
149e530c
SR
80 return actions.order.create({
81 purchase_units: [{ // eslint-disable-line
82 amount: {
83 currency_code: currency, // eslint-disable-line
84 value: amount
ab6ca275
SR
85 },
86 description: Truncate.truncate(description, {length: 127, stripTags: true}),
149e530c
SR
87 }],
88 application_context: { // eslint-disable-line
89 shipping_preference: 'NO_SHIPPING', // eslint-disable-line
90 brand_name: Truncate.truncate(paypalConfig.brandname, {length: 127, stripTags: true}), // eslint-disable-line
91 },
92 });
93 },
048a6521
SR
94 // Finalise the transaction.
95 onApprove: function(data) {
0cf4bb2a
SR
96 modal.getRoot().on(ModalEvents.outsideClick, (e) => {
97 // Prevent closing the modal when clicking outside of it.
98 e.preventDefault();
99 });
100
048a6521 101 modal.setBody(getString('authorising', 'pg_paypal'));
e1151bf6 102
048a6521
SR
103 // Call server to validate and capture payment for order.
104 return Ajax.call([{
105 methodname: 'pg_paypal_create_transaction_complete',
106 args: {
107 component,
7d10f352
SR
108 paymentarea: paymentArea,
109 componentid: componentId,
048a6521
SR
110 orderid: data.orderID,
111 },
112 }])[0]
113 .then(function(res) {
114 modal.hide();
115 return callback(res);
e1151bf6
SR
116 });
117 }
ed1cc025 118 }).render(modal.getBody()[0]);
149e530c
SR
119 });
120};
121
d00c00dd
SR
122/**
123 * The callback definition for process.
124 *
125 * @callback processCallback
126 * @param {bool} success
127 * @param {string} message
128 */
129
ed1cc025
SR
130/**
131 * Calls a function from an external javascript file.
132 *
133 * @param {string} jsFile URL of the external JavaScript file
134 * @param {function} func The function to call
135 */
136const callExternalFunction = (jsFile, func) => {
137 // Check to see if this file has already been loaded. If so just go straight to the func.
b9789963 138 if (callExternalFunction.currentlyloaded == jsFile) {
ed1cc025 139 func();
149e530c
SR
140 return;
141 }
142
b9789963
SR
143 // PayPal can only work with one currency at the same time. We have to unload the previously loaded script
144 // if it was loaded for a different currency. Weird way indeed, but the only way.
145 // See: https://github.com/paypal/paypal-checkout-components/issues/1180
146 if (callExternalFunction.currentlyloaded) {
147 const suspectedScript = document.querySelector(`script[src="${callExternalFunction.currentlyloaded}"]`);
148 if (suspectedScript) {
149 suspectedScript.parentNode.removeChild(suspectedScript);
150 }
151 }
152
149e530c
SR
153 const script = document.createElement('script');
154
155 if (script.readyState) {
156 script.onreadystatechange = function() {
157 if (this.readyState == 'complete' || this.readyState == 'loaded') {
158 this.onreadystatechange = null;
ed1cc025 159 func();
149e530c
SR
160 }
161 };
162 } else {
163 script.onload = function() {
ed1cc025 164 func();
149e530c
SR
165 };
166 }
167
168 script.setAttribute('src', jsFile);
169 document.head.appendChild(script);
170
b9789963 171 callExternalFunction.currentlyloaded = jsFile;
149e530c
SR
172};
173
ed1cc025 174/**
b9789963 175 * Holds the full url of loaded external JavaScript file.
ed1cc025
SR
176 *
177 * @static
b9789963 178 * @type {string}
ed1cc025 179 */
b9789963 180callExternalFunction.currentlyloaded = '';