<?php
/**
 * Authorize.net Gateway eCheck Class
 *
 * Handles eCheck payments through Authorize.net gateway for WooCommerce.
 *
 * @package    Authorize_Net_Payments_For_Woocommerce
 * @subpackage Authorize_Net_Payments_For_Woocommerce/includes
 * @since      1.0.0
 */

if (! defined('ABSPATH')) {
	exit;
}

require_once plugin_dir_path(__FILE__) . '../trait-anet-customer-profile.php';

/**
 * Authorize.net Gateway eCheck Payment Class
 *
 * Extends WooCommerce eCheck payment gateway to provide Authorize.net eCheck processing
 * Functionality including tokenization, customer profiles, and transaction management.
 *
 * @package    Authorize_Net_Payments_For_Woocommerce
 * @subpackage Gateway
 * @since      1.0.0
 */
class Anet_ECheck_Gateway extends WC_Payment_Gateway_ECheck
{
	use Anet_Customer_Profile, Anet_Order_Meta;
	/**
	 * Meta key for storing Authorize.net customer profile ID
	 *
	 * @var mixed
	 */
	private $meta_key;
	/**
	 * The current version of the plugin.
	 *
	 * @var      string    $version    The current version of the plugin.
	 */
	protected $version;
	/**
	 * The Admin object of this plugin.
	 *
	 * @var      object    $plugin_admin    The current payment gateways admin object.
	 */
	protected $plugin_admin;
	/**
	 * The gateway object of this plugin.
	 *
	 * @var      object    $gateway    The current payment gateways object.
	 */
	private $gateway;
	/**
	 * The gateway ID property.
	 *
	 * @var string
	 */
	public $id;
	/**
	 * The transaction type for the gateway.
	 *
	 * @var string
	 */
	public $transaction_type;
	/**
	 * The login ID for Authorize.net API.
	 *
	 * @var string
	 */
	public $login_id;
	/**
	 * The transaction key for Authorize.net API.
	 *
	 * @var string
	 */
	public $transaction_key;
	/**
	 * The client key for Accept.js.
	 *
	 * @var string
	 */
	public $client_key;
	/**
	 * Whether the gateway is in test mode.
	 *
	 * @var bool
	 */
	public $testmode;
	/**
	 * Whether logging is enabled.
	 *
	 * @var bool
	 */
	public $logging;
	/**
	 * Whether tokenization is enabled.
	 *
	 * @var bool
	 */
	public $tokenization;
	/**
	 * Constructor for the Authorize.net eCheck gateway.
	 *
	 * Initializes the gateway by setting up properties, loading form fields and settings,
	 * Configuring credentials based on environment, and setting up helper configurations.
	 *
	 * @since 1.0.0
	 */
	public function __construct()
	{
		// Setup general properties.
		$this->setup_properties();
		// Load the form fields.
		$this->form_fields = $this->init_form_fields();
		// Load the settings.
		$this->init_settings();
		// Get setting values.
		$this->title                	= $this->get_option('title');
		$this->description          	= $this->get_option('description');
		$this->enabled              	= $this->get_option('enabled');
		$this->testmode             	= ANET_ENVIRONMENT_TEST === $this->get_option('testmode');
		$this->logging              	= ANET_YES === $this->get_option('logging');
		$this->tokenization            	= ANET_YES === $this->get_option('tokenization');
		// Use correct credentials based on selected environment.
		$this->login_id           = $this->testmode ? $this->get_option('login_id_test') : $this->get_option('login_id');
		$this->transaction_key    = $this->testmode ? $this->get_option('transaction_key_test') : $this->get_option('transaction_key');
		$this->client_key         = $this->testmode ? $this->get_option('client_key_test') : $this->get_option('client_key');
		// To fetch customer profile id.
		$this->meta_key = $this->get_customer_profile_meta_key(true);
		if ($this->client_key && $this->tokenization) {
			$this->supports[] = 'tokenization';
		}
		Anet_Setting_Helper::set_login_id($this->login_id);
		Anet_Setting_Helper::set_transaction_key($this->transaction_key);
		Anet_Setting_Helper::set_testmode($this->testmode);
		Anet_Setting_Helper::set_logging($this->logging);
	}
	/**
	 * Define WordPress hooks for the gateway.
	 *
	 * Registers all necessary WordPress actions and filters for the gateway functionality,
	 * including admin scripts, payment scripts, admin options processing, and tokenization hooks.
	 *
	 * @since 1.0.0
	 * @return void
	 */
	public function define_hooks()
	{
		// Load admin-env.js script.
		add_action('admin_enqueue_scripts', array($this, 'admin_scripts'));
		// Load accept.js script.
		add_action('wp_enqueue_scripts', array($this, 'payment_scripts'));
		// Save admin config.
		add_action('woocommerce_update_options_payment_gateways_' . $this->id, array($this, 'process_admin_options'));
		// Register admin hooks directly.
		require_once plugin_dir_path(__DIR__) . '../includes/admin/class-anet-echeck-admin.php';
		$this->plugin_admin = new Anet_ECheck_Admin($this->get_version(), $this);
		$settings = get_option(ANET_WOOCOMMERCE_UNDERSCORE . $this->get_id() . ANET_UNDERSCORE_SETTINGS, array());
		if (isset($settings['enabled']) && ANET_YES === $settings['enabled']) {
			add_action('admin_enqueue_scripts', array($this->plugin_admin, 'enqueue_scripts'));
		}
		// Tokenization.
		require_once(dirname(__FILE__) . '/../api/tokenization/class-anet-echeck-tokenization.php');
		$customer_profile = new Anet_ECheck_Tokenization();
		add_filter('woocommerce_account_payment_methods_columns', array($customer_profile, 'account_payment_methods_echeck_columns'));
		add_action('woocommerce_account_payment_methods_column_title', array($customer_profile, 'add_payment_method_echeck_title'));
		add_action('woocommerce_account_payment_methods_column_details', array($customer_profile, 'add_payment_method_echeck_details'));
		add_filter('woocommerce_payment_methods_list_item', array($customer_profile, 'add_payment_methods_list_item_echeck_edit'), ANET_ACTION_HOOK_DEFAULT_PRIORITY, ANET_VAL_TWO);
		add_action('wp_enqueue_scripts', array($customer_profile, 'enqueue_myaccount_echeck'));
		add_filter('woocommerce_get_query_vars', array($customer_profile, 'add_custom_query_vars_echeck'));
		add_action('woocommerce_account_edit-echeck_endpoint', array($customer_profile, 'render_edit_echeck'));
	}
	/**
	 * Setup general properties for the gateway.
	 *
	 * @return void
	 */
	protected function setup_properties()
	{
		$this->id                 = ANET_ECHECK_GATEWAY_ID;
		$this->method_title       = __('Authorize.net Payments for WooCommerce', 'authorize-net-payments-for-woocommerce');
		$this->method_description = __('To accept eCheck transactions, you must have eCheck enabled in your Authorize.net account.', 'authorize-net-payments-for-woocommerce');
		$this->has_fields         = true;
	}
	/**
	 * Initialize form fields for the payment method configuration.
	 *
	 * Creates and returns the array of form fields that will be displayed in the
	 * WooCommerce payment gateway settings page for this payment method.
	 *
	 * @since 1.0.0
	 * @return array Array of form field configurations
	 */
	public function init_form_fields()
	{
		$form_fields = array(
			'enabled'         => array(
				'title'       => __('Enable/Disable', 'authorize-net-payments-for-woocommerce'),
				'description' => __('Enabling/Disabling the gateway.', 'authorize-net-payments-for-woocommerce'),
				'desc_tip'    => true,
				'label'       => __('Enable eCheck', 'authorize-net-payments-for-woocommerce'),
				'type'        => 'checkbox',
				'default'     => ANET_NO,
			),
			'title'           => array(
				'title'       => __('Title', 'authorize-net-payments-for-woocommerce'),
				'description' => __('Payment method title that the customer will see in the checkout page.', 'authorize-net-payments-for-woocommerce'),
				'desc_tip'    => true,
				'type'        => 'text',
				'default'     => __('eCheck - Authorize.net', 'authorize-net-payments-for-woocommerce'),
			),
			'description'     => array(
				'title'       => __('Description', 'authorize-net-payments-for-woocommerce'),
				'type'        => 'textarea',
				'description' => __('Payment method description that the customer will see on your website.', 'authorize-net-payments-for-woocommerce'),
				'desc_tip'    => true,
				/* translators: %s: Payment method title */
				'default'     => sprintf(__('Pay with your eCheck via %s.', 'authorize-net-payments-for-woocommerce'), $this->method_title),
			),
			'testmode'         => array(
				'title'       => __('Environment', 'authorize-net-payments-for-woocommerce'),
				'label'       => __('Enable Sandbox Mode', 'authorize-net-payments-for-woocommerce'),
				'type'        => 'select',
				'description' => __('Select the gateway environment to use for transactions.', 'authorize-net-payments-for-woocommerce'),
				'desc_tip'    => true,
				'default'     => ANET_ENVIRONMENT_PRODUCTION,
				'options'  => array(
					ANET_ENVIRONMENT_PRODUCTION => __('Production', 'authorize-net-payments-for-woocommerce'),
					ANET_ENVIRONMENT_TEST       => __('Sandbox', 'authorize-net-payments-for-woocommerce'),
				),
			),
			// Production credentials.
			'login_id' => array(
				'title' => __('API Login ID (Production)', 'authorize-net-payments-for-woocommerce'),
				'type'  => 'text',
				'class' => 'environment-field production-field anet_echeck-field',
			),
			'transaction_key' => array(
				'title' => __('Transaction Key (Production)', 'authorize-net-payments-for-woocommerce'),
				'type'  => 'password',
				'class' => 'environment-field production-field anet_echeck-field',
			),
			'client_key' => array(
				'title' => __('Client Key (Production)', 'authorize-net-payments-for-woocommerce'),
				'type'  => 'password',
				'class' => 'environment-field production-field anet_echeck-field',
			),
			// Test credentials.
			'login_id_test' => array(
				'title' => __('API Login ID (Sandbox)', 'authorize-net-payments-for-woocommerce'),
				'type'  => 'text',
				'class' => 'environment-field test-field anet_echeck-field',
			),
			'transaction_key_test' => array(
				'title' => __('Transaction Key (Sandbox)', 'authorize-net-payments-for-woocommerce'),
				'type'  => 'password',
				'class' => 'environment-field test-field anet_echeck-field',
			),
			'client_key_test' => array(
				'title' => __('Client Key (Sandbox)', 'authorize-net-payments-for-woocommerce'),
				'type'  => 'password',
				'class' => 'environment-field test-field anet_echeck-field',
			),
			'logging'              		=> array(
				'title'       => __('Debug Mode', 'authorize-net-payments-for-woocommerce'),
				'label'       => __('Log debug messages', 'authorize-net-payments-for-woocommerce'),
				'type'        => 'select',
				/* translators: %s: Path to the log file */
				'description' => sprintf(__('Select to enable request/response logs. <code>%s</code>.', 'authorize-net-payments-for-woocommerce'), WC_Log_Handler_File::get_log_file_path('authorize-net-payments-for-woocommerce')),
				'desc_tip'    => true,
				'default'     => ANET_NO,
				'options'  => array(
					ANET_NO  => __('Off', 'authorize-net-payments-for-woocommerce'),
					ANET_YES => __('On', 'authorize-net-payments-for-woocommerce'),
				),
			),
			'tokenization' => array(
				'title'   => esc_html__('Tokenization', 'authorize-net-payments-for-woocommerce'),
				'label'   => esc_html__('Allow customers to securely save their payment details for future checkout.', 'authorize-net-payments-for-woocommerce'),
				'type'    => 'checkbox',
				'default' => ANET_NO,
				'description' => __('* To use this feature, you must have the Customer Information Manager (CIM) product enabled on your Authorize.net account.', 'authorize-net-payments-for-woocommerce'),
			),
		);
		return $form_fields;
	}
	/**
	 * Enqueue admin scripts for the payment gateway settings page.
	 *
	 * Loads JavaScript files needed for the admin configuration interface,
	 * specifically for environment toggle functionality.
	 *
	 * @since 1.0.0
	 * @return void
	 */
	public function admin_scripts()
	{
		$screen = get_current_screen();
		if (false !== strpos($screen->id, 'woocommerce_page_wc-settings')) {
			wp_enqueue_script(
				'anet-admin-env-toggle',
				plugins_url('../../assets/js/admin/anet-admin-env.js', __FILE__),
				array('jquery'),
				ANET_PLUGIN_VERSION,
				true
			);
		}
	}
	/**
	 * Check if the gateway is available for use.
	 *
	 * @since 1.0.0
	 * @return bool True if available, false otherwise.
	 */
	public function is_available()
	{
		$is_available = parent::is_available();
		// Require SSL in production mode.
		if ($is_available && ! $this->testmode && ! is_ssl()) {
			$is_available = false;
		}
		return $is_available;
	}
	/**
	 * Display the payment form on checkout page.
	 *
	 * Renders the eCheck payment form including description, saved payment methods
	 * for logged-in users, and the payment form template.
	 *
	 * @since 1.0.0
	 * @return void
	 */
	public function payment_fields()
	{
		if (empty($this->client_key) || empty($this->transaction_key) || empty($this->login_id)) {
			echo '<div class="woocommerce-error">Unable to load the payment form. Please contact customer care for any assistance.</div>';
			return;
		}
		$helper = new Anet_Api_Helper(ANET_PAYMENT_TYPE_ECHECK);
		$helper->set_credentials($this->login_id, $this->transaction_key, $this->testmode);
		if (!$helper->is_authentication_valid()) {
			echo '<div class="woocommerce-error">Unable to load the payment form. Please contact customer care for any assistance.</div>';
			return;
		}
		if ($this->description  && is_checkout()) {
			echo wp_kses_post(wpautop(wp_kses_post($this->description)));
		}
		if (is_user_logged_in() && is_checkout()) {
			$user_id = get_current_user_id();
			$tokens = WC_Payment_Tokens::get_customer_tokens($user_id, $this->id);
			if (!empty($tokens)) {
				$this->saved_payment_methods();
			}
		}
		require_once(plugin_dir_path(__FILE__) . '../../templates/checkout/anet-echeck.php');
	}
	/**
	 * Add payment method to customer account section.
	 *
	 * Handles the process of adding a new eCheck payment method to the customer's
	 * account. Creates a customer profile with Authorize.net using the submitted
	 * payment data and user billing information.
	 *
	 * @since 1.0.0
	 * @return array Response array containing result status and redirect URL
	 */
	public function add_payment_method()
	{
		$user_id = get_current_user_id();
		$source = $this->get_payment_source(get_current_user_id(), false);
		$payment_args = array(
			'email' => get_user_meta($user_id, 'billing_email', true),
			'opaqueData' => array(
				'dataDescriptor' => wc_clean($source->source['descriptor']),
				'dataValue'      => wc_clean($source->source['nonce']),
			),
			'billTo' => array(
				'firstName' => get_user_meta($user_id, 'billing_first_name', true),
				'lastName' => get_user_meta($user_id, 'billing_last_name', true),
				'company' => get_user_meta($user_id, 'billing_company', true),
				'address' => get_user_meta($user_id, 'billing_address_1', true),
				'city' => get_user_meta($user_id, 'billing_city', true),
				'state' => get_user_meta($user_id, 'billing_state', true),
				'zip' => get_user_meta($user_id, 'billing_postcode', true),
				'country' => get_user_meta($user_id, 'billing_country', true),
			),
		);
		$response = [
			'result' => "failure",
			'redirect' => wc_get_endpoint_url(ANET_ADD_PAYMENT_METHODS)
		];
		$result = Anet_ECheck_Api::create_customer_profile($user_id, $payment_args);
		if (true === $result['result']) {
			$response = [
				'result' => "success",
				'redirect' => wc_get_account_endpoint_url(ANET_PAYMENT_METHODS)
			];
		}
		return $response;
	}
	/**
	 * Enqueue payment scripts for the eCheck gateway.
	 *
	 * Loads and enqueues JavaScript files required for Accept.js integration
	 * on cart, checkout, and add payment method pages. Includes the Accept.js
	 * library and custom eCheck handling scripts.
	 *
	 * @since 3.1.0
	 * @version 4.0.0
	 * @return void
	 */
	public function payment_scripts()
	{
		if (! $this->client_key || ! is_cart() && ! is_checkout() && ! is_add_payment_method_page()) {
			return;
		}
		$js_url = ($this->testmode ? ANET_ACCEPT_TEST_DOMAIN : ANET_ACCEPT_PROD_DOMAIN);
		wp_enqueue_script('anet-accept', $js_url, array(), null, true); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
		wp_enqueue_script('woocommerce_anet', plugin_dir_url(__FILE__) . '../../assets/js/frontend/anet-accept.js', array('jquery-payment', 'anet-accept', 'wp-i18n'), ANET_PLUGIN_VERSION, true);
	    wp_set_script_translations('woocommerce_anet', 'authorize-net-payments-for-woocommerce');
		wp_localize_script(
			'woocommerce_anet',
			'anet_params',
			array(
				'anet_error'      		   => ANET_STRING_ERROR,
				'anet_space'      		   => ANET_SPACE,
			)
		);
		/**
		 * Filter the JavaScript parameters passed to the eCheck payment form.
		 *
		 * Allows modification of parameters sent to Accept.js for eCheck processing,
		 * including API endpoint URLs, client configuration, and form validation settings.
		 *
		 * @since 1.0.0
		 * @param array $params JavaScript parameters for eCheck payment processing.
		 */
		wp_localize_script('woocommerce_anet', 'anet_echeck_params', apply_filters('anet_echeck_params', $this->javascript_params())); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
	}
	/**
	 * Generate JavaScript parameters for Accept.js integration.
	 *
	 * Creates an array of parameters that will be localized to JavaScript for
	 * use with Authorize.net's Accept.js payment form.
	 *
	 * @since 1.0.0
	 * @return array Array of JavaScript parameters for Accept.js
	 */
	public function javascript_params()
	{
		$anet_echeck_params = array(
			'login_id'              => $this->login_id,
			'client_key'            => $this->client_key,
			'test_mode'				=> $this->testmode,
			'accept_js_url'         => ($this->testmode ? ANET_ACCEPT_TEST_DOMAIN : ANET_ACCEPT_PROD_DOMAIN)
		);
		// If we're on the pay page we need to pass accept.js the address of the order.
		$order_id = absint(get_query_var('order-pay'));
		$order = wc_get_order($order_id);
		if ($order) { // Ensure $order is valid before accessing its methods.
			$anet_echeck_params['billing_first_name'] = $order->get_billing_first_name();
			$anet_echeck_params['billing_last_name']  = $order->get_billing_last_name();
		} else {
			$anet_echeck_params['billing_first_name'] = '';
			$anet_echeck_params['billing_last_name']  = '';
		}
		return $anet_echeck_params;
	}
	/**
	 * Generate the request for the payment.
	 *
	 * Creates and returns the payment request arguments array for Authorize.net API
	 * transaction processing, including payment data, billing/shipping addresses, 
	 * line items, and transaction settings.
	 *
	 * @since 1.0.0
	 * @param WC_Order $order The WooCommerce order object.
	 * @return array Array of payment request arguments for Authorize.net API
	 * @throws Exception If payment source is missing or invalid.
	 */
	protected function generate_payment_request_args($order)
	{
		// Fetch the payment source using the get_payment_source method.
		$source = $this->get_payment_source(get_current_user_id(), false);
		// Validate the source.
		if (empty($source->source)) {
			throw new Exception(esc_html__('Payment source is missing. Please try again.', 'authorize-net-payments-for-woocommerce'));
		}
		// Determine the source type and set the payment data accordingly.
		$payment_data = array();
		if ('opaque_data' === $source->source['source_type']) {
			// Create the payment data for a payment nonce.
			$payment_data = array(
				'opaqueData' => array(
					'dataDescriptor' => wc_clean($source->source['descriptor']),
					'dataValue'      => wc_clean($source->source['nonce']),
				),
			);
		} elseif ('echeck_stored_token' === $source->source['source_type']) {
			// Create the payment data for a payment nonce.
			$payment_data = array(
				'tokenData' => array(
					'customerProfileId' => wc_clean($source->source['customerProfileId']),
					'paymentProfileId'  => wc_clean($source->source['paymentProfileId']),
				),
			);
		} else {
			throw new Exception(esc_html__('Invalid payment source type.', 'authorize-net-payments-for-woocommerce'));
		}
		// Set the customer's Bill To address.
		$billing_address = array(
			'firstName'   => substr($order->get_billing_first_name(), 0, 50),
			'lastName'    => substr($order->get_billing_last_name(), 0, 50),
			'company' => substr($order->get_billing_company(), 0, 50),
			'address'     => substr(trim($order->get_billing_address_1() . ' ' . $order->get_billing_address_2()), 0, 60),
			'city'        => substr($order->get_billing_city(), 0, 40),
			'state'       => substr($order->get_billing_state(), 0, 40),
			'zip'         => substr($order->get_billing_postcode(), 0, 20),
			'country'     => substr($order->get_billing_country(), 0, 60),
			'phoneNumber' => substr($order->get_billing_phone(), 0, 25),
		);
		// Set the customer's Ship To address.
		$shipping_address = array(
			'firstName' => substr($order->get_shipping_first_name(), 0, 50),
			'lastName'  => substr($order->get_shipping_last_name(), 0, 50),
			'company'  => substr($order->get_shipping_company(), 0, 50),
			'address'   => substr(trim($order->get_shipping_address_1() . ' ' . $order->get_shipping_address_2()), 0, 60),
			'city'      => substr($order->get_shipping_city(), 0, 40),
			'state'     => substr($order->get_shipping_state(), 0, 40),
			'zip'       => substr($order->get_shipping_postcode(), 0, 20),
			'country'   => substr($order->get_shipping_country(), 0, 60),
		);
		// Fetch line items using the get_order_line_items method.
		$line_items = $this->get_order_line_items($order);
		$anet_line_items = array();
		foreach ($line_items as $line_item) {
			$anet_line_items[] = array(
				'itemId'      => substr($line_item->id, 0, 31),
				'name'        => substr($line_item->name, 0, 31),
				'description' => substr($line_item->description, 0, 255),
				'quantity'    => $line_item->quantity,
				'unitPrice'   => $line_item->unit_price,
				'taxable'     => $line_item->taxable,
				'totalAmount' => $line_item->line_total,
			);
		}
		// Add values for transaction settings.
		$transaction_settings = array(
			array(
				'settingName'  => 'duplicateWindow',
				'settingValue' => '60',
			)
		);
		// Add basic custom fields.
		$custom_fields = array(
			array(
				'name'  => 'Customer Name',
				'value' => sanitize_text_field($order->get_billing_first_name()) . ' ' . sanitize_text_field($order->get_billing_last_name()),
			),
			array(
				'name'  => 'Customer Email',
				'value' => sanitize_email($order->get_billing_email()),
			)
		);
		$customer_id = (is_user_logged_in() ? get_current_user_id() : 'guest_' . time());
		// Fetch the currency code dynamically from WooCommerce.
		$currency_code = get_woocommerce_currency();
		// Create complete request args (strictly follow ordering of request arguments).
		$request_args = array(
			'refId'              => $order->get_id(),
			'transactionRequest' => array(
				'transactionType'     => ANET_TRANSACTION_TYPE_AUTH_CAPTURE,
				'amount'              => wc_clean($order->get_total()),
				'currencyCode'        => $currency_code,
				'payment'             => $payment_data,
				'order'               => array(
					'invoiceNumber' => $order->get_order_number(),
				),
				'lineItems'           => $anet_line_items,
				'tax'                 => array(
					'amount' => $order->get_total_tax(),
				),
				'shipping'            => array(
					'amount' => $order->get_shipping_total(),
				),
				'customer'            => array(
					'id'    => $customer_id,
					'email' => substr($order->get_billing_email(), 0, 255),
				),
				'billTo'              => $billing_address,
				'shipTo'              => $shipping_address,
				'customerIP'          => WC_Geolocation::get_ip_address(),
				'transactionSettings' => array(
					'setting' => $transaction_settings,
				),
				'userFields'          => array(
					'userField' => $custom_fields,
				),
			),
		);
		/**
		 * Filter payment request arguments before sending to Authorize.net.
		 *
		 * Allows modification of the complete payment request data including transaction details,
		 * line items, customer information, and billing data before API submission.
		 *
		 * @since 1.0.0
		 * @param array $request_args Complete payment request arguments array.
		 * @param WC_Order $order The WooCommerce order object.
		 */
		$request_args = apply_filters('wc_anet_generate_payment_request_args', $request_args, $order); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
		return $request_args;
	}
	/**
	 * Get formatted line items from WooCommerce order.
	 *
	 * Extracts and formats order line items for use in Authorize.net transaction requests.
	 * Includes product details, metadata, pricing, and tax information.
	 *
	 * @since 1.0.0
	 * @param WC_Order $order The WooCommerce order object.
	 * @return array Array of formatted line item objects
	 */
	public function get_order_line_items($order)
	{
		$line_items = array();
		foreach ($order->get_items() as $id => $item) {
			$line_item = new \stdClass();
			$product   = $item->get_product();
			$name      = $item->get_name();
			$quantity  = $item->get_quantity();
			$sku       = $product instanceof \WC_Product ? $product->get_sku() : '';
			$item_desc = array();
			// Add SKU to description if available.
			if (!empty($sku)) {
				$item_desc[] = sprintf('SKU: %s', $sku);
			}
			// Get formatted meta data for the item.
			$item_meta = $this->get_item_formatted_meta_data($item, ANET_UNDERSCORE, true);
			if (!empty($item_meta)) {
				foreach ($item_meta as $meta) {
					$item_desc[] = sprintf('%s: %s', $meta['label'], $meta['value']);
				}
			}
			$item_desc = implode(', ', $item_desc);
			// Populate line item details.
			$line_item->id          = $id;
			$line_item->name        = htmlentities($name, ENT_QUOTES, 'UTF-8', false);
			$line_item->description = htmlentities($item_desc, ENT_QUOTES, 'UTF-8', false);
			$line_item->quantity    = $quantity;
			$line_item->unit_price  = $order->get_item_total($item, false); // Unit price excluding tax.
			$line_item->line_total  = $order->get_line_total($item); // Total price for the line item.
			$line_item->taxable     = $item->get_tax_class() ? true : false; // Check if the item is taxable.
			$line_item->meta        = $item_meta;
			$line_item->product     = is_object($product) ? $product : null;
			$line_item->item        = $item;
			$line_items[] = $line_item;
		}
		return $line_items;
	}
	/**
	 * Get formatted meta data for an order item.
	 *
	 * Extracts and formats meta data from order items for display purposes.
	 * Handles both new WC_Order_Item objects and legacy order item arrays.
	 *
	 * @since 1.0.0
	 * @param WC_Order_Item|array $item Order item object or array.
	 * @param string $hide_prefix Prefix to hide in meta keys (default: '_').
	 * @param bool $include_all Whether to include all meta data (default: false).
	 * @return array Array of formatted meta data
	 */
	public function get_item_formatted_meta_data($item, $hide_prefix = ANET_UNDERSCORE, $include_all = false)
	{
		if ($item instanceof \WC_Order_Item) {
			$meta_data = $item->get_formatted_meta_data($hide_prefix, $include_all);
			$item_meta = array();
			foreach ($meta_data as $meta) {
				$item_meta[] = array(
					'label' => $meta->display_key,
					'value' => $meta->value,
				);
			}
		} else {
			$item_meta = new \WC_Order_Item_Meta($item);
			$item_meta = $item_meta->get_formatted($hide_prefix);
		}
		return $item_meta;
	}
	/**
	 * Get the payment source from the submitted form data.
	 *
	 * Determines the type of payment source (opaque data, stored token, or eCheck details)
	 * and returns the appropriate payment source object for transaction processing.
	 *
	 * @since 1.0.0
	 * @param int $user_id The user ID for token retrieval.
	 * @param bool $force_customer Whether to force customer creation (default: false).
	 * @return object Payment source object with token_id, customer, and source properties
	 * @throws Exception If no valid payment source is found.
	 */
	protected function get_payment_source($user_id, $force_customer = false)
	{
		// Check for opaque data (nonce).
		if ($this->is_opaque_data_provided()) {
			$opaque_source = (object) array(
				'token_id' => false,
				'customer' => false,
				'source'   => $this->get_opaque_data(),
			);
			return $opaque_source;
		}
		if ($this->is_stored_card_transaction()) {
			$token_source = (object) array(
				'token_id' => true,
				'customer' => false,
				'source'   => $this->get_token_details($user_id),
			);
			return $token_source;
		}
		throw new Exception(esc_html__('Payment processing failed. Please ensure JavaScript is enabled and try again.', 'authorize-net-payments-for-woocommerce'));
	}
	/**
	 * Check if stored card transaction is provided.
	 *
	 * Determines whether the current request contains data for processing
	 * a payment using a stored payment token/method.
	 *
	 * @since 1.0.0
	 * @return bool True if stored card transaction data is present, false otherwise
	 */
	public function is_stored_card_transaction()
	{
		$has_token = isset($_POST['token']) || isset($_POST['wc-anet_echeck-payment-token']); // phpcs:ignore WordPress.Security.NonceVerification.Missing
		return $has_token;
	}
	/**
	 * Check if opaque data (payment nonce) is provided.
	 *
	 * Determines whether the current request contains Accept.js opaque data
	 * (payment nonce and data descriptor) required for secure payment processing.
	 *
	 * @since 1.0.0
	 * @return bool True if opaque data is present, false otherwise
	 */
	public function is_opaque_data_provided()
	{
		$has_opaque_data = isset($_POST['anet_nonce']) && isset($_POST['anet_data_descriptor']); // phpcs:ignore WordPress.Security.NonceVerification.Missing
		return $has_opaque_data;
	}
	/**
	 * Get token details for stored eCheck payment method.
	 *
	 * Retrieves and formats stored payment token details including customer profile ID
	 * and payment profile ID for use with Authorize.net customer profiles.
	 *
	 * @since 1.0.0
	 * @param int $user_id The user ID for retrieving token details.
	 * @return array Array containing token details and payment profile information
	 */
	protected function get_token_details($user_id)
	{
		$token = false;
		if (isset($_POST['token'])) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
			$token = sanitize_text_field(wp_unslash($_POST['token'])); // phpcs:ignore WordPress.Security.NonceVerification.Missing
		} elseif (isset($_POST['wc-anet_echeck-payment-token'])) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
			$token = sanitize_text_field(wp_unslash($_POST['wc-anet_echeck-payment-token'])); // phpcs:ignore WordPress.Security.NonceVerification.Missing
		}
		$token_details = array(
			'token'      => $token,
			'customerProfileId' => get_user_meta($user_id, $this->meta_key, true),
			'paymentProfileId' => WC_Payment_Tokens::get_tokens(['token_id' => $token, 'user_id' => $user_id, 'gateway_id' => ANET_ECHECK_GATEWAY_ID])[$token]->get_token(),
			'source_type' => 'echeck_stored_token',
		);
		return $token_details;
	}
	/**
	 * Get opaque data (payment nonce) details from Accept.js.
	 *
	 * Extracts and formats the payment nonce and data descriptor submitted
	 * by Accept.js for secure payment processing.
	 *
	 * @since 1.0.0
	 * @return array Array containing nonce, descriptor, and source type
	 */
	protected function get_opaque_data()
	{
		$opaque_data = array(
			'nonce'      => isset($_POST['anet_nonce']) ? sanitize_text_field(wp_unslash($_POST['anet_nonce'])) : '', // phpcs:ignore WordPress.Security.NonceVerification.Missing
			'descriptor' => isset($_POST['anet_data_descriptor']) ? sanitize_text_field(wp_unslash($_POST['anet_data_descriptor'])) : '', // phpcs:ignore WordPress.Security.NonceVerification.Missing
			'source_type' => 'opaque_data',
		);
		return $opaque_data;
	}
	/**
	 * Validate payment fields on checkout.
	 * 
	 * @return bool True if validation passes, false otherwise.
	 */
	public function validate_fields()
	{
		$valid = true;
		// Get billing address from checkout data.
		$billing_address_1 = '';
		$raw_billing_address_1 = '';
		if (isset($_POST['billing_address_1'])) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
			$raw_billing_address_1 = wp_unslash($_POST['billing_address_1']); // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
			$billing_address_1 = sanitize_text_field($raw_billing_address_1);
		}
		$billing_address_2 = '';
		$raw_billing_address_2 = '';
		if (isset($_POST['billing_address_2'])) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
			$raw_billing_address_2 = wp_unslash($_POST['billing_address_2']); // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
			$billing_address_2 = sanitize_text_field($raw_billing_address_2);
		}
		// Get billing first and last name.
		$billing_first_name = '';
		$raw_billing_first_name = '';
		if (isset($_POST['billing_first_name'])) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
			$raw_billing_first_name = wp_unslash($_POST['billing_first_name']); // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
			$billing_first_name = sanitize_text_field($raw_billing_first_name);
		}
		$billing_last_name = '';
		$raw_billing_last_name = '';
		if (isset($_POST['billing_last_name'])) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
			$raw_billing_last_name = wp_unslash($_POST['billing_last_name']); // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
			$billing_last_name = sanitize_text_field($raw_billing_last_name);
		}
		$billing_company = '';
		$raw_billing_company = '';
		if (isset($_POST['billing_company'])) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
			$raw_billing_company = wp_unslash($_POST['billing_company']); // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
			$billing_company = sanitize_text_field($raw_billing_company);
		}
		// Check if ship to different address is enabled.
		$ship_to_different_address = isset($_POST['ship_to_different_address']) && sanitize_text_field(wp_unslash($_POST['ship_to_different_address'])); // phpcs:ignore WordPress.Security.NonceVerification.Missing
		// Get shipping address from checkout data (only if shipping to different address).
		$shipping_address_1 = '';
		$shipping_address_2 = '';
		$shipping_first_name = '';
		$shipping_last_name = '';
		$shipping_company = '';
		$shipping_postcode = '';
		$raw_shipping_address_1 = '';
		$raw_shipping_address_2 = '';
		$raw_shipping_first_name = '';
		$raw_shipping_last_name = '';
		$raw_shipping_company = '';
		$raw_shipping_postcode = '';
		if ($ship_to_different_address) {
			if (isset($_POST['shipping_address_1'])) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
				$raw_shipping_address_1 = wp_unslash($_POST['shipping_address_1']); // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
				$shipping_address_1 = sanitize_text_field($raw_shipping_address_1);
			}
			if (isset($_POST['shipping_address_2'])) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
				$raw_shipping_address_2 = wp_unslash($_POST['shipping_address_2']); // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
				$shipping_address_2 = sanitize_text_field($raw_shipping_address_2);
			}
			// Get shipping first and last name.
			if (isset($_POST['shipping_first_name'])) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
				$raw_shipping_first_name = wp_unslash($_POST['shipping_first_name']); // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
				$shipping_first_name = sanitize_text_field($raw_shipping_first_name);
			}
			if (isset($_POST['shipping_last_name'])) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
				$raw_shipping_last_name = wp_unslash($_POST['shipping_last_name']); // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
				$shipping_last_name = sanitize_text_field($raw_shipping_last_name);
			}
			if (isset($_POST['shipping_company'])) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
				$raw_shipping_company = wp_unslash($_POST['shipping_company']); // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
				$shipping_company = sanitize_text_field($raw_shipping_company);
			}
			if (isset($_POST['shipping_postcode'])) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
				$raw_shipping_postcode = wp_unslash($_POST['shipping_postcode']); // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
				$shipping_postcode = sanitize_text_field($raw_shipping_postcode);
			}
			// Get shipping city.
			if (isset($_POST['shipping_city'])) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
				$raw_shipping_city = wp_unslash($_POST['shipping_city']); // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
				$shipping_city = sanitize_text_field($raw_shipping_city);
			}
		}
		// Get billing postcode.
		$billing_postcode = '';
		$raw_billing_postcode = '';
		if (isset($_POST['billing_postcode'])) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
			$raw_billing_postcode = wp_unslash($_POST['billing_postcode']); // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
			$billing_postcode = sanitize_text_field($raw_billing_postcode);
		}
		// Get billing city.
		$billing_city = '';
		$raw_billing_city = '';
		if (isset($_POST['billing_city'])) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
			$raw_billing_city = wp_unslash($_POST['billing_city']); // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
			$billing_city = sanitize_text_field($raw_billing_city);
		}
		// Get billing phone.
		$billing_phone = '';
		$raw_billing_phone = '';
		if (isset($_POST['billing_phone'])) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
			$raw_billing_phone = wp_unslash($_POST['billing_phone']); // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
			$billing_phone = sanitize_text_field($raw_billing_phone);
		}
		// Validate billing first name.
		if (!empty($raw_billing_first_name)) {
			// Check for angular brackets specifically in raw input.
			if (strpos($raw_billing_first_name, '<') !== false || strpos($raw_billing_first_name, '>') !== false) {
				// Keep original input with angular brackets to show in field for error context.
				$billing_first_name = $raw_billing_first_name;
				wc_add_notice(__('Billing First name is invalid. Special characters are not allowed.', 'authorize-net-payments-for-woocommerce'), 'error');
				$valid = false;
			} elseif (!preg_match('/^[a-zA-Z\s\-\.]+$/', $billing_first_name)) {
				wc_add_notice(__('Billing First name is invalid. Special characters are not allowed.', 'authorize-net-payments-for-woocommerce'), 'error');
				$valid = false;
			}
		}
		// Validate billing last name.
		if (!empty($raw_billing_last_name)) {
			// Check for angular brackets specifically in raw input.
			if (strpos($raw_billing_last_name, '<') !== false || strpos($raw_billing_last_name, '>') !== false) {
				// Keep original input with angular brackets to show in field for error context.
				$billing_last_name = $raw_billing_last_name;
				wc_add_notice(__('Billing Last name is invalid. Special characters are not allowed.', 'authorize-net-payments-for-woocommerce'), 'error');
				$valid = false;
			} elseif (!preg_match('/^[a-zA-Z\s\-\.]+$/', $billing_last_name)) {
				wc_add_notice(__('Billing Last name is invalid. Special characters are not allowed.', 'authorize-net-payments-for-woocommerce'), 'error');
				$valid = false;
			}
		}
		// Validate billing company name.
		if (!empty($raw_billing_company)) {
			// Check for angular brackets specifically in raw input.
			if (strpos($raw_billing_company, '<') !== false || strpos($raw_billing_company, '>') !== false) {
				// Keep original input with angular brackets to show in field for error context.
				$billing_company = $raw_billing_company;
				wc_add_notice(__('Invalid Billing Company name. Special characters are not allowed.', 'authorize-net-payments-for-woocommerce'), 'error');
				$valid = false;
			} elseif (!preg_match('/^[a-zA-Z\s\-\.]+$/', $billing_company)) {
				wc_add_notice(__('Invalid Billing Company name. Special characters are not allowed.', 'authorize-net-payments-for-woocommerce'), 'error');
				$valid = false;
			}
		}
		// Only validate if billing address is provided.
		if (!empty($raw_billing_address_1)) {
			// Check for angular brackets specifically in raw input.
			if (strpos($raw_billing_address_1, '<') !== false || strpos($raw_billing_address_1, '>') !== false) {
				// Keep original input with angular brackets to show in field for error context.
				$billing_address_1 = $raw_billing_address_1;
				wc_add_notice(__('Billing Street address line 1 is invalid. Special characters are not allowed.', 'authorize-net-payments-for-woocommerce'), 'error');
				$valid = false;
			} elseif (!preg_match('/^[a-zA-Z0-9\s\-\.]+$/', $billing_address_1)) {
				wc_add_notice(__('Billing Street address line 1 is invalid. Special characters are not allowed.', 'authorize-net-payments-for-woocommerce'), 'error');
				$valid = false;
			} elseif (strlen(trim($billing_address_1)) < 5) {
				wc_add_notice(__('Please enter a complete billing Street address (minimum 5 characters).', 'authorize-net-payments-for-woocommerce'), 'error');
				$valid = false;
			}
		}
		// Validate billing address 2.
		if (!empty($raw_billing_address_2)) {
			// Check for angular brackets specifically in raw input.
			if (strpos($raw_billing_address_2, '<') !== false || strpos($raw_billing_address_2, '>') !== false) {
				// Keep original input with angular brackets to show in field for error context.
				$billing_address_2 = $raw_billing_address_2;
				wc_add_notice(__('Billing Street address is invalid. Special characters are not allowed.', 'authorize-net-payments-for-woocommerce'), 'error');
				$valid = false;
			} elseif (!preg_match('/^[a-zA-Z0-9\s\-\.]+$/', $billing_address_2)) {
				wc_add_notice(__('Billing Street address is invalid. Special characters are not allowed.', 'authorize-net-payments-for-woocommerce'), 'error');
				$valid = false;
			}
		}
		// Validate shipping fields only if ship to different address is checked.
		if ($ship_to_different_address) {
			// Validate shipping first name.
			if (!empty($raw_shipping_first_name)) {
				// Check for angular brackets specifically in raw input.
				if (strpos($raw_shipping_first_name, '<') !== false || strpos($raw_shipping_first_name, '>') !== false) {
					// Keep original input with angular brackets to show in field for error context.
					$shipping_first_name = $raw_shipping_first_name;
					wc_add_notice(__('Shipping First name is invalid. Special characters are not allowed.', 'authorize-net-payments-for-woocommerce'), 'error');
					$valid = false;
				} elseif (!preg_match('/^[a-zA-Z\s\-\.]+$/', $shipping_first_name)) {
					wc_add_notice(__('Shipping First name is invalid. Special characters are not allowed.', 'authorize-net-payments-for-woocommerce'), 'error');
					$valid = false;
				}
			}
			// Validate shipping last name.
			if (!empty($raw_shipping_last_name)) {
				// Check for angular brackets specifically in raw input.
				if (strpos($raw_shipping_last_name, '<') !== false || strpos($raw_shipping_last_name, '>') !== false) {
					// Keep original input with angular brackets to show in field for error context.
					$shipping_last_name = $raw_shipping_last_name;
					wc_add_notice(__('Shipping Last name is invalid. Special characters are not allowed.', 'authorize-net-payments-for-woocommerce'), 'error');
					$valid = false;
				} elseif (!preg_match('/^[a-zA-Z\s\-\.]+$/', $shipping_last_name)) {
					wc_add_notice(__('Shipping Last name is invalid. Special characters are not allowed.', 'authorize-net-payments-for-woocommerce'), 'error');
					$valid = false;
				}
			}
			// Validate shipping address 1.
			if (!empty($raw_shipping_address_1)) {
				// Check for angular brackets specifically in raw input.
				if (strpos($raw_shipping_address_1, '<') !== false || strpos($raw_shipping_address_1, '>') !== false) {
					// Keep original input with angular brackets to show in field for error context.
					$shipping_address_1 = $raw_shipping_address_1;
					wc_add_notice(__('Shipping Street address is invalid. Special characters are not allowed.', 'authorize-net-payments-for-woocommerce'), 'error');
					$valid = false;
				} elseif (!preg_match('/^[a-zA-Z0-9\s\-\.]+$/', $shipping_address_1)) {
					wc_add_notice(__('Shipping Street address is invalid. Special characters are not allowed.', 'authorize-net-payments-for-woocommerce'), 'error');
					$valid = false;
				} elseif (strlen(trim($shipping_address_1)) < 5) {
					wc_add_notice(__('Please enter a complete shipping Street address (minimum 5 characters).', 'authorize-net-payments-for-woocommerce'), 'error');
					$valid = false;
				}
			}
			// Validate shipping address 2.
			if (!empty($raw_shipping_address_2)) {
				// Check for angular brackets specifically in raw input.
				if (strpos($raw_shipping_address_2, '<') !== false || strpos($raw_shipping_address_2, '>') !== false) {
					// Keep original input with angular brackets to show in field for error context.
					$shipping_address_2 = $raw_shipping_address_2;
					wc_add_notice(__('Shipping Street address is invalid. Special characters are not allowed.', 'authorize-net-payments-for-woocommerce'), 'error');
					$valid = false;
				} elseif (!preg_match('/^[a-zA-Z0-9\s\-\.]+$/', $shipping_address_2)) {
					wc_add_notice(__('Shipping Street address is invalid. Special characters are not allowed.', 'authorize-net-payments-for-woocommerce'), 'error');
					$valid = false;
				}
			}
			// Validate shipping company name.
			if (!empty($raw_shipping_company)) {
				// Check for angular brackets specifically in raw input.
				if (strpos($raw_shipping_company, '<') !== false || strpos($raw_shipping_company, '>') !== false) {
					// Keep original input with angular brackets to show in field for error context.
					$shipping_company = $raw_shipping_company;
					wc_add_notice(__('Invalid Shipping Company name. Special characters are not allowed.', 'authorize-net-payments-for-woocommerce'), 'error');
					$valid = false;
				} elseif (!preg_match('/^[a-zA-Z\s\-\.]+$/', $shipping_company)) {
					// Validate shipping company name - only allow alphabetic, spaces, hyphens, and periods.
					wc_add_notice(__('Invalid Shipping Company name. Special characters are not allowed.', 'authorize-net-payments-for-woocommerce'), 'error');
					$valid = false;
				}
			}
			// Validate shipping postcode.
			if (!empty($raw_shipping_postcode)) {
				// Check for angular brackets specifically in raw input.
				if (strpos($raw_shipping_postcode, '<') !== false || strpos($raw_shipping_postcode, '>') !== false) {
					// Keep original input with angular brackets to show in field for error context.
					$shipping_postcode = $raw_shipping_postcode;
					wc_add_notice(__('Shipping ZIP Code is invalid. Special characters are not allowed.', 'authorize-net-payments-for-woocommerce'), 'error');
					$valid = false;
				} elseif (!preg_match('/^[0-9]{1,6}$/', $shipping_postcode)) {
					wc_add_notice(__('Shipping ZIP Code is invalid. Only numeric values (up to 6 digits) are allowed.', 'authorize-net-payments-for-woocommerce'), 'error');
					$valid = false;
				}
			}
		}
		// Validate billing postcode.
		if (!empty($raw_billing_postcode)) {
			// Check for angular brackets specifically in raw input.
			if (strpos($raw_billing_postcode, '<') !== false || strpos($raw_billing_postcode, '>') !== false) {
				// Keep original input with angular brackets to show in field for error context.
				$billing_postcode = $raw_billing_postcode;
				wc_add_notice(__('Billing ZIP Code is invalid. Special characters are not allowed.', 'authorize-net-payments-for-woocommerce'), 'error');
				$valid = false;
			} elseif (!preg_match('/^[0-9]{1,6}$/', $billing_postcode)) {
				wc_add_notice(__('Billing ZIP Code is invalid. Only numeric values (up to 6 digits) are allowed.', 'authorize-net-payments-for-woocommerce'), 'error');
				$valid = false;
			}
		}
		// Validate billing city.
		if (!empty($raw_billing_city)) {
			// Check for angular brackets specifically in raw input.
			if (strpos($raw_billing_city, '<') !== false || strpos($raw_billing_city, '>') !== false) {
				// Keep original input with angular brackets to show in field for error context.
				$billing_city = $raw_billing_city;
				wc_add_notice(__('Billing Town / City is invalid. Special characters are not allowed.', 'authorize-net-payments-for-woocommerce'), 'error');
				$valid = false;
			} elseif (!preg_match('/^[a-zA-Z\s\-\.]+$/', $billing_city)) {
				wc_add_notice(__('Billing Town / City is invalid. Special characters are not allowed.', 'authorize-net-payments-for-woocommerce'), 'error');
				$valid = false;
			}
		}
		// Validate billing phone.
		if (!empty($raw_billing_phone)) {
			// Check for angular brackets specifically in raw input.
			if (strpos($raw_billing_phone, '<') !== false || strpos($raw_billing_phone, '>') !== false) {
				// Keep original input with angular brackets to show in field for error context.
				$billing_phone = $raw_billing_phone;
				wc_add_notice(__('Billing Phone is invalid. Special characters are not allowed.', 'authorize-net-payments-for-woocommerce'), 'error');
				$valid = false;
			} elseif (!preg_match('/^[a-zA-Z0-9\s\-\+\(\)\.]+$/', $billing_phone)) {
				wc_add_notice(__('Billing Phone is invalid. Special characters are not allowed.', 'authorize-net-payments-for-woocommerce'), 'error');
				$valid = false;
			}
		}
		// Validate shipping city.
		if (!empty($raw_shipping_city)) {
			// Check for angular brackets specifically in raw input.
			if (strpos($raw_shipping_city, '<') !== false || strpos($raw_shipping_city, '>') !== false) {
				// Keep original input with angular brackets to show in field for error context.
				$shipping_city = $raw_shipping_city;
				wc_add_notice(__('Shipping Town / City is invalid. Special characters are not allowed.', 'authorize-net-payments-for-woocommerce'), 'error');
				$valid = false;
			} elseif (!preg_match('/^[a-zA-Z\s\-\.]+$/', $shipping_city)) {
				wc_add_notice(__('Shipping Town / City is invalid. Special characters are not allowed.', 'authorize-net-payments-for-woocommerce'), 'error');
				$valid = false;
			}
		}		// Call parent validation and combine results.
		$parent_valid = parent::validate_fields();
		return $valid && $parent_valid;
	}
	/**
	 * Process the payment.
	 *
	 * @param int $order_id Reference.
	 * @param bool $retry Should we retry on fail.
	 * @param bool $force_customer Force user creation.
	 *
	 * @return array|void
	 * @throws Exception If payment will not be accepted.
	 *
	 */
	public function process_payment($order_id, $retry = true, $force_customer = false)
	{
		$return_response =  array(
			'result'   => 'failure',
			'message' => __('Unable to complete your order. Please check your details and try again.', 'authorize-net-payments-for-woocommerce')
		);
		$response = array('success' => false); // phpcs:ignore WordPress.Security.NonceVerification.Missing
		$order = wc_get_order($order_id);
		$tokenization = new Anet_ECheck_Tokenization();
		try {
			// Generate payment arguments.
			$payment_args = $this->generate_payment_request_args($order);
			// Execute the transaction using the SDK.
			if (isset($payment_args['transactionRequest']['payment']['tokenData']) && isset($payment_args['transactionRequest']['payment']['tokenData']['customerProfileId']) && isset($payment_args['transactionRequest']['payment']['tokenData']['paymentProfileId'])) {
				$response = $tokenization->stored_card($order, $payment_args);
			} else {
				$response = Anet_ECheck_Api::execute($payment_args);
			}
			// Storing card.
			if (isset($_POST['anet_echeck_save_payment_method']) && ANET_SAVING_CARD_VAL_ONE === $_POST['anet_echeck_save_payment_method'] && true === $response['success'] && isset($response['transaction_id'])) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
				$user_id = $order->get_user_id();
				$customer_profile_id = get_user_meta($user_id, $this->meta_key, true);
				$transaction_id = $response['transaction_id'];
				$tokenization->create_customer_payment_profile_from_transaction($customer_profile_id, $transaction_id, $user_id);
			}
			// Process the response.
			$this->process_response($response, $order);
			if (true === $response['success'] && isset($response['transaction_id'])) {
				WC()->cart->empty_cart();
				$return_response = array(
					'result'   => 'success',
					'redirect' => $this->get_return_url($order),
				);
			}
		} catch (Throwable $e) {
			/* translators: %s: Error message returned from the payment gateway */
			Anet_Setting_Helper::log(sprintf(__('Error: %s', 'authorize-net-payments-for-woocommerce'), $e->getMessage()), ANET_PAYMENT_TYPE_ECHECK, 'error');
			// Update order status to failed.
			/* translators: %s: Error message returned from the payment gateway */
			$order->update_status(ANET_WOOCOMMERCE_ORDER_STATUS_FAILED, sprintf(__('Payment failed: %s', 'authorize-net-payments-for-woocommerce'), $e->getMessage()));
		} finally {
			// For block checkout, format error properly.
			if ('failure' === $return_response['result']) {
				$return_response = array(
					'result'   => 'failure',
					'messages' => array(
						array(
							'type'    => 'error',
							'message' => $return_response['message'],
						),
					),
				);
			}
			return $return_response;
		}
	}
	/**
	 * Get the unique identifier for this payment gateway.
	 *
	 * Returns the gateway ID used to identify this payment method within
	 * WooCommerce's payment gateway system.
	 *
	 * @since 1.0.0
	 * @return string The gateway identifier string
	 */
	public function get_id()
	{
		return $this->id;
	}
	/**
	 * Process the payment response from Authorize.net.
	 *
	 * Handles the response from Authorize.net payment processing, updates order status,
	 * adds transaction metadata, and triggers appropriate WooCommerce actions.
	 *
	 * @since 1.0.0
	 * @param array $response Payment response data from Authorize.net.
	 * @param WC_Order $order The WooCommerce order object.
	 * @return array The processed response array
	 */
	public function process_response($response, $order)
	{
		$order_id = $order->get_id();
		// Store charge data.
		$this->update_order_meta($order, '_anet_echeck_message', isset($response['message']) ? $response['message'] : '');
		$order->set_transaction_id(isset($response['transaction_id']) ? $response['transaction_id'] : '');
		// Since we are using authCaptureTransaction, we assume the charge is captured.
		if (ANET_SANDBOX_STATUS_AUTHORIZED === $response['resultCode']) {
			if (isset($response['responseCode']) && 4 !== $response['responseCode']) {
				$this->update_order_meta($order, '_anet_echeck_charge_captured', ANET_YES);
				$this->update_order_meta($order, '_anet_echeck_charge_id', isset($response['transaction_id']) ? $response['transaction_id'] : '');
				$order->payment_complete(isset($response['transaction_id']) ? $response['transaction_id'] : '');
				/* translators: %s: Charge Transaction ID returned from the payment gateway */
				$complete_message = sprintf(__('Charge Transaction ID: %s', 'authorize-net-payments-for-woocommerce'), isset($response['transaction_id']) ? $response['transaction_id'] : '');
			} else {
				$this->update_order_meta($order, '_anet_echeck_charge_captured', ANET_NO);
				if (isset($response['responseCode']) && 4 === $response['responseCode']) {
					$this->update_order_meta($order, '_anet_echeck_fds_hold', ANET_YES);
				}
				if ($order->has_status(array(ANET_WOOCOMMERCE_ORDER_STATUS_PENDING, ANET_WOOCOMMERCE_ORDER_STATUS_FAILED))) {
					wc_reduce_stock_levels($order_id);
				}
				$order->update_status(ANET_WOOCOMMERCE_ORDER_STATUS_ON_HOLD);
			}
		} else {
			// Generic failure for other resultCode.
			$order->update_status(ANET_WOOCOMMERCE_ORDER_STATUS_FAILED);
			$error_message = __('Unable to process your request. Please try again later.', 'authorize-net-payments-for-woocommerce');
			wc_add_notice($error_message, 'error');
			/* translators: %s: Transaction ID returned from the payment gateway */
			$complete_message = sprintf(__("Transaction ID: %s", 'authorize-net-payments-for-woocommerce'), isset($response['transaction_id']) ? $response['transaction_id'] : '');
		}
		$order->add_order_note($complete_message);
		$order->save();
		/**
		 * Action fired after processing Authorize.net payment response.
		 *
		 * Allows developers to hook into the payment processing workflow after
		 * the response has been handled and order status updated.
		 *
		 * @since 1.0.0
		 * @param array $response Payment response data from Authorize.net.
		 * @param WC_Order $order The WooCommerce order object.
		 */
		do_action('wc_gateway_anet_process_response', $response, $order); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
		return $response;
	}
	/**
	 * Get the URL to view log files in WooCommerce admin.
	 *
	 * Generates a URL that links to the WooCommerce log viewer for this gateway's
	 * debug logs in the admin area.
	 *
	 * @since 1.0.0
	 * @return string URL to the log file viewer
	 */
	public function get_log_url()
	{
		$log_file_name = WC_Log_Handler_File::get_log_file_name('authorize-net-payments-for-woocommerce');
		$name_array    = explode(ANET_HYPHEN, $log_file_name);
		if (! empty($name_array[6])) {
			unset($name_array[6]);
		}
		$log_url = admin_url('admin.php?page=wc-status&tab=logs&view=single_file&file_id=' . implode(ANET_HYPHEN, $name_array));
		return $log_url;
	}
	/**
	 * Check if the gateway supports a specific feature.
	 *
	 * Determines whether this payment gateway supports specific WooCommerce features
	 * like tokenization, add payment method, or refunds based on gateway configuration.
	 *
	 * @since 1.0.0
	 * @param string $feature The feature name to check support for.
	 * @return bool True if the feature is supported, false otherwise
	 */
	public function supports($feature)
	{
		$services_supported = array('add_payment_method', 'tokenization', 'refunds');
		$settings           = get_option(ANET_WOOCOMMERCE_UNDERSCORE . $this->get_id() . ANET_UNDERSCORE_SETTINGS, array());
		if ('add_payment_method' === $feature || 'tokenization' === $feature) {
			$tokenization_enabled = ANET_YES === $settings['tokenization'];
			$feature_supported = $tokenization_enabled ? in_array($feature, $services_supported, true) : false;
			return $feature_supported;
		} else {
			$general_support = in_array($feature, $services_supported, true);
			return $general_support;
		}
	}
	/**
	 * Get the current version number of the plugin.
	 *
	 * Returns the version string of the Authorize.net Gateway plugin
	 * for use in version tracking and compatibility checking.
	 *
	 * @since 1.0.0
	 * @return string The version number of the plugin
	 */
	public function get_version()
	{
		return $this->version;
	}
	/**
	 * Process refund for an eCheck payment.
	 *
	 * Handles refund requests for orders processed through the Authorize.net eCheck gateway.
	 * Validates order permissions and delegates actual refund processing to the admin class.
	 *
	 * @since 1.0.0
	 * @param int $order_id The WooCommerce order ID to refund.
	 * @param float|null $amount The refund amount (null for full refund).
	 * @param string $reason The reason for the refund.
	 * @return array Response array containing refund status and details
	 */
	public function process_refund($order_id, $amount = null, $reason = '')
	{
		$order = wc_get_order($order_id);
		if (! $order) {
			return new WP_Error('invalid_order', __('Invalid order ID.', 'authorize-net-payments-for-woocommerce'));
		}
		if (! current_user_can('edit_shop_order', $order_id)) { // phpcs:ignore.
			return new WP_Error('invalid_permissions', __('You do not have permission to refund this order.', 'authorize-net-payments-for-woocommerce'));
		}
		if ($order->get_payment_method('edit') !== $this->id) {
			return new WP_Error('invalid_payment_method', __('This order was not paid via Authorize.net.', 'authorize-net-payments-for-woocommerce'));
		}
		if (is_null($this->plugin_admin)) {
			require_once plugin_dir_path(__DIR__) . '../includes/admin/class-anet-echeck-admin.php';
			$this->plugin_admin = new Anet_ECheck_Admin($this->get_version(), $this);
		}
		return $this->plugin_admin->process_refund($order_id, $amount, $reason);
	}
}
