PluginProbe ʕ •ᴥ•ʔ
Tutor LMS – eLearning and online course solution / 3.9.5
Tutor LMS – eLearning and online course solution v3.9.5
3.9.14 3.9.13 3.9.12 3.9.11 trunk 1.0.0 1.0.0-alpha 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.2.0 1.2.1 1.2.11 1.2.12 1.2.13 1.2.20 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.9 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 1.7.3 1.7.4 1.7.5 1.7.6 1.7.7 1.7.8 1.7.9 1.8.0 1.8.1 1.8.10 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 1.8.9 1.9.0 1.9.1 1.9.10 1.9.11 1.9.12 1.9.13 1.9.14 1.9.15 1.9.16 1.9.2 1.9.3 1.9.4 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.0.1 2.0.10 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 2.1.0 2.1.1 2.1.10 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.3.0 2.4.0 2.5.0 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 3.0.0 3.0.1 3.0.2 3.1.0 3.2.0 3.2.1 3.2.2 3.2.3 3.3.0 3.3.1 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.7.0 3.7.1 3.7.2 3.7.3 3.7.4 3.8.0 3.8.1 3.8.2 3.8.3 3.9.0 3.9.1 3.9.10 3.9.2 3.9.3 3.9.4 3.9.5 3.9.6 3.9.7 3.9.8 3.9.9
tutor / classes / Earnings.php
tutor / classes Last commit date
Addons.php 11 months ago Admin.php 8 months ago Ajax.php 9 months ago Announcements.php 1 year ago Assets.php 7 months ago Backend_Page_Trait.php 1 year ago BaseController.php 1 year ago Config.php 11 months ago Container.php 11 months ago Course.php 6 months ago Course_Embed.php 3 years ago Course_Filter.php 1 year ago Course_List.php 10 months ago Course_Settings_Tabs.php 1 year ago Course_Widget.php 1 year ago Custom_Validation.php 3 years ago Dashboard.php 1 year ago Earnings.php 9 months ago FormHandler.php 2 years ago Frontend.php 1 year ago Gutenberg.php 1 year ago Icon.php 8 months ago Input.php 1 year ago Instructor.php 1 year ago Instructors_List.php 11 months ago Lesson.php 8 months ago Options_V2.php 7 months ago Permalink.php 2 years ago Post_types.php 1 year ago Private_Course_Access.php 1 year ago Q_And_A.php 10 months ago Question_Answers_List.php 11 months ago Quiz.php 5 months ago QuizBuilder.php 11 months ago Quiz_Attempts_List.php 9 months ago RestAPI.php 2 years ago Reviews.php 9 months ago Rewrite_Rules.php 2 years ago Shortcode.php 9 months ago Singleton.php 1 year ago Student.php 1 year ago Students_List.php 1 year ago Taxonomies.php 1 year ago Template.php 9 months ago Theme_Compatibility.php 3 years ago Tools.php 1 year ago Tools_V2.php 1 year ago Tutor.php 7 months ago TutorEDD.php 1 year ago Tutor_Base.php 2 years ago Tutor_Setup.php 8 months ago Upgrader.php 9 months ago User.php 5 months ago Utils.php 5 months ago Video_Stream.php 3 years ago WhatsNew.php 9 months ago Withdraw.php 1 year ago Withdraw_Requests_List.php 11 months ago WooCommerce.php 7 months ago
Earnings.php
422 lines
1 <?php
2 /**
3 * Manage earnings
4 *
5 * @package Tutor\Ecommerce
6 * @author Themeum <support@themeum.com>
7 * @link https://themeum.com
8 * @since 3.0.0
9 */
10
11 namespace TUTOR;
12
13 use Tutor\Ecommerce\Ecommerce;
14 use TUTOR\Singleton;
15 use Tutor\Models\OrderModel;
16 use Tutor\Helpers\QueryHelper;
17
18 /**
19 * Manage earnings
20 */
21 class Earnings extends Singleton {
22 /**
23 * Constant for earning process by
24 *
25 * @since 3.8.2
26 */
27 const PROCESS_BY_TUTOR = 'tutor';
28 const PROCESS_BY_WOOCOMMERCE = 'woocommerce';
29
30 /**
31 * Error message for the invalid earning data
32 *
33 * @since 1.0.0
34 *
35 * @var string
36 */
37 const INVALID_DATA_MSG = 'Invalid earning data';
38
39 /**
40 * Earning table name
41 *
42 * @since 3.0.0
43 *
44 * @var string
45 */
46 private $earning_table;
47
48 /**
49 * Order id
50 *
51 * @since 3.0.0
52 *
53 * @var int
54 */
55 private $order_id;
56
57 /**
58 * Keep earning data here
59 *
60 * @since 3.0.0
61 *
62 * @var array
63 */
64 public $earning_data = array();
65
66 /**
67 * Set table name prop
68 *
69 * @since 3.0.0
70 */
71 protected function __construct() {
72 global $wpdb;
73 $this->earning_table = $wpdb->prefix . 'tutor_earnings';
74 }
75
76 /**
77 * Prepare earnings from this order to store it as
78 * earning & commission data.
79 *
80 * @since 3.0.0
81 *
82 * @param int $order_id Order id.
83 *
84 * @return mixed
85 */
86 public function prepare_order_earnings( int $order_id ) {
87 $this->order_id = $order_id;
88
89 $order_model = new OrderModel();
90 $order_details = $order_model->get_order_by_id( $order_id );
91 $items = is_object( $order_details ) && property_exists( $order_details, 'items' ) ? $order_details->items : array();
92
93 $deducted_amount = $order_details->refund_amount + $order_details->coupon_amount;
94 if ( $order_details->discount_amount ) {
95 $discount_amount = $order_model->calculate_discount_amount( $order_details->discount_type, $order_details->discount_amount, $order_details->subtotal_price );
96 $deducted_amount += $discount_amount;
97 }
98
99 if ( is_array( $items ) && count( $items ) ) {
100
101 foreach ( $items as $item ) {
102
103 $subtotal_price = $item->regular_price;
104 $item_sold_price = $order_model->get_item_sold_price( $item->id, false );
105
106 try {
107 $per_earning_refund = $order_details->subtotal_price
108 ? ( $deducted_amount * $subtotal_price ) / $order_details->subtotal_price
109 : 0;
110 } catch ( \Throwable $th ) {
111 tutor_log( $th );
112 $per_earning_refund = 0;
113 }
114
115 // Split deduct amount fro admin & instructor.
116 $split_deduction = tutor_split_amounts( $per_earning_refund );
117
118 // Split earnings.
119 $split_earnings = tutor_split_amounts( $subtotal_price );
120
121 // Deduct earnings.
122 $admin_amount = $split_earnings['admin'] - $split_deduction['admin'];
123 $instructor_amount = $split_earnings['instructor'] - $split_deduction['instructor'];
124
125 $course_id = $item->id;
126
127 if ( OrderModel::TYPE_SINGLE_ORDER !== $order_details->order_type ) {
128 $plan_info = apply_filters( 'tutor_get_plan_info', null, $course_id );
129 if ( $plan_info && isset( $plan_info->is_membership_plan ) && $plan_info->is_membership_plan ) {
130 $course_id = null;
131 } else {
132 $course_id = apply_filters( 'tutor_subscription_course_by_plan', $item->id, $order_details );
133 }
134 }
135
136 $this->earning_data[] = $this->prepare_earning_data( $item_sold_price, $course_id, $order_id, $order_details->order_status, $admin_amount, $instructor_amount );
137 }
138 }
139 }
140
141 /**
142 * Get process by
143 *
144 * @since 3.8.2
145 *
146 * @return string
147 */
148 private function get_process_by() {
149 $monetize_by = tutor_utils()->get_option( 'monetize_by' );
150
151 switch ( $monetize_by ) {
152 case Ecommerce::MONETIZE_BY:
153 $process_by = self::PROCESS_BY_TUTOR;
154 break;
155 case WooCommerce::MONETIZE_BY:
156 $process_by = self::PROCESS_BY_WOOCOMMERCE;
157 break;
158 default:
159 $process_by = '';
160 break;
161 }
162
163 return $process_by;
164 }
165
166 /**
167 * Prepare earning data
168 *
169 * @since 3.0.0
170 *
171 * @param mixed $total_price Total price of an item.
172 * @param int $course_id Connected course id.
173 * @param int $order_id Order id.
174 * @param string $order_status Order status.
175 * @param string $admin_amount Admin amount.
176 * @param string $instructor_amount Instructor status.
177 *
178 * @return array
179 */
180 public function prepare_earning_data( $total_price, $course_id, $order_id, $order_status, $admin_amount, $instructor_amount ) {
181 $fees_deduct_data = array();
182 $tutor_earning_fees = tutor_utils()->get_option( 'fee_amount_type' );
183 $enable_fees_deducting = tutor_utils()->get_option( 'enable_fees_deducting' );
184
185 $course_price_grand_total = $total_price;
186
187 // Site maintenance fees.
188 $fees_amount = 0;
189
190 // Deduct predefined amount (percent or fixed).
191 if ( $enable_fees_deducting ) {
192 $fees_name = tutor_utils()->get_option( 'fees_name', '' );
193 $fees_amount = (float) tutor_utils()->avalue_dot( 'fees_amount', $tutor_earning_fees );
194 $fees_type = tutor_utils()->avalue_dot( 'fees_type', $tutor_earning_fees );
195
196 if ( $fees_amount > 0 ) {
197 if ( 'percent' === $fees_type ) {
198 $fees_amount = ( $total_price * $fees_amount ) / 100;
199 }
200
201 $course_price_grand_total = max( $total_price - $fees_amount, 0 );
202 }
203
204 $fees_deduct_data = array(
205 'deduct_fees_amount' => $fees_amount,
206 'deduct_fees_name' => $fees_name,
207 'deduct_fees_type' => $fees_type,
208 );
209 }
210
211 if ( $fees_amount ) {
212 list( $admin_fees, $instructor_fees ) = array_values( tutor_split_amounts( $fees_amount ) );
213
214 // Deduct fees.
215 $admin_amount -= $admin_fees;
216 $instructor_amount -= $instructor_fees;
217 }
218
219 // Distribute amount between admin and instructor.
220 $sharing_enabled = tutor_utils()->get_option( 'enable_revenue_sharing' );
221 $instructor_rate = $sharing_enabled ? tutor_utils()->get_option( 'earning_instructor_commission' ) : 0;
222 $admin_rate = $sharing_enabled ? tutor_utils()->get_option( 'earning_admin_commission' ) : 100;
223 $commission_type = 'percent';
224
225 // Course author id.
226 $user_id = get_post_field( 'post_author', $course_id );
227
228 // (Use Pro Filter - Start)
229 // The response must be same array structure.
230 // Do not change used variable names here, or change in both of here and pro plugin
231 $pro_arg = array(
232 'user_id' => $user_id,
233 'instructor_rate' => $instructor_rate,
234 'admin_rate' => $admin_rate,
235 'instructor_amount' => max( 0, $instructor_amount ),
236 'admin_amount' => max( 0, $admin_amount ),
237 'course_price_grand_total' => $course_price_grand_total,
238 'commission_type' => $commission_type,
239 );
240
241 $pro_calculation = apply_filters( 'tutor_pro_earning_calculator', $pro_arg );
242 extract( $pro_calculation ); //phpcs:ignore
243 // (Use Pro Filter - End).
244
245 // Prepare insertable earning data.
246 $earning_data = array(
247 'user_id' => $user_id,
248 'course_id' => $course_id,
249 'order_id' => $order_id,
250 'order_status' => $order_status,
251 'course_price_total' => $total_price,
252 'course_price_grand_total' => $course_price_grand_total,
253
254 'instructor_amount' => $instructor_amount,
255 'instructor_rate' => $instructor_rate,
256 'admin_amount' => $admin_amount,
257 'admin_rate' => $admin_rate,
258
259 'commission_type' => $commission_type,
260 'process_by' => self::get_process_by(),
261 'created_at' => current_time( 'mysql', true ),
262 );
263 $earning_data = apply_filters( 'tutor_new_earning_data', array_merge( $earning_data, $fees_deduct_data ) );
264
265 return $earning_data;
266 }
267
268 /**
269 * Get order earnings
270 *
271 * @since 3.0.0
272 *
273 * @param int $order_id Order id.
274 *
275 * @return mixed Array of objects on success
276 */
277 public function get_order_earnings( int $order_id ) {
278 return QueryHelper::get_all(
279 $this->earning_table,
280 array( 'order_id' => $order_id ),
281 'earning_id'
282 );
283 }
284
285 /**
286 * Store earnings
287 *
288 * @since 3.0.0
289 *
290 * @throws \Exception If earning_data is empty.
291 *
292 * @return int On success inserted id will be returned
293 */
294 public function store_earnings() {
295 if ( empty( $this->earning_data ) ) {
296 throw new \Exception( self::INVALID_DATA_MSG );
297 }
298
299 $inserted_id = 0;
300 try {
301 foreach ( $this->earning_data as $earning ) {
302 $inserted_id = QueryHelper::insert( $this->earning_table, $earning );
303 }
304 } catch ( \Throwable $th ) {
305 throw new \Exception( $th->getMessage() );
306 }
307
308 return $inserted_id;
309 }
310
311 /**
312 * Check if earning for a order already exists
313 *
314 * @since 3.0.0
315 *
316 * @param int $order_id Order id.
317 *
318 * @return mixed Earning row if exists, false|null otherwise.
319 */
320 public function is_exist_order_earning( $order_id ) {
321 $row = QueryHelper::get_row(
322 $this->earning_table,
323 array(
324 'order_id' => $order_id,
325 ),
326 'earning_id'
327 );
328
329 return $row;
330 }
331
332 /**
333 * Update earning data
334 *
335 * Use prepare_order_earnings before updating
336 *
337 * @since 3.0.0
338 *
339 * @param int $earning_id Earning id.
340 *
341 * @throws \Exception If earning_data is empty.
342 *
343 * @return bool true|false
344 */
345 public function update_earning( $earning_id ) {
346 if ( empty( $this->earning_data ) ) {
347 throw new \Exception( self::INVALID_DATA_MSG );
348 }
349
350 $update = QueryHelper::update(
351 $this->earning_table,
352 $this->earning_data,
353 array( 'earning_id' => $earning_id )
354 );
355
356 if ( $update ) {
357 $this->earning_data = null;
358 }
359
360 return $update;
361 }
362
363 /**
364 * Delete earning
365 *
366 * @since 3.0.0
367 *
368 * @param int $earning_id Earning id.
369 *
370 * @return bool true|false
371 */
372 public function delete_earning( $earning_id ) {
373 return QueryHelper::delete(
374 $this->earning_table,
375 array( 'earning_id' => $earning_id )
376 );
377 }
378
379 /**
380 * Delete earning by order id
381 *
382 * @since 3.0.0
383 *
384 * @param int $order_id Order id.
385 *
386 * @return bool true|false
387 */
388 public function delete_earning_by_order( $order_id ) {
389 return QueryHelper::delete(
390 $this->earning_table,
391 array( 'order_id' => $order_id )
392 );
393 }
394
395 /**
396 * Before storing earning this method will check if
397 * earning exist for the given order id. If found it will
398 * remove then store.
399 *
400 * @since 3.0.0
401 *
402 * @throws \Exception If earning_data is empty.
403 *
404 * @return int On success inserted id will be returned
405 */
406 public function remove_before_store_earnings() {
407 if ( empty( $this->earning_data ) ) {
408 throw new \Exception( self::INVALID_DATA_MSG );
409 }
410
411 if ( $this->is_exist_order_earning( $this->order_id ) ) {
412 $this->delete_earning_by_order( $this->order_id );
413 }
414
415 try {
416 return $this->store_earnings();
417 } catch ( \Throwable $th ) {
418 tutor_log( $th );
419 }
420 }
421 }
422