PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.4.8
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.4.8
4.4.8 4.4.7 4.4.6 4.4.5 4.4.4 4.4.3 4.4.2 4.4.1 4.4.0 4.3.9.1 4.3.9 4.3.8 4.3.7 4.1.6.9 4.1.6.9.1 4.1.6.9.2 4.1.6.9.3 4.1.6.9.4 4.1.7 4.1.7.1 4.1.7.2 4.1.7.3 4.1.7.3.1 4.1.7.3.2 4.2.0 All 139 releases
← All changes | inc/order/class-lp-order.php +638 -277 4.2.04.4.8 View file →
@@ -3,14 +3,19 @@
3 3 * Class LP_Order
4 4 *
5 5 * @author ThimPress
6 6 * @package LearnPress/Classes
7 - * @version 4.0.0
7 + * @version 4.0.1
8 8 */
9 9
10 -/**
11 - * Prevent loading this file directly
12 - */
10 +use LearnPress\Databases\Order\LPOrderItemsDB;
11 +use LearnPress\Databases\PostDB;
12 +use LearnPress\Filters\Order\OrderItemsFilter;
13 +use LearnPress\Filters\PostFilter;
14 +use LearnPress\Models\UserItems\UserCourseModel;
15 +use LearnPress\Models\UserItems\UserItemModel;
16 +use LearnPress\Models\UserModel;
17 +
13 18 defined( 'ABSPATH' ) || exit();
14 19
15 20 if ( ! class_exists( 'LP_Order' ) ) {
16 21
@@ -59,8 +64,15 @@
59 64 '_user_ip' => '',
60 65 '_checkout_email' => '',
61 66 );
62 67
68 + const META_KEY_TRANSACTION_ID = '_transaction_id';
69 + const META_KEY_REFUND_REQUEST = '_lp_refund_request';
70 + const META_KEY_REFUND_REQUEST_REASON = '_lp_refund_request_reason';
71 + const META_KEY_REFUNDED_BY = '_lp_refunded_by';
72 + const META_KEY_REFUNDED_AT = '_lp_refunded_at';
73 + const META_KEY_REFUNDED_AMOUNT = '_lp_refunded_amount';
74 +
63 75 /**
64 76 * Store order status in transactions.
65 77 *
66 78 * @var array
@@ -101,9 +113,9 @@
101 113
102 114 /**
103 115 * Set order date.
104 116 *
105 - * @param int|string $date
117 + * @param int|string $date Date time string is time zone of WP setting
106 118 */
107 119 public function set_order_date( $date ): LP_Order {
108 120 $this->set_data_date( 'order_date', $date );
109 121
@@ -196,33 +208,8 @@
196 208 return apply_filters( 'learn-press/confirm-order-received-text', __( 'Thank you. Your order has been received.', 'learnpress' ), $this->get_id() );
197 209 }
198 210
199 211 /**
200 - * Magic function for getting object property dynamic.
201 - *
202 - * @param string $prop
203 - *
204 - * @return int|mixed|null
205 - * @deprecated 4.2.0
206 - * @note Tungnx when change all direct access from $this->{key} to $this->get_data({key}), can remove this method
207 - */
208 - public function __get( $prop ) {
209 - _deprecated_function( __METHOD__, '4.2.0', 'get_data({key})' );
210 - if ( $prop == 'post' ) {
211 - // print_r( debug_backtrace() );
212 - // die( '$post is deprecated' );
213 - } elseif ( $prop == 'id' ) {
214 - return $this->get_id();
215 - }
216 - $value = null;
217 - if ( ! property_exists( $this, $prop ) ) {
218 - $value = get_post_meta( $this->get_id(), '_' . $prop, true );
219 - }
220 -
221 - return $value;
222 - }
223 -
224 - /**
225 212 * Checks to see if current order has status as passed.
226 213 *
227 214 * @param string|array $status String or an array of statuses
228 215 *
@@ -235,20 +222,8 @@
235 222 return apply_filters( 'learn-press/has-order-status', $has, $status, $this->get_id() );
236 223 }
237 224
238 225 /**
239 - * Check if order has a status as wp default.
240 - *
241 - * @return bool
242 - * @deprecated 4.2.0
243 - */
244 - public function has_invalid_status() {
245 - _deprecated_function( __METHOD__, '4.2.0' );
246 - return true;
247 - //return ! $this->has_status( learn_press_get_order_statuses( false, true ) );
248 - }
249 -
250 - /**
251 226 * Updates order to new status if needed
252 227 *
253 228 * @param mixed $new_status
254 229 * @param bool $manual Is this a manual order status change?.
@@ -258,16 +233,10 @@
258 233 public function update_status( $new_status = 'pending', $manual = false ): bool {
259 234 $result = false;
260 235
261 236 try {
262 - $old_status = $this->get_status();
263 -
264 - //do_action( 'learn-press/before-update-status-lp-order', $new_status, $old_status, $this, $manual );
265 -
266 237 $this->set_status( $new_status );
267 238 $result = $this->save();
268 -
269 - //do_action( 'learn-press/after-update-status-lp-order', $new_status, $old_status, $this, $manual );
270 239 } catch ( Throwable $e ) {
271 240 error_log( $e->getMessage() );
272 241 }
273 242
@@ -274,26 +243,8 @@
274 243 return $result;
275 244 }
276 245
277 246 /**
278 - * Set payment method for this order.
279 - * If payment method is an instance of LP_Gateway_Abstract then
280 - * update it to database.
281 - *
282 - * @param LP_Gateway_Abstract|string $payment_method
283 - * @deprecated 4.2.0
284 - */
285 - public function set_payment_method( $payment_method ) {
286 - _deprecated_function( __METHOD__, '4.2.0' );
287 - /*if ( $payment_method instanceof LP_Gateway_Abstract ) {
288 - update_post_meta( $this->get_id(), '_payment_method', $payment_method->get_id() );
289 - update_post_meta( $this->get_id(), '_payment_method_title', $payment_method->get_title() );
290 - }
291 -
292 - $this->payment_method = $payment_method;*/
293 - }
294 -
295 - /**
296 247 * Format order number id
297 248 *
298 249 * @return string
299 250 */
@@ -348,12 +299,15 @@
348 299 break;
349 300 case LP_ORDER_FAILED:
350 301 $status = __( 'Failed', 'learnpress' );
351 302 break;
303 + case LP_ORDER_TRASH:
304 + $status = __( 'Trash', 'learnpress' );
305 + break;
352 306 case 'on-hold':
353 307 $status = __( 'On hold', 'learnpress' );
354 308 break;
355 - case 'refunded':
309 + case LP_ORDER_REFUNDED:
356 310 $status = __( 'Refunded', 'learnpress' );
357 311 break;
358 312 default:
359 313 $status = '';
@@ -374,15 +328,16 @@
374 328 * @since 4.2.0
375 329 * @version 1.0.0
376 330 */
377 331 public static function get_icons_status(): array {
378 - $icons = [
332 + $icons = array(
379 333 LP_ORDER_COMPLETED => "<i class='dashicons dashicons-yes-alt'></i>",
380 334 LP_ORDER_PENDING => "<i class='dashicons dashicons-flag'></i>",
381 335 LP_ORDER_PROCESSING => "<i class='dashicons dashicons-clock'></i>",
382 336 LP_ORDER_CANCELLED => "<i class='dashicons dashicons-dismiss'></i>",
383 337 LP_ORDER_FAILED => "<i class='dashicons dashicons-warning'></i>",
384 - ];
338 + LP_ORDER_REFUNDED => "<i class='dashicons dashicons-undo'></i>",
339 + );
385 340
386 341 return apply_filters( 'lp/order/status/icons', $icons );
387 342 }
388 343
@@ -387,11 +342,13 @@
387 342 }
388 343
389 344 /**
390 345 * Set order status.
346 + * $new_status shouldn't have prefix 'lp-'
391 347 *
392 348 * @param string $new_status
393 349 * @param string $note - Optional. Note for changing status.
350 + *
394 351 * @sicne 3.0.0
395 352 * @version 1.0.1
396 353 */
397 354 public function set_status( string $new_status = '', string $note = '' ) {
@@ -396,9 +353,9 @@
396 353 */
397 354 public function set_status( string $new_status = '', string $note = '' ) {
398 355 // Ensure status not has prefix 'lp-'.
399 356 $new_status = str_replace( 'lp-', '', $new_status );
400 - $valid_statuses = array_values( LP_Order::get_order_statuses() );
357 + $valid_statuses = array_values( self::get_order_statuses() );
401 358 if ( ! in_array( $new_status, $valid_statuses ) && 'trash' !== $new_status ) {
402 359 $new_status = LP_ORDER_PENDING;
403 360 }
404 361
@@ -405,9 +362,9 @@
405 362 $this->_set_data( 'status', $new_status );
406 363 }
407 364
408 365 public function get_order_status_html() {
409 - $order_status = $this->get_status();
366 + $order_status = self::get_status_label( $this->get_status() );
410 367 $status = ucfirst( $order_status );
411 368 $class = 'order-status order-status-' . sanitize_title( $status );
412 369 $html = sprintf( '<span class="%s">%s</span>', apply_filters( 'learn_press_order_status_class', $class, $status, $this ), $status );
413 370
@@ -419,9 +376,8 @@
419 376 *
420 377 * @param string $transaction_id
421 378 *
422 379 * @return bool
423 - * @throws Exception
424 380 */
425 381 public function payment_complete( $transaction_id = '' ): bool {
426 382 do_action( 'learn-press/payment-pre-complete', $this->get_id() );
427 383
@@ -516,29 +472,46 @@
516 472
517 473 return apply_filters( 'learn-press/order/guest-customer-name', $customer_name );
518 474 }
519 475
520 - /*public function customer_exists() {
476 + /*
477 + public function customer_exists() {
521 478 return false !== get_userdata( $this->get_data( 'user_id' ) );
522 479 }*/
523 480
524 - public $order_items_loaded = false;
525 -
526 481 /**
527 - * Get items of the order
482 + * Get items of the order only to show.
528 483 *
529 484 * @return mixed
530 485 */
531 486 public function get_items() {
532 - if ( $this->order_items_loaded ) {
533 - return $this->order_items_loaded;
534 - }
487 + $items = $this->_curd->read_items( $this );
535 488
536 - $items = $this->_curd->read_items( $this );
489 + return apply_filters( 'learn-press/order-items', $items );
490 + }
537 491
538 - $this->order_items_loaded = $items;
492 + /**
493 + * Get all items of the order.
494 + * Only use for add/remove items to learn (learn_press_user_items) table.
495 + * Where call this function, must be careful, must set_time_limit( 0 );.
496 + *
497 + * @return array|null
498 + * @version 1.0.1
499 + * @since 4.2.7.2
500 + */
501 + public function get_all_items() {
502 + global $wpdb;
503 + $lp_order_items = LP_Database::getInstance();
504 + $table_order_items = $lp_order_items->tb_lp_order_items;
539 505
540 - return apply_filters( 'learn-press/order-items', $items );
506 + $query = $wpdb->prepare(
507 + "SELECT order_item_id, order_item_name, item_id, item_type
508 + From $table_order_items
509 + WHERE order_id = %d",
510 + $this->get_id()
511 + );
512 +
513 + return $wpdb->get_results( $query, ARRAY_A );
541 514 }
542 515
543 516 /**
544 517 * Get items of the order by filter
@@ -568,15 +541,15 @@
568 541 * @return array|bool
569 542 * @editor tungnx
570 543 */
571 544 public function get_item_ids() {
572 - $items = $this->get_items();
545 + $items = $this->get_all_items();
573 546
574 547 if ( $items ) {
575 548 $course_ids = array();
576 549 foreach ( $items as $item ) {
577 - if ( isset( $item['course_id'] ) ) {
578 - $course_ids[] = (int) $item['course_id'];
550 + if ( $item['item_type'] === LP_COURSE_CPT ) {
551 + $course_ids[] = (int) $item['item_id'];
579 552 }
580 553 }
581 554
582 555 return $course_ids;
@@ -583,9 +556,9 @@
583 556 /**
584 557 * Comment by tungnx. Because it will error if item didn't have key 'course_id'.
585 558 * Ex: case buy certificate, will not have that key
586 559 */
587 - //return @wp_list_pluck( $items, 'course_id' );
560 + // return @wp_list_pluck( $items, 'course_id' );
588 561 }
589 562
590 563 return false;
591 564 }
@@ -642,14 +615,14 @@
642 615 * @param array|int $item
643 616 *
644 617 * @return int
645 618 * @throws Exception
646 - * @editor tungnx
647 - * @modify 4.1.5
619 + * @since 1.0.0
620 + * @version 4.2.5
648 621 */
649 622 public function add_item( $item ): int {
650 623 global $wpdb;
651 - $lp_user_items_db = LP_User_Items_DB::getInstance();
624 + $order_item_id = 0;
652 625
653 626 try {
654 627 if ( is_numeric( $item ) ) {
655 628 $item = array(
@@ -657,9 +630,9 @@
657 630 'order_item_name' => get_the_title( $item ),
658 631 );
659 632 }
660 633
661 - $item_type = get_post_type( $item['item_id'] );
634 + $item_type = $item['item_type'] ?? get_post_type( $item['item_id'] );
662 635 if ( ! in_array( $item_type, learn_press_get_item_types_can_purchase() ) ) {
663 636 return false;
664 637 }
665 638
@@ -677,23 +650,12 @@
677 650 );
678 651
679 652 switch ( $item_type ) {
680 653 case LP_COURSE_CPT:
681 - $course = learn_press_get_course( $item['item_id'] );
682 - $item['subtotal'] = apply_filters( 'learnpress/order/item/subtotal', $course->get_price() * $item['quantity'], $course, $item );
683 - $item['total'] = apply_filters( 'learnpress/order/item/total', $course->get_price() * $item['quantity'], $course, $item );
684 - $item['order_item_name'] = apply_filters( 'learnpress/order/item/title', $course->get_title(), $course, $item );
685 -
686 - if ( $this->check_can_delete_item_old( $course ) ) {
687 - // Delete lp_user_items old
688 - $user_ids = $this->get_users();
689 - foreach ( $user_ids as $user_id ) {
690 - $lp_user_items_db->delete_user_items_old( $user_id, $course->get_id() );
691 - }
692 - // End
693 - }
694 -
695 - //learn_press_add_order_item_meta( $order_item_id, '_course_id', $item['item_id'] );
654 + $course = learn_press_get_course( $item['item_id'] );
655 + $item['subtotal'] = apply_filters( 'learnpress/order/item/subtotal', $course->get_price() * $item['quantity'], $course, $item, $this );
656 + $item['total'] = apply_filters( 'learnpress/order/item/total', $course->get_price() * $item['quantity'], $course, $item, $this );
657 + $item['order_item_name'] = apply_filters( 'learnpress/order/item/title', get_post_field( 'post_title', $item['item_id'], 'raw' ), $course, $item, $this );
696 658 $item['meta']['_course_id'] = $item['item_id'];
697 659 break;
698 660 default:
699 661 $item = apply_filters( 'learnpress/order/add-item/item_type_' . $item_type, $item );
@@ -716,12 +678,15 @@
716 678 '%s',
717 679 )
718 680 );
719 681 $order_item_id = absint( $wpdb->insert_id );
682 + // Clear cache
683 + $key = "order/{$this->get_id()}/{$this->get_status()}/items";
684 + LP_Cache::cache_load_first( 'clear', $key );
720 685 // End insert new order item
721 686
722 687 // Add learnpress_order_itemmeta
723 - $item['meta']['_quantity'] = $item['quantity'];
688 + $item['meta']['_quantity'] = $item['quantity'] ?? 1;
724 689 $item['meta']['_subtotal'] = $item['subtotal'] ?? 0;
725 690 $item['meta']['_total'] = $item['total'] ?? 0;
726 691
727 692 if ( is_array( $item['meta'] ) ) {
@@ -771,27 +736,21 @@
771 736 public function get_subtotal() {
772 737 return $this->get_data( 'subtotal' );
773 738 }
774 739
775 - public function cln() {
776 - return $this->_curd->cln( $this );
777 - }
778 -
779 - public function cln_items( $to ) {
780 - return $this->_curd->cln_items( $this->get_id(), $to );
781 - }
782 -
783 740 /**
784 741 * Remove an item from database and it's data.
785 742 *
786 - * @param int $item_id
743 + * @param $order_item_id
787 744 *
788 745 * @return bool
746 + * @throws Exception
789 747 */
790 - public function remove_item( $item_id ) {
748 + public function remove_item( $order_item_id ): bool {
791 749 global $wpdb;
792 750
793 - $item_id = absint( $item_id );
751 + $item_id = absint( $order_item_id );
752 + $order_item_id = absint( $order_item_id );
794 753
795 754 if ( ! $item_id ) {
796 755 return false;
797 756 }
@@ -802,11 +761,60 @@
802 761 * @since 3.0.0
803 762 */
804 763 do_action( 'learn-press/before-delete-order-item', $item_id, $this->get_id() );
805 764
765 + $filter = new OrderItemsFilter();
766 + $filter->order_item_id = $order_item_id;
767 + $filter->return_string_query = true;
768 + $filter->run_query_count = false;
769 + $order_items_db = LPOrderItemsDB::getInstance();
770 + $order_items_db->get_query_single_row( $filter );
771 + $query_single_row = $order_items_db->get_items( $filter );
772 + $itemObj = $order_items_db->wpdb->get_row( $query_single_row );
773 +
774 + if ( $itemObj && $itemObj->item_type === LP_COURSE_CPT ) {
775 + $course_id = $itemObj->item_id;
776 + $user_ids = $this->get_user_id();
777 + if ( is_array( $user_ids ) ) {
778 + foreach ( $user_ids as $user_id ) {
779 + // Delete course item on learnpress_user_items if exists
780 + $userCourseItem = UserItemModel::find_user_item(
781 + $user_id,
782 + $course_id,
783 + LP_COURSE_CPT,
784 + $this->get_id(),
785 + LP_ORDER_CPT,
786 + true
787 + );
788 + if ( $userCourseItem instanceof UserItemModel ) {
789 + $userCourseModel = new UserCourseModel( $userCourseItem );
790 + $userCourseModel->delete();
791 + }
792 + }
793 + } else {
794 + // Delete course item on learnpress_user_items if exists
795 + $userCourseItem = UserItemModel::find_user_item(
796 + $user_ids,
797 + $course_id,
798 + LP_COURSE_CPT,
799 + $this->get_id(),
800 + LP_ORDER_CPT,
801 + true
802 + );
803 + if ( $userCourseItem instanceof UserItemModel ) {
804 + $userCourseModel = new UserCourseModel( $userCourseItem );
805 + $userCourseModel->delete();
806 + }
807 + }
808 + }
809 +
806 810 $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}learnpress_order_items WHERE order_item_id = %d", $item_id ) );
807 811 $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}learnpress_order_itemmeta WHERE learnpress_order_item_id = %d", $item_id ) );
808 812
813 + // Clear cache
814 + $key = "order/{$this->get_id()}/{$this->get_status()}/items";
815 + LP_Cache::cache_load_first( 'clear', $key );
816 +
809 817 /**
810 818 * @since 3.0.0
811 819 */
812 820 do_action( 'learn-press/deleted-order-item', $item_id, $this->get_id() );
@@ -825,13 +833,17 @@
825 833 */
826 834 public function add_items( $items ) {
827 835 settype( $items, 'array' );
828 836 $item_ids = array();
829 - foreach ( $items as $item ) {
830 - $item_id = $this->add_item( $item );
831 - if ( $item_id ) {
832 - $item_ids[] = $item_id;
837 + try {
838 + foreach ( $items as $item ) {
839 + $item_id = $this->add_item( $item );
840 + if ( $item_id ) {
841 + $item_ids[] = $item_id;
842 + }
833 843 }
844 + } catch ( Throwable $e ) {
845 + error_log( $e->getMessage() );
834 846 }
835 847
836 848 return $item_ids;
837 849 }
@@ -864,8 +876,9 @@
864 876
865 877 /**
866 878 * Get user id in array.
867 879 * user_id = 0 -> User type Guest
880 + *
868 881 * @return int[]
869 882 * @editor tungnx
870 883 * @modify 4.1.4
871 884 * @version 1.0.1
@@ -871,9 +884,9 @@
871 884 * @version 1.0.1
872 885 */
873 886 public function get_users(): array {
874 887 $users = $this->get_user_id();
875 - if ( $users !== -1 ) {
888 + if ( $users !== - 1 ) {
876 889 settype( $users, 'array' );
877 890
878 891 $users = array_map(
879 892 function ( $user ) {
@@ -900,9 +913,9 @@
900 913 $found_selected = true;
901 914 } else {
902 915 $found_selected = false;
903 916 }
904 - echo sprintf( '<option value="%d" %s>%s</option>', esc_attr( $user->get_id() ), selected( $found_selected, true, false ), esc_html( $user->user_login ) );
917 + printf( '<option value="%d" %s>%s</option>', esc_attr( $user->get_id() ), selected( $found_selected, true, false ), esc_html( $user->user_login ) );
905 918 }
906 919 echo '</select>';
907 920 }
908 921
@@ -907,9 +920,8 @@
907 920 }
908 921
909 922
910 923 public function get_checkout_payment_url() {
911 -
912 924 }
913 925
914 926 public function get_formatted_order_subtotal() {
915 927 $currency_symbol = learn_press_get_currency_symbol( $this->get_currency() );
@@ -930,23 +942,26 @@
930 942 public function set_currency( $value ) {
931 943 $this->_set_data( 'currency', $value );
932 944 }
933 945
946 + /**
947 + * Get payment method title.
948 + *
949 + * @return array|mixed
950 + */
934 951 public function get_payment_method_title() {
935 - if ( $this->get_data( 'order_total' ) == 0 ) {
936 - $title = '';
937 - } else {
938 - $title = $this->get_data( 'payment_method_title' );
939 - }
940 -
941 - return $title;
952 + return esc_html( $this->get_data( 'payment_method_title', '' ) );
942 953 }
943 954
955 + /**
956 + * Get view order detail url.
957 + *
958 + * @return string
959 + */
944 960 public function get_view_order_url() {
945 961 global $wp_query;
946 962
947 - $view_order_url = learn_press_get_endpoint_url( 'view-order', $this->get_id(), learn_press_get_page_link( 'profile' ) );
948 - $user = learn_press_get_current_user();
963 + $userModel = UserModel::find( get_current_user_id(), true );
949 964 $view_order_endpoint = LP_Settings::instance()->get( 'profile_endpoints.order-details' );
950 965
951 966 if ( ! $view_order_endpoint ) {
952 967 $view_order_endpoint = 'order-details';
@@ -951,14 +966,21 @@
951 966 if ( ! $view_order_endpoint ) {
952 967 $view_order_endpoint = 'order-details';
953 968 }
954 969
970 + if ( ! $userModel ) {
971 + return '';
972 + }
973 +
974 + $user_slug = $userModel->user_nicename;
975 + $link_profile = learn_press_get_page_link( 'profile' );
976 +
955 977 $view_order_endpoint = urlencode( $view_order_endpoint );
956 978 if ( get_option( 'permalink_structure' ) ) {
957 - $view_order_url = learn_press_get_page_link( 'profile' ) . $user->get_data( 'user_login' ) . '/' . $view_order_endpoint . '/' . $this->get_id() . '/';
979 + $view_order_url = $link_profile . $user_slug . '/' . $view_order_endpoint . '/' . $this->get_id() . '/';
958 980 } else {
959 981 $args = array(
960 - 'user' => $user->get_data( 'user_login' ),
982 + 'user' => $user_slug,
961 983 );
962 984 $args['view'] = $view_order_endpoint;
963 985
964 986 if ( $view_order_endpoint ) {
@@ -965,9 +987,9 @@
965 987 $args['id'] = $this->get_id();
966 988 }
967 989 $view_order_url = add_query_arg(
968 990 $args,
969 - learn_press_get_page_link( 'profile' )
991 + $link_profile
970 992 );
971 993 }
972 994
973 995 return apply_filters( 'learn_press_view_order_url', $view_order_url, $this );
@@ -983,10 +1005,11 @@
983 1005 public function get_cancel_order_url( $force = false ) {
984 1006
985 1007 $url = false;
986 1008 if ( $this->has_status( 'pending' ) ) {
987 - $user = learn_press_get_current_user();
988 - $url = learn_press_user_profile_link( $user->get_id(), LP_Settings::instance()->get( 'profile_endpoints.profile-orders' ) );
1009 + $user_id = get_current_user_id();
1010 + $profile = LP_Profile::instance( $user_id );
1011 + $url = $profile->get_tab_link( LP_Settings::instance()->get( 'profile_endpoints.orders' ) );
989 1012 if ( ! $force ) {
990 1013 $url = esc_url_raw( add_query_arg( 'cancel-order', $this->get_id(), $url ) );
991 1014 } else {
992 1015 $url = esc_url_raw( add_query_arg( 'cancelled-order', $this->get_id(), $url ) );
@@ -998,13 +1021,53 @@
998 1021 return apply_filters( 'learn-press/order-cancel-url', $url, $this->get_id() );
999 1022 }
1000 1023
1001 1024 /**
1025 + * Get refund URL if order can be refunded by customer.
1026 + *
1027 + * @since 4.3.4
1028 + * @version 1.0.0
1029 + * @return bool|string
1030 + */
1031 + /*public function get_refund_order_url() {
1032 + $url = false;
1033 + $user = learn_press_get_current_user();
1034 + if ( ! $user instanceof LP_User || $user->get_id() <= 0 ) {
1035 + return apply_filters( 'learn-press/order-refund-url', $url, $this->get_id() );
1036 + }
1037 +
1038 + $eligibility = learn_press_get_order_refund_eligibility( $this, $user->get_id() );
1039 + if ( empty( $eligibility['eligible'] ) ) {
1040 + return apply_filters( 'learn-press/order-refund-url', $url, $this->get_id() );
1041 + }
1042 +
1043 + $url = learn_press_user_profile_link(
1044 + $user->get_id(),
1045 + LP_Settings::instance()->get( 'profile_endpoints.orders', 'orders' )
1046 + );
1047 + $url = esc_url_raw( $url );
1048 +
1049 + return apply_filters( 'learn-press/order-refund-url', $url, $this->get_id() );
1050 + }*/
1051 +
1052 + /**
1002 1053 * Get profile order's actions.
1003 1054 *
1004 1055 * @return array|mixed
1005 1056 */
1006 1057 public function get_profile_order_actions() {
1058 + $actions = [];
1059 +
1060 + $userModel = UserModel::find( get_current_user_id(), true );
1061 + if ( ! $userModel instanceof UserModel ) {
1062 + return $actions;
1063 + }
1064 +
1065 + $order_user_id = $this->get_users();
1066 + if ( ! in_array( $userModel->get_id(), $order_user_id ) ) {
1067 + return $actions;
1068 + }
1069 +
1007 1070 $actions = array(
1008 1071 'view' => array(
1009 1072 'url' => $this->get_view_order_url(),
1010 1073 'text' => __( 'View', 'learnpress' ),
@@ -1017,8 +1080,40 @@
1017 1080 'url' => $this->get_cancel_order_url(),
1018 1081 'text' => __( 'Cancel', 'learnpress' ),
1019 1082 );
1020 1083 }
1084 +
1085 + $refund_request_status = $this->get_refund_request();
1086 + $can_send_request_refund = $this->can_send_request_refund( $userModel );
1087 + if ( 'pending' === $refund_request_status ) {
1088 + $actions['refund-requested'] = array(
1089 + 'url' => '',
1090 + 'text' => __( 'Refund Requested', 'learnpress' ),
1091 + );
1092 + } elseif ( $can_send_request_refund === true ) {
1093 + $require_reason = 'yes' === learn_press_get_refund_setting( 'require_refund_reason', 'no' );
1094 +
1095 + $actions['refund'] = array(
1096 + 'url' => learn_press_user_profile_link(
1097 + $userModel->get_id(),
1098 + LP_Settings::instance()->get( 'profile_endpoints.orders', 'orders' )
1099 + ),
1100 + 'text' => __( 'Refund', 'learnpress' ),
1101 + 'class' => 'lp-refund-order-action',
1102 + 'data' => array(
1103 + 'order_id' => $this->get_id(),
1104 + 'require_reason' => $require_reason ? 'yes' : 'no',
1105 + 'reason_prompt' => __( 'Enter your refund reason', 'learnpress' ),
1106 + 'reason_placeholder' => __( 'Please describe why you want a refund.', 'learnpress' ),
1107 + 'reason_required' => __( 'Refund reason is required.', 'learnpress' ),
1108 + 'confirm_title' => __( 'Request a refund?', 'learnpress' ),
1109 + 'confirm_text' => __( 'This request will be sent and processed according to payment settings.', 'learnpress' ),
1110 + 'confirm_button' => __( 'Submit Request', 'learnpress' ),
1111 + 'cancel_button' => __( 'Cancel', 'learnpress' ),
1112 + ),
1113 + );
1114 + }
1115 +
1021 1116 $actions = apply_filters( 'learn-press/profile-order-actions', $actions, $this->get_id() );
1022 1117
1023 1118 return $actions;
1024 1119 }
@@ -1023,32 +1118,31 @@
1023 1118 return $actions;
1024 1119 }
1025 1120
1026 1121 public function add_note( $note = null ) {
1122 + $comment_author = '';
1123 + $comment_author_email = '';
1027 1124 if ( is_user_logged_in() ) {
1028 1125 $user = get_user_by( 'id', get_current_user_id() );
1029 1126 $comment_author = $user->display_name;
1030 1127 $comment_author_email = $user->user_email;
1031 - $comment_post_ID = $this->get_id();
1032 - $comment_author_url = '';
1033 - $comment_content = $note;
1034 - $comment_agent = 'LearnPress';
1035 - $comment_type = 'lp_order_note';
1036 - $comment_parent = 0;
1037 - $comment_approved = 1;
1128 + }
1038 1129
1039 - $commentdata = apply_filters(
1040 - 'learn_press_new_order_note_data',
1041 - compact( 'comment_post_ID', 'comment_author', 'comment_author_email', 'comment_author_url', 'comment_content', 'comment_agent', 'comment_type', 'comment_parent', 'comment_approved' ),
1042 - $this->get_id()
1043 - );
1130 + $comment_post_ID = $this->get_id();
1131 + $comment_author_url = '';
1132 + $comment_content = $note;
1133 + $comment_agent = 'LearnPress';
1134 + $comment_type = 'lp_order_note';
1135 + $comment_parent = 0;
1136 + $comment_approved = 1;
1044 1137
1045 - $comment_id = wp_insert_comment( $commentdata );
1138 + $commentdata = apply_filters(
1139 + 'learn_press_new_order_note_data',
1140 + compact( 'comment_post_ID', 'comment_author', 'comment_author_email', 'comment_author_url', 'comment_content', 'comment_agent', 'comment_type', 'comment_parent', 'comment_approved' ),
1141 + $this->get_id()
1142 + );
1046 1143
1047 - return $comment_id;
1048 - }
1049 -
1050 - return false;
1144 + return wp_insert_comment( $commentdata );
1051 1145 }
1052 1146
1053 1147 /**
1054 1148 * Get user_name
@@ -1053,8 +1147,9 @@
1053 1147 /**
1054 1148 * Get user_name
1055 1149 *
1056 1150 * @param int $user_id
1151 + *
1057 1152 * @return string
1058 1153 */
1059 1154 public function get_user_name( int $user_id = 0 ): string {
1060 1155 $user = learn_press_get_user( $user_id );
@@ -1078,38 +1173,8 @@
1078 1173 return is_array( $this->get_user_id() );
1079 1174 }
1080 1175
1081 1176 /**
1082 - * Print the list of all users has assigned to this order
1083 - * in case this order is for multi users
1084 - *
1085 - * @since 2.1.5
1086 - * @deprecated 4.1.6.9
1087 - */
1088 - /*public function print_users() {
1089 - $user_ids = get_post_meta( $this->get_id(), '_user_id' );
1090 - if ( $user_ids ) {
1091 - global $wpdb;
1092 - if ( is_array( $user_ids[0] ) ) {
1093 - $user_ids = reset( $user_ids );
1094 - }
1095 - $format_ids = array_fill( 0, sizeof( $user_ids ), '%d' );
1096 - $sql = $wpdb->prepare( "SELECT user_login, user_email FROM {$wpdb->users} WHERE ID IN(" . join( ',', $format_ids ) . ')', $user_ids );
1097 - $users = $wpdb->get_results( $sql );
1098 - $size = sizeof( $users );
1099 -
1100 - foreach ( $users as $i => $user ) {
1101 - printf( '<strong>%s</strong> ( %s )', $user->user_login, $user->user_email );
1102 - if ( $i < $size - 1 ) {
1103 - echo ', ';
1104 - }
1105 - }
1106 - } else {
1107 - _e( 'No user assigned', 'learnpress' );
1108 - }
1109 - }*/
1110 -
1111 - /**
1112 1177 * Get email of user has bought this order.
1113 1178 * In case this order is for multi users return an array with multi email addresses.
1114 1179 *
1115 1180 * @return array
@@ -1121,9 +1186,9 @@
1121 1186 $user_ids = $this->get_users();
1122 1187 if ( ! empty( $user_ids ) ) {
1123 1188 foreach ( $user_ids as $user_id ) {
1124 1189 $user = learn_press_get_user( $user_id );
1125 - if ( $user->is_exists() ) {
1190 + if ( $user && $user->is_exists() ) {
1126 1191 $data[ $user_id ] = $user->get_data(
1127 1192 array(
1128 1193 'id',
1129 1194 'email',
@@ -1145,16 +1210,22 @@
1145 1210 /**
1146 1211 * Get email's user by user_id
1147 1212 *
1148 1213 * @param int $user_id
1214 + *
1149 1215 * @return string
1150 1216 * @editor tungnx
1151 1217 * @modify 4.1.3
1218 + * @version 1.0.3
1152 1219 */
1153 1220 public function get_user_email( int $user_id = 0 ): string {
1154 - $user = learn_press_get_user( $user_id );
1155 - if ( $user && ! $user instanceof LP_User_Guest ) {
1156 - $email = $user->get_email();
1221 + if ( $user_id == 0 ) {
1222 + $user_id = (int) $this->get_user_id();
1223 + }
1224 +
1225 + $userModel = UserModel::find( $user_id, true );
1226 + if ( $userModel instanceof UserModel ) {
1227 + $email = $userModel->get_email();
1157 1228 } else {
1158 1229 $email = $this->get_checkout_email();
1159 1230 }
1160 1231
@@ -1160,16 +1231,8 @@
1160 1231
1161 1232 return $email;
1162 1233 }
1163 1234
1164 - public function get_child_orders( $force = false ) {
1165 - if ( $force ) {
1166 - wp_cache_delete( 'order-' . $this->get_id(), 'learn-press/child-orders' );
1167 - }
1168 -
1169 - return apply_filters( 'learn-press/child-orders', $this->_curd->get_child_orders( $this->get_id() ), $this->get_id() );
1170 - }
1171 -
1172 1235 /**
1173 1236 * Order title
1174 1237 *
1175 1238 * @param string $context
@@ -1206,9 +1269,9 @@
1206 1269 *
1207 1270 * @return array|int
1208 1271 */
1209 1272 public function get_user_id() {
1210 - return $this->get_data( 'user_id', -1 );
1273 + return $this->get_data( 'user_id', - 1 );
1211 1274 }
1212 1275
1213 1276 /**
1214 1277 * Get date modified of order.
@@ -1246,75 +1309,8 @@
1246 1309 return $this->get_data( 'created_via', '' );
1247 1310 }
1248 1311
1249 1312 /**
1250 - * Update order status if changed and trigger actions.
1251 - *
1252 - * @return bool
1253 - * @throws Exception
1254 - *
1255 - * @editor tungnx
1256 - * @modify 4.1.3 - comment - not use
1257 - */
1258 - // public function _save_status(): bool {
1259 - //
1260 - // // Nothing changed
1261 - // if ( ! $this->_status ) {
1262 - // return false;
1263 - // }
1264 - //
1265 - // $order_id = $this->get_id();
1266 - // $old_status = $this->_status['from'] ?? '';
1267 - // $new_status = $this->_status['to'] ?? '';
1268 - //
1269 - // // Only update if new status is difference with old status.
1270 - // if ( $new_status !== $old_status ) {
1271 - //
1272 - // if ( ! $this->get_user_id() ) {
1273 - // // $new_status = 'pending';
1274 - // }
1275 - //
1276 - // if ( doing_action( 'save_post' ) ) {
1277 - // // Update post's status using wpdb to preventing loop
1278 - // global $wpdb;
1279 - // $updated = $wpdb->update( $wpdb->posts, array( 'post_status' => 'lp-' . $new_status ), array( 'ID' => $order_id ), array( '%s' ) );
1280 - // } else {
1281 - // $updated = wp_update_post(
1282 - // array(
1283 - // 'post_status' => 'lp-' . $new_status,
1284 - // 'ID' => $order_id,
1285 - // )
1286 - // );
1287 - // }
1288 - //
1289 - // // Clear cache
1290 - // wp_cache_delete( $order_id, 'posts' );
1291 - //
1292 - // /**
1293 - // * Trigger actions after status was changed
1294 - // *
1295 - // * @deprecated
1296 - // */
1297 - // //do_action( 'learn_press_order_status_' . $new_status, $order_id );
1298 - // //do_action( 'learn_press_order_status_' . $old_status . '_to_' . $new_status, $order_id );
1299 - // //do_action( 'learn_press_order_status_changed', $order_id, $old_status, $new_status );
1300 - // // backward compatible
1301 - // do_action( 'learn_press_update_order_status', $new_status, $order_id );
1302 - //
1303 - // /**
1304 - // * @since 3.0.0
1305 - // */
1306 - // do_action( 'learn-press/order/status-' . $new_status, $order_id, $old_status );
1307 - // do_action( 'learn-press/order/status-' . $old_status . '-to-' . $new_status, $order_id );
1308 - // do_action( 'learn-press/order/status-changed', $order_id, $old_status, $new_status );
1309 - //
1310 - // return true;
1311 - // }
1312 - //
1313 - // return false;
1314 - // }
1315 -
1316 - /**
1317 1313 * Save order data.
1318 1314 *
1319 1315 * @return mixed
1320 1316 *
@@ -1321,26 +1317,44 @@
1321 1317 * @throws Exception
1322 1318 */
1323 1319 public function save() {
1324 1320 $old_status = '';
1321 + $is_created = ! $this->get_id();
1325 1322
1326 1323 if ( $this->get_id() ) {
1327 - $old_status = str_replace( 'lp-', '', get_post_status( $this->get_id() ) );
1328 - $return = $this->_curd->update( $this );
1324 + $old_status_post = get_post_status( $this->get_id() );
1325 + if ( ! in_array( $old_status_post, array( 'trash', 'auto-draft' ) ) ) {
1326 + $old_status = str_replace( 'lp-', '', $old_status_post );
1327 + } else {
1328 + $old_status = $old_status_post;
1329 + }
1330 +
1331 + $return = $this->_curd->update( $this );
1329 1332 } else {
1330 - $return = $this->_curd->create( $this );
1333 + $return = $this->_curd->create( $this );
1334 + $this->_id = $return;
1331 1335 }
1332 1336
1333 - $new_status = str_replace( 'lp-', '', get_post_status( $this->get_id() ) );
1334 - $order_id = $this->get_id();
1337 + $new_status_post = get_post_status( $this->get_id() );
1338 + if ( ! in_array( $new_status_post, array( 'trash', 'auto-draft' ) ) ) {
1339 + $new_status = str_replace( 'lp-', '', $new_status_post );
1340 + } else {
1341 + $new_status = $new_status_post;
1342 + }
1335 1343
1344 + $order_id = $this->get_id();
1345 +
1346 + if ( $is_created && $order_id ) {
1347 + do_action( 'learn-press/order/created', $order_id, $this );
1348 + }
1349 +
1336 1350 if ( $new_status !== $old_status ) {
1351 + do_action( 'learn-press/order/status-changed', $order_id, $old_status, $new_status );
1337 1352 do_action( 'learn-press/order/status-' . $new_status, $order_id, $old_status );
1338 1353 do_action( 'learn-press/order/status-' . $old_status . '-to-' . $new_status, $order_id );
1339 - do_action( 'learn-press/order/status-changed', $order_id, $old_status, $new_status );
1340 1354 }
1341 1355
1342 - //$this->_save_status();
1356 + // $this->_save_status();
1343 1357
1344 1358 return $return;
1345 1359 }
1346 1360
@@ -1358,9 +1372,8 @@
1358 1372 return learn_press_get_order( $order );
1359 1373 }
1360 1374
1361 1375 /** Getter/Setter **/
1362 -
1363 1376 public function set_customer_message( $message ) {
1364 1377 $this->_set_data( 'customer_message', $message );
1365 1378 }
1366 1379
@@ -1402,9 +1415,9 @@
1402 1415 *
1403 1416 * @return bool
1404 1417 */
1405 1418 public function is_completed(): bool {
1406 - return $this->get_status() === 'completed';
1419 + return $this->get_status() === LP_ORDER_COMPLETED;
1407 1420 }
1408 1421
1409 1422 /**
1410 1423 * Check Order is created manual.
@@ -1414,9 +1427,9 @@
1414 1427 * @author tungnx
1415 1428 * @version 1.0.0
1416 1429 */
1417 1430 public function is_manual(): bool {
1418 - return $this->get_created_via() === 'manual';
1431 + return $this->get_created_via() === LP_ORDER_CREATED_VIA_MANUAL;
1419 1432 }
1420 1433
1421 1434 /**
1422 1435 * Check can delete lp_user_items old
@@ -1471,16 +1484,364 @@
1471 1484 *
1472 1485 * @return array
1473 1486 */
1474 1487 public static function get_order_statuses(): array {
1475 - $order_statuses = [
1488 + $order_statuses = array(
1476 1489 LP_ORDER_COMPLETED_DB => LP_ORDER_COMPLETED,
1477 1490 LP_ORDER_PROCESSING_DB => LP_ORDER_PROCESSING,
1478 1491 LP_ORDER_PENDING_DB => LP_ORDER_PENDING,
1479 1492 LP_ORDER_CANCELLED_DB => LP_ORDER_CANCELLED,
1480 1493 LP_ORDER_FAILED_DB => LP_ORDER_FAILED,
1481 - ];
1494 + LP_ORDER_REFUNDED_DB => LP_ORDER_REFUNDED,
1495 + );
1482 1496
1483 1497 return apply_filters( 'lp/order/statuses', $order_statuses );
1498 + }
1499 +
1500 + /**
1501 + * Query list orders.
1502 + *
1503 + * @param PostFilter $post_filter
1504 + * @param array $param [ 'author' => int, 'post_status' => string|array, 'refund_request_status' => string, 's' => string, 'm' => string, 'posts_per_page' => int, 'paged' => int, 'orderby' => string, 'order' => string ]
1505 + *
1506 + * @return void
1507 + * @throws Exception
1508 + * @since 4.3.2.8
1509 + * @version 1.0.2
1510 + */
1511 + public static function handle_params_query_list_orders( PostFilter &$post_filter, array $param = array() ) {
1512 + $post_db = PostDB::getInstance();
1513 + $user_of_order = absint( $param['author'] ?? 0 );
1514 + $status = $param['post_status'] ?? '';
1515 + $key = $param['s'] ?? '';
1516 + $month = $param['m'] ?? '';
1517 + $limit = $param['posts_per_page'] ?? 20;
1518 + $paged = $param['paged'] ?? 1;
1519 + $refund_request_status = sanitize_key( (string) ( $param['refund_request_status'] ?? '' ) );
1520 + $order_by = $param['orderby'] ?? 'menu_order';
1521 + if ( empty( $order_by ) ) {
1522 + $order_by = 'ID';
1523 + } else {
1524 + switch ( $order_by ) {
1525 + case 'date':
1526 + $order_by = 'post_date';
1527 + break;
1528 + default:
1529 + $allowed_key = array(
1530 + 'ID',
1531 + 'post_title',
1532 + 'post_author',
1533 + 'post_status',
1534 + 'order_total',
1535 + 'menu_order'
1536 + );
1537 + if ( ! in_array( $order_by, $allowed_key ) ) {
1538 + $order_by = 'ID';
1539 + }
1540 + break;
1541 + }
1542 + }
1543 +
1544 + $order = $param['order'] ?? 'DESC';
1545 + // End convert params
1546 +
1547 + if ( $order_by === 'order_total' ) {
1548 + $post_filter->join[] = "INNER JOIN {$post_db->tb_postmeta} pm2 ON p.ID = pm2.post_id AND pm2.meta_key = '_order_total'";
1549 + $post_filter->order_by = 'CAST(pm2.meta_value AS DECIMAL(10,2))';
1550 + } else {
1551 + $post_filter->order_by = $order_by;
1552 + }
1553 +
1554 + if ( ! empty( $key ) ) {
1555 + $pattern = '/^#\d+$/';
1556 + $is_order_id_sure = false;
1557 + if ( preg_match( $pattern, $key ) ) {
1558 + $is_order_id_sure = true;
1559 + $key = str_replace( '#', '', $key );
1560 + }
1561 +
1562 + $pattern2 = '#^0+.*\d+$#';
1563 + if ( preg_match( $pattern2, $key ) ) {
1564 + $key = (int) $key;
1565 + }
1566 +
1567 + $key = trim( $key );
1568 +
1569 + if ( $is_order_id_sure ) {
1570 + $post_filter->where[] = $post_db->wpdb->prepare( 'AND p.ID = %d', $key );
1571 + } else {
1572 + $post_filter->join[] = "INNER JOIN {$post_db->tb_lp_order_items} lpori ON p.ID = lpori.order_id";
1573 + $post_filter->where[] = $post_db->wpdb->prepare(
1574 + 'AND (p.ID = %d OR lpori.order_item_name like %s)',
1575 + $key,
1576 + '%' . $key . '%'
1577 + );
1578 + }
1579 + }
1580 +
1581 + if ( ! empty( $month ) ) {
1582 + $year = substr( $month, 0, 4 );
1583 + $post_filter->where[] = $post_db->wpdb->prepare(
1584 + 'AND YEAR(p.post_date) = %s',
1585 + $year
1586 + );
1587 + if ( strlen( $month ) > 5 ) {
1588 + $mon = substr( $month, 4, 2 );
1589 + $post_filter->where[] = $post_db->wpdb->prepare(
1590 + 'AND MONTH(p.post_date) = %s',
1591 + $mon
1592 + );
1593 + }
1594 + }
1595 +
1596 + $post_filter->order = $order;
1597 + $post_filter->limit = $limit;
1598 + $post_filter->page = $paged;
1599 +
1600 + if ( ! empty( $user_of_order ) ) {
1601 + $user_id = absint( $user_of_order );
1602 + $post_filter->join[] = "INNER JOIN {$post_db->tb_postmeta} pm1 ON p.ID = pm1.post_id AND pm1.meta_key = '_user_id'";
1603 + $post_filter->where[] = "AND ( pm1.meta_value like '%\"$user_id\"%' OR pm1.meta_value = $user_id )";
1604 + }
1605 +
1606 + if ( ! empty( $refund_request_status ) ) {
1607 + $post_filter->where[] = $post_db->wpdb->prepare(
1608 + "AND EXISTS (
1609 + SELECT 1 FROM {$post_db->tb_postmeta} refund_request_pm
1610 + WHERE refund_request_pm.post_id = p.ID
1611 + AND refund_request_pm.meta_key = %s
1612 + AND refund_request_pm.meta_value = %s
1613 + )",
1614 + LP_Order::META_KEY_REFUND_REQUEST,
1615 + $refund_request_status
1616 + );
1617 + }
1618 + if ( ! empty( $status ) && $status !== 'all' ) {
1619 + $post_filter->post_status = (array) $status;
1620 + } else {
1621 + $post_filter->where[] = $post_db->wpdb->prepare( 'AND p.post_status != %s', LP_ORDER_TRASH );
1622 + }
1623 + }
1624 +
1625 + /**
1626 + * Get transaction id from order meta
1627 + *
1628 + * @since 4.4.0
1629 + * @version 1.0.0
1630 + * @return string
1631 + */
1632 + public function get_transaction_id(): string {
1633 + return (string) $this->get_meta( self::META_KEY_TRANSACTION_ID );
1634 + }
1635 +
1636 + /**
1637 + * Get refund request status from order meta.
1638 + *
1639 + * @since 4.4.0
1640 + * @version 1.0.0
1641 + * @return string pending|approved|auto-approved|denied|''
1642 + */
1643 + public function get_refund_request(): string {
1644 + return (string) get_post_meta( $this->get_id(), self::META_KEY_REFUND_REQUEST, true );
1645 + }
1646 +
1647 + /**
1648 + * Check the order can be refunded.
1649 + *
1650 + * Validates multiple conditions required for refund eligibility:
1651 + * - Refund requests must be enabled in settings
1652 + * - Order must not already be refunded
1653 + * - Order must be in 'completed' status
1654 + * - Refund amount must not exceed order total (including previously refunded amount)
1655 + * - Payment gateway must have refund method implemented (override from abstract)
1656 + *
1657 + * @param float $amount The amount to refund..
1658 + *
1659 + * @return bool|WP_Error Returns true if refund is allowed, WP_Error with specific error code otherwise.
1660 + *
1661 + * Error codes:
1662 + * - 'refund_disabled': Refund feature is disabled in settings
1663 + * - 'order_refunded': Order already has refunded status
1664 + * - 'order_not_completed': Order is not in completed status
1665 + * - 'order_refund_amount_exceeds_total': Generic error (amount exceeds total or gateway unavailable)
1666 + * - 'order_refund_gateway_unavailable': Gateway does not support refund
1667 + *
1668 + * @since 4.4.0
1669 + * @version 1.0.0
1670 + */
1671 + public function can_refund( $amount = 0 ) {
1672 + try {
1673 + $error_code = '';
1674 + if ( 'yes' !== learn_press_get_refund_setting( 'enable_refund_requests', 'no' ) ) {
1675 + $error_code = 'refund_disabled';
1676 + throw new Exception( __( 'Refund requests are currently disabled.', 'learnpress' ) );
1677 + }
1678 +
1679 + if ( $this->has_status( LP_ORDER_REFUNDED ) ) {
1680 + $error_code = 'order_refunded';
1681 + throw new Exception( __( 'Order has refunded', 'learnpress' ) );
1682 + }
1683 +
1684 + if ( ! $this->has_status( LP_ORDER_COMPLETED ) ) {
1685 + $error_code = 'order_not_completed';
1686 + throw new Exception( __( 'Order is not completed', 'learnpress' ) );
1687 + }
1688 +
1689 + $refunded_amount = $this->get_meta( LP_Order::META_KEY_REFUNDED_AMOUNT );
1690 + if ( $refunded_amount > 0 ) {
1691 + $amount = $refunded_amount + $amount;
1692 + }
1693 +
1694 + if ( $amount > $this->get_total() ) {
1695 + $error_code = 'order_refund_amount_exceeds_total';
1696 + throw new Exception( __( 'Refund amount is greater than order total.', 'learnpress' ) );
1697 + }
1698 +
1699 + $payment_method = strtolower( $this->get_data( 'payment_method', '' ) );
1700 + $gateway = LP_Gateways::instance()->get_gateway( $payment_method );
1701 +
1702 + $has_refund_override = false;
1703 + if ( $gateway && method_exists( $gateway, 'refund' ) ) {
1704 + // Check gateway has refund method override
1705 + $reflection = new ReflectionMethod( $gateway, 'refund' );
1706 + $has_refund_override = $reflection->class === get_class( $gateway );
1707 + }
1708 +
1709 + if ( ! $gateway || ! $has_refund_override ) {
1710 + $error_code = 'order_refund_gateway_unavailable';
1711 + throw new Exception( __( 'Refund gateway is unavailable.', 'learnpress' ) );
1712 + }
1713 + } catch ( Throwable $e ) {
1714 + if ( empty( $error_code ) ) {
1715 + $error_code = 'order_can_not_refund';
1716 + }
1717 +
1718 + return new WP_Error( $error_code, $e->getMessage() );
1719 + }
1720 +
1721 + return true;
1722 + }
1723 +
1724 + /**
1725 + * Check user can send request refund
1726 + *
1727 + * Apply for cache 1 user 1 order, only user of this order can send request refund
1728 + *
1729 + * @since 4.4.0
1730 + * @version 1.0.0
1731 + * @return bool|WP_Error
1732 + */
1733 + public function can_send_request_refund( UserModel $userModel ) {
1734 + try {
1735 + $error_code = '';
1736 +
1737 + // Check user of this order
1738 + $order_user = (int) $this->get_user_id();
1739 + if ( $order_user !== $userModel->get_id() ) {
1740 + $error_code = 'request_refund_user_invalid';
1741 + throw new Exception(
1742 + __( 'You do not have permission to refund this order.', 'learnpress' )
1743 + );
1744 + }
1745 +
1746 + // Check refund requests are enabled
1747 + if ( 'yes' !== learn_press_get_refund_setting( 'enable_refund_requests', 'no' ) ) {
1748 + $error_code = 'refund_disabled';
1749 + throw new Exception( __( 'Refund requests are currently disabled.', 'learnpress' ) );
1750 + }
1751 +
1752 + // Check order total > 0
1753 + if ( $this->get_total() <= 0 ) {
1754 + $error_code = 'order_total_invalid';
1755 + throw new Exception( __( 'Order total is not valid.', 'learnpress' ) );
1756 + }
1757 +
1758 + // Check request refund status exists
1759 + $request_status = $this->get_refund_request();
1760 + if ( 'rejected' === $request_status ) {
1761 + if ( 'yes' !== learn_press_get_refund_setting( 'allow_resend_after_rejected', 'no' ) ) {
1762 + $error_code = 'request_refund_is_rejected';
1763 + throw new Exception( __( 'Request refund is rejected!.', 'learnpress' ) );
1764 + }
1765 + } elseif ( ! empty( $request_status ) ) {
1766 + $error_code = 'request_refund_sent';
1767 + throw new Exception(
1768 + sprintf(
1769 + __( 'Request refund has %s', 'learnpress' ),
1770 + $request_status
1771 + )
1772 + );
1773 + }
1774 +
1775 + // Check time limit for refund request
1776 + $refund_time_limit = learn_press_get_refund_setting( 'refund_time_limit', 30 );
1777 + if ( $refund_time_limit > 0 ) {
1778 + $order_time = absint( $this->get_order_date( 'timestamp' ) );
1779 + if ( $order_time > 0 ) {
1780 + $now = current_time( 'timestamp' );
1781 + $allowed_time_limit = strtotime( '+ ' . $refund_time_limit . ' day', $order_time );
1782 + if ( $allowed_time_limit < $now ) {
1783 + $error_code = 'request_refund_time_expired';
1784 + throw new Exception( __( 'Time for refund request expired', 'learnpress' ) );
1785 + }
1786 + }
1787 + }
1788 +
1789 + // Check gateway support refund
1790 + $payment_method = strtolower( $this->get_data( 'payment_method', '' ) );
1791 + $gateway = LP_Gateways::instance()->get_gateway( $payment_method );
1792 +
1793 + $gateway_has_refund_override = false;
1794 + if ( $gateway && method_exists( $gateway, 'refund' ) ) {
1795 + // Check gateway has refund method override
1796 + $reflection = new ReflectionMethod( $gateway, 'refund' );
1797 + $gateway_has_refund_override = $reflection->class === get_class( $gateway );
1798 + }
1799 +
1800 + if ( ! $gateway || ! $gateway_has_refund_override ) {
1801 + $error_code = 'order_refund_gateway_unavailable';
1802 + throw new Exception( __( 'Refund gateway is unavailable.', 'learnpress' ) );
1803 + }
1804 + } catch ( Throwable $e ) {
1805 + if ( empty( $error_code ) ) {
1806 + $error_code = 'user_can_not_send_request_refund';
1807 + }
1808 +
1809 + return new WP_Error( $error_code, $e->getMessage() );
1810 + }
1811 +
1812 + return true;
1813 + }
1814 +
1815 + /**
1816 + * Refund order with amount
1817 + *
1818 + * @return void
1819 + * @throws Exception
1820 + */
1821 + public function refund( $amount ) {
1822 + $can_refund = $this->can_refund( $amount );
1823 + if ( is_wp_error( $can_refund ) ) {
1824 + throw new Exception( $can_refund->get_error_message() );
1825 + }
1826 +
1827 + $payment_method = strtolower( $this->get_data( 'payment_method', '' ) );
1828 + $gateway = LP_Gateways::instance()->get_gateway( $payment_method );
1829 + /** @var LP_Gateway_Abstract|LP_Gateway_Paypal $gateway */
1830 + $gateway->refund( $this, $amount );
1831 +
1832 + $user_id = get_current_user_id();
1833 + update_post_meta( $this->get_id(), self::META_KEY_REFUNDED_AMOUNT, (float) $amount );
1834 + update_post_meta( $this->get_id(), self::META_KEY_REFUNDED_BY, $user_id );
1835 + update_post_meta( $this->get_id(), self::META_KEY_REFUNDED_AT, gmdate( LP_Datetime::$format, time() ) );
1836 + $this->update_status( LP_ORDER_REFUNDED );
1837 +
1838 + $this->add_note(
1839 + sprintf(
1840 + __( 'Order %1$s has been refunded %2$s.', 'learnpress' ),
1841 + $this->get_order_number(),
1842 + learn_press_format_price( $amount, learn_press_get_currency_symbol( $this->get_currency() ) )
1843 + )
1844 + );
1484 1845 }
1485 1846 }
1486 1847 }