From 03b9d60b8eb2c475c9447a1eb7117b8d14aab65a Mon Sep 17 00:00:00 2001 From: Shamim Rezaie Date: Tue, 9 Feb 2021 17:59:28 +1100 Subject: [PATCH 1/3] MDL-70287 core_payment: Improving existing and missing phpdocs --- .../fee/classes/payment/service_provider.php | 2 +- payment/classes/helper.php | 30 ++++++++++--------- .../local/callback/service_provider.php | 9 ++++-- 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/enrol/fee/classes/payment/service_provider.php b/enrol/fee/classes/payment/service_provider.php index cab7b0f4275..02497be1cfe 100644 --- a/enrol/fee/classes/payment/service_provider.php +++ b/enrol/fee/classes/payment/service_provider.php @@ -37,7 +37,7 @@ class service_provider implements \core_payment\local\callback\service_provider * Callback function that returns the enrolment cost and the accountid * for the course that $instanceid enrolment instance belongs to. * - * @param string $paymentarea + * @param string $paymentarea Payment area * @param int $instanceid The enrolment instance id * @return \core_payment\local\entities\payable */ diff --git a/payment/classes/helper.php b/payment/classes/helper.php index b5dc3c17471..39c4343d829 100644 --- a/payment/classes/helper.php +++ b/payment/classes/helper.php @@ -60,9 +60,9 @@ class helper { /** * Returns the list of gateways that can process payments in the given currency. * - * @param string $component - * @param string $paymentarea - * @param int $itemid + * @param string $component Name of the component that the paymentarea and itemid belong to + * @param string $paymentarea Payment area + * @param int $itemid An identifier that is known to the component * @return string[] */ public static function get_available_gateways(string $component, string $paymentarea, int $itemid): array { @@ -165,7 +165,9 @@ class helper { } /** - * @param string $component + * Get the name of the service provider class + * + * @param string $component The component * @return string * @throws \coding_exception */ @@ -185,8 +187,8 @@ class helper { /** * Asks the payable from the related component. * - * @param string $component Name of the component that the itemid belongs to - * @param string $paymentarea + * @param string $component Name of the component that the paymentarea and itemid belong to + * @param string $paymentarea Payment area * @param int $itemid An internal identifier that is used by the component * @return local\entities\payable */ @@ -199,10 +201,10 @@ class helper { /** * Returns the gateway configuration for given component and gateway * - * @param string $component - * @param string $paymentarea - * @param int $itemid - * @param string $gatewayname + * @param string $component Name of the component that the paymentarea and itemid belong to + * @param string $paymentarea Payment area + * @param int $itemid An identifier that is known to the component + * @param string $gatewayname The gateway name * @return array * @throws \moodle_exception */ @@ -225,8 +227,8 @@ class helper { * * @uses \core_payment\local\callback\service_provider::deliver_order() * - * @param string $component Name of the component that the itemid belongs to - * @param string $paymentarea + * @param string $component Name of the component that the paymentarea and itemid belong to + * @param string $paymentarea Payment area * @param int $itemid An internal identifier that is used by the component * @param int $paymentid payment id as inserted into the 'payments' table, if needed for reference * @param int $userid The userid the order is going to deliver to @@ -244,8 +246,8 @@ class helper { * Each payment gateway may then store the additional information their way. * * @param int $accountid Account id - * @param string $component Name of the component that the itemid belongs to - * @param string $paymentarea + * @param string $component Name of the component that the paymentarea and itemid belong to + * @param string $paymentarea Payment area * @param int $itemid An internal identifier that is used by the component * @param int $userid Id of the user who is paying * @param float $amount Amount of payment diff --git a/payment/classes/local/callback/service_provider.php b/payment/classes/local/callback/service_provider.php index 20fe0725e19..ba91668db6f 100644 --- a/payment/classes/local/callback/service_provider.php +++ b/payment/classes/local/callback/service_provider.php @@ -35,14 +35,19 @@ namespace core_payment\local\callback; interface service_provider { /** - * @param string $paymentarea + * Callback function that returns the cost of the given item in the specified payment area, + * along with the accountid that payments are paid to. + * + * @param string $paymentarea Payment area * @param int $itemid An identifier that is known to the plugin * @return \core_payment\local\entities\payable */ public static function get_payable(string $paymentarea, int $itemid): \core_payment\local\entities\payable; /** - * @param string $paymentarea + * Callback function that delivers what the user paid for to them. + * + * @param string $paymentarea Payment area * @param int $itemid An identifier that is known to the plugin * @param int $paymentid payment id as inserted into the 'payments' table, if needed for reference * @param int $userid The userid the order is going to deliver to From 51b73e431ee2ae5b4a3a5358349e44024730a2b1 Mon Sep 17 00:00:00 2001 From: Shamim Rezaie Date: Thu, 3 Dec 2020 15:55:11 +1100 Subject: [PATCH 2/3] MDL-70287 core_payment: Add get_success_url to service_provider --- .../fee/classes/payment/service_provider.php | 15 ++ .../tests/payment/service_provider_test.php | 133 ++++++++++++++++++ payment/classes/helper.php | 13 ++ .../local/callback/service_provider.php | 9 ++ payment/upgrade.txt | 5 + 5 files changed, 175 insertions(+) create mode 100644 enrol/fee/tests/payment/service_provider_test.php create mode 100644 payment/upgrade.txt diff --git a/enrol/fee/classes/payment/service_provider.php b/enrol/fee/classes/payment/service_provider.php index 02497be1cfe..8490b6d102b 100644 --- a/enrol/fee/classes/payment/service_provider.php +++ b/enrol/fee/classes/payment/service_provider.php @@ -49,6 +49,21 @@ class service_provider implements \core_payment\local\callback\service_provider return new \core_payment\local\entities\payable($instance->cost, $instance->currency, $instance->customint1); } + /** + * Callback function that returns the URL of the page the user should be redirected to in the case of a successful payment. + * + * @param string $paymentarea Payment area + * @param int $instanceid The enrolment instance id + * @return \moodle_url + */ + public static function get_success_url(string $paymentarea, int $instanceid): \moodle_url { + global $DB; + + $courseid = $DB->get_field('enrol', 'courseid', ['enrol' => 'fee', 'id' => $instanceid], MUST_EXIST); + + return new \moodle_url('/course/view.php', ['id' => $courseid]); + } + /** * Callback function that delivers what the user paid for to them. * diff --git a/enrol/fee/tests/payment/service_provider_test.php b/enrol/fee/tests/payment/service_provider_test.php new file mode 100644 index 00000000000..80783ff8800 --- /dev/null +++ b/enrol/fee/tests/payment/service_provider_test.php @@ -0,0 +1,133 @@ +. + +/** + * Unit tests for the enrol_fee's payment subsystem callback implementation. + * + * @package enrol_fee + * @category test + * @copyright 2021 Shamim Rezaie + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace enrol_fee\payment; + +/** + * Unit tests for the enrol_fee's payment subsystem callback implementation. + * + * @coversDefaultClass service_provider + */ +class service_provider_testcase extends \advanced_testcase { + + /** + * Test for service_provider::get_payable(). + * + * @covers ::get_payable + */ + public function test_get_payable() { + global $DB; + $this->resetAfterTest(); + + $studentrole = $DB->get_record('role', ['shortname' => 'student']); + $feeplugin = enrol_get_plugin('fee'); + $generator = $this->getDataGenerator(); + $account = $generator->get_plugin_generator('core_payment')->create_payment_account(['gateways' => 'paypal']); + $course = $generator->create_course(); + + $data = [ + 'courseid' => $course->id, + 'customint1' => $account->get('id'), + 'cost' => 250, + 'currency' => 'USD', + 'roleid' => $studentrole->id, + ]; + $id = $feeplugin->add_instance($course, $data); + + $payable = service_provider::get_payable('fee', $id); + + $this->assertEquals($account->get('id'), $payable->get_account_id()); + $this->assertEquals(250, $payable->get_amount()); + $this->assertEquals('USD', $payable->get_currency()); + } + + /** + * Test for service_provider::get_success_url(). + * + * @covers ::get_success_url + */ + public function test_get_success_url() { + global $CFG, $DB; + $this->resetAfterTest(); + + $studentrole = $DB->get_record('role', ['shortname' => 'student']); + $feeplugin = enrol_get_plugin('fee'); + $generator = $this->getDataGenerator(); + $account = $generator->get_plugin_generator('core_payment')->create_payment_account(['gateways' => 'paypal']); + $course = $generator->create_course(); + + $data = [ + 'courseid' => $course->id, + 'customint1' => $account->get('id'), + 'cost' => 250, + 'currency' => 'USD', + 'roleid' => $studentrole->id, + ]; + $id = $feeplugin->add_instance($course, $data); + + $successurl = service_provider::get_success_url('fee', $id); + $this->assertEquals( + $CFG->wwwroot . '/course/view.php?id=' . $course->id, + $successurl->out(false) + ); + } + + /** + * Test for service_provider::deliver_order(). + * + * @covers ::deliver_order + */ + public function test_deliver_order() { + global $DB; + $this->resetAfterTest(); + + $studentrole = $DB->get_record('role', ['shortname' => 'student']); + $feeplugin = enrol_get_plugin('fee'); + $generator = $this->getDataGenerator(); + $account = $generator->get_plugin_generator('core_payment')->create_payment_account(['gateways' => 'paypal']); + $course = $generator->create_course(); + $context = \context_course::instance($course->id); + $user = $generator->create_user(); + + $data = [ + 'courseid' => $course->id, + 'customint1' => $account->get('id'), + 'cost' => 250, + 'currency' => 'USD', + 'roleid' => $studentrole->id, + ]; + $id = $feeplugin->add_instance($course, $data); + + $paymentid = $generator->get_plugin_generator('core_payment')->create_payment([ + 'accountid' => $account->get('id'), + 'amount' => 10, + 'userid' => $user->id + ]); + + service_provider::deliver_order('fee', $id, $paymentid, $user->id); + $this->assertTrue(is_enrolled($context, $user)); + $this->assertTrue(user_has_role_assignment($user->id, $studentrole->id, $context->id)); + } +} diff --git a/payment/classes/helper.php b/payment/classes/helper.php index 39c4343d829..43a205733ff 100644 --- a/payment/classes/helper.php +++ b/payment/classes/helper.php @@ -198,6 +198,19 @@ class helper { return component_class_callback($providerclass, 'get_payable', [$paymentarea, $itemid]); } + /** + * Fetches the URL of the page the user should be redirected to from the related component + * + * @param string $component Name of the component that the paymentarea and itemid belong to + * @param string $paymentarea Payment area + * @param int $itemid An identifier that is known to the component + * @return \moodle_url + */ + public static function get_success_url(string $component, string $paymentarea, int $itemid): \moodle_url { + $providerclass = static::get_service_provider_classname($component); + return component_class_callback($providerclass, 'get_success_url', [$paymentarea, $itemid]); + } + /** * Returns the gateway configuration for given component and gateway * diff --git a/payment/classes/local/callback/service_provider.php b/payment/classes/local/callback/service_provider.php index ba91668db6f..9bd56478401 100644 --- a/payment/classes/local/callback/service_provider.php +++ b/payment/classes/local/callback/service_provider.php @@ -44,6 +44,15 @@ interface service_provider { */ public static function get_payable(string $paymentarea, int $itemid): \core_payment\local\entities\payable; + /** + * Callback function that returns the URL of the page the user should be redirected to in the case of a successful payment. + * + * @param string $paymentarea Payment area + * @param int $itemid An identifier that is known to the plugin + * @return \moodle_url + */ + public static function get_success_url(string $paymentarea, int $itemid): \moodle_url; + /** * Callback function that delivers what the user paid for to them. * diff --git a/payment/upgrade.txt b/payment/upgrade.txt new file mode 100644 index 00000000000..04adc193e2b --- /dev/null +++ b/payment/upgrade.txt @@ -0,0 +1,5 @@ +This files describes API changes in /payment/*, +information provided here is intended especially for developers. + +=== 3.11 === +* Service provider plugins using the payment subsystem are now required to implement the get_success_url method. From 93178492572f35f7dfaab3721e8325ac80605aee Mon Sep 17 00:00:00 2001 From: Shamim Rezaie Date: Thu, 3 Dec 2020 19:08:32 +1100 Subject: [PATCH 3/3] MDL-70287 core_payment: Redirect user to the successurl after payment --- enrol/fee/classes/plugin.php | 1 + enrol/fee/templates/payment_region.mustache | 4 ++++ payment/amd/build/gateways_modal.min.js | 2 +- payment/amd/build/gateways_modal.min.js.map | 2 +- payment/amd/src/gateways_modal.js | 2 +- payment/classes/helper.php | 6 ++++-- 6 files changed, 12 insertions(+), 5 deletions(-) diff --git a/enrol/fee/classes/plugin.php b/enrol/fee/classes/plugin.php index 549247cd7a0..68170c70126 100644 --- a/enrol/fee/classes/plugin.php +++ b/enrol/fee/classes/plugin.php @@ -212,6 +212,7 @@ class enrol_fee_plugin extends enrol_plugin { 'instanceid' => $instance->id, 'description' => get_string('purchasedescription', 'enrol_fee', format_string($course->fullname, true, ['context' => $context])), + 'successurl' => \enrol_fee\payment\service_provider::get_success_url('fee', $instance->id)->out(false), ]; echo $OUTPUT->render_from_template('enrol_fee/payment_region', $data); } diff --git a/enrol/fee/templates/payment_region.mustache b/enrol/fee/templates/payment_region.mustache index b8947aaec90..71235543648 100644 --- a/enrol/fee/templates/payment_region.mustache +++ b/enrol/fee/templates/payment_region.mustache @@ -28,17 +28,20 @@ * data-itemid * data-cost * data-description + * data-successurl Context variables required for this template: * cost - Human readable cost string including amount and currency * instanceid - Id of the enrolment instance * description - The description for this purchase + * successurl - The URL of the course Example context (json): { "cost": "$108.50", "instanceid": 11, "description": "Enrolment in course Introduction to algorithms", + "successurl": "https://moodlesite/course/view.php?id=2", "isguestuser": false } @@ -63,6 +66,7 @@ data-paymentarea="fee" data-itemid="{{instanceid}}" data-cost="{{cost}}" + data-successurl="{{successurl}}" data-description={{# quote }}{{description}}{{/ quote }} > {{# str }} sendpaymentbutton, enrol_fee {{/ str }} diff --git a/payment/amd/build/gateways_modal.min.js b/payment/amd/build/gateways_modal.min.js index 3cda8102180..80961db4f99 100644 --- a/payment/amd/build/gateways_modal.min.js +++ b/payment/amd/build/gateways_modal.min.js @@ -1,2 +1,2 @@ -define ("core_payment/gateways_modal",["exports","core/modal_factory","core/templates","core/str","./repository","./selectors","core/modal_events","core_payment/events","core/toast","core/notification","./modal_gateways"],function(a,b,c,d,e,f,g,h,i,j,k){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=l(b);c=l(c);f=l(f);g=l(g);h=l(h);j=l(j);k=l(k);var o="undefined"!=typeof window?window:"undefined"!=typeof self?self:"undefined"!=typeof global?global:{};function l(a){return a&&a.__esModule?a:{default:a}}function m(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function n(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var h=a.apply(b,c);function f(a){m(h,d,e,f,g,"next",a)}function g(a){m(h,d,e,f,g,"throw",a)}f(void 0)})}}var p=function(){document.addEventListener("click",function(a){var b=a.target.closest("[data-action=\"core_payment/triggerPayment\"]");if(b){a.preventDefault();q(b,{focusOnClose:a.target})}})},q=function(){var a=n(regeneratorRuntime.mark(function a(l){var m,n,o,p,q,u,v,w,x,y,z=arguments;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:m=1.\n\n/**\n * Contain the logic for the gateways modal.\n *\n * @module core_payment/gateways_modal\n * @package core_payment\n * @copyright 2019 Shamim Rezaie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport ModalFactory from 'core/modal_factory';\nimport Templates from 'core/templates';\nimport {get_string as getString} from 'core/str';\nimport {getAvailableGateways} from './repository';\nimport Selectors from './selectors';\nimport ModalEvents from 'core/modal_events';\nimport PaymentEvents from 'core_payment/events';\nimport {add as addToast, addToastRegion} from 'core/toast';\nimport Notification from 'core/notification';\nimport ModalGateways from './modal_gateways';\n\n/**\n * Register event listeners for the module.\n */\nconst registerEventListeners = () => {\n document.addEventListener('click', e => {\n const gatewayTrigger = e.target.closest('[data-action=\"core_payment/triggerPayment\"]');\n if (gatewayTrigger) {\n e.preventDefault();\n\n show(gatewayTrigger, {focusOnClose: e.target});\n }\n });\n};\n\n/**\n * Shows the gateway selector modal.\n *\n * @param {HTMLElement} rootNode\n * @param {Object} options - Additional options\n * @param {HTMLElement} options.focusOnClose The element to focus on when the modal is closed.\n */\nconst show = async(rootNode, {\n focusOnClose = null,\n} = {}) => {\n const modal = await ModalFactory.create({\n type: ModalGateways.TYPE,\n title: await getString('selectpaymenttype', 'core_payment'),\n body: await Templates.render('core_payment/gateways_modal', {}),\n });\n\n const rootElement = modal.getRoot()[0];\n addToastRegion(rootElement);\n\n modal.show();\n\n modal.getRoot().on(ModalEvents.hidden, () => {\n // Destroy when hidden.\n modal.destroy();\n try {\n focusOnClose.focus();\n } catch (e) {\n // eslint-disable-line\n }\n });\n\n modal.getRoot().on(PaymentEvents.proceed, (e) => {\n const gateway = (rootElement.querySelector(Selectors.values.gateway) || {value: ''}).value;\n\n if (gateway) {\n processPayment(\n gateway,\n rootNode.dataset.component,\n rootNode.dataset.paymentarea,\n rootNode.dataset.itemid,\n rootNode.dataset.description\n )\n .then(message => {\n modal.hide();\n Notification.addNotification({\n message: message,\n type: 'success',\n });\n location.reload();\n\n // The following return statement is never reached. It is put here just to make eslint happy.\n return message;\n })\n .catch(message => Notification.alert('', message));\n } else {\n // We cannot use await in the following line.\n // The reason is that we are preventing the default action of the save event being triggered,\n // therefore we cannot define the event handler function asynchronous.\n getString('nogatewayselected', 'core_payment').then(message => addToast(message)).catch();\n }\n\n e.preventDefault();\n });\n\n // Re-calculate the cost when gateway is changed.\n rootElement.addEventListener('change', e => {\n if (e.target.matches(Selectors.elements.gateways)) {\n updateCostRegion(rootElement, rootNode.dataset.cost);\n }\n });\n\n const gateways = await getAvailableGateways(rootNode.dataset.component, rootNode.dataset.paymentarea, rootNode.dataset.itemid);\n const context = {\n gateways\n };\n\n const {html, js} = await Templates.renderForPromise('core_payment/gateways', context);\n Templates.replaceNodeContents(rootElement.querySelector(Selectors.regions.gatewaysContainer), html, js);\n selectSingleGateway(rootElement);\n await updateCostRegion(rootElement, rootNode.dataset.cost);\n};\n\n/**\n * Auto-select the gateway if there is only one gateway.\n *\n * @param {HTMLElement} root An HTMLElement that contains the cost region\n */\nconst selectSingleGateway = root => {\n const gateways = root.querySelectorAll(Selectors.elements.gateways);\n\n if (gateways.length == 1) {\n gateways[0].checked = true;\n }\n};\n\n/**\n * Shows the cost of the item the user is purchasing in the cost region.\n *\n * @param {HTMLElement} root An HTMLElement that contains the cost region\n * @param {string} defaultCost The default cost that is going to be displayed if no gateway is selected\n * @returns {Promise}\n */\nconst updateCostRegion = async(root, defaultCost = '') => {\n const gatewayElement = root.querySelector(Selectors.values.gateway);\n const surcharge = parseInt((gatewayElement || {dataset: {surcharge: 0}}).dataset.surcharge);\n const cost = (gatewayElement || {dataset: {cost: defaultCost}}).dataset.cost;\n\n const {html, js} = await Templates.renderForPromise('core_payment/fee_breakdown', {fee: cost, surcharge});\n Templates.replaceNodeContents(root.querySelector(Selectors.regions.costContainer), html, js);\n};\n\n/**\n * Process payment using the selected gateway.\n *\n * @param {string} gateway The gateway to be used for payment\n * @param {string} component Name of the component that the itemId belongs to\n * @param {string} paymentArea Name of the area in the component that the itemId belongs to\n * @param {number} itemId An internal identifier that is used by the component\n * @param {string} description Description of the payment\n * @returns {Promise}\n */\nconst processPayment = async(gateway, component, paymentArea, itemId, description) => {\n const paymentMethod = await import(`paygw_${gateway}/gateways_modal`);\n return paymentMethod.process(component, paymentArea, itemId, description);\n};\n\n/**\n * Set up the payment actions.\n */\nexport const init = () => {\n if (!init.initialised) {\n // Event listeners should only be registered once.\n init.initialised = true;\n registerEventListeners();\n }\n};\n\n/**\n * Whether the init function was called before.\n *\n * @static\n * @type {boolean}\n */\ninit.initialised = false;\n"],"file":"gateways_modal.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/gateways_modal.js"],"names":["registerEventListeners","document","addEventListener","e","gatewayTrigger","target","closest","preventDefault","show","focusOnClose","rootNode","ModalFactory","ModalGateways","TYPE","Templates","render","type","title","body","create","modal","rootElement","getRoot","on","ModalEvents","hidden","destroy","focus","PaymentEvents","proceed","gateway","querySelector","Selectors","values","value","processPayment","dataset","component","paymentarea","itemid","description","then","message","hide","Notification","addNotification","location","href","successurl","catch","alert","matches","elements","gateways","updateCostRegion","cost","context","renderForPromise","html","js","replaceNodeContents","regions","gatewaysContainer","selectSingleGateway","root","querySelectorAll","length","checked","defaultCost","gatewayElement","surcharge","parseInt","fee","costContainer","paymentArea","itemId","paymentMethod","process","init","initialised"],"mappings":"0UAwBA,OACA,OAGA,OACA,OACA,OAEA,OACA,O,geAKMA,CAAAA,CAAsB,CAAG,UAAM,CACjCC,QAAQ,CAACC,gBAAT,CAA0B,OAA1B,CAAmC,SAAAC,CAAC,CAAI,CACpC,GAAMC,CAAAA,CAAc,CAAGD,CAAC,CAACE,MAAF,CAASC,OAAT,CAAiB,+CAAjB,CAAvB,CACA,GAAIF,CAAJ,CAAoB,CAChBD,CAAC,CAACI,cAAF,GAEAC,CAAI,CAACJ,CAAD,CAAiB,CAACK,YAAY,CAAEN,CAAC,CAACE,MAAjB,CAAjB,CACP,CACJ,CAPD,CAQH,C,CASKG,CAAI,4CAAG,WAAME,CAAN,wJAET,EAFS,KACTD,YADS,CACTA,CADS,YACM,IADN,QAGWE,SAHX,MAICC,UAAcC,IAJf,gBAKQ,iBAAU,mBAAV,CAA+B,cAA/B,CALR,mCAMOC,WAAUC,MAAV,CAAiB,6BAAjB,CAAgD,EAAhD,CANP,0BAILC,IAJK,MAKLC,KALK,MAMLC,IANK,6BAGwBC,MAHxB,yBAGHC,CAHG,QASHC,CATG,CASWD,CAAK,CAACE,OAAN,GAAgB,CAAhB,CATX,CAUT,qBAAeD,CAAf,EAEAD,CAAK,CAACZ,IAAN,GAEAY,CAAK,CAACE,OAAN,GAAgBC,EAAhB,CAAmBC,UAAYC,MAA/B,CAAuC,UAAM,CAEzCL,CAAK,CAACM,OAAN,GACA,GAAI,CACAjB,CAAY,CAACkB,KAAb,EACH,CAAC,MAAOxB,CAAP,CAAU,CAEX,CACJ,CARD,EAUAiB,CAAK,CAACE,OAAN,GAAgBC,EAAhB,CAAmBK,UAAcC,OAAjC,CAA0C,SAAC1B,CAAD,CAAO,CAC7C,GAAM2B,CAAAA,CAAO,CAAG,CAACT,CAAW,CAACU,aAAZ,CAA0BC,UAAUC,MAAV,CAAiBH,OAA3C,GAAuD,CAACI,KAAK,CAAE,EAAR,CAAxD,EAAqEA,KAArF,CAEA,GAAIJ,CAAJ,CAAa,CACTK,CAAc,CACVL,CADU,CAEVpB,CAAQ,CAAC0B,OAAT,CAAiBC,SAFP,CAGV3B,CAAQ,CAAC0B,OAAT,CAAiBE,WAHP,CAIV5B,CAAQ,CAAC0B,OAAT,CAAiBG,MAJP,CAKV7B,CAAQ,CAAC0B,OAAT,CAAiBI,WALP,CAAd,CAOCC,IAPD,CAOM,SAAAC,CAAO,CAAI,CACbtB,CAAK,CAACuB,IAAN,GACAC,UAAaC,eAAb,CAA6B,CACzBH,OAAO,CAAEA,CADgB,CAEzB1B,IAAI,CAAE,SAFmB,CAA7B,EAIA8B,QAAQ,CAACC,IAAT,CAAgBrC,CAAQ,CAAC0B,OAAT,CAAiBY,UAAjC,CAGA,MAAON,CAAAA,CACV,CAjBD,EAkBCO,KAlBD,CAkBO,SAAAP,CAAO,QAAIE,WAAaM,KAAb,CAAmB,EAAnB,CAAuBR,CAAvB,CAAJ,CAlBd,CAmBH,CApBD,IAoBO,CAIH,iBAAU,mBAAV,CAA+B,cAA/B,EAA+CD,IAA/C,CAAoD,SAAAC,CAAO,QAAI,UAASA,CAAT,CAAJ,CAA3D,EAAkFO,KAAlF,EACH,CAED9C,CAAC,CAACI,cAAF,EACH,CA/BD,EAkCAc,CAAW,CAACnB,gBAAZ,CAA6B,QAA7B,CAAuC,SAAAC,CAAC,CAAI,CACxC,GAAIA,CAAC,CAACE,MAAF,CAAS8C,OAAT,CAAiBnB,UAAUoB,QAAV,CAAmBC,QAApC,CAAJ,CAAmD,CAC/CC,CAAgB,CAACjC,CAAD,CAAcX,CAAQ,CAAC0B,OAAT,CAAiBmB,IAA/B,CACnB,CACJ,CAJD,EA1DS,gBAgEc,2BAAqB7C,CAAQ,CAAC0B,OAAT,CAAiBC,SAAtC,CAAiD3B,CAAQ,CAAC0B,OAAT,CAAiBE,WAAlE,CAA+E5B,CAAQ,CAAC0B,OAAT,CAAiBG,MAAhG,CAhEd,SAgEHc,CAhEG,QAiEHG,CAjEG,CAiEO,CACZH,QAAQ,CAARA,CADY,CAjEP,iBAqEgBvC,WAAU2C,gBAAV,CAA2B,uBAA3B,CAAoDD,CAApD,CArEhB,kBAqEFE,CArEE,GAqEFA,IArEE,CAqEIC,CArEJ,GAqEIA,EArEJ,CAsET7C,UAAU8C,mBAAV,CAA8BvC,CAAW,CAACU,aAAZ,CAA0BC,UAAU6B,OAAV,CAAkBC,iBAA5C,CAA9B,CAA8FJ,CAA9F,CAAoGC,CAApG,EACAI,CAAmB,CAAC1C,CAAD,CAAnB,CAvES,gBAwEHiC,CAAAA,CAAgB,CAACjC,CAAD,CAAcX,CAAQ,CAAC0B,OAAT,CAAiBmB,IAA/B,CAxEb,0CAAH,uD,CAgFJQ,CAAmB,CAAG,SAAAC,CAAI,CAAI,CAChC,GAAMX,CAAAA,CAAQ,CAAGW,CAAI,CAACC,gBAAL,CAAsBjC,UAAUoB,QAAV,CAAmBC,QAAzC,CAAjB,CAEA,GAAuB,CAAnB,EAAAA,CAAQ,CAACa,MAAb,CAA0B,CACtBb,CAAQ,CAAC,CAAD,CAAR,CAAYc,OAAZ,GACH,CACJ,C,CASKb,CAAgB,4CAAG,WAAMU,CAAN,iHAAYI,CAAZ,gCAA0B,EAA1B,CACfC,CADe,CACEL,CAAI,CAACjC,aAAL,CAAmBC,UAAUC,MAAV,CAAiBH,OAApC,CADF,CAEfwC,CAFe,CAEHC,QAAQ,CAAC,CAACF,CAAc,EAAI,CAACjC,OAAO,CAAE,CAACkC,SAAS,CAAE,CAAZ,CAAV,CAAnB,EAA8ClC,OAA9C,CAAsDkC,SAAvD,CAFL,CAGff,CAHe,CAGR,CAACc,CAAc,EAAI,CAACjC,OAAO,CAAE,CAACmB,IAAI,CAAEa,CAAP,CAAV,CAAnB,EAAmDhC,OAAnD,CAA2DmB,IAHnD,gBAKIzC,WAAU2C,gBAAV,CAA2B,4BAA3B,CAAyD,CAACe,GAAG,CAAEjB,CAAN,CAAYe,SAAS,CAATA,CAAZ,CAAzD,CALJ,iBAKdZ,CALc,GAKdA,IALc,CAKRC,CALQ,GAKRA,EALQ,CAMrB7C,UAAU8C,mBAAV,CAA8BI,CAAI,CAACjC,aAAL,CAAmBC,UAAU6B,OAAV,CAAkBY,aAArC,CAA9B,CAAmFf,CAAnF,CAAyFC,CAAzF,EANqB,yCAAH,uD,CAmBhBxB,CAAc,4CAAG,WAAML,CAAN,CAAeO,CAAf,CAA0BqC,CAA1B,CAAuCC,CAAvC,CAA+CnC,CAA/C,uMACyBV,CADzB,sOACyBA,CADzB,yDACyBA,CADzB,6BACb8C,CADa,iCAEZA,CAAa,CAACC,OAAd,CAAsBxC,CAAtB,CAAiCqC,CAAjC,CAA8CC,CAA9C,CAAsDnC,CAAtD,CAFY,0CAAH,uD,CAQPsC,CAAI,CAAG,UAAM,CACtB,GAAI,CAACA,CAAI,CAACC,WAAV,CAAuB,CAEnBD,CAAI,CAACC,WAAL,IACA/E,CAAsB,EACzB,CACJ,C,UAQD8E,CAAI,CAACC,WAAL,G","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Contain the logic for the gateways modal.\n *\n * @module core_payment/gateways_modal\n * @package core_payment\n * @copyright 2019 Shamim Rezaie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport ModalFactory from 'core/modal_factory';\nimport Templates from 'core/templates';\nimport {get_string as getString} from 'core/str';\nimport {getAvailableGateways} from './repository';\nimport Selectors from './selectors';\nimport ModalEvents from 'core/modal_events';\nimport PaymentEvents from 'core_payment/events';\nimport {add as addToast, addToastRegion} from 'core/toast';\nimport Notification from 'core/notification';\nimport ModalGateways from './modal_gateways';\n\n/**\n * Register event listeners for the module.\n */\nconst registerEventListeners = () => {\n document.addEventListener('click', e => {\n const gatewayTrigger = e.target.closest('[data-action=\"core_payment/triggerPayment\"]');\n if (gatewayTrigger) {\n e.preventDefault();\n\n show(gatewayTrigger, {focusOnClose: e.target});\n }\n });\n};\n\n/**\n * Shows the gateway selector modal.\n *\n * @param {HTMLElement} rootNode\n * @param {Object} options - Additional options\n * @param {HTMLElement} options.focusOnClose The element to focus on when the modal is closed.\n */\nconst show = async(rootNode, {\n focusOnClose = null,\n} = {}) => {\n const modal = await ModalFactory.create({\n type: ModalGateways.TYPE,\n title: await getString('selectpaymenttype', 'core_payment'),\n body: await Templates.render('core_payment/gateways_modal', {}),\n });\n\n const rootElement = modal.getRoot()[0];\n addToastRegion(rootElement);\n\n modal.show();\n\n modal.getRoot().on(ModalEvents.hidden, () => {\n // Destroy when hidden.\n modal.destroy();\n try {\n focusOnClose.focus();\n } catch (e) {\n // eslint-disable-line\n }\n });\n\n modal.getRoot().on(PaymentEvents.proceed, (e) => {\n const gateway = (rootElement.querySelector(Selectors.values.gateway) || {value: ''}).value;\n\n if (gateway) {\n processPayment(\n gateway,\n rootNode.dataset.component,\n rootNode.dataset.paymentarea,\n rootNode.dataset.itemid,\n rootNode.dataset.description\n )\n .then(message => {\n modal.hide();\n Notification.addNotification({\n message: message,\n type: 'success',\n });\n location.href = rootNode.dataset.successurl;\n\n // The following return statement is never reached. It is put here just to make eslint happy.\n return message;\n })\n .catch(message => Notification.alert('', message));\n } else {\n // We cannot use await in the following line.\n // The reason is that we are preventing the default action of the save event being triggered,\n // therefore we cannot define the event handler function asynchronous.\n getString('nogatewayselected', 'core_payment').then(message => addToast(message)).catch();\n }\n\n e.preventDefault();\n });\n\n // Re-calculate the cost when gateway is changed.\n rootElement.addEventListener('change', e => {\n if (e.target.matches(Selectors.elements.gateways)) {\n updateCostRegion(rootElement, rootNode.dataset.cost);\n }\n });\n\n const gateways = await getAvailableGateways(rootNode.dataset.component, rootNode.dataset.paymentarea, rootNode.dataset.itemid);\n const context = {\n gateways\n };\n\n const {html, js} = await Templates.renderForPromise('core_payment/gateways', context);\n Templates.replaceNodeContents(rootElement.querySelector(Selectors.regions.gatewaysContainer), html, js);\n selectSingleGateway(rootElement);\n await updateCostRegion(rootElement, rootNode.dataset.cost);\n};\n\n/**\n * Auto-select the gateway if there is only one gateway.\n *\n * @param {HTMLElement} root An HTMLElement that contains the cost region\n */\nconst selectSingleGateway = root => {\n const gateways = root.querySelectorAll(Selectors.elements.gateways);\n\n if (gateways.length == 1) {\n gateways[0].checked = true;\n }\n};\n\n/**\n * Shows the cost of the item the user is purchasing in the cost region.\n *\n * @param {HTMLElement} root An HTMLElement that contains the cost region\n * @param {string} defaultCost The default cost that is going to be displayed if no gateway is selected\n * @returns {Promise}\n */\nconst updateCostRegion = async(root, defaultCost = '') => {\n const gatewayElement = root.querySelector(Selectors.values.gateway);\n const surcharge = parseInt((gatewayElement || {dataset: {surcharge: 0}}).dataset.surcharge);\n const cost = (gatewayElement || {dataset: {cost: defaultCost}}).dataset.cost;\n\n const {html, js} = await Templates.renderForPromise('core_payment/fee_breakdown', {fee: cost, surcharge});\n Templates.replaceNodeContents(root.querySelector(Selectors.regions.costContainer), html, js);\n};\n\n/**\n * Process payment using the selected gateway.\n *\n * @param {string} gateway The gateway to be used for payment\n * @param {string} component Name of the component that the itemId belongs to\n * @param {string} paymentArea Name of the area in the component that the itemId belongs to\n * @param {number} itemId An internal identifier that is used by the component\n * @param {string} description Description of the payment\n * @returns {Promise}\n */\nconst processPayment = async(gateway, component, paymentArea, itemId, description) => {\n const paymentMethod = await import(`paygw_${gateway}/gateways_modal`);\n return paymentMethod.process(component, paymentArea, itemId, description);\n};\n\n/**\n * Set up the payment actions.\n */\nexport const init = () => {\n if (!init.initialised) {\n // Event listeners should only be registered once.\n init.initialised = true;\n registerEventListeners();\n }\n};\n\n/**\n * Whether the init function was called before.\n *\n * @static\n * @type {boolean}\n */\ninit.initialised = false;\n"],"file":"gateways_modal.min.js"} \ No newline at end of file diff --git a/payment/amd/src/gateways_modal.js b/payment/amd/src/gateways_modal.js index 5d07448a030..2981e843db0 100644 --- a/payment/amd/src/gateways_modal.js +++ b/payment/amd/src/gateways_modal.js @@ -95,7 +95,7 @@ const show = async(rootNode, { message: message, type: 'success', }); - location.reload(); + location.href = rootNode.dataset.successurl; // The following return statement is never reached. It is put here just to make eslint happy. return message; diff --git a/payment/classes/helper.php b/payment/classes/helper.php index 43a205733ff..e10721ceb7d 100644 --- a/payment/classes/helper.php +++ b/payment/classes/helper.php @@ -141,8 +141,8 @@ class helper { /** * Returns the attributes to place on a pay button. * - * @param string $component Name of the component that the itemid belongs to - * @param string $paymentarea + * @param string $component Name of the component that the paymentarea and itemid belong to + * @param string $paymentarea Payment area * @param int $itemid An internal identifier that is used by the component * @param string $description Description of the payment * @return array @@ -151,6 +151,7 @@ class helper { string $description): array { $payable = static::get_payable($component, $paymentarea, $itemid); + $successurl = static::get_success_url($component, $paymentarea, $itemid); return [ 'id' => 'gateways-modal-trigger', @@ -161,6 +162,7 @@ class helper { 'data-itemid' => $itemid, 'data-cost' => static::get_cost_as_string($payable->get_amount(), $payable->get_currency()), 'data-description' => $description, + 'data-successurl' => $successurl->out(false), ]; }