PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.4.9
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.4.9
4.4.9 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 All 140 releases
← All changes | inc/order/lp-order-functions.php +229 -7 4.3.7 → 4.4.9 View file →
@@ -5,8 +5,10 @@
5 5 * @author ThimPress
6 6 * @package LearnPress/Functions
7 7 * @version 1.0
8 8 */
9 +use LearnPress\Models\UserItems\UserCourseModel;
10 +use LearnPress\Models\UserModel;
9 11
10 12 defined( 'ABSPATH' ) || exit;
11 13
12 14 /**
@@ -177,9 +179,9 @@
177 179
178 180 /**
179 181 * Format price with currency and other settings.
180 182 *
181 - * @param float $price
183 + * @param float $price
182 184 * @param string $currency
183 185 *
184 186 * @return string
185 187 */
@@ -320,8 +322,16 @@
320 322 'show_in_admin_all_list' => true,
321 323 'show_in_admin_status_list' => true,
322 324 'label_count' => _n_noop( 'Failed <span class="count">(%s)</span>', 'Failed <span class="count">(%s)</span>', 'learnpress' ),
323 325 );
326 + $order_statues['lp-refunded'] = array(
327 + 'label' => _x( 'Refunded', 'Order status', 'learnpress' ),
328 + 'public' => false,
329 + 'exclude_from_search' => false,
330 + 'show_in_admin_all_list' => true,
331 + 'show_in_admin_status_list' => true,
332 + 'label_count' => _n_noop( 'Refunded <span class="count">(%s)</span>', 'Refunded <span class="count">(%s)</span>', 'learnpress' ),
333 + );
324 334 $order_statues['trash'] = array(
325 335 'label' => _x( 'Trash', 'Order status', 'learnpress' ),
326 336 'public' => false,
327 337 'exclude_from_search' => false,
@@ -333,18 +343,20 @@
333 343 return $order_statues;
334 344 }
335 345
336 346 function _learn_press_get_order_status_description( $status ) {
347 +
348 + $status = str_replace( 'lp-', '', (string) $status );
337 349 $descriptions = array(
338 350 'pending' => __( 'Order received in case a user purchases a course but doesn\'t finalize the order.', 'learnpress' ),
339 351 'processing' => __( 'Payment received and the order is awaiting fulfillment.', 'learnpress' ),
340 352 'completed' => __( 'The order is fulfilled and completed.', 'learnpress' ),
341 353 'cancelled' => __( 'The order is cancelled by an admin or the customer.', 'learnpress' ),
354 + 'refunded' => __( 'Order was refunded to the customer.', 'learnpress' ),
342 355 );
343 356
344 357 return apply_filters( 'learn_press_order_status_description', ! empty( $descriptions[ $status ] ) ? $descriptions[ $status ] : '' );
345 358 }
346 -
347 359 /**
348 360 * Get status of an order by the ID.
349 361 *
350 362 * @param int $order_id
@@ -351,8 +363,9 @@
351 363 *
352 364 * @return bool|string
353 365 */
354 366 function learn_press_get_order_status( $order_id ) {
367 +
355 368 $order = learn_press_get_order( $order_id );
356 369
357 370 if ( $order ) {
358 371 return $order->get_status();
@@ -360,8 +373,74 @@
360 373
361 374 return false;
362 375 }
363 376
377 +if ( ! function_exists( 'learn_press_get_refund_setting' ) ) {
378 + /**
379 + * Get refund setting value with safe defaults.
380 + *
381 + * @param string $key
382 + * @param mixed $default
383 + *
384 + * @since 4.3.4
385 + * @version 1.0.0
386 + * @return mixed
387 + */
388 + function learn_press_get_refund_setting( string $key, $default = null ) {
389 + $defaults = array(
390 + 'enable_refund_requests' => 'no',
391 + 'auto_refund' => 'no',
392 + 'refund_time_limit' => 30,
393 + 'require_refund_reason' => 'no',
394 + 'allow_resend_after_rejected' => 'no',
395 + 'refund_max_completion' => 0,
396 + );
397 +
398 + if ( null === $default && array_key_exists( $key, $defaults ) ) {
399 + $default = $defaults[ $key ];
400 + }
401 +
402 + return LP_Settings::get_option( $key, $default );
403 + }
404 +}
405 +
406 +if ( ! function_exists( 'learn_press_get_order_refund_supported_gateways' ) ) {
407 + /**
408 + * Get list gateways that support refund.
409 + *
410 + * @since 4.3.4
411 + * @version 1.1.0
412 + * @return array
413 + */
414 + function learn_press_get_order_refund_supported_gateways(): array {
415 + if ( ! class_exists( 'LP_Gateways' ) ) {
416 + return array();
417 + }
418 +
419 + $gateways_instance = LP_Gateways::instance();
420 + if ( ! $gateways_instance || ! method_exists( $gateways_instance, 'get_gateways' ) ) {
421 + return array();
422 + }
423 +
424 + $supported_gateways = array();
425 + $all_gateways = (array) $gateways_instance->get_gateways();
426 + foreach ( $all_gateways as $gateway_id => $gateway ) {
427 + $gateway_id = sanitize_key( (string) $gateway_id );
428 + if ( empty( $gateway_id ) ) {
429 + continue;
430 + }
431 +
432 + if ( ! is_object( $gateway ) || ! is_callable( array( $gateway, 'refund' ) ) ) {
433 + continue;
434 + }
435 +
436 + $supported_gateways[] = $gateway_id;
437 + }
438 +
439 + return array_values( array_unique( $supported_gateways ) );
440 + }
441 +}
442 +
364 443 if ( ! function_exists( 'learn_press_cancel_order_process' ) ) {
365 444 /**
366 445 * Process action allows user to cancel an order is pending
367 446 * in their profile.
@@ -366,8 +445,9 @@
366 445 * Process action allows user to cancel an order is pending
367 446 * in their profile.
368 447 */
369 448 function learn_press_cancel_order_process() {
449 +
370 450 if ( empty( $_REQUEST['cancel-order'] ) || empty( $_REQUEST['lp-nonce'] ) ||
371 451 ! wp_verify_nonce( $_REQUEST['lp-nonce'], 'cancel-order' ) || is_admin() ) {
372 452 return;
373 453 }
@@ -378,12 +458,12 @@
378 458 LP_Settings::instance()->get( 'profile_endpoints.orders', 'orders' )
379 459 );
380 460
381 461 try {
382 - $message = [
462 + $message = array(
383 463 'status' => 'error',
384 464 'content' => '',
385 - ];
465 + );
386 466
387 467 $order_id = absint( $_REQUEST['cancel-order'] );
388 468 $order = learn_press_get_order( $order_id );
389 469
@@ -403,10 +483,12 @@
403 483 $message['status'] = 'success';
404 484 $message['content'] = sprintf( __( 'Order number <strong>%s</strong> has been cancelled', 'learnpress' ), $order->get_order_number() );
405 485 } else {
406 486 throw new Exception(
407 - __( 'The order number <strong>%s</strong> can not be cancelled.', 'learnpress' ),
408 - $order->get_order_number()
487 + sprintf(
488 + __( 'The order number <strong>%s</strong> can not be cancelled.', 'learnpress' ),
489 + $order->get_order_number()
490 + )
409 491 );
410 492 }
411 493 } catch ( Throwable $e ) {
412 494 $message['content'] = $e->getMessage();
@@ -417,15 +499,155 @@
417 499 exit();
418 500 }
419 501 }
420 502 add_action( 'init', 'learn_press_cancel_order_process' );
503 +if ( ! function_exists( 'learn_press_get_order_refund_event_data' ) ) {
504 + /**
505 + * Build normalized refund event payload.
506 + *
507 + * @since 4.3.5
508 + * @version 1.0.1
509 + *
510 + * @param LP_Order $order
511 + * @param array $overrides
512 + *
513 + * @return array
514 + */
515 + function learn_press_get_order_refund_event_data( LP_Order $order, array $overrides = array() ): array {
516 + $order_id = $order->get_id();
517 + $requested_at = get_post_meta( $order_id, '_lp_refund_requested_at', true );
518 + if ( ! empty( $requested_at ) ) {
519 + $requested_at = new LP_Datetime( $requested_at );
520 + $requested_at = sprintf(
521 + esc_html__( '%1$s %2$s', 'learnpress' ),
522 + $requested_at->format( LP_Datetime::I18N_FORMAT_HAS_TIME ),
523 + LP_Datetime::get_timezone_string()
524 + );
525 + }
421 526
527 + $user_id = (int) $order->get_user_id();
528 + $userOrderModel = UserModel::find( $user_id, true );
529 + if ( $userOrderModel instanceof UserModel ) {
530 + $requested_by = $userOrderModel->get_display_name();
531 + } else {
532 + $requested_by = $order->get_checkout_email();
533 + }
422 534
535 + $data = array(
536 + 'order_id' => $order_id,
537 + 'order_number' => $order->get_order_number(),
538 + 'order_key' => $order->get_order_key(),
539 + 'order_status' => $order->get_status(),
540 + 'request_status' => $order->get_refund_request(),
541 + 'requested_by' => $requested_by,
542 + 'requested_at' => $requested_at,
543 + 'reviewed_by' => absint( get_post_meta( $order_id, '_lp_refund_reviewed_by', true ) ),
544 + 'reviewed_at' => (string) get_post_meta( $order_id, '_lp_refund_reviewed_at', true ),
545 + 'reason' => (string) get_post_meta( $order_id, LP_Order::META_KEY_REFUND_REQUEST_REASON, true ),
546 + 'requester_email' => '',
547 + 'admin_order_edit_url' => add_query_arg(
548 + array(
549 + 'post' => $order_id,
550 + 'action' => 'edit',
551 + ),
552 + admin_url( 'post.php' )
553 + ),
554 + );
555 +
556 + $requested_user_id = absint( $data['requested_by'] );
557 + if ( ! empty( $requested_user_id ) ) {
558 + $requested_user = get_user_by( 'id', $requested_user_id );
559 + if ( $requested_user instanceof WP_User ) {
560 + $data['requester_email'] = $requested_user->user_email;
561 + }
562 + }
563 +
564 + if ( ! empty( $overrides ) ) {
565 + $data = array_merge( $data, $overrides );
566 + }
567 +
568 + return $data;
569 + }
570 +}
423 571 /**
424 - * get total price order complete
572 + * Render pending refund request panel on order detail.
425 573 *
574 + * @since 4.3.4
575 + * @version 1.0.0
576 + * @param LP_Order $order
426 577 */
578 +function learn_press_admin_order_refund_request_panel( $order ) {
427 579
580 + if ( ! $order instanceof LP_Order ) {
581 + return;
582 + }
583 +
584 + $order_id = $order->get_id();
585 + if ( ! current_user_can( 'edit_post', $order_id ) ) {
586 + return;
587 + }
588 +
589 + $refund_event_data = learn_press_get_order_refund_event_data( $order );
590 + $refund_request_status = sanitize_key( (string) ( $refund_event_data['request_status'] ?? '' ) );
591 + $requested_by = $refund_event_data['requested_by'] ?? '';
592 + $requested_at = $refund_event_data['requested_at'] ?? '';
593 + $refund_reason = $refund_event_data['reason'] ?? '';
594 + $requester_email = '';
595 +
596 + $status_labels = array(
597 + 'pending' => __( 'Pending review', 'learnpress' ),
598 + 'approved' => __( 'Approved', 'learnpress' ),
599 + 'auto-approved' => __( 'Auto approved', 'learnpress' ),
600 + 'rejected' => __( 'Rejected', 'learnpress' ),
601 + );
602 + $status_label = $status_labels[ $refund_request_status ] ?? '';
603 + if ( empty( $status_label ) && ! empty( $refund_request_status ) ) {
604 + $status_label = ucwords( str_replace( '-', ' ', $refund_request_status ) );
605 + }
606 +
607 + $render_statuses = array( 'pending', 'approved', 'auto-approved' );
608 + if ( ! in_array( $refund_request_status, $render_statuses, true ) ) {
609 + return;
610 + }
611 +
612 + $is_pending = ( 'pending' === $refund_request_status );
613 + $currency_symbol = learn_press_get_currency_symbol( $order->get_currency() );
614 +
615 + $order_total = round( max( 0, floatval( $order->get_total() ) ), 2 );
616 + $order_total_formatted = learn_press_format_price( $order_total, $currency_symbol );
617 +
618 + // Refund amount is only meaningful once the refund has been executed
619 + // (approved/auto-approved), when _lp_refund_amount has been written.
620 + $refund_amount_formatted = '';
621 + if ( ! $is_pending ) {
622 + $refund_amount = get_post_meta( $order_id, LP_Order::META_KEY_REFUNDED_AMOUNT, true );
623 + if ( '' === $refund_amount || null === $refund_amount ) {
624 + $refund_amount = $order_total; // Fallback for full refunds without a stored amount.
625 + }
626 + $refund_amount = round( max( 0, floatval( $refund_amount ) ), 2 );
627 + $refund_amount_formatted = learn_press_format_price( $refund_amount, $currency_symbol );
628 + }
629 +
630 + learn_press_admin_view(
631 + 'meta-boxes/order/refund-request-panel',
632 + compact(
633 + 'order_id',
634 + 'is_pending',
635 + 'order_total',
636 + 'order_total_formatted',
637 + 'refund_amount_formatted',
638 + 'requested_by',
639 + 'requested_at',
640 + 'requester_email',
641 + 'refund_reason',
642 + 'status_label'
643 + )
644 + );
645 +}
646 +
647 +/**
648 + * get total price order complete
649 + */
428 650 function learn_press_get_total_price_order_complete() {
429 651 global $wpdb;
430 652
431 653 $query = $wpdb->prepare(