namespace, $this->base, array( array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'create_item' ), 'permission_callback' => array( $this, 'create_order_permission', ), ), array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permission', ), 'args' => array( 'page' => array( 'default' => 1 ), 'per_page' => array( 'default' => 10 ), ), ), array( 'methods' => WP_REST_Server::DELETABLE, 'callback' => array( $this, 'bulk_delete_items', ), 'permission_callback' => array( $this, 'bulk_delete_items_permission', ), ), ) ); register_rest_route( $this->namespace, $this->base . '/(?P[\d]+)', array( array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => array( $this, 'update_item' ), 'permission_callback' => array( $this, 'update_item_permission', ), 'args' => array( 'id' => array( 'required' => true ), 'status' => array( 'type' => 'string' ), ), ), array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), 'permission_callback' => array( $this, 'get_item_permission', ), 'args' => array( 'id' => array( 'required' => true ), ), ), array( 'methods' => WP_REST_Server::DELETABLE, 'callback' => array( $this, 'delete_item' ), 'permission_callback' => array( $this, 'delete_item_permission', ), 'args' => array( 'id' => array( 'required' => true ), ), ), ) ); register_rest_route( $this->namespace, $this->base . '/(?P[\d]+)/payment', array( array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => array( $this, 'update_payment_status', ), 'permission_callback' => array( $this, 'update_payment_status_permission', ), ), ) ); register_rest_route( $this->namespace, $this->base . '/(?P[\d]+)/refund', array( array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => array( $this, 'refund_payment' ), 'permission_callback' => array( $this, 'update_payment_status_permission', ), ), ) ); } /** * Check if the current user has permission to create orders * * @param WP_REST_Request $request * * @return bool */ public function create_order_permission( $request ) { return true; } /** * Prepare and validate order data before creation * * @param array $data * * @return array|WP_Error */ private function prepare_item_for_order_create( array $data ) { if ( ! isset( $data['timezone'] ) || ! Time_Zone_Handler::validate_time_zone( $data['timezone'] ) ) { $data['timezone'] = 'UTC'; } if ( isset( $data['pay_now'] ) && $data['pay_now'] ) { $validate = booktics_validate( $data, array( 'payment_method' => array( 'required' ), ) ); if ( is_wp_error( $validate ) ) { return $validate; } } $error = ( new \WP_Error() ); if ( ! is_array( $data['items'] ) || count( $data['items'] ) == 0 ) { $error->add( 'items', __( 'Items are required', 'booktics' ) ); return $error; } foreach ( $data['items'] as $key => $item ) { $item_validate = booktics_validate( $item, array( 'date' => array( 'required' ), 'start_time' => array( 'required' ), 'end_time' => array( 'required' ), 'team_member_id' => array( 'required' ), 'service_id' => array( 'required' ), ) ); if ( is_wp_error( $item_validate ) ) { return $item_validate; } $converted_start_time = Time_Zone_Handler::convert_to_utc( $item['start_time'], $item['date'], $data['timezone'] ); $converted_end_time = Time_Zone_Handler::convert_to_utc( $item['end_time'], $item['date'], $data['timezone'] ); $item['start_time'] = $converted_start_time['time']; $item['end_time'] = $converted_end_time['time']; $start_time = strtotime( $item['start_time'] ); $end_time = strtotime( $item['end_time'] ); if ( ! $start_time || ! $end_time || $start_time >= $end_time ) { $error->add( 'time_invalid', __( 'Invalid time range provided', 'booktics' ) ); return $error; } $data['items'][ $key ]['start_time'] = $converted_start_time['time']; $data['items'][ $key ]['end_time'] = $converted_end_time['time']; $data['items'][ $key ]['date'] = gmdate( 'Y-m-d', strtotime( $converted_start_time['date'] ) ); $data['items'][ $key ]['customer_id'] = absint( $item['customer_id'] ); $data['items'][ $key ]['team_member_id'] = absint( $item['team_member_id'] ); $data['items'][ $key ]['service_id'] = absint( $item['service_id'] ); $data['items'][ $key ]['additional_duration_id'] = $item['additional_duration_id']; $data['items'][ $key ]['price'] = floatval( $item['price'] ); $data['items'][ $key ]['subtotal'] = floatval( $item['subtotal'] ); $data['items'][ $key ]['quantity'] = absint( $item['quantity'] ?? 1 ); $data['items'][ $key ]['group_booking'] = $item['group_booking'] ?? array(); } return $data; } /** * Create a new order with the provided data * * @param WP_REST_Request $request * * @return WP_Error|WP_HTTP_Response * @throws Exception */ public function create_item( $request ) { $input_data = json_decode( $request->get_body(), true ); $validator = new Order_Validator(); $validation_result = $validator->validate( $input_data ); if ( is_wp_error( $validation_result ) ) { return $this->error( __( 'Order validation failed', 'booktics' ), 422, 'Validation', $validation_result->get_error_messages() ); } $db = new Booktics_Database(); $validation = $this->prepare_item_for_order_create( $input_data ); if ( is_wp_error( $validation ) ) { return $this->error( $validation->get_error_message() ); } if ( ! isset( $input_data['customer_id'] ) ) { $customer_id = $this->find_or_create_customer( $input_data ); if ( is_wp_error( $customer_id ) ) { return $this->error( $customer_id->get_error_message() ); } $input_data['customer_id'] = $customer_id; } try { $db->begin_transaction(); // Order creation logic $input_data['currency'] = $input_data['currency'] ?? 'usd'; $order_model = new Order_Model( $db ); $order_model = $order_model->create( array( 'customer_id' => $input_data['customer_id'], 'payment_status' => 'pending', 'price_breakdown' => $input_data['price_breakdown'] ?? array(), 'tax_total' => $input_data['tax_total'] ?? 0, 'coupon_code' => $input_data['coupon_code'] ?? '', 'coupon_discount' => $input_data['coupon_discount'] ?? 0, 'subtotal' => $input_data['subtotal'], 'timezone' => $input_data['timezone'] ?? 'UTC', 'total' => $input_data['total'], 'payment_method' => $input_data['payment_method'] ?? null, ) ); foreach ( $input_data['items'] as $item ) { if ( isset( $item['uuid'] ) ) { ( new Cart_Manager() )->remove_cart_item( $item['uuid'] ); } $total_duration_min = $this->calculate_total_duration( $item ); $appointment_data = array( 'start_time' => $item['start_time'] ?? '', 'end_time' => $item['end_time'] ?? '', 'date' => gmdate( 'Y-m-d', strtotime( $item['date'] ) ), 'customer_id' => $input_data['customer_id'] ?? '', 'team_member_id' => $item['team_member_id'] ?? '', 'service_id' => $item['service_id'] ?? '', 'additional_duration_id' => $item['additional_duration_id'] ?? '', 'duration' => $total_duration_min, 'order_id' => $order_model->id, 'quantity' => $item['quantity'] ?? 1, 'price' => $item['price'] ?? 0, 'subtotal' => $item['subtotal'] ?? 0, 'total' => $item['total'] ?? 0, 'status' => 'pending', ); $appointment_data = apply_filters( 'booktics_appointment_pro_format', $appointment_data, $item ); Appointment_Model::create( $appointment_data ); } if ( $input_data['pay_now'] ) { $payment_method = ( new Payment_Method_Manager() )->get_payment_method( $input_data['payment_method'] ); if ( $payment_method ) { $payment_intent = $payment_method->process_payment( $order_model ); if ( $payment_intent['status'] === 'error' ) { return $this->error( $payment_intent['message'] ); } if ( $payment_intent['status'] === 'success' ) { ( new Order_Model( $db ) )->update( $order_model->id, array( 'payment_intent_id' => $payment_intent['payment_intent'], ) ); ( new Payment_Model( $db ) )->create( array( 'order_id' => $order_model->id, 'customer_id' => $input_data['customer_id'], 'amount' => $input_data['total'] * 100, 'currency' => $input_data['currency'], 'intent_id' => $payment_intent['payment_intent'], 'transaction_id' => null, 'payment_method' => $input_data['payment_method'], 'status' => 'pending', 'date' => gmdate( 'Y-m-d' ), 'date_time' => gmdate( 'Y-m-d H:i:s' ), ) ); } } else { return $this->error( __( 'Payment method can not found!', 'booktics' ) ); } } $db->commit(); } catch ( Exception $e ) { $db->roll_back(); return $this->error( $e->getMessage() ); } do_action( 'booktics_order_created', $order_model ); $customer = ( new Customer_Model() )->find( $input_data['customer_id'] ); $appointments = ( new Appointment_Model() )->where( 'order_id', $order_model->id ); $appointment_details = array(); foreach ( $appointments as $appointment ) { $appointment = $appointment->to_array(); $team_member = ( new Team_Member_Model() )->find( $appointment['team_member_id'] ); $service = ( new Service_Model() )->find( $appointment['service_id'] ); $service = $service ? $service->to_array() : array(); $additional_durations = $service['additional_durations'] ?? array(); $additional_durations = ! empty( $additional_durations ) ? array_filter( $additional_durations, function ( $duration ) use ( $appointment ) { return $duration['id'] === $appointment['additional_duration_id']; } ) : array(); $additional_duration = ! empty( $additional_durations ) ? reset( $additional_durations ) : array(); $formatted_appointment = array( 'id' => $appointment['id'], 'date' => $appointment['date'], 'start_time' => $appointment['start_time'], 'end_time' => $appointment['end_time'], 'additional_duration_id' => $appointment['additional_duration_id'], 'team' => array( 'id' => $team_member->id, 'display_name' => $team_member->display_name ?? '', 'image' => $team_member->image ?? '', ), 'service' => array( 'id' => $appointment['service_id'], 'title' => $service['title'] ?? '', 'additional_duration' => $additional_duration, ), 'duration' => $appointment['duration'], 'quantity' => $appointment['quantity'], 'price' => $appointment['price'], 'subtotal' => $appointment['subtotal'], 'total' => $appointment['total'], 'status' => $appointment['status'], ); $formatted_appointment = apply_filters( 'booktics_appointment_pro_format', $formatted_appointment, $appointment ); $appointment_details[] = $formatted_appointment; $appointment_time = $appointment['date'] . ' ' . $appointment['start_time']; $dt = new DateTime( $appointment_time, wp_timezone() ); $timestamp = $dt->getTimestamp(); do_action( 'global_notification_hook', 'appointment_scheduled', array( 'appointment_service' => $service['title'] ?? '', 'appointment_team_member' => $team_member->display_name ?? '', 'start_time' => $appointment['start_time'], 'appointment_date' => $appointment['date'], 'appointment_time' => $timestamp, 'attendee_count' => $appointment['attendee_count'], 'user_email' => $customer->user_email ?? '', ) ); } do_action( 'global_notification_hook', 'order_created', array( 'order_no' => $order_model->order_no, 'order_date' => booktics_datetime( 'M j, Y, g:ia', time(), 'UTC' ), 'order_total' => $input_data['total'], 'user_email' => $customer->user_email, ) ); $response = array( 'id' => $order_model->id, 'order_no' => $order_model->order_no, 'customer_email' => $customer->user_email, 'subtotal' => $order_model->subtotal, 'total' => $order_model->total, 'payment_status' => $order_model->payment_status, 'items' => $appointment_details, 'payment_intent_id' => $payment_intent['payment_intent'] ?? '', 'client_secret' => $payment_intent['client_secret'] ?? '', ); return $this->response( $response, __( 'Order created successfully', 'booktics' ), 201 ); } /** * Find existing customer by email or create new customer * * @param array $data * * @return int|WP_Error */ private function find_or_create_customer( array $data ) { $customer = get_user_by( 'email', $data['customer_email'] ); if ( $customer ) { return $customer->ID; } $userdata = array( 'user_login' => User_Model::generate_username( $data['customer_email'] ), 'user_email' => $data['customer_email'], 'user_pass' => wp_generate_password(), 'first_name' => $data['customer_name'] ?? '', 'display_name' => $data['customer_name'] ?? '', 'phone' => $data['customer_phone'] ?? '', ); $customer = new Customer_Model(); return $customer->save( $userdata ); } /** * Check if user has permission to get orders * * @param WP_REST_Request $request * * @return bool */ public function get_items_permission( $request ) { return true; } /** * Get paginated list of orders * * @param WP_REST_Request $request * * @return WP_Error|WP_HTTP_Response */ public function get_items( $request ) { $page = (int) $request->get_param( 'paged' ) == 0 ? 1 : $request->get_param( 'paged' ); $per_page = (int) $request->get_param( 'per_page' ) == 0 ? 10 : $request->get_param( 'per_page' ); $payment_status = $request->get_param( 'payment_status' ); $payment_method = $request->get_param( 'payment_method' ); $order_date = $request->get_param( 'order_date' ); $team_member = (int) $request->get_param( 'team_member' ); $service = (int) $request->get_param( 'service' ); $search = $request->get_param( 'search' ); $db = new Booktics_Database(); $orderModel = new Order_Model( $db ); $conditions = array(); if ( $payment_status ) { $conditions['payment_status'] = $payment_status; } if ( $payment_method ) { $conditions['payment_method'] = $payment_method; } if ( $order_date ) { $conditions['DATE(created_at)'] = array( '=', $order_date ); } if ( $search ) { $conditions['order_no'] = array( 'LIKE', '%' . $search . '%' ); } $orders = $orderModel->get( $conditions, $page, $per_page, array( 'created_at', 'DESC', ) ); if ( $team_member || $service ) { foreach ( $orders['items'] as $key => $order ) { $appointments = ( new Appointment_Model() )->where( 'order_id', $order->id ); $keep_order = false; foreach ( $appointments as $appointment ) { if ( $team_member && $appointment->team_member_id == $team_member ) { $keep_order = true; } if ( $service && $appointment->service_id == $service ) { $keep_order = true; } } if ( ! $keep_order && ( $team_member || $service ) ) { unset( $orders['items'][ $key ] ); --$orders['total']; } } $orders['items'] = array_values( $orders['items'] ); $orders['total_pages'] = ceil( $orders['total'] / $per_page ); } foreach ( $orders['items'] as &$order ) { $customer = ( new Customer_Model() )->find( $order->customer_id ); $order->customer = array( 'display_name' => $customer ? $customer->display_name : '', 'email' => $customer ? $customer->user_email : '', ); $order->total_services = ( new Appointment_Model() )->get_appointments_count_by_order_id( $order->id ); } return $this->response( $orders, __( 'Orders fetched successfully', 'booktics' ) ); } /** * Check if user has permission to update order * * @param WP_REST_Request $request * * @return bool */ public function update_item_permission( $request ) { return true; } /** * Update an existing order * * @param WP_REST_Request $request * * @return WP_Error|WP_HTTP_Response */ public function update_item( $request ) { $order_id = (int) $request->get_param( 'id' ); $data = json_decode( $request->get_body(), true ); $db = new Booktics_Database(); $order_model = new Order_Model( $db ); $order = $order_model->find( array( 'id' => $order_id ) ); if ( ! $order ) { return $this->error( __( 'Order not found', 'booktics' ) ); } $order_model->update( $order_id, array( 'status' => $data['status'] ?? $order->status, 'payment_status' => $data['payment_status'] ?? $order->payment_status, ) ); return $this->response( $order_model->find( array( 'id' => $order_id ) ), __( 'Order updated successfully', 'booktics' ) ); } /** * Check if user has permission to get order * * @param WP_REST_Request $request * * @return bool */ public function get_item_permission( $request ) { return true; } /** * Get single order * * @param WP_REST_Request $request * * @return WP_Error|WP_HTTP_Response */ public function get_item( $request ) { $order_id = (int) $request->get_param( 'id' ); $db = new Booktics_Database(); $order_model = new Order_Model( $db ); $order = $order_model->find( array( 'id' => $order_id ) ); if ( ! $order ) { return $this->error( __( 'Order not found', 'booktics' ) ); } $customer = ( new Customer_Model() )->find( $order->customer_id ); $appointments = ( new Appointment_Model() )->where( 'order_id', $order->id ); $appointment_details = array(); foreach ( $appointments as $appointment ) { $appointment = $appointment->to_array(); $team_member = ( new Team_Member_Model() )->find( $appointment['team_member_id'] ); $service = ( new Service_Model() )->find( $appointment['service_id'] ); $service = $service ? $service->to_array() : array(); $additional_durations = $service['additional_durations'] ?? array(); $additional_durations = ! empty( $additional_durations ) ? array_filter( $additional_durations, function ( $duration ) use ( $appointment ) { return $duration['id'] === $appointment['additional_duration_id']; } ) : array(); $additional_duration = ! empty( $additional_durations ) ? reset( $additional_durations ) : array(); $formatted_appointment = array( 'id' => $appointment['id'], 'date' => $appointment['date'], 'start_time' => $appointment['start_time'], 'end_time' => $appointment['end_time'], 'team' => array( 'id' => $team_member->id, 'display_name' => $team_member->display_name ?? '', 'image' => $team_member->image ?? '', ), 'service' => array( 'id' => $appointment['service_id'], 'title' => $service['title'] ?? '', 'additional_duration' => $additional_duration, ), 'quantity' => $appointment['quantity'], 'price' => $appointment['price'], 'subtotal' => $appointment['subtotal'], 'total' => $appointment['total'], 'status' => $appointment['status'], ); $formatted_appointment = apply_filters( 'booktics_appointment_pro_format', $formatted_appointment, $appointment ); $appointment_details[] = $formatted_appointment; } $response = array( 'id' => $order->id, 'order_no' => $order->order_no, 'customer' => array( 'display_name' => $customer->display_name, 'email' => $customer->user_email, ), 'subtotal' => $order->subtotal, 'total' => $order->total, 'payment_status' => $order->payment_status, 'status' => $order->status, 'items' => $appointment_details, 'created_at' => $order->created_at, 'payment_gateway' => $order->payment_method, ); return $this->response( $response, __( 'Order fetched successfully', 'booktics' ) ); } /** * Check if user has permission to update payment status * * @param $request * * @return bool */ public function update_payment_status_permission( $request ) { return true; } /** * Update payment status for an order * * @param $request */ public function update_payment_status( $request ) { $data = json_decode( $request->get_body(), true ); $order_id = ! empty( $data['order_id'] ) ? intval( $data['order_id'] ) : 0; $payment_status = ! empty( $data['payment_status'] ) ? $data['payment_status'] : 0; $payment_method = ! empty( $data['payment_method'] ) ? $data['payment_method'] : null; $db = new Booktics_Database(); $order_model = new Order_Model( $db ); $order = $order_model->find( array( 'id' => $order_id ) ); if ( ! $order ) { return $this->error( __( 'Order not found', 'booktics' ) ); } if ( ! in_array( $payment_method, array( 'stripe', 'paypal' ) ) ) { return $this->error( __( 'Unauthorized payment method', 'booktics' ) ); } try { $payment_handler = ( new Payment_Method_Manager() )->get_payment_method( $payment_method ); if ( ! $payment_handler ) { return $this->error( __( 'Payment method not found', 'booktics' ) ); } $status = $payment_handler->get_payment_status( $order ); if ( is_wp_error( $status ) ) { return $this->error( $status->get_error_message() ); } $order_model->update( $order_id, array( 'payment_status' => $status, 'transaction_id' => $data['transaction_id'] ?? null, ) ); // Payment model status and transaction id update return $this->response( array( 'payment_status' => $status, ), __( 'Payment status updated successfully', 'booktics' ) ); } catch ( \Exception $e ) { return $this->error( $e->getMessage() ); } } /** * Check if user has permission to delete order * * @param WP_REST_Request $request * * @return bool */ public function delete_item_permission( $request ) { return true; } /** * Delete an order * * @param $request */ public function delete_item( $request ) { $order_id = (int) $request->get_param( 'id' ); $db = new Booktics_Database(); $order_model = new Order_Model( $db ); $order = $order_model->find( array( 'id' => $order_id ) ); if ( ! $order ) { return $this->error( __( 'Order not found', 'booktics' ) ); } try { $db->begin_transaction(); // Delete related appointments $appointmentModel = new Appointment_Model(); $appointments = $appointmentModel->where( 'order_id', $order_id ); foreach ( $appointments as $appointment ) { $appointment->delete(); } // Delete related payments $paymentModel = new Payment_Model( $db ); $payments = $paymentModel->get( array( 'order_id' => $order_id ) ); foreach ( $payments['items'] as $payment ) { ( new Payment_Model( $db ) )->delete( $payment->id ); } // Delete the order $order_model->delete( $order_id ); $db->commit(); return $this->response( array(), __( 'Order deleted successfully', 'booktics' ) ); } catch ( Exception $e ) { $db->roll_back(); return $this->error( $e->getMessage() ); } } /** * Check if user has permission to bulk delete orders * * @param WP_REST_Request $request * * @return bool */ public function bulk_delete_items_permission( $request ) { return true; } /** * Bulk delete orders * * @param WP_REST_Request $request * * @return WP_Error|WP_HTTP_Response */ public function bulk_delete_items( $request ) { $order_ids = array_map( 'intval', $request->get_param( 'ids' ) ); $db = new Booktics_Database(); try { $db->begin_transaction(); foreach ( $order_ids as $order_id ) { $order_model = new Order_Model( $db ); $order = $order_model->find( array( 'id' => $order_id ) ); if ( ! $order ) { continue; } // Delete related appointments $appointmentModel = new Appointment_Model(); $appointments = $appointmentModel->where( 'order_id', $order_id ); foreach ( $appointments as $appointment ) { $appointment->delete(); } // Delete related payments $paymentModel = new Payment_Model( $db ); $payments = $paymentModel->get( array( 'order_id' => $order_id ) ); foreach ( $payments['items'] as $payment ) { ( new Payment_Model( $db ) )->delete( $payment->id ); } // Delete the order $order_model->delete( $order_id ); } $db->commit(); return $this->response( array(), __( 'Orders deleted successfully', 'booktics' ) ); } catch ( Exception $e ) { $db->roll_back(); return $this->error( $e->getMessage() ); } } /** * Process payment refund * * @param $request */ public function refund_payment( $request ) { $order_id = (int) $request->get_param( 'id' ); $db = new Booktics_Database(); $order_model = new Order_Model( $db ); $order = $order_model->find( array( 'id' => $order_id ) ); $payment_method = ( new Payment_Method_Manager() )->get_payment_method( $order->payment_method ); if ( ! $payment_method ) { return $this->error( __( 'Invalid payment method!', 'booktics' ) ); } $response = $payment_method->process_refund( $order ); if ( $response['status'] === 'error' ) { return $this->error( $response['message'] ); } $order_model->update( $order_id, array( 'payment_status' => 'refunded', ) ); return $this->response( $order_model->find( array( 'id' => $order_id ) ), __( 'Refund successful', 'booktics' ) ); } /** * Calculate total duration in minutes for a service appointment * * @param array $item * * @return int */ private function calculate_total_duration( $item ) { $service_model = Service_Model::find( $item['service_id'] ); if ( ! $service_model ) { return 0; } $service_duration = array_filter( $service_model->additional_durations, function ( $duration ) use ( $item ) { return $item['additional_duration_id'] === $duration['id']; } ); if ( empty( $service_duration ) ) { return 0; } $service_duration = reset( $service_duration ); $total_duration_min = $service_duration['duration_unit'] === 'min' ? $service_duration['duration_time'] : ( $service_duration['duration_time'] * 60 ); if ( ! empty( $item['extra_services'] ) ) { $total_duration_min = apply_filters( 'booktics_extra_service_duration', $total_duration_min, $item['extra_services'] ); } return $total_duration_min; } }