<?php
/**
 * Class LP_Order
 *
 * @author  ThimPress
 * @package LearnPress/Classes
 * @version 4.0.1
 */

use LearnPress\Databases\Order\LPOrderItemsDB;
use LearnPress\Databases\PostDB;
use LearnPress\Filters\Order\OrderItemsFilter;
use LearnPress\Filters\PostFilter;
use LearnPress\Models\UserItems\UserCourseModel;
use LearnPress\Models\UserItems\UserItemModel;
use LearnPress\Models\UserModel;

defined( 'ABSPATH' ) || exit();

if ( ! class_exists( 'LP_Order' ) ) {

	/**
	 * Class LP_Order
	 */
	class LP_Order extends LP_Abstract_Post_Data {

		/**
		 * @var string
		 */
		protected $_post_type = LP_ORDER_CPT;

		/**
		 * @var array
		 */
		protected $_data = array(
			'user_id'          => '',
			'order_date'       => '',
			'date_modified'    => '',
			'customer_message' => '',
			'customer_note'    => '',
			'status'           => '',
			'order_key'        => '',
			'total'            => 0,
			'subtotal'         => 0,
			'created_via'      => '',
			'checkout_email'   => '',
		);

		/**
		 * @var array
		 */
		protected $_meta_keys = array(
			'_user_id'              => 'user',
			'_order_currency'       => 'currency',
			'_order_subtotal'       => 'subtotal',
			'_order_total'          => 'total',
			'_payment_method'       => 'payment_method',
			'_payment_method_title' => 'payment_method_title',
			'_order_version'        => 'order_version',
			'_edit_last'            => '',
			'_edit_lock'            => '',
			'_prices_include_tax'   => '',
			'_order_key'            => '',
			'_user_ip'              => '',
			'_checkout_email'       => '',
		);

		const META_KEY_TRANSACTION_ID        = '_transaction_id';
		const META_KEY_REFUND_REQUEST        = '_lp_refund_request';
		const META_KEY_REFUND_REQUEST_REASON = '_lp_refund_request_reason';
		const META_KEY_REFUNDED_BY           = '_lp_refunded_by';
		const META_KEY_REFUNDED_AT           = '_lp_refunded_at';
		const META_KEY_REFUNDED_AMOUNT       = '_lp_refunded_amount';

		/**
		 * Store order status in transactions.
		 *
		 * @var array
		 */
		protected $_status = array();

		/**
		 * LP_Order constructor.
		 *
		 * @param bool $order_id
		 *
		 * @throws Exception
		 */
		public function __construct( $order_id = false ) {
			$this->_curd = new LP_Order_CURD();
			if ( is_numeric( $order_id ) && $order_id > 0 ) {
				$this->set_id( $order_id );
			} elseif ( $order_id instanceof self ) {
				$this->set_id( absint( $order_id->get_id() ) );
			} elseif ( ! empty( $order_id->ID ) ) {
				$this->set_id( absint( $order_id->ID ) );
			}

			if ( $this->get_id() > 0 ) {
				$this->load();
			}
		}

		/**
		 * Load the order data.
		 * Check if the id is not zero but it's post type does not exists.
		 *
		 * @throws Exception
		 */
		public function load() {
			$this->_curd->load( $this );
		}

		/**
		 * Set order date.
		 *
		 * @param int|string $date
		 */
		public function set_order_date( $date ): LP_Order {
			$this->set_data_date( 'order_date', $date );

			return $this;
		}

		/**
		 * Get date of this order.
		 *
		 * @param string $context
		 *
		 * @return string|LP_Datetime
		 */
		public function get_order_date( $context = '' ) {
			$date = $this->get_data( 'order_date' );

			if ( 'edit' !== $context ) {

				if ( $date instanceof LP_Datetime ) {
					$strtime = strtotime( $date->toSql() );

					switch ( $context ) {
						case 'd':
							$date = date_i18n( 'Y-m-d', $strtime );
							break;
						case 'h':
							$date = date_i18n( 'H', $strtime );
							break;
						case 'm':
							$date = date_i18n( 'i', $strtime );
							break;
						case 'timestamp':
							$date = $strtime;
							break;
						default:
							$post      = get_post( $this->get_id() );
							$m_time    = $post->post_date;
							$time      = get_post_time( 'G', true, $post );
							$time_diff = time() - $time;

							if ( $time_diff > 0 && $time_diff < DAY_IN_SECONDS ) {
								$date = sprintf( __( '%s ago', 'learnpress' ), human_time_diff( $time ) );
							} else {
								$date = mysql2date( get_option( 'date_format' ), $m_time );
							}
					}
				}
			} elseif ( ! $date instanceof LP_Datetime ) {
				$date = new LP_Datetime( $date );
			}

			return $date;
		}

		/**
		 * Set order key
		 *
		 * @param string $order_key
		 */
		public function set_order_key( $order_key ) {
			$this->_set_data( 'order_key', $order_key );
		}

		/**
		 * Get order key.
		 *
		 * @return array|mixed
		 */
		public function get_order_key() {
			return $this->get_data( 'order_key', '' );
		}

		/**
		 * Get confirm received text
		 *
		 * @return string
		 * @since 3.0.0
		 */
		public function get_confirm_order_received_text() {
			return apply_filters( 'learn-press/confirm-order-received-text', __( 'Thank you. Your order has been received.', 'learnpress' ), $this->get_id() );
		}

		/**
		 * Get thank you message after the order is placed.
		 *
		 * @return mixed
		 * @since 3.0.0
		 */
		public function get_thankyou_message() {
			return apply_filters( 'learn-press/confirm-order-received-text', __( 'Thank you. Your order has been received.', 'learnpress' ), $this->get_id() );
		}

		/**
		 * Checks to see if current order has status as passed.
		 *
		 * @param string|array $status String or an array of statuses
		 *
		 * @return mixed
		 */
		public function has_status( $status ) {
			settype( $status, 'array' );
			$has = in_array( $this->get_status(), $status );

			return apply_filters( 'learn-press/has-order-status', $has, $status, $this->get_id() );
		}

		/**
		 * Updates order to new status if needed
		 *
		 * @param mixed $new_status
		 * @param bool  $manual Is this a manual order status change?.
		 *
		 * @return bool
		 */
		public function update_status( $new_status = 'pending', $manual = false ): bool {
			$result = false;

			try {
				$this->set_status( $new_status );
				$result = $this->save();
			} catch ( Throwable $e ) {
				error_log( $e->getMessage() );
			}

			return $result;
		}

		/**
		 * Format order number id
		 *
		 * @return string
		 */
		public function get_order_number(): string {
			return learn_press_transaction_order_number( $this->get_id() );
		}

		/**
		 * Get status of the order
		 *
		 * @return mixed
		 */
		public function get_order_status() {
			return $this->get_status();
		}

		public function get_user_ip_address() {
			return $this->get_data( 'user_ip_address' );
		}

		/**
		 * Get current status of order
		 *
		 * @return mixed
		 */
		public function get_status() {
			return $this->get_data( 'status', '' );
		}

		/**
		 * Get label of lp order status
		 *
		 * @param string $status
		 *
		 * @return string
		 * @since 4.2.0
		 * @version 1.0.0
		 */
		public static function get_status_label( string $status ): string {
			switch ( $status ) {
				case LP_ORDER_COMPLETED:
					$status = __( 'Completed', 'learnpress' );
					break;
				case LP_ORDER_PENDING:
					$status = __( 'Pending', 'learnpress' );
					break;
				case LP_ORDER_PROCESSING:
					$status = __( 'Processing', 'learnpress' );
					break;
				case LP_ORDER_CANCELLED:
					$status = __( 'Cancelled', 'learnpress' );
					break;
				case LP_ORDER_FAILED:
					$status = __( 'Failed', 'learnpress' );
					break;
				case LP_ORDER_TRASH:
					$status = __( 'Trash', 'learnpress' );
					break;
				case 'on-hold':
					$status = __( 'On hold', 'learnpress' );
					break;
				case LP_ORDER_REFUNDED:
					$status = __( 'Refunded', 'learnpress' );
					break;
				default:
					$status = '';
					break;
			}

			if ( ! is_string( $status ) ) {
				$status = '';
			}

			return $status;
		}

		/**
		 * Get icons of lp order status
		 *
		 * @return array
		 * @since 4.2.0
		 * @version 1.0.0
		 */
		public static function get_icons_status(): array {
			$icons = array(
				LP_ORDER_COMPLETED  => "<i class='dashicons dashicons-yes-alt'></i>",
				LP_ORDER_PENDING    => "<i class='dashicons dashicons-flag'></i>",
				LP_ORDER_PROCESSING => "<i class='dashicons dashicons-clock'></i>",
				LP_ORDER_CANCELLED  => "<i class='dashicons dashicons-dismiss'></i>",
				LP_ORDER_FAILED     => "<i class='dashicons dashicons-warning'></i>",
				LP_ORDER_REFUNDED   => "<i class='dashicons dashicons-undo'></i>",
			);

			return apply_filters( 'lp/order/status/icons', $icons );
		}

		/**
		 * Set order status.
		 * $new_status shouldn't have prefix 'lp-'
		 *
		 * @param string $new_status
		 * @param string $note - Optional. Note for changing status.
		 *
		 * @sicne 3.0.0
		 * @version 1.0.1
		 */
		public function set_status( string $new_status = '', string $note = '' ) {
			// Ensure status not has prefix 'lp-'.
			$new_status     = str_replace( 'lp-', '', $new_status );
			$valid_statuses = array_values( self::get_order_statuses() );
			if ( ! in_array( $new_status, $valid_statuses ) && 'trash' !== $new_status ) {
				$new_status = LP_ORDER_PENDING;
			}

			$this->_set_data( 'status', $new_status );
		}

		public function get_order_status_html() {
			$order_status = self::get_status_label( $this->get_status() );
			$status       = ucfirst( $order_status );
			$class        = 'order-status order-status-' . sanitize_title( $status );
			$html         = sprintf( '<span class="%s">%s</span>', apply_filters( 'learn_press_order_status_class', $class, $status, $this ), $status );

			return apply_filters( 'learn_press_order_status_html', $html, $this );
		}

		/**
		 * Mark order as complete
		 *
		 * @param string $transaction_id
		 *
		 * @return bool
		 */
		public function payment_complete( $transaction_id = '' ): bool {
			do_action( 'learn-press/payment-pre-complete', $this->get_id() );

			$valid_order_statuses = apply_filters(
				'learn-press/valid-order-statuses-for-payment-complete',
				array(
					'pending',
					'processing',
				),
				$this
			);

			if ( $this->get_id() && $this->has_status( $valid_order_statuses ) ) {

				$this->update_status( 'completed' );

				if ( ! empty( $transaction_id ) ) {
					add_post_meta( $this->get_id(), '_transaction_id', $transaction_id, true );
				}

				do_action( 'learn-press/payment-complete', $this->get_id() );
			} else {
				do_action( 'learn-press/payment-complete-order-status-' . $this->get_status(), $this->get_id() );
			}

			return true;
		}

		/**
		 * Get checkout order successful url.
		 *
		 * @return string
		 */
		public function get_checkout_order_received_url() {
			$received_url = learn_press_get_endpoint_url( 'lp-order-received', $this->get_id(), learn_press_get_page_link( 'checkout' ) );

			$received_url = add_query_arg( 'key', $this->get_order_key(), $received_url );

			$received_url = apply_filters( 'learn_press_get_checkout_order_received_url', $received_url, $this );

			/**
			 * @since 3.0.0
			 */
			return apply_filters( 'learn-press/checkout-order-received-url', $received_url, $this );
		}

		/*********************************/

		/**
		 * Get customer name of the order
		 */
		public function get_customer_name() {
			$customer_name = '';

			if ( 'auto-draft' !== get_post_status( $this->get_id() ) ) {
				$user_ids = $this->get_users();
				if ( ! empty( $user_ids ) ) {
					$customer_name = array();
					foreach ( $user_ids as $uid ) {
						$customer = learn_press_get_user( $uid );
						if ( $customer && $customer->is_exists() ) {
							if ( $customer->get_data( 'display_name' ) ) {
								$customer_name[] = $customer->get_data( 'display_name' );
							} elseif ( $customer->get_data( 'user_nicename' ) ) {
								$customer_name[] = $customer->get_data( 'user_nicename' );
							} elseif ( $customer->get_data( 'user_login' ) ) {
								$customer_name[] = $customer->get_data( 'user_login' );
							}
						} else {
							$customer_name[] = $this->get_guest_customer_name();
						}
					}

					$customer_name = join( ', ', $customer_name );
				}
			}

			if ( ! $customer_name ) {
				$customer_name = $this->get_guest_customer_name();
			}

			return $customer_name;
		}

		public function get_guest_customer_name() {
			$checkout_email = $this->get_checkout_email();
			if ( $checkout_email ) {
				$customer_name = sprintf( __( '%s (Guest)', 'learnpress' ), $checkout_email );
			} else {
				$customer_name = sprintf( __( '(Guest)', 'learnpress' ), $checkout_email );
			}

			return apply_filters( 'learn-press/order/guest-customer-name', $customer_name );
		}

		/*
		public function customer_exists() {
			return false !== get_userdata( $this->get_data( 'user_id' ) );
		}*/

		/**
		 * Get items of the order only to show.
		 *
		 * @return mixed
		 */
		public function get_items() {
			$items = $this->_curd->read_items( $this );

			return apply_filters( 'learn-press/order-items', $items );
		}

		/**
		 * Get all items of the order.
		 * Only use for add/remove items to learn (learn_press_user_items) table.
		 * Where call this function, must be careful, must set_time_limit( 0 );.
		 *
		 * @return array|null
		 * @version 1.0.1
		 * @since 4.2.7.2
		 */
		public function get_all_items() {
			global $wpdb;
			$lp_order_items    = LP_Database::getInstance();
			$table_order_items = $lp_order_items->tb_lp_order_items;

			$query = $wpdb->prepare(
				"SELECT order_item_id, order_item_name, item_id, item_type
				From $table_order_items
				WHERE order_id = %d",
				$this->get_id()
			);

			return $wpdb->get_results( $query, ARRAY_A );
		}

		/**
		 * Get items of the order by filter
		 *
		 * @param array $filter
		 *
		 * @return mixed
		 * @author tungnx
		 */
		public function get_items_filter( $filter = array() ) {
			$items = $this->_curd->read_items_filter( $this, $filter );

			return apply_filters( 'learn-press/order-items', $items );
		}

		public function is_child() {
			return $this->get_parent_id();
		}

		public function get_parent() {
			return $this->get_parent_id() ? learn_press_get_order( $this->get_parent_id() ) : false;
		}

		/**
		 * Get list of course ids from order.
		 *
		 * @return array|bool
		 * @editor tungnx
		 */
		public function get_item_ids() {
			$items = $this->get_all_items();

			if ( $items ) {
				$course_ids = array();
				foreach ( $items as $item ) {
					if ( $item['item_type'] === LP_COURSE_CPT ) {
						$course_ids[] = (int) $item['item_id'];
					}
				}

				return $course_ids;
				/**
				 * Comment by tungnx. Because it will error if item didn't have key 'course_id'.
				 * Ex: case buy certificate, will not have that key
				 */
				// return @wp_list_pluck( $items, 'course_id' );
			}

			return false;
		}

		/**
		 * Check is order of Guest (user not login)
		 *
		 * @return bool
		 */
		public function is_guest(): bool {
			return ! get_user_by( 'ID', $this->get_user_id() );
		}

		public function get_item_meta( &$item ) {
			$metas = get_metadata( 'learnpress_order_item', $item['id'] );
			if ( $metas ) {
				foreach ( $metas as $k => $v ) {
					$item[ preg_replace( '!^_!', '', $k ) ] = LP_Helper::maybe_unserialize( $v[0] );
				}
			}
		}

		/**
		 * Remove all items from an order
		 */
		public function remove_order_items() {
			global $wpdb;
			$wpdb->query(
				$wpdb->prepare(
					"
				DELETE FROM itemmeta
				USING $wpdb->learnpress_order_itemmeta itemmeta
				INNER JOIN $wpdb->learnpress_order_items items
				WHERE itemmeta.learnpress_order_item_id = items.order_item_id
				AND items.order_id = %d",
					$this->get_id()
				)
			);
			$wpdb->query(
				$wpdb->prepare(
					"
				DELETE FROM {$wpdb->learnpress_order_items}
				WHERE order_id = %d",
					$this->get_id()
				)
			);
			$wpdb->query( $wpdb->prepare( "ALTER TABLE {$wpdb->learnpress_order_itemmeta} AUTO_INCREMENT = %d", 1 ) );
			$wpdb->query( $wpdb->prepare( "ALTER TABLE {$wpdb->learnpress_order_items} AUTO_INCREMENT = %d", 1 ) );
		}

		/**
		 * Add a new item to order.
		 *
		 * @param array|int $item
		 *
		 * @return int
		 * @throws Exception
		 * @since 1.0.0
		 * @version 4.2.5
		 */
		public function add_item( $item ): int {
			global $wpdb;
			$order_item_id = 0;

			try {
				if ( is_numeric( $item ) ) {
					$item = array(
						'item_id'         => absint( $item ),
						'order_item_name' => get_the_title( $item ),
					);
				}

				$item_type = $item['item_type'] ?? get_post_type( $item['item_id'] );
				if ( ! in_array( $item_type, learn_press_get_item_types_can_purchase() ) ) {
					return false;
				}

				$item = wp_parse_args(
					$item,
					array(
						'item_id'         => 0,
						'item_type'       => '',
						'order_item_name' => '',
						'quantity'        => 1,
						'subtotal'        => 0,
						'total'           => 0,
						'meta'            => array(),
					)
				);

				switch ( $item_type ) {
					case LP_COURSE_CPT:
						$course                     = learn_press_get_course( $item['item_id'] );
						$item['subtotal']           = apply_filters( 'learnpress/order/item/subtotal', $course->get_price() * $item['quantity'], $course, $item, $this );
						$item['total']              = apply_filters( 'learnpress/order/item/total', $course->get_price() * $item['quantity'], $course, $item, $this );
						$item['order_item_name']    = apply_filters( 'learnpress/order/item/title', get_post_field( 'post_title', $item['item_id'], 'raw' ), $course, $item, $this );
						$item['meta']['_course_id'] = $item['item_id'];
						break;
					default:
						$item = apply_filters( 'learnpress/order/add-item/item_type_' . $item_type, $item );
						break;
				}

				// Insert new order item
				$wpdb->insert(
					$wpdb->learnpress_order_items,
					array(
						'order_item_name' => $item['order_item_name'],
						'order_id'        => $this->get_id(),
						'item_id'         => $item['item_id'],
						'item_type'       => $item_type,
					),
					array(
						'%s',
						'%d',
						'%d',
						'%s',
					)
				);
				$order_item_id = absint( $wpdb->insert_id );
				// Clear cache
				$key = "order/{$this->get_id()}/{$this->get_status()}/items";
				LP_Cache::cache_load_first( 'clear', $key );
				// End insert new order item

				// Add learnpress_order_itemmeta
				$item['meta']['_quantity'] = $item['quantity'] ?? 1;
				$item['meta']['_subtotal'] = $item['subtotal'] ?? 0;
				$item['meta']['_total']    = $item['total'] ?? 0;

				if ( is_array( $item['meta'] ) ) {
					foreach ( $item['meta'] as $k => $v ) {
						learn_press_add_order_item_meta( $order_item_id, $k, $v );
					}
				}

				do_action( 'learn-press/added-order-item-data', $order_item_id, $item, $this->get_id() );
			} catch ( Throwable $e ) {
				error_log( __FUNCTION__ . ': ' . $e->getMessage() );
			}

			return $order_item_id;
		}

		/**
		 * Set total
		 *
		 * @param int|float $total
		 */
		public function set_total( $total = 0 ) {
			$this->_set_data( 'total', $total );
		}

		/**
		 * Get total
		 *
		 * @return int|float
		 */
		public function get_total() {
			return $this->get_data( 'total', 0 );
		}

		/**
		 * @param float|int $subtotal
		 */
		public function set_subtotal( $subtotal = 0 ) {
			$this->_set_data( 'subtotal', $subtotal );
		}

		/**
		 * Get subtotal
		 *
		 * @return float
		 */
		public function get_subtotal() {
			return $this->get_data( 'subtotal' );
		}

		/**
		 * Remove an item from database and it's data.
		 *
		 * @param $order_item_id
		 *
		 * @return bool
		 * @throws Exception
		 */
		public function remove_item( $order_item_id ): bool {
			global $wpdb;

			$item_id       = absint( $order_item_id );
			$order_item_id = absint( $order_item_id );

			if ( ! $item_id ) {
				return false;
			}

			do_action( 'learn_press_before_delete_order_item', $item_id );

			/**
			 * @since 3.0.0
			 */
			do_action( 'learn-press/before-delete-order-item', $item_id, $this->get_id() );

			$filter                      = new OrderItemsFilter();
			$filter->order_item_id       = $order_item_id;
			$filter->return_string_query = true;
			$filter->run_query_count     = false;
			$order_items_db              = LPOrderItemsDB::getInstance();
			$order_items_db->get_query_single_row( $filter );
			$query_single_row = $order_items_db->get_items( $filter );
			$itemObj          = $order_items_db->wpdb->get_row( $query_single_row );

			if ( $itemObj && $itemObj->item_type === LP_COURSE_CPT ) {
				$course_id = $itemObj->item_id;
				$user_ids  = $this->get_user_id();
				if ( is_array( $user_ids ) ) {
					foreach ( $user_ids as $user_id ) {
						// Delete course item on learnpress_user_items if exists
						$userCourseItem = UserItemModel::find_user_item(
							$user_id,
							$course_id,
							LP_COURSE_CPT,
							$this->get_id(),
							LP_ORDER_CPT,
							true
						);
						if ( $userCourseItem instanceof UserItemModel ) {
							$userCourseModel = new UserCourseModel( $userCourseItem );
							$userCourseModel->delete();
						}
					}
				} else {
					// Delete course item on learnpress_user_items if exists
					$userCourseItem = UserItemModel::find_user_item(
						$user_ids,
						$course_id,
						LP_COURSE_CPT,
						$this->get_id(),
						LP_ORDER_CPT,
						true
					);
					if ( $userCourseItem instanceof UserItemModel ) {
						$userCourseModel = new UserCourseModel( $userCourseItem );
						$userCourseModel->delete();
					}
				}
			}

			$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}learnpress_order_items WHERE order_item_id = %d", $item_id ) );
			$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}learnpress_order_itemmeta WHERE learnpress_order_item_id = %d", $item_id ) );

			// Clear cache
			$key = "order/{$this->get_id()}/{$this->get_status()}/items";
			LP_Cache::cache_load_first( 'clear', $key );

			/**
			 * @since 3.0.0
			 */
			do_action( 'learn-press/deleted-order-item', $item_id, $this->get_id() );

			do_action( 'learn_press_delete_order_item', $item_id );

			return true;
		}

		/**
		 * Add multiple items.
		 *
		 * @param array $items
		 *
		 * @return array
		 */
		public function add_items( $items ) {
			settype( $items, 'array' );
			$item_ids = array();
			try {
				foreach ( $items as $item ) {
					$item_id = $this->add_item( $item );
					if ( $item_id ) {
						$item_ids[] = $item_id;
					}
				}
			} catch ( Throwable $e ) {
				error_log( $e->getMessage() );
			}

			return $item_ids;
		}

		/**
		 * @param string $field
		 *
		 * @return array|bool|int|LP_User|mixed
		 */
		public function get_user( $field = '' ) {

			$users = $this->get_users();
			$uid   = reset( $users );

			$user = learn_press_get_user( $uid );
			if ( false === $user ) {
				return false;
			}

			if ( strtolower( $field ) == 'id' ) {
				return $user->get_id();
			}

			if ( $field && $user ) {
				return $user->get_data( $field );
			}

			return $user;
		}

		/**
		 * Get user id in array.
		 * user_id = 0 -> User type Guest
		 *
		 * @return int[]
		 * @editor tungnx
		 * @modify 4.1.4
		 * @version 1.0.1
		 */
		public function get_users(): array {
			$users = $this->get_user_id();
			if ( $users !== - 1 ) {
				settype( $users, 'array' );

				$users = array_map(
					function ( $user ) {
						return absint( $user );
					},
					$users
				);

				$users = array_unique( $users );
			} else {
				$users = array();
			}

			return $users;
		}

		public function dropdown_users() {
			$order_users = $this->get_users();
			$users       = get_users( array() );
			echo '<select name="order-customer[]" id="order-customer" multiple="multiple">';
			foreach ( (array) $users as $user ) {
				// $user->get_id() = (int) $user->get_id();
				if ( in_array( $user->get_id(), $order_users ) ) {
					$found_selected = true;
				} else {
					$found_selected = false;
				}
				printf( '<option value="%d" %s>%s</option>', esc_attr( $user->get_id() ), selected( $found_selected, true, false ), esc_html( $user->user_login ) );
			}
			echo '</select>';
		}


		public function get_checkout_payment_url() {
		}

		public function get_formatted_order_subtotal() {
			$currency_symbol = learn_press_get_currency_symbol( $this->get_currency() );

			return learn_press_format_price( $this->get_subtotal(), $currency_symbol );
		}

		public function get_formatted_order_total() {
			$currency_symbol = learn_press_get_currency_symbol( $this->get_currency() );

			return learn_press_format_price( $this->get_total(), $currency_symbol );
		}

		public function get_currency() {
			return $this->get_data( 'currency' ) ? $this->get_data( 'currency' ) : learn_press_get_currency();
		}

		public function set_currency( $value ) {
			$this->_set_data( 'currency', $value );
		}

		/**
		 * Get payment method title.
		 *
		 * @return array|mixed
		 */
		public function get_payment_method_title() {
			return esc_html( $this->get_data( 'payment_method_title', '' ) );
		}

		/**
		 * Get view order detail url.
		 *
		 * @return string
		 */
		public function get_view_order_url() {
			global $wp_query;

			$userModel           = UserModel::find( get_current_user_id(), true );
			$view_order_endpoint = LP_Settings::instance()->get( 'profile_endpoints.order-details' );

			if ( ! $view_order_endpoint ) {
				$view_order_endpoint = 'order-details';
			}

			if ( ! $userModel ) {
				return '';
			}

			$user_slug    = $userModel->user_nicename;
			$link_profile = learn_press_get_page_link( 'profile' );

			$view_order_endpoint = urlencode( $view_order_endpoint );
			if ( get_option( 'permalink_structure' ) ) {
				$view_order_url = $link_profile . $user_slug . '/' . $view_order_endpoint . '/' . $this->get_id() . '/';
			} else {
				$args         = array(
					'user' => $user_slug,
				);
				$args['view'] = $view_order_endpoint;

				if ( $view_order_endpoint ) {
					$args['id'] = $this->get_id();
				}
				$view_order_url = add_query_arg(
					$args,
					$link_profile
				);
			}

			return apply_filters( 'learn_press_view_order_url', $view_order_url, $this );
		}

		/**
		 * Get cancel url if it's status is pending.
		 *
		 * @param bool $force
		 *
		 * @return mixed
		 */
		public function get_cancel_order_url( $force = false ) {

			$url = false;
			if ( $this->has_status( 'pending' ) ) {
				$user_id = get_current_user_id();
				$profile = LP_Profile::instance( $user_id );
				$url     = $profile->get_tab_link( LP_Settings::instance()->get( 'profile_endpoints.orders' ) );
				if ( ! $force ) {
					$url = esc_url_raw( add_query_arg( 'cancel-order', $this->get_id(), $url ) );
				} else {
					$url = esc_url_raw( add_query_arg( 'cancelled-order', $this->get_id(), $url ) );
				}

				$url = wp_nonce_url( $url, 'cancel-order', 'lp-nonce' );
			}

			return apply_filters( 'learn-press/order-cancel-url', $url, $this->get_id() );
		}

		/**
		 * Get refund URL if order can be refunded by customer.
		 *
		 * @since 4.3.4
		 * @version 1.0.0
		 * @return bool|string
		 */
		/*public function get_refund_order_url() {
			$url  = false;
			$user = learn_press_get_current_user();
			if ( ! $user instanceof LP_User || $user->get_id() <= 0 ) {
				return apply_filters( 'learn-press/order-refund-url', $url, $this->get_id() );
			}

			$eligibility = learn_press_get_order_refund_eligibility( $this, $user->get_id() );
			if ( empty( $eligibility['eligible'] ) ) {
				return apply_filters( 'learn-press/order-refund-url', $url, $this->get_id() );
			}

			$url = learn_press_user_profile_link(
				$user->get_id(),
				LP_Settings::instance()->get( 'profile_endpoints.orders', 'orders' )
			);
			$url = esc_url_raw( $url );

			return apply_filters( 'learn-press/order-refund-url', $url, $this->get_id() );
		}*/

		/**
		 * Get profile order's actions.
		 *
		 * @return array|mixed
		 */
		public function get_profile_order_actions() {
			$actions = [];

			$userModel = UserModel::find( get_current_user_id(), true );
			if ( ! $userModel instanceof UserModel ) {
				return $actions;
			}

			$order_user_id = $this->get_users();
			if ( ! in_array( $userModel->get_id(), $order_user_id ) ) {
				return $actions;
			}

			$actions = array(
				'view' => array(
					'url'  => $this->get_view_order_url(),
					'text' => __( 'View', 'learnpress' ),
				),
			);

			$cancel_url = $this->get_cancel_order_url();
			if ( $cancel_url ) {
				$actions['cancel'] = array(
					'url'  => $this->get_cancel_order_url(),
					'text' => __( 'Cancel', 'learnpress' ),
				);
			}

			$refund_request_status   = $this->get_refund_request();
			$can_send_request_refund = $this->can_send_request_refund( $userModel );
			if ( 'pending' === $refund_request_status ) {
				$actions['refund-requested'] = array(
					'url'  => '',
					'text' => __( 'Refund Requested', 'learnpress' ),
				);
			} elseif ( $can_send_request_refund === true ) {
				$require_reason = 'yes' === learn_press_get_refund_setting( 'require_refund_reason', 'no' );

				$actions['refund'] = array(
					'url'   => learn_press_user_profile_link(
						$userModel->get_id(),
						LP_Settings::instance()->get( 'profile_endpoints.orders', 'orders' )
					),
					'text'  => __( 'Refund', 'learnpress' ),
					'class' => 'lp-refund-order-action',
					'data'  => array(
						'order_id'           => $this->get_id(),
						'require_reason'     => $require_reason ? 'yes' : 'no',
						'reason_prompt'      => __( 'Enter your refund reason', 'learnpress' ),
						'reason_placeholder' => __( 'Please describe why you want a refund.', 'learnpress' ),
						'reason_required'    => __( 'Refund reason is required.', 'learnpress' ),
						'confirm_title'      => __( 'Request a refund?', 'learnpress' ),
						'confirm_text'       => __( 'This request will be sent and processed according to payment settings.', 'learnpress' ),
						'confirm_button'     => __( 'Submit Request', 'learnpress' ),
						'cancel_button'      => __( 'Cancel', 'learnpress' ),
					),
				);
			}

			$actions = apply_filters( 'learn-press/profile-order-actions', $actions, $this->get_id() );

			return $actions;
		}

		public function add_note( $note = null ) {
			$comment_author       = '';
			$comment_author_email = '';
			if ( is_user_logged_in() ) {
				$user                 = get_user_by( 'id', get_current_user_id() );
				$comment_author       = $user->display_name;
				$comment_author_email = $user->user_email;
			}

			$comment_post_ID    = $this->get_id();
			$comment_author_url = '';
			$comment_content    = $note;
			$comment_agent      = 'LearnPress';
			$comment_type       = 'lp_order_note';
			$comment_parent     = 0;
			$comment_approved   = 1;

			$commentdata = apply_filters(
				'learn_press_new_order_note_data',
				compact( 'comment_post_ID', 'comment_author', 'comment_author_email', 'comment_author_url', 'comment_content', 'comment_agent', 'comment_type', 'comment_parent', 'comment_approved' ),
				$this->get_id()
			);

			return wp_insert_comment( $commentdata );
		}

		/**
		 * Get user_name
		 *
		 * @param int $user_id
		 *
		 * @return string
		 */
		public function get_user_name( int $user_id = 0 ): string {
			$user = learn_press_get_user( $user_id );

			if ( $user && ! $user instanceof LP_User_Guest ) {
				$user_name = $user->get_display_name();
			} else {
				$user_name = $this->get_checkout_email();
			}

			return apply_filters( 'learn-press/order/user-name', sprintf( _x( '%1$s', 'full name', 'learnpress' ), $user_name ) );
		}

		/**
		 * Check to see if this order is for multi users
		 *
		 * @return bool
		 * @since 2.1.5
		 */
		public function is_multi_users(): bool {
			return is_array( $this->get_user_id() );
		}

		/**
		 * Get email of user has bought this order.
		 * In case this order is for multi users return an array with multi email addresses.
		 *
		 * @return array
		 * @since 2.1.5
		 */
		public function get_user_data(): array {
			$data = array();

			$user_ids = $this->get_users();
			if ( ! empty( $user_ids ) ) {
				foreach ( $user_ids as $user_id ) {
					$user = learn_press_get_user( $user_id );
					if ( $user && $user->is_exists() ) {
						$data[ $user_id ] = $user->get_data(
							array(
								'id',
								'email',
								'user_login',
								'description',
								'first_name',
								'last_name',
								'nickname',
								'display_name',
							)
						);
					}
				}
			}

			return $data;
		}

		/**
		 * Get email's user by user_id
		 *
		 * @param int $user_id
		 *
		 * @return string
		 * @editor tungnx
		 * @modify 4.1.3
		 * @version 1.0.3
		 */
		public function get_user_email( int $user_id = 0 ): string {
			if ( $user_id == 0 ) {
				$user_id = (int) $this->get_user_id();
			}

			$userModel = UserModel::find( $user_id, true );
			if ( $userModel instanceof UserModel ) {
				$email = $userModel->get_email();
			} else {
				$email = $this->get_checkout_email();
			}

			return $email;
		}

		/**
		 * Order title
		 *
		 * @param string $context
		 *
		 * @return array|mixed
		 */
		public function get_title( $context = '' ) {
			return $this->get_data( 'order_title', __( 'Order on', 'learnpress' ) . ' ' . current_time( 'l jS F Y h:i:s A' ) );
		}

		public function get_parent_id() {
			return $this->get_data( 'parent_id', 0 );
		}

		public function set_parent_id( $parent_id ) {
			$this->_set_data( 'parent_id', $parent_id );
		}

		public function set_user_id( $user_id ) {
			$this->_set_data( 'user_id', $user_id );
		}

		/**
		 * Set 1 for is user guest
		 *
		 * @param int $user_type_guest
		 */
		public function set_user_type_guest( int $user_type_guest ) {
			$this->_set_data( 'user_type_guest', $user_type_guest );
		}

		/**
		 * Get user's ids of order.
		 *
		 * @return array|int
		 */
		public function get_user_id() {
			return $this->get_data( 'user_id', - 1 );
		}

		/**
		 * Get date modified of order.
		 *
		 * @return LP_Datetime
		 */
		public function get_date_modified() {
			return $this->get_data( 'date_modified' );
		}

		/**
		 * Set date modified of order.
		 *
		 * @param mixed $date
		 */
		public function set_date_modified( $date ) {
			$this->_set_data( 'date_modified', $date );
		}

		/**
		 * Set method for creating the order, such as: checkout
		 *
		 * @param string $created_via
		 */
		public function set_created_via( $created_via ) {
			$this->_set_data( 'created_via', $created_via );
		}

		/**
		 * Get method which order is created, such as: checkout
		 *
		 * @return string
		 */
		public function get_created_via(): string {
			return $this->get_data( 'created_via', '' );
		}

		/**
		 * Save order data.
		 *
		 * @return mixed
		 *
		 * @throws Exception
		 */
		public function save() {
			$old_status = '';

			if ( $this->get_id() ) {
				$old_status_post = get_post_status( $this->get_id() );
				if ( ! in_array( $old_status_post, array( 'trash', 'auto-draft' ) ) ) {
					$old_status = str_replace( 'lp-', '', $old_status_post );
				} else {
					$old_status = $old_status_post;
				}

				$return = $this->_curd->update( $this );
			} else {
				$return    = $this->_curd->create( $this );
				$this->_id = $return;
			}

			$new_status_post = get_post_status( $this->get_id() );
			if ( ! in_array( $new_status_post, array( 'trash', 'auto-draft' ) ) ) {
				$new_status = str_replace( 'lp-', '', $new_status_post );
			} else {
				$new_status = $new_status_post;
			}

			$order_id = $this->get_id();

			if ( $new_status !== $old_status ) {
				do_action( 'learn-press/order/status-changed', $order_id, $old_status, $new_status );
				do_action( 'learn-press/order/status-' . $new_status, $order_id, $old_status );
				do_action( 'learn-press/order/status-' . $old_status . '-to-' . $new_status, $order_id );
			}

			// $this->_save_status();

			return $return;
		}

		/**
		 * Get an instance of LP_Order by post ID or WP_Post object
		 *
		 * @param $order
		 * @param $force
		 *
		 * @return LP_Order
		 */
		public static function instance( $order, $force = true ) {
			// learn_press_deprecated_function( 'new LP_Order', '3.0', 'learn_press_get_order' );

			return learn_press_get_order( $order );
		}

		/** Getter/Setter  **/
		public function set_customer_message( $message ) {
			$this->_set_data( 'customer_message', $message );
		}

		public function set_customer_note( $note ) {
			$this->_set_data( 'customer_note', $note );
		}

		public function get_customer_note() {
			return $this->get_data( 'customer_note' ) . '';
		}

		public function set_user_ip_address( $value ) {
			$this->_set_data( 'user_ip_address', $value );
		}

		public function set_user_agent( $value ) {
			$this->_set_data( 'user_agent', $value );
		}

		public function get_user_agent() {
			return $this->get_data( 'user_agent' );
		}

		public function set_checkout_email( $email ) {
			$this->_set_data( 'checkout_email', $email );
		}

		/**
		 * Get email checkout for case Guest
		 *
		 * @return string
		 */
		public function get_checkout_email(): string {
			return $this->get_data( 'checkout_email', '' );
		}

		/**
		 * Short function to check order is completed.
		 *
		 * @return bool
		 */
		public function is_completed(): bool {
			return $this->get_status() === LP_ORDER_COMPLETED;
		}

		/**
		 * Check Order is created manual.
		 *
		 * @return bool
		 * @since 4.1.3
		 * @author tungnx
		 * @version 1.0.0
		 */
		public function is_manual(): bool {
			return $this->get_created_via() === LP_ORDER_CREATED_VIA_MANUAL;
		}

		/**
		 * Check can delete lp_user_items old
		 *
		 * @param LP_Course $course
		 *
		 * @return bool
		 * @throws Exception
		 * @author tungnx
		 * @since 4.1.4
		 * @version 1.0.0
		 */
		public function check_can_delete_item_old( LP_Course $course ): bool {
			$can_delete = false;

			$lp_user_items_db = LP_User_Items_DB::getInstance();

			/**
			 * For case user buy on frontend (not LP Order manual)
			 * And course enable repurchase and repurchase_type is keep
			 */
			$allow_repurchase_type = '';

			$filter          = new LP_User_Items_Filter();
			$filter->user_id = get_current_user_id();
			$filter->item_id = $course->get_id();
			$user_course     = $lp_user_items_db->get_last_user_course( $filter );

			if ( $user_course && isset( $user_course->user_item_id ) ) {
				$latest_user_item_id = $user_course->user_item_id;

				/** Get allow_repurchase_type for reset or update. Add in: rest-api/v1/frontend/class-lp-courses-controller.php: purchase_course */
				$allow_repurchase_type = learn_press_get_user_item_meta( $latest_user_item_id, '_lp_allow_repurchase_type' );
			}

			/**
			 * Course is free
			 * Or not allow repurchase
			 * Or repurchase not type 'Keep'
			 * And else
			 * Will deleted lp_user_items old
			 */
			if ( $course->is_free() || empty( $allow_repurchase_type ) || ! $course->allow_repurchase() || $allow_repurchase_type != 'keep' ) {
				$can_delete = true;
			}

			return apply_filters( 'learnpress/order/can_delete_old_item', $can_delete, $course );
		}

		/**
		 * Get list status of LP Order
		 *
		 * @return array
		 */
		public static function get_order_statuses(): array {
			$order_statuses = array(
				LP_ORDER_COMPLETED_DB  => LP_ORDER_COMPLETED,
				LP_ORDER_PROCESSING_DB => LP_ORDER_PROCESSING,
				LP_ORDER_PENDING_DB    => LP_ORDER_PENDING,
				LP_ORDER_CANCELLED_DB  => LP_ORDER_CANCELLED,
				LP_ORDER_FAILED_DB     => LP_ORDER_FAILED,
				LP_ORDER_REFUNDED_DB   => LP_ORDER_REFUNDED,
			);

			return apply_filters( 'lp/order/statuses', $order_statuses );
		}

		/**
		 * Query list orders.
		 *
		 * @param PostFilter $post_filter
		 * @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 ]
		 *
		 * @return void
		 * @throws Exception
		 * @since 4.3.2.8
		 * @version 1.0.0
		 */
		public static function handle_params_query_list_orders( PostFilter &$post_filter, array $param = array() ) {
			$post_db               = PostDB::getInstance();
			$user_of_order         = absint( $param['author'] ?? 0 );
			$status                = $param['post_status'] ?? '';
			$key                   = $param['s'] ?? '';
			$month                 = $param['m'] ?? '';
			$limit                 = $param['posts_per_page'] ?? 20;
			$paged                 = $param['paged'] ?? 1;
			$refund_request_status = sanitize_key( (string) ( $param['refund_request_status'] ?? '' ) );
			$order_by              = $param['orderby'] ?? 'date';
			if ( empty( $order_by ) ) {
				$order_by = 'ID';
			} else {
				switch ( $order_by ) {
					case 'date':
						$order_by = 'post_date';
						break;
					case 'title':
						$order_by = 'ID';
						break;
				}
			}

			$order = $param['order'] ?? 'DESC';
			// End convert params

			if ( $order_by === 'order_total' ) {
				$post_filter->join[]   = "INNER JOIN {$post_db->tb_postmeta} pm2 ON p.ID = pm2.post_id AND pm2.meta_key = '_order_total'";
				$post_filter->where[]  = 'AND CAST(pm2.meta_value AS UNSIGNED)';
				$post_filter->order_by = 'pm2.meta_value';
			} else {
				$post_filter->order_by = $order_by;
			}

			if ( ! empty( $key ) ) {
				$pattern          = '/^#\d+$/';
				$is_order_id_sure = false;
				if ( preg_match( $pattern, $key ) ) {
					$is_order_id_sure = true;
					$key              = str_replace( '#', '', $key );
				}

				$pattern2 = '#^0+.*\d+$#';
				if ( preg_match( $pattern2, $key ) ) {
					$key = (int) $key;
				}

				$key = trim( $key );

				if ( $is_order_id_sure ) {
					$post_filter->where[] = $post_db->wpdb->prepare( 'AND p.ID = %d', $key );
				} else {
					$post_filter->join[]  = "INNER JOIN {$post_db->tb_lp_order_items} lpori ON p.ID = lpori.order_id";
					$post_filter->where[] = $post_db->wpdb->prepare(
						'AND (p.ID = %d OR lpori.order_item_name like %s)',
						$key,
						'%' . $key . '%'
					);
				}
			}

			if ( ! empty( $month ) ) {
				$year                 = substr( $month, 0, 4 );
				$post_filter->where[] = "AND YEAR(p.post_date) = $year";
				if ( strlen( $month ) > 5 ) {
					$mon                  = substr( $month, 4, 2 );
					$post_filter->where[] = "AND MONTH(p.post_date) = $mon";
				}
			}

			$post_filter->order = $order;
			$post_filter->limit = $limit;
			$post_filter->page  = $paged;

			if ( ! empty( $user_of_order ) ) {
				$user_id              = absint( $user_of_order );
				$post_filter->join[]  = "INNER JOIN {$post_db->tb_postmeta} pm1 ON p.ID = pm1.post_id AND pm1.meta_key = '_user_id'";
				$post_filter->where[] = "AND ( pm1.meta_value like '%\"$user_id\"%' OR pm1.meta_value = $user_id )";
			}

			if ( ! empty( $refund_request_status ) ) {
				$post_filter->where[] = $post_db->wpdb->prepare(
					"AND EXISTS (
						SELECT 1 FROM {$post_db->tb_postmeta} refund_request_pm
						WHERE refund_request_pm.post_id = p.ID
						AND refund_request_pm.meta_key = %s
						AND refund_request_pm.meta_value = %s
					)",
					LP_Order::META_KEY_REFUND_REQUEST,
					$refund_request_status
				);
			}
			if ( ! empty( $status ) && $status !== 'all' ) {
				$post_filter->post_status = (array) $status;
			} else {
				$post_filter->where[] = $post_db->wpdb->prepare( 'AND p.post_status != %s', LP_ORDER_TRASH );
			}
		}

		/**
		 * Get transaction id from order meta
		 *
		 * @since 4.4.0
		 * @version 1.0.0
		 * @return string
		 */
		public function get_transaction_id(): string {
			return (string) $this->get_meta( self::META_KEY_TRANSACTION_ID );
		}

		/**
		 * Get refund request status from order meta.
		 *
		 * @since 4.4.0
		 * @version 1.0.0
		 * @return string pending|approved|auto-approved|denied|''
		 */
		public function get_refund_request(): string {
			return (string) get_post_meta( $this->get_id(), self::META_KEY_REFUND_REQUEST, true );
		}

		/**
		 * Check the order can be refunded.
		 *
		 * Validates multiple conditions required for refund eligibility:
		 * - Refund requests must be enabled in settings
		 * - Order must not already be refunded
		 * - Order must be in 'completed' status
		 * - Refund amount must not exceed order total (including previously refunded amount)
		 * - Payment gateway must have refund method implemented (override from abstract)
		 *
		 * @param float $amount The amount to refund..
		 *
		 * @return bool|WP_Error Returns true if refund is allowed, WP_Error with specific error code otherwise.
		 *
		 * Error codes:
		 * - 'refund_disabled': Refund feature is disabled in settings
		 * - 'order_refunded': Order already has refunded status
		 * - 'order_not_completed': Order is not in completed status
		 * - 'order_refund_amount_exceeds_total': Generic error (amount exceeds total or gateway unavailable)
		 * - 'order_refund_gateway_unavailable': Gateway does not support refund
		 *
		 * @since 4.4.0
		 * @version 1.0.0
		 */
		public function can_refund( $amount = 0 ) {
			try {
				$error_code = '';
				if ( 'yes' !== learn_press_get_refund_setting( 'enable_refund_requests', 'no' ) ) {
					$error_code = 'refund_disabled';
					throw new Exception( __( 'Refund requests are currently disabled.', 'learnpress' ) );
				}

				if ( $this->has_status( LP_ORDER_REFUNDED ) ) {
					$error_code = 'order_refunded';
					throw new Exception( __( 'Order has refunded', 'learnpress' ) );
				}

				if ( ! $this->has_status( LP_ORDER_COMPLETED ) ) {
					$error_code = 'order_not_completed';
					throw new Exception( __( 'Order is not completed', 'learnpress' ) );
				}

				$refunded_amount = $this->get_meta( LP_Order::META_KEY_REFUNDED_AMOUNT );
				if ( $refunded_amount > 0 ) {
					$amount = $refunded_amount + $amount;
				}

				if ( $amount > $this->get_total() ) {
					$error_code = 'order_refund_amount_exceeds_total';
					throw new Exception( __( 'Refund amount is greater than order total.', 'learnpress' ) );
				}

				$payment_method = strtolower( $this->get_data( 'payment_method', '' ) );
				$gateway        = LP_Gateways::instance()->get_gateway( $payment_method );

				$has_refund_override = false;
				if ( $gateway && method_exists( $gateway, 'refund' ) ) {
					// Check gateway has refund method override
					$reflection          = new ReflectionMethod( $gateway, 'refund' );
					$has_refund_override = $reflection->class === get_class( $gateway );
				}

				if ( ! $gateway || ! $has_refund_override ) {
					$error_code = 'order_refund_gateway_unavailable';
					throw new Exception( __( 'Refund gateway is unavailable.', 'learnpress' ) );
				}
			} catch ( Throwable $e ) {
				if ( empty( $error_code ) ) {
					$error_code = 'order_can_not_refund';
				}

				return new WP_Error( $error_code, $e->getMessage() );
			}

			return true;
		}

		/**
		 * Check user can send request refund
		 *
		 * Apply for cache 1 user 1 order, only user of this order can send request refund
		 *
		 * @since 4.4.0
		 * @version 1.0.0
		 * @return bool|WP_Error
		 */
		public function can_send_request_refund( UserModel $userModel ) {
			try {
				$error_code = '';

				// Check user of this order
				$order_user = (int) $this->get_user_id();
				if ( $order_user !== $userModel->get_id() ) {
					$error_code = 'request_refund_user_invalid';
					throw new Exception(
						__( 'You do not have permission to refund this order.', 'learnpress' )
					);
				}

				// Check refund requests are enabled
				if ( 'yes' !== learn_press_get_refund_setting( 'enable_refund_requests', 'no' ) ) {
					$error_code = 'refund_disabled';
					throw new Exception( __( 'Refund requests are currently disabled.', 'learnpress' ) );
				}

				// Check order total > 0
				if ( $this->get_total() <= 0 ) {
					$error_code = 'order_total_invalid';
					throw new Exception( __( 'Order total is not valid.', 'learnpress' ) );
				}

				// Check request refund status exists
				$request_status = $this->get_refund_request();
				if ( 'rejected' === $request_status ) {
					if ( 'yes' !== learn_press_get_refund_setting( 'allow_resend_after_rejected', 'no' ) ) {
						$error_code = 'request_refund_is_rejected';
						throw new Exception( __( 'Request refund is rejected!.', 'learnpress' ) );
					}
				} elseif ( ! empty( $request_status ) ) {
					$error_code = 'request_refund_sent';
					throw new Exception(
						sprintf(
							__( 'Request refund has %s', 'learnpress' ),
							$request_status
						)
					);
				}

				// Check time limit for refund request
				$refund_time_limit = learn_press_get_refund_setting( 'refund_time_limit', 30 );
				if ( $refund_time_limit > 0 ) {
					$order_time = absint( $this->get_order_date( 'timestamp' ) );
					if ( $order_time > 0 ) {
						$now                = current_time( 'timestamp' );
						$allowed_time_limit = strtotime( '+ ' . $refund_time_limit . ' day', $order_time );
						if ( $allowed_time_limit < $now ) {
							$error_code = 'request_refund_time_expired';
							throw new Exception( __( 'Time for refund request expired', 'learnpress' ) );
						}
					}
				}

				// Check gateway support refund
				$payment_method = strtolower( $this->get_data( 'payment_method', '' ) );
				$gateway        = LP_Gateways::instance()->get_gateway( $payment_method );

				$gateway_has_refund_override = false;
				if ( $gateway && method_exists( $gateway, 'refund' ) ) {
					// Check gateway has refund method override
					$reflection                  = new ReflectionMethod( $gateway, 'refund' );
					$gateway_has_refund_override = $reflection->class === get_class( $gateway );
				}

				if ( ! $gateway || ! $gateway_has_refund_override ) {
					$error_code = 'order_refund_gateway_unavailable';
					throw new Exception( __( 'Refund gateway is unavailable.', 'learnpress' ) );
				}
			} catch ( Throwable $e ) {
				if ( empty( $error_code ) ) {
					$error_code = 'user_can_not_send_request_refund';
				}

				return new WP_Error( $error_code, $e->getMessage() );
			}

			return true;
		}

		/**
		 * Refund order with amount
		 *
		 * @return void
		 * @throws Exception
		 */
		public function refund( $amount ) {
			$can_refund = $this->can_refund( $amount );
			if ( is_wp_error( $can_refund ) ) {
				throw new Exception( $can_refund->get_error_message() );
			}

			$payment_method = strtolower( $this->get_data( 'payment_method', '' ) );
			$gateway        = LP_Gateways::instance()->get_gateway( $payment_method );
			/** @var LP_Gateway_Abstract|LP_Gateway_Paypal $gateway */
			$gateway->refund( $this, $amount );

			$user_id = get_current_user_id();
			update_post_meta( $this->get_id(), self::META_KEY_REFUNDED_AMOUNT, (float) $amount );
			update_post_meta( $this->get_id(), self::META_KEY_REFUNDED_BY, $user_id );
			update_post_meta( $this->get_id(), self::META_KEY_REFUNDED_AT, gmdate( LP_Datetime::$format, time() ) );
			$this->update_status( LP_ORDER_REFUNDED );

			$this->add_note(
				sprintf(
					__( 'Order %1$s has been refunded %2$s.', 'learnpress' ),
					$this->get_order_number(),
					learn_press_format_price( $amount, learn_press_get_currency_symbol( $this->get_currency() ) )
				)
			);
		}
	}
}
