Commit | Line | Data |
---|---|---|
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 | ||
24 | import * as Repository from './repository'; | |
25 | import Templates from 'core/templates'; | |
149e530c | 26 | import Truncate from 'core/truncate'; |
ed1cc025 | 27 | import ModalFactory from 'core/modal_factory'; |
d00c00dd | 28 | import ModalEvents from 'core/modal_events'; |
048a6521 | 29 | import {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 | 36 | const 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 |
53 | export 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 |
127 | const switchSdk = (clientId, currency) => { |
128 | const sdkUrl = `https://www.paypal.com/sdk/js?client-id=${clientId}¤cy=${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 | 174 | switchSdk.currentlyloaded = ''; |