<?php
/**
 * Authorize.net Gateway Credit Card Class
 *
 * Handles credit card 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';
require_once plugin_dir_path(__FILE__) . '../api/payments/class-anet-reporting.php';

/**
 * Authorize.net Gateway Credit Card Payment Class
 *
 * Extends WooCommerce payment gateway to provide Authorize.net credit card processing
 * functionality including tokenization, customer profiles, and transaction management.
 *
 * @package    Authorize_Net_Payments_For_Woocommerce
 * @subpackage Gateway
 * @since      1.0.0
 */
class Anet_Credit_Card_Gateway extends WC_Payment_Gateway
{
	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 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;
	/**
	 * Allowed card types for processing.
	 *
	 * @var array
	 */
	public $allowed_card_types;
	/**
	 * Whether tokenization is enabled.
	 *
	 * @var bool
	 */
	public $tokenization;
	/**
	 * Constructor for the Authorize.net credit card 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->transaction_type         = ANET_TRANSACTION_TYPE_CHARGE === $this->get_option('transaction_type', ANET_TRANSACTION_TYPE_CHARGE);
		$this->logging              	= ANET_YES === $this->get_option('logging');
		$this->allowed_card_types   	= $this->get_option('allowed_card_types', array());
		$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(false);
		if ($this->client_key && $this->tokenization) {
			$this->supports = array(
				'products',
				'tokenization',
				'subscriptions',
				'subscription_cancellation',
				'subscription_suspension',
				'subscription_reactivation',
				'subscription_amount_changes',
				'subscription_date_changes',
				'subscription_payment_method_change',
				'subscription_payment_method_change_customer',
				'subscription_payment_method_change_admin',
				'multiple_subscriptions',
			);
		}
		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-credit-card-admin.php';
		$this->plugin_admin = new Anet_Credit_Card_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 plugin_dir_path(__FILE__) . '../api/tokenization/class-anet-credit-card-tokenization.php';
		$customer_profile = new Anet_Credit_Card_Tokenization();
		add_filter('woocommerce_account_payment_methods_columns', array($customer_profile, 'account_payment_methods_columns'));
		add_action('woocommerce_account_payment_methods_column_title', array($customer_profile, 'add_payment_method_title'));
		add_action('woocommerce_account_payment_methods_column_default', array($customer_profile, 'add_payment_method_default'));
		add_action('woocommerce_account_payment_methods_column_details', array($customer_profile, 'add_payment_method_details'));
		add_action('woocommerce_payment_token_set_default', array($customer_profile, 'handle_set_default_token'), ANET_ACTION_HOOK_DEFAULT_PRIORITY, ANET_VAL_TWO);
		add_filter('woocommerce_payment_methods_list_item', array($customer_profile, 'add_payment_methods_list_item_edit'), ANET_ACTION_HOOK_DEFAULT_PRIORITY, ANET_VAL_TWO);
		add_action('wp_enqueue_scripts', array($customer_profile, 'enqueue_myaccount_credit_card'));
		add_filter('woocommerce_get_query_vars', array($customer_profile, 'add_custom_query_vars_credit_card'));
		add_action('woocommerce_account_edit-credit-card_endpoint', array($customer_profile, 'render_edit_credit_card'));
		//Fraud Detection Suite.
		add_action('init', array($this, 'schedule_order_updates'));
		add_action('wc_payment_gateway_update_orders', array($this, 'handle_order_updates'));
		// ADFS Order Confirmation Message Customization.
		add_filter('gettext', array($this, 'customize_order_received_message'), ANET_VAL_TWENTY, ANET_VAL_THREE);
	}
	/**
	 * Setup general properties for the gateway.
	 *
	 * Configures the basic properties of the payment gateway including
	 * ID, title, description, and form field settings.
	 *
	 * @since 1.0.0
	 * @return void
	 */
	protected function setup_properties()
	{
		$this->id                 = ANET_CREDIT_CARD_GATEWAY_ID;
		$this->method_title       = __('Authorize.net Payments for WooCommerce', 'authorize-net-payments-for-woocommerce');
		$this->method_description = __('Pay with Authorize.net Credit Card.', 'authorize-net-payments-for-woocommerce');
		$this->has_fields         = 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 credit card payment form including description, saved payment methods
	 * for logged-in users, and the payment form template with subscription notices.
	 *
	 * @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_CREDIT_CARD);
		$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-credit-card.php');
		ob_start();
		wc_print_notice(esc_html__('One or more items in your order is a subscription/recurring purchase. By continuing with payment, you agree that your payment method will be automatically charged at the price and frequency listed here until it ends or you cancel.', 'authorize-net-payments-for-woocommerce'), 'notice');
		$anet_subscription_notice = ob_get_clean();
		$anet_subscription = new Anet_Subscriptions();
		if ((is_checkout()) && ($anet_subscription->is_subscription_active && (WC_Subscriptions_Cart::cart_contains_subscription() || wcs_cart_contains_renewal() || WC_Subscriptions_Change_Payment_Gateway::$is_request_to_change_payment))) {
			echo '<div id="wc-anet-subscription-notice" style="display:none">'
				. wc_kses_notice($anet_subscription_notice) . // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
				'</div>';
		}
	}
	/**
	 * Add payment method.
	 * 
	 * @return array
	 */
	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_Credit_Card_Api::create_customer_profile($user_id, $payment_args);
		if ($result['result']) {
			$response = [
				'result' => "success",
				'redirect' => wc_get_account_endpoint_url(ANET_PAYMENT_METHODS)
			];
		}
		return $response;
	}
	/**
	 * Add the Admin Fields for our payment methods.
	 */
	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 Credit Card', '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'     => __('Credit Card - 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 credit card 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-field',
			),
			'transaction_key' => array(
				'title' => __('Transaction Key (Production)', 'authorize-net-payments-for-woocommerce'),
				'type'  => 'password',
				'class' => 'environment-field production-field anet-field',
			),
			'client_key' => array(
				'title' => __('Client Key (Production)', 'authorize-net-payments-for-woocommerce'),
				'type'  => 'password',
				'class' => 'environment-field production-field anet-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-field',
			),
			'transaction_key_test' => array(
				'title' => __('Transaction Key (Sandbox)', 'authorize-net-payments-for-woocommerce'),
				'type'  => 'password',
				'class' => 'environment-field test-field anet-field',
			),
			'client_key_test' => array(
				'title' => __('Client Key (Sandbox)', 'authorize-net-payments-for-woocommerce'),
				'type'  => 'password',
				'class' => 'environment-field test-field anet-field',
			),
			'transaction_type' => array(
				'title'       => __('Transaction Type', 'authorize-net-payments-for-woocommerce'),
				'description' => __('Charge will automatically capture/settle a transaction if the authorization is approved. Authorization simply authorizes the order total for capture later.', 'authorize-net-payments-for-woocommerce'),
				'desc_tip'    => true,
				'label'       => __('Capture charge immediately', 'authorize-net-payments-for-woocommerce'),
				'type'        => 'select',
				'default'  => ANET_TRANSACTION_TYPE_CHARGE,
				'options'  => array(
					ANET_TRANSACTION_TYPE_CHARGE        => __('Charge', 'authorize-net-payments-for-woocommerce'),
					ANET_TRANSACTION_TYPE_AUTHORIZATION => __('Authorization', 'authorize-net-payments-for-woocommerce'),
				),
			),
			'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'),
			),
			'allowed_card_types' => array(
				'title'       => __('Allowed Card types', 'authorize-net-payments-for-woocommerce'),
				'class'       => 'wc-enhanced-select',
				'type'        => 'multiselect',
				'description' => __('Select the card types you want to allow payments from.', 'authorize-net-payments-for-woocommerce'),
				'desc_tip'    => true,
				'default'     => array('visa'),
				'options'     => array(
					'visa'        => __('Visa', 'authorize-net-payments-for-woocommerce'),
					'mastercard'  => __('MasterCard', 'authorize-net-payments-for-woocommerce'),
					'discover'    => __('Discover', 'authorize-net-payments-for-woocommerce'),
					'amex'        => __('American Express', 'authorize-net-payments-for-woocommerce'),
					'jcb'         => __('JCB', 'authorize-net-payments-for-woocommerce'),
					'diners-club' => __('Diners Club', '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
			);
			wp_add_inline_script(
				'anet-admin-env-toggle',
				'const ANET_ENV_TEST = "' . ANET_ENVIRONMENT_TEST . '";'
			);
		}
	}
	/**
	 * Enqueue payment scripts for the credit card 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 credit card 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 credit card payment form.
		 *
		 * Allows modification of parameters sent to Accept.js for credit card processing,
		 * including API endpoint URLs, client configuration, and form validation settings.
		 *
		 * @since 1.0.0
		 * @param array $params JavaScript parameters for credit card payment processing.
		 */
		wp_localize_script('woocommerce_anet', 'anet_params_cc', apply_filters('anet_params_cc', $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_params = array(
			'login_id'              => $this->login_id,
			'client_key'            => $this->client_key,
			'test_mode'				=> $this->testmode,
			'allowed_card_types'    => $this->allowed_card_types,
			'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_params['billing_first_name'] = $order->get_billing_first_name();
			$anet_params['billing_last_name']  = $order->get_billing_last_name();
		} else {
			$anet_params['billing_first_name'] = '';
			$anet_params['billing_last_name']  = '';
		}
		return $anet_params;
	}
	/**
	 * Generate the request for the payment.
	 *
	 * @param WC_Order $order The WooCommerce order object.
	 * @param bool     $merchant_initiated Whether this is a merchant initiated transaction.
	 * @param string   $token_id Optional token ID for stored cards.
	 * 
	 * @return array The payment request arguments.
	 * @throws Exception If payment source is missing or invalid.
	 */
	public function generate_payment_request_args($order, $merchant_initiated = false, $token_id = null)
	{
		// Fetch the payment source using the get_payment_source method.
		if ($merchant_initiated) {
			$source = $this->getTokenDetailsMit($token_id, $order->get_user_id());
		} else {
			$source = $this->get_payment_source($order->get_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 (ANET_SOURCE_TYPE_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 (ANET_SOURCE_TYPE_STORED_TOKEN === $source->source['source_type']) {
			// Create the payment data for a payment nonce..
			$payment_data = array(
				'tokenData' => array(
					'token_id' => wc_clean($source->source['token']),
					'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'     => ($this->transaction_type ? ANET_TRANSACTION_TYPE_AUTH_CAPTURE : ANET_TRANSACTION_TYPE_AUTHORIZE),
				'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
		$this->transaction_type = (ANET_TRANSACTION_TYPE_AUTH_CAPTURE === $request_args['transactionRequest']['transactionType']);
		return $request_args;
	}
	/**
	 * Get order line items formatted for Authorize.net API.
	 *
	 * @since 1.0.0
	 * 
	 * @param WC_Order $order The WooCommerce order object.
	 * 
	 * @return array Array of line items for the API request.
	 */
	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.
	 *
	 * @since 1.0.0
	 * 
	 * @param WC_Order_Item $item The order item object.
	 * @param string        $hide_prefix Prefix to hide from display.
	 * @param bool          $include_all Whether to include all meta data.
	 * 
	 * @return array The 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 payment source for processing.
	 *
	 * @param int  $user_id The user ID.
	 * @param bool $force_customer Whether to force customer creation.
	 * 
	 * @return object Payment source object.
	 * @throws Exception If payment source is missing or invalid.
	 */
	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'));
	}
	/**
	 * Get token details for merchant initiated transactions.
	 *
	 * @param string $token The payment token.
	 * @param int    $user_id The user ID.
	 * 
	 * @return object Payment source object with token details.
	 */
	public function getTokenDetailsMit($token, $user_id)
	{
		$source = 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_CREDIT_CARD_GATEWAY_ID])[$token]->get_token(),
			'source_type' => ANET_SOURCE_TYPE_STORED_TOKEN,
		);
		$token_details = (object) array(
			'token_id' => true,
			'customer' => false,
			'source'   => $source,
		);
		return $token_details;
	}
	/**
	 * Check if it is stored card transaction provided.
	 *
	 * @return bool
	 */
	/**
	 * Check if this is a stored card transaction.
	 *
	 * @since 1.0.0
	 * 
	 * @return bool True if stored card transaction, false otherwise.
	 */
	public function is_stored_card_transaction()
	{
		$has_token = isset($_POST['token']) || isset($_POST['wc-anet-payment-token']); // phpcs:ignore WordPress.Security.NonceVerification.Missing
		return $has_token;
	}
	/**
	 * Check if opaque data (nonce) is provided.
	 *
	 * @return bool
	 */
	/**
	 * Check if opaque data (nonce) is provided.
	 *
	 * @since 1.0.0
	 * 
	 * @return bool True if opaque data is provided, 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 card.
	 *
	 * @param int $user_id The user ID.
	 * @return array
	 */
	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-payment-token'])) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
			$token = sanitize_text_field(wp_unslash($_POST['wc-anet-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_CREDIT_CARD_GATEWAY_ID])[$token]->get_token(),
			'source_type' => ANET_SOURCE_TYPE_STORED_TOKEN,
		);
		return $token_details;
	}
	/**
	 * Get opaque data (nonce) details.
	 *
	 * @return array
	 */
	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' => ANET_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
			$billing_last_name = sanitize_text_field(wp_unslash($_POST['billing_last_name'])); // phpcs:ignore WordPress.Security.NonceVerification.Missing
		}
		// Billing company name.
		$billing_company = '';
		$raw_billing_company = '';
		if (isset($_POST['billing_company'])) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
			$billing_company = sanitize_text_field(wp_unslash($_POST['billing_company'])); // phpcs:ignore WordPress.Security.NonceVerification.Missing
		}
		// 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 = '';
		$raw_shipping_address_1 = '';
		$shipping_address_2 = '';
		$raw_shipping_address_2 = '';
		$shipping_first_name = '';
		$raw_shipping_first_name = '';
		$shipping_last_name = '';
		$raw_shipping_last_name = '';
		$shipping_company = '';
		$raw_shipping_company = '';
		$shipping_postcode = '';
		$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);
			}
		}
		// 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;
			}
		}
		// Get 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);
		}
		// 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 = sanitize_text_field(wp_unslash($_POST['billing_last_name'])); // phpcs:ignore WordPress.Security.NonceVerification.Missing
				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;
			}
		}
		// Get billing company name.
		$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);
		}
		// 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 = sanitize_text_field(wp_unslash($_POST['billing_company'])); // phpcs:ignore WordPress.Security.NonceVerification.Missing
				wc_add_notice(__('Billing Company name is invalid. 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(__('Billing Company name is invalid. 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 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 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 company.
			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(__('Shipping Company name is invalid. Special characters are not allowed.', 'authorize-net-payments-for-woocommerce'), 'error');
					$valid = false;
				} elseif (!preg_match('/^[a-zA-Z\s\-\.]+$/', $shipping_company)) {
					wc_add_notice(__('Shipping Company 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 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 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;
				}
			}
		}
		// Get billing postcode with raw input.
		$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 with raw input.
		$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 with raw input.
		$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 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 = sanitize_text_field(wp_unslash($_POST['billing_postcode'])); // phpcs:ignore WordPress.Security.NonceVerification.Missing
				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 = sanitize_text_field(wp_unslash($_POST['billing_city'])); // phpcs:ignore WordPress.Security.NonceVerification.Missing
				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 = sanitize_text_field(wp_unslash($_POST['billing_phone'])); // phpcs:ignore WordPress.Security.NonceVerification.Missing
				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 = sanitize_text_field(wp_unslash($_POST['shipping_city'])); // phpcs:ignore WordPress.Security.NonceVerification.Missing
				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;
	}
	/**
	 * Check if this is a subscription order.
	 *
	 * @since 1.0.0
	 * 
	 * @param WC_Order $order The WooCommerce order object.
	 * 
	 * @return bool True if subscription order, false otherwise.
	 */
	public function is_subscription_order($order)
	{
		$anet_subscription = new Anet_Subscriptions();
		return $anet_subscription->is_subscription_active && (wcs_order_contains_subscription($order) || wcs_order_contains_renewal($order));
	}
	/**
	 * 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',
			/* translators: %s: URL to the order history page */
			'message' => __('Unable to complete your order. Please check your details and try again.', 'authorize-net-payments-for-woocommerce')
		);
		$response = array('success' => false);
		$order = wc_get_order($order_id);
		$tokenization = new Anet_Credit_Card_Tokenization();
		// Handle zero amount subscription orders.
		if (0.00 === (float) $order->get_total() && $this->is_subscription_order($order)) {
			return $this->process_zero_amount_subscription($order);
		}
		try {
			// Generate payment arguments.
			$payment_args = $this->generate_payment_request_args($order);
			$is_store_card = false;
			$anet_subscription = new Anet_Subscriptions();
			$is_subscription_order = $anet_subscription->is_subscription_active && (wcs_order_contains_subscription($order) || wcs_order_contains_renewal($order));
			if ($anet_subscription->is_subscription_active && WC_Subscriptions_Change_Payment_Gateway::$is_request_to_change_payment) {
				if (isset($payment_args['transactionRequest']['payment']['tokenData']) && isset($payment_args['transactionRequest']['payment']['tokenData']['paymentProfileId'])) {
					return $anet_subscription->change_payment_method($payment_args['transactionRequest']['payment']['tokenData']['paymentProfileId'], $order, null);
				} elseif (isset($payment_args['transactionRequest']['payment']['opaqueData'])) {
					return $anet_subscription->change_payment_method(null, $order, $payment_args);
				}
			}
			// 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);
				if ($is_subscription_order && isset($response['success']) && $response['success']) {
					$anet_subscription->update_order_subscription_token($order, $payment_args['transactionRequest']['payment']['tokenData']['paymentProfileId']);
				}
			} elseif (isset($payment_args['transactionRequest']['payment']['opaqueData'])) {
				if ((isset($_POST['anet_save_payment_method']) && ANET_SAVING_CARD_VAL_ONE === sanitize_text_field(wp_unslash($_POST['anet_save_payment_method']))) || $is_subscription_order) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
					$is_store_card = true;
				}
				$response = Anet_Credit_Card_Api::execute($payment_args, $order);
			}
			if ($is_store_card && $response['success'] && isset($response['transaction_id'])) {
				$user_id = $order->get_user_id();
				$customer_profile_id = get_user_meta($user_id, $this->meta_key, true);
				$transaction_id = $response['transaction_id'];
				$tokenization_response = $tokenization->create_customer_payment_profile_from_transaction($customer_profile_id, $transaction_id, $user_id);
				if ($is_subscription_order && isset($tokenization_response['result']) && $tokenization_response['result'] && !empty($tokenization_response['customerPaymentProfileId'])) {
					$anet_subscription->update_order_subscription_token($order, $tokenization_response['customerPaymentProfileId']);
				}
			}
			$this->process_response($response, $order);
			if ($response['success'] && isset($response['transaction_id'])) {
				if ($is_subscription_order) {
					$anet_subscription->add_payment_data_to_subscription($order);
				}
				// Empty the cart.
				WC()->cart->empty_cart();
				$return_response = array(
					'result'   => 'success',
					'redirect' => $this->get_return_url($order),
				);
			}
			// Return success and redirect to the thank-you page.
		} 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_CREDIT_CARD, '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()));
		}
		// Handle decline scenario.
		if (isset($response['responseCode']) && ANET_VAL_THREE === (int)$response['responseCode']) {
			$return_response['message'] = isset($response['errors']) ? $response['errors'] : __('This transaction has been declined.', 'authorize-net-payments-for-woocommerce');
		}
		// 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;
	}
	/**
	 * Returns gateway id.
	 *
	 * @return string
	 */
	/**
	 * Get gateway ID.
	 *
	 * @since 1.0.0
	 * 
	 * @return string The gateway ID.
	 */
	public function get_id()
	{
		return $this->id;
	}
	/**
	 * Store extra meta data for an order from an Authorize.net Response.
	 *
	 * @since 1.0.0
	 * 
	 * @param array    $response The payment response array.
	 * @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_credit_card_auth_code', isset($response['authCode']) ? $response['authCode'] : '');
		$this->update_order_meta($order, '_anet_credit_card_message', isset($response['message']) ? $response['message'] : '');
		$order->set_transaction_id(isset($response['transaction_id']) ? $response['transaction_id'] : '');
		if (isset($response['resultCode']) && ANET_SANDBOX_STATUS_AUTHORIZED === $response['resultCode']) {
			// Check if this is a charge transaction held for review (response code 4).
			$is_held_for_review = isset($response['responseCode']) && ANET_VAL_FOUR === (int)$response['responseCode'];
			if ($this->transaction_type && !$is_held_for_review) {
				// Successful charge transaction (not held for review).
				$this->update_order_meta($order, '_anet_credit_card_charge_captured', ANET_YES);
				$this->update_order_meta($order, '_anet_credit_card_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'] : '');
			} elseif ($this->transaction_type && $is_held_for_review) {
				// Charge transaction held for review.
				$this->update_order_meta($order, '_anet_credit_card_charge_captured', ANET_NO);
				$this->update_order_meta($order, '_anet_credit_card_charge_id', isset($response['transaction_id']) ? $response['transaction_id'] : '');
				$order->update_status(ANET_WOOCOMMERCE_ORDER_STATUS_PENDING_PAYMENT);
				$this->update_order_meta($order, '_anet_credit_card_fds_review', ANET_YES);
				if ($order->has_status(array(ANET_WOOCOMMERCE_ORDER_STATUS_PENDING, ANET_WOOCOMMERCE_ORDER_STATUS_FAILED))) {
					wc_reduce_stock_levels($order_id);
				}
				$order->add_order_note(__('FDS Trigger - Review', 'authorize-net-payments-for-woocommerce'));
				/* translators: %s: Charge Transaction ID returned from the payment gateway */
				$complete_message = sprintf(__("Charge Transaction ID (Held for Review): %s", 'authorize-net-payments-for-woocommerce'), isset($response['transaction_id']) ? $response['transaction_id'] : '');
			} else {
				// Authorization transaction (not charge).
				$this->update_order_meta($order, '_anet_credit_card_charge_captured', ANET_NO);
				$this->update_order_meta($order, '_anet_credit_card_authorization_id', isset($response['transaction_id']) ? $response['transaction_id'] : '');
				if ($is_held_for_review) {
					$order->update_status(ANET_WOOCOMMERCE_ORDER_STATUS_PENDING_PAYMENT);
					$this->update_order_meta($order, '_anet_credit_card_fds_review', ANET_YES);
				} else {
					$order->update_status(ANET_WOOCOMMERCE_ORDER_STATUS_ON_HOLD);
				}
				if ($order->has_status(array(ANET_WOOCOMMERCE_ORDER_STATUS_PENDING, ANET_WOOCOMMERCE_ORDER_STATUS_FAILED))) {
					wc_reduce_stock_levels($order_id);
				}
				/* translators: %s: Authorize Transaction ID returned from the payment gateway */
				$complete_message = sprintf(__("Authorize Transaction ID: %s", 'authorize-net-payments-for-woocommerce'), isset($response['transaction_id']) ? $response['transaction_id'] : '');
			}
		} elseif (isset($response['responseCode']) && ANET_VAL_THREE === (int)$response['responseCode']) {
			// Handle declined or failed transactions.
			$order->update_status(ANET_WOOCOMMERCE_ORDER_STATUS_FAILED);
			$this->update_order_meta($order, '_anet_credit_card_fds_decline', ANET_YES);
			$error_message = isset($response['errors']) ? $response['errors'] : __('This transaction has been declined.', 'authorize-net-payments-for-woocommerce');
			wc_add_notice($error_message, 'error');
			$order->add_order_note(__('FDS Trigger - Declined', 'authorize-net-payments-for-woocommerce'));
			/* 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'] : '');
		} 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();
		/**
		 * Fires after processing a payment response from Authorize.net.
		 *
		 * @since 1.0.0
		 * 
		 * @param array    $response The payment response array.
		 * @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 log URL for WooCommerce logs.
	 *
	 * @since 1.0.0
	 * 
	 * @return string The log URL.
	 */
	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;
	}
	/**
	 * Checks feature supports for our plugin.
	 *
	 * @since 1.0.0
	 * 
	 * @param string $feature The feature to check support for.
	 *
	 * @return bool True if 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;
		} elseif (in_array($feature, $this->supports, true)) {
			return true;
		} else {
			$general_support = in_array($feature, $services_supported, true);
			return $general_support;
		}
	}
	/**
	 * Retrieve the version number of the plugin.
	 *
	 * @since 1.0.0
	 * 
	 * @return string The version number of the plugin.
	 */
	public function get_version()
	{
		return $this->version;
	}
	/**
	 * Initiates refund process.
	 *
	 * @since 1.0.0
	 * 
	 * @param int    $order_id The order ID.
	 * @param float  $amount The refund amount.
	 * @param string $reason The refund reason.
	 *
	 * @return bool|WP_Error True on success, WP_Error on failure.
	 */
	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 WordPress.WP.Capabilities.Unknown
			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-credit-card-admin.php';
			$this->plugin_admin = new Anet_Credit_Card_Admin($this->get_version(), $this);
		}
		return $this->plugin_admin->process_refund($order_id, $amount, $reason);
	}
	/** 
	 * Function to show allowed card type in normal checkout.
	 * Get_icon function to show allowed card type as per admin configured.
	 *
	 * @return string
	 */
	public function get_icon()
	{
		$card_types = array(
			'visa' => 'Visa',
			'mastercard' => 'Mastercard',
			'amex' => 'Amex',
			'discover' => 'Discover',
			'jcb' => 'JCB',
			'diners-club' => 'Diners Club'
		);
		$icon = '';
		foreach ($card_types as $card_type => $card_name) {
			if (in_array($card_type, $this->allowed_card_types, true)) {
				$image_name = ('diners-club' === $card_type) ? 'diners' : $card_type;
				$icon .= sprintf(
					'<img style="margin-left: 0.3em" src="%s" alt="%s" width="32" />',
					WC_HTTPS::force_https_url(WC()->plugin_url() . '/assets/images/icons/credit-cards/' . $image_name . '.svg'),
					$card_name
				);
			}
		}
		/**
		 * Filter the gateway icon HTML output.
		 *
		 * @since 1.0.0
		 * 
		 * @param string $icon The icon HTML.
		 * @param string $id The gateway ID.
		 */
		return apply_filters('woocommerce_gateway_icon', $icon, $this->id); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
	}
	/**
	 * Handles the order updates when scheduler is run.
	 *
	 * @param object $gateway gateway.
	 *
	 * @return void
	 */
	public function handle_order_updates($gateway)
	{
		$reporting = new Anet_Reporting($this);
		$response  = $reporting->get_unsettled_transactions();
	}
	/**
	 * Schedules the order update routines.
	 *
	 * Only schedule a routine for a gateway if it's.
	 * - Not inheriting another's settings.
	 * - Configured and Advanced Fraud Detection Suite is enabled.
	 *
	 * @return void
	 */
	public function schedule_order_updates()
	{
		$args = array(($this->method_title));
		if (false === as_next_scheduled_action('wc_payment_gateway_update_orders', $args)) {
			as_schedule_recurring_action(time() + $this->get_order_update_interval(), $this->get_order_update_interval(), 'wc_payment_gateway_update_orders', $args, ANET_PLUGIN_VERSION);
		}
	}
	/**
	 * Gets the order update interval.
	 *
	 * @return int
	 */
	private function get_order_update_interval()
	{
		return max(ANET_VAL_ONE, ANET_CRON_JOB_TIME * MINUTE_IN_SECONDS);
	}
	/**
	 * Customize the "Thank you" message on order received page for ADFS transactions.
	 * 
	 * @param string $translated_text The translated text.
	 * @param string $text The original text.
	 * @param string $domain The text domain.
	 * @return string Modified text.
	 */
	public function customize_order_received_message($translated_text, $text, $domain)
	{
		// Only process WooCommerce thank you message on order received page.
		if (ANET_WOOCOMMERCE === $domain) {
			global $wp;
			// Only proceed if we're actually on the order-received page.
			if (!isset($wp->query_vars['order-received'])) {
				return $translated_text;
			}
			// Only process the specific thank you message.
			if (strpos($text, 'Thank you') === false || strpos($text, 'order has been received') === false) {
				return $translated_text;
			}
			$order_id = absint($wp->query_vars['order-received']);
			$order = wc_get_order($order_id);
			// Only modify for our gateway AND only when ADFS is actually triggered.
			if ($order && $this->id === $order->get_payment_method()) {
				$fds_review = $order->get_meta('_anet_credit_card_fds_review');
				// ONLY modify message if ADFS was actually triggered.
				if (ANET_YES === $fds_review) {
					return __('Thank you. Your order has been received and order is in review.', 'authorize-net-payments-for-woocommerce');
				}
			}
		}
		// Return original message for all other cases.
		return $translated_text;
	}
	/**
	 * Common function to process zero amount subscription orders.
	 * Calls appropriate handler based on payment source type.
	 *
	 * @since 1.0.0
	 * 
	 * @param WC_Order $order The order object.
	 * @return array Payment result array.
	 * @throws Exception If payment processing fails.
	 */
	private function process_zero_amount_subscription($order)
	{
		try {
			$user_id = $order->get_user_id();
			$source = $this->get_payment_source($user_id, false);
			if (empty($source)) {
				throw new Exception(__('Payment source not found for zero amount subscription.', 'authorize-net-payments-for-woocommerce'));
			}
			// Check if this is a stored card transaction and call appropriate handler.
			if ($source->token_id && isset($source->source['source_type']) && ANET_SOURCE_TYPE_STORED_TOKEN === $source->source['source_type']) {
				return $this->handle_stored_card_zero_amount($order, $source);
			} else {
				return $this->handle_new_card_zero_amount($order, $source, $user_id);
			}
		} catch (Exception $e) {
			wc_add_notice($e->getMessage(), 'error');
			throw $e;
		}
	}
	/**
	 * Handle stored card with zero amount subscription.
	 *
	 * @since 1.0.0
	 * 
	 * @param WC_Order $order The order object.
	 * @param object $source Payment source object.
	 * @return array Success result array.
	 * @throws Exception If stored card processing fails.
	 */
	private function handle_stored_card_zero_amount($order, $source)
	{
		try {
			// Validate stored card payment profile.
			if (empty($source->source['paymentProfileId'])) {
				/* translators: Error message when payment profile ID is missing for a stored card */
				throw new Exception(__('Payment profile ID not found for stored card.', 'authorize-net-payments-for-woocommerce'));
			}
			$payment_profile_id = $source->source['paymentProfileId'];
			// Complete payment.
			$order->payment_complete();
			// Order note for stored card zero amount subscription.
			$order->add_order_note(__('Customer is on a $0 subscription plan right now. Orders will be visible once subscription is charged.', 'authorize-net-payments-for-woocommerce'));
			// Update subscription token.
			$anet_subscription = new Anet_Subscriptions();
			if ($anet_subscription->is_subscription_active && !empty($payment_profile_id)) {
				$anet_subscription->update_order_subscription_token($order, $payment_profile_id);
				$anet_subscription->add_payment_data_to_subscription($order);
			}
			WC()->cart->empty_cart();
			return array('result' => 'success', 'redirect' => $this->get_return_url($order));
		} catch (Exception $e) {
			/* translators: %s: Error message returned from the payment gateway */
			Anet_Setting_Helper::log(sprintf(__('Stored card zero amount error: %s', 'authorize-net-payments-for-woocommerce'), $e->getMessage()), ANET_PAYMENT_TYPE_CREDIT_CARD, 'error');
			throw $e;
		}
	}
	/**
	 * Handle new card with storing for zero amount subscription.
	 *
	 * @since 1.0.0
	 * 
	 * @param WC_Order $order The order object.
	 * @param object $source Payment source object.
	 * @param int $user_id User ID.
	 * @return array Success result array.
	 * @throws Exception If new card processing fails.
	 */
	private function handle_new_card_zero_amount($order, $source, $user_id)
	{
		try {
			// Validate payment source data.
			if (empty($source->source['descriptor']) || empty($source->source['nonce'])) {
				/* translators: Error message when payment source data is invalid for creating a new customer profile */
				throw new Exception(__('Invalid payment source data for new customer profile.', 'authorize-net-payments-for-woocommerce'));
			}
			// Build payment arguments for storing new card.
			$payment_args = array(
				'email' => $order->get_billing_email(),
				'opaqueData' => array(
					'dataDescriptor' => wc_clean($source->source['descriptor']),
					'dataValue'      => wc_clean($source->source['nonce']),
				),
				'billTo' => array(
					'firstName' => $order->get_billing_first_name(),
					'lastName'  => $order->get_billing_last_name(),
					'address'   => $order->get_billing_address_1(),
					'city'      => $order->get_billing_city(),
					'state'     => $order->get_billing_state(),
					'zip'       => $order->get_billing_postcode(),
					'country'   => $order->get_billing_country(),
				),
			);
			// Create customer profile to store the card.
			$result = Anet_Credit_Card_Api::create_customer_profile($user_id, $payment_args);
			// Validate API response and extract payment profile ID.
			if (empty($result['result']) || empty($result['customerPaymentProfileId'])) {
				$error_message = !empty($result['error']) ? $result['error'] : __('Failed to create customer profile or missing payment profile ID.', 'authorize-net-payments-for-woocommerce');
				/* translators: %s: Error message returned from the payment gateway */
				throw new Exception(sprintf(__('Unable to store payment method for subscription: %s', 'authorize-net-payments-for-woocommerce'), $error_message));
			}
			$payment_profile_id = $result['customerPaymentProfileId'];
			// Complete payment.
			$order->payment_complete();
			// Order note for stored card zero amount subscription.
			$order->add_order_note(__('Customer is on a $0 subscription plan right now. Orders will be visible once subscription is charged.', 'authorize-net-payments-for-woocommerce'));
			// Update subscription token with newly created profile.
			$anet_subscription = new Anet_Subscriptions();
			if ($anet_subscription->is_subscription_active && !empty($payment_profile_id)) {
				$anet_subscription->update_order_subscription_token($order, $payment_profile_id);
				$anet_subscription->add_payment_data_to_subscription($order);
			}
			WC()->cart->empty_cart();
			return array('result' => 'success', 'redirect' => $this->get_return_url($order));
		} catch (Exception $e) {
			/* translators: %s: Error message returned from the payment gateway */
			Anet_Setting_Helper::log(sprintf(__('New card zero amount error: %s', 'authorize-net-payments-for-woocommerce'), $e->getMessage()), ANET_PAYMENT_TYPE_CREDIT_CARD, 'error');
			throw $e;
		}
	}
}
