MDL-69166 core_payment: Use promises instead of callbacks
authorShamim Rezaie <shamim@moodle.com>
Sun, 18 Oct 2020 15:31:44 +0000 (02:31 +1100)
committerShamim Rezaie <shamim@moodle.com>
Tue, 27 Oct 2020 04:45:25 +0000 (15:45 +1100)
payment/amd/build/gateways_modal.min.js
payment/amd/build/gateways_modal.min.js.map
payment/amd/src/gateways_modal.js
payment/gateway/paypal/amd/build/gateways_modal.min.js
payment/gateway/paypal/amd/build/gateways_modal.min.js.map
payment/gateway/paypal/amd/build/repository.min.js
payment/gateway/paypal/amd/build/repository.min.js.map
payment/gateway/paypal/amd/src/gateways_modal.js
payment/gateway/paypal/amd/src/repository.js

index d177a3d..3cda810 100644 (file)
Binary files a/payment/amd/build/gateways_modal.min.js and b/payment/amd/build/gateways_modal.min.js differ
index cc38c76..d215e53 100644 (file)
Binary files a/payment/amd/build/gateways_modal.min.js.map and b/payment/amd/build/gateways_modal.min.js.map differ
index 3ae9f66..5d07448 100644 (file)
@@ -87,25 +87,25 @@ const show = async(rootNode, {
                 rootNode.dataset.component,
                 rootNode.dataset.paymentarea,
                 rootNode.dataset.itemid,
                 rootNode.dataset.component,
                 rootNode.dataset.paymentarea,
                 rootNode.dataset.itemid,
-                rootNode.dataset.description,
-                ({success, message = ''}) => {
-                    modal.hide();
-                    if (success) {
-                        Notification.addNotification({
-                            message: message,
-                            type: 'success',
-                        });
-                        location.reload();
-                    } else {
-                        Notification.alert('', message);
-                    }
-                },
-            );
+                rootNode.dataset.description
+            )
+            .then(message => {
+                modal.hide();
+                Notification.addNotification({
+                    message: message,
+                    type: 'success',
+                });
+                location.reload();
+
+                // The following return statement is never reached. It is put here just to make eslint happy.
+                return message;
+            })
+            .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.
         } 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.
-            getString('nogatewayselected', 'core_payment').then(message => addToast(message));
+            getString('nogatewayselected', 'core_payment').then(message => addToast(message)).catch();
         }
 
         e.preventDefault();
         }
 
         e.preventDefault();
@@ -166,22 +166,13 @@ const updateCostRegion = async(root, defaultCost = '') => {
  * @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
  * @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
- * @param {processPaymentCallback} callback The callback function to call when processing is finished
- * @returns {Promise<void>}
+ * @returns {Promise<string>}
  */
  */
-const processPayment = async(gateway, component, paymentArea, itemId, description, callback) => {
+const processPayment = async(gateway, component, paymentArea, itemId, description) => {
     const paymentMethod = await import(`paygw_${gateway}/gateways_modal`);
     const paymentMethod = await import(`paygw_${gateway}/gateways_modal`);
-    paymentMethod.process(component, paymentArea, itemId, description, callback);
+    return paymentMethod.process(component, paymentArea, itemId, description);
 };
 
 };
 
-/**
- * The callback definition for processPayment.
- *
- * @callback processPaymentCallback
- * @param {bool} success
- * @param {string} message
- */
-
 /**
  * Set up the payment actions.
  */
 /**
  * Set up the payment actions.
  */
index 8faa42b..21236a0 100644 (file)
Binary files a/payment/gateway/paypal/amd/build/gateways_modal.min.js and b/payment/gateway/paypal/amd/build/gateways_modal.min.js differ
index e3a4b5e..62f3228 100644 (file)
Binary files a/payment/gateway/paypal/amd/build/gateways_modal.min.js.map and b/payment/gateway/paypal/amd/build/gateways_modal.min.js.map differ
index 83d0ead..72cdeb9 100644 (file)
Binary files a/payment/gateway/paypal/amd/build/repository.min.js and b/payment/gateway/paypal/amd/build/repository.min.js differ
index 9f0f383..792cd9b 100644 (file)
Binary files a/payment/gateway/paypal/amd/build/repository.min.js.map and b/payment/gateway/paypal/amd/build/repository.min.js.map differ
index 4d68227..04f8a96 100644 (file)
@@ -24,7 +24,6 @@
 import * as Repository from './repository';
 import Templates from 'core/templates';
 import Truncate from 'core/truncate';
 import * as Repository from './repository';
 import Templates from 'core/templates';
 import Truncate from 'core/truncate';
-import Ajax from 'core/ajax';
 import ModalFactory from 'core/modal_factory';
 import ModalEvents from 'core/modal_events';
 import {get_string as getString} from 'core/str';
 import ModalFactory from 'core/modal_factory';
 import ModalEvents from 'core/modal_events';
 import {get_string as getString} from 'core/str';
@@ -49,102 +48,95 @@ const showModalWithPlaceholder = async() => {
  * @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
  * @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
- * @param {processCallback} callback The callback function to call when processing is finished
- * @returns {Promise<void>}
+ * @returns {Promise<string>}
  */
  */
-export const process = async(component, paymentArea, itemId, description, callback) => {
-
-    const [
-        modal,
-        paypalConfig,
-    ] = await Promise.all([
+export const process = (component, paymentArea, itemId, description) => {
+    return Promise.all([
         showModalWithPlaceholder(),
         Repository.getConfigForJs(component, paymentArea, itemId),
         showModalWithPlaceholder(),
         Repository.getConfigForJs(component, paymentArea, itemId),
-    ]);
-    const currency = paypalConfig.currency;
-    const amount = paypalConfig.cost; // Cost with surcharge.
-
-    modal.getRoot().on(ModalEvents.hidden, () => {
-        // Destroy when hidden.
-        modal.destroy();
-    });
-
-    const paypalScript = `https://www.paypal.com/sdk/js?client-id=${paypalConfig.clientid}&currency=${currency}`;
-
-    callExternalFunction(paypalScript, () => {
-        modal.setBody(''); // We have to clear the body. The render method in paypal.Buttons will render everything.
-
-        paypal.Buttons({ // eslint-disable-line
-            // Set up the transaction.
-            createOrder: function(data, actions) {
-                return actions.order.create({
-                    purchase_units: [{ // eslint-disable-line
-                        amount: {
-                            currency_code: currency, // eslint-disable-line
-                            value: amount
+    ])
+    .then(([modal, paypalConfig]) => {
+        modal.getRoot().on(ModalEvents.hidden, () => {
+            // Destroy when hidden.
+            modal.destroy();
+        });
+
+        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
                         },
                         },
-                        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'));
-
-                // Call server to validate and capture payment for order.
-                return Ajax.call([{
-                    methodname: 'paygw_paypal_create_transaction_complete',
-                    args: {
-                        component,
-                        paymentarea: paymentArea,
-                        itemid: itemId,
-                        orderid: data.orderID,
-                    },
-                }])[0]
-                .then(function(res) {
-                    modal.hide();
-                    return callback(res);
-                });
-            }
-        }).render(modal.getBody()[0]);
+                    });
+                },
+                // 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);
     });
 };
 
 /**
     });
 };
 
 /**
- * The callback definition for process.
+ * Unloads the previously loaded PayPal JavaScript SDK, and loads a new one.
  *
  *
- * @callback processCallback
- * @param {bool} success
- * @param {string} message
+ * @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}`;
 
 
-/**
- * Calls a function from an external javascript file.
- *
- * @param {string} jsFile URL of the external JavaScript file
- * @param {function} func The function to call
- */
-const callExternalFunction = (jsFile, func) => {
     // Check to see if this file has already been loaded. If so just go straight to the func.
     // Check to see if this file has already been loaded. If so just go straight to the func.
-    if (callExternalFunction.currentlyloaded == jsFile) {
-        func();
-        return;
+    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
     }
 
     // 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 (callExternalFunction.currentlyloaded) {
-        const suspectedScript = document.querySelector(`script[src="${callExternalFunction.currentlyloaded}"]`);
+    if (switchSdk.currentlyloaded) {
+        const suspectedScript = document.querySelector(`script[src="${switchSdk.currentlyloaded}"]`);
         if (suspectedScript) {
             suspectedScript.parentNode.removeChild(suspectedScript);
         }
         if (suspectedScript) {
             suspectedScript.parentNode.removeChild(suspectedScript);
         }
@@ -152,29 +144,31 @@ const callExternalFunction = (jsFile, func) => {
 
     const script = document.createElement('script');
 
 
     const script = document.createElement('script');
 
-    if (script.readyState) {
-        script.onreadystatechange = function() {
-            if (this.readyState == 'complete' || this.readyState == 'loaded') {
-                this.onreadystatechange = null;
-                func();
-            }
-        };
-    } else {
-        script.onload = function() {
-            func();
-        };
-    }
+    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', jsFile);
-    document.head.appendChild(script);
+        script.setAttribute('src', sdkUrl);
+        document.head.appendChild(script);
 
 
-    callExternalFunction.currentlyloaded = jsFile;
+        switchSdk.currentlyloaded = sdkUrl;
+    });
 };
 
 /**
 };
 
 /**
- * Holds the full url of loaded external JavaScript file.
+ * Holds the full url of loaded PayPal JavaScript SDK.
  *
  * @static
  * @type {string}
  */
  *
  * @static
  * @type {string}
  */
-callExternalFunction.currentlyloaded = '';
+switchSdk.currentlyloaded = '';
index 627753e..facec04 100644 (file)
@@ -30,7 +30,7 @@ import Ajax from 'core/ajax';
  * @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} 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}>}
+ * @returns {Promise<{clientid: string, brandname: string, cost: number, currency: string}>}
  */
 export const getConfigForJs = (component, paymentArea, itemId) => {
     const request = {
  */
 export const getConfigForJs = (component, paymentArea, itemId) => {
     const request = {
@@ -44,3 +44,26 @@ export const getConfigForJs = (component, paymentArea, itemId) => {
 
     return Ajax.call([request])[0];
 };
 
     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];
+};