From 34f369a438eaf28d0f90cb230bda4f1b164e08f7 Mon Sep 17 00:00:00 2001 From: Syed Galib Ahmed Date: Thu, 27 Aug 2026 12:14:52 +0600 Subject: [PATCH 1/6] fix(featured-listing): persist order expiration before early return --- includes/classes/class-featured-listing-checkout.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/includes/classes/class-featured-listing-checkout.php b/includes/classes/class-featured-listing-checkout.php index 9110b22653..07cb3329e5 100644 --- a/includes/classes/class-featured-listing-checkout.php +++ b/includes/classes/class-featured-listing-checkout.php @@ -173,6 +173,10 @@ private function updated_listing_expiration( $listing, OrderDTO $order ) { ? date_create_from_format( 'Y-m-d H:i:s', $listing_expiration, wp_timezone() ) : false; + + $order->set_expires_at( $order_expiration ); + directorist_order_repository()->silent_update( $order ); + if ( $listing_expiration_date && $listing_expiration === $listing_expiration_date->format( 'Y-m-d H:i:s' ) && $listing_expiration_date->getTimestamp() >= $order_expiration->getTimestamp() @@ -180,9 +184,6 @@ private function updated_listing_expiration( $listing, OrderDTO $order ) { return; } - $order->set_expires_at( $order_expiration ); - directorist_order_repository()->silent_update( $order ); - update_post_meta( $listing->ID, '_expiry_date', $order_expiration->format( 'Y-m-d H:i:s' ) ); } From 33f08652ae0107afa54013003ad29eabdce214ac Mon Sep 17 00:00:00 2001 From: Syed Galib Ahmed Date: Thu, 27 Aug 2026 12:47:39 +0600 Subject: [PATCH 2/6] fix(payment): resolve prepaid and featured expiry handling --- .../class-featured-listing-checkout.php | 27 ++++++++++------ .../Version1/class-orders-controller.php | 32 +++++++++++++++++-- 2 files changed, 47 insertions(+), 12 deletions(-) diff --git a/includes/classes/class-featured-listing-checkout.php b/includes/classes/class-featured-listing-checkout.php index 07cb3329e5..691567601d 100644 --- a/includes/classes/class-featured-listing-checkout.php +++ b/includes/classes/class-featured-listing-checkout.php @@ -143,13 +143,15 @@ public function handle_after_order_update( OrderDTO $dto ) { } if ( Status::PAID === $dto->get_status() ) { + $order_expiration = $this->refresh_order_expiration( $dto ); + directorist_set_listing_featured( $dto->get_listing_id(), true ); // Publish the listing if it's pending $listing = get_post( $dto->get_listing_id() ); if ( $listing ) { - $this->updated_listing_expiration( $listing, $dto ); + $this->update_listing_expiration( $listing, $order_expiration ); } if ( $listing && 'publish' !== $listing->post_status ) { @@ -160,23 +162,30 @@ public function handle_after_order_update( OrderDTO $dto ) { } } - private function updated_listing_expiration( $listing, OrderDTO $order ) { + private function refresh_order_expiration( OrderDTO $order ) { + $featured_days = (int) get_directorist_option( 'featured_listing_time', 30 ); + $order_expiration = directorist_now()->add_days( $featured_days ); + + $order->set_expires_at( $order_expiration ); + $expiration_update = ( new OrderDTO() ) + ->set_id( $order->get_id() ) + ->set_expires_at( $order_expiration ); + + directorist_order_repository()->silent_update( $expiration_update ); + + return $order_expiration; + } + + private function update_listing_expiration( $listing, $order_expiration ) { if ( get_post_meta( $listing->ID, '_never_expire', true ) ) { return; } - $featured_days = (int) get_directorist_option( 'featured_listing_time', 30 ); - $order_expiration = directorist_now()->add_days( $featured_days ); - $listing_expiration = get_post_meta( $listing->ID, '_expiry_date', true ); $listing_expiration_date = $listing_expiration ? date_create_from_format( 'Y-m-d H:i:s', $listing_expiration, wp_timezone() ) : false; - - $order->set_expires_at( $order_expiration ); - directorist_order_repository()->silent_update( $order ); - if ( $listing_expiration_date && $listing_expiration === $listing_expiration_date->format( 'Y-m-d H:i:s' ) && $listing_expiration_date->getTimestamp() >= $order_expiration->getTimestamp() diff --git a/includes/rest-api/Version1/class-orders-controller.php b/includes/rest-api/Version1/class-orders-controller.php index afbea5c885..2b805adc77 100644 --- a/includes/rest-api/Version1/class-orders-controller.php +++ b/includes/rest-api/Version1/class-orders-controller.php @@ -12,6 +12,7 @@ use Directorist\DTO\Order\DTO as OrderDTO; use Directorist\DTO\Payment\DTO as PaymentDTO; use Directorist\Enums\Order\Status as OrderStatus; +use Directorist\Enums\Payment\Status as PaymentStatus; use WP_Error; use WP_REST_Request; use WP_REST_Server; @@ -140,7 +141,8 @@ public function create_item( $request ) { $dto = apply_filters( 'directorist_rest_legacy_order_create_dto', $dto, $request ); - $order_id = directorist_order_repository()->create( $dto ); + $order_repository = directorist_order_repository(); + $order_id = $order_repository->create( $dto ); if ( ! $order_id ) { return new WP_Error( 'invalid_order', __( 'Unable to create order.', 'directorist' ), array( 'status' => 400 ) ); @@ -153,11 +155,21 @@ public function create_item( $request ) { ->set_order_id( $order_id ) ->set_amount( $amount ) ->set_currency( $currency ) - ->set_status( $this->legacy_status_to_current_status( $legacy_status ) ) + ->set_status( $this->legacy_payment_status_to_current_status( $legacy_status ) ) ->set_method( $gateway ) ->set_transaction_id( $transaction_id ? $transaction_id : null ); - directorist_payment_repository()->create( apply_filters( 'directorist_rest_legacy_order_create_payment_dto', $payment_dto, $request, $dto ) ); + $payment_dto = apply_filters( 'directorist_rest_legacy_order_create_payment_dto', $payment_dto, $request, $dto ); + $payment_repository = directorist_payment_repository(); + $payment_id = $payment_repository->create( $payment_dto ); + $payment = $payment_repository->get_last_payment( $order_id ); + + if ( ! $payment_id || ! $payment || $payment->status !== $payment_dto->get_status() ) { + $payment_repository->delete_by( 'order_id', $order_id ); + $order_repository->delete_by_id( $order_id ); + + return new WP_Error( 'rest_payment_create_failed', __( 'Unable to create payment.', 'directorist' ), array( 'status' => 500 ) ); + } } do_action( 'atbdp_order_created', $order_id, $listing_id ); @@ -315,6 +327,20 @@ protected function legacy_status_to_current_status( string $status ): string { return $map[ $status ] ?? OrderStatus::PENDING; } + protected function legacy_payment_status_to_current_status( string $status ): string { + $map = array( + 'created' => PaymentStatus::PENDING, + 'pending' => PaymentStatus::PENDING, + 'prepaid' => PaymentStatus::PAID, + 'completed' => PaymentStatus::PAID, + 'failed' => PaymentStatus::FAILED, + 'cancelled' => PaymentStatus::CANCELLED, + 'refunded' => PaymentStatus::REFUNDED, + ); + + return $map[ $status ] ?? PaymentStatus::PENDING; + } + protected function current_status_to_legacy_status( string $status ): string { $map = array( OrderStatus::PENDING => 'pending', From 0e9bbf13c41f5877b25ce0215705823df8d2c907 Mon Sep 17 00:00:00 2001 From: Syed Galib Ahmed Date: Thu, 27 Aug 2026 15:08:34 +0600 Subject: [PATCH 3/6] fix(payment): preserve prepaid order lifecycle --- includes/repositories/payment-repository.php | 26 ++++++++++++++++--- .../Version1/class-orders-controller.php | 13 +++++++--- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/includes/repositories/payment-repository.php b/includes/repositories/payment-repository.php index d669148943..0daaf82e46 100644 --- a/includes/repositories/payment-repository.php +++ b/includes/repositories/payment-repository.php @@ -9,6 +9,7 @@ use Directorist\Utils\Database\Query\Builder; use Directorist\DTO\Payment\DTO; use Directorist\Enums\Order\Status as OrderStatus; +use Directorist\Enums\Payment\Status as PaymentStatus; use Directorist\DBModels\Payment; class PaymentRepository extends Repository { @@ -33,15 +34,34 @@ public function get( $order_id ) { * @return int */ public function create( \Directorist\Utils\DTO $dto ) { - $payment_id = parent::create( $dto ); + $payment_id = $this->create_without_order_update( $dto ); + $payment = $payment_id ? $this->get_last_payment( $dto->get_order_id() ) : null; + + if ( ! $payment || (int) $payment->id !== $payment_id || $payment->status !== $dto->get_status() ) { + return 0; + } - if ( $dto->is_initialized( 'status' ) && $dto->get_status() === OrderStatus::PAID ) { + if ( $dto->is_initialized( 'status' ) && $dto->get_status() === PaymentStatus::PAID ) { $this->order_repository->update_status( $dto->get_order_id(), OrderStatus::PAID ); } return $payment_id; } + /** + * Create a payment without synchronizing the related order status. + * + * @param \Directorist\Utils\DTO $dto Payment data. + * @return int Payment ID, or zero when the insert fails. + */ + public function create_without_order_update( \Directorist\Utils\DTO $dto ) { + global $wpdb; + + $payment_id = parent::create( $dto ); + + return $wpdb->last_error ? 0 : $payment_id; + } + public function update_status_by_order_id( int $order_id, string $status ): int { return $this->get_query_builder() ->where( 'order_id', $order_id ) @@ -69,4 +89,4 @@ public function to_dto( $payment ) { return $dto; } -} \ No newline at end of file +} diff --git a/includes/rest-api/Version1/class-orders-controller.php b/includes/rest-api/Version1/class-orders-controller.php index 2b805adc77..054940c981 100644 --- a/includes/rest-api/Version1/class-orders-controller.php +++ b/includes/rest-api/Version1/class-orders-controller.php @@ -161,15 +161,19 @@ public function create_item( $request ) { $payment_dto = apply_filters( 'directorist_rest_legacy_order_create_payment_dto', $payment_dto, $request, $dto ); $payment_repository = directorist_payment_repository(); - $payment_id = $payment_repository->create( $payment_dto ); + $payment_id = $payment_repository->create_without_order_update( $payment_dto ); $payment = $payment_repository->get_last_payment( $order_id ); - if ( ! $payment_id || ! $payment || $payment->status !== $payment_dto->get_status() ) { + if ( ! $payment_id || ! $payment || (int) $payment->id !== $payment_id || $payment->status !== $payment_dto->get_status() ) { $payment_repository->delete_by( 'order_id', $order_id ); $order_repository->delete_by_id( $order_id ); return new WP_Error( 'rest_payment_create_failed', __( 'Unable to create payment.', 'directorist' ), array( 'status' => 500 ) ); } + + if ( OrderStatus::PAID === $dto->get_status() ) { + $order_repository->update_status( $order_id, OrderStatus::PAID ); + } } do_action( 'atbdp_order_created', $order_id, $listing_id ); @@ -270,6 +274,9 @@ protected function get_order( int $order_id ) { protected function prepare_legacy_order_data( $order, WP_REST_Request $request ): array { $payment = ! empty( $order->payment ) ? $order->payment : null; $plan_id = $this->get_plan_id( $order ); + $status = OrderStatus::PREPAID === ( $order->status ?? '' ) + ? $order->status + : ( $payment->status ?? $order->status ?? '' ); $data = array( 'id' => (int) $order->id, @@ -285,7 +292,7 @@ protected function prepare_legacy_order_data( $order, WP_REST_Request $request ) 'remaining_featured_listings' => 0, 'amount' => round( (float) ( $order->amount ?? 0 ), 2 ), 'currency' => (string) ( $order->currency ?? '' ), - 'payment_status' => $this->current_status_to_legacy_status( $payment->status ?? $order->status ?? '' ), + 'payment_status' => $this->current_status_to_legacy_status( $status ), 'payment_gateway' => (string) ( $payment->method ?? '' ), 'transaction_id' => (string) ( $payment->transaction_id ?? '' ), 'created_by' => 'web', From 3bc8e8a6e09c569aec42d8809adf7f5f6cd10b4b Mon Sep 17 00:00:00 2001 From: Syed Galib Ahmed Date: Thu, 27 Aug 2026 15:26:24 +0600 Subject: [PATCH 4/6] fix(payment): preserve entitlements and inserted payments --- .../class-featured-listing-checkout.php | 2 +- includes/repositories/order-repository.php | 29 +++++++++++++++++++ includes/repositories/payment-repository.php | 2 +- .../Version1/class-orders-controller.php | 2 +- 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/includes/classes/class-featured-listing-checkout.php b/includes/classes/class-featured-listing-checkout.php index 691567601d..cd52ce17f0 100644 --- a/includes/classes/class-featured-listing-checkout.php +++ b/includes/classes/class-featured-listing-checkout.php @@ -157,7 +157,7 @@ public function handle_after_order_update( OrderDTO $dto ) { if ( $listing && 'publish' !== $listing->post_status ) { directorist_set_listing_status( $dto->get_listing_id(), 'publish' ); } - } else { + } elseif ( ! directorist_order_repository()->listing_has_active_featured_entitlement( $dto->get_listing_id() ) ) { directorist_set_listing_featured( $dto->get_listing_id(), false ); } } diff --git a/includes/repositories/order-repository.php b/includes/repositories/order-repository.php index 8b8d453c12..c46779507b 100644 --- a/includes/repositories/order-repository.php +++ b/includes/repositories/order-repository.php @@ -89,6 +89,35 @@ public function listing_has_paid_featured_order( int $listing_id ): bool { return $order ? true : false; } + public function listing_has_active_featured_entitlement( int $listing_id ): bool { + $orders = $this->get_query_builder() + ->select( 'd_order.ref_type', 'd_order.created_at', 'd_order.expires_at' ) + ->where( 'd_order.listing_id', $listing_id ) + ->where( 'd_order.is_featured_listing', 1 ) + ->where( 'd_order.status', OrderStatus::PAID ) + ->get(); + + $current_time = directorist_now()->getTimestamp(); + $featured_days = (int) get_directorist_option( 'featured_listing_time', 30 ); + $is_active = false; + + foreach ( $orders as $order ) { + if ( ! empty( $order->expires_at ) ) { + $is_active = ( new DateTime( $order->expires_at ) )->getTimestamp() > $current_time; + } elseif ( 'featured_listing' === $order->ref_type ) { + $is_active = ( new DateTime( $order->created_at ) )->add_days( $featured_days )->getTimestamp() > $current_time; + } else { + $is_active = true; + } + + if ( $is_active ) { + break; + } + } + + return (bool) apply_filters( 'directorist_listing_has_active_featured_entitlement', $is_active, $listing_id, $orders ); + } + public function get_latest_paid_featured_order_by_listing_id( int $listing_id ) { return $this->get_query_builder() ->select( 'd_order.created_at', 'd_order.expires_at' ) diff --git a/includes/repositories/payment-repository.php b/includes/repositories/payment-repository.php index 0daaf82e46..bb87f503ad 100644 --- a/includes/repositories/payment-repository.php +++ b/includes/repositories/payment-repository.php @@ -35,7 +35,7 @@ public function get( $order_id ) { */ public function create( \Directorist\Utils\DTO $dto ) { $payment_id = $this->create_without_order_update( $dto ); - $payment = $payment_id ? $this->get_last_payment( $dto->get_order_id() ) : null; + $payment = $payment_id ? $this->get_by_id( $payment_id ) : null; if ( ! $payment || (int) $payment->id !== $payment_id || $payment->status !== $dto->get_status() ) { return 0; diff --git a/includes/rest-api/Version1/class-orders-controller.php b/includes/rest-api/Version1/class-orders-controller.php index 054940c981..f2f55bf2c4 100644 --- a/includes/rest-api/Version1/class-orders-controller.php +++ b/includes/rest-api/Version1/class-orders-controller.php @@ -162,7 +162,7 @@ public function create_item( $request ) { $payment_dto = apply_filters( 'directorist_rest_legacy_order_create_payment_dto', $payment_dto, $request, $dto ); $payment_repository = directorist_payment_repository(); $payment_id = $payment_repository->create_without_order_update( $payment_dto ); - $payment = $payment_repository->get_last_payment( $order_id ); + $payment = $payment_id ? $payment_repository->get_by_id( $payment_id ) : null; if ( ! $payment_id || ! $payment || (int) $payment->id !== $payment_id || $payment->status !== $payment_dto->get_status() ) { $payment_repository->delete_by( 'order_id', $order_id ); From 496d25e396f7b9f9f5d8d4672ae91e078fd6a988 Mon Sep 17 00:00:00 2001 From: Syed Galib Ahmed Date: Thu, 27 Aug 2026 15:43:16 +0600 Subject: [PATCH 5/6] fix(featured-listing): preserve entitlement expiration --- includes/classes/class-cron.php | 19 ++++++------------- .../class-featured-listing-checkout.php | 14 ++++++++++---- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/includes/classes/class-cron.php b/includes/classes/class-cron.php index e1f84d1597..282db94048 100644 --- a/includes/classes/class-cron.php +++ b/includes/classes/class-cron.php @@ -103,9 +103,7 @@ private function featured_listing_followup() { } if ( directorist_is_monetization_enabled() && directorist_is_featured_listing_enabled() ) { - $featured_days = (int) get_directorist_option( 'featured_listing_time', 30 ); - $orders = directorist_order_repository(); - $current_time = directorist_now()->getTimestamp(); + $orders = directorist_order_repository(); // Define the query $args = [ 'post_type' => ATBDP_POST_TYPE, @@ -126,20 +124,15 @@ private function featured_listing_followup() { // Start the Loop if ( $listings->found_posts ) { foreach ( $listings->posts as $listing ) { - $order = $orders->get_latest_paid_featured_order_by_listing_id( $listing->ID ); + $has_paid_order = $orders->listing_has_paid_featured_order( $listing->ID ); + $has_active_entitlement = $orders->listing_has_active_featured_entitlement( $listing->ID ); - if ( ! $order ) { + if ( ! $has_paid_order || $has_active_entitlement ) { continue; } - $expiration = ! empty( $order->expires_at ) - ? new \Directorist\Helpers\DateTime( $order->expires_at ) - : ( new \Directorist\Helpers\DateTime( $order->created_at ) )->add_days( $featured_days ); - - if ( $expiration->getTimestamp() <= $current_time ) { - do_action( 'atbdp_listing_featured_to_general', $listing->ID ); - update_post_meta( $listing->ID, '_featured', '' ); - } + do_action( 'atbdp_listing_featured_to_general', $listing->ID ); + update_post_meta( $listing->ID, '_featured', '' ); } } } diff --git a/includes/classes/class-featured-listing-checkout.php b/includes/classes/class-featured-listing-checkout.php index cd52ce17f0..6e7b77048c 100644 --- a/includes/classes/class-featured-listing-checkout.php +++ b/includes/classes/class-featured-listing-checkout.php @@ -26,7 +26,7 @@ public function register_hooks() { } add_filter( 'directorist_checkout_types', [$this, 'add_checkout_type'] ); - add_action( 'directorist_after_order_update', [$this, 'handle_after_order_update'] ); + add_action( 'directorist_after_order_update', [$this, 'handle_after_order_update'], 10, 2 ); add_filter( 'directorist_checkout_validation', [$this, 'validate_checkout'], 10, 2 ); add_action( 'directorist_checkout_table', [$this, 'handle_checkout_table'], 10, 4 ); add_filter( 'directorist_checkout_subtotal', [$this, 'handle_checkout_subtotal'], 10, 3 ); @@ -133,7 +133,7 @@ public function handle_checkout_create_order( OrderDTO $dto, string $checkout_ty $dto->set_listing_id( $request->get_param( 'listing_id' ) )->set_is_featured_listing( 1 )->set_ref_type( self::CHECKOUT_TYPE )->set_amount( $amount )->set_sub_total( $amount ); } - public function handle_after_order_update( OrderDTO $dto ) { + public function handle_after_order_update( OrderDTO $dto, $old_order = null ) { if ( ! $this->is_featured_order( $dto ) ) { return; } @@ -143,14 +143,16 @@ public function handle_after_order_update( OrderDTO $dto ) { } if ( Status::PAID === $dto->get_status() ) { - $order_expiration = $this->refresh_order_expiration( $dto ); + $order_expiration = $this->is_paid_transition( $old_order ) + ? $this->refresh_order_expiration( $dto ) + : null; directorist_set_listing_featured( $dto->get_listing_id(), true ); // Publish the listing if it's pending $listing = get_post( $dto->get_listing_id() ); - if ( $listing ) { + if ( $listing && $order_expiration ) { $this->update_listing_expiration( $listing, $order_expiration ); } @@ -162,6 +164,10 @@ public function handle_after_order_update( OrderDTO $dto ) { } } + private function is_paid_transition( $old_order ): bool { + return ! $old_order || ! isset( $old_order->status ) || Status::PAID !== $old_order->status; + } + private function refresh_order_expiration( OrderDTO $order ) { $featured_days = (int) get_directorist_option( 'featured_listing_time', 30 ); $order_expiration = directorist_now()->add_days( $featured_days ); From 46e6422c0ee2bb3550747fbf11df185d15631947 Mon Sep 17 00:00:00 2001 From: Syed Galib Ahmed Date: Thu, 27 Aug 2026 15:56:41 +0600 Subject: [PATCH 6/6] fix(featured-listing): initialize missing paid expiry --- includes/classes/class-featured-listing-checkout.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/includes/classes/class-featured-listing-checkout.php b/includes/classes/class-featured-listing-checkout.php index 6e7b77048c..c1ea7b3007 100644 --- a/includes/classes/class-featured-listing-checkout.php +++ b/includes/classes/class-featured-listing-checkout.php @@ -143,7 +143,7 @@ public function handle_after_order_update( OrderDTO $dto, $old_order = null ) { } if ( Status::PAID === $dto->get_status() ) { - $order_expiration = $this->is_paid_transition( $old_order ) + $order_expiration = $this->should_refresh_order_expiration( $dto, $old_order ) ? $this->refresh_order_expiration( $dto ) : null; @@ -168,6 +168,10 @@ private function is_paid_transition( $old_order ): bool { return ! $old_order || ! isset( $old_order->status ) || Status::PAID !== $old_order->status; } + private function should_refresh_order_expiration( OrderDTO $order, $old_order ): bool { + return $this->is_paid_transition( $old_order ) || ! $order->is_initialized( 'expires_at' ); + } + private function refresh_order_expiration( OrderDTO $order ) { $featured_days = (int) get_directorist_option( 'featured_listing_time', 30 ); $order_expiration = directorist_now()->add_days( $featured_days );