update_status( $status ); } return false; } /** * Add order item meta data. * * @param int $item_id * @param string $meta_key * @param mixed $meta_value * @param string $prev_value * * @return false|int */ function learn_press_add_order_item_meta( $item_id, $meta_key, $meta_value, $prev_value = '' ) { return add_metadata( 'learnpress_order_item', $item_id, $meta_key, $meta_value, $prev_value ); } /** * Update order item meta data. * * @param int $item_id * @param string $meta_key * @param mixed $meta_value * @param string $prev_value * * @return bool|int */ function learn_press_update_order_item_meta( $item_id, $meta_key, $meta_value, $prev_value = '' ) { return update_metadata( 'learnpress_order_item', $item_id, $meta_key, $meta_value, $prev_value ); } /** * Delete order item meta data. * * @param int $item_id * @param string $meta_key * @param mixed $meta_value * @param bool $delete_all * * @return bool */ function learn_press_delete_order_item_meta( $item_id, $meta_key, $meta_value, $delete_all = false ) { return delete_metadata( 'learnpress_order_item', $item_id, $meta_key, $meta_value, $delete_all ); } /** * Get order item meta data. * * @param int $item_id * @param string $meta_key * @param bool $single * * @return mixed */ function learn_press_get_order_item_meta( $item_id, $meta_key, $single = true ) { return get_metadata( 'learnpress_order_item', $item_id, $meta_key, $single ); } /** * Get order * * @param mixed $the_order * * @return LP_Order|bool object instance */ function learn_press_get_order( $the_order = false ) { global $post; $the_id = 0; if ( false === $the_order && is_a( $post, 'WP_Post' ) && LP_ORDER_CPT === get_post_type( $post ) ) { $the_id = $post->ID; } elseif ( is_numeric( $the_order ) ) { $the_id = $the_order; } elseif ( $the_order instanceof LP_Order ) { $the_id = $the_order->get_id(); } elseif ( ! empty( $the_order->ID ) ) { $the_id = $the_order->ID; } if ( LP_ORDER_CPT != get_post_type( $the_id ) ) { return false; } return new LP_Order( $the_id ); } /** * Count orders by it's status * * @param array $args * @Todo tungnx review to rewrite query * @return array */ function learn_press_count_orders( $args = array() ) { if ( is_string( $args ) ) { $args = array( 'status' => $args ); } else { $args = wp_parse_args( $args, array( 'status' => '', ) ); } global $wpdb; $statuses = $args['status']; if ( ! $statuses ) { $statuses = array_keys( LP_Order::get_order_statuses() ); } settype( $statuses, 'array' ); $size_of_status = sizeof( $statuses ); foreach ( $statuses as $k => $status ) { $statuses[ $k ] = ! preg_match( '~^lp-~', $status ) ? 'lp-' . $status : $status; } $format = array_fill( 0, $size_of_status, '%s' ); $counts = array_fill_keys( $statuses, 0 ); $statuses[] = LP_ORDER_CPT; $query = $wpdb->prepare( " SELECT COUNT(ID) AS count, post_status AS status FROM {$wpdb->posts} o WHERE post_status IN(" . join( ',', $format ) . ') AND post_type = %s GROUP BY o.post_status ', $statuses ); $results = $wpdb->get_results( $query ); if ( $results ) { foreach ( $results as $result ) { if ( array_key_exists( $result->status, $counts ) ) { $counts[ $result->status ] = absint( $result->count ); } } } return $size_of_status > 1 ? $counts : reset( $counts ); } /** * Format price with currency and other settings. * * @param float $price * @param string $currency * * @return string */ function learn_press_format_price( $price = 0, $currency = '' ): string { if ( ! is_numeric( $price ) ) { $price = 0; } $before = $after = ''; $currency = esc_html( is_string( $currency ) && '' !== $currency ? $currency : learn_press_get_currency_symbol() ); $thousands_separator = esc_html( LP_Settings::get_option( 'thousands_separator', ',' ) ); $number_of_decimals = esc_html( LP_Settings::get_option( 'number_of_decimals', 2 ) ); $decimals_separator = esc_html( LP_Settings::get_option( 'decimals_separator', '.' ) ); switch ( LP_Settings::get_option( 'currency_pos' ) ) { default: $before = $currency; break; case 'left_with_space': $before = $currency . ' '; break; case 'right': $after = $currency; break; case 'right_with_space': $after = ' ' . $currency; } return $before . number_format( $price, $number_of_decimals, $decimals_separator, $thousands_separator ) . $after; } /** * Update * * @param $order_id * * @return array|bool */ function learn_press_update_order_items( $order_id ) { $order = learn_press_get_order( $order_id ); if ( ! $order ) { return false; } $subtotal = 0; $total = 0; $items = $order->get_items(); if ( $items ) { foreach ( $items as $item ) { $subtotal += $item['subtotal']; $total += $item['total']; } } update_post_meta( $order_id, '_order_currency', learn_press_get_currency() ); update_post_meta( $order_id, '_prices_include_tax', 'no' ); update_post_meta( $order_id, '_order_subtotal', $subtotal ); update_post_meta( $order_id, '_order_total', $total ); update_post_meta( $order_id, '_order_key', learn_press_generate_order_key() ); update_post_meta( $order_id, '_payment_method', '' ); update_post_meta( $order_id, '_payment_method_title', '' ); update_post_meta( $order_id, '_order_version', '1.0' ); return array( 'subtotal' => $subtotal, 'total' => $total, 'currency' => learn_press_get_currency(), ); } /** * Format order's ID in ten numbers. Eg: 0000000XXX. * * @param int $order_number * * @since 2.0.0 * * @return string */ function learn_press_transaction_order_number( $order_number ) { $formatted_number = apply_filters( 'learn_press_get_order_number', '#' . sprintf( "%'.010d", $order_number ), $order_number ); return apply_filters( 'learn-press/order-number-formatted', $formatted_number, $order_number ); } /** * Get list of registered order's statues for registering with wp post's status. * * @since 2.0.0 * * @return array */ function learn_press_get_register_order_statuses() { $order_statues = array(); $order_statues['lp-completed'] = array( 'label' => _x( 'Completed', 'Order status', 'learnpress' ), 'public' => false, 'exclude_from_search' => false, 'show_in_admin_all_list' => true, 'show_in_admin_status_list' => true, 'label_count' => _n_noop( 'Completed (%s)', 'Completed (%s)', 'learnpress' ), ); $order_statues['lp-pending'] = array( 'label' => _x( 'Pending', 'Order status', 'learnpress' ), 'public' => false, 'exclude_from_search' => false, 'show_in_admin_all_list' => true, 'show_in_admin_status_list' => true, 'label_count' => _n_noop( 'Pending Payment (%s)', 'Pending Payment (%s)', 'learnpress' ), ); $order_statues['lp-processing'] = array( 'label' => _x( 'Processing', 'Order status', 'learnpress' ), 'public' => false, 'exclude_from_search' => false, 'show_in_admin_all_list' => true, 'show_in_admin_status_list' => true, 'label_count' => _n_noop( 'Processing (%s)', 'Processing (%s)', 'learnpress' ), ); $order_statues['lp-cancelled'] = array( 'label' => _x( 'Cancelled', 'Order status', 'learnpress' ), 'public' => false, 'exclude_from_search' => false, 'show_in_admin_all_list' => true, 'show_in_admin_status_list' => true, 'label_count' => _n_noop( 'Cancelled (%s)', 'Cancelled (%s)', 'learnpress' ), ); $order_statues['lp-failed'] = array( 'label' => _x( 'Failed', 'Order status', 'learnpress' ), 'public' => false, 'exclude_from_search' => false, 'show_in_admin_all_list' => true, 'show_in_admin_status_list' => true, 'label_count' => _n_noop( 'Failed (%s)', 'Failed (%s)', 'learnpress' ), ); $order_statues['lp-refunded'] = array( 'label' => _x( 'Refunded', 'Order status', 'learnpress' ), 'public' => false, 'exclude_from_search' => false, 'show_in_admin_all_list' => true, 'show_in_admin_status_list' => true, 'label_count' => _n_noop( 'Refunded (%s)', 'Refunded (%s)', 'learnpress' ), ); $order_statues['trash'] = array( 'label' => _x( 'Trash', 'Order status', 'learnpress' ), 'public' => false, 'exclude_from_search' => false, 'show_in_admin_all_list' => true, 'show_in_admin_status_list' => true, 'label_count' => _n_noop( 'Trash (%s)', 'Trash (%s)', 'learnpress' ), ); return $order_statues; } function _learn_press_get_order_status_description( $status ) { $status = str_replace( 'lp-', '', (string) $status ); $descriptions = array( 'pending' => __( 'Order received in case a user purchases a course but doesn\'t finalize the order.', 'learnpress' ), 'processing' => __( 'Payment received and the order is awaiting fulfillment.', 'learnpress' ), 'completed' => __( 'The order is fulfilled and completed.', 'learnpress' ), 'cancelled' => __( 'The order is cancelled by an admin or the customer.', 'learnpress' ), 'refunded' => __( 'Order was refunded to the customer.', 'learnpress' ), ); return apply_filters( 'learn_press_order_status_description', ! empty( $descriptions[ $status ] ) ? $descriptions[ $status ] : '' ); } /** * Get status of an order by the ID. * * @param int $order_id * * @return bool|string */ function learn_press_get_order_status( $order_id ) { $order = learn_press_get_order( $order_id ); if ( $order ) { return $order->get_status(); } return false; } if ( ! function_exists( 'learn_press_get_refund_setting' ) ) { /** * Get refund setting value with safe defaults. * * @param string $key * @param mixed $default * * @since 4.3.4 * @version 1.0.0 * @return mixed */ function learn_press_get_refund_setting( string $key, $default = null ) { $defaults = array( 'enable_refund_requests' => 'no', 'auto_refund' => 'no', 'refund_time_limit' => 30, 'require_refund_reason' => 'no', 'allow_resend_after_rejected' => 'no', 'refund_max_completion' => 0, ); if ( null === $default && array_key_exists( $key, $defaults ) ) { $default = $defaults[ $key ]; } return LP_Settings::get_option( $key, $default ); } } if ( ! function_exists( 'learn_press_get_order_refund_supported_gateways' ) ) { /** * Get list gateways that support refund. * * @since 4.3.4 * @version 1.1.0 * @return array */ function learn_press_get_order_refund_supported_gateways(): array { if ( ! class_exists( 'LP_Gateways' ) ) { return array(); } $gateways_instance = LP_Gateways::instance(); if ( ! $gateways_instance || ! method_exists( $gateways_instance, 'get_gateways' ) ) { return array(); } $supported_gateways = array(); $all_gateways = (array) $gateways_instance->get_gateways(); foreach ( $all_gateways as $gateway_id => $gateway ) { $gateway_id = sanitize_key( (string) $gateway_id ); if ( empty( $gateway_id ) ) { continue; } if ( ! is_object( $gateway ) || ! is_callable( array( $gateway, 'refund' ) ) ) { continue; } $supported_gateways[] = $gateway_id; } return array_values( array_unique( $supported_gateways ) ); } } if ( ! function_exists( 'learn_press_cancel_order_process' ) ) { /** * Process action allows user to cancel an order is pending * in their profile. */ function learn_press_cancel_order_process() { if ( empty( $_REQUEST['cancel-order'] ) || empty( $_REQUEST['lp-nonce'] ) || ! wp_verify_nonce( $_REQUEST['lp-nonce'], 'cancel-order' ) || is_admin() ) { return; } $user_id = get_current_user_id(); $profile = LP_Profile::instance( $user_id ); $url = $profile->get_tab_link( LP_Settings::instance()->get( 'profile_endpoints.orders', 'orders' ) ); try { $message = array( 'status' => 'error', 'content' => '', ); $order_id = absint( $_REQUEST['cancel-order'] ); $order = learn_press_get_order( $order_id ); if ( ! $order ) { throw new Exception( sprintf( __( 'Order number %s not found', 'learnpress' ), $order_id ) ); } $user_ids = (array) $order->get_user_id(); if ( ! in_array( $user_id, $user_ids ) ) { throw new Exception( __( 'You do not have permission to cancel this order.', 'learnpress' ) ); } if ( $order->has_status( LP_ORDER_PENDING ) ) { $order->update_status( LP_ORDER_CANCELLED ); $order->add_note( __( 'The order is cancelled by the customer', 'learnpress' ) ); $message['status'] = 'success'; $message['content'] = sprintf( __( 'Order number %s has been cancelled', 'learnpress' ), $order->get_order_number() ); } else { throw new Exception( sprintf( __( 'The order number %s can not be cancelled.', 'learnpress' ), $order->get_order_number() ) ); } } catch ( Throwable $e ) { $message['content'] = $e->getMessage(); } learn_press_set_message( $message ); wp_safe_redirect( $url ); exit(); } } add_action( 'init', 'learn_press_cancel_order_process' ); if ( ! function_exists( 'learn_press_get_order_refund_event_data' ) ) { /** * Build normalized refund event payload. * * @since 4.3.5 * @version 1.0.1 * * @param LP_Order $order * @param array $overrides * * @return array */ function learn_press_get_order_refund_event_data( LP_Order $order, array $overrides = array() ): array { $order_id = $order->get_id(); $requested_at = get_post_meta( $order_id, '_lp_refund_requested_at', true ); if ( ! empty( $requested_at ) ) { $requested_at = new LP_Datetime( $requested_at ); $requested_at = sprintf( esc_html__( '%1$s %2$s', 'learnpress' ), $requested_at->format( LP_Datetime::I18N_FORMAT_HAS_TIME ), LP_Datetime::get_timezone_string() ); } $user_id = (int) $order->get_user_id(); $userOrderModel = UserModel::find( $user_id, true ); if ( $userOrderModel instanceof UserModel ) { $requested_by = $userOrderModel->get_display_name(); } else { $requested_by = $order->get_checkout_email(); } $data = array( 'order_id' => $order_id, 'order_number' => $order->get_order_number(), 'order_key' => $order->get_order_key(), 'order_status' => $order->get_status(), 'request_status' => $order->get_refund_request(), 'requested_by' => $requested_by, 'requested_at' => $requested_at, 'reviewed_by' => absint( get_post_meta( $order_id, '_lp_refund_reviewed_by', true ) ), 'reviewed_at' => (string) get_post_meta( $order_id, '_lp_refund_reviewed_at', true ), 'reason' => (string) get_post_meta( $order_id, LP_Order::META_KEY_REFUND_REQUEST_REASON, true ), 'requester_email' => '', 'admin_order_edit_url' => add_query_arg( array( 'post' => $order_id, 'action' => 'edit', ), admin_url( 'post.php' ) ), ); $requested_user_id = absint( $data['requested_by'] ); if ( ! empty( $requested_user_id ) ) { $requested_user = get_user_by( 'id', $requested_user_id ); if ( $requested_user instanceof WP_User ) { $data['requester_email'] = $requested_user->user_email; } } if ( ! empty( $overrides ) ) { $data = array_merge( $data, $overrides ); } return $data; } } /** * Render pending refund request panel on order detail. * * @since 4.3.4 * @version 1.0.0 * @param LP_Order $order */ function learn_press_admin_order_refund_request_panel( $order ) { if ( ! $order instanceof LP_Order ) { return; } $order_id = $order->get_id(); if ( ! current_user_can( 'edit_post', $order_id ) ) { return; } $refund_event_data = learn_press_get_order_refund_event_data( $order ); $refund_request_status = sanitize_key( (string) ( $refund_event_data['request_status'] ?? '' ) ); $requested_by = $refund_event_data['requested_by'] ?? ''; $requested_at = $refund_event_data['requested_at'] ?? ''; $refund_reason = $refund_event_data['reason'] ?? ''; $requester_email = ''; $status_labels = array( 'pending' => __( 'Pending review', 'learnpress' ), 'approved' => __( 'Approved', 'learnpress' ), 'auto-approved' => __( 'Auto approved', 'learnpress' ), 'rejected' => __( 'Rejected', 'learnpress' ), ); $status_label = $status_labels[ $refund_request_status ] ?? ''; if ( empty( $status_label ) && ! empty( $refund_request_status ) ) { $status_label = ucwords( str_replace( '-', ' ', $refund_request_status ) ); } $render_statuses = array( 'pending', 'approved', 'auto-approved' ); if ( ! in_array( $refund_request_status, $render_statuses, true ) ) { return; } $is_pending = ( 'pending' === $refund_request_status ); $currency_symbol = learn_press_get_currency_symbol( $order->get_currency() ); $order_total = round( max( 0, floatval( $order->get_total() ) ), 2 ); $order_total_formatted = learn_press_format_price( $order_total, $currency_symbol ); // Refund amount is only meaningful once the refund has been executed // (approved/auto-approved), when _lp_refund_amount has been written. $refund_amount_formatted = ''; if ( ! $is_pending ) { $refund_amount = get_post_meta( $order_id, LP_Order::META_KEY_REFUNDED_AMOUNT, true ); if ( '' === $refund_amount || null === $refund_amount ) { $refund_amount = $order_total; // Fallback for full refunds without a stored amount. } $refund_amount = round( max( 0, floatval( $refund_amount ) ), 2 ); $refund_amount_formatted = learn_press_format_price( $refund_amount, $currency_symbol ); } learn_press_admin_view( 'meta-boxes/order/refund-request-panel', compact( 'order_id', 'is_pending', 'order_total', 'order_total_formatted', 'refund_amount_formatted', 'requested_by', 'requested_at', 'requester_email', 'refund_reason', 'status_label' ) ); } /** * get total price order complete */ function learn_press_get_total_price_order_complete() { global $wpdb; $query = $wpdb->prepare( "SELECT SUM(meta_value) as order_total From `{$wpdb->prefix}postmeta` as mt INNER JOIN `{$wpdb->prefix}posts` as p ON p.id = mt.post_id WHERE p.post_type = %s AND mt.meta_key = %s AND p.post_status = %s ", LP_ORDER_CPT, '_order_total', 'lp-completed' ); $total = $wpdb->get_results( $query )[0]->order_total; return learn_press_format_price( $total, true ); }