PluginProbe
Booktics – Appointment Booking Calendar for Service Businesses / 1.0.2
Booktics – Appointment Booking Calendar for Service Businesses v1.0.2
1.0.29 1.0.28 1.0.27 1.0.26 1.0.25 1.0.24 1.0.23 1.0.22 1.0.21 1.0.20 1.0.19 trunk 1.0.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.15 1.0.16 1.0.17 1.0.18 1.0.2 1.0.3 All 31 releases
booktics / core / order / controllers / order-controller.php

order-controller.php in Booktics – Appointment Booking Calendar for Service Businesses 1.0.2, at core/order/controllers/order-controller.php

949 lines 36.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Booktics\Order\Controllers;
4
5 use Booktics\Abstracts\Base_Rest_Controller;
6 use Booktics\Abstracts\Booktics_Database;
7 use Booktics\Abstracts\User_Model;
8 use Booktics\Cart\Cart_Manager;
9 use Booktics\Models\Appointment_Model;
10 use Booktics\Models\Customer_Model;
11 use Booktics\Models\Order_Model;
12 use Booktics\Models\Payment_Model;
13 use Booktics\Models\Service_Model;
14 use Booktics\Models\Team_Member_Model;
15 use Booktics\Order\Order_Validator;
16 use Booktics\Payment\Payment_Method_Manager;
17 use Booktics\Services\Time_Zone_Handler;
18 use Booktics_Stripe\Admin\Controllers\Booktics_Stripe_Controller;
19 use DateTime;
20 use Exception;
21 use WP_Error;
22 use WP_HTTP_Response;
23 use WP_REST_Request;
24 use WP_REST_Server;
25
26 /**
27 * Order controller
28 *
29 * @package Booktics/Order
30 */
31 class Order_Controller extends Base_Rest_Controller {
32
33 /**
34 * Endpoint namespace
35 *
36 * @var string
37 */
38 protected $namespace = 'booktics/v1';
39
40 /**
41 * Route name
42 *
43 * @var string
44 */
45 protected $base = 'orders';
46
47 /**
48 * Register routes
49 *
50 * @return void
51 */
52 public function register_routes(): void {
53 register_rest_route(
54 $this->namespace, $this->base, array(
55 array(
56 'methods' => WP_REST_Server::CREATABLE,
57 'callback' => array( $this, 'create_item' ),
58 'permission_callback' => array(
59 $this,
60 'create_order_permission',
61 ),
62 ),
63 array(
64 'methods' => WP_REST_Server::READABLE,
65 'callback' => array( $this, 'get_items' ),
66 'permission_callback' => array(
67 $this,
68 'get_items_permission',
69 ),
70 'args' => array(
71 'page' => array( 'default' => 1 ),
72 'per_page' => array( 'default' => 10 ),
73 ),
74 ),
75 array(
76 'methods' => WP_REST_Server::DELETABLE,
77 'callback' => array(
78 $this,
79 'bulk_delete_items',
80 ),
81 'permission_callback' => array(
82 $this,
83 'bulk_delete_items_permission',
84 ),
85 ),
86 )
87 );
88
89 register_rest_route(
90 $this->namespace, $this->base . '/(?P<id>[\d]+)', array(
91 array(
92 'methods' => WP_REST_Server::EDITABLE,
93 'callback' => array( $this, 'update_item' ),
94 'permission_callback' => array(
95 $this,
96 'update_item_permission',
97 ),
98 'args' => array(
99 'id' => array( 'required' => true ),
100 'status' => array( 'type' => 'string' ),
101 ),
102 ),
103 array(
104 'methods' => WP_REST_Server::READABLE,
105 'callback' => array( $this, 'get_item' ),
106 'permission_callback' => array(
107 $this,
108 'get_item_permission',
109 ),
110 'args' => array(
111 'id' => array( 'required' => true ),
112 ),
113 ),
114 array(
115 'methods' => WP_REST_Server::DELETABLE,
116 'callback' => array( $this, 'delete_item' ),
117 'permission_callback' => array(
118 $this,
119 'delete_item_permission',
120 ),
121 'args' => array(
122 'id' => array( 'required' => true ),
123 ),
124 ),
125 )
126 );
127
128 register_rest_route(
129 $this->namespace, $this->base . '/(?P<id>[\d]+)/payment', array(
130 array(
131 'methods' => WP_REST_Server::EDITABLE,
132 'callback' => array(
133 $this,
134 'update_payment_status',
135 ),
136 'permission_callback' => array(
137 $this,
138 'update_payment_status_permission',
139 ),
140 ),
141 )
142 );
143
144 register_rest_route(
145 $this->namespace, $this->base . '/(?P<id>[\d]+)/refund', array(
146 array(
147 'methods' => WP_REST_Server::EDITABLE,
148 'callback' => array( $this, 'refund_payment' ),
149 'permission_callback' => array(
150 $this,
151 'update_payment_status_permission',
152 ),
153 ),
154 )
155 );
156 }
157
158 /**
159 * Check if the current user has permission to create orders
160 *
161 * @param WP_REST_Request $request
162 *
163 * @return bool
164 */
165 public function create_order_permission( $request ) {
166 return true;
167 }
168
169 /**
170 * Prepare and validate order data before creation
171 *
172 * @param array $data
173 *
174 * @return array|WP_Error
175 */
176 private function prepare_item_for_order_create( array $data ) {
177 if ( ! isset( $data['timezone'] ) || ! Time_Zone_Handler::validate_time_zone( $data['timezone'] ) ) {
178 $data['timezone'] = 'UTC';
179 }
180
181 if ( isset( $data['pay_now'] ) && $data['pay_now'] ) {
182 $validate = booktics_validate(
183 $data, array(
184 'payment_method' => array( 'required' ),
185 )
186 );
187
188 if ( is_wp_error( $validate ) ) {
189 return $validate;
190 }
191 }
192
193 $error = ( new \WP_Error() );
194 if ( ! is_array( $data['items'] ) || count( $data['items'] ) == 0 ) {
195 $error->add( 'items', __( 'Items are required', 'booktics' ) );
196
197 return $error;
198 }
199
200 foreach ( $data['items'] as $key => $item ) {
201 $item_validate = booktics_validate(
202 $item, array(
203 'date' => array( 'required' ),
204 'start_time' => array( 'required' ),
205 'end_time' => array( 'required' ),
206 'team_member_id' => array( 'required' ),
207 'service_id' => array( 'required' ),
208 )
209 );
210
211 if ( is_wp_error( $item_validate ) ) {
212 return $item_validate;
213 }
214
215 $converted_start_time = Time_Zone_Handler::convert_to_utc(
216 $item['start_time'],
217 $item['date'],
218 $data['timezone']
219 );
220 $converted_end_time = Time_Zone_Handler::convert_to_utc(
221 $item['end_time'],
222 $item['date'],
223 $data['timezone']
224 );
225 $item['start_time'] = $converted_start_time['time'];
226 $item['end_time'] = $converted_end_time['time'];
227
228 $start_time = strtotime( $item['start_time'] );
229 $end_time = strtotime( $item['end_time'] );
230
231 if ( ! $start_time || ! $end_time || $start_time >= $end_time ) {
232 $error->add( 'time_invalid', __( 'Invalid time range provided', 'booktics' ) );
233
234 return $error;
235 }
236
237 $data['items'][ $key ]['start_time'] = $converted_start_time['time'];
238 $data['items'][ $key ]['end_time'] = $converted_end_time['time'];
239 $data['items'][ $key ]['date'] = gmdate( 'Y-m-d', strtotime( $converted_start_time['date'] ) );
240 $data['items'][ $key ]['customer_id'] = absint( $item['customer_id'] );
241 $data['items'][ $key ]['team_member_id'] = absint( $item['team_member_id'] );
242 $data['items'][ $key ]['service_id'] = absint( $item['service_id'] );
243 $data['items'][ $key ]['additional_duration_id'] = $item['additional_duration_id'];
244 $data['items'][ $key ]['price'] = floatval( $item['price'] );
245 $data['items'][ $key ]['subtotal'] = floatval( $item['subtotal'] );
246 $data['items'][ $key ]['quantity'] = absint( $item['quantity'] ?? 1 );
247 $data['items'][ $key ]['group_booking'] = $item['group_booking'] ?? array();
248 }
249
250 return $data;
251 }
252
253
254 /**
255 * Create a new order with the provided data
256 *
257 * @param WP_REST_Request $request
258 *
259 * @return WP_Error|WP_HTTP_Response
260 * @throws Exception
261 */
262 public function create_item( $request ) {
263 $input_data = json_decode( $request->get_body(), true );
264 $validator = new Order_Validator();
265 $validation_result = $validator->validate( $input_data );
266
267 if ( is_wp_error( $validation_result ) ) {
268 return $this->error(
269 __( 'Order validation failed', 'booktics' ),
270 422,
271 'Validation',
272 $validation_result->get_error_messages()
273 );
274 }
275
276 $db = new Booktics_Database();
277 $validation = $this->prepare_item_for_order_create( $input_data );
278
279 if ( is_wp_error( $validation ) ) {
280 return $this->error( $validation->get_error_message() );
281 }
282
283 if ( ! isset( $input_data['customer_id'] ) ) {
284 $customer_id = $this->find_or_create_customer( $input_data );
285 if ( is_wp_error( $customer_id ) ) {
286 return $this->error( $customer_id->get_error_message() );
287 }
288 $input_data['customer_id'] = $customer_id;
289 }
290 try {
291 $db->begin_transaction();
292 // Order creation logic
293 $input_data['currency'] = $input_data['currency'] ?? 'usd';
294 $order_model = new Order_Model( $db );
295 $order_model = $order_model->create(
296 array(
297 'customer_id' => $input_data['customer_id'],
298 'payment_status' => 'pending',
299 'price_breakdown' => $input_data['price_breakdown'] ?? array(),
300 'tax_total' => $input_data['tax_total'] ?? 0,
301 'coupon_code' => $input_data['coupon_code'] ?? '',
302 'coupon_discount' => $input_data['coupon_discount'] ?? 0,
303 'subtotal' => $input_data['subtotal'],
304 'timezone' => $input_data['timezone'] ?? 'UTC',
305 'total' => $input_data['total'],
306 'payment_method' => $input_data['payment_method'] ?? null,
307 )
308 );
309
310 foreach ( $input_data['items'] as $item ) {
311 if ( isset( $item['uuid'] ) ) {
312 ( new Cart_Manager() )->remove_cart_item( $item['uuid'] );
313 }
314 $total_duration_min = $this->calculate_total_duration( $item );
315 $appointment_data = array(
316 'start_time' => $item['start_time'] ?? '',
317 'end_time' => $item['end_time'] ?? '',
318 'date' => gmdate( 'Y-m-d', strtotime( $item['date'] ) ),
319 'customer_id' => $input_data['customer_id'] ?? '',
320 'team_member_id' => $item['team_member_id'] ?? '',
321 'service_id' => $item['service_id'] ?? '',
322 'additional_duration_id' => $item['additional_duration_id'] ?? '',
323 'duration' => $total_duration_min,
324 'order_id' => $order_model->id,
325 'quantity' => $item['quantity'] ?? 1,
326 'price' => $item['price'] ?? 0,
327 'subtotal' => $item['subtotal'] ?? 0,
328 'total' => $item['total'] ?? 0,
329 'status' => 'pending',
330 );
331 $appointment_data = apply_filters( 'booktics_appointment_pro_format', $appointment_data, $item );
332 Appointment_Model::create( $appointment_data );
333 }
334
335 if ( $input_data['pay_now'] ) {
336 $payment_method = ( new Payment_Method_Manager() )->get_payment_method( $input_data['payment_method'] );
337 if ( $payment_method ) {
338 $payment_intent = $payment_method->process_payment( $order_model );
339 if ( $payment_intent['status'] === 'error' ) {
340 return $this->error( $payment_intent['message'] );
341 }
342 if ( $payment_intent['status'] === 'success' ) {
343 ( new Order_Model( $db ) )->update(
344 $order_model->id, array(
345 'payment_intent_id' => $payment_intent['payment_intent'],
346 )
347 );
348
349 ( new Payment_Model( $db ) )->create(
350 array(
351 'order_id' => $order_model->id,
352 'customer_id' => $input_data['customer_id'],
353 'amount' => $input_data['total'] * 100,
354 'currency' => $input_data['currency'],
355 'intent_id' => $payment_intent['payment_intent'],
356 'transaction_id' => null,
357 'payment_method' => $input_data['payment_method'],
358 'status' => 'pending',
359 'date' => gmdate( 'Y-m-d' ),
360 'date_time' => gmdate( 'Y-m-d H:i:s' ),
361 )
362 );
363 }
364 } else {
365 return $this->error( __( 'Payment method can not found!', 'booktics' ) );
366 }
367 }
368
369 $db->commit();
370 } catch ( Exception $e ) {
371 $db->roll_back();
372
373 return $this->error( $e->getMessage() );
374 }
375 do_action( 'booktics_order_created', $order_model );
376
377 $customer = ( new Customer_Model() )->find( $input_data['customer_id'] );
378 $appointments = ( new Appointment_Model() )->where( 'order_id', $order_model->id );
379 $appointment_details = array();
380 foreach ( $appointments as $appointment ) {
381 $appointment = $appointment->to_array();
382 $team_member = ( new Team_Member_Model() )->find( $appointment['team_member_id'] );
383 $service = ( new Service_Model() )->find( $appointment['service_id'] );
384 $service = $service ? $service->to_array() : array();
385 $additional_durations = $service['additional_durations'] ?? array();
386 $additional_durations = ! empty( $additional_durations )
387 ? array_filter(
388 $additional_durations, function ( $duration ) use ( $appointment ) {
389 return $duration['id'] === $appointment['additional_duration_id'];
390 }
391 ) : array();
392 $additional_duration = ! empty( $additional_durations ) ? reset( $additional_durations ) : array();
393 $formatted_appointment = array(
394 'id' => $appointment['id'],
395 'date' => $appointment['date'],
396 'start_time' => $appointment['start_time'],
397 'end_time' => $appointment['end_time'],
398 'additional_duration_id' => $appointment['additional_duration_id'],
399 'team' => array(
400 'id' => $team_member->id,
401 'display_name' => $team_member->display_name ?? '',
402 'image' => $team_member->image ?? '',
403 ),
404 'service' => array(
405 'id' => $appointment['service_id'],
406 'title' => $service['title'] ?? '',
407 'additional_duration' => $additional_duration,
408 ),
409 'duration' => $appointment['duration'],
410 'quantity' => $appointment['quantity'],
411 'price' => $appointment['price'],
412 'subtotal' => $appointment['subtotal'],
413 'total' => $appointment['total'],
414 'status' => $appointment['status'],
415 );
416 $formatted_appointment = apply_filters( 'booktics_appointment_pro_format', $formatted_appointment, $appointment );
417 $appointment_details[] = $formatted_appointment;
418 $appointment_time = $appointment['date'] . ' ' . $appointment['start_time'];
419 $dt = new DateTime( $appointment_time, wp_timezone() );
420 $timestamp = $dt->getTimestamp();
421 do_action(
422 'global_notification_hook', 'appointment_scheduled', array(
423 'appointment_service' => $service['title'] ?? '',
424 'appointment_team_member' => $team_member->display_name ?? '',
425 'start_time' => $appointment['start_time'],
426 'appointment_date' => $appointment['date'],
427 'appointment_time' => $timestamp,
428 'attendee_count' => $appointment['attendee_count'],
429 'customer_name' => $customer->display_name ?? '',
430 'user_email' => $customer->user_email ?? '',
431 )
432 );
433 }
434
435 do_action(
436 'global_notification_hook', 'order_created', array(
437 'order_no' => $order_model->order_no,
438 'order_date' => booktics_datetime( 'M j, Y, g:ia', time(), 'UTC' ),
439 'order_total' => $input_data['total'],
440 'customer_name' => $customer->display_name ?? '',
441 'user_email' => $customer->user_email,
442 )
443 );
444
445 $response = array(
446 'id' => $order_model->id,
447 'order_no' => $order_model->order_no,
448 'customer_email' => $customer->user_email,
449 'subtotal' => $order_model->subtotal,
450 'total' => $order_model->total,
451 'payment_status' => $order_model->payment_status,
452 'items' => $appointment_details,
453 'payment_intent_id' => $payment_intent['payment_intent'] ?? '',
454 'client_secret' => $payment_intent['client_secret'] ?? '',
455 );
456
457 return $this->response( $response, __( 'Order created successfully', 'booktics' ), 201 );
458 }
459
460 /**
461 * Find existing customer by email or create new customer
462 *
463 * @param array $data
464 *
465 * @return int|WP_Error
466 */
467 private function find_or_create_customer( array $data ) {
468 $customer = get_user_by( 'email', $data['customer_email'] );
469
470 if ( $customer ) {
471 return $customer->ID;
472 }
473
474 $userdata = array(
475 'user_login' => User_Model::generate_username( $data['customer_email'] ),
476 'user_email' => $data['customer_email'],
477 'user_pass' => wp_generate_password(),
478 'first_name' => $data['customer_name'] ?? '',
479 'display_name' => $data['customer_name'] ?? '',
480 'phone' => $data['customer_phone'] ?? '',
481 );
482
483 $customer = new Customer_Model();
484
485 return $customer->save( $userdata );
486 }
487
488 /**
489 * Check if user has permission to get orders
490 *
491 * @param WP_REST_Request $request
492 *
493 * @return bool
494 */
495 public function get_items_permission( $request ) {
496 return true;
497 }
498
499 /**
500 * Get paginated list of orders
501 *
502 * @param WP_REST_Request $request
503 *
504 * @return WP_Error|WP_HTTP_Response
505 */
506 public function get_items( $request ) {
507 $page = (int) $request->get_param( 'paged' ) == 0 ? 1 : $request->get_param( 'paged' );
508 $per_page = (int) $request->get_param( 'per_page' ) == 0 ? 10 : $request->get_param( 'per_page' );
509 $payment_status = $request->get_param( 'payment_status' );
510 $payment_method = $request->get_param( 'payment_method' );
511 $order_date = $request->get_param( 'order_date' );
512 $team_member = (int) $request->get_param( 'team_member' );
513 $service = (int) $request->get_param( 'service' );
514 $search = $request->get_param( 'search' );
515
516 $db = new Booktics_Database();
517 $order_model = new Order_Model( $db );
518 $conditions = array();
519
520 if ( $payment_status ) {
521 $conditions['payment_status'] = $payment_status;
522 }
523
524 if ( $payment_method ) {
525 $conditions['payment_method'] = $payment_method;
526 }
527
528 if ( $order_date ) {
529 $conditions['DATE(created_at)'] = array( '=', $order_date );
530 }
531
532 if ( $search ) {
533 $conditions['order_no'] = array( 'LIKE', '%' . $search . '%' );
534 }
535
536 $orders = $order_model->get(
537 $conditions, $page, $per_page, array(
538 'created_at',
539 'DESC',
540 )
541 );
542
543 if ( $team_member || $service ) {
544 foreach ( $orders['items'] as $key => $order ) {
545 $appointments = ( new Appointment_Model() )->where( 'order_id', $order->id );
546 $keep_order = false;
547
548 foreach ( $appointments as $appointment ) {
549 if ( $team_member && $appointment->team_member_id == $team_member ) {
550 $keep_order = true;
551 }
552 if ( $service && $appointment->service_id == $service ) {
553 $keep_order = true;
554 }
555 }
556
557 if ( ! $keep_order && ( $team_member || $service ) ) {
558 unset( $orders['items'][ $key ] );
559 --$orders['total'];
560 }
561 }
562
563 $orders['items'] = array_values( $orders['items'] );
564 $orders['total_pages'] = ceil( $orders['total'] / $per_page );
565 }
566
567 foreach ( $orders['items'] as &$order ) {
568 $customer = ( new Customer_Model() )->find( $order->customer_id );
569 $order->customer = array(
570 'display_name' => $customer ? $customer->display_name : '',
571 'email' => $customer ? $customer->user_email : '',
572 );
573 $order->total_services = ( new Appointment_Model() )->get_appointments_count_by_order_id( $order->id );
574 }
575
576 return $this->response( $orders, __( 'Orders fetched successfully', 'booktics' ) );
577 }
578
579 /**
580 * Check if user has permission to update order
581 *
582 * @param WP_REST_Request $request
583 *
584 * @return bool
585 */
586 public function update_item_permission( $request ) {
587 return true;
588 }
589
590 /**
591 * Update an existing order
592 *
593 * @param WP_REST_Request $request
594 *
595 * @return WP_Error|WP_HTTP_Response
596 */
597 public function update_item( $request ) {
598 $order_id = (int) $request->get_param( 'id' );
599 $data = json_decode( $request->get_body(), true );
600 $db = new Booktics_Database();
601 $order_model = new Order_Model( $db );
602 $order = $order_model->find( array( 'id' => $order_id ) );
603
604 if ( ! $order ) {
605 return $this->error( __( 'Order not found', 'booktics' ) );
606 }
607
608 $order_model->update(
609 $order_id, array(
610 'status' => $data['status'] ?? $order->status,
611 'payment_status' => $data['payment_status'] ?? $order->payment_status,
612 )
613 );
614
615 return $this->response( $order_model->find( array( 'id' => $order_id ) ), __( 'Order updated successfully', 'booktics' ) );
616 }
617
618 /**
619 * Check if user has permission to get order
620 *
621 * @param WP_REST_Request $request
622 *
623 * @return bool
624 */
625 public function get_item_permission( $request ) {
626 return true;
627 }
628
629 /**
630 * Get single order
631 *
632 * @param WP_REST_Request $request
633 *
634 * @return WP_Error|WP_HTTP_Response
635 */
636 public function get_item( $request ) {
637 $order_id = (int) $request->get_param( 'id' );
638 $db = new Booktics_Database();
639 $order_model = new Order_Model( $db );
640 $order = $order_model->find( array( 'id' => $order_id ) );
641
642 if ( ! $order ) {
643 return $this->error( __( 'Order not found', 'booktics' ) );
644 }
645
646 $customer = ( new Customer_Model() )->find( $order->customer_id );
647 $appointments = ( new Appointment_Model() )->where( 'order_id', $order->id );
648 $appointment_details = array();
649
650 foreach ( $appointments as $appointment ) {
651 $appointment = $appointment->to_array();
652 $team_member = ( new Team_Member_Model() )->find( $appointment['team_member_id'] );
653 $service = ( new Service_Model() )->find( $appointment['service_id'] );
654 $service = $service ? $service->to_array() : array();
655 $additional_durations = $service['additional_durations'] ?? array();
656 $additional_durations = ! empty( $additional_durations )
657 ? array_filter(
658 $additional_durations, function ( $duration ) use ( $appointment ) {
659 return $duration['id'] === $appointment['additional_duration_id'];
660 }
661 ) : array();
662 $additional_duration = ! empty( $additional_durations ) ? reset( $additional_durations ) : array();
663 $formatted_appointment = array(
664 'id' => $appointment['id'],
665 'date' => $appointment['date'],
666 'start_time' => $appointment['start_time'],
667 'end_time' => $appointment['end_time'],
668 'team' => array(
669 'id' => $team_member->id,
670 'display_name' => $team_member->display_name ?? '',
671 'image' => $team_member->image ?? '',
672 ),
673 'service' => array(
674 'id' => $appointment['service_id'],
675 'title' => $service['title'] ?? '',
676 'additional_duration' => $additional_duration,
677 ),
678 'quantity' => $appointment['quantity'],
679 'price' => $appointment['price'],
680 'subtotal' => $appointment['subtotal'],
681 'total' => $appointment['total'],
682 'status' => $appointment['status'],
683 );
684 $formatted_appointment = apply_filters( 'booktics_appointment_pro_format', $formatted_appointment, $appointment );
685 $appointment_details[] = $formatted_appointment;
686 }
687
688 $response = array(
689 'id' => $order->id,
690 'order_no' => $order->order_no,
691 'customer' => array(
692 'display_name' => $customer->display_name,
693 'email' => $customer->user_email,
694 ),
695 'subtotal' => $order->subtotal,
696 'total' => $order->total,
697 'payment_status' => $order->payment_status,
698 'status' => $order->status,
699 'items' => $appointment_details,
700 'created_at' => $order->created_at,
701 'payment_gateway' => $order->payment_method,
702 );
703
704 return $this->response( $response, __( 'Order fetched successfully', 'booktics' ) );
705 }
706
707 /**
708 * Check if user has permission to update payment status
709 *
710 * @param $request
711 *
712 * @return bool
713 */
714 public function update_payment_status_permission( $request ) {
715 return true;
716 }
717
718 /**
719 * Update payment status for an order
720 *
721 * @param $request
722 */
723 public function update_payment_status( $request ) {
724 $data = json_decode( $request->get_body(), true );
725 $order_id = ! empty( $data['order_id'] ) ? intval( $data['order_id'] ) : 0;
726 $payment_status = ! empty( $data['payment_status'] ) ? $data['payment_status'] : 0;
727 $payment_method = ! empty( $data['payment_method'] ) ? $data['payment_method'] : null;
728
729 $db = new Booktics_Database();
730 $order_model = new Order_Model( $db );
731 $order = $order_model->find( array( 'id' => $order_id ) );
732
733 if ( ! $order ) {
734 return $this->error( __( 'Order not found', 'booktics' ) );
735 }
736
737 if ( ! in_array( $payment_method, array( 'stripe', 'paypal' ) ) ) {
738 return $this->error( __( 'Unauthorized payment method', 'booktics' ) );
739 }
740
741 try {
742 $payment_handler = ( new Payment_Method_Manager() )->get_payment_method( $payment_method );
743 if ( ! $payment_handler ) {
744 return $this->error( __( 'Payment method not found', 'booktics' ) );
745 }
746
747 $status = $payment_handler->get_payment_status( $order );
748 if ( is_wp_error( $status ) ) {
749 return $this->error( $status->get_error_message() );
750 }
751
752 $order_model->update(
753 $order_id, array(
754 'payment_status' => $status,
755 'transaction_id' => $data['transaction_id'] ?? null,
756 )
757 );
758
759 // Payment model status and transaction id update
760 return $this->response(
761 array(
762 'payment_status' => $status,
763 ), __( 'Payment status updated successfully', 'booktics' )
764 );
765 } catch ( \Exception $e ) {
766 return $this->error( $e->getMessage() );
767 }
768 }
769
770 /**
771 * Check if user has permission to delete order
772 *
773 * @param WP_REST_Request $request
774 *
775 * @return bool
776 */
777 public function delete_item_permission( $request ) {
778 return true;
779 }
780
781 /**
782 * Delete an order
783 *
784 * @param $request
785 */
786 public function delete_item( $request ) {
787 $order_id = (int) $request->get_param( 'id' );
788 $db = new Booktics_Database();
789 $order_model = new Order_Model( $db );
790 $order = $order_model->find( array( 'id' => $order_id ) );
791
792 if ( ! $order ) {
793 return $this->error( __( 'Order not found', 'booktics' ) );
794 }
795
796 try {
797 $db->begin_transaction();
798
799 // Delete related appointments
800 $appointment_model = new Appointment_Model();
801 $appointments = $appointment_model->where( 'order_id', $order_id );
802 foreach ( $appointments as $appointment ) {
803 $appointment->delete();
804 }
805
806 // Delete related payments
807 $payment_model = new Payment_Model( $db );
808 $payments = $payment_model->get( array( 'order_id' => $order_id ) );
809 foreach ( $payments['items'] as $payment ) {
810 ( new Payment_Model( $db ) )->delete( $payment->id );
811 }
812 // Delete the order
813 $order_model->delete( $order_id );
814
815 $db->commit();
816
817 return $this->response( array(), __( 'Order deleted successfully', 'booktics' ) );
818 } catch ( Exception $e ) {
819 $db->roll_back();
820
821 return $this->error( $e->getMessage() );
822 }
823 }
824
825 /**
826 * Check if user has permission to bulk delete orders
827 *
828 * @param WP_REST_Request $request
829 *
830 * @return bool
831 */
832 public function bulk_delete_items_permission( $request ) {
833 return true;
834 }
835
836 /**
837 * Bulk delete orders
838 *
839 * @param WP_REST_Request $request
840 *
841 * @return WP_Error|WP_HTTP_Response
842 */
843 public function bulk_delete_items( $request ) {
844 $order_ids = array_map( 'intval', $request->get_param( 'ids' ) );
845 $db = new Booktics_Database();
846
847 try {
848 $db->begin_transaction();
849
850 foreach ( $order_ids as $order_id ) {
851 $order_model = new Order_Model( $db );
852 $order = $order_model->find( array( 'id' => $order_id ) );
853
854 if ( ! $order ) {
855 continue;
856 }
857
858 // Delete related appointments
859 $appointment_model = new Appointment_Model();
860 $appointments = $appointment_model->where( 'order_id', $order_id );
861 foreach ( $appointments as $appointment ) {
862 $appointment->delete();
863 }
864
865 // Delete related payments
866 $payment_model = new Payment_Model( $db );
867 $payments = $payment_model->get( array( 'order_id' => $order_id ) );
868 foreach ( $payments['items'] as $payment ) {
869 ( new Payment_Model( $db ) )->delete( $payment->id );
870 }
871
872 // Delete the order
873 $order_model->delete( $order_id );
874 }
875
876 $db->commit();
877
878 return $this->response( array(), __( 'Orders deleted successfully', 'booktics' ) );
879 } catch ( Exception $e ) {
880 $db->roll_back();
881
882 return $this->error( $e->getMessage() );
883 }
884 }
885
886 /**
887 * Process payment refund
888 *
889 * @param $request
890 */
891 public function refund_payment( $request ) {
892 $order_id = (int) $request->get_param( 'id' );
893 $db = new Booktics_Database();
894 $order_model = new Order_Model( $db );
895 $order = $order_model->find( array( 'id' => $order_id ) );
896
897 $payment_method = ( new Payment_Method_Manager() )->get_payment_method( $order->payment_method );
898 if ( ! $payment_method ) {
899 return $this->error( __( 'Invalid payment method!', 'booktics' ) );
900 }
901 $response = $payment_method->process_refund( $order );
902 if ( $response['status'] === 'error' ) {
903 return $this->error( $response['message'] );
904 }
905
906 $order_model->update(
907 $order_id, array(
908 'payment_status' => 'refunded',
909 )
910 );
911
912 return $this->response(
913 $order_model->find( array( 'id' => $order_id ) ),
914 __( 'Refund successful', 'booktics' )
915 );
916 }
917
918 /**
919 * Calculate total duration in minutes for a service appointment
920 *
921 * @param array $item
922 *
923 * @return int
924 */
925 private function calculate_total_duration( $item ) {
926 $service_model = Service_Model::find( $item['service_id'] );
927 if ( ! $service_model ) {
928 return 0;
929 }
930 $service_duration = array_filter(
931 $service_model->additional_durations, function ( $duration ) use ( $item ) {
932 return $item['additional_duration_id'] === $duration['id'];
933 }
934 );
935 if ( empty( $service_duration ) ) {
936 return 0;
937 }
938 $service_duration = reset( $service_duration );
939 $total_duration_min = $service_duration['duration_unit'] === 'min'
940 ? $service_duration['duration_time']
941 : ( $service_duration['duration_time'] * 60 );
942 if ( ! empty( $item['extra_services'] ) ) {
943 $total_duration_min = apply_filters( 'booktics_extra_service_duration', $total_duration_min, $item['extra_services'] );
944 }
945
946 return $total_duration_min;
947 }
948 }
949