1 <?php
2
3 4 5 6 7 8 9 10 11
12
13 namespace WC_POS\API;
14
15 use WC_API_Server;
16 use WC_Emails;
17 use WC_Order;
18 use WC_POS\Admin\Settings\Checkout;
19 use WC_POS\Gateways\Cash;
20 use WP_Error;
21
22 class Orders {
23
24
25 protected $base = '/orders';
26
27
28 private $data = array();
29 private $flag = false;
30
31
32 33 34
35 public function __construct( WC_API_Server $server ) {
36
37
38 add_filter( 'woocommerce_api_order_response', array( $this, 'order_response' ), 10, 4 );
39
40
41 add_filter( 'woocommerce_email', array( $this, 'woocommerce_email' ), 99 );
42
43 if( substr( $server->path, 0, strlen( $this->base ) ) === $this->base ){
44 add_filter( 'woocommerce_api_create_order_data', array( $this, 'create_order_data') );
45 add_filter( 'woocommerce_api_edit_order_data', array( $this, 'edit_order_data'), 10, 2 );
46 add_action( 'woocommerce_api_create_order', array( $this, 'create_order') );
47 add_action( 'woocommerce_api_edit_order', array( $this, 'edit_order') );
48
49
50 add_action( 'woocommerce_pos_process_payment', array( $this, 'process_payment' ), 10, 2 );
51 add_action( 'woocommerce_payment_complete', array( $this, 'payment_complete' ), 10, 1 );
52 }
53
54 }
55
56
57 58 59 60 61 62
63 public function register_routes( $routes ) {
64
65
66 $routes[ $this->base . '/(?P<order_id>\d+)/email/(?P<email>.+)' ] = array(
67 array( array( $this, 'email_receipt' ), WC_API_Server::CREATABLE | WC_API_Server::ACCEPT_DATA )
68 );
69
70 return $routes;
71 }
72
73
74 75 76 77 78 79
80 public function create_order_data(array $data){
81
82
83 add_filter( 'woocommerce_product_tax_class', array( $this, 'product_tax_class' ), 10, 2 );
84 add_filter( 'woocommerce_get_product_from_item', array( $this, 'get_product_from_item' ), 10, 3 );
85 add_filter( 'pre_option_woocommerce_tax_based_on', array( $this, 'woocommerce_tax_based_on' ) );
86 add_filter( 'woocommerce_find_rates', array( $this, 'find_rates'), 10, 2 );
87 add_action( 'woocommerce_order_add_product', array( $this, 'order_add_product'), 10, 5 );
88 add_action( 'updated_order_item_meta', array( $this, 'updated_order_item_meta'), 10, 4 );
89
90
91
92 if( version_compare( WC()->version, '2.4', '<' ) && isset($data['fee_lines']) ){
93 foreach( $data['fee_lines'] as &$fee ){
94 if( !isset($fee['taxable']) || $fee['taxable'] == false ){
95 $fee['taxable'] = true;
96 $fee['tax_class'] = 'upgrade-woocommerce-' . time();
97 }
98 }
99 }
100
101
102
103
104 $has_fee = isset($data['fee_lines']) && !empty($data['fee_lines']);
105 $has_shipping = isset($data['shipping_lines']) && !empty($data['shipping_lines']);
106
107 if( $has_shipping )
108 add_action( 'woocommerce_order_add_shipping', array( $this, 'order_add_shipping'), 10, 3 );
109
110 if( $has_fee || $has_shipping )
111 add_filter( 'update_post_metadata', array( $this, 'update_post_metadata'), 10, 5 );
112
113
114 $customer_id = isset( $data['customer_id'] ) ? $data['customer_id'] : 0 ;
115 if( $customer_id ){
116 $data['billing_address'] = $this->get_customer_details($customer_id, 'billing');
117 $data['shipping_address'] = $this->get_customer_details($customer_id, 'shipping');
118 }
119
120
121 $this->data = $data;
122
123 return $this->data;
124 }
125
126 127 128 129 130 131 132
133 public function edit_order_data(array $data, $order_id){
134 $this->delete_order_items($order_id);
135 return $this->create_order_data($data);
136 }
137
138 139 140 141 142 143
144 public function product_tax_class($tax_class, $product){
145
146 $id = isset($product->id) ? $product->id: false;
147 $id = isset($product->variation_id) ? $product->variation_id: $id;
148
149 if($id){
150 $item = $this->get_line_item($id);
151 if( isset( $item['tax_class'] ) ){
152 $tax_class = sanitize_text_field( $item['tax_class'] );
153 }
154 }
155
156 return $tax_class;
157 }
158
159 160 161 162 163 164 165
166 public function get_product_from_item($product, $item, $WC_Product){
167
168 $id = isset($product->id) ? $product->id: false;
169 $id = isset($product->variation_id) ? $product->variation_id: $id;
170
171 if($id){
172 $data = $this->get_line_item($id);
173 if( isset( $data['taxable'] ) ){
174
175
176 $product->tax_status = $data['taxable'] ? 'taxable' : 'none';
177 }
178 }
179
180 return $product;
181
182 }
183
184 185 186 187 188 189 190 191
192 public function order_add_product($order_id, $item_id, $product, $qty, $args){
193
194 $id = isset($product->id) ? $product->id: false;
195 $id = isset($product->variation_id) ? $product->variation_id: $id;
196
197 if($id){
198 $data = $this->get_line_item($id);
199
200
201 if( isset( $data['title'] ) ){
202 wc_update_order_item( $item_id,
203 array(
204 'order_item_name' => sanitize_text_field( $data['title'] )
205 )
206 );
207 }
208
209
210 if( isset( $data['meta'] ) && !empty( $data['meta'] ) ){
211 $this->add_product_meta( $item_id, $data['meta'] );
212 }
213
214 }
215
216 }
217
218 219 220 221 222
223 private function add_product_meta($item_id, array $meta){
224
225
226 foreach($meta as $m) {
227 $label = isset($m['label']) ? $m['label'] : '';
228 $value = isset($m['value']) ? $m['value'] : '';
229 wc_add_order_item_meta( $item_id, $label, $value );
230 }
231
232 }
233
234 235 236 237 238 239
240 public function order_add_shipping($order_id, $item_id, $rate){
241 $shipping_line = $this->get_shipping_line($rate, $item_id);
242
243 if($shipping_line && isset($shipping_line['tax'])){
244 $taxes = array();
245 foreach($shipping_line['tax'] as $k => $tax){
246 if( !empty($tax) ){
247 $taxes[$k] = isset($tax['total']) ? wc_format_decimal( $tax['total'] ) : 0 ;
248 }
249 }
250 wc_update_order_item_meta( $item_id, 'taxes', $taxes );
251 }
252 }
253
254 255 256 257
258 public function woocommerce_tax_based_on(){
259 return 'base';
260 }
261
262 263 264 265 266 267
268 public function find_rates( $matched_tax_rates, $args ){
269 if ( $matched_tax_rates ) {
270 foreach ( $matched_tax_rates as &$rate ) {
271 $rate['shipping'] = 'no';
272 }
273 }
274 return $matched_tax_rates;
275 }
276
277 278 279 280
281 private function get_line_item($product_id){
282 if(!isset($this->data['line_items']))
283 return;
284
285 foreach($this->data['line_items'] as $item){
286 if( isset($item['product_id']) && $item['product_id'] == $product_id )
287 return $item;
288 }
289 }
290
291 292 293 294 295 296
297 private function get_shipping_line($rate, $item_id){
298 if(!isset($this->data['shipping_lines']))
299 return;
300
301 $lines = $this->data['shipping_lines'];
302 foreach($lines as $key => &$line){
303 if(
304 $line['method_id'] == $rate->method_id &&
305 $line['method_title'] == $rate->label &&
306 $line['total'] == $rate->cost &&
307 !isset( $line['id'] )
308 )
309 $line['id'] = $item_id;
310 return $line;
311 }
312 }
313
314 315 316 317 318 319 320
321 public function updated_order_item_meta($meta_id, $order_id, $meta_key, $meta_value){
322
323 if($meta_key == '_line_tax_data'){
324 $line_subtotal_taxes = isset($meta_value['subtotal']) ? $meta_value['subtotal'] : array();
325 $line_taxes = isset($meta_value['total']) ? $meta_value['total'] : array();
326 $line_subtotal_tax = array_sum( $line_subtotal_taxes );
327 $line_tax = array_sum( $line_taxes );
328
329 wc_update_order_item_meta( $order_id, '_line_subtotal_tax', wc_format_decimal( $line_subtotal_tax ) );
330 wc_update_order_item_meta( $order_id, '_line_tax', wc_format_decimal( $line_tax ) );
331 }
332
333 }
334
335 336 337 338 339 340 341 342 343 344 345
346 public function update_post_metadata($null, $order_id, $meta_key, $meta_value, $prev_value){
347
348
349
350 if( $meta_key == '_order_shipping_tax' )
351 $this->flag = true;
352
353 if( $meta_key != '_order_shipping' || ! $this->flag )
354 return $null;
355
356
357 $shipping_tax_total = isset($this->data['shipping_tax']) ? $this->data['shipping_tax'] : 0 ;
358 update_post_meta( $order_id, '_order_shipping_tax', wc_format_decimal( $shipping_tax_total ) );
359
360
361
362
363
364
365
366 $tax_items = array();
367 $order_tax = 0;
368 $order = wc_get_order( $order_id );
369 foreach ( $order->get_tax_totals() as $code => $tax ) {
370 $tax_items[$tax->rate_id] = $tax->id;
371 $order_tax += $tax->amount;
372 }
373
374
375 update_post_meta($order_id, '_order_tax', $order_tax);
376
377
378 if( isset( $shipping['shipping_lines'] ) ) :
379 foreach($this->data['shipping_lines'] as $shipping) :
380 if( isset( $shipping['tax'] ) ) :
381 foreach( $shipping['tax'] as $rate_id => $tax ) :
382 if( isset( $tax['total'] ) ) :
383
384 if( array_key_exists( $rate_id, $tax_items ) ){
385 wc_update_order_item_meta( $tax_items[$rate_id], 'shipping_tax_amount', wc_format_decimal( $tax['total'] ) );
386 } else {
387 $order->add_tax( $rate_id, 0, $tax['total'] );
388 }
389
390 endif;
391 endforeach;
392 endif;
393 endforeach;
394 endif;
395
396 return $null;
397 }
398
399 400 401 402
403 public function create_order( $order_id ){
404
405 $current_user = wp_get_current_user();
406
407 update_post_meta( $order_id, '_pos', 1 );
408 update_post_meta( $order_id, '_pos_user', $current_user->ID );
409 update_post_meta( $order_id, '_pos_user_name', $current_user->user_firstname . ' ' . $current_user->user_lastname );
410
411
412 if( ! get_post_meta( $order_id, '_order_tax', true ) ){
413 update_post_meta( $order_id, '_order_tax', 0 );
414 }
415
416 if( ! get_post_meta( $order_id, '_order_shipping_tax', true ) ){
417 update_post_meta( $order_id, '_order_shipping_tax', 0 );
418 }
419
420
421 do_action( 'woocommerce_pos_process_payment', $order_id, $this->data);
422 }
423
424 425 426 427
428 public function edit_order( $order_id ){
429
430 do_action( 'woocommerce_pos_process_payment', $order_id, $this->data);
431 }
432
433 434 435 436 437
438 public function process_payment( $order_id, $data ) {
439
440 if ( !isset( $data[ 'payment_details' ] ) ) {
441 return;
442 }
443
444
445 $logged_in_user = get_current_user_id();
446 $customer_id = isset( $data[ 'customer_id' ] ) ? $data[ 'customer_id' ] : 0;
447 wp_set_current_user( $customer_id );
448
449
450 $gateway_id = $data[ 'payment_details' ][ 'method_id' ];
451 add_filter( 'option_woocommerce_' . $gateway_id . '_settings', array( $this, 'force_enable_gateway' ) );
452 $settings = Checkout::get_instance();
453 $gateways = $settings->load_enabled_gateways();
454
455 if ( isset( $gateways[ $gateway_id ] ) ) {
456 do_action( 'woocommerce_pos_process_' . strtolower( $gateway_id ) . '_payment', $order_id, $data );
457 $response = $gateways[ $gateway_id ]->process_payment( $order_id );
458 if ( isset( $response[ 'result' ] ) && $response[ 'result' ] == 'success' ) {
459 $this->payment_success( $gateway_id, $order_id, $response );
460 } else {
461 $this->payment_failure( $gateway_id, $order_id, $response );
462 }
463
464 } else {
465 $this->payment_failure( $gateway_id, $order_id, '' );
466 }
467
468
469 wp_set_current_user( $logged_in_user );
470
471
472 wc_clear_notices();
473 }
474
475 476 477 478 479
480 public function force_enable_gateway($data) {
481 if ( isset( $data[ 'enabled' ] ) ) {
482 $data[ 'enabled' ] = 'yes';
483 }
484
485 return $data;
486 }
487
488 489 490 491 492
493 private function payment_success($gateway_id, $order_id, $response){
494
495
496 ob_start();
497 do_action( 'woocommerce_thankyou_' . $gateway_id, $order_id );
498 $response['messages'] = ob_get_contents();
499 ob_end_clean();
500
501
502 if( isset($response['redirect']) ){
503 $response['messages'] = $this->payment_redirect($gateway_id, $order_id, $response);
504 }
505
506 update_post_meta( $order_id, '_pos_payment_result', 'success' );
507 update_post_meta( $order_id, '_pos_payment_message', $response['messages'] );
508 }
509
510 511 512 513 514
515 private function payment_failure($gateway_id, $order_id, $response){
516 $message = isset($response['messages']) ? $response['messages'] : wc_get_notices( 'error' );
517
518
519 if(empty($message)){
520 $message = __( 'There was an error processing the payment', 'woocommerce-pos');
521 }
522
523 update_post_meta( $order_id, '_pos_payment_result', 'failure' );
524 update_post_meta( $order_id, '_pos_payment_message', $message );
525 }
526
527 528 529 530 531 532
533 private function payment_redirect($gateway_id, $order_id, $response){
534 $message = $response['messages'];
535
536
537 $success_url = wc_get_endpoint_url( 'order-received', $order_id, get_permalink( wc_get_page_id( 'checkout' ) ) );
538 $success = wp_parse_args( parse_url( $success_url ), array( 'host' => '', 'path' => '' ));
539 $redirect = wp_parse_args( parse_url( $response['redirect'] ), array( 'host' => '', 'path' => '' ));
540
541 $offsite = $success['host'] !== $redirect['host'];
542 $reload = !$offsite && $success['path'] !== $redirect['path'] && $response['messages'] == '';
543
544 if($offsite || $reload){
545 update_post_meta( $order_id, '_pos_payment_redirect', $response['redirect'] );
546 $message = __('You are now being redirected offsite to complete the payment. ', 'woocommerce-pos');
547 $message .= sprintf( __('<a href="%s">Click here</a> if you are not redirected automatically. ', 'woocommerce-pos'), $response['redirect'] );
548 }
549
550 return $message;
551 }
552
553 554 555
556 public function payment_complete( $order_id ) {
557
558
559 $order = new WC_Order( $order_id );
560 if( $order->status == 'processing' ) {
561 $message = __('POS Transaction completed.', 'woocommerce-pos');
562 $order->update_status( wc_pos_get_option( 'checkout', 'order_status' ), $message );
563 }
564
565 }
566
567 568 569 570 571 572 573 574 575
576 public function order_response( $order_data, $order, $fields, $server ) {
577
578
579 $order_data['cashier'] = $this->add_cashier_details( $order );
580
581
582 $payment_details = isset( $order_data['payment_details'] ) ? $order_data['payment_details'] : array();
583 $order_data['payment_details'] = $this->add_payment_details( $order, $payment_details );
584
585
586
587
588
589
590
591 if( version_compare( WC()->version, '2.4', '<' ) && wc_pos_get_option( 'general', 'decimal_qty' ) ){
592 $order_data['line_items'] = $this->filter_qty($order_data['line_items']);
593 }
594
595
596 $subtotal_tax = 0;
597 foreach( $order_data['line_items'] as $item ) {
598 if(isset( $item['subtotal_tax'] )) {
599 $subtotal_tax += wc_format_decimal( $item['subtotal_tax'] );
600 }
601 }
602 $order_data['subtotal_tax'] = wc_format_decimal( $subtotal_tax );
603
604
605 foreach( $order_data['shipping_lines'] as &$item ) {
606 if(isset( $item['id'] )) {
607 $taxes = wc_get_order_item_meta( $item['id'], 'taxes' );
608 $item['total_tax'] = is_array($taxes) ? wc_format_decimal( array_sum($taxes) ) : 0 ;
609 }
610 }
611
612
613 $order_data['cart_discount_tax'] = get_post_meta($order->id, '_cart_discount_tax', true);
614
615 return $order_data;
616 }
617
618 619 620 621
622 private function add_cashier_details( $order ) {
623
624 if ( !$cashier_id = get_post_meta( $order->id, '_pos_user', true ) ) {
625 return;
626 }
627
628 $first_name = get_post_meta( $order->id, '_pos_user_first_name', true );
629 $last_name = get_post_meta( $order->id, '_pos_user_last_name', true );
630 if ( !$first_name && !$last_name && $user_info = get_userdata( $cashier_id ) ) {
631 $first_name = $user_info->first_name;
632 $last_name = $user_info->last_name;
633 }
634
635 $cashier = array(
636 'id' => $cashier_id,
637 'first_name' => $first_name,
638 'last_name' => $last_name
639 );
640
641 return apply_filters( 'woocommerce_pos_order_response_cashier', $cashier, $order );
642
643 }
644
645 646 647 648 649
650 private function add_payment_details( $order, array $payment_details ){
651
652 $payment_details['result'] = get_post_meta( $order->id, '_pos_payment_result', true );
653 $payment_details['message'] = get_post_meta( $order->id, '_pos_payment_message', true );
654 $payment_details['redirect'] = get_post_meta( $order->id, '_pos_payment_redirect', true );
655
656 if( isset( $payment_details['method_id'] ) && $payment_details['method_id'] == 'pos_cash' ){
657 $payment_details['method_pos_cash'] = Cash::payment_details( $order );
658 }
659
660 return apply_filters( 'woocommerce_pos_order_response_payment_details', $payment_details, $order );
661
662 }
663
664 665 666 667 668 669 670
671 private function filter_address( $address, $order, $type = 'billing' ){
672 $fields = apply_filters('woocommerce_admin_'.$type.'_fields', false);
673 if( $fields ){
674 $address = array();
675 foreach($fields as $key => $value){
676 $address[$key] = $order->{$type.'_'.$key};
677 }
678 }
679 return $address;
680 }
681
682 683 684 685 686 687 688
689 private function get_customer_details( $user_id, $type_to_load ){
690 $customer_data = array(
691 $type_to_load . '_first_name' => get_user_meta( $user_id, $type_to_load . '_first_name', true ),
692 $type_to_load . '_last_name' => get_user_meta( $user_id, $type_to_load . '_last_name', true ),
693 $type_to_load . '_company' => get_user_meta( $user_id, $type_to_load . '_company', true ),
694 $type_to_load . '_address_1' => get_user_meta( $user_id, $type_to_load . '_address_1', true ),
695 $type_to_load . '_address_2' => get_user_meta( $user_id, $type_to_load . '_address_2', true ),
696 $type_to_load . '_city' => get_user_meta( $user_id, $type_to_load . '_city', true ),
697 $type_to_load . '_postcode' => get_user_meta( $user_id, $type_to_load . '_postcode', true ),
698 $type_to_load . '_country' => get_user_meta( $user_id, $type_to_load . '_country', true ),
699 $type_to_load . '_state' => get_user_meta( $user_id, $type_to_load . '_state', true ),
700 $type_to_load . '_email' => get_user_meta( $user_id, $type_to_load . '_email', true ),
701 $type_to_load . '_phone' => get_user_meta( $user_id, $type_to_load . '_phone', true ),
702 );
703 $customer_data = apply_filters( 'woocommerce_found_customer_details', $customer_data, $user_id, $type_to_load );
704
705
706 $data = array();
707 foreach( $customer_data as $key => $value ): if($value):
708 $key = str_replace( $type_to_load.'_', '', $key );
709 $data[$key] = $value;
710 endif; endforeach;
711 return $data;
712 }
713
714 715 716 717
718 private function filter_qty($line_items){
719 foreach( $line_items as &$item ){
720 $qty = wc_get_order_item_meta( $item['id'], '_qty' );
721 $item['quantity'] = wc_stock_amount( $qty );
722 }
723 return $line_items;
724 }
725
726
727 728 729 730
731 private function delete_order_items($order_id) {
732 global $wpdb;
733 $order_item_ids = $wpdb->get_col( $wpdb->prepare( "
734 SELECT order_item_id
735 FROM {$wpdb->prefix}woocommerce_order_items
736 WHERE order_id = %d
737 ", $order_id ) );
738
739 foreach ( $order_item_ids as $item_id ) {
740 wc_delete_order_item( $item_id );
741 }
742 }
743
744 745 746 747
748 public function woocommerce_email(WC_Emails $wc_emails) {
749
750 if( ! wc_pos_get_option( 'checkout', 'customer_emails' ) ){
751 $this->remove_customer_emails($wc_emails);
752 }
753
754 if( ! wc_pos_get_option( 'checkout', 'admin_emails' ) ){
755 $this->remove_admin_emails($wc_emails);
756 }
757 }
758
759 760 761
762 private function remove_customer_emails(WC_Emails $wc_emails){
763 remove_action(
764 'woocommerce_order_status_pending_to_processing_notification',
765 array(
766 $wc_emails->emails['WC_Email_Customer_Processing_Order'],
767 'trigger'
768 )
769 );
770 remove_action(
771 'woocommerce_order_status_pending_to_on-hold_notification',
772 array(
773 $wc_emails->emails['WC_Email_Customer_Processing_Order'],
774 'trigger'
775 )
776 );
777 remove_action(
778 'woocommerce_order_status_completed_notification',
779 array(
780 $wc_emails->emails['WC_Email_Customer_Completed_Order'],
781 'trigger'
782 )
783 );
784 }
785
786 787 788
789 private function remove_admin_emails( WC_Emails $wc_emails){
790
791
792
793 remove_action(
794 'woocommerce_order_status_pending_to_processing_notification',
795 array(
796 $wc_emails->emails['WC_Email_New_Order'],
797 'trigger'
798 )
799 );
800 remove_action(
801 'woocommerce_order_status_pending_to_completed_notification',
802 array(
803 $wc_emails->emails['WC_Email_New_Order'],
804 'trigger'
805 )
806 );
807 remove_action(
808 'woocommerce_order_status_pending_to_on-hold_notification',
809 array(
810 $wc_emails->emails['WC_Email_New_Order'],
811 'trigger'
812 )
813 );
814 remove_action(
815 'woocommerce_order_status_failed_to_processing_notification',
816 array(
817 $wc_emails->emails['WC_Email_New_Order'],
818 'trigger'
819 )
820 );
821 remove_action(
822 'woocommerce_order_status_failed_to_completed_notification',
823 array(
824 $wc_emails->emails['WC_Email_New_Order'],
825 'trigger'
826 )
827 );
828 remove_action(
829 'woocommerce_order_status_failed_to_on-hold_notification',
830 array(
831 $wc_emails->emails['WC_Email_New_Order'],
832 'trigger'
833 )
834 );
835
836 }
837
838 839 840 841 842 843 844
845 static public function email_receipt( $order_id, $email ) {
846 $order = wc_get_order( absint( $order_id ) );
847
848 if ( is_object( $order ) ) {
849 if ( $email != '' ) {
850 $order->billing_email = $email;
851 }
852 WC()->mailer()->customer_invoice( $order );
853
854
855 do_action( 'woocommerce_pos_email_receipt', $email, $order_id, $order );
856
857 return array(
858 'result' => 'success',
859 'message' => __( 'Email sent', 'woocommerce-pos' )
860 );
861 }
862
863 return new WP_Error(
864 'woocommerce_pos_settings_error',
865 __( 'There was an error sending the email', 'woocommerce-pos' ),
866 array( 'status' => 400 )
867 );
868
869 }
870
871 }