[Back]
<?php
/*
 * Copyright (c) 2024 LatePoint LLC. All rights reserved.
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly.
}


if ( ! class_exists( 'OsInvoicesController' ) ) :


	class OsInvoicesController extends OsController {

		function __construct() {
			parent::__construct();

			$this->action_access['public'] = array_merge( $this->action_access['public'], [ 'view_by_key', 'payment_form', 'summary_before_payment' ] );

			$this->views_folder = LATEPOINT_VIEWS_ABSPATH . 'invoices/';
		}


		public function new_form() {
			$order_id = absint( sanitize_text_field( $this->params['order_id'] ) );

			if ( ! is_numeric( $order_id ) ) {
				echo __( 'Invalid Order ID', 'latepoint' );

				return;
			}

			$order = new OsOrderModel( $order_id );
			if ( empty( $order ) || $order->is_new_record() ) {
				echo __( 'Invalid Order ID', 'latepoint' );

				return;
			}

			$invoice                  = new OsInvoiceModel();
			$invoice->order_id        = $order->id;
			$invoice->payment_portion = LATEPOINT_PAYMENT_PORTION_CUSTOM;

			$this->vars['invoice'] = $invoice;

			$this->format_render( __FUNCTION__ );
		}

		private function get_invoice_params() {
			$invoice_params                    = $this->params['invoice'];

			// input date is in WP format (or in viewer's format), we need to make it "end of the day" and also convert to UTC timezone
			$due_at_wp_time = sanitize_text_field( $invoice_params['due_at'] ).' 23:59:59';
			$invoice_params['due_at'] = OsWpDateTime::os_createFromFormat(LATEPOINT_DATETIME_DB_FORMAT, $due_at_wp_time)->setTimezone(new DateTimeZone('UTC'))->format(LATEPOINT_DATETIME_DB_FORMAT);

			$invoice_params['order_id']        = absint( sanitize_text_field( $this->params['invoice']['order_id'] ) );
			$invoice_params['payment_portion'] = sanitize_text_field( $this->params['invoice']['payment_portion'] );
			$invoice_params['charge_amount']   = OsParamsHelper::sanitize_param( sanitize_text_field( $this->params['invoice']['charge_amount'] ), 'money' );

			$errors = [];
			if ( ! in_array( $invoice_params['payment_portion'], array_keys( OsPaymentsHelper::get_payment_portions_list() ) ) ) {
				$errors[] = __( 'Invalid payment portion', 'latepoint' );
			}
			if ( ! is_numeric( $invoice_params['order_id'] ) ) {
				$errors[] = __( 'Invalid Order ID', 'latepoint' );
			}
			if ( ! empty( $errors ) ) {
				return new WP_Error( 'invalid_params', implode( ', ', $errors ) );
			}

			return $invoice_params;
		}

		public function process_data_update() {

			if ( ! filter_var( $this->params['invoice_id'], FILTER_VALIDATE_INT ) ) {
				echo 'Invalid invoice';

				return;
			}
			$invoice = new OsInvoiceModel( $this->params['invoice_id'] );
			$old_invoice = clone $invoice;

			if ( empty( $invoice ) || $invoice->is_new_record() ) {
				echo 'Invalid invoice';

				return;
			}

			$invoice->charge_amount = OsParamsHelper::sanitize_param( sanitize_text_field( $this->params['invoice']['charge_amount'] ), 'money' );
			$due_at_wp_time = sanitize_text_field( $this->params['invoice']['due_at'] ).' 23:59:59';
			$due_at_utc_time = OsWpDateTime::os_createFromFormat(LATEPOINT_DATETIME_DB_FORMAT, $due_at_wp_time)->setTimezone(new DateTimeZone('UTC'))->format(LATEPOINT_DATETIME_DB_FORMAT);
			$invoice->due_at        = $due_at_utc_time;
			$invoice->status        = sanitize_text_field( $this->params['invoice']['status'] );

			if ( $invoice->save() ) {

				/**
				 * Invoice was updated
				 *
				 * @param {OsInvoiceModel} $invoice instance of invoice model after it was updated
				 * @param {OsInvoiceModel} $old_invoice instance of invoice model before it was updated
				 *
				 * @since 5.1.0
				 * @hook latepoint_invoice_updated
				 *
				 */
				do_action( 'latepoint_invoice_updated', $invoice, $old_invoice );
				$status = LATEPOINT_STATUS_SUCCESS;
				ob_start();
				OsInvoicesHelper::invoice_document_html( $invoice, true );
				$message = ob_get_clean();

			} else {
				$status  = LATEPOINT_STATUS_ERROR;
				$message = $invoice->get_error_messages();
			}

			$this->send_json( [ 'status' => $status, 'message' => $message ] );
		}

		public function edit_data() {
			if ( ! filter_var( $this->params['invoice_id'], FILTER_VALIDATE_INT ) ) {
				echo __( 'Invalid Invoice ID', 'latepoint' );

				return;
			}
			$invoice = new OsInvoiceModel( $this->params['invoice_id'] );
			if ( empty( $invoice ) || $invoice->is_new_record() ) {
				echo __( 'Invoice not found', 'latepoint' );

				return;
			}

			$this->vars['invoice'] = $invoice;

			$this->format_render( __FUNCTION__ );
		}

		public function reload_invoice_tile() {
			if ( ! filter_var( $this->params['invoice_id'], FILTER_VALIDATE_INT ) ) {
				echo 'Invalid invoice';

				return;
			}
			$invoice = new OsInvoiceModel( $this->params['invoice_id'] );
			if ( empty( $invoice ) || $invoice->is_new_record() ) {
				echo 'Invalid invoice';

				return;
			}

			$this->send_json( [ 'status' => LATEPOINT_STATUS_SUCCESS, 'message' => OsInvoicesHelper::generate_invoice_tile_on_order_edit_form( $invoice ) ] );
		}

		public function create() {
			$invoice_params = $this->get_invoice_params();
			if ( is_wp_error( $invoice_params ) ) {
				$this->send_json( [ 'status' => LATEPOINT_STATUS_ERROR, 'message' => $invoice_params->get_error_message() ] );

				return;
			}

			$order = new OsOrderModel( $invoice_params['order_id'] );
			if ( empty( $order ) || $order->is_new_record() ) {
				echo __( 'Invalid Order ID', 'latepoint' );

				return;
			}

			$invoice = new OsInvoiceModel();
			$invoice->set_data( $invoice_params );

			$invoice->data = json_encode( OsInvoicesHelper::generate_invoice_data_from_order( $order ) );
			if ( $invoice->save() ) {
				/**
				 * Invoice was created
				 *
				 * @param {OsInvoiceModel} $invoice instance of invoice model that was created
				 *
				 * @since 5.1.0
				 * @hook latepoint_invoice_created
				 *
				 */
				do_action( 'latepoint_invoice_created', $invoice );
				$response_html = OsInvoicesHelper::generate_invoice_tile_on_order_edit_form( $invoice );
				$this->send_json( [ 'status' => LATEPOINT_STATUS_SUCCESS, 'message' => $response_html ] );
			} else {
				$this->send_json( [ 'status' => LATEPOINT_STATUS_ERROR, 'message' => __( 'Error: ', 'latepoint' ) . $invoice->get_error_messages() ] );
			}

		}

		public function change_status() {
			if ( ! filter_var( $this->params['invoice_id'], FILTER_VALIDATE_INT ) ) {
				echo 'Invalid Invoice';

				return;
			}

			if ( ! in_array( $this->params['status'], array_keys( OsInvoicesHelper::list_of_statuses_for_select() ) ) ) {
				echo 'Invalid Status';

				return;
			}

			$invoice = new OsInvoiceModel( $this->params['invoice_id'] );
			$invoice->change_status( $this->params['status'] );

			$status = LATEPOINT_STATUS_SUCCESS;

			ob_start();
			OsInvoicesHelper::invoice_document_html( $invoice, true );
			$response_html = ob_get_clean();

			if ( $this->get_return_format() == 'json' ) {

				$this->send_json( [ 'status' => $status, 'message' => $response_html ] );
			}
		}

		public function email_form() {
			if ( ! filter_var( $this->params['invoice_id'], FILTER_VALIDATE_INT ) ) {
				echo __( 'Invalid Invoice ID', 'latepoint' );

				return;
			}
			$invoice = new OsInvoiceModel( $this->params['invoice_id'] );
			if ( empty( $invoice ) || $invoice->is_new_record() ) {
				echo __( 'Invoice not found', 'latepoint' );

				return;
			}
			$errors = [];

			$to      = __( '{{customer_email}}', 'latepoint' );
			$subject = OsInvoicesHelper::get_subject_for_invoice_email();
			$content = OsInvoicesHelper::get_content_for_invoice_email();

			if ( ! empty( $this->params['invoice_email'] ) ) {
				// send email
				$to       = $this->params['invoice_email[to]'] ?? $to;
				$order    = new OsOrderModel( $invoice->order_id );
				$customer = new OsCustomerModel( $order->customer_id );

				$original_to = $to;
				$to          = OsReplacerHelper::replace_all_vars( $to, [ 'order' => $order, 'customer' => $customer, 'invoice' => $invoice ] );
				$subject     = OsReplacerHelper::replace_all_vars( $subject, [ 'order' => $order, 'customer' => $customer, 'invoice' => $invoice ] );
				$content     = OsReplacerHelper::replace_all_vars( $content, [ 'order' => $order, 'customer' => $customer, 'invoice' => $invoice ] );
				if ( OsUtilHelper::is_valid_email( $to ) ) {
					$mailer = new OsMailer();
					wp_mail( $to, $subject, $content, $mailer->get_headers() );
					// set back so it appears correctly on the front
					$to                    = $original_to;
					$this->vars['success'] = __( 'Invoice email sent', 'latepoint' );
				} else {
					$errors[] = __( 'Please enter a valid email address.', 'latepoint' );
				}

			}

			$this->vars['errors']  = $errors;
			$this->vars['to']      = $to;
			$this->vars['subject'] = $subject;
			$this->vars['content'] = $content;
			$this->vars['invoice'] = $invoice;

			$this->format_render( __FUNCTION__ );
		}


		public function payment_form() {
			$invoice_access_key = sanitize_text_field( $this->params['key'] );
			if ( empty( $invoice_access_key ) ) {
				echo __( 'Invalid Invoice Key', 'latepoint' );
				exit;
			}

			$invoice = OsInvoicesHelper::get_invoice_by_key( $invoice_access_key );
			if ( $invoice->is_new_record() ) {
				echo __( 'Invoice not found', 'latepoint' );
				exit;
			}

			$errors = [];
			$order  = $invoice->get_order();

			// find an existing transaction intent for this invoice

			$transaction_intent = new OsTransactionIntentModel();
			$transaction_intent = $transaction_intent->where( [ 'status' => [
					LATEPOINT_TRANSACTION_INTENT_STATUS_NEW,
					LATEPOINT_TRANSACTION_INTENT_STATUS_PROCESSING,
					LATEPOINT_TRANSACTION_INTENT_STATUS_CONVERTED
				], 'invoice_id' => $invoice->id ] )->set_limit( 1 )->get_results_as_models();
			if ( empty( $transaction_intent ) ) {
				$transaction_intent = new OsTransactionIntentModel();
			}

			$transaction_intent->charge_amount = $invoice->charge_amount;
			$transaction_intent->invoice_id    = $invoice->id;
			$transaction_intent->order_id      = $order->id;
			$transaction_intent->customer_id   = $order->customer_id;
			$transaction_intent->set_payment_data_value( 'time', LATEPOINT_PAYMENT_TIME_NOW, false );
			$transaction_intent->set_payment_data_value( 'portion', $invoice->payment_portion, false );

			$form_prev_button = esc_html__( 'Back', 'latepoint' );
			$form_next_button = esc_html__( 'Next', 'latepoint' );
			$invoice_link     = '';
			$receipt_link     = '';

			$selected_payment_method    = $this->params['payment_method'] ?? '';
			$selected_payment_processor = $this->params['payment_processor'] ?? '';
			$payment_token              = $this->params['payment_token'] ?? '';

			$enabled_payment_methods = OsPaymentsHelper::get_enabled_payment_methods_for_payment_time( LATEPOINT_PAYMENT_TIME_NOW );
			// if only one available, force select it
			if ( count( $enabled_payment_methods ) == 1 ) {
				$selected_payment_method = array_key_first( $enabled_payment_methods );
			}

			if ( $selected_payment_method ) {
				$enabled_payment_processors = OsPaymentsHelper::get_enabled_payment_processors_for_payment_time_and_method( LATEPOINT_PAYMENT_TIME_NOW, $selected_payment_method );
				if ( count( $enabled_payment_processors ) == 1 ) {
					$selected_payment_processor = array_key_first( $enabled_payment_processors );
				}
			}

			if ( ! $selected_payment_method ) {
				$current_step     = 'methods';
				$form_heading     = __( 'Payment Methods', 'latepoint' );
				$form_prev_button = false;
				$form_next_button = false;
			} else {
				$transaction_intent->set_payment_data_value( 'method', $selected_payment_method, false );
				if ( ! $selected_payment_processor ) {
					$current_step = 'processors';
					$form_heading = __( 'Payment Processors', 'latepoint' );

					// hide prev button if we don't need to pick a payment methods
					if ( count( $enabled_payment_methods ) <= 1 ) {
						$form_prev_button = false;
					}
					$form_next_button = false;
				} else {
					$transaction_intent->set_payment_data_value( 'processor', $selected_payment_processor, false );
					$form_next_button = sprintf( esc_html__( 'Pay %s', 'latepoint' ), OsMoneyHelper::format_price( $transaction_intent->charge_amount, true, false ) );
					$form_heading     = __( 'Payment Form', 'latepoint' );
					// hide prev button if we don't need to pick a payment method or processor
					if ( count( $enabled_payment_methods ) <= 1 && count( $enabled_payment_processors ) <= 1 ) {
						$form_prev_button = false;
					}
					if ( $payment_token ) {
						$transaction_intent->set_payment_data_value( 'token', $payment_token, false );
					}
					if ( ! $payment_token || empty( $this->params['submitting_payment'] ) ) {
						$current_step = 'pay';
						$transaction_intent->calculate_specs_charge_amount();
						$transaction_intent->save();
					} else {
						$transaction_id = $transaction_intent->convert_to_transaction();
						if ( $transaction_id ) {
							$transaction               = new OsTransactionModel( $transaction_id );
							$form_next_button          = false;
							$form_prev_button          = false;
							$invoice_link              = apply_filters( 'latepoint_transaction_invoice_link', $invoice_link, $invoice );
							$receipt_link              = apply_filters( 'latepoint_transaction_receipt_link', $receipt_link, $invoice, $transaction );
							$current_step              = 'confirmation';
							$this->vars['transaction'] = $transaction;
							$form_heading              = __( 'Confirmation', 'latepoint' );
						} else {
							$current_step = 'pay';
							$errors[]     = implode( ', ', $transaction_intent->get_error_messages() );
						}
					}
				}
			}


			$this->vars['invoice']                    = $invoice;
			$this->vars['invoice_link']               = $invoice_link;
			$this->vars['receipt_link']               = $receipt_link;
			$this->vars['form_heading']               = $form_heading;
			$this->vars['payment_token']              = $payment_token;
			$this->vars['errors']                     = $errors;
			$this->vars['in_lightbox']                = $this->params['in_lightbox'] ?? 'yes';
			$this->vars['transaction_intent']         = $transaction_intent;
			$this->vars['current_step']               = $current_step;
			$this->vars['selected_payment_method']    = $selected_payment_method;
			$this->vars['selected_payment_processor'] = $selected_payment_processor;
			$this->vars['enabled_payment_methods']    = $enabled_payment_methods;

			$this->vars['form_next_button']   = $form_next_button;
			$this->vars['form_prev_button']   = $form_prev_button;
			$this->vars['invoice_access_key'] = $invoice_access_key;


			$this->vars['order'] = $order;

			$this->format_render( __FUNCTION__ );
		}

		public function summary_before_payment() {
			$invoice_access_key = sanitize_text_field( $this->params['key'] );
			if ( empty( $invoice_access_key ) ) {
				echo __( 'Invalid Invoice Key', 'latepoint' );
				exit;
			}

			$invoice = OsInvoicesHelper::get_invoice_by_key( $invoice_access_key );
			if ( $invoice->is_new_record() ) {
				echo __( 'Invoice not found', 'latepoint' );
				exit;
			}

			$this->vars['invoice'] = $invoice;
			$this->vars['order']   = $invoice->get_order();

			if ( $this->get_return_format() == 'json' ) {
				$this->vars['in_lightbox'] = true;
				$this->set_layout( 'none' );
				$response_html = $this->format_render_return( __FUNCTION__ );
				$this->send_json( [ 'status' => LATEPOINT_STATUS_SUCCESS, 'message' => $response_html ] );
			} else {
				$this->vars['in_lightbox'] = false;
				$this->set_layout( 'clean' );
				$this->format_render( __FUNCTION__ );
			}
		}


		function view_by_key() {
			$invoice_access_key    = sanitize_text_field( $this->params['key'] );
			$invoice               = new OsInvoiceModel();
			$invoice               = $invoice->where( [ 'access_key' => $invoice_access_key ] )->set_limit( 1 )->get_results_as_models();
			$this->vars['invoice'] = $invoice;

			$this->set_layout( 'clean' );
			$this->format_render( __FUNCTION__ );
		}

		function view() {
			if ( ! filter_var( $this->params['id'], FILTER_VALIDATE_INT ) ) {
				return;
			}

			$invoice = new OsInvoiceModel( $this->params['id'] );

			$this->vars['invoice'] = $invoice;

			$this->set_layout( 'none' );
			$response_html = $this->format_render_return( __FUNCTION__ );

			$status = LATEPOINT_STATUS_SUCCESS;

			if ( $this->get_return_format() == 'json' ) {

				$this->send_json( [ 'status' => $status, 'message' => $response_html ] );
			}
		}
	}

endif;