MDL-69166 core_payment: payment gateways can have a surcharge
authorShamim Rezaie <shamim@moodle.com>
Tue, 30 Jun 2020 07:37:50 +0000 (17:37 +1000)
committerShamim Rezaie <shamim@moodle.com>
Tue, 27 Oct 2020 04:44:59 +0000 (15:44 +1100)
13 files changed:
lang/en/payment.php
payment/amd/build/gateways_modal.min.js
payment/amd/build/gateways_modal.min.js.map
payment/amd/build/selectors.min.js
payment/amd/build/selectors.min.js.map
payment/amd/src/gateways_modal.js
payment/amd/src/selectors.js
payment/classes/external/get_gateways_for_currency.php
payment/classes/helper.php
payment/gateway/paypal/classes/external/transaction_complete.php
payment/gateway/paypal/settings.php
payment/templates/fee_breakdown.mustache
payment/templates/gateway.mustache

index 632f3be..b2cb516 100644 (file)
  */
 
 $string['callbacknotimplemented'] = 'The callback is not implemented for component {$a}.';
+$string['feeincludesurcharge'] = '{$a->fee} (includes {$a->surcharge}% surcharge for using this payment type)';
 $string['nogateway'] = 'There is no payment gateway that can be used.';
 $string['nogatewayselected'] = 'You first need to select a payment gateway.';
 $string['selectpaymenttype'] = 'Select payment type';
 $string['supportedcurrencies'] = 'Supported currencies';
+$string['surcharge'] = 'Surcharge (percentage)';
+$string['surcharge_desc'] = 'The surcharge is an additional percentage charged to users who choose to pay using this payment gateway.';
index aa2086b..963edab 100644 (file)
Binary files a/payment/amd/build/gateways_modal.min.js and b/payment/amd/build/gateways_modal.min.js differ
index 0a2256d..2b632d4 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 ac36b96..21da99c 100644 (file)
Binary files a/payment/amd/build/selectors.min.js and b/payment/amd/build/selectors.min.js differ
index e6532f2..e8a7ee1 100644 (file)
Binary files a/payment/amd/build/selectors.min.js.map and b/payment/amd/build/selectors.min.js.map differ
index e1488d0..a115e71 100644 (file)
@@ -72,7 +72,8 @@ const show = async(rootNode, {
         body: await Templates.render('core_payment/gateways_modal', {}),
     });
 
-    addToastRegion(modal.getRoot()[0]);
+    const rootElement = modal.getRoot()[0];
+    addToastRegion(rootElement);
 
     modal.show();
 
@@ -87,14 +88,17 @@ const show = async(rootNode, {
     });
 
     modal.getRoot().on(PaymentEvents.proceed, (e) => {
-        const root = modal.getRoot()[0];
-        const gateway = (root.querySelector(Selectors.values.gateway) || {value: ''}).value;
+        const gateway = (rootElement.querySelector(Selectors.values.gateway) || {value: ''}).value;
 
         if (gateway) {
             processPayment(
                 gateway,
-                rootNode.dataset.amount,
-                rootNode.dataset.currency,
+                {
+                    value: parseFloat(rootNode.dataset.amount),
+                    currency: rootNode.dataset.currency,
+                    surcharge: parseInt((rootElement.querySelector(Selectors.values.gateway) || {dataset: {surcharge: 0}})
+                        .dataset.surcharge),
+                },
                 rootNode.dataset.component,
                 rootNode.dataset.componentid,
                 rootNode.dataset.description,
@@ -121,6 +125,13 @@ const show = async(rootNode, {
         e.preventDefault();
     });
 
+    // Re-calculate the cost when gateway is changed.
+    rootElement.addEventListener('change', e => {
+        if (e.target.matches(Selectors.elements.gateways)) {
+            updateCostRegion(rootElement, parseFloat(rootNode.dataset.amount), rootNode.dataset.currency);
+        }
+    });
+
     const currency = rootNode.dataset.currency;
     const gateways = await getGatewaysSupportingCurrency(currency);
     const context = {
@@ -128,9 +139,8 @@ const show = async(rootNode, {
     };
 
     const {html, js} = await Templates.renderForPromise('core_payment/gateways', context);
-    const root = modal.getRoot()[0];
-    Templates.replaceNodeContents(root.querySelector(Selectors.regions.gatewaysContainer), html, js);
-    updateCostRegion(root, parseFloat(rootNode.dataset.amount), rootNode.dataset.currency);
+    Templates.replaceNodeContents(rootElement.querySelector(Selectors.regions.gatewaysContainer), html, js);
+    await updateCostRegion(rootElement, parseFloat(rootNode.dataset.amount), rootNode.dataset.currency);
 };
 
 /**
@@ -143,9 +153,11 @@ const show = async(rootNode, {
  */
 const updateCostRegion = async(root, amount, currency) => {
     const locale = await updateCostRegion.locale; // This only takes a bit the first time.
+    const surcharge = parseInt((root.querySelector(Selectors.values.gateway) || {dataset: {surcharge: 0}}).dataset.surcharge);
+    amount += amount * surcharge / 100;
     const localisedCost = amount.toLocaleString(locale, {style: "currency", currency: currency});
 
-    const {html, js} = await Templates.renderForPromise('core_payment/fee_breakdown', {fee: localisedCost});
+    const {html, js} = await Templates.renderForPromise('core_payment/fee_breakdown', {fee: localisedCost, surcharge});
     Templates.replaceNodeContents(root.querySelector(Selectors.regions.costContainer), html, js);
 };
 updateCostRegion.locale = getString("localecldr", "langconfig");
@@ -154,18 +166,21 @@ updateCostRegion.locale = getString("localecldr", "langconfig");
  * Process payment using the selected gateway.
  *
  * @param {string} gateway The gateway to be used for payment
- * @param {number} amount Amount of payment
- * @param {string} currency The currency in the three-character ISO-4217 format
+ * @param {Object} amount - Amount of payment
+ * @param {number} amount.value The numerical part of the amount
+ * @param {string} amount.currency The currency part of the amount in the three-character ISO-4217 format
+ * @param {number} amount.surcharge The surcharge percentage that should be added to the amount
  * @param {string} component Name of the component that the componentid belongs to
  * @param {number} componentid 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>}
  */
-const processPayment = async(gateway, amount, currency, component, componentid, description, callback) => {
+const processPayment = async(gateway, {value, currency, surcharge = 0}, component, componentid, description, callback) => {
     const paymentMethod = await import(`pg_${gateway}/gateways_modal`);
 
-    paymentMethod.process(amount, currency, component, componentid, description, callback);
+    value += value * surcharge / 100;
+    paymentMethod.process(value, currency, component, componentid, description, callback);
 };
 
 /**
index 1a571e8..3fea06c 100644 (file)
@@ -23,6 +23,9 @@
  */
 
 export default {
+    elements: {
+        gateways: '[data-region="gateways-container"] input[type="radio"]',
+    },
     regions: {
         gatewaysContainer: '[data-region="gateways-container"]',
         costContainer: '[data-region="fee-breakdown-container"]',
index 4028e63..42fe3f0 100644 (file)
@@ -67,6 +67,7 @@ class get_gateways_for_currency extends external_api {
                 'shortname' => $gateway,
                 'name' => get_string('gatewayname', 'pg_' . $gateway),
                 'description' => get_string('gatewaydescription', 'pg_' . $gateway),
+                'surcharge' => \core_payment\helper::get_gateway_surcharge($gateway),
             ];
         }
 
@@ -84,6 +85,7 @@ class get_gateways_for_currency extends external_api {
                     'shortname' => new external_value(PARAM_PLUGIN, 'Name of the plugin'),
                     'name' => new external_value(PARAM_TEXT, 'Human readable name of the gateway'),
                     'description' => new external_value(PARAM_TEXT, 'description of the gateway'),
+                    'surcharge' => new external_value(PARAM_INT, 'percentage of surcharge when using the gateway'),
                 ])
         );
     }
index 58c8925..2adf097 100644 (file)
@@ -76,6 +76,16 @@ class helper {
         return $gateways;
     }
 
+    /**
+     * Returns the percentage of surcharge that is applied when using a gateway
+     *
+     * @param string $gateway Name of the gateway
+     * @return int
+     */
+    public static function get_gateway_surcharge(string $gateway): int {
+        return get_config('pg_' . $gateway, 'surcharge') ?: 0;
+    }
+
     /**
      * Returns the attributes to place on a pay button.
      *
@@ -164,4 +174,16 @@ class helper {
 
         return $id;
     }
+
+    /**
+     * This functions adds the settings that are common for all payment gateways.
+     *
+     * @param \admin_settingpage $settings The settings object
+     * @param string $gateway The gateway name prefic with pg_
+     */
+    public static function add_common_gateway_settings(\admin_settingpage $settings, string $gateway): void {
+        $settings->add(new \admin_setting_configtext($gateway . '/surcharge', get_string('surcharge', 'core_payment'),
+                get_string('surcharge_desc', 'core_payment'), 0, PARAM_INT));
+
+    }
 }
index 2b95d2a..889f13a 100644 (file)
@@ -77,6 +77,11 @@ class transaction_complete extends external_api {
             'currency' => $currency
         ] = payment_helper::get_cost($component, $componentid);
 
+        // Add surcharge if there is any.
+        if ($config->surcharge) {
+            $amount += $amount * $config->surcharge / 100;
+        }
+
         $paypalhelper = new paypal_helper($config->clientid, $config->secret, $sandbox);
         $orderdetails = $paypalhelper->get_order_details($orderid);
 
index cdd850a..2463cfa 100644 (file)
@@ -41,4 +41,6 @@ if ($ADMIN->fulltree) {
     ];
     $settings->add(new admin_setting_configselect('pg_paypal/environment', get_string('environment', 'pg_paypal'),
             get_string('environment_desc', 'pg_paypal'), 'live', $options));
+
+    \core_payment\helper::add_common_gateway_settings($settings, 'pg_paypal');
 }
index 2e74fd0..1483461 100644 (file)
 
 }}
 <div class="core_payment_fee_breakdown">
-    {{# str }} labelvalue, core, {
-        "label": {{# quote }}{{# str }} cost {{/ str }}{{/ quote }},
-        "value": "{{fee}}"
-        } {{/ str }}
+    {{#surcharge}}
+        {{# str }} labelvalue, core, {
+            "label": {{# quote }}{{# str }} cost {{/ str }}{{/ quote }},
+            "value": {{# quote }}{{# str }} feeincludesurcharge, core_payment, {
+                "fee": "{{fee}}",
+                "surcharge": {{surcharge}}
+                } {{/ str }}{{/ quote }}
+            } {{/ str }}
+    {{/surcharge}}
+    {{^surcharge}}
+        {{# str }} labelvalue, core, {
+            "label": {{# quote }}{{# str }} cost {{/ str }}{{/ quote }},
+            "value": "{{fee}}"
+            } {{/ str }}
+    {{/surcharge}}
 </div>
index e93fbad..9e2c41a 100644 (file)
@@ -29,6 +29,7 @@
     * shortname
     * name
     * description
+    * surcharge
     * image
 
     Example context (json):
         "shortname": "paypal",
         "name": "PayPal",
         "description": "A description for PayPal.",
+        "surcharge": "3"
     }
 
 }}
 <div class="custom-control custom-radio {{shortname}}">
-    <input class="custom-control-input" type="radio" name="payby" id="id-payby-{{uniqid}}-{{shortname}}"  value="{{shortname}}" {{#checked}} checked="checked" {{/checked}} />
+    <input class="custom-control-input" type="radio" name="payby" id="id-payby-{{uniqid}}-{{shortname}}" data-surcharge="{{surcharge}}" value="{{shortname}}" {{#checked}} checked="checked" {{/checked}} />
     <label class="custom-control-label bg-light border p-3 my-3" for="id-payby-{{uniqid}}-{{shortname}}">
         <p class="h3">{{name}}</p>
         <p class="content mb-2">{{description}}</p>