From 70d5f60c15cfcb3265223ec6bfa25733a6fb18e2 Mon Sep 17 00:00:00 2001 From: sanjana4khan Date: Tue, 27 Oct 2026 10:37:41 +0600 Subject: [PATCH 1/7] =?UTF-8?q?Fix(=F0=9F=9B=A0):after=20course=20expirati?= =?UTF-8?q?on=20user=20can=20access=20the=20course=20through=20success=20u?= =?UTF-8?q?rl.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ecommerce/HooksHandler.php | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/ecommerce/HooksHandler.php b/ecommerce/HooksHandler.php index 84ae7416e5..0e17987471 100644 --- a/ecommerce/HooksHandler.php +++ b/ecommerce/HooksHandler.php @@ -285,19 +285,13 @@ public function handle_payment_status_changed( $order_id, $prev_payment_status, */ public function handle_order_placement_success( int $order_id ) { $order_data = $this->order_model->get_order_by_id( $order_id ); - if ( $order_data ) { - $user_id = $order_data->student->id; - - ( new CartModel() )->clear_user_cart( $user_id ); - - // Manage enrollment & earnings. - $order = ( new OrderModel() )->get_order_by_id( $order_id ); - $payment_status = $order->payment_status; - - $order_status = $this->order_model->get_order_status_by_payment_status( $payment_status ); - - $this->manage_earnings_and_enrollments( $order_status, $order_id ); + if ( ! $order_data ) { + return; } + + // Only the buyer may trigger post-checkout processing. + $user_id = (int) $order_data->student->id; + ( new CartModel() )->clear_user_cart( $user_id ); } /** From 088e09509bc134dcd7b459d5ddb6d51f8afd10ee Mon Sep 17 00:00:00 2001 From: sanjana4khan Date: Tue, 27 Oct 2026 15:44:43 +0600 Subject: [PATCH 2/7] tutor_order_enrolled hook added. --- ecommerce/HooksHandler.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ecommerce/HooksHandler.php b/ecommerce/HooksHandler.php index 361e13ddee..0bdc79c315 100644 --- a/ecommerce/HooksHandler.php +++ b/ecommerce/HooksHandler.php @@ -521,8 +521,12 @@ public function handle_free_checkout( array $order_data ) { add_filter( 'tutor_enroll_data', fn( $enroll_data ) => array_merge( $enroll_data, array( 'post_status' => 'completed' ) ) ); $enrolled_id = EnrollmentModel::do_enroll( $item['item_id'], $order_data['id'], $user_id ); - if ( $enrolled_id && tutor_utils()->is_addon_enabled( 'course-bundle' ) && get_post_type( $item['item_id'] ) === CourseBundle::POST_TYPE ) { - BundleModel::enroll_to_bundle_courses( $item['item_id'], $user_id ); + if ( $enrolled_id ) { + do_action( 'tutor_order_enrolled', (object) $order_data, $enrolled_id ); + + if ( tutor_utils()->is_addon_enabled( 'course-bundle' ) && get_post_type( $item['item_id'] ) === CourseBundle::POST_TYPE ) { + BundleModel::enroll_to_bundle_courses( $item['item_id'], $user_id ); + } } } From dacd877cd8c1ffc2aa52d2291240ffe1f1ee9818 Mon Sep 17 00:00:00 2001 From: sanjana4khan Date: Tue, 27 Oct 2026 17:06:58 +0600 Subject: [PATCH 3/7] removed unnecessary comments --- ecommerce/HooksHandler.php | 1 - 1 file changed, 1 deletion(-) diff --git a/ecommerce/HooksHandler.php b/ecommerce/HooksHandler.php index 0bdc79c315..a3c902e1c2 100644 --- a/ecommerce/HooksHandler.php +++ b/ecommerce/HooksHandler.php @@ -296,7 +296,6 @@ public function handle_order_placement_success( int $order_id ) { return; } - // Only the buyer may trigger post-checkout processing. $user_id = (int) $order_data->student->id; ( new CartModel() )->clear_user_cart( $user_id ); } From 500eed87f4103e98bca5a76c051e00bcd709710e Mon Sep 17 00:00:00 2001 From: sanjana4khan Date: Wed, 28 Oct 2026 11:51:58 +0600 Subject: [PATCH 4/7] comment added --- ecommerce/HooksHandler.php | 1 + 1 file changed, 1 insertion(+) diff --git a/ecommerce/HooksHandler.php b/ecommerce/HooksHandler.php index a3c902e1c2..983c5de0c8 100644 --- a/ecommerce/HooksHandler.php +++ b/ecommerce/HooksHandler.php @@ -285,6 +285,7 @@ public function handle_payment_status_changed( $order_id, $prev_payment_status, * Clear cart items, managing enrollment & earnings * * @since 3.0.0 + * @since 4.2.1 removed earnings and enrollment manage functionalities. * * @param int $order_id Order id. * From 9381340474fe62232a17ccd4f4a0a336eb70478a Mon Sep 17 00:00:00 2001 From: sanjana4khan Date: Thu, 29 Oct 2026 18:02:27 +0600 Subject: [PATCH 5/7] Different solution applied to solve enrollment issue --- ecommerce/HooksHandler.php | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/ecommerce/HooksHandler.php b/ecommerce/HooksHandler.php index 983c5de0c8..eeaa73f3c4 100644 --- a/ecommerce/HooksHandler.php +++ b/ecommerce/HooksHandler.php @@ -297,8 +297,16 @@ public function handle_order_placement_success( int $order_id ) { return; } - $user_id = (int) $order_data->student->id; - ( new CartModel() )->clear_user_cart( $user_id ); + $user_id = $order_data->student->id; + if ( get_current_user_id() === $user_id || ! isset( $order_data->enrollment ) ) { + ( new CartModel() )->clear_user_cart( $user_id ); + + // Manage enrollment & earnings. + $payment_status = $order_data->payment_status; + $order_status = $this->order_model->get_order_status_by_payment_status( $payment_status ); + + $this->manage_earnings_and_enrollments( $order_status, $order_id ); + } } /** @@ -521,12 +529,8 @@ public function handle_free_checkout( array $order_data ) { add_filter( 'tutor_enroll_data', fn( $enroll_data ) => array_merge( $enroll_data, array( 'post_status' => 'completed' ) ) ); $enrolled_id = EnrollmentModel::do_enroll( $item['item_id'], $order_data['id'], $user_id ); - if ( $enrolled_id ) { - do_action( 'tutor_order_enrolled', (object) $order_data, $enrolled_id ); - - if ( tutor_utils()->is_addon_enabled( 'course-bundle' ) && get_post_type( $item['item_id'] ) === CourseBundle::POST_TYPE ) { - BundleModel::enroll_to_bundle_courses( $item['item_id'], $user_id ); - } + if ( $enrolled_id && tutor_utils()->is_addon_enabled( 'course-bundle' ) && get_post_type( $item['item_id'] ) === CourseBundle::POST_TYPE ) { + BundleModel::enroll_to_bundle_courses( $item['item_id'], $user_id ); } } From 2977cc0e8bdee43a51af831d92c3c5cc5275ff8a Mon Sep 17 00:00:00 2001 From: Sanjana Khan Date: Fri, 30 Oct 2026 03:54:04 +0600 Subject: [PATCH 6/7] Different solution applied to solve enrollment issue --- ecommerce/HooksHandler.php | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/ecommerce/HooksHandler.php b/ecommerce/HooksHandler.php index eeaa73f3c4..0b90f401a4 100644 --- a/ecommerce/HooksHandler.php +++ b/ecommerce/HooksHandler.php @@ -285,7 +285,7 @@ public function handle_payment_status_changed( $order_id, $prev_payment_status, * Clear cart items, managing enrollment & earnings * * @since 3.0.0 - * @since 4.2.1 removed earnings and enrollment manage functionalities. + * @since 4.2.1 manage earnings and enrollment only for the buyer's unprocessed order. * * @param int $order_id Order id. * @@ -298,9 +298,17 @@ public function handle_order_placement_success( int $order_id ) { } $user_id = $order_data->student->id; - if ( get_current_user_id() === $user_id || ! isset( $order_data->enrollment ) ) { - ( new CartModel() )->clear_user_cart( $user_id ); + ( new CartModel() )->clear_user_cart( $user_id ); + /** + * Skip if the order is already processed, otherwise revisiting the success URL + * re-activates expired/cancelled enrollments and recalculates earnings. + * + * @since 4.2.1 + */ + $is_processed = isset( $order_data->enrollment ) || Earnings::get_instance()->is_exist_order_earning( $order_id ); + + if ( get_current_user_id() === $user_id && ! $is_processed ) { // Manage enrollment & earnings. $payment_status = $order_data->payment_status; $order_status = $this->order_model->get_order_status_by_payment_status( $payment_status ); @@ -529,8 +537,12 @@ public function handle_free_checkout( array $order_data ) { add_filter( 'tutor_enroll_data', fn( $enroll_data ) => array_merge( $enroll_data, array( 'post_status' => 'completed' ) ) ); $enrolled_id = EnrollmentModel::do_enroll( $item['item_id'], $order_data['id'], $user_id ); - if ( $enrolled_id && tutor_utils()->is_addon_enabled( 'course-bundle' ) && get_post_type( $item['item_id'] ) === CourseBundle::POST_TYPE ) { - BundleModel::enroll_to_bundle_courses( $item['item_id'], $user_id ); + if ( $enrolled_id ) { + do_action( 'tutor_order_enrolled', (object) $order_data, $enrolled_id ); + + if ( tutor_utils()->is_addon_enabled( 'course-bundle' ) && get_post_type( $item['item_id'] ) === CourseBundle::POST_TYPE ) { + BundleModel::enroll_to_bundle_courses( $item['item_id'], $user_id ); + } } } From 6c52a9988e9ed2c6b717493ba06710fd2680752c Mon Sep 17 00:00:00 2001 From: sanjana4khan Date: Fri, 30 Oct 2026 13:35:40 +0600 Subject: [PATCH 7/7] Issue fixed where multiple earnings are being created for free checkout with multiple courses. --- ecommerce/HooksHandler.php | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/ecommerce/HooksHandler.php b/ecommerce/HooksHandler.php index 0b90f401a4..41a36c4d2d 100644 --- a/ecommerce/HooksHandler.php +++ b/ecommerce/HooksHandler.php @@ -298,22 +298,22 @@ public function handle_order_placement_success( int $order_id ) { } $user_id = $order_data->student->id; - ( new CartModel() )->clear_user_cart( $user_id ); + if ( get_current_user_id() === $user_id ) { + ( new CartModel() )->clear_user_cart( $user_id ); - /** - * Skip if the order is already processed, otherwise revisiting the success URL - * re-activates expired/cancelled enrollments and recalculates earnings. - * - * @since 4.2.1 - */ - $is_processed = isset( $order_data->enrollment ) || Earnings::get_instance()->is_exist_order_earning( $order_id ); - - if ( get_current_user_id() === $user_id && ! $is_processed ) { - // Manage enrollment & earnings. - $payment_status = $order_data->payment_status; - $order_status = $this->order_model->get_order_status_by_payment_status( $payment_status ); + /** + * Skip if the order is already processed, otherwise revisiting the success URL re-activates expired/cancelled + * enrollments and recalculates earnings. + * + * @since 4.2.1 + */ + $is_processed = isset( $order_data->enrollment ) || Earnings::get_instance()->is_exist_order_earning( $order_id ); + if ( ! $is_processed ) { + $payment_status = $order_data->payment_status; + $order_status = $this->order_model->get_order_status_by_payment_status( $payment_status ); - $this->manage_earnings_and_enrollments( $order_status, $order_id ); + $this->manage_earnings_and_enrollments( $order_status, $order_id ); + } } } @@ -517,6 +517,7 @@ public function handle_free_checkout( array $order_data ) { $user_id = $order_data['user_id']; $items = $order_data['items']; $coupon_code = $order_data['coupon_code'] ?? ''; + $order = $this->order_model->get_order_by_id( $order_id ); // If a coupon brought the total to zero, reserve coupon usage before enrolling. if ( ! empty( $coupon_code ) ) { @@ -538,7 +539,7 @@ public function handle_free_checkout( array $order_data ) { $enrolled_id = EnrollmentModel::do_enroll( $item['item_id'], $order_data['id'], $user_id ); if ( $enrolled_id ) { - do_action( 'tutor_order_enrolled', (object) $order_data, $enrolled_id ); + do_action( 'tutor_order_enrolled', $order, $enrolled_id ); if ( tutor_utils()->is_addon_enabled( 'course-bundle' ) && get_post_type( $item['item_id'] ) === CourseBundle::POST_TYPE ) { BundleModel::enroll_to_bundle_courses( $item['item_id'], $user_id ); @@ -548,6 +549,11 @@ public function handle_free_checkout( array $order_data ) { // Promote coupon usage to consumed upon completed enrollment. $this->coupon_ctrl->store_coupon_usage( $order_id ); + + // Free orders never get a payment webhook, so recording their (zero) earnings. + $earnings = Earnings::get_instance(); + $earnings->prepare_order_earnings( $order_id ); + $earnings->remove_before_store_earnings(); } return $order_data; }