diff --git a/CHANGELOG.md b/CHANGELOG.md index cca5f3c5..7103d29a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Wallet Enrollment for Moodle # ========== +## V 5.0.0 ## +- Overall code improvement. +- Add a category based wallet. +- Enhance all operations and coupon validation. +- using caches to store balance data to avoid multiple requests. +- Add enrol page view event. +- using ajax to view any user wallet balance from charger form. +- Fix negative balance bug. + ## V 4.5.0 ## - Add option to display users with capabilities to credit other users wallets in topping up option, this is helpful in case no payment account exists. - Fix: In case of no topping up options available, nothing will be displayed even the policy. @@ -7,7 +16,7 @@ ## V 4.4.0 ## - Fix exception thrown when add another instance. - Confirmation page before purchase the course. -- Fix wrong enrolments due to multiple instances. +- Fix wrong enrollments due to multiple instances. ## V 4.3.0 ## - Fix exception thrown when no availability specified or disabled. diff --git a/confirm.php b/confirm.php index 3ae2b0a9..c1e9f9f6 100644 --- a/confirm.php +++ b/confirm.php @@ -25,6 +25,10 @@ require_once(__DIR__.'/../../config.php'); require_once(__DIR__.'/lib.php'); +use enrol_wallet\util\instance; +use enrol_wallet\util\balance; +use enrol_wallet\util\balance_op; + global $USER; require_login(null, false); @@ -40,8 +44,9 @@ redirect(new moodle_url('/course/view.php', ['id' => $courseid])); } +$helper = new instance($instanceid); $wallet = new enrol_wallet_plugin; -$instance = $wallet->get_instance_by_id($instanceid); +$instance = $helper->get_instance(); $course = get_course($courseid); $params = [ @@ -109,13 +114,13 @@ $params['confirm'] = true; $confirmurl = new moodle_url('/enrol/wallet/confirm.php', $params); $confirmbutton = new single_button($confirmurl, get_string('confirm')); - +$balance = balance::create_from_instance($instance); $a = [ - 'balance' => enrol_wallet\transactions::get_user_balance($USER->id), - 'cost' => $wallet::get_cost_after_discount($USER->id, $instance), + 'balance' => $balance->get_valid_balance(), + 'cost' => $helper->get_cost_after_discount(), 'currency' => $instance->currency, - 'course' => $course->fullname, - 'policy' => '', + 'course' => $course->fullname, + 'policy' => '', ]; // Display refund policy if enabled. diff --git a/extendlib.php b/extendlib.php index a38d40c2..a2f6a610 100644 --- a/extendlib.php +++ b/extendlib.php @@ -21,7 +21,7 @@ * @copyright 2023 Mo Farouk * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ - +use enrol_wallet\util\balance; /** * To add the category and node information into the my profile page. * If is a regular user, it show the balance, refund policy and topping up options. @@ -135,7 +135,7 @@ function enrol_wallet_extend_navigation_frontpage(navigation_node $parentnode, s $hassiteconfig = has_capability('moodle/site:config', $context); $any = ($captransactions || $capcredit || $capbulkedit || $capcouponview || $capcouponcreate); - $ismoodle = (get_config('enrol_wallet', 'walletsource') == enrol_wallet\transactions::SOURCE_MOODLE); + $ismoodle = (get_config('enrol_wallet', 'walletsource') == balance::MOODLE); if ($hassiteconfig && $any) { @@ -311,7 +311,7 @@ function enrol_wallet_validate_extend_signup_form($data) { function enrol_wallet_update_wordpress_user($user) { // Check the wallet source first. $source = get_config('enrol_wallet', 'walletsource'); - if ($source == enrol_wallet\transactions::SOURCE_WORDPRESS) { + if ($source == balance::WP) { // Create or update corresponding user in wordpress. $wordpress = new \enrol_wallet\wordpress; $wordpress->create_wordpress_user($user, $user->password); @@ -407,7 +407,8 @@ function enrol_wallet_before_standard_top_of_body_html() { // Check the conditions. $condition = get_config('enrol_wallet', 'noticecondition'); - $balance = \enrol_wallet\transactions::get_user_balance($USER->id); + $op = new balance(); + $balance = $op->get_total_balance(); if ($balance !== false && is_numeric($balance) && $balance <= (int)$condition) { // Display the warning. \core\notification::warning(get_string('lowbalancenotification', 'enrol_wallet', $balance)); @@ -424,7 +425,10 @@ function enrol_wallet_after_require_login() { if (isguestuser() || empty($USER->id)) { return; } - + $source = get_config('enrol_wallet', 'walletsource'); + if ($source != balance::WP) { + return; + } // Prevent multiple calls. $done = get_user_preferences('enrol_wallet_wploggedin', false, $USER); if ($done) { @@ -432,7 +436,8 @@ function enrol_wallet_after_require_login() { } if (isset($SESSION->wantsurl)) { - $return = (new moodle_url($SESSION->wantsurl))->out(false); + $return = $SESSION->wantsurl; + unset($SESSION->wantsurl); } else { $return = (new moodle_url('/'))->out(false); } diff --git a/externallib.php b/externallib.php index 87680e18..4fe274c1 100644 --- a/externallib.php +++ b/externallib.php @@ -27,6 +27,8 @@ require_once("$CFG->libdir/externallib.php"); require_once("$CFG->dirroot/enrol/wallet/lib.php"); +use enrol_wallet\util\balance; + /** * wallet enrolment external functions. * @@ -185,11 +187,12 @@ public static function enrol_user($courseid, $instanceid = 0) { } else { $costafter = $enrol->get_cost_after_discount($USER->id, $instance); $cost = $instance->cost; - $balance = \enrol_wallet\transactions::get_user_balance($USER->id); + $balance = balance::create_from_instance($instance); + $a = [ 'cost_before' => $cost, 'cost_after' => $costafter, - 'user_balance' => $balance, + 'user_balance' => $balance->get_valid_balance(), ]; if ($enrolstatus == \enrol_wallet_plugin::INSUFFICIENT_BALANCE) { $enrolstatus = get_string('insufficient_balance', 'enrol_wallet', $a); @@ -225,5 +228,4 @@ public static function enrol_user_returns() { ] ); } - } diff --git a/extrasettings.php b/extrasettings.php index 7d3f1df0..b953238c 100644 --- a/extrasettings.php +++ b/extrasettings.php @@ -33,7 +33,7 @@ $capcouponcreate = has_capability('enrol/wallet:createcoupon', $context); $capcouponedit = has_capability('enrol/wallet:editcoupon', $context); -$ismoodle = (get_config('enrol_wallet', 'walletsource') == enrol_wallet\transactions::SOURCE_MOODLE); +$ismoodle = (get_config('enrol_wallet', 'walletsource') == enrol_wallet\util\balance::MOODLE); // Adding these pages for only users with required capability. // These aren't appear to user's with capabilities, Only admins!. // That is because enrolment plugins not loading the settings unless the user has the capability moodle/site:config. diff --git a/lib.php b/lib.php index 6909fee3..54a9545e 100644 --- a/lib.php +++ b/lib.php @@ -31,7 +31,16 @@ use enrol_wallet\form\applycoupon_form; use enrol_wallet\form\insuf_form; use enrol_wallet\form\topup_form; -use enrol_wallet\transactions; +use enrol_wallet\coupons; +use enrol_wallet\util\instance as helper; +use enrol_wallet\util\balance_op; +use enrol_wallet\util\balance; +use enrol_wallet\event\enrolpage_viewed; +use enrol_wallet\output\payment_info; +use enrol_wallet\restriction\info; +use enrol_wallet\editselectedusers_operation; +use enrol_wallet\deleteselectedusers_operation; +use enrol_wallet\restriction\frontend; /** * wallet enrolment plugin implementation. @@ -43,27 +52,27 @@ class enrol_wallet_plugin extends enrol_plugin { /** * If coupons disabled. */ - public const WALLET_NOCOUPONS = 0; + public const WALLET_NOCOUPONS = coupons::NOCOUPONS; /** * If only fixed value coupons enabled. */ - public const WALLET_COUPONSFIXED = 1; + public const WALLET_COUPONSFIXED = coupons::FIXED; /** * If only percentage discount coupons enabled. */ - public const WALLET_COUPONSDISCOUNT = 2; + public const WALLET_COUPONSDISCOUNT = coupons::DISCOUNT; /** * If all coupons enabled. */ - public const WALLET_COUPONSALL = 3; + public const WALLET_COUPONSALL = coupons::ALL; /** * If enrol coupons available. */ - public const WALLET_COUPONSENROL = 4; + public const WALLET_COUPONSENROL = coupons::ENROL; /** * If category coupons available. */ - public const WALLET_COUPONCAT = 5; + public const WALLET_COUPONCAT = coupons::CATEGORY; /** * If the user has insufficient balance. */ @@ -88,15 +97,17 @@ class enrol_wallet_plugin extends enrol_plugin { */ protected $costafter; + /** + * Helper Class + * @var helper + */ + protected $helper; /** * Summary of __construct - * @param mixed $instance + * @param \stdClass $instance */ public function __construct($instance = null) { - if (!empty($instance)) { - global $USER; - $this->costafter = $this->get_cost_after_discount($USER->id, $instance); - } + $this->load_config(); } /** @@ -118,7 +129,7 @@ public function get_info_icons(array $instances) { global $USER; $costs = []; foreach ($instances as $instance) { - $cost = $this->get_cost_after_discount($USER->id, $instance); + $cost = $this->get_cost_after_discount($USER->id, $instance, true); $enrolstat = $this->can_self_enrol($instance); $canenrol = (true === $enrolstat); $insuf = (self::INSUFFICIENT_BALANCE == $enrolstat || self::INSUFFICIENT_BALANCE_DISCOUNTED == $enrolstat); @@ -330,7 +341,8 @@ public function unenrol_user(stdClass $instance, $userid) { 'coursename' => get_course($instance->courseid)->fullname, ]; $desc = get_string('refunduponunenrol_desc', 'enrol_wallet', $a); - transactions::payment_topup($credit, $userid, $desc, $userid, false, false); + $op = new balance_op($userid); + $op->credit($credit, balance_op::C_UNENROL, $instance->id, $desc, false, false); return parent::unenrol_user($instance, $userid); } @@ -343,7 +355,7 @@ public function unenrol_user(stdClass $instance, $userid) { * @return bool - true means it is possible to change enrol period and status in user_enrolments table */ public function allow_manage(stdClass $instance) { - $context = \context_course::instance($instance->courseid); + $context = context_course::instance($instance->courseid); if (!has_capability('enrol/wallet:manage', $context)) { return false; } @@ -401,25 +413,22 @@ public function can_add_instance($courseid) { * @return bool|array true if enrolled else error code and message */ public function enrol_self(stdClass $instance, \stdClass $user = null, $charge = true) { - global $CFG, $DB; + global $CFG, $DB, $USER; require_once("$CFG->dirroot/enrol/wallet/locallib.php"); if (empty($user)) { - global $USER; $user = $USER; } // Get the name of the course. - $coursename = get_course($instance->courseid)->fullname; - - $coupon = $this->check_discount_coupon(); - + $helper = $this->get_helper($instance, $user->id); + $op = new balance_op($user->id, $helper->get_course_category()); // Get the final cost after discount (if there is no discount it return the full cost). - $costafter = (!empty($this->costafter)) ? $this->costafter : $this->get_cost_after_discount($user->id, $instance, $coupon); + $costafter = $helper->get_cost_after_discount(); if ($charge) { $canborrow = enrol_wallet_is_borrow_eligible($user->id); // Deduct fees from user's account. - if (!transactions::debit($user->id, $costafter, $coursename, '', '', $instance->courseid, $canborrow)) { + if (!$op->debit($costafter, balance_op::D_ENROL_INSTANCE, $instance->id, '', $canborrow)) { throw new moodle_exception('cannotdeductbalance', 'enrol_wallet'); } } @@ -442,7 +451,7 @@ public function enrol_self(stdClass $instance, \stdClass $user = null, $charge = } catch (\moodle_exception $e) { // Rollback the transaction in case of error. if ($charge) { - transactions::payment_topup($costafter, $user->id, 'Refund due to error', '', false, false); + $op->credit($costafter, balance_op::C_ROLLBACK, $instance->id, 'Refund due to error', false, false); } throw $e; @@ -451,24 +460,20 @@ public function enrol_self(stdClass $instance, \stdClass $user = null, $charge = \core\notification::success(get_string('youenrolledincourse', 'enrol')); // Mark coupon as used (this is for percentage discount coupons only). - if ($coupon != null && $costafter < $instance->cost) { - transactions::mark_coupon_used($coupon, $user->id, $instance->id); + if ($couponutil = $helper->get_coupon_helper()) { + $couponutil->mark_coupon_used(); } // Unset the session coupon to make sure not used again. // This is a double check, already included in mark_coupon_used(). - if (isset($_SESSION['coupon'])) { - $_SESSION['coupon'] = ''; - unset($_SESSION['coupon']); - } + coupons::unset_session_coupon(); // Now apply the cashback if enabled. $cashbackenabled = get_config('enrol_wallet', 'cashback'); if ($cashbackenabled) { - transactions::apply_cashback($user->id, $costafter, $coursename, $instance->courseid); + $op->apply_cashback(); } - // Send welcome message. if ($instance->customint4 != ENROL_DO_NOT_SEND_EMAIL) { $this->email_welcome_message($instance, $user); @@ -485,12 +490,11 @@ public function enrol_self(stdClass $instance, \stdClass $user = null, $charge = */ public function hide_due_cheaper_instance($thisinstance) { global $DB, $USER; - $coupon = $this->check_discount_coupon(); $courseid = $thisinstance->courseid; // Check the status of this instance. $thisid = $thisinstance->id; - $thiscost = $this->get_cost_after_discount($USER->id, $thisinstance, $coupon); + $thiscost = $this->get_cost_after_discount($USER->id, $thisinstance); $thisenrolstat = $this->can_self_enrol($thisinstance); $thiscanenrol = (true === $thisenrolstat); $thisinsuf = (self::INSUFFICIENT_BALANCE == $thisenrolstat || self::INSUFFICIENT_BALANCE_DISCOUNTED == $thisenrolstat); @@ -510,7 +514,7 @@ public function hide_due_cheaper_instance($thisinstance) { continue; } - $othercost = $this->get_cost_after_discount($USER->id, $instance, $coupon); + $othercost = $this->get_cost_after_discount($USER->id, $instance, true); $enrolstat = $this->can_self_enrol($instance); $canenrol = (true === $enrolstat); $insuf = (self::INSUFFICIENT_BALANCE == $enrolstat || self::INSUFFICIENT_BALANCE_DISCOUNTED == $enrolstat); @@ -590,12 +594,12 @@ public function enrol_page_hook(stdClass $instance) { return ''; } - $coupon = $this->check_discount_coupon(); - $couponsetting = get_config('enrol_wallet', 'coupons'); + enrolpage_viewed::create_and_trigger($instance); - $this->costafter = self::get_cost_after_discount($USER->id, $instance, $coupon); - $costafter = $this->costafter; - $costbefore = $instance->cost; + $couponsetting = get_config('enrol_wallet', 'coupons'); + $helper = $this->get_helper($instance); + $costafter = $helper->get_cost_after_discount(); + $costbefore = $instance->cost; $enrolstatus = $this->can_self_enrol($instance); @@ -621,13 +625,13 @@ public function enrol_page_hook(stdClass $instance) { // Now prepare the coupon form. // Check the coupons settings first. - if ($couponsetting != self::WALLET_NOCOUPONS && $costafter != 0) { + if (coupons::is_enabled() && $costafter != 0) { $data = new stdClass(); $data->header = $this->get_instance_name($instance); $data->instance = $instance; - - $action = new moodle_url('/enrol/wallet/extra/action.php'); - $couponform = new applycoupon_form(null, $data); + $data->url = (new \moodle_url('/enrol/index.php', ['id' => $instance->courseid]))->out(); + $couponaction = new \moodle_url('/enrol/wallet/extra/coupon_action.php'); + $couponform = new applycoupon_form($couponaction, $data); if ($submitteddata = $couponform->get_data()) { enrol_wallet_process_coupon_data($submitteddata); } @@ -640,8 +644,8 @@ public function enrol_page_hook(stdClass $instance) { self::INSUFFICIENT_BALANCE == $enrolstatus || self::INSUFFICIENT_BALANCE_DISCOUNTED == $enrolstatus ) { - - $balance = transactions::get_user_balance($USER->id); + $balancehelper = new balance(0, $helper->get_course_category()); + $balance = $balancehelper->get_valid_balance(); // This user has insufficient wallet balance to be directly enrolled. // So we will show him several ways for payments or recharge his wallet. $data = new stdClass(); @@ -665,10 +669,10 @@ public function enrol_page_hook(stdClass $instance) { $output .= ob_get_clean(); // Now prepare the coupon form. - if ($couponsetting != self::WALLET_NOCOUPONS) { - - $action = new moodle_url('/enrol/wallet/extra/action.php'); - $couponform = new applycoupon_form(null, $data); + if (coupons::is_enabled()) { + $data->url = (new \moodle_url('/enrol/index.php', ['id' => $instance->courseid]))->out(); + $couponaction = new \moodle_url('/enrol/wallet/extra/coupon_action.php'); + $couponform = new applycoupon_form($couponaction, $data); if ($submitteddata = $couponform->get_data()) { enrol_wallet_process_coupon_data($submitteddata); } @@ -749,7 +753,7 @@ public function can_self_enrol(stdClass $instance, $checkuserenrolment = true) { // Check if user is already enroled. if ($ue = $DB->get_record('user_enrolments', ['userid' => $USER->id, 'enrolid' => $instance->id])) { // Check if repurchase enabled, the enrolment already endded and the user isn't suspended. - if (!$repurchase || (!empty($ue->timeend) && $ue->timeend < time()) || $ue->status == ENROL_USER_SUSPENDED) { + if (!$repurchase || (!empty($ue->timeend) && $ue->timeend > time()) || $ue->status == ENROL_USER_SUSPENDED) { return get_string('alreadyenroled', 'enrol_wallet'); } } @@ -809,7 +813,7 @@ public function can_self_enrol(stdClass $instance, $checkuserenrolment = true) { } if (!empty($this->config->restrictionenabled) && !empty($instance->customtext2)) { - $info = new \enrol_wallet\restriction\info($instance); + $info = new info($instance); if (!$info->is_available($reasons, true, $USER->id)) { $return[] = $reasons; @@ -828,7 +832,9 @@ public function can_self_enrol(stdClass $instance, $checkuserenrolment = true) { } // Insufficient balance. - $costafter = self::get_cost_after_discount($USER->id, $instance, null); + $helper = $this->get_helper($instance); + $costafter = $helper->get_cost_after_discount(); + // Check if the discount gives acceptable cost. if (!is_numeric($costafter) || $costafter < 0) { return get_string('nocost', 'enrol_wallet'); @@ -837,7 +843,8 @@ public function can_self_enrol(stdClass $instance, $checkuserenrolment = true) { require_once("$CFG->dirroot/enrol/wallet/locallib.php"); $this->costafter = $costafter; $costbefore = $instance->cost; - $balance = transactions::get_user_balance($USER->id); + $balancehelper = new balance(0, $helper->get_course_category()); + $balance = $balancehelper->get_valid_balance(); $canborrow = enrol_wallet_is_borrow_eligible($USER->id); if ($balance < $costafter && !$canborrow) { if ($costbefore == $costafter) { @@ -850,6 +857,28 @@ public function can_self_enrol(stdClass $instance, $checkuserenrolment = true) { return true; } + /** + * Get the helper class + * @param \stdClass|int $instance + * @param int $userid + * @return helper + */ + protected function get_helper($instance, $userid = 0) { + global $USER; + if (is_number($instance)) { + $instanceid = $instance; + } else { + $instanceid = $instance->id; + } + + if (empty($this->helper) + || $this->helper->id !== $instanceid + || (!empty($userid) && $userid != $this->helper->userid) + || (empty($userid) && $USER->id != $this->helper->userid)) { + $this->helper = new helper($instance, $userid); + } + return $this->helper; + } /** * Return information for enrolment instance containing list of parameters required * for enrolment, name of enrolment plugin etc. @@ -859,7 +888,6 @@ public function can_self_enrol(stdClass $instance, $checkuserenrolment = true) { */ public function get_enrol_info(stdClass $instance) { global $USER; - $coupon = $this->check_discount_coupon(); $instanceinfo = new stdClass(); $instanceinfo->id = $instance->id; @@ -867,7 +895,7 @@ public function get_enrol_info(stdClass $instance) { $instanceinfo->type = $this->get_name(); $instanceinfo->name = $this->get_instance_name($instance); $instanceinfo->status = $this->can_self_enrol($instance); - $instanceinfo->cost = $this->get_cost_after_discount($USER->id, $instance, $coupon); + $instanceinfo->cost = $this->get_cost_after_discount($USER->id, $instance); return $instanceinfo; } @@ -1331,10 +1359,10 @@ public function get_bulk_operations(course_enrolment_manager $manager) { $context = $manager->get_context(); $bulkoperations = []; if (has_capability("enrol/wallet:manage", $context)) { - $bulkoperations['editselectedusers'] = new \enrol_wallet\editselectedusers_operation($manager, $this); + $bulkoperations['editselectedusers'] = new editselectedusers_operation($manager, $this); } if (has_capability("enrol/wallet:unenrol", $context)) { - $bulkoperations['deleteselectedusers'] = new \enrol_wallet\deleteselectedusers_operation($manager, $this); + $bulkoperations['deleteselectedusers'] = new deleteselectedusers_operation($manager, $this); } return $bulkoperations; } @@ -1398,8 +1426,6 @@ public function edit_instance_form($instance, \MoodleQuickForm $mform, $context) $mform->addElement('select', 'status', get_string('status', 'enrol_wallet'), $options); $mform->addHelpButton('status', 'status', 'enrol_wallet'); - // TODO add password as an optional restriction. - // New enrolments option. $options = $this->get_newenrols_options(); $mform->addElement('select', 'customint6', get_string('newenrols', 'enrol_wallet'), $options); @@ -1556,7 +1582,7 @@ protected function include_availability($instance, $mform, $context) { // availabilityconditionsjson for consistency with moodleform_mod. $mform->addElement('textarea', 'availabilityconditionsjson', get_string('accessrestrictions', 'availability')); - \enrol_wallet\restriction\frontend::include_availability_javascript($course, $courses); + frontend::include_availability_javascript($course, $courses); } @@ -1569,7 +1595,7 @@ protected function include_availability($instance, $mform, $context) { * @param object $context context of existing course or parent category if course does not exist * @return void */ - public function course_edit_form($instance, \MoodleQuickForm $mform, $data, $context) { + public function course_edit_form($instance, MoodleQuickForm $mform, $data, $context) { global $DB; $courseid = $data->id ?? optional_param('id', null, PARAM_INT); @@ -1933,10 +1959,8 @@ public function update_instance($instance, $data) { * @return stdClass|false */ public function get_instance_by_id($instanceid) { - global $DB; - $instance = $DB->get_record('enrol', ['enrol' => 'wallet', 'id' => $instanceid], '*', MUST_EXIST); - - return $instance; + $helper = $this->get_helper($instanceid); + return $helper->get_instance(); } /** @@ -1945,13 +1969,8 @@ public function get_instance_by_id($instanceid) { * @return bool|stdClass */ public function get_course_by_instance_id($instanceid) { - global $DB; - $courseid = $DB->get_field('enrol', 'courseid', ['enrol' => 'wallet', 'id' => $instanceid], IGNORE_MISSING); - if (!$courseid) { - return false; - } - $course = get_course($courseid); - return $course; + $helper = $this->get_helper($instanceid); + return $helper->get_course(); } /** @@ -2030,11 +2049,7 @@ public function get_welcome_email_contact($sendoption, $context) { * @return string|null return the coupon code, or null if not found. */ public static function check_discount_coupon() { - $coupon = optional_param('coupon', null, PARAM_RAW); - if (!empty($coupon)) { - $_SESSION['coupon'] = $coupon; - } - return !empty($_SESSION['coupon']) ? $_SESSION['coupon'] : $coupon; + return coupons::check_discount_coupon(); } /** @@ -2043,76 +2058,19 @@ public static function check_discount_coupon() { * * @param int $userid * @param object $instance - * @param string $coupon the coupon code in case if the discount from it. + * @param bool $recalculate * @return float the cost after discount. */ - public static function get_cost_after_discount($userid, $instance, $coupon = null) { - global $DB; - $cost = $instance->cost; - if ($ue = $DB->get_record('user_enrolments', ['enrolid' => $instance->id, 'userid' => $userid])) { - if (!empty($ue->timeend) && get_config('enrol_wallet', 'repurchase')) { - if ($first = get_config('enrol_wallet', 'repurchase_firstdis')) { - $cost = (100 - $first) * $instance->cost / 100; - $second = get_config('enrol_wallet', 'repurchase_seconddis'); - $timepassed = $ue->timemodified > $ue->timecreated + $ue->timeend - $ue->timestart; - if ($second && $ue->modifierid == $userid && $timepassed) { - $cost = (100 - $second) * $instance->cost / 100; - } - } - } - } - - $couponsetting = get_config('enrol_wallet', 'coupons'); - $percentav = $couponsetting == self::WALLET_COUPONSALL || $couponsetting == self::WALLET_COUPONSDISCOUNT; - // Check if there is a discount coupon first. - if (empty($coupon)) { - $coupon = self::check_discount_coupon(); - } - - $costaftercoupon = $cost; - - if (!empty($coupon) && $percentav) { - // Save coupon in session. - $_SESSION['coupon'] = $coupon; - - $coupondata = transactions::get_coupon_value($coupon, $userid, $instance->id); - - $type = (is_array($coupondata)) ? $coupondata['type'] : ''; - if ($type == 'percent' && $coupondata['value'] <= 100) { - - $costaftercoupon = $instance->cost * (1 - $coupondata['value'] / 100); - - } - } - - // Check if the discount according to custom profile field in enabled. - if (!$fieldid = get_config('enrol_wallet', 'discount_field')) { - return $costaftercoupon; + public function get_cost_after_discount($userid, $instance, $recalculate = false) { + if (isset($this->costafter) && !$recalculate) { + return $this->costafter; } - // Check the data in the discount field. - $data = $DB->get_field('user_info_data', 'data', ['userid' => $userid, 'fieldid' => $fieldid]); - - if (empty($data)) { - return $costaftercoupon; - } - // If the user has free access to courses return 0 cost. - if (stripos(strtolower($data), 'free') !== false) { - return 0; - // If there is a word no in the data means no discount. - } else if (stripos(strtolower($data), 'no') !== false) { - return $costaftercoupon; - } else { - // Get the integer from the data. - preg_match('/\d+/', $data, $matches); - if (isset($matches[0]) && intval($matches[0]) <= 100) { - // Cannot allow discount more than 100%. - $discount = intval($matches[0]); - $cost = $costaftercoupon * (100 - $discount) / 100; - return $cost; - } else { - return $costaftercoupon; - } + $helper = $this->get_helper($instance, $userid); + $costafter = $helper->get_cost_after_discount($recalculate); + if (!$recalculate) { + $this->costafter = $costafter; } + return $costafter; } /** @@ -2123,7 +2081,7 @@ public static function get_cost_after_discount($userid, $instance, $coupon = nul * @return string */ public static function show_payment_info(stdClass $instance, $fee) { - global $USER, $OUTPUT, $DB, $CFG; + global $PAGE, $CFG; require_once("$CFG->dirroot/enrol/wallet/locallib.php"); if (!enrol_wallet_is_valid_account($instance->customint1)) { @@ -2133,52 +2091,9 @@ public static function show_payment_info(stdClass $instance, $fee) { if (!class_exists('\core_payment\helper')) { return ''; } - - $balance = (float)transactions::get_user_balance($USER->id); - $cost = (float)$fee - $balance; - - // If user already had enough balance no need to display direct payment to the course. - if ($balance >= $fee) { - return ''; - } - - $course = $DB->get_record('course', ['id' => $instance->courseid], '*', MUST_EXIST); - $context = context_course::instance($course->id); - - if ($cost < 0.01) { // No cost. - return '

'.get_string('nocost', 'enrol_wallet').'

'; - - } else { - require_once(__DIR__.'/classes/payment/service_provider.php'); - - $payrecord = [ - 'cost' => $cost, - 'currency' => $instance->currency, - 'userid' => $USER->id, - 'instanceid' => $instance->id, - 'timecreated' => time(), - ]; - if (!$id = $DB->get_field('enrol_wallet_items', 'id', $payrecord, IGNORE_MULTIPLE)) { - $id = $DB->insert_record('enrol_wallet_items', $payrecord); - } - - $data = [ - 'isguestuser' => isguestuser() || !isloggedin(), - 'cost' => \core_payment\helper::get_cost_as_string($cost, $instance->currency), - 'itemid' => $id, - 'description' => get_string('purchasedescription', 'enrol_wallet', - format_string($course->fullname, true, ['context' => $context])), - 'successurl' => \enrol_wallet\payment\service_provider::get_success_url('wallet', $instance->id)->out(false), - ]; - - if (!empty($balance)) { - $data['balance'] = \core_payment\helper::get_cost_as_string($balance, $instance->currency); - } else { - $data['balance'] = false; - } - } - - return $OUTPUT->render_from_template('enrol_wallet/payment_region', $data); + $renderinfo = new payment_info($instance); + $renderer = $PAGE->get_renderer('enrol_wallet'); + return $renderer->render($renderinfo); } } diff --git a/locallib.php b/locallib.php index 8909c2bb..b4e3dda7 100644 --- a/locallib.php +++ b/locallib.php @@ -21,6 +21,9 @@ * @copyright 2023 Mohammad Farouk * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +use enrol_wallet\util\balance; +use enrol_wallet\util\balance_op; +use enrol_wallet\coupons; /** * Enable enrol Wallet plugin. @@ -160,10 +163,8 @@ function enrol_wallet_display_charger_form() { $mform = new enrol_wallet\form\charger_form($action, null, 'get'); $result = optional_param('result', '', PARAM_RAW); - ob_start(); - echo $result; - $mform->display(); - $output = ob_get_clean(); + $output = $result; + $output .= $mform->render(); return $output; } @@ -181,32 +182,28 @@ function enrol_wallet_handle_charger_form($data) { $value = $data['value'] ?? ''; $userid = $data['userlist']; - $err = ''; + $catid = $data['category'] ?? 0; $charger = $USER->id; - $transactions = new enrol_wallet\transactions; - $before = $transactions->get_user_balance($userid); + $operations = new balance_op($userid, $catid); + $before = $operations->get_total_balance(); if ($op === 'credit') { $desc = get_string('charger_credit_desc', 'enrol_wallet', fullname($USER)); // Process the transaction. - $result = $transactions->payment_topup($value, $userid, $desc, $charger); - $after = $transactions->get_user_balance($userid); + $operations->credit($value, $operations::USER, $charger, $desc); + $after = $operations->get_total_balance(); } else if ($op === 'debit') { $neg = $data['neg'] ?? optional_param('neg', false, PARAM_BOOL); // Process the payment. - $result = $transactions->debit($userid, $value, '', $charger, '', 0, $neg); - $after = $transactions->get_user_balance($userid); + $operations->debit($value, $operations::USER, $charger, '', $neg); + $after = $operations->get_total_balance(); - } else if ($op === 'balance') { - - $result = $before; } $params = [ - 'result' => $result, 'before' => $before, 'after' => ($op == 'balance') ? $before : $after, 'userid' => $userid, @@ -218,54 +215,144 @@ function enrol_wallet_handle_charger_form($data) { return false; } +/** + * Displaying the results after charging the wallet of other user. + * @param array $params parameters from the charging form results. + * @return bool + */ +function enrol_wallet_display_transaction_results($params = []) { + global $OUTPUT; + if (!has_capability('enrol/wallet:viewotherbalance', context_system::instance())) { + return false; + } + + $result = $params['result'] ?? optional_param('result', false, PARAM_TEXT); + $before = $params['before'] ?? optional_param('before', '', PARAM_FLOAT); + $after = $params['after'] ?? optional_param('after', '', PARAM_FLOAT); + $userid = $params['userid'] ?? optional_param('userid', '', PARAM_INT); + $err = $params['err'] ?? optional_param('error', '', PARAM_TEXT); + + $info = ''; + if (!empty($err)) { + + $info .= get_string('ch_result_error', 'enrol_wallet', $err); + $type = 'error'; + + } else { + + $user = \core_user::get_user($userid); + $userfull = $user->firstname.' '.$user->lastname.' ('.$user->email.')'; + // Display the result to the user. + $info .= get_string('ch_result_before', 'enrol_wallet', $before); + $type = 'success'; + if (!empty($result) && is_numeric($result)) { + $success = true; + } else { + $success = false; + if (is_string($result)) { + $info .= $result; + } + } + $a = [ + 'userfull' => $userfull, + 'after' => $after, + 'after_before' => ($after - $before), + 'before' => $before, + ]; + if ($after !== $before) { + + if ($after !== '') { + $info .= get_string('ch_result_after', 'enrol_wallet', $after); + } + if ($after < 0) { + $info .= get_string('ch_result_negative', 'enrol_wallet'); + $type = 'warning'; + } + + $info .= get_string('ch_result_info_charge', 'enrol_wallet', $a); + + } else { + + $info .= get_string('ch_result_info_balance', 'enrol_wallet', $a); + $type = $success ? 'info' : 'error'; + + } + } + // Display the results. + core\notification::add($info, $type); + + return true; +} + /** * Process the data from apply_coupon_form * @param object $data - * @return void + * @return string the redirect url. */ function enrol_wallet_process_coupon_data($data) { - global $USER, $DB; + global $USER, $DB, $PAGE; $data = (array)$data; - $cancel = $data['cancel'] ?? ''; + + $cancel = $data['cancel'] ?? optional_param('cancel', false, PARAM_BOOL); $url = $data['url'] ?? ''; - $redirecturl = !empty($url) ? new moodle_url('/'.$url) : new moodle_url('/'); + if (empty($url)) { + $redirecturl = new moodle_url('/'); + } else { + $redirecturl = new moodle_url($url); + } if ($cancel) { // Important to unset the session coupon. - if (isset($_SESSION['coupon'])) { - $_SESSION['coupon'] = ''; - unset($_SESSION['coupon']); - } + coupons::unset_session_coupon(); + $redirecturl->remove_params('coupon', 'submitcoupon'); redirect($redirecturl); } - $userid = $USER->id; - - $coupon = $data['coupon']; - $instanceid = $data['instanceid'] ?? ''; - $courseid = $data['id'] ?? 0; - $cmid = $data['cmid'] ?? 0; - $sectionid = $data['sectionid'] ?? 0; + $couponutil = new coupons($data['coupon']); - $couponsetting = get_config('enrol_wallet', 'coupons'); + if (!empty($data['instanceid'])) { + $area = $couponutil::AREA_ENROL; + $areaid = $data['instanceid']; + } else if (!empty($data['cmid'])) { + $area = $couponutil::AREA_CM; + $areaid = $data['cmid']; + } else if (!empty($data['sectionid'])) { + $area = $couponutil::AREA_SECTION; + $areaid = $data['sectionid']; + } else { + $area = $couponutil::AREA_TOPUP; + $areaid = 0; + } - // Get the coupon data. - $coupondata = enrol_wallet\transactions::get_coupon_value($coupon, $userid, $instanceid, false, $cmid, $sectionid); - if (empty($coupondata) || is_string($coupondata)) { - $msg = get_string('coupon_applyerror', 'enrol_wallet', $coupondata ?? ''); + $couponutil->validate_coupon($area, $areaid); + if ($error = $couponutil->has_error()) { + $msg = get_string('coupon_applyerror', 'enrol_wallet', $error ?? ''); $msgtype = 'error'; // This mean that the function return error. } else { - $wallet = new enrol_wallet_plugin; - $value = $coupondata['value'] ?? 0; - $type = $coupondata['type']; + $value = $couponutil->get_value(); + $type = $couponutil->type; + switch($area) { + case coupons::AREA_ENROL: + $id = $DB->get_field('enrol', 'courseid', ['id' => $areaid, 'enrol' => 'wallet'], IGNORE_MISSING); + if (!empty($id)) { + $redirecturl = new moodle_url('/enrol/index.php', ['id' => $id, 'coupon' => $couponutil->code]); + } + break; + case coupons::AREA_CM: + case coupons::AREA_SECTION: + if (!empty($url)) { + $redirecturl = $url . '&' . http_build_query(['coupon' => $couponutil->code]); + } + break; + default: + } + $couponutil->apply_coupon(); // Check the type to determine what to do. - if ($type == 'fixed') { - + if ($type == $couponutil::FIXED) { // Apply the coupon code to add its value to the user's wallet and enrol if value is enough. - enrol_wallet\transactions::apply_coupon($coupondata, $userid, $instanceid); $currency = get_config('enrol_wallet', 'currency'); $a = [ 'value' => $value, @@ -274,103 +361,51 @@ function enrol_wallet_process_coupon_data($data) { $msg = get_string('coupon_applyfixed', 'enrol_wallet', $a); $msgtype = 'success'; - } else if ($type == 'percent' && - ($couponsetting == enrol_wallet_plugin::WALLET_COUPONSDISCOUNT - || $couponsetting == enrol_wallet_plugin::WALLET_COUPONSALL) - && !empty($instanceid)) { + } else if ($type == $couponutil::DISCOUNT && $area == $couponutil::AREA_ENROL) { // Percentage discount coupons applied in enrolment. - $id = $DB->get_field('enrol', 'courseid', ['id' => $instanceid, 'enrol' => 'wallet'], IGNORE_MISSING); - - if ($id) { - - $redirecturl = new moodle_url('/enrol/index.php', ['id' => $id, 'coupon' => $coupon]); + if (!empty($id)) { $msg = get_string('coupon_applydiscount', 'enrol_wallet', $value); $msgtype = 'success'; - } else { - $msg = get_string('coupon_applynocourse', 'enrol_wallet'); $msgtype = 'error'; - } - } else if ($type == 'percent' && - ($couponsetting == enrol_wallet_plugin::WALLET_COUPONSDISCOUNT - || $couponsetting == enrol_wallet_plugin::WALLET_COUPONSALL) - && (!empty($cmid) || !empty($sectionid))) { + } else if ($type == $couponutil::DISCOUNT + && in_array($area, [$couponutil::AREA_SECTION, $couponutil::AREA_CM])) { // This is the case when the coupon applied by availability wallet. - $_SESSION['coupon'] = $coupon; - $redirecturl = $url . '&' . http_build_query(['coupon' => $coupon]); $msg = get_string('coupon_applydiscount', 'enrol_wallet', $value); $msgtype = 'success'; - } else if ($type == 'category' && !empty($instanceid) && !empty($coupondata['category'])) { + } else if ($type == $couponutil::CATEGORY) { // This type of coupons is restricted to be used in certain categories. - $course = $wallet->get_course_by_instance_id($instanceid); - $ok = false; - if ($coupondata['category'] == $course->category) { - $ok = true; - } else { - $parents = core_course_category::get($course->category)->get_parents(); - if (in_array($coupondata['category'], $parents)) { - $ok = true; - } - } - - $redirecturl = new moodle_url('/enrol/index.php', ['id' => $course->id]); - - if ($ok) { - enrol_wallet\transactions::get_coupon_value($coupon, $userid, $instanceid, true); - $msg = get_string('coupon_categoryapplied', 'enrol_wallet'); - $msgtype = 'success'; - } else { - $categoryname = core_course_category::get($coupondata['category'])->get_nested_name(false); - $msg = get_string('coupon_categoryfail', 'enrol_wallet', $categoryname); - $msgtype = 'error'; - } - - } else if ($type == 'enrol' && !empty($instanceid) && !empty($coupondata['courses'])) { - // This type has no value, it used to enrol the user direct to the course. - $courseid = $DB->get_field('enrol', 'courseid', ['id' => $instanceid, 'enrol' => 'wallet'], IGNORE_MISSING); - - if (in_array($courseid, $coupondata['courses'])) { - // Apply the coupon and enrol the user. - enrol_wallet\transactions::get_coupon_value($coupon, $userid, $instanceid, true); - - $msg = get_string('coupon_enrolapplied', 'enrol_wallet'); - $msgtype = 'success'; - } else { - $available = ''; - foreach ($coupondata['courses'] as $courseid) { - $coursename = get_course($courseid)->fullname; - $available .= '- ' . $coursename . '
'; - } - - $msg = get_string('coupon_enrolerror', 'enrol_wallet', $available); - $msgtype = 'error'; - } - - } else if (($type == 'percent' || $type == 'course' || $type == 'category') && empty($instanceid)) { + $msg = get_string('coupon_categoryapplied', 'enrol_wallet'); + $msgtype = 'success'; - $msg = get_string('coupon_applynothere', 'enrol_wallet'); - $msgtype = 'error'; + } else if ($type == $couponutil::AREA_ENROL) { + // Apply the coupon and enrol the user. + $msg = get_string('coupon_enrolapplied', 'enrol_wallet'); + $msgtype = 'success'; } else { - $msg = get_string('invalidcoupon_operation', 'enrol_wallet'); $msgtype = 'error'; } } + if ($msgtype === 'error') { + $redirecturl->param('error', $msg); + } core\notification::add($msg, $msgtype); + return $redirecturl; } /** * Display links to generate and view coupons. * @return string */ function enrol_wallet_display_coupon_urls() { - if (get_config('enrol_wallet', 'walletsource') !== enrol_wallet\transactions::SOURCE_MOODLE) { + if (get_config('enrol_wallet', 'walletsource') !== balance::MOODLE) { return ''; } $context = context_system::instance(); @@ -398,74 +433,7 @@ function enrol_wallet_display_coupon_urls() { return $render; } -/** - * Displaying the results after charging the wallet of other user. - * @param array $params parameters from the charging form results. - * @return bool - */ -function enrol_wallet_display_transaction_results($params = []) { - global $OUTPUT; - if (!has_capability('enrol/wallet:viewotherbalance', context_system::instance())) { - return false; - } - - $result = $params['result'] ?? optional_param('result', false, PARAM_ALPHANUM); - $before = $params['before'] ?? optional_param('before', '', PARAM_FLOAT); - $after = $params['after'] ?? optional_param('after', '', PARAM_FLOAT); - $userid = $params['userid'] ?? optional_param('userid', '', PARAM_INT); - $err = $params['err'] ?? optional_param('error', '', PARAM_TEXT); - - $info = ''; - if (!empty($err)) { - - $info = get_string('ch_result_error', 'enrol_wallet', $err); - $type = 'error'; - - } else { - - $user = \core_user::get_user($userid); - $userfull = $user->firstname.' '.$user->lastname.' ('.$user->email.')'; - // Display the result to the user. - $info = get_string('ch_result_before', 'enrol_wallet', $before); - $type = 'success'; - if (!empty($result) && is_numeric($result)) { - $success = true; - } else { - $success = false; - if (is_string($result)) { - $info .= $result; - } - } - $a = [ - 'userfull' => $userfull, - 'after' => $after, - 'after_before' => ($after - $before), - 'before' => $before, - ]; - if ($after !== $before) { - - if ($after !== '') { - $info .= get_string('ch_result_after', 'enrol_wallet', $after); - } - if ($after < 0) { - $info .= get_string('ch_result_negative', 'enrol_wallet'); - $type = 'warning'; - } - $info .= get_string('ch_result_info_charge', 'enrol_wallet', $a); - - } else { - - $info .= get_string('ch_result_info_balance', 'enrol_wallet', $a); - $type = $success ? 'info' : 'error'; - - } - } - // Display the results. - core\notification::add($info, $type); - - return true; -} /** * Return html string contains information about current user wallet balance. @@ -473,60 +441,10 @@ function enrol_wallet_display_transaction_results($params = []) { * @return bool|string */ function enrol_wallet_display_current_user_balance($userid = 0) { - global $USER, $OUTPUT, $CFG; - $isparent = false; - if (file_exists("$CFG->dirroot/auth/parent/auth.php")) { - require_once("$CFG->dirroot/auth/parent/auth.php"); - require_once("$CFG->dirroot/auth/parent/lib.php"); - $authparent = new auth_plugin_parent; - $isparent = $authparent->is_parent($USER); - } - - $currentuser = false; - if (empty($userid) || $userid == $USER->id) { - $userid = $USER->id; - $currentuser = true; - } - - // Get the user balance. - $balance = \enrol_wallet\transactions::get_user_balance($userid); - $norefund = \enrol_wallet\transactions::get_nonrefund_balance($userid); - // Get the default currency. - $currency = get_config('enrol_wallet', 'currency'); - $policy = get_config('enrol_wallet', 'refundpolicy'); - // Prepare transaction URL to display. - $params = []; - if (!$currentuser) { - $params['user'] = $userid; - } - $transactionsurl = new moodle_url('/enrol/wallet/extra/transaction.php', $params); - $transactions = html_writer::link($transactionsurl, get_string('transactions', 'enrol_wallet')); - if ($currentuser) { - // Transfer link. - $transferenabled = get_config('enrol_wallet', 'transfer_enabled'); - $transferurl = new moodle_url('/enrol/wallet/extra/transfer.php'); - $transfer = html_writer::link($transferurl, get_string('transfer', 'enrol_wallet')); - - if (!$isparent) { - // Referral link. - $refenabled = get_config('enrol_wallet', 'referral_enabled'); - $referralurl = new moodle_url('/enrol/wallet/extra/referral.php'); - $referral = html_writer::link($referralurl, get_string('referral_program', 'enrol_wallet')); - } - } - - $tempctx = new stdClass; - $tempctx->main = number_format($balance - $norefund, 2); - $tempctx->balance = number_format($balance, 2); - $tempctx->currency = $currency; - $tempctx->norefund = number_format($norefund, 2); - $tempctx->transactions = $transactions; - $tempctx->transfer = !empty($transferenabled) ? $transfer : false; - $tempctx->referral = !empty($refenabled) ? $referral : false; - $tempctx->policy = !empty($policy) ? $policy : false; - // Display the current user's balance in the wallet. - $render = $OUTPUT->render_from_template('enrol_wallet/display', $tempctx); - return $render; + global $USER, $OUTPUT, $CFG, $PAGE; + $renderable = new \enrol_wallet\output\wallet_balance($userid); + $renderer = $PAGE->get_renderer('enrol_wallet'); + return $renderer->render($renderable); } /** @@ -543,7 +461,7 @@ function enrol_wallet_display_topup_options() { return ''; } - $username = optional_param('s', '', PARAM_RAW); + $username = optional_param('s', '', PARAM_USERNAME); if (!empty($username)) { $user = get_complete_user_data('username', $username); } else { @@ -575,25 +493,23 @@ function enrol_wallet_display_topup_options() { // Display options to charge with coupons or other payment methods. $topupurl = new moodle_url('/enrol/wallet/extra/topup.php'); $topupform = new \enrol_wallet\form\topup_form($topupurl, $data); - ob_start(); - $topupform->display(); - $render .= ob_get_clean(); + $render .= $topupform->render(); } // Check if fixed coupons enabled. - if ($couponsetting == enrol_wallet_plugin::WALLET_COUPONSFIXED || - $couponsetting == enrol_wallet_plugin::WALLET_COUPONSALL) { + $enabledcoupons = coupons::get_enabled(); + $intersect = array_intersect($enabledcoupons, [coupons::ALL, coupons::FIXED, coupons::CATEGORY]); + if (!empty($intersect)) { // Display the coupon form to enable user to topup wallet using fixed coupon. require_once($CFG->dirroot.'/enrol/wallet/classes/form/applycoupon_form.php'); - $action = new moodle_url('/enrol/wallet/extra/action.php'); - $couponform = new \enrol_wallet\form\applycoupon_form(null, $data); + $couponaction = new moodle_url('/enrol/wallet/extra/coupon_action.php'); + $couponform = new enrol_wallet\form\applycoupon_form($couponaction, $data); if ($submitteddata = $couponform->get_data()) { enrol_wallet_process_coupon_data($submitteddata); } - ob_start(); - $couponform->display(); - $render .= ob_get_clean(); + + $render .= $couponform->render(); } // If plugin block_vc exist, add credit options by it. @@ -603,9 +519,7 @@ function enrol_wallet_display_topup_options() { require_once("$CFG->dirroot/blocks/vc/classes/form/vc_credit_form.php"); $vcform = new \block_vc\form\vc_credit_form($CFG->wwwroot.'/blocks/vc/credit.php'); - ob_start(); - $vcform->display(); - $render .= $OUTPUT->box(ob_get_clean()); + $render .= $OUTPUT->box($vcform->render()); // This code make the container collapsed at the load of the page, where setExpanded not working. $jscode = " @@ -615,6 +529,7 @@ function enrol_wallet_display_topup_options() { $PAGE->requires->js_init_code($jscode, true); } + // Display teller men (user with capabilities to credit and choosen in the settings to be displayed). $tellermen = get_config('enrol_wallet', 'tellermen'); if (!empty($tellermen)) { require_once($CFG->dirroot.'/user/lib.php'); @@ -633,6 +548,7 @@ function enrol_wallet_display_topup_options() { $render .= html_writer::end_tag('ul'); $render .= $OUTPUT->box_end(); } + // Display the manual refund policy. $policy = get_config('enrol_wallet', 'refundpolicy'); if (!empty($policy) && !empty($render)) { @@ -704,78 +620,6 @@ function enrol_wallet_is_valid_account($accountid) { return true; } -/** - * Display the form to let users transfer balance to each other. - * @return string - */ -function enrol_wallet_get_transfer_form() { - $transferenabled = get_config('enrol_wallet', 'transfer_enabled'); - if (empty($transferenabled)) { - return ''; - } - - global $CFG, $USER; - require_once($CFG->libdir.'/formslib.php'); - $isparent = false; - if (file_exists("$CFG->dirroot/auth/parent/auth.php")) { - require_once("$CFG->dirroot/auth/parent/lib.php"); - $isparent = auth_parent_is_parent($USER); - } - - $url = new moodle_url('/enrol/wallet/extra/transfer.php'); - - $mform = new MoodleQuickForm('wallet_transfer', 'post', $url); - - $balance = enrol_wallet\transactions::get_user_balance($USER->id); - $currency = get_config('enrol_wallet', 'currency'); - - $mform->addElement('header', 'transferformhead', get_string('transfer', 'enrol_wallet')); - - $displaybalance = format_string(format_float($balance, 2) . ' ' . $currency); - $mform->addElement('static', 'displaybalance', get_string('availablebalance', 'enrol_wallet'), $displaybalance); - - if ($isparent) { - $options = []; - foreach (auth_parent_get_children($USER) as $childid) { - $child = core_user::get_user($childid); - $options[$child->email] = fullname($child); - } - $mform->addElement('select', 'email', get_string('user'), $options); - - $mform->addElement('hidden', 'parent'); - $mform->setType('parent', PARAM_BOOL); - $mform->setDefault('parent', true); - } else { - $mform->addElement('text', 'email', get_string('email')); - $mform->setType('email', PARAM_EMAIL); - } - - $mform->addElement('text', 'amount', get_string('amount', 'enrol_wallet')); - $mform->setType('amount', PARAM_FLOAT); - - $percentfee = get_config('enrol_wallet', 'transferpercent'); - if (!empty($percentfee) && !$isparent) { - $a = ['fee' => $percentfee]; - $feefrom = get_config('enrol_wallet', 'transferfee_from'); - $a['from'] = get_string($feefrom, 'enrol_wallet'); - - $mform->addElement('static', 'feedesc', - get_string('transferpercent', 'enrol_wallet'), - get_string('transferfee_desc', 'enrol_wallet', $a)); - } - - $mform->addElement('submit', 'confirm', get_string('confirm')); - - $mform->addElement('hidden', 'sesskey'); - $mform->setType('sesskey', PARAM_TEXT); - $mform->setDefault('sesskey', sesskey()); - - ob_start(); - $mform->display(); - $output = ob_get_clean(); - return $output; -} - /** * Check if the user is eligible to get enrolled with insufficient balance. * @param null|int|stdClass $userid null for current user. @@ -805,8 +649,8 @@ function enrol_wallet_is_borrow_eligible($userid = null) { return false; } - $op = new enrol_wallet\transactions; - $balance = $op->get_user_balance($userid); + $op = new enrol_wallet\util\balance_op($userid); + $balance = $op->get_main_balance(); if ($balance < 0) { return false; @@ -818,6 +662,7 @@ function enrol_wallet_is_borrow_eligible($userid = null) { 'userid' => $userid, ]; $where = 'timecreated >= :period AND type = :type AND userid = :userid'; + $where .= " AND (category is NULL OR category = 0)"; $count = $DB->count_records_select('enrol_wallet_transactions', $where, $params); if ($count >= $number) { return true; diff --git a/settings.php b/settings.php index 77536bbd..ce99f3a9 100644 --- a/settings.php +++ b/settings.php @@ -23,6 +23,8 @@ */ defined('MOODLE_INTERNAL') || die(); +use enrol_wallet\util\balance; +use enrol_wallet\coupons; $context = context_system::instance(); @@ -66,27 +68,27 @@ get_string('pluginname_desc', 'enrol_wallet'))); // Adding choice between using wordpress (woowallet) of internal moodle wallet. $sources = [ - enrol_wallet\transactions::SOURCE_WORDPRESS => get_string('sourcewordpress', 'enrol_wallet'), - enrol_wallet\transactions::SOURCE_MOODLE => get_string('sourcemoodle', 'enrol_wallet'), + balance::WP => get_string('sourcewordpress', 'enrol_wallet'), + balance::MOODLE => get_string('sourcemoodle', 'enrol_wallet'), ]; $settings->add(new admin_setting_configselect('enrol_wallet/walletsource', get_string('walletsource', 'enrol_wallet'), get_string('walletsource_help', 'enrol_wallet'), - enrol_wallet\transactions::SOURCE_MOODLE, + balance::MOODLE, $sources)); $settings->add(new admin_setting_configcheckbox('enrol_wallet/wordpressloggins', get_string('wordpressloggins', 'enrol_wallet'), get_string('wordpressloggins_desc', 'enrol_wallet'), 0)); $settings->hide_if('enrol_wallet/wordpressloggins', 'enrol_wallet/walletsource', - 'eq', enrol_wallet\transactions::SOURCE_MOODLE); + 'eq', balance::MOODLE); // Define the WordPress site URL configuration setting. $settings->add(new admin_setting_configtext('enrol_wallet/wordpress_url', get_string('wordpressurl', 'enrol_wallet'), get_string('wordpressurl_desc', 'enrol_wallet'), 'https://example.com' // Default value for the WordPress site URL. )); - $settings->hide_if('enrol_wallet/wordpress_url', 'enrol_wallet/walletsource', 'eq', enrol_wallet\transactions::SOURCE_MOODLE); + $settings->hide_if('enrol_wallet/wordpress_url', 'enrol_wallet/walletsource', 'eq', balance::MOODLE); // Secret shared key. $settings->add(new admin_setting_configtext('enrol_wallet/wordpress_secretkey', get_string('wordpress_secretkey', 'enrol_wallet'), @@ -94,7 +96,7 @@ 'S0mTh1ng/123' )); $settings->hide_if('enrol_wallet/wordpress_secretkey', 'enrol_wallet/walletsource', - 'eq', enrol_wallet\transactions::SOURCE_MOODLE); + 'eq', balance::MOODLE); // Note: let's reuse the ext sync constants and strings here, internally it is very similar, // it describes what should happened when users are not supposed to be enrolled any more. @@ -258,16 +260,11 @@ $menu)); // Adding options to enable and disable coupons. - $choices = [ - enrol_wallet_plugin::WALLET_NOCOUPONS => get_string('nocoupons', 'enrol_wallet'), - enrol_wallet_plugin::WALLET_COUPONSFIXED => get_string('couponsfixed', 'enrol_wallet'), - enrol_wallet_plugin::WALLET_COUPONSDISCOUNT => get_string('couponsdiscount', 'enrol_wallet'), - enrol_wallet_plugin::WALLET_COUPONSALL => get_string('couponsall', 'enrol_wallet'), - ]; - $settings->add(new admin_setting_configselect('enrol_wallet/coupons', + $choices = coupons::get_coupons_options(); + $settings->add(new admin_setting_configmulticheckbox('enrol_wallet/coupons', get_string('couponstype', 'enrol_wallet'), get_string('couponstype_help', 'enrol_wallet'), - enrol_wallet_plugin::WALLET_NOCOUPONS, + [], $choices)); // Add settings for conditional discount. diff --git a/styles.css b/styles.css index bb8b861b..8327888f 100644 --- a/styles.css +++ b/styles.css @@ -4,15 +4,26 @@ background-color: rgb(255 255 255 / 47%); border-radius: 5px; opacity: .9; - font-size: 15px; + font-size: 85%; font-weight: 600; color: darkred; - padding-top: 1px; + padding-top: 2%; margin-left: -5px; width: 27px; font-variant-caps: all-small-caps; text-align: center; } +.enrol-wallet-balance-details { + display: flex; + align-items: stretch; + justify-content: center; + align-content: stretch; + flex-wrap: wrap; + gap: 10px; +} +.enrol_wallet_offer { + clip-path: polygon(50% 0%, 61% 18%, 80% 10%, 79% 31%, 100% 35%, 87% 54%, 100% 70%, 82% 74%, 80% 90%, 62% 88%, 50% 100%, 38% 86%, 20% 90%, 19% 73%, 0% 70%, 14% 53%, 0% 35%, 21% 30%, 20% 10%, 39% 16%); +} @media (min-width: 768px) { #page-enrol-wallet-confirm #region-main { diff --git a/tests/transactions_test.php b/tests/transactions_test.php index 81f5f498..c8708e53 100644 --- a/tests/transactions_test.php +++ b/tests/transactions_test.php @@ -37,6 +37,7 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class transactions_test extends \advanced_testcase { + /** * The transactions class. * @var @@ -174,14 +175,13 @@ public function test_get_coupon_value(): void { 'maxusage' => 1, ]; $DB->insert_record('enrol_wallet_coupons', $coupon); + $couponhelper = new coupons('test1', $user->id); - $coupondata = transactions::get_coupon_value('test1', $user->id); - - $this->assertEquals(50, $coupondata['value']); - $this->assertEquals('fixed', $coupondata['type']); - + $this->assertEquals(50, $couponhelper->get_value()); + $this->assertEquals('fixed', $couponhelper->get_type()); + $this->assertTrue($couponhelper->validate_coupon(coupons::AREA_TOPUP)); $sink = $this->redirectEvents(); - transactions::mark_coupon_used('test1', $user->id, 0); + $couponhelper->mark_coupon_used(); // Check the event triggered. $events = $sink->get_events(); $sink->close(); @@ -189,7 +189,7 @@ public function test_get_coupon_value(): void { $this->assertInstanceOf('\enrol_wallet\event\coupon_used', $events[0]); $this->assertEquals('test1', $events[0]->other['code']); - $coupondata = transactions::get_coupon_value('test1', $user->id); + $coupondata = coupons::get_coupon_value('test1', $user->id); $this->assertTrue(is_string($coupondata)); @@ -197,17 +197,4 @@ public function test_get_coupon_value(): void { $this->assertEquals($user->id, $usage->userid); } - - /** - * Summary of test_validate_coupon - * - * @covers ::validate_coupon - * @return void - */ - public function test_validate_coupon(): void { - global $DB; - $this->resetAfterTest(); - - } - } diff --git a/tests/turn_non_refundable_test.php b/tests/turn_non_refundable_test.php index 7bc778eb..74d5907a 100644 --- a/tests/turn_non_refundable_test.php +++ b/tests/turn_non_refundable_test.php @@ -25,6 +25,8 @@ use enrol_wallet\transactions; use enrol_wallet\task\turn_non_refundable; +use enrol_wallet\util\balance; +use enrol_wallet\util\balance_op; /** * Testing the adhoc task to transform certain amount to nonrefundable. @@ -47,9 +49,10 @@ public function test_turn_non_refundable(): void { $period = get_config('enrol_wallet', 'refundperiod'); $this->assertEquals(14 * DAYSECS, $period); - transactions::payment_topup(200, $user->id); - $balance = transactions::get_user_balance($user->id); - $norefund = transactions::get_nonrefund_balance($user->id); + $op = new balance_op($user->id); + $op->credit(200); + $balance = $op->get_main_balance(); + $norefund = $op->get_main_nonrefundable(); $this->assertEquals(200, $balance); $this->assertEquals(0, $norefund); @@ -65,15 +68,16 @@ public function test_turn_non_refundable(): void { ob_start(); $task->execute(); ob_end_clean(); - - $balance = transactions::get_user_balance($user->id); - $norefund = transactions::get_nonrefund_balance($user->id); + $op = new balance_op($user->id); + $balance = $op->get_main_balance(); + $norefund = $op->get_main_nonrefundable(); $this->assertEquals(200, $balance); $this->assertEquals(50, $norefund); + $this->assertEquals(0, $op->get_total_free()); // Test that the debited amount not transformed as it already used. - transactions::debit($user->id, 40); + $op->debit(40, $op::OTHER); $taskdata['amount'] = 50; @@ -84,11 +88,11 @@ public function test_turn_non_refundable(): void { $task->execute(); ob_end_clean(); - $balance = transactions::get_user_balance($user->id); - $norefund = transactions::get_nonrefund_balance($user->id); + $op = new balance_op($user->id); - $this->assertEquals(160, $balance); - $this->assertEquals(60, $norefund); + $this->assertEquals(160, $op->get_main_balance()); + $this->assertEquals(60, $op->get_main_nonrefundable()); + $this->assertEquals(0, $op->get_total_free()); // Test that if the amount is greater than the balance, nonrefundable not exceed balance. $taskdata['amount'] = 250; @@ -100,11 +104,13 @@ public function test_turn_non_refundable(): void { $task->execute(); ob_end_clean(); - $balance = transactions::get_user_balance($user->id); - $norefund = transactions::get_nonrefund_balance($user->id); + $bal = new balance($user->id); + $balance = $bal->get_main_balance(); + $norefund = $bal->get_main_nonrefundable(); $this->assertEquals(160, $balance); $this->assertEquals(160, $norefund); + $this->assertEquals(0, $bal->get_total_free()); } /** @@ -117,7 +123,9 @@ public function test_check_transform_validation(): void { $user = $this->getDataGenerator()->create_user(); // Charge the wallet with already nonrefundable balance. - transactions::payment_topup(200, $user->id, '', '', false); + $op = new balance_op($user->id); + $op->credit(200, $op::OTHER, 0, '', false); + $data = (object)[ 'userid' => $user->id, 'amount' => 200, @@ -128,12 +136,12 @@ public function test_check_transform_validation(): void { $this->assertStringContainsString('Non refundable amount grater than or equal user\'s balance', $output1); // Charge more with refundable balance. - transactions::payment_topup(100, $user->id); + $op->credit(100); $output2 = $task->check_transform_validation($data, $trace); $this->assertEquals($output2, 200); // Not transform what already used. - transactions::debit($user->id, 50); + $op->debit(50); $output3 = $task->check_transform_validation($data, $trace); $this->assertEquals($output3, 150); diff --git a/tests/util/balance_op_test.php b/tests/util/balance_op_test.php new file mode 100644 index 00000000..7ce6d529 --- /dev/null +++ b/tests/util/balance_op_test.php @@ -0,0 +1,616 @@ +. + +/** + * Contains tests for balance operations + * + * @package enrol_wallet + * @category test + * @copyright 2024 Mohammad Farouk + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace enrol_wallet\util; + +use enrol_wallet\util\balance_op; +use enrol_wallet\util\balance; +use enrol_wallet\transactions; + +/** + * Tests for balance operations class. + * + * @package enrol_wallet + * @category test + * @copyright 2024 Mohammad Farouk + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class balance_op_test extends \advanced_testcase { + /** + * Test conditional discounts. + * @covers ::conditional_discount_charging() + * @return void + */ + public function test_conditional_discount_charging(): void { + global $DB; + $this->resetAfterTest(); + + $user1 = $this->getDataGenerator()->create_user(); + $user2 = $this->getDataGenerator()->create_user(); + $user3 = $this->getDataGenerator()->create_user(); + $user4 = $this->getDataGenerator()->create_user(); + $this->setAdminUser(); + global $USER; + $now = time(); + set_config('conditionaldiscount_apply', 1, 'enrol_wallet'); + $params = [ + 'cond' => 400, + 'percent' => 15, + 'timecreated' => $now, + 'timemodified' => $now, + 'usermodified' => $USER->id, + ]; + $DB->insert_record('enrol_wallet_cond_discount', $params); + + $params = [ + 'cond' => 600, + 'percent' => 20, + 'timecreated' => $now, + 'timemodified' => $now, + 'usermodified' => $USER->id, + ]; + $DB->insert_record('enrol_wallet_cond_discount', $params); + + $params = [ + 'cond' => 800, + 'percent' => 25, + 'timecreated' => $now, + 'timemodified' => $now, + 'usermodified' => $USER->id, + ]; + $DB->insert_record('enrol_wallet_cond_discount', $params); + + $params = [ + 'cond' => 200, + 'percent' => 50, + 'timeto' => $now - DAYSECS, // Expired. + 'timecreated' => $now, + 'timemodified' => $now, + 'usermodified' => $USER->id, + ]; + $DB->insert_record('enrol_wallet_cond_discount', $params); + + $params = [ + 'cond' => 400, + 'percent' => 50, + 'timefrom' => $now + DAYSECS, // Not available yet. + 'timecreated' => $now, + 'timemodified' => $now, + 'usermodified' => $USER->id, + ]; + $DB->insert_record('enrol_wallet_cond_discount', $params); + + transactions::payment_topup(200, $user1->id); + // The user tries to pay 500, this is the number passes to the function. + $extra2 = 500 * 0.15; + transactions::payment_topup(500 * 0.85, $user2->id); + + $extra3 = 700 * 0.2; + transactions::payment_topup(700 * 0.8, $user3->id); + + $extra4 = 1000 * 0.25; + transactions::payment_topup(1000 * 0.75, $user4->id); + + $balance = new balance($user1->id); + $balance1 = $balance->get_total_balance(); + $norefund1 = $balance->get_total_nonrefundable(); + $free1 = $balance->get_total_free(); + + $balance = new balance($user2->id); + $balance2 = $balance->get_total_balance(); + $norefund2 = $balance->get_total_nonrefundable(); + $free2 = $balance->get_total_free(); + + $balance = new balance($user3->id); + $balance3 = $balance->get_total_balance(); + $norefund3 = $balance->get_total_nonrefundable(); + $free3 = $balance->get_total_free(); + + $balance = new balance($user4->id); + $balance4 = $balance->get_total_balance(); + $norefund4 = $balance->get_total_nonrefundable(); + $free4 = $balance->get_total_free(); + + $this->assertEquals(200, $balance1); + $this->assertEquals(0, $norefund1); + $this->assertEquals(0, $free1); + + $this->assertEquals(500, $balance2); + $this->assertEquals($extra2, $norefund2); + $this->assertEquals($extra2, $free2); + + $this->assertEquals(700, $balance3); + $this->assertEquals($extra3, $norefund3); + $this->assertEquals($extra3, $free3); + + $this->assertEquals(1000, $balance4); + $this->assertEquals($extra4, $norefund4); + $this->assertEquals($extra4, $free4); + + } + + /** + * Test credit function. + * @covers ::credit + * @return void + */ + public function test_credit():void { + global $DB; + $this->resetAfterTest(); + $gen = $this->getDataGenerator(); + $user1 = $gen->create_user(); + $user2 = $gen->create_user(); + $cat1 = $gen->create_category(); + $cat2 = $gen->create_category(); + + $op = new balance_op($user1->id); + $op->credit(100); + $this->assertEquals(100, $op->get_main_balance()); + $this->assertEquals(100, $op->get_main_refundable()); + $this->assertEquals(0, $op->get_main_nonrefundable()); + $this->assertEquals(100, $op->get_total_balance()); + $this->assertEquals(100, $op->get_total_refundable()); + $this->assertEquals(0, $op->get_total_nonrefundable()); + $this->assertEquals(100, $op->get_valid_balance()); + $this->assertEquals(0, $op->get_valid_nonrefundable()); + + $op = new balance_op($user1->id, $cat1); + $this->assertEquals(100, $op->get_main_balance()); + $this->assertEquals(100, $op->get_main_refundable()); + $this->assertEquals(0, $op->get_main_nonrefundable()); + $this->assertEquals(100, $op->get_total_balance()); + $this->assertEquals(100, $op->get_total_refundable()); + $this->assertEquals(0, $op->get_total_nonrefundable()); + $this->assertEquals(100, $op->get_valid_balance()); + $this->assertEquals(0, $op->get_valid_nonrefundable()); + + $op->credit(100); + $this->assertEquals(100, $op->get_main_balance()); + $this->assertEquals(100, $op->get_main_refundable()); + $this->assertEquals(0, $op->get_main_nonrefundable()); + $this->assertEquals(200, $op->get_total_balance()); + $this->assertEquals(200, $op->get_total_refundable()); + $this->assertEquals(0, $op->get_total_nonrefundable()); + $this->assertEquals(200, $op->get_valid_balance()); + $this->assertEquals(0, $op->get_valid_nonrefundable()); + + $op = new balance_op($user1->id, $cat2); + $this->assertEquals(100, $op->get_main_balance()); + $this->assertEquals(100, $op->get_main_refundable()); + $this->assertEquals(0, $op->get_main_nonrefundable()); + $this->assertEquals(200, $op->get_total_balance()); + $this->assertEquals(200, $op->get_total_refundable()); + $this->assertEquals(0, $op->get_total_nonrefundable()); + $this->assertEquals(100, $op->get_valid_balance()); + $this->assertEquals(0, $op->get_valid_nonrefundable()); + + $op->credit(30); + $this->assertEquals(100, $op->get_main_balance()); + $this->assertEquals(100, $op->get_main_refundable()); + $this->assertEquals(0, $op->get_main_nonrefundable()); + $this->assertEquals(230, $op->get_total_balance()); + $this->assertEquals(230, $op->get_total_refundable()); + $this->assertEquals(0, $op->get_total_nonrefundable()); + $this->assertEquals(130, $op->get_valid_balance()); + $this->assertEquals(0, $op->get_valid_nonrefundable()); + + $op = new balance_op($user1->id); + $this->assertEquals(100, $op->get_main_balance()); + $this->assertEquals(100, $op->get_main_refundable()); + $this->assertEquals(0, $op->get_main_nonrefundable()); + $this->assertEquals(230, $op->get_total_balance()); + $this->assertEquals(230, $op->get_total_refundable()); + $this->assertEquals(0, $op->get_total_nonrefundable()); + $this->assertEquals(100, $op->get_valid_balance()); + $this->assertEquals(0, $op->get_valid_nonrefundable()); + + $op->credit(40, $op::OTHER, 0, '', false); + $this->assertEquals(140, $op->get_main_balance()); + $this->assertEquals(100, $op->get_main_refundable()); + $this->assertEquals(40, $op->get_main_nonrefundable()); + $this->assertEquals(270, $op->get_total_balance()); + $this->assertEquals(230, $op->get_total_refundable()); + $this->assertEquals(40, $op->get_total_nonrefundable()); + $this->assertEquals(140, $op->get_valid_balance()); + $this->assertEquals(40, $op->get_valid_nonrefundable()); + + $op = new balance_op($user1->id, $cat1); + $this->assertEquals(140, $op->get_main_balance()); + $this->assertEquals(100, $op->get_main_refundable()); + $this->assertEquals(40, $op->get_main_nonrefundable()); + $this->assertEquals(270, $op->get_total_balance()); + $this->assertEquals(230, $op->get_total_refundable()); + $this->assertEquals(40, $op->get_total_nonrefundable()); + $this->assertEquals(240, $op->get_valid_balance()); + $this->assertEquals(40, $op->get_valid_nonrefundable()); + + $op->credit(70, $op::OTHER, 0, '', false); + $this->assertEquals(140, $op->get_main_balance()); + $this->assertEquals(100, $op->get_main_refundable()); + $this->assertEquals(40, $op->get_main_nonrefundable()); + $this->assertEquals(340, $op->get_total_balance()); + $this->assertEquals(230, $op->get_total_refundable()); + $this->assertEquals(110, $op->get_total_nonrefundable()); + $this->assertEquals(310, $op->get_valid_balance()); + $this->assertEquals(110, $op->get_valid_nonrefundable()); + + $op = new balance_op($user1->id, $cat2); + $this->assertEquals(140, $op->get_main_balance()); + $this->assertEquals(100, $op->get_main_refundable()); + $this->assertEquals(40, $op->get_main_nonrefundable()); + $this->assertEquals(340, $op->get_total_balance()); + $this->assertEquals(230, $op->get_total_refundable()); + $this->assertEquals(110, $op->get_total_nonrefundable()); + $this->assertEquals(170, $op->get_valid_balance()); + $this->assertEquals(40, $op->get_valid_nonrefundable()); + + $op->credit(20, $op::OTHER, 0, '', false); + $this->assertEquals(140, $op->get_main_balance()); + $this->assertEquals(100, $op->get_main_refundable()); + $this->assertEquals(40, $op->get_main_nonrefundable()); + $this->assertEquals(360, $op->get_total_balance()); + $this->assertEquals(230, $op->get_total_refundable()); + $this->assertEquals(130, $op->get_total_nonrefundable()); + $this->assertEquals(190, $op->get_valid_balance()); + $this->assertEquals(60, $op->get_valid_nonrefundable()); + + $count = $DB->count_records('enrol_wallet_transactions', ['userid' => $user1->id]); + $this->assertEquals(6, $count); + $record = [ + 'userid' => $user1->id, + 'category' => 0, + 'balance' => 100, + 'balbefore' => 0, + 'amount' => 100, + 'norefund' => 0, + ]; + $exist1 = $DB->record_exists('enrol_wallet_transactions', $record); + $record = [ + 'userid' => $user1->id, + 'category' => 0, + 'balance' => 140, + 'balbefore' => 100, + 'amount' => 40, + 'norefund' => 40, + ]; + $exist2 = $DB->record_exists('enrol_wallet_transactions', $record); + $record = [ + 'userid' => $user1->id, + 'category' => $cat1->id, + 'balance' => 100, + 'balbefore' => 0, + 'amount' => 100, + 'norefund' => 0, + ]; + $exist3 = $DB->record_exists('enrol_wallet_transactions', $record); + $record = [ + 'userid' => $user1->id, + 'category' => $cat1->id, + 'balance' => 170, + 'balbefore' => 100, + 'amount' => 70, + 'norefund' => 70, + ]; + $exist4 = $DB->record_exists('enrol_wallet_transactions', $record); + $record = [ + 'userid' => $user1->id, + 'category' => $cat2->id, + 'balance' => 30, + 'balbefore' => 0, + 'amount' => 30, + 'norefund' => 0, + ]; + $exist5 = $DB->record_exists('enrol_wallet_transactions', $record); + $record = [ + 'userid' => $user1->id, + 'category' => $cat2->id, + 'balance' => 50, + 'balbefore' => 30, + 'amount' => 20, + 'norefund' => 20, + ]; + $exist6 = $DB->record_exists('enrol_wallet_transactions', $record); + $this->assertTrue($exist1); + $this->assertTrue($exist2); + $this->assertTrue($exist3); + $this->assertTrue($exist4); + $this->assertTrue($exist5); + $this->assertTrue($exist6); + } + + /** + * Test debit method + * @covers ::debit + * @return void + */ + public function test_debit():void { + global $DB; + $this->resetAfterTest(); + $gen = $this->getDataGenerator(); + $user1 = $gen->create_user(); + $user2 = $gen->create_user(); + $cat1 = $gen->create_category(); + $cat2 = $gen->create_category(); + $cat3 = $gen->create_category(['parent' => $cat2->id]); + $catbalance = [ + $cat1->id => (object)[ + 'refundable' => 50, + 'nonrefundable' => 30, + ], + $cat2->id => (object)[ + 'refundable' => 70, + 'nonrefundable' => 100, + ], + ]; + $record = [ + 'userid' => $user1->id, + 'refundable' => 200, + 'nonrefundable' => 120, + 'cat_balance' => json_encode($catbalance), + ]; + $DB->insert_record('enrol_wallet_balance', $record, false); + $op = new balance_op($user1->id); + $this->assertEquals(320, $op->get_main_balance()); + $this->assertEquals(200, $op->get_main_refundable()); + $this->assertEquals(120, $op->get_main_nonrefundable()); + $this->assertEquals(320, $op->get_total_refundable()); + $this->assertEquals(250, $op->get_total_nonrefundable()); + $this->assertEquals(570, $op->get_total_balance()); + $this->assertEquals(320, $op->get_valid_balance()); + $this->assertEquals(120, $op->get_valid_nonrefundable()); + + $this->setUser($user1); + $op->debit(40); + $this->assertEquals(280, $op->get_main_balance()); + $this->assertEquals(160, $op->get_main_refundable()); + $this->assertEquals(120, $op->get_main_nonrefundable()); + $this->assertEquals(280, $op->get_total_refundable()); + $this->assertEquals(250, $op->get_total_nonrefundable()); + $this->assertEquals(530, $op->get_total_balance()); + $this->assertEquals(280, $op->get_valid_balance()); + $this->assertEquals(120, $op->get_valid_nonrefundable()); + + $op = new balance_op($user1->id, $cat1); + $this->assertEquals(280, $op->get_main_balance()); + $this->assertEquals(160, $op->get_main_refundable()); + $this->assertEquals(120, $op->get_main_nonrefundable()); + $this->assertEquals(280, $op->get_total_refundable()); + $this->assertEquals(250, $op->get_total_nonrefundable()); + $this->assertEquals(530, $op->get_total_balance()); + $this->assertEquals(360, $op->get_valid_balance()); + $this->assertEquals(150, $op->get_valid_nonrefundable()); + + $op->debit(10); + $this->assertEquals(280, $op->get_main_balance()); + $this->assertEquals(160, $op->get_main_refundable()); + $this->assertEquals(120, $op->get_main_nonrefundable()); + $this->assertEquals(270, $op->get_total_refundable()); + $this->assertEquals(250, $op->get_total_nonrefundable()); + $this->assertEquals(520, $op->get_total_balance()); + $this->assertEquals(350, $op->get_valid_balance()); + $this->assertEquals(150, $op->get_valid_nonrefundable()); + + $op->debit(50); + $this->assertEquals(280, $op->get_main_balance()); + $this->assertEquals(160, $op->get_main_refundable()); + $this->assertEquals(120, $op->get_main_nonrefundable()); + $this->assertEquals(230, $op->get_total_refundable()); + $this->assertEquals(240, $op->get_total_nonrefundable()); + $this->assertEquals(470, $op->get_total_balance()); + $this->assertEquals(300, $op->get_valid_balance()); + $this->assertEquals(140, $op->get_valid_nonrefundable()); + + $op->debit(40); + $this->assertEquals(260, $op->get_main_balance()); + $this->assertEquals(140, $op->get_main_refundable()); + $this->assertEquals(120, $op->get_main_nonrefundable()); + $this->assertEquals(210, $op->get_total_refundable()); + $this->assertEquals(220, $op->get_total_nonrefundable()); + $this->assertEquals(430, $op->get_total_balance()); + $this->assertEquals(260, $op->get_valid_balance()); + $this->assertEquals(120, $op->get_valid_nonrefundable()); + + $op = new balance_op($user1->id, $cat3); + $this->assertEquals(260, $op->get_main_balance()); + $this->assertEquals(140, $op->get_main_refundable()); + $this->assertEquals(120, $op->get_main_nonrefundable()); + $this->assertEquals(210, $op->get_total_refundable()); + $this->assertEquals(220, $op->get_total_nonrefundable()); + $this->assertEquals(430, $op->get_total_balance()); + $this->assertEquals(430, $op->get_valid_balance()); + $this->assertEquals(220, $op->get_valid_nonrefundable()); + + $op->credit(30); + $op->credit(40, $op::OTHER, 0, '', false); + $this->assertEquals(260, $op->get_main_balance()); + $this->assertEquals(140, $op->get_main_refundable()); + $this->assertEquals(120, $op->get_main_nonrefundable()); + $this->assertEquals(240, $op->get_total_refundable()); + $this->assertEquals(260, $op->get_total_nonrefundable()); + $this->assertEquals(500, $op->get_total_balance()); + $this->assertEquals(500, $op->get_valid_balance()); + $this->assertEquals(260, $op->get_valid_nonrefundable()); + + $op = new balance_op($user1->id, $cat2); + $this->assertEquals(260, $op->get_main_balance()); + $this->assertEquals(140, $op->get_main_refundable()); + $this->assertEquals(120, $op->get_main_nonrefundable()); + $this->assertEquals(240, $op->get_total_refundable()); + $this->assertEquals(260, $op->get_total_nonrefundable()); + $this->assertEquals(500, $op->get_total_balance()); + $this->assertEquals(430, $op->get_valid_balance()); + $this->assertEquals(220, $op->get_valid_nonrefundable()); + + $op = new balance_op($user1->id, $cat3); + $op->debit(300); + $this->assertEquals(200, $op->get_main_balance()); + $this->assertEquals(80, $op->get_main_refundable()); + $this->assertEquals(120, $op->get_main_nonrefundable()); + $this->assertEquals(80, $op->get_total_refundable()); + $this->assertEquals(120, $op->get_total_nonrefundable()); + $this->assertEquals(200, $op->get_total_balance()); + $this->assertEquals(200, $op->get_valid_balance()); + $this->assertEquals(120, $op->get_valid_nonrefundable()); + } + + /** + * Test free balance add and deduct + * @covers ::get_total_free + * @return void + */ + public function test_free_balance():void { + global $DB; + $this->resetAfterTest(); + $gen = $this->getDataGenerator(); + $cat1 = $gen->create_category(); + $cat2 = $gen->create_category(); + $course1 = $gen->create_course(['category' => $cat1->id]); + $course2 = $gen->create_course(['category' => $cat2->id]); + $instance1 = $DB->get_record('enrol', ['courseid' => $course1->id, 'enrol' => 'wallet'], '*', MUST_EXIST); + $instance2 = $DB->get_record('enrol', ['courseid' => $course2->id, 'enrol' => 'wallet'], '*', MUST_EXIST); + $user1 = $gen->create_user(); + $user2 = $gen->create_user(); + $user3 = $gen->create_user(); + $user4 = $gen->create_user(); + $user5 = $gen->create_user(); + $user6 = $gen->create_user(); + $user7 = $gen->create_user(); + $user8 = $gen->create_user(); + $user9 = $gen->create_user(); + $user10 = $gen->create_user(); + + $op = new balance_op($user1->id); + $op->credit(50, $op::C_ACCOUNT_GIFT, 0, '', false); + $op->credit(100); + $this->assertEquals(50, $op->get_main_free()); + $this->assertEquals(150, $op->get_main_balance()); + $sink = $this->redirectEvents(); + $op->debit(30, $op::OTHER); + $this->assertEquals(50, $op->get_main_free()); + $this->assertEquals(120, $op->get_main_balance()); + $events = $sink->get_events(); + $sink->clear(); + + foreach ($events as $key => $event) { + if ($event->eventname !== '\enrol_wallet\event\transactions_triggered') { + unset($events[$key]); + } + } + + $this->assertEquals(1, count($events)); + $debitevent = reset($events); + $this->assertEquals(0, $debitevent->other['freecut']); + + $op->debit(100, $op::OTHER); + $this->assertEquals(20, $op->get_main_free()); + $this->assertEquals(20, $op->get_main_balance()); + $events = $sink->get_events(); + $sink->close(); + + foreach ($events as $key => $event) { + if ($event->eventname !== '\enrol_wallet\event\transactions_triggered') { + unset($events[$key]); + } + } + + $this->assertEquals(1, count($events)); + $debitevent = reset($events); + $this->assertEquals(30, $debitevent->other['freecut']); + + $op = new balance_op($user2->id); + $op->credit(50, $op::C_REFERRAL, $user3->id, '', false); + $this->assertEquals(50, $op->get_main_free()); + + $op = new balance_op($user3->id); + $op->credit(50, $op::C_AWARD, $course1->id, '', false); + $this->assertEquals(50, $op->get_total_free()); + $this->assertEquals(50, $op->get_valid_free()); + $this->assertEquals(0, $op->get_main_free()); + + $op = new balance_op($user4->id); + $op->credit(50, $op::C_ROLLBACK, $instance1->id, '', false); + $this->assertEquals(50, $op->get_total_free()); + $this->assertEquals(50, $op->get_valid_free()); + $this->assertEquals(0, $op->get_main_free()); + + $op = new balance_op($user5->id); + $op->credit(50, $op::C_CASHBACK, $course2->id, '', false); + $this->assertEquals(50, $op->get_total_free()); + $this->assertEquals(50, $op->get_valid_free()); + $this->assertEquals(0, $op->get_main_free()); + $op = new balance_op($user5->id); + $op->credit(50, $op::C_DISCOUNT, 0, '', false); + $op->credit(50); + $op = new balance_op($user5->id, $cat2); + $this->assertEquals(150, $op->get_total_balance()); + $this->assertEquals(100, $op->get_main_balance()); + $this->assertEquals(150, $op->get_valid_balance()); + $this->assertEquals(50, $op->get_main_nonrefundable()); + $this->assertEquals(50, $op->get_main_refundable()); + $this->assertEquals(100, $op->get_total_nonrefundable()); + $this->assertEquals(100, $op->get_total_free()); + $this->assertEquals(100, $op->get_valid_free()); + $op = new balance_op($user5->id, $cat1); + $this->assertEquals(150, $op->get_total_balance()); + $this->assertEquals(100, $op->get_main_balance()); + $this->assertEquals(100, $op->get_valid_balance()); + $this->assertEquals(50, $op->get_main_nonrefundable()); + $this->assertEquals(50, $op->get_main_refundable()); + $this->assertEquals(100, $op->get_total_nonrefundable()); + $this->assertEquals(100, $op->get_total_free()); + $this->assertEquals(50, $op->get_valid_free()); + $op = new balance_op($user5->id, $cat2); + $sink = $this->redirectEvents(); + $op->debit(120, $op::OTHER); + $events = $sink->get_events(); + $sink->close(); + + foreach ($events as $key => $event) { + if ($event->eventname !== '\enrol_wallet\event\transactions_triggered') { + unset($events[$key]); + } + } + + $this->assertEquals(1, count($events)); + $debitevent = reset($events); + $this->assertEquals(70, $debitevent->other['freecut']); + + $op = new balance_op($user5->id, $cat2); + $this->assertEquals(30, $op->get_total_balance()); + $this->assertEquals(30, $op->get_main_balance()); + $this->assertEquals(30, $op->get_valid_balance()); + $this->assertEquals(30, $op->get_main_nonrefundable()); + $this->assertEquals(0, $op->get_main_refundable()); + $this->assertEquals(30, $op->get_total_nonrefundable()); + $this->assertEquals(30, $op->get_total_free()); + $this->assertEquals(30, $op->get_valid_free()); + + $op = new balance_op($user6->id); + $op->credit(50, $op::C_DISCOUNT, 0, '', false); + $this->assertEquals(50, $op->get_main_free()); + $this->assertEquals(50, $op->get_total_free()); + $this->assertEquals(50, $op->get_valid_free()); + } + +} diff --git a/tests/util/instance_test.php b/tests/util/instance_test.php new file mode 100644 index 00000000..1b9cb282 --- /dev/null +++ b/tests/util/instance_test.php @@ -0,0 +1,213 @@ +. + +namespace enrol_wallet\util; + +defined('MOODLE_INTERNAL') || die(); +global $CFG; +require_once($CFG->dirroot.'/enrol/wallet/lib.php'); + +use enrol_wallet_plugin; + +use enrol_wallet\coupons; +/** + * Tests for Wallet enrolment + * + * @package enrol_wallet + * @category test + * @copyright 2024 2024, Mohammad Farouk + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class instance_test extends \advanced_testcase { + /** + * Testing get cost after discount. + * + * @covers ::get_cost_after_discount() + */ + public function test_get_cost_after_discount(): void { + global $DB; + self::resetAfterTest(true); + + $walletplugin = new enrol_wallet_plugin; + // Check that cost after discount return the original cost. + $user1 = $this->getDataGenerator()->create_user(); + $course1 = $this->getDataGenerator()->create_course(); + + $instance1 = $DB->get_record('enrol', ['courseid' => $course1->id, 'enrol' => 'wallet'], '*', MUST_EXIST); + $instance1->customint6 = 1; + $instance1->cost = 200; + $DB->update_record('enrol', $instance1); + $walletplugin->update_status($instance1, ENROL_INSTANCE_ENABLED); + + $costafter = $walletplugin->get_cost_after_discount($user1->id, $instance1); + $this->assertEquals($costafter, $instance1->cost); + // Check the discounts according to user profile field. + // Create a custom profile field. + $fielddata = (object)[ + 'name' => 'discount', + 'shortname' => 'discount', + ]; + $fieldid = $DB->insert_record('user_info_field', $fielddata, true); + + $walletplugin->set_config('discount_field', $fieldid); + $op = new balance_op($user1->id); + $op->credit(150); + $userfielddata = (object)[ + 'userid' => $user1->id, + 'fieldid' => $fieldid, + 'data' => 'free', + ]; + $userdataid = $DB->insert_record('user_info_data', $userfielddata); + $costafter = $walletplugin->get_cost_after_discount($user1->id, $instance1, true); + $this->assertEquals(0, $costafter); + + $dataupdate = (object)[ + 'id' => $userdataid, + 'data' => '20% discount', + ]; + $DB->update_record('user_info_data', $dataupdate); + $costafter = $walletplugin->get_cost_after_discount($user1->id, $instance1, true); + $this->assertEquals(200 * 80 / 100, $costafter); + + // Check coupon discounts. + $user2 = $this->getDataGenerator()->create_user(); + $op = new balance_op($user2->id); + $op->credit(150); + + // Create percent discount coupon. + $all = implode(',', coupons::TYPES); + set_config('coupons', $all, 'enrol_wallet'); + $coupon = [ + 'code' => 'test1', + 'type' => 'percent', + 'value' => 50, + 'maxusage' => 1, + ]; + $DB->insert_record('enrol_wallet_coupons', $coupon); + coupons::set_session_coupon('test1'); + $this->assertEquals('test1', coupons::check_discount_coupon()); + $costafter = $walletplugin->get_cost_after_discount($user2->id, $instance1, true); + $this->assertEquals(100, $costafter); + set_config('coupons', coupons::DISCOUNT, 'enrol_wallet'); + $costafter = $walletplugin->get_cost_after_discount($user2->id, $instance1, true); + $this->assertEquals(100, $costafter); + set_config('coupons', coupons::FIXED .',' . coupons::ENROL, 'enrol_wallet'); + $costafter = $walletplugin->get_cost_after_discount($user2->id, $instance1, true); + $this->assertEquals(200, $costafter); + set_config('coupons', coupons::NOCOUPONS, 'enrol_wallet'); + $costafter = $walletplugin->get_cost_after_discount($user2->id, $instance1, true); + $this->assertEquals(200, $costafter); + coupons::unset_session_coupon(); + + // Create a fixed & category coupons and check that there is no discount. + set_config('coupons', enrol_wallet_plugin::WALLET_COUPONSALL, 'enrol_wallet'); + $coupon = [ + 'code' => 'test2', + 'type' => 'fixed', + 'value' => 50, + 'maxusage' => 1, + ]; + $DB->insert_record('enrol_wallet_coupons', $coupon); + $coupon = [ + 'code' => 'test3', + 'type' => 'category', + 'value' => 50, + 'maxusage' => 1, + 'category' => $course1->category, + ]; + $DB->insert_record('enrol_wallet_coupons', $coupon); + coupons::set_session_coupon('test2'); + $costafter = $walletplugin->get_cost_after_discount($user2->id, $instance1, true); + $this->assertEquals(200, $costafter); + coupons::set_session_coupon('test3'); + $costafter = $walletplugin->get_cost_after_discount($user2->id, $instance1, true); + $this->assertEquals(200, $costafter); + coupons::unset_session_coupon(); + } + + /** + * Testing discounts after first and second repurchase + * @covers ::get_cost_after_discount + */ + public function test_repurchase_discount_and_function() { + global $DB; + $this->resetAfterTest(); + $wallet = new enrol_wallet_plugin; + // Now lets check the discount for repurchace. + $course2 = $this->getDataGenerator()->create_course(); + $context = \context_course::instance($course2->id); + + $instance = $DB->get_record('enrol', ['courseid' => $course2->id, 'enrol' => 'wallet'], '*', MUST_EXIST); + $instance->customint6 = 1; + $instance->cost = 100; + $instance->enrolperiod = DAYSECS; + $DB->update_record('enrol', $instance); + $wallet->update_status($instance, ENROL_INSTANCE_ENABLED); + + $user = $this->getDataGenerator()->create_user(); + $op = new balance_op($user->id); + $op->credit(450); + + $wallet->enrol_self($instance, $user); + $record = $DB->get_record('user_enrolments', ['enrolid' => $instance->id, 'userid' => $user->id]); + $record->timemodified = time() - 10 * DAYSECS; + $record->timecreated = time() - 10 * DAYSECS; + $DB->update_record('user_enrolments', $record); + + $op = new balance_op($user->id); + $this->assertTrue(is_enrolled($context, $user)); + $this->assertEquals(350, $op->get_total_balance()); + + // The enrolment expired. + $wallet->update_user_enrol($instance, $user->id, ENROL_USER_ACTIVE, time() - 5 * DAYSECS, time() - 3 * DAYSECS); + $DB->update_record('user_enrolments', (object)['id' => $record->id, 'timemodified' => time() - 5 * DAYSECS]); + + $this->assertFalse(is_enrolled($context, $user, '', true)); + // Check the cost again. + $inst = new instance($instance, $user->id); + $this->setUser($user); + $this->assertNotTrue($wallet->can_self_enrol($instance)); + $this->assertEquals(100, $inst->get_cost_after_discount(true)); + + // Enable the repurchase. + set_config('repurchase', 1, 'enrol_wallet'); + $this->assertTrue($wallet->can_self_enrol($instance)); + $this->assertEquals(100, $inst->get_cost_after_discount(true)); + + // Set first discount. + set_config('repurchase_firstdis', 40, 'enrol_wallet'); + $this->assertTrue($wallet->can_self_enrol($instance)); + $this->assertEquals(60, $inst->get_cost_after_discount(true)); + + // Make sure it is not affected by second discount option. + set_config('repurchase_seconddis', 60, 'enrol_wallet'); + $this->assertEquals(60, $inst->get_cost_after_discount(true)); + $wallet = new enrol_wallet_plugin; + // Enrol the user. + $wallet->enrol_self($instance, $user); + $this->assertTrue(is_enrolled($context, $user, '', true)); + $balance = balance::create_from_instance($instance, $user->id); + $this->assertEquals(290, $balance->get_valid_balance()); + + // Expire the user enrolment. + $wallet->update_user_enrol($instance, $user->id, ENROL_USER_ACTIVE, time() - 2 * DAYSECS, time() - 2 * HOURSECS); + $record = $DB->get_record('user_enrolments', ['enrolid' => $instance->id, 'userid' => $user->id]); + $DB->update_record('user_enrolments', (object)['id' => $record->id, 'timemodified' => time() - 2 * DAYSECS]); + + $this->assertFalse(is_enrolled($context, $user, '', true)); + $this->assertEquals(40, $inst->get_cost_after_discount(true)); + } +} diff --git a/version.php b/version.php index 9071cbeb..d3079672 100644 --- a/version.php +++ b/version.php @@ -24,7 +24,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2023121701; +$plugin->version = 2024020300; $plugin->requires = 2020061500; $plugin->component = 'enrol_wallet'; $plugin->release = '4.5.0';