<?php
// phpcs:disable WordPress.PHP.DevelopmentFunctions
// phpcs:disable WordPress.Files.FileName.InvalidClassFileName
// phpcs:disable WordPress.WP.AlternativeFunctions.json_encode_json_encode
// phpcs:disable WordPress.Files.FileName.NotHyphenatedLowercase

use WeeConnectPay\CloverMerchant;
use WeeConnectPay\Environment;
use WeeConnectPay\Exceptions\Codes\ExceptionCode;
use WeeConnectPay\Exceptions\WeeConnectPayException;
use WeeConnectPay\Integrations\AdminPanel;
use WeeConnectPay\Integrations\Authentication;
use WeeConnectPay\Integrations\IntegrationHealth;
use WeeConnectPay\Integrations\IntegrationSettings;
use WeeConnectPay\Integrations\PaymentFields;
use WeeConnectPay\StandardizedResponse;
use WeeConnectPay\WordPress\Plugin\includes\WeeConnectPayAPI;
use WeeConnectPay\WordPress\Plugin\includes\WeeConnectPayCustomTenderHelper;
use WeeConnectPay\WordPress\Plugin\includes\WeeConnectPayHelper;
use WeeConnectPay\WordPress\Plugin\includes\WeeConnectPayOrderProcessor;
use WeeConnectPay\WordPress\Plugin\includes\WeeConnectPayUtilities;
use WeeConnectPay\Integrations\LogService;

if ( ! class_exists( WC_Payment_Gateway::class ) ) {
	return null;
}

/**
 * Handles the WeeConnectPay/Clover/WooCommerce payment gateway
 */
class WC_Gateway_Weeconnectpay extends WC_Payment_Gateway {
	/**
	 * Instance of the WeeConnectPay API
	 *
	 * @var WeeConnectPayAPI $api
	 */
	private WeeConnectPayAPI $api;
	/**
	 * @var string
	 */
	private string $url_api;
	/**
	 * @var string
	 */
	private ?string $integration_id = null;

	/**
	 * @return WeeConnectPayAPI
	 */
	public function getApi(): WeeConnectPayAPI {
		return $this->api;
	}

	/**
	 * @param WeeConnectPayAPI $api
	 *
	 * @return WC_Gateway_Weeconnectpay
	 */
	public function setApi( WeeConnectPayAPI $api ): WC_Gateway_Weeconnectpay {
		$this->api = $api;

		return $this;
	}

	private bool $is_debug = true;


	/**
	 * @var IntegrationSettings $integrationSettings
	 */
	private IntegrationSettings $integrationSettings;

	/**
	 * @return IntegrationSettings
	 */
	public function getIntegrationSettings(): IntegrationSettings {
		return $this->integrationSettings;
	}

	/**
	 * @param IntegrationSettings $integrationSettings
	 *
	 * @return WC_Gateway_Weeconnectpay
	 */
	public function setIntegrationSettings( IntegrationSettings $integrationSettings ): WC_Gateway_Weeconnectpay {
		$this->integrationSettings = $integrationSettings;

		return $this;
	}

	/**
	 * @updated 3.3.0
	 * @throws \WeeConnectPay\Exceptions\SettingsInitializationException
	 */
	public function __construct() {
		$this->id                 = 'weeconnectpay';
		$this->icon               = WEECONNECTPAY_PLUGIN_URL . 'site/img/clover-square-logo.svg';
		$this->has_fields         = true;
		$this->method_title       = __( 'Clover via WeeConnectPay', 'weeconnectpay' );
		$this->method_description = __(
			'Simplify online payments by adding the Clover payment option to your shopping cart. Then you will see your payments in real time on your Clover web portal.',
			'weeconnectpay'
		);
		$this->supports           = array(
			'refunds',
			'products',
			'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',
		);

		$this->init_form_fields();
		$this->init_settings();

		try {
			$integration_settings = new IntegrationSettings();

			$integration_settings->getIntegrationUuid();
			// Save the integration settings as an attribute on this class
			$this->setIntegrationSettings( $integration_settings );
		} catch ( WeeConnectPayException $e ) {
			// Missing integration ID is expected for client_credentials before first connection — not an error
			if ( $integration_settings->getAuthMethodOrDefault() !== 'client_credentials' ) {
				LogService::error( 'WeeConnectPay: Exception in gateway constructor. Message: ' . $e->getMessage() );
				$integration_settings = IntegrationSettings::reinitialize();
			}
			$this->setIntegrationSettings( $integration_settings );
		}

		try {
			// Save the API as an attribute on this class
			$this->setApi( new WeeConnectPayAPI() );
		} catch ( WeeConnectPayException $e ) {
			LogService::error( 'WeeConnectPay: Exception in gateway constructor. Message: ' . $e->getMessage() );
			return StandardizedResponse::emitError( $e->toObject() );
		}

		$this->url_api        = $this->api->url_api;
		try {
			$this->integration_id = $this->integrationSettings->getIntegrationUuid();
		} catch ( WeeConnectPayException $e ) {
			$this->integration_id = null;
		}

		$this->title       = $this->get_option( 'title' );
		$this->enabled     = $this->get_option( 'enabled' );
		$this->description = $this->get_option( 'description' );

		// Runs when we update the gateway options through woocommerce -- Used for options saved using our DB structure and not WooCommerce
		add_action(
			'woocommerce_update_options_payment_gateways_' . $this->id,
			array(
				$this,
				'update_gateway_options',
			)
		);

		// Runs when we update the gateway options through woocommerce
		add_action(
			'woocommerce_update_options_payment_gateways_' . $this->id,
			array(
				$this,
				'process_admin_options',
			)
		);

		// API Callback
		add_action(
			'woocommerce_api_callback_' . strtolower( get_class( $this ) ),
			array(
				$this,
				'weeconnectpay_callback_handler',
			)
		);

		add_action( 'woocommerce_after_checkout_validation', array( $this, 'maybe_add_wc_notice' ), 10, 2 );
		add_action( 'woocommerce_checkout_process', array( $this, 'maybe_add_wc_notice' ), 10, 2 );

		// Runs when trying to login with Clover.
		add_action( 'woocommerce_sections_checkout', array( $this, 'display_clover_login_notice' ) );

		// Leads a disconnected merchant back to the connection page from anywhere in wp-admin.
		add_action( 'admin_notices', array( $this, 'display_integration_health_notice' ) );


		add_action( 'woocommerce_checkout_order_processed', array( $this, 'weeconnectpay_maybe_handle_zero_total_order' ), 10, 1 );

		// Subscriptions.
		add_action( 'woocommerce_scheduled_subscription_payment_' . $this->id, array( $this, 'process_renewal_payment' ), 10, 2 );

		// Preauthorization charge capture.
		add_action( 'woocommerce_order_item_add_action_buttons', array( $this, 'add_capture_charge_button' ) );
	}

	function weeconnectpay_maybe_handle_zero_total_order( $order_id ) {

		// Get the order object.
		$order = wc_get_order( $order_id );

		// Check if the order is using your payment gateway.
		// Adjust 'weeconnectpay' to your actual gateway identifier.
		// if ( 'weeconnectpay' !== $order->get_payment_method() ) {
		// return;
		// }

		// Check if the order total is 0 OR if a gift card custom tender is present.
		// For example, you might be storing a meta flag when a gift card is applied.
		$pendingCustomTenderTotal = WeeConnectPayCustomTenderHelper::getCustomTendersPendingTotal( $order );

		if ( $order->get_total() > 0 ) {
			LogService::debug( 'Skipping custom logic of weeconnectpay_maybe_handle_zero_total_order -- order total is > 0' );
			return;
		}

		if ( $pendingCustomTenderTotal <= 0 ) {
			LogService::debug( 'Skipping custom logic of weeconnectpay_maybe_handle_zero_total_order -- no pending custom tenders present' );
			return;
		}

		$integrationSettings = new IntegrationSettings();
		$processor           = new WeeConnectPayOrderProcessor( $integrationSettings );

		// If additional POST data is needed for processing, provide it here.
		// In many cases for zero-total orders the POST data may not be available so you can pass an empty array.
		$postData = array();

		// Run your processing routine.
		$result = $processor->processOrderPayment( $order, $postData );

		// Optionally log the result for debugging.
		LogService::info( 'WeeConnectPay processed order via woocommerce_checkout_order_processed hook: ' . json_encode( $result ) );
	}


	public function update_gateway_options() {
		// Get the value of the "Post Tokenization Verification" setting from $_POST
		$postTokenizationVerification          = $_POST['woocommerce_weeconnectpay_post_tokenization_verification'] ?? '0';
		$googleRecaptchaEnabled                = $_POST['woocommerce_weeconnectpay_google_recaptcha_enabled'] ?? '0';
		$googleRecaptchaSiteKey                = $_POST['woocommerce_weeconnectpay_google_recaptcha_site_key'] ?? '';
		$googleRecaptchaSecretKey              = $_POST['woocommerce_weeconnectpay_google_recaptcha_secret_key'] ?? '';
		$googleRecaptchaMinHumanScoreThreshold = $_POST['woocommerce_weeconnectpay_min_human_score_threshold'] ?? 0.5;
		$honeypotFieldEnabled                  = $_POST['woocommerce_weeconnectpay_honeypot_field_enabled'] ?? '0';
		$debugMode                             = $_POST['woocommerce_weeconnectpay_debug_mode'] ?? '0';
		$cloverOrderPrinting                   = $_POST['woocommerce_weeconnectpay_clover_order_printing'] ?? '0';
		$cloverOrderPrintingRecurring          = $_POST['woocommerce_weeconnectpay_clover_order_printing_recurring'] ?? '0';
		$googlePayEnabled                      = $_POST['woocommerce_weeconnectpay_google_pay_enabled'] ?? '0';
		$transactionMode                       = $_POST['woocommerce_weeconnectpay_transaction_mode'] ?? 'order_payment';
		$threeDSecureBeta                      = ( isset( $_POST['woocommerce_weeconnectpay_three_d_secure_beta'] ) && $_POST['woocommerce_weeconnectpay_three_d_secure_beta'] === '1' ) ? '1' : '0';

		try {
			$integrationSettings = new IntegrationSettings();

			// Post Tokenization Verification
			$integrationSettings->setPostTokenizationVerification( $postTokenizationVerification );

			// Google reCAPTCHA v3 enabled?
			$integrationSettings->setGoogleRecaptcha( $googleRecaptchaEnabled );

			// Google reCAPTCHA v3 Site Key
			$integrationSettings->setGoogleRecaptchaSiteKey( $googleRecaptchaSiteKey );

			// Google reCAPTCHA v3 Secret Key
			$integrationSettings->setGoogleRecaptchaSecretKey( $googleRecaptchaSecretKey );

			// Google reCAPTCHA v3 Minimum Score Human Threshold
			$integrationSettings->setGoogleRecaptchaMinimumHumanScoreThreshold( $googleRecaptchaMinHumanScoreThreshold );

			// Honeypot Field
			$integrationSettings->setHoneypotField( $honeypotFieldEnabled );

			// Debug Mode
			$integrationSettings->setDebugMode( $debugMode );

			// Clover Order Printing
			$integrationSettings->setCloverOrderPrinting( $cloverOrderPrinting );

			// Clover Order Printing for Recurring/Subscription Orders
			$integrationSettings->setCloverOrderPrintingRecurring( $cloverOrderPrintingRecurring );

			// Google Pay
			$integrationSettings->setGooglePay( $googlePayEnabled );

			// Transaction Mode
			$integrationSettings->setTransactionMode( $transactionMode );

			// 3-D Secure beta opt-in
			$integrationSettings->setThreeDSecureBeta( $threeDSecureBeta );

		} catch ( WeeConnectPayException $e ) {
			LogService::error( 'Failed to save WooCommerce gateway options: ' . $e->getMessage() );
		}
	}

	/**
	 * Override the parent admin_options method with our app.
	 *
	 * @since 1.4.0
	 * @updated 3.2.1
	 */
	public function admin_options() {
		try {
			$authMethod = $this->integrationSettings->getAuthMethodOrDefault();

			// For client_credentials auth, skip legacy integration ID setup
			if ( $authMethod !== 'client_credentials' ) {
				if ( ! $this->integration_id ) {
					LogService::info( 'Setting the integration id during admin_options hook' );
					$this->integration_id = $this->integrationSettings->getIntegrationUuid();
					LogService::info( 'Finished setting the integration id during admin_options hook. ID: ' . json_encode( $this->integration_id ) );
				}
			}

			// Reconcile against the backend's authoritative health verdict (pull channel).
			// For client_credentials, a Clover-side disconnect leaves accessTokenExists()
			// true while the integration is dead — the health flag is what catches that.
			$health = ( new IntegrationHealth( $this->integrationSettings ) )->reconcile();

			$isAuthenticated = $authMethod === 'client_credentials'
				? ( $this->integrationSettings->accessTokenExists() && ! $health->isDisconnected() )
				: $this->integrationSettings->isAuthValid();

			$vue_data = array(
				'pluginUrl'       => WEECONNECTPAY_PLUGIN_URL,
				'debugMode'       => $this->integrationSettings->getDebugModeOrDefault(),
				'isAuthenticated' => $isAuthenticated,
				'health'          => $health->toArray(),
				'restUrl'         => rtrim( get_rest_url(), '/' ),
				'nonce'           => wp_create_nonce( 'wp_rest' ),
				'wizardCompleted' => $this->integrationSettings->getWizardCompletedOrDefault(),
				'authMethod'      => $authMethod,
				'environment'     => Environment::getCurrentKey(),
				'environments'    => Environment::getVisibleEnvironments(),
			);

			// Legacy auth: include redirect URL
			if ( $authMethod !== 'client_credentials' ) {
				try {
					$vue_data['redirectUrl'] = $this->integrationSettings::redirectUrl();
				} catch ( \Throwable $e ) {
					$vue_data['redirectUrl'] = '#';
				}
			}

			// Add merchant info if available
			try {
				$cloverMerchant = $this->integrationSettings->getCloverMerchant();
				$vue_data['merchantName'] = $cloverMerchant->getName();
				$vue_data['merchantId']   = $cloverMerchant->getUuid();
			} catch ( \Throwable $e ) {
				// Merchant info not available yet
			}

			// GitPod support
			if ( getenv( 'GITPOD_WORKSPACE_URL' ) ) {
				/** @noinspection PhpUndefinedConstantInspection */
				$vue_data['gitpodBackendWorkspaceUrl'] = GITPOD_WCP_BACKEND_WORKSPACE_URL ?? 'GITPOD_URL_NOT_SET';
			}

			// React app replaces the WC form entirely — always hide the save button
			global $hide_save_button;
			$hide_save_button = true;

			$admin_panel = new AdminPanel();
			$admin_panel->init( $vue_data );

		} catch ( Exception $e ) {
			LogService::error( 'Exception in admin_options: ' . $e->getMessage() );
		}
	}

	function maybe_add_wc_notice( $fields, WP_Error $errors = null ) {
		global $wp;

		// Reset prevent submit
		$_POST['weeconnectpay_prevent_submit'] = false;

		if ( wc_notice_count( 'error' ) !== 0 ) {
			return;
		}

		// Untouched form
		if ( isset( $_POST['weeconnectpay_prevent_submit_empty_cc_form'] ) ) {
			wc_add_notice( __( 'Please enter your payment information.', 'weeconnectpay' ), 'error' );
			$_POST['weeconnectpay_prevent_submit'] = true;
		}

		// Card Number
		if ( isset( $_POST['weeconnectpay_prevent_submit_card_number_error_cc_form'] ) ) {
			wc_add_notice( __( 'Please enter a valid credit card number.', 'weeconnectpay' ), 'error' );
			$_POST['weeconnectpay_prevent_submit'] = true;
		}
		if ( isset( $_POST['weeconnectpay_prevent_submit_empty_card_number_cc_form'] ) ) {
			wc_add_notice( __( 'Please enter a valid credit card number.', 'weeconnectpay' ), 'error' );
			$_POST['weeconnectpay_prevent_submit'] = true;
		}

		// Expiry Date
		if ( isset( $_POST['weeconnectpay_prevent_submit_date_error_cc_form'] ) ) {
			wc_add_notice( __( 'Please enter a valid credit card expiry date.', 'weeconnectpay' ), 'error' );
			$_POST['weeconnectpay_prevent_submit'] = true;
		}
		if ( isset( $_POST['weeconnectpay_prevent_submit_empty_date_cc_form'] ) ) {
			wc_add_notice( __( 'Please enter a valid credit card expiry date.', 'weeconnectpay' ), 'error' );
			$_POST['weeconnectpay_prevent_submit'] = true;
		}

		// CVV
		if ( isset( $_POST['weeconnectpay_prevent_submit_cvv_error_cc_form'] ) ) {
			wc_add_notice( __( 'Please enter a valid credit card CVV number.', 'weeconnectpay' ), 'error' );
			$_POST['weeconnectpay_prevent_submit'] = true;
		}
		if ( isset( $_POST['weeconnectpay_prevent_submit_empty_cvv_cc_form'] ) ) {
			wc_add_notice( __( 'Please enter a valid credit card CVV number.', 'weeconnectpay' ), 'error' );
			$_POST['weeconnectpay_prevent_submit'] = true;
		}

		// Postal Code
		if ( isset( $_POST['weeconnectpay_prevent_submit_postal_code_error_cc_form'] ) ) {
			wc_add_notice( __( 'Please enter a valid credit card postal code.', 'weeconnectpay' ), 'error' );
			$_POST['weeconnectpay_prevent_submit'] = true;
		}
		if ( isset( $_POST['weeconnectpay_prevent_submit_empty_postal_code_cc_form'] ) ) {
			wc_add_notice( __( 'Please enter a valid credit card postal code.', 'weeconnectpay' ), 'error' );
			$_POST['weeconnectpay_prevent_submit'] = true;
		}
	}

	/**
	 * Displays a confirmation or error message while trying to connect to the Clover API.
	 *
	 * @since 3.11.1
	 * @access public
	 *
	 * @return void
	 */
	public function display_clover_login_notice() {
		// Bails out if it's not the right context.
		if ( ! isset( $_GET['section'], $_GET['weeconnectpay_status'] ) || 'weeconnectpay' !== $_GET['section'] ) {
			return;
		}

		// Defines specific status messages based on the value returned by the 'weeconnectpay_status' parameter.
		$status_messages = array(
			'connected' => __( 'The connection with Clover has been successfully established!', 'weeconnectpay' ),
			'error'     => __( 'An error occurred while trying to establish a connection with Clover, please try again in a few minutes.', 'weeconnectpay' ),
		);

		$notice_class   = in_array( $_GET['weeconnectpay_status'], array( 'connected' ) ) ? 'notice-success' : 'notice-error';
		$notice_message = $status_messages[ $_GET['weeconnectpay_status'] ] ?? $status_messages['error']; // Defaults to 'error' if the status message can't be found.
		echo '<div class="notice is-dismissible ' . sanitize_html_class( $notice_class ) . '"><p>' . esc_html( $notice_message ) . '</p></div>';
	}

	/**
	 * Surfaces an authoritative backend disconnect across wp-admin and leads the
	 * merchant back to the connection page. Non-dismissable on purpose: it reflects a
	 * live broken state that no longer processes payments, and it clears on its own
	 * once health heals (the cached flag is cleared on the next healthy reconcile).
	 *
	 * When the integration healed but the heal is unproven (evidence: unverified,
	 * WSO-66), the error is replaced by an informational "awaiting first transaction"
	 * notice in the same scope, so the merchant who saw the red banner everywhere
	 * gets the all-clear everywhere too. It disappears once a transaction proves
	 * the connection.
	 *
	 * Reads the cached verdict only — the backend pull happens at settings render
	 * (admin_options), not on every admin page load.
	 *
	 * @since 4.2.0
	 * @return void
	 */
	public function display_integration_health_notice() {
		try {
			if ( ! isset( $this->integrationSettings ) ) {
				return;
			}

			// Health surfacing is scoped to client_credentials; legacy keeps its own flow.
			if ( $this->integrationSettings->getAuthMethodOrDefault() !== 'client_credentials' ) {
				return;
			}

			$health = new IntegrationHealth( $this->integrationSettings );
			if ( $health->isDisconnected() ) {
				printf(
					'<div class="notice notice-error"><p>%1$s <a href="%2$s">%3$s</a></p></div>',
					esc_html( $this->integration_health_notice_message( $health->remediation() ) ),
					esc_url( WeeConnectPayUtilities::getSettingsURL() ),
					esc_html__( 'Go to WeeConnectPay settings', 'weeconnectpay' )
				);
			} elseif ( $health->isAwaitingFirstTransaction() ) {
				// Display-only reassurance after an unverified heal (contract §4, WSO-66):
				// the backend healed on a fresh token alone, so the connection is restored
				// but unproven until a real transaction succeeds. No action link — there is
				// nothing for the merchant to do. Self-clears on the first successful
				// charge (PUSH carries healed+verified) or a verified settings-page pull.
				printf(
					'<div class="notice notice-info"><p>%1$s</p></div>',
					esc_html__( 'WeeConnectPay has been reconnected and is awaiting your first transaction to confirm the connection.', 'weeconnectpay' )
				);
			}
		} catch ( \Throwable $e ) {
			// A notice must never break wp-admin.
			LogService::debug( 'WeeConnectPay: could not render integration health notice: ' . $e->getMessage() );
		}
	}

	/**
	 * Merchant-facing remediation copy keyed off the disconnect scenario.
	 * Two distinct remediations (contract §8.3): reconnect Clover vs re-issue the WCP key.
	 *
	 * @param string|null $remediation
	 * @return string
	 */
	private function integration_health_notice_message( ?string $remediation ): string {
		switch ( $remediation ) {
			case IntegrationHealth::REMEDIATION_RECONNECT_CLOVER:
				return __( 'WeeConnectPay can no longer reach your Clover account, so payments are not being processed. Please reconnect with Clover.', 'weeconnectpay' );
			case IntegrationHealth::REMEDIATION_REISSUE_WCP_KEY:
				return __( 'Your WeeConnectPay credentials are no longer valid, so payments are not being processed. Please re-issue your WeeConnectPay key.', 'weeconnectpay' );
			default:
				return __( 'Your WeeConnectPay integration is disconnected and is not processing payments. Please reconnect to continue accepting payments.', 'weeconnectpay' );
		}
	}

	/**
	 * Generates the WeeConnectPay settings form fields for WooCommerce to use in the payment gateway settings
	 *
	 * @return void
	 * @since 1.0.0
	 * @updated 3.3.0
	 */
	public function init_form_fields() {
		$integrationSettings                  = new IntegrationSettings();
		$isPostTokenizationVerificationActive = $integrationSettings->getPostTokenizationVerificationOrDefault();
		$isGoogleRecaptchaActive              = $integrationSettings->getGoogleRecaptchaOrDefault();
		$googleRecaptchaSiteKey               = $integrationSettings->getGoogleRecaptchaSiteKeyOrDefault();
		$googleRecaptchaSecretKey             = $integrationSettings->getGoogleRecaptchaSecretKeyOrDefault();
		$isHoneypotFieldActive                = $integrationSettings->getHoneypotFieldOrDefault();
		$isCloverOrderPrintingActive          = $integrationSettings->getCloverOrderPrintingOrDefault();
		$isCloverOrderPrintingRecurringActive = $integrationSettings->getCloverOrderPrintingRecurringOrDefault();
		$isGooglePayActive                    = $integrationSettings->getGooglePayOrDefault();

		$this->form_fields = array(
			'enabled'                        => array(
				'title'       => __( 'Enable', 'weeconnectpay' ),
				'label'       => __( 'Enable payment gateway', 'weeconnectpay' ),
				'type'        => 'checkbox',
				'description' => '',
				'default'     => 'yes',
			),
			'authorize_button'               => array(
				'title' => __( 'Authorize Plugin', 'weeconnectpay' ),
				'type'  => 'authorize_button',
			),
			'title'                          => array(
				'title'       => __( 'Title', 'weeconnectpay' ),
				'type'        => 'text',
				'description' => __( 'This controls the title which the user sees during checkout.', 'weeconnectpay' ),
				'desc_tip'    => true,
				'default'     => __( 'Payment by Credit Card', 'weeconnectpay' ),
			),
			'post_tokenization_verification' => array(
				'title'       => __( 'Post-Tokenization Verification', 'weeconnectpay' ),
				'type'        => 'checkbox',
				'label'       => __( 'Enable Post-Tokenization Verification', 'weeconnectpay' ),
				'default'     => 'no',
				'description' => __( 'When enabled, additional verification will be performed after card tokenization.', 'weeconnectpay' ),
			),
			'transaction_mode' => array(
				'title'       => __( 'Transaction Mode', 'weeconnectpay' ),
				'type'        => 'select',
				'default'     => 'order_payment',
				'description' => __( 'How payments are processed. Orders + Payment creates a Clover order. Authorization Only places a hold for later capture.', 'weeconnectpay' ),
				'options'     => array(
					'order_payment' => __( 'Orders + Payment (Recommended)', 'weeconnectpay' ),
					'auth_capture'  => __( 'Authorization + Capture', 'weeconnectpay' ),
					'pre_auth'      => __( 'Authorization Only (Pre-authorization)', 'weeconnectpay' ),
				),
			),
			'three_d_secure_beta' => array(
				'title'       => __( '3-D Secure (Beta)', 'weeconnectpay' ),
				'type'        => 'checkbox',
				'label'       => __( 'Enable 3-D Secure liability-shift authentication on card payments', 'weeconnectpay' ),
				'default'     => 'no',
				'description' => __( 'Beta — requires 3-D Secure to be enabled on your Clover merchant account (we do not verify this). When on, Visa requires that customers also provide their cardholder name and email or phone number during checkout; the payment form will collect these via Clover.', 'weeconnectpay' ),
				'desc_tip'    => false,
			),
			'debug_mode'                     => array(
				'title'       => __( 'Debug Mode', 'weeconnectpay' ),
				'type'        => 'checkbox',
				'label'       => __( 'Enable Debug Mode', 'weeconnectpay' ),
				'default'     => 'no',
				'description' => __( 'When enabled, debug information will be logged and can be viewed in the logs section below.', 'weeconnectpay' ),
			),
			'clover_order_printing'          => array(
				'title'       => __( 'Clover Order Printing', 'weeconnectpay' ),
				'type'        => 'checkbox',
				'label'       => __( 'Enable Clover Order Printing', 'weeconnectpay' ),
				'default'     => $isCloverOrderPrintingActive ? 'yes' : 'no',
				'description' => __( 'When enabled, orders will be automatically sent to Clover for printing after successful payment. This is a best-effort feature and will not interrupt order processing if printing fails.', 'weeconnectpay' ),
				'desc_tip'    => true,
			),
			'clover_order_printing_recurring' => array(
				'title'       => __( 'Clover Order Printing for Recurring Orders', 'weeconnectpay' ),
				'type'        => 'checkbox',
				'label'       => __( 'Enable Clover Order Printing for Recurring/Subscription Orders', 'weeconnectpay' ),
				'default'     => $isCloverOrderPrintingRecurringActive ? 'yes' : 'no',
				'description' => __( 'When enabled, recurring and subscription orders will also be sent to Clover for printing. This setting only applies when Clover Order Printing is enabled.', 'weeconnectpay' ),
				'desc_tip'    => true,
			),
			'google_recaptcha_enabled'       => array(
				'title'       => __( 'Google reCAPTCHA', 'weeconnectpay' ),
				'type'        => 'checkbox',
				'label'       => __( 'Enable Google reCAPTCHA', 'weeconnectpay' ),
				'description' => __( 'Enable Google reCAPTCHA v3 for extra security. This new reCAPTCHA is completely hidden from the customer. A score value between 0 (100% automated) and 1 (100% human) will be added in the order notes for each payment attempt.', 'weeconnectpay' ),
				'desc_tip'    => true,
				'default'     => $isGoogleRecaptchaActive ? 'yes' : 'no',
			),
			'google_recaptcha_site_key'      => array(
				'type'        => 'text',
				'title'       => __( 'Google reCAPTCHA Site Key', 'weeconnectpay' ),
				'description' => sprintf(
					/* translators: %s: URL to Google reCAPTCHA admin */
					__( 'Don\'t have a site key and private key for this domain? <a href="%s" target="_blank">Click here</a> to set it up.', 'weeconnectpay' ),
					'https://www.google.com/recaptcha/admin/create'
				),
				'default'     => $googleRecaptchaSiteKey,
			),
			'google_recaptcha_secret_key'    => array(
				'type'    => 'password',
				'title'   => __( 'Google reCAPTCHA Secret Key', 'weeconnectpay' ),
				'default' => $googleRecaptchaSecretKey,
			),
			'min_human_score_threshold'      => array(
				'title'             => __( 'Google reCAPTCHA Minimum Human Score Threshold', 'weeconnectpay' ),
				'type'              => 'number',
				'description'       => __( 'Enhance order security: Set a reCAPTCHA score threshold. The recommended default value is 0.5. Orders with scores below this setting will be considered as non-human order, the status will be set as "failed" in WooCommerce and no resource will be created in your Clover account.', 'weeconnectpay' ),
				'default'           => '0.5', // You can set a default value here.
				'desc_tip'          => true,
				'custom_attributes' => array(
					'step' => '0.1', // This sets the increment step to 0.1
					'min'  => '0',   // Minimum value
					'max'  => '1',   // Maximum value
				),
			),
			'honeypot_field_enabled'         => array(
				'title'       => __( 'Honeypot Fields', 'weeconnectpay' ),
				'type'        => 'checkbox',
				'label'       => __( 'Enable Honeypot Fields', 'weeconnectpay' ),
				'description' => __( 'As an additional bot detection step, hidden fields that are sometimes filled by bots will be added to the form.', 'weeconnectpay' ),
				'desc_tip'    => true,
				'default'     => $isHoneypotFieldActive ? 'yes' : 'no',
			),
			'google_pay_enabled'             => array(
				'title'       => __( 'Google Pay', 'weeconnectpay' ),
				'type'        => 'checkbox',
				'label'       => __( 'Enable Google Pay', 'weeconnectpay' ),
				'description' => __( 'Enable Google Pay payment method for supported browsers and devices. This feature is available for classic checkout only.', 'weeconnectpay' ),
				'desc_tip'    => true,
				'default'     => $isGooglePayActive ? 'yes' : 'no',
			),
		);
	}

	/**
	 * Screen button Field
	 */
	public function generate_authorize_button_html() {
		// $redirect_url = $this->url_api . '/login/clover?intent=authorize-redirect&integration_id=' . $this->integration_id;
		$redirect_url = IntegrationSettings::redirectUrl();
		try {
			$clover_merchant = $this->integrationSettings->getCloverMerchant();
		} catch ( Throwable $exception ) {
			LogService::error( 'Error fetching the current merchant for display in plugin settings. Message: ' . $exception->getMessage() );
		}

		?>
		<tr valign="top">
			<td colspan="2" class="">
				<div>
					<?php
					if ( isset( $clover_merchant ) ) {
						if ( $clover_merchant instanceof CloverMerchant ) {
							echo '<b><div>';
							esc_html_e( 'Merchant Name: ', 'weeconnectpay' );
							echo '</b>';
							esc_html_e( $clover_merchant->getName() );
							echo '</div>';
							echo '<b><div>';
							esc_html_e( 'Merchant ID: ', 'weeconnectpay' );
							echo '</b>';
							esc_html_e( $clover_merchant->getUuid() );
							echo '</div></br>';
						}
					}
					?>
				</div>
			</td>
			<td colspan="2" class="">
				<a href="<?php echo esc_url( $redirect_url ); ?>" class="button">
									<?php
									// if ( $this->authVerifyHttpCode !== 200 ) {
									// _e( 'Authorize Plugin', 'weeconnectpay' );
									// } else {
									esc_html_e( 'Log in as another Clover merchant or employee', 'weeconnectpay' );
									// }
									?>
					</a>
			</td>
		</tr>
		<?php
	}


	/*
	@TODO: Checks to prevent loading the gateway if it shouldn't
	 * @BODY: To check: Physical Location, Currency, etc
	 */
	public function payment_fields() {

		global $woocommerce;
		try {
			$script_data = array(
				'pakms'           => $this->integrationSettings->getPublicAccessKey(),
				'locale'          => WeeConnectPayUtilities::getLocale(),
				'amount'          => $woocommerce->cart->total * 100,
				'siteKey'         => $this->integrationSettings->getGoogleRecaptchaSiteKeyOrDefault(),
				'googlePayEnabled' => $this->integrationSettings->getGooglePayOrDefault() === '1',
				'threeDSecureBetaEnabled' => $this->integrationSettings->getThreeDSecureBetaOrDefault() === '1',
				'clover3DSSdkUrl'  => Environment::getClover3DSSdkUrl(),
				// Endpoint the 3DS flow controller calls after the browser
				// completes the method or challenge step (see WSO-9 Half B).
				'threeDSecureFinalizeUrl' => rest_url( 'weeconnectpay/v1/woocommerce/3ds/finalize' ),
				'threeDSecureRestNonce'   => wp_create_nonce( 'wp_rest' ),
			);

			// Safely add merchantId if available
			try {
				$merchant = $this->integrationSettings->getCloverMerchant();
				if ( $merchant && method_exists( $merchant, 'getUuid' ) ) {
					$merchantId = $merchant->getUuid();
					if ( ! empty( $merchantId ) ) {
						$script_data['merchantId'] = $merchantId;
					}
				}
			} catch ( Exception $e ) {
				// merchantId is optional; log but continue
				LogService::debug( 'merchantId not available for Classic checkout: ' . $e->getMessage() );
			}

			$payment_fields = new PaymentFields();
			$payment_fields->init( $script_data );
		} catch ( Exception $exception ) {
			LogService::error( 'Exception caught in payment_fields: ' . $exception->getMessage() );
			return array(
				'result'   => 'fail',
				'redirect' => '',
			);
		}
	}

	/**
	 * Tells WooCommerce whether the gateway should be available IE:( In checkout / order pay, etc ).
	 *
	 * @return bool
	 * @since 1.3.7
	 */
	public function is_available(): bool {
		// If WeeConnectPay is not enabled, return.
		if ( 'no' === $this->enabled ) {
			return false;
		}

		if ( ! ( new IntegrationSettings() )->arePaymentProcessingSettingsReady() ) {
			return false;
		}

		// SSL ( Even though plugin no longer loads without it )
		if ( ! is_ssl() ) {
			return false;
		}

		// Hide the gateway at checkout while the integration is authoritatively
		// disconnected — a charge would only fail. Reads the cached verdict (no
		// HTTP); the PUSH/PULL channels keep it current, and it clears on `healed`
		// so the gateway returns automatically once the merchant reconnects.
		//
		// Autonomous recovery (WSO-61): while disconnected the PUSH heal is dead
		// (no charge can fire through a hidden gateway) and the PULL otherwise only
		// runs when an admin reopens settings. Spend one throttled reconcile here so
		// a reconnected merchant re-offers the method on the next render with no
		// settings visit. Gated behind isDisconnected() — a healthy store makes no
		// HTTP call — and re-checked after, because a `healed` reconcile clears the
		// flag in place and the gateway becomes available on this same evaluation.
		$health = new IntegrationHealth( $this->integrationSettings );
		if ( $health->isDisconnected() ) {
			$health->reconcileThrottled();
			if ( $health->isDisconnected() ) {
				return false;
			}
		}

		return true;
	}


	/**
	 * @inheritDoc
	 * @updated 3.12.6
	 * 
	 * @param $order Either an order_id or an order object.
	 */
	public function process_payment( $order ) : array {
		$order      = wc_get_order( $order ); // Make sure we're working with an order object.
		$processor  = new WeeConnectPayOrderProcessor( $this->integrationSettings );
		$data       = $_POST;
		$order_type = WeeConnectPayHelper::get_order_type( $order );

		/**
		 * Step 1: Handling charge-based flows (pre-auth and auth+capture).
		 *
		 * Both pre_auth and auth_capture use the charges endpoint (POST /v1/clover/charges)
		 * instead of creating a Clover order. The difference is the capture flag:
		 * - pre_auth:     capture=false → order goes on-hold, merchant captures later.
		 * - auth_capture: capture=true  → authorize + capture in one request, order goes to processing.
		 *
		 * These flows are only used when the following conditions are met:
		 * 1. The transaction mode is 'pre_auth' or 'auth_capture' in the plugin settings.
		 * 2. This is a 'default' order type. Subscription orders always use the order flow.
		 * 3. No custom tenders are used. Custom tender orders always use the order flow.
		 *
		 * When conditions aren't met, both modes fall through to processOrderPayment() (safe default).
		 *
		 * @var string $transaction_mode             The transaction mode: 'order_payment', 'auth_capture', or 'pre_auth'.
		 * @var bool   $is_pre_authorization_enabled TRUE when transaction mode is 'pre_auth'.
		 * @var bool   $is_auth_capture              TRUE when transaction mode is 'auth_capture'.
		 * @var bool   $is_default_order             TRUE when the order type is 'default'.
		 * @var bool   $has_custom_tenders           FALSE when there are no custom tenders pending for this order.
		 *
		 * @return   array The response from the charge process.
		 */
		$transaction_mode             = $this->integrationSettings->getTransactionModeOrDefault() ?: 'order_payment';
		$is_pre_authorization_enabled = 'pre_auth' === $transaction_mode;
		$is_auth_capture              = 'auth_capture' === $transaction_mode;
		$is_default_order             = 'default' === $order_type;
		$has_custom_tenders           = ! empty( WeeConnectPayCustomTenderHelper::getCustomTendersPendingTotal( $order ) );
		if ( ( $is_pre_authorization_enabled || $is_auth_capture ) && $is_default_order && ! $has_custom_tenders ) {
			return $processor->processPreAuthorization( $order, $data, $is_auth_capture );
		}

		/**
		 * Step 2: Handling subscription orders and payment method changes.
		 *
		 * Must be handled before processing payments, since it might either:
		 * - Return a response immediately (when the payment method change context is 'change_payment_method'), or
		 * - Alter the $data array to include the token UUID.
		 *
		 * @var bool        $is_subscription_order TRUE when the order type is 'subscription', 'subscription_renewal', or 'subscription_early_renewal'.
		 * @var string|null $uuid_token            The UUID token for the subscription.
		 * @var string|null $context               The payment method change context.
		 * 
		 * @return   void|array The response after updating the payment method, else it continues to the next step.
		 */
		$is_subscription_order = in_array( $order_type, array( 'subscription', 'subscription_renewal', 'subscription_early_renewal' ), true );
		if ( $is_subscription_order ) {
			$uuid_token = WeeConnectPayHelper::get_token_uuid_from_subscription_order( $order );
			$context    = WeeConnectPayHelper::get_payment_method_change_context( $data, $order, $uuid_token, $order_type );

			// # Debug case: Missing card token. Might be needed if the payment was triggered manually from the backend.
			if ( empty( $data['token'] ) ) {
				LogService::debug( sprintf( 'Credit Card token missing for order (#%d)! Was this payment triggered manually from the backend?', $order->get_id() ) );
			}

			if ( $context ) {
				LogService::info(sprintf( 'Processing payment method change for order #%d. Context: %s', $order->get_id(), $context ) );

				// Returns the response immediately if only the payment method is being changed.
				$updated_payment_response = $processor->updatePaymentMethod( $order, $data );
				if ( 'change_payment_method' === $context ) {
					return $updated_payment_response;
				}
			}

			// Try to get an existing token from the order, which will be used when processing the payment.
			$data['token'] = $uuid_token ?? $data['token'] ?? null;
		}

		/**
		 * Step 3: Processing the payment.
		 *
		 * This is the behavior for default orders (non-subscription orders) when the pre-authorization is not enabled.
		 */
		$response = $processor->processOrderPayment( $order, $data );

		return $response;
	}

	/**
	 * @param float    $renewal_total
	 * @param WC_Order $renewal_order
	 */
	public function process_renewal_payment( $renewal_total, $renewal_order ) {
		LogService::info(sprintf(
			'Processing renewal payment for order #%d - Amount: %.2f',
			$renewal_order->get_id(),
			$renewal_total
		));
		$this->process_payment( $renewal_order );
	}

	/**
	 * iFrame payment callback handler. Called from the WeeConnectPay API server after a user attempts to pay for their order through the iFrame.
	 *
	 * @updated 3.0.0
	 * @deprecated since 3.0.0
	 */
	public function weeconnectpay_callback_handler() {
		die();
	}

	/**
	 * @param $order_id
	 * @param $amount
	 * @param $reason
	 *
	 * @updated 3.7.0
	 *
	 * @return bool|WP_Error
	 */
	public function process_refund( $order_id, $amount = null, $reason = null ) {
		LogService::info(
			sprintf(
				'Initiating refund for order #%d - Amount: %.2f, Reason: %s',
				$order_id,
				$amount ?? 0,
				$reason ?? 'not provided'
			)
		);

		/** @TODO: Add wpdb prefix on order creation call */
		// Needed for the DB prefix for multi site
		global $wpdb;
		$order = new WC_Order( $order_id );

		// Check for custom tenders in the order
		$customTenders = WeeConnectPayCustomTenderHelper::getCustomTenders( $order );
		if ( ! empty( $customTenders ) ) {
			$message = __( 'This order contains gift card or loyalty card payments. For security reasons, partial refunds are not available when multiple payment methods are used. Please use the "Refund" button in the WeeConnectPay Charges section above to process a full refund for each transaction.', 'weeconnectpay' );
			LogService::info(
				sprintf(
					'Refund blocked - Order #%d contains custom tenders, directing user to use individual charge refunds',
					$order_id
				)
			);
			return new WP_Error( 'wc-order', $message );
		}

		// Check if this is a charge-based payment (no order UUID means it used charge API)
		$clover_order_uuid = $order->get_meta( 'weeconnectpay_clover_order_uuid' );
		if ( empty( $clover_order_uuid ) ) {
			LogService::info(
				sprintf(
					'Order #%d has no Clover order UUID - routing to charge-based refund',
					$order_id
				)
			);
			return $this->process_charge_based_refund( $order, $amount, $reason );
		}

		$tax_included            = $order->get_meta( 'weeconnectpay_tax_included' );
		$merged_qty              = $order->get_meta( 'weeconnectpay_merged_qty' );
		$shipping_as_line_item   = null;
		$shipping_line_item_name = null;
		$shipping_item           = array();

		LogService::debug(
			sprintf(
				'Refund metadata for order #%d - Tax Included: %s, Merged Qty: %s',
				$order_id,
				$tax_included ? 'yes' : 'no',
				$merged_qty ? 'yes' : 'no'
			)
		);

		// Get the WC_Order Object instance (from the order ID)
		if ( ! is_a( $order, 'WC_Order' ) ) {
			LogService::error( sprintf( 'Refund failed - Invalid order ID #%d (not a WC_Order object)', $order_id ) );
			return new WP_Error( 'wc-order', __( 'Provided ID is not a WC Order', 'weeconnectpay' ) );
		}

		// Get the Order refunds (array of refunds)
		$order_refunds = $order->get_refunds();

		// Only get the last refund order created since we're only going to process the one we just created
		if ( ! isset( $order_refunds[0] ) ) {
			LogService::error( sprintf( 'Refund failed - No refund order found for order #%d', $order_id ) );
			return new WP_Error( 'wc-order', __( 'No WC Order Refund found', 'weeconnectpay' ) );
		}
		$latest_refund = $order_refunds[0];

		// Make sure we're not trying to refund an amount that is 0 or higher
		if ( ! $amount || $amount <= 0 ) {
			LogService::error( sprintf( 'Refund failed - Invalid amount (%.2f) for order #%d', $amount ?? 0, $order_id ) );
			return new WP_Error( 'wc-order', __( 'Refund amount must be higher than 0.', 'weeconnectpay' ) );
		}

		// Make sure it's an order refund object
		if ( ! is_a( $latest_refund, 'WC_Order_Refund' ) ) {
			LogService::error( sprintf( 'Refund failed - Latest refund is not a WC_Order_Refund object for order #%d', $order_id ) );
			return new WP_Error( 'wc-order', __( 'Last created refund is not a WC Order Refund', 'weeconnectpay' ) );
		}

		// Make sure it's not already been refunded HERE ( Payment processor checks need to be done on our backend for other refund means )
		if ( 'refunded' === $latest_refund->get_status() ) {
			LogService::error( sprintf( 'Refund failed - Order #%d has already been refunded', $order_id ) );
			return new WP_Error( 'wc-order', __( 'Order has been already refunded', 'weeconnectpay' ) );
		}

		$line_items = array();
		// Potential polymorphic calls during iteration -- Better try/catch as Woocommerce "conveniently" marks the refund as complete if there's an unhandled exception.
		try {
			LogService::info( sprintf( 'Processing line items for refund on order #%d', $order_id ) );
			// Get all the line items to refund

			$undocumentedChangePrefixText = __( "Due to an undocumented breaking change in the Clover API, we have temporarily disabled partial refunds.\n", 'weeconnectpay' );
			$orderWillNotBeRefundedText   = __( 'This request to refund will not be processed. Should you want to do a partial refund, you can do so through your Clover web dashboard.' );
			foreach ( $latest_refund->get_items() as $item_id => $item ) {
				LogService::debug(
					sprintf(
						'Processing refund for line item ID: %s on order #%d',
						$item_id,
						$order_id
					)
				);

				// Original order line item
				$refunded_item_id = $item->get_meta( '_refunded_item_id' );
				$refunded_item    = $order->get_item( $refunded_item_id );

				LogService::debug(
					sprintf(
						'Line item details - Order #%d, Item ID: %s, Name: %s, Quantity: %d, Total: %.2f, Tax: %.2f',
						$order_id,
						$refunded_item_id,
						$refunded_item->get_name(),
						abs( $item->get_quantity() ),
						abs( $item->get_total() ),
						abs( $item->get_total_tax() )
					)
				);

				// Check if the absolute value of refunded quantity, total, and tax match
				if ( abs( $item->get_quantity() ) != $refunded_item->get_quantity() ) {
					// Quantity must match total quantity -- This is no longer going to be relevant with Atomic Order as we will be able to split units on Clover's end and separate taxes
					$refundErrorReasonSprintfFormat = __( 'To refund this line item (%1$s), the quantity to refund (currently %2$s) must be the total line item quantity (%3$s)' );
					$refundFailureReason            = sprintf(
						$refundErrorReasonSprintfFormat,
						$refunded_item->get_name(),
						abs( $item->get_quantity() ),
						$refunded_item->get_quantity()
					);

					LogService::error( "Refund error - Partial refunds not allowed due to mismatched line item quantity. Item ID: $refunded_item_id" );
					return new WP_Error( 'wc-order', $undocumentedChangePrefixText . $refundFailureReason . "\n\n" . $orderWillNotBeRefundedText );
				} elseif ( WeeConnectPayHelper::safe_amount_to_cents_int( abs( $item->get_total() ) ) != WeeConnectPayHelper::safe_amount_to_cents_int( $refunded_item->get_total() ) ) {
					// Subtotal amount must match the refund subtotal amount
					$refundErrorReasonSprintfFormat = __( 'To refund this line item (%1$s), the amount before tax to refund (currently $%2$s) must be the line item total amount before tax ($%3$s)' );
					$refundFailureReason            = sprintf(
						$refundErrorReasonSprintfFormat,
						$refunded_item->get_name(),
						abs( $item->get_total() ),
						$refunded_item->get_total()
					);

					LogService::error( "Refund error - Partial refunds not allowed due to mismatched line item total. Item ID: $refunded_item_id" );
					return new WP_Error( 'wc-order', $undocumentedChangePrefixText . $refundFailureReason . "\n\n" . $orderWillNotBeRefundedText );
				} elseif ( WeeConnectPayHelper::safe_amount_to_cents_int( abs( $item->get_total_tax() ) ) != WeeConnectPayHelper::safe_amount_to_cents_int( $refunded_item->get_total_tax() ) ) {
					// Total Tax amount must match refund tax amount
					$refundErrorReasonSprintfFormat = __( 'To refund this line item (%1$s), the tax to refund (currently $%2$s) must be the line item total tax ($%3$s)' );
					$refundFailureReason            = sprintf(
						$refundErrorReasonSprintfFormat,
						$refunded_item->get_name(),
						abs( $item->get_total_tax() ),
						$refunded_item->get_total_tax()
					);

					LogService::error( "Refund error - Partial refunds not allowed due to mismatched line item tax. Item ID: $refunded_item_id" );
					return new WP_Error( 'wc-order', $undocumentedChangePrefixText . $refundFailureReason . "\n\n" . $orderWillNotBeRefundedText );
				}

				// Order Refund line item
				$line_items[] = array(
					'refunded_quantity'    => $item->get_quantity(),
					'refunded_line_total'  => WeeConnectPayHelper::safe_amount_to_cents_int( $item->get_total() ),
					'refunded_total_tax'   => WeeConnectPayHelper::safe_amount_to_cents_int( $item->get_total_tax() ),
					'order_refund_item_id' => $item_id,
					'refunded_item'        => array(
						'line_item_id'     => $refunded_item_id,
						'line_total'       => WeeConnectPayHelper::safe_amount_to_cents_int( $refunded_item->get_total() ),
						'line_total_tax'   => WeeConnectPayHelper::safe_amount_to_cents_int( $refunded_item->get_total_tax() ),
						'line_quantity'    => $refunded_item->get_quantity(),
						'line_description' => WeeConnectPayHelper::name_and_qty_as_clover_line_desc(
							$refunded_item->get_name(),
							$refunded_item->get_quantity()
						),
					),
				);

				// Log line item details for successful inclusion
				LogService::info( "Refund processed - Item ID: $refunded_item_id, Quantity: " . abs( $item->get_quantity() ) . ', Line Total: ' . abs( $item->get_total() ) . ', Tax: ' . $item->get_total_tax() );
			}

			// Fees refund
			/** @var WC_Order_Item_Fee $fee */
			foreach ( $latest_refund->get_fees() as $fee_id => $fee ) {

				// Get the metadata for the refunded fee item
				$refunded_fee_id = $fee->get_meta( '_refunded_item_id' );

				// Retrieve all fees from the original order
				$order_fees = $order->get_fees();

				// Initialize variable to hold the original fee item
				$refunded_fee = null;

				// Loop through the order fees to find the matching fee
				foreach ( $order_fees as $order_fee_id => $order_fee ) {
					if ( $order_fee_id == $refunded_fee_id ) {
						$refunded_fee = $order_fee;
						break;
					}
				}

				if ( ! $refunded_fee ) {
					// Subtotal amount must match the refund subtotal amount
					/* translators: 1: refund amount, 2: support email address */
					$refundErrorReasonSprintfFormat = __( 'Could not find the fee to refund (%1$s) within the original order. Please contact %2$s if you are seeing this message.', 'weeconnectpay' );
					$refundFailureReason            = sprintf(
						$refundErrorReasonSprintfFormat,
						$refunded_fee_id,
						'support@weeconnectpay.com'
					);

					LogService::error( "Refund error - Could not find the fee to refund (%s) within the original order. Refunded fee ID: $refunded_fee_id | Refunded fee name: {$refunded_fee->get_name()}" );
					return new WP_Error( 'wc-order', $undocumentedChangePrefixText . $refundFailureReason . "\n\n" . $orderWillNotBeRefundedText );
				}

				// Check if the absolute value of refunded quantity, total, and tax match -- Although quantity should never be used for fees, this is WordPress,
				// and a fee item is a child of an item, and somebody could have the brilliant idea to change the quantity of a fee, so I'm leaving it here.
				if ( abs( $fee->get_quantity() ) != $refunded_fee->get_quantity() ) {
					// Quantity must match total quantity -- This is no longer going to be relevant with Atomic Order as we will be able to split units on Clover's end and separate taxes
					$refundErrorReasonSprintfFormat = __( 'To refund this fee (%1$s), the quantity to refund (currently %2$s) must be the total fee quantity (%3$s)' );
					$refundFailureReason            = sprintf(
						$refundErrorReasonSprintfFormat,
						$refunded_fee->get_name(),
						abs( $fee->get_quantity() ),
						$refunded_fee->get_quantity()
					);

					LogService::error( "Refund error - Partial refunds not allowed due to mismatched fee quantity. Item ID: $refunded_fee_id" );
					return new WP_Error( 'wc-order', $undocumentedChangePrefixText . $refundFailureReason . "\n\n" . $orderWillNotBeRefundedText );

				} elseif ( WeeConnectPayHelper::safe_amount_to_cents_int( abs( $fee->get_total() ) ) != WeeConnectPayHelper::safe_amount_to_cents_int( $refunded_fee->get_total() ) ) {
					// Subtotal amount must match the refund subtotal amount
					$refundErrorReasonSprintfFormat = __( 'To refund this fee (%1$s), the amount before tax to refund (currently $%2$s) must be the fee total amount before tax ($%3$s)' );
					$refundFailureReason            = sprintf(
						$refundErrorReasonSprintfFormat,
						$refunded_fee->get_name(),
						abs( $fee->get_total() ),
						$refunded_fee->get_total()
					);

					LogService::error( "Refund error - Partial refunds not allowed due to mismatched fee total. Fee ID: $refunded_fee_id " );
					return new WP_Error( 'wc-order', $undocumentedChangePrefixText . $refundFailureReason . "\n\n" . $orderWillNotBeRefundedText );
				} elseif ( WeeConnectPayHelper::safe_amount_to_cents_int( abs( $fee->get_total_tax() ) ) != WeeConnectPayHelper::safe_amount_to_cents_int( $refunded_fee->get_total_tax() ) ) {
					// Total Tax amount must match refund tax amount
					$refundErrorReasonSprintfFormat = __( 'To refund this fee (%1$s), the tax to refund (currently $%2$s) must be the fee total tax ($%3$s)' );
					$refundFailureReason            = sprintf(
						$refundErrorReasonSprintfFormat,
						$refunded_fee->get_name(),
						abs( $fee->get_total_tax() ),
						$refunded_fee->get_total_tax()
					);

					LogService::error( "Refund error - Partial refunds not allowed due to mismatched fee tax. Item ID: $refunded_fee_id" );
					return new WP_Error( 'wc-order', $undocumentedChangePrefixText . $refundFailureReason . "\n\n" . $orderWillNotBeRefundedText );
				}

				// Order Refund line fee
				$line_items[] = array(
					'refunded_quantity'    => $fee->get_quantity(),
					'refunded_line_total'  => WeeConnectPayHelper::safe_amount_to_cents_int( $fee->get_total() ),
					'refunded_total_tax'   => WeeConnectPayHelper::safe_amount_to_cents_int( $fee->get_total_tax() ),
					'order_refund_item_id' => $fee_id,
					'refunded_item'        => array(
						'line_item_id'     => $refunded_fee_id,
						'line_total'       => WeeConnectPayHelper::safe_amount_to_cents_int( $refunded_fee->get_total() ),
						'line_total_tax'   => WeeConnectPayHelper::safe_amount_to_cents_int( $refunded_fee->get_total_tax() ),
						'line_quantity'    => $refunded_fee->get_quantity(),
						'line_description' => WeeConnectPayHelper::name_and_qty_as_clover_line_desc(
							$refunded_fee->get_name(),
							$refunded_fee->get_quantity()
						),
					),
				);

				// Log line fee details for successful inclusion
				LogService::info( "Refund processed - Item ID: $refunded_fee_id, Quantity: " . abs( $fee->get_quantity() ) . ', Line Total: ' . abs( $fee->get_total() ) . ', Tax: ' . $fee->get_total_tax() );
			}

			// Add shipping if it's part of the refund request
			if ( $latest_refund->get_shipping_total() + $latest_refund->get_shipping_tax() ) {

				// $refundShippingTotal = $latest_refund->get_shipping_total();
				// $refundShippingTax = $latest_refund->get_shipping_tax();
				// $refundShippingMethod = $latest_refund->get_shipping_method();
				// $totalShippingRefunded = $latest_refund->get_total_shipping_refunded();
				// Log details for debugging
				//
				// $order->get_shipping_total();
				// $orderShippingTotal = $order->get_shipping_total();
				// $orderShippingTax = $order->get_shipping_tax();
				// $orderShippingMethod = $order->get_shipping_method();
				// $totalShippingRefunded = $order->get_total_shipping_refunded();

				$shipping_line_item_name = $order->get_meta( 'weeconnectpay_shipping_line_item_name' );
				$shipping_as_line_item   = $order->get_meta( 'weeconnectpay_shipping_as_clover_line_item' );

				LogService::info(
					"Refund check - Shipping name: $shipping_line_item_name, 
				            Refunded Shipping Total: " . abs( $latest_refund->get_shipping_total() ) . ', Original Shipping Total: ' . $order->get_shipping_total() . ',
				            Refunded Shipping Taxes: ' . abs( $latest_refund->get_shipping_tax() ) . ', Original Shipping Taxes: ' . $order->get_shipping_tax()
				);

				$shippingTotalToCents         = WeeConnectPayHelper::safe_amount_to_cents_int( $order->get_shipping_total() );
				$shippingTotalRefundedToCents = WeeConnectPayHelper::safe_amount_to_cents_int( abs( $latest_refund->get_shipping_total() ) );

				if ( $shippingTotalToCents != $shippingTotalRefundedToCents ) {
					// Subtotal amount must match the refund subtotal amount
					$refundErrorReasonSprintfFormat = __( 'To refund this shipping item (%1$s), the amount before tax to refund (currently $%2$s) must be the shipping item total amount before tax ($%3$s)' );
					$refundFailureReason            = sprintf(
						$refundErrorReasonSprintfFormat,
						$shipping_line_item_name,
						abs( $latest_refund->get_shipping_total() ),
						$order->get_shipping_total()
					);

					LogService::error( "Refund error - Partial refunds not allowed due to mismatched shipping item total. Shipping item name: $shipping_line_item_name" );
					return new WP_Error( 'wc-order', $undocumentedChangePrefixText . $refundFailureReason . "\n\n" . $orderWillNotBeRefundedText );
				} elseif ( WeeConnectPayHelper::safe_amount_to_cents_int( $order->get_shipping_tax() ) != WeeConnectPayHelper::safe_amount_to_cents_int( abs( $latest_refund->get_shipping_tax() ) ) ) {
					// Total Tax amount must match refund tax amount
					$refundErrorReasonSprintfFormat = __( 'To refund this shipping item (%1$s), the shipping tax to refund (currently $%2$s) must be the shipping item total tax ($%3$s)' );
					$refundFailureReason            = sprintf(
						$refundErrorReasonSprintfFormat,
						$shipping_line_item_name,
						abs( $latest_refund->get_shipping_tax() ),
						$order->get_shipping_tax()
					);

					LogService::error( "Refund error - Partial refunds not allowed due to mismatched shipping item tax. Shipping item name: $shipping_line_item_name" );
					return new WP_Error( 'wc-order', $undocumentedChangePrefixText . $refundFailureReason . "\n\n" . $orderWillNotBeRefundedText );
				}

				// If there's an amount to refund related to shipping and the order to be refunded has the shipping line item name AND shipping as line item metadata
				if ( $shipping_as_line_item && $shipping_line_item_name ) {

					$shipping_line_item_amount = WeeConnectPayHelper::safe_amount_to_cents_int( $latest_refund->get_shipping_total() ) + WeeConnectPayHelper::safe_amount_to_cents_int( $latest_refund->get_shipping_tax() );

					$shipping_item['refunded_shipping_amount'] = $shipping_line_item_amount;
					$shipping_item['refunded_shipping_name']   = $shipping_line_item_name;
				} else {
					// Quick insight 2024 -- Do we really want to stop all the refund logic by returning here?
					return false;
				}
			}
		} catch ( Throwable $e ) {
			LogService::error( 'DEBUG: Process refund first try/catch exception: ' . $e->getMessage() );

			return false;
		}
		LogService::debug( 'DEBUG: Process refund AFTER first try/catch.' );

		$formatted_number = WeeConnectPayHelper::safe_amount_to_cents_int( $amount );

		$refund_payload = array(
			'clover_order_uuid'     => $order->get_meta( 'weeconnectpay_clover_order_uuid' ),
			'shipping_as_line_item' => '1' === $shipping_as_line_item,
			'tax_included'          => '1' === $tax_included,
			'merged_qty'            => '1' === $merged_qty,
			'woocommerce_order_id'  => $order_id,
			'wpdb_prefix'           => $wpdb->prefix,
			'amount'                => $formatted_number,
			'reason'                => $reason,
			'line_items'            => $line_items,
		);

		LogService::debug(
			sprintf(
				'Prepared refund payload for order #%d: %s',
				$order_id,
				json_encode( $refund_payload )
			)
		);

		// Add shipping item to payload if it's being refunded as a line item
		if ( isset( $shipping_item['refunded_shipping_amount'] ) ) {
			$refund_payload['shipping_item'] = $shipping_item;
			LogService::debug(
				sprintf(
					'Added shipping item to refund payload - Order #%d, Amount: %.2f, Name: %s',
					$order_id,
					$shipping_item['refunded_shipping_amount'] / 100,
					$shipping_item['refunded_shipping_name']
				)
			);
		}

		$refund_response = $this->api->refund_woocommerce_order( $refund_payload );
		LogService::debug(
			sprintf(
				'Refund API response for order #%d: %s',
				$order_id,
				json_encode( $refund_response )
			)
		);

		if ( $refund_response instanceof WP_Error ) {
			LogService::error(
				sprintf(
					'Refund API call failed for order #%d - Error: %s',
					$order_id,
					$refund_response->get_error_message()
				)
			);
			return $refund_response;
		}

		if ( isset( $refund_response->id )
			&& isset( $refund_response->amount )
			&& isset( $refund_response->charge )
			&& isset( $refund_response->status )
			&& ( 'succeeded' === $refund_response->status )
		) {
			$formatted_refund_amount = number_format( (float) $refund_response->amount / 100, 2, '.', '' );

			LogService::info(
				sprintf(
					'Refund successful for order #%d - Amount: %.2f, Refund ID: %s, Charge ID: %s',
					$order_id,
					$formatted_refund_amount,
					$refund_response->id,
					$refund_response->charge
				)
			);

			$chargeRefundNote  = '<b>' . __( 'Refunded: ', 'weeconnectpay' ) . '</b>';
			$chargeRefundNote .= sprintf(
				__( '%1$s %2$s', 'weeconnectpay' ),
				$formatted_refund_amount,
				$order->get_currency()
			)
								. '<br>';
			$chargeRefundNote .= '<b>' . __( 'Refund ID: ', 'weeconnectpay' ) . '</b>' . $refund_response->id . '<br>';
			$chargeRefundNote .= '<b>' . __( 'Charge refunded: ', 'weeconnectpay' ) . '</b>' . $refund_response->charge . '<br>';

			$raw_reason = $reason;
			if ( '' !== $reason ) {
				$reason            = '<b>' . __( 'Reason: ', 'weeconnectpay' ) . '</b>' . $reason;
				$chargeRefundNote .= $reason;
			}

			$order->add_order_note( $chargeRefundNote );

			// Record refund event in charge metadata for timeline display.
			try {
				WeeConnectPayHelper::addRefundEventToCharge(
					$order,
					$refund_response->charge,
					$refund_response->id,
					(int) $refund_response->amount,
					! empty( $raw_reason ) ? $raw_reason : 'wc_refund'
				);
			} catch ( \Exception $e ) {
				LogService::error( 'Failed to record refund event: ' . $e->getMessage() );
			}

			return true;
		} elseif ( isset( $refund_response->id )
					&& isset( $refund_response->amount_returned )
					&& isset( $refund_response->items )
					&& isset( $refund_response->status )
					&& ( 'returned' === $refund_response->status )
		) {
			$formatted_returned_amount = number_format( (float) $refund_response->amount_returned / 100, 2, '.', '' );

			LogService::info(
				sprintf(
					'Return successful for order #%d - Amount: %.2f, Return ID: %s',
					$order_id,
					$formatted_returned_amount,
					$refund_response->id
				)
			);

			$returnString  = '<b>' . __( 'Refunded: ', 'weeconnectpay' ) . '</b>';
			$returnString .= sprintf(
				__( '%1$s %2$s', 'weeconnectpay' ),
				$formatted_returned_amount,
				$order->get_currency()
			)
								. '<br>';
			$returnString .= '<b>' . __( 'Refund ID: ', 'weeconnectpay' ) . '</b>' . $refund_response->id . '<br>';

			$raw_reason = $reason;
			if ( '' !== $reason ) {
				$reason        = '<b>' . __( 'Reason: ', 'weeconnectpay' ) . '</b>' . $reason;
				$returnString .= $reason;
			}

			foreach ( $refund_response->items as $item_returned ) {
				if ( isset( $item_returned->parent )
					&& isset( $item_returned->description )
					&& isset( $item_returned->amount )
				) {
					$clover_item_id                   = $item_returned->parent;
					$clover_item_returned_description = $item_returned->description ?? null;
					$formatted_return_amount          = number_format( (float) $item_returned->amount / 100, 2, '.', '' );

					LogService::debug(
						sprintf(
							'Returned item details - Order #%d, Item ID: %s, Description: %s, Amount: %.2f',
							$order_id,
							$clover_item_id,
							$clover_item_returned_description,
							$formatted_return_amount
						)
					);

					$returnString .= '<b>' . __( 'Returned clover item ID: ', 'weeconnectpay' ) . '</b>';
					$returnString .= sprintf(
						__( '%1$s(%2$s %3$s) - %4$s', 'weeconnectpay' ),
						$clover_item_id,
						$formatted_return_amount,
						$order->get_currency(),
						$clover_item_returned_description
					)
									. '<br>';
				}
			}
			$order->add_order_note( $returnString );

			// Record return event in charge metadata for timeline display.
			try {
				$charges = $order->get_meta( 'weeconnectpay_charges', true );
				if ( is_array( $charges ) ) {
					// Find the active payment charge for this order.
					$target_charge_id = null;
					foreach ( $charges as $cid => $c ) {
						if ( in_array( $c['status'] ?? '', array( 'success', 'partially_refunded' ), true ) ) {
							$target_charge_id = $cid;
							break;
						}
					}
					if ( $target_charge_id ) {
						WeeConnectPayHelper::addRefundEventToCharge(
							$order,
							$target_charge_id,
							$refund_response->id,
							(int) $refund_response->amount_returned,
							! empty( $raw_reason ) ? $raw_reason : 'wc_return'
						);
					}
				}
			} catch ( \Exception $e ) {
				LogService::error( 'Failed to record return event: ' . $e->getMessage() );
			}

			return true;
		} else {
			LogService::error(
				sprintf(
					'Refund failed for order #%d - Invalid or unexpected API response',
					$order_id
				)
			);
			return new WP_Error( 'wc-order', __( 'Order has been already refunded', 'weeconnectpay' ) );
		}
	}

	/**
	 * Process refund for charge-based payments (preauth/capture flow).
	 *
	 * This method handles refunds for orders that were paid using the charge API
	 * (preauth → capture flow) instead of the order API.
	 *
	 * @since 3.17.0
	 * @param WC_Order    $order  The WooCommerce order object.
	 * @param float|null  $amount The refund amount in dollars.
	 * @param string|null $reason The refund reason.
	 * @return bool|WP_Error True on success, WP_Error on failure.
	 */
	private function process_charge_based_refund( $order, $amount, $reason ) {
		$order_id = $order->get_id();

		LogService::info(
			sprintf(
				'Processing charge-based refund for order #%d - Amount: %.2f',
				$order_id,
				$amount ?? 0
			)
		);

		// Get charges from order meta
		$charges = $order->get_meta( 'weeconnectpay_charges', true );
		if ( empty( $charges ) || ! is_array( $charges ) ) {
			LogService::error( sprintf( 'Charge-based refund failed - No charges found for order #%d', $order_id ) );
			return new WP_Error( 'wc-order', __( 'No charges found for this order.', 'weeconnectpay' ) );
		}

		// Find a refundable charge — accept 'success' or 'partially_refunded' (the status
		// set by addRefundEventToCharge after a previous partial refund on the same charge).
		$refundable_charge = null;
		foreach ( $charges as $charge ) {
			if ( in_array( $charge['status'] ?? '', array( 'success', 'partially_refunded' ), true ) ) {
				$refundable_charge = $charge;
				break;
			}
		}

		if ( ! $refundable_charge ) {
			LogService::error( sprintf( 'Charge-based refund failed - No refundable charge found for order #%d', $order_id ) );
			return new WP_Error(
				'wc-order',
				__( 'No refundable charge found. Please use the WeeConnectPay Charges metabox for individual charge refunds.', 'weeconnectpay' )
			);
		}

		$charge_id        = $refundable_charge['charge_id'];
		$charge_amount    = $refundable_charge['amount'] ?? 0; // Amount in cents
		$already_refunded = WeeConnectPayHelper::getChargeRefundedTotal( $refundable_charge );
		$charge_remaining = $charge_amount - $already_refunded;

		// Convert refund amount from dollars to cents
		$refund_amount_cents = WeeConnectPayHelper::safe_amount_to_cents_int( $amount );

		// Clover's charge-refund endpoint accepts an `amount` parameter, so partial refunds via
		// the WC native Refund button are supported. Reject only when the request exceeds what is
		// still refundable on this charge (original amount minus any prior refunds).
		if ( $refund_amount_cents > $charge_remaining ) {
			LogService::error(
				sprintf(
					'Charge-based refund failed - Requested %d cents exceeds remaining %d cents on charge %s (order #%d)',
					$refund_amount_cents,
					$charge_remaining,
					$charge_id,
					$order_id
				)
			);
			return new WP_Error(
				'wc-order',
				sprintf(
					/* translators: 1: remaining refundable amount, 2: currency code */
					__( 'Refund exceeds the remaining refundable amount on this charge (%1$s %2$s remaining).', 'weeconnectpay' ),
					number_format( $charge_remaining / 100, 2, '.', '' ),
					$order->get_currency()
				)
			);
		}

		LogService::debug(
			sprintf(
				'Charge-based refund details - Order #%d, Charge ID: %s, Charge Amount: %d cents',
				$order_id,
				$charge_id,
				$charge_amount
			)
		);

		try {
			// Process the refund via the helper function.
			$refundData = WeeConnectPayHelper::processCloverChargeRefund(
				$charge_id,
				$refund_amount_cents,
				'requested_by_customer',
				''
			);

			$refund_id = isset( $refundData['id'] ) ? $refundData['id'] : '';

			// Record refund event in charge metadata for timeline display.
			WeeConnectPayHelper::addRefundEventToCharge(
				$order,
				$charge_id,
				$refund_id,
				$refund_amount_cents,
				! empty( $reason ) ? $reason : 'wc_refund'
			);

			// Add the order note via the shared helper so the format matches the metabox refund flow
			// (Refunded amount + Refund ID + Refunded charge ID + Reason).
			WeeConnectPayHelper::addRefundNotesToOrder(
				$order,
				array(
					'result' => 'success',
					'data'   => $refundData,
				),
				! empty( $reason ) ? $reason : null
			);

			LogService::info(
				sprintf(
					'Charge-based refund successful for order #%d - Amount: %.2f, Charge ID: %s',
					$order_id,
					$amount,
					$charge_id
				)
			);

			return true;

		} catch ( Exception $e ) {
			LogService::error(
				sprintf(
					'Charge-based refund failed for order #%d - Error: %s',
					$order_id,
					$e->getMessage()
				)
			);
			return new WP_Error( 'wc-order', $e->getMessage() );
		}
	}

	/**
	 * Adds Capture Charge button for orders with preauth enabled.
	 *
	 * @param  WC_Order $order WooCommerce order object.
	 * @return void
	 */
	public function add_capture_charge_button( $order ) {
		// Only show button to users with WooCommerce management permissions
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
			return;
		}
		
		// Check if order is eligible for capture using helper method
		if ( ! \WeeConnectPay\WordPress\Plugin\includes\WeeConnectPayHelper::canCaptureCharge( $order, $this->id ) ) {
			return;
		}

		$order_id  = esc_attr( $order->get_id() );
		$query_url = admin_url( 'admin-ajax.php' );

		// Creates a nonce for the capture charge button.
		$nonce       = wp_create_nonce( 'capture_charge_' . $order_id . '_nonce' );
		$attributes  = ' data-capture-charge-' . $order_id . '-nonce="' . $nonce . '" ';
		$attributes .= ' data-order-id="' . $order_id . '" ';
		$attributes .= ' type="button" ';
		$attributes .= ' class="button-primary button" ';
		$attributes .= ' id="capture_charge_button_' . $order_id . '" ';
		$icon        = '<span class="dashicons dashicons-yes" style="vertical-align: middle;"></span>';
		
		// Translatable confirmation message
		$capture_confirm_message = __( 'Are you sure you want to capture this charge? This action cannot be undone.', 'weeconnectpay' );

		// Displays the capture charge button.
		echo '<button ' . $attributes . ' >' . $icon . ' ' . esc_html__( 'Capture Preauthorized Charge', 'weeconnectpay' ) . '</button>';

		// Adds JavaScript to handle the capture charge button.
		$javascript = <<<JS
			<script type="text/javascript">
				( function () {
					'use strict';

					// Remove any existing handler first to prevent duplicate event listeners
					// (WooCommerce AJAX re-renders can cause the script to run multiple times)
					jQuery( document ).off( 'click.weeconnectpay_capture_{$order_id}', '#capture_charge_button_{$order_id}' );
					
					// Bind the handler with a namespaced event
					jQuery( document ).on(
						'click.weeconnectpay_capture_{$order_id}',
						'#capture_charge_button_{$order_id}',
						function ( e ) {
							var button = jQuery( this );
							
							// Add confirmation prompt
							if ( ! confirm( '{$capture_confirm_message}' ) ) {
								return false;
							}
							
							// Disable button and change icon to WordPress spinner with inline animation
							button.prop( 'disabled', true );
							button.find( '.dashicons' )
								.removeClass( 'dashicons-yes' )
								.addClass( 'dashicons-update' )
								.css( 'animation', 'rotation 2s infinite linear' );
							
							jQuery.ajax( {
								type: "POST",
								url:  "{$query_url}",
								data: {
									action:               "weeconnectpay_capture_charge",
									order_id:             "{$order_id}",
									capture_charge_nonce: "{$nonce}",
								},
								success: function ( response ) {
									if ( response.data != null ) {
										if ( response.success && response.data.result === 'success' ) {
											// Redirect to show WooCommerce admin message (follows WooCommerce pattern)
											if ( response.data.redirect_url ) {
												window.location.href = response.data.redirect_url;
											} else {
												location.reload();
											}
										} else {
											// Capture failed - show error message to user
											// Try to get the most specific error message available
											var errorMessage = 'Failed to capture charge. Please try again.';
											if ( response.data ) {
												if ( response.data.error && response.data.error.message ) {
													// API error message (most specific)
													errorMessage = response.data.error.message;
												} else if ( response.data.message ) {
													// Generic error message
													errorMessage = response.data.message;
												}
											}
											alert( errorMessage );
											
											// Re-enable button on failure and restore original icon
											button.prop( 'disabled', false );
											button.find( '.dashicons' )
												.removeClass( 'dashicons-update' )
												.addClass( 'dashicons-yes' )
												.css( 'animation', '' );
										}
									}
								},
								error: function ( XMLHttpRequest, textStatus, errorThrown ) {
									alert( errorThrown );
									// Re-enable button on error and restore original icon
									button.prop( 'disabled', false );
									button.find( '.dashicons' )
										.removeClass( 'dashicons-update' )
										.addClass( 'dashicons-yes' )
										.css( 'animation', '' );
								},
							} );
						}
					);
				} )();
			</script>
			JS;
		
		echo $javascript;
	}


	/**
	 * Used in process_payment. Handles a WeeConnectPayException to properly display a notice and return the failure array.
	 *
	 * @param WeeConnectPayException $exception
	 *
	 * @return string[]
	 * @since 2.0.6
	 * @updated 2.6.0
	 */
	protected function handleProcessPaymentException( WeeConnectPayException $exception ): array {
		if ( $exception->getCode() === ExceptionCode::MISSING_SHIPPING_STATE
			|| $exception->getCode() === ExceptionCode::CUSTOMER_CREATION_EXCEPTION
			|| $exception->getCode() === ExceptionCode::STANDARDIZED_RESPONSE_EXCEPTION
			|| $exception->getCode() === ExceptionCode::INVALID_JSON_EXCEPTION
			|| $exception->getCode() === ExceptionCode::ORDER_LINE_ITEM_TOTAL_MISMATCH
			|| $exception->getCode() === ExceptionCode::UNSUPPORTED_ORDER_ITEM_TYPE
		) {
			wc_add_notice( esc_html( $exception->getMessage() ), 'error' );
		} else {
			LogService::error( 'An unhandled exception happened with the payment processor. Message: ' . $exception->getMessage() );
		}

		return array(
			'result'   => 'fail',
			'redirect' => '',
		);
	}

	/**
	 * Used in process_payment. Handles a WeeConnectPayException to properly display a notice and return the failure array.
	 *
	 * @param WeeConnectPayException $exception
	 *
	 * @return string[]
	 * @since 2.4.0
	 */
	protected function handleCustomerCreationException( WeeConnectPayException $exception ): array {
		if ( $exception->getCode() === ExceptionCode::MISSING_SHIPPING_STATE ) {
			wc_add_notice( esc_html( $exception->getMessage() ), 'error' );
		} else {
			LogService::error( 'An unhandled exception happened while preparing the order with the payment processor.' );
		}

		return array(
			'result'   => 'fail',
			'redirect' => '',
		);
	}



	/**
	 * @return array|void
	 */
	protected function verifyAuthentication() {
		$wp_env = WeeConnectPayUtilities::get_wp_env();

		switch ( $wp_env ) {
			case 'gitpod':
				$url_api = GITPOD_WCP_BACKEND_WORKSPACE_URL ?? 'GITPOD_URL_NOT_SET';
				break;
			case 'local':
			case 'development':
				// Do dev stuff
				$url_api = 'https://weeconnect-api.test';
				break;
			case 'staging':
				// Do staging stuff
				$url_api = 'https://apidev.weeconnectpay.com';
				break;
			case 'production':
			default:
				// Do production stuff
				$url_api = 'https://api.weeconnectpay.com';
		}

		try {

			$integration_id = Authentication::fetchIntegrationId();

			$integrationSettings = new IntegrationSettings();
			if ( $integrationSettings->accessTokenExists() ) {
				$authVerifyHttpCode = Authentication::verify( $integration_id );
			} else {
				$authVerifyHttpCode = 401;
			}
		} catch ( WeeConnectPayException $exception ) {
			die( json_encode( StandardizedResponse::emitError( $exception->toObject() ) ) );
		}

		return array( $url_api, $integration_id, $authVerifyHttpCode );
	}
}


