MDL-69166 core_payment: Use promises instead of callbacks
[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 *
6b3d163a 19 * @module paygw_paypal/gateway_modal
149e530c
SR
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';
ed1cc025 27import ModalFactory from 'core/modal_factory';
d00c00dd 28import ModalEvents from 'core/modal_events';
048a6521 29import {get_string as getString} from 'core/str';
149e530c
SR
30
31/**
ed1cc025 32 * Creates and shows a modal that contains a placeholder.
149e530c 33 *
ed1cc025 34 * @returns {Promise<Modal>}
149e530c 35 */
048a6521 36const showModalWithPlaceholder = async() => {
ed1cc025 37 const modal = await ModalFactory.create({
6b3d163a 38 body: await Templates.render('paygw_paypal/paypal_button_placeholder', {})
ed1cc025
SR
39 });
40 modal.show();
41 return modal;
149e530c
SR
42};
43
ed1cc025
SR
44/**
45 * Process the payment.
46 *
52305c9a
SR
47 * @param {string} component Name of the component that the itemId belongs to
48 * @param {string} paymentArea The area of the component that the itemId belongs to
49 * @param {number} itemId An internal identifier that is used by the component
ed1cc025 50 * @param {string} description Description of the payment
0fb7847e 51 * @returns {Promise<string>}
ed1cc025 52 */
0fb7847e
SR
53export const process = (component, paymentArea, itemId, description) => {
54 return Promise.all([
048a6521 55 showModalWithPlaceholder(),
52305c9a 56 Repository.getConfigForJs(component, paymentArea, itemId),
0fb7847e
SR
57 ])
58 .then(([modal, paypalConfig]) => {
59 modal.getRoot().on(ModalEvents.hidden, () => {
60 // Destroy when hidden.
61 modal.destroy();
62 });
63
64 return Promise.all([
65 modal,
66 paypalConfig,
67 switchSdk(paypalConfig.clientid, paypalConfig.currency),
68 ]);
69 })
70 .then(([modal, paypalConfig]) => {
71 // We have to clear the body. The render method in paypal.Buttons will render everything.
72 modal.setBody('');
73
74 return new Promise(resolve => {
75 window.paypal.Buttons({
76 // Set up the transaction.
77 createOrder: function(data, actions) {
78 return actions.order.create({
79 purchase_units: [{ // eslint-disable-line
80 amount: {
81 currency_code: paypalConfig.currency_code, // eslint-disable-line
82 value: paypalConfig.cost,
83 },
84 description: Truncate.truncate(description, {length: 127, stripTags: true}),
85 }],
86 application_context: { // eslint-disable-line
87 shipping_preference: 'NO_SHIPPING', // eslint-disable-line
88 brand_name: Truncate.truncate(paypalConfig.brandname, {length: 127, stripTags: true}), // eslint-disable-line
ab6ca275 89 },
0fb7847e
SR
90 });
91 },
92 // Finalise the transaction.
93 onApprove: function(data) {
94 modal.getRoot().on(ModalEvents.outsideClick, (e) => {
95 // Prevent closing the modal when clicking outside of it.
96 e.preventDefault();
97 });
98
99 modal.setBody(getString('authorising', 'paygw_paypal'));
100
101 Repository.markTransactionComplete(component, paymentArea, itemId, data.orderID)
102 .then(res => {
103 modal.hide();
104 return res;
105 })
106 .then(resolve);
107 }
108 }).render(modal.getBody()[0]);
109 });
110 })
111 .then(res => {
112 if (res.success) {
113 return Promise.resolve(res.message);
114 }
115
116 return Promise.reject(res.message);
149e530c
SR
117 });
118};
119
d00c00dd 120/**
0fb7847e 121 * Unloads the previously loaded PayPal JavaScript SDK, and loads a new one.
d00c00dd 122 *
0fb7847e
SR
123 * @param {string} clientId PayPal client ID
124 * @param {string} currency The currency
125 * @returns {Promise}
d00c00dd 126 */
0fb7847e
SR
127const switchSdk = (clientId, currency) => {
128 const sdkUrl = `https://www.paypal.com/sdk/js?client-id=${clientId}&currency=${currency}`;
d00c00dd 129
ed1cc025 130 // Check to see if this file has already been loaded. If so just go straight to the func.
0fb7847e
SR
131 if (switchSdk.currentlyloaded === sdkUrl) {
132 return Promise.resolve();
149e530c
SR
133 }
134
b9789963
SR
135 // PayPal can only work with one currency at the same time. We have to unload the previously loaded script
136 // if it was loaded for a different currency. Weird way indeed, but the only way.
137 // See: https://github.com/paypal/paypal-checkout-components/issues/1180
0fb7847e
SR
138 if (switchSdk.currentlyloaded) {
139 const suspectedScript = document.querySelector(`script[src="${switchSdk.currentlyloaded}"]`);
b9789963
SR
140 if (suspectedScript) {
141 suspectedScript.parentNode.removeChild(suspectedScript);
142 }
143 }
144
149e530c
SR
145 const script = document.createElement('script');
146
0fb7847e
SR
147 return new Promise(resolve => {
148 if (script.readyState) {
149 script.onreadystatechange = function() {
150 if (this.readyState == 'complete' || this.readyState == 'loaded') {
151 this.onreadystatechange = null;
152 resolve();
153 }
154 };
155 } else {
156 script.onload = function() {
157 resolve();
158 };
159 }
149e530c 160
0fb7847e
SR
161 script.setAttribute('src', sdkUrl);
162 document.head.appendChild(script);
149e530c 163
0fb7847e
SR
164 switchSdk.currentlyloaded = sdkUrl;
165 });
149e530c
SR
166};
167
ed1cc025 168/**
0fb7847e 169 * Holds the full url of loaded PayPal JavaScript SDK.
ed1cc025
SR
170 *
171 * @static
b9789963 172 * @type {string}
ed1cc025 173 */
0fb7847e 174switchSdk.currentlyloaded = '';