<?php
/**
 * NMI Card Payment Gateway.
 *
 * @package flo-checkout
 */

namespace Flo_Checkout\Gateways;

use Exception;
use Flo_Checkout\Gateways\Helpers\Gateway_Logger_Trait;
use Flo_Checkout\Gateways\Helpers\Interface_Flo_WC_Payment_Gateway;
use Flo_Checkout\Gateways\NMI\NMI_Hash_Input;
use Flo_Checkout\Shared;
use Northmill_Online_Shared_Checkout\Northmill_Online_Shared\Data\Payment_Methods;
use Northmill_Online_Shared_Checkout\Northmill_Online_Shared\Modules\Loggers\Log_Level_Enum;
use SimpleXMLElement;
use stdClass;
use WC_Data_Exception;
use WC_Order;
use WC_Payment_Gateway;
use WP_Error;

defined( 'ABSPATH' ) || exit;

/**
 * NMI Pay Gateway class.
 */
class NMI_Pay extends WC_Payment_Gateway implements Interface_Flo_WC_Payment_Gateway {
	use Gateway_Logger_Trait;

	public const ORDER_META_PROCESSED       = '_nmi_payment_page_process';
	public const ORDER_META_PROCESSED_VALUE = 'processed';

	/**
	 * Retry interval.
	 *
	 * @var int
	 */
	public int $retry_interval;

	/**
	 * Sandbox settings value.
	 *
	 * @var string
	 */
	private string $sandbox;

	/**
	 * Whether gateway is in sandbox mode.
	 *
	 * @var bool
	 */
	private bool $is_sandbox;

	/**
	 * Gateway terminal ID.
	 *
	 * @var string
	 */
	private string $terminal_id;

	/**
	 * Gateway transaction key.
	 *
	 * @var string
	 */
	private string $transaction_key;

	/**
	 * Gateway hash key.
	 *
	 * @var string
	 */
	private string $hash_key;

	/**
	 * Get Gateway ID.
	 *
	 * @return string
	 */
	public static function get_id(): string {
		return Payment_Methods::NMC_NMI;
	}

	/**
	 * Whether the gateway supports blocks.
	 *
	 * @return bool
	 */
	public static function has_block_support(): bool {
		return true;
	}

	/**
	 * Public setup.
	 *
	 * @return void
	 */
	public static function public_setup(): void {}

	/**
	 * NMI_Pay constructor.
	 */
	public function __construct() {
		$this->id                 = self::get_id();
		$this->method_title       = __( 'Northmill Checkout - NMI Pay', 'flo-checkout-plugin' );
		$this->method_description = __( 'Use credit card payment with Northmill. Requires SSL in live mode.', 'flo-checkout-plugin' );
		$this->has_fields         = false;
		$this->supports           = array(
			'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',
			'refunds',
		);

		$this->init_form_fields();

		// Get setting values.
		$this->enabled         = $this->get_option( 'enabled' );
		$this->sandbox         = $this->get_option( 'sandbox' );
		$this->is_sandbox      = 'yes' === $this->sandbox;
		$this->title           = $this->get_option( 'title' );
		$this->description     = $this->get_option( 'description' );
		$this->terminal_id     = ! $this->is_sandbox ? $this->get_option( 'live_terminal_id' ) : $this->get_option( 'sandbox_terminal_id' );
		$this->transaction_key = ! $this->is_sandbox ? $this->get_option( 'live_transaction_key' ) : $this->get_option( 'sandbox_transaction_key' );
		$this->hash_key        = ! $this->is_sandbox ? $this->get_option( 'live_hash_key' ) : $this->get_option( 'sandbox_hash_key' );

		// Save settings fields.
		add_action(
			'woocommerce_update_options_payment_gateways_' . $this->id,
			array(
				$this,
				'process_admin_options',
			),
			10,
			0
		);

		add_action( 'woocommerce_api_nmi_pay_gateway', array( $this, 'nmi_callback_response' ), 10, 0 );

		add_action(
			'woocommerce_scheduled_subscription_payment_' . $this->id,
			array(
				$this,
				'scheduled_NMI_subscription_payment',
			),
			10,
			2
		);
	}

	/**
	 * Plugin options, we deal with it in Step 3 too
	 */
	public function init_form_fields(): void {
		$label       = __( 'Enable Logging', 'flo-checkout-plugin' );
		$description = __( 'Enable the logging of errors.', 'flo-checkout-plugin' );

		if ( defined( 'WC_LOG_DIR' ) ) {
			$nmi_pay_log_url = add_query_arg( 'tab', 'logs', add_query_arg( 'page', 'wc-status', admin_url( 'admin.php' ) ) );
			$nmi_pay_log_key = 'nmi_pay-' . sanitize_file_name( wp_hash( 'nmi_pay' ) ) . '-log';
			$nmi_pay_log_url = add_query_arg( 'log_file', $nmi_pay_log_key, $nmi_pay_log_url );

			// translators: %1$s: opening a tag, %2$s: closing a tag.
			$label .= ' | ' . sprintf( __( '%1$sView Log%2$s', 'flo-checkout-plugin' ), '<a href="' . esc_url( $nmi_pay_log_url ) . '">', '</a>' );
		}

		$this->form_fields = array(
			'enabled'                 => array(
				'title'       => __( 'Enable/Disable', 'flo-checkout-plugin' ),
				'label'       => __( 'Enable credit card payment with Northmill', 'flo-checkout-plugin' ),
				'type'        => 'checkbox',
				'description' => '',
				'default'     => 'no',
			),
			'sandbox'                 => array(
				'title'       => __( 'Sandbox', 'flo-checkout-plugin' ),
				'label'       => __( 'Enable Sandbox Mode', 'flo-checkout-plugin' ),
				'type'        => 'checkbox',
				'description' => __( 'Enabling test mode(real payments will not be taken).', 'flo-checkout-plugin' ),
				'default'     => 'yes',
			),
			'title'                   => array(
				'title'       => __( 'Title', 'flo-checkout-plugin' ),
				'type'        => 'text',
				'description' => __( 'This controls the title which the user sees during checkout within the credit card payment type.', 'flo-checkout-plugin' ),
				'default'     => __( 'Pay with credit card', 'flo-checkout-plugin' ),
			),
			'description'             => array(
				'title'       => __( 'Description', 'flo-checkout-plugin' ),
				'type'        => 'text',
				'description' => __( 'This controls the description which the user sees during checkout within the credit card payment type.', 'flo-checkout-plugin' ),
				'default'     => __( 'Pay with credit card.', 'flo-checkout-plugin' ),
			),
			'sandbox_terminal_id'     => array(
				'title'       => __( 'Sandbox - Terminal-ID', 'flo-checkout-plugin' ),
				'type'        => 'text',
				'description' => __( 'This is the sandbox account Terminal-ID', 'flo-checkout-plugin' ),
				'default'     => '',
			),
			'sandbox_transaction_key' => array(
				'title'       => __( 'Sandbox Transaction Key', 'flo-checkout-plugin' ),
				'type'        => 'text',
				'description' => __( 'This is the sandbox Transaction Key. Follow these instructions create accounts https://testwebmis.creditcall.com.', 'flo-checkout-plugin' ),
				'default'     => '',
			),
			'sandbox_hash_key'        => array(
				'title'       => __( 'Sandbox Hash Key', 'flo-checkout-plugin' ),
				'type'        => 'text',
				'description' => __( 'This is hash key for sandbox transaction. This hash key can be request from eKashu Support (support@ekashu.com)', 'flo-checkout-plugin' ),
				'default'     => '',
			),
			'live_terminal_id'        => array(
				'title'       => __( 'Live - Terminal-ID', 'flo-checkout-plugin' ),
				'type'        => 'text',
				'description' => __( 'This is the live account Terminal-ID.', 'flo-checkout-plugin' ),
				'default'     => '',
			),
			'live_transaction_key'    => array(
				'title'       => __( 'Live Transaction Key', 'flo-checkout-plugin' ),
				'type'        => 'text',
				'description' => __( 'This is the live Transaction Key. Follow these instructions create accounts https://testwebmis.creditcall.com.', 'flo-checkout-plugin' ),
				'default'     => '',
			),
			'live_hash_key'           => array(
				'title'       => __( 'Live Hash Key', 'flo-checkout-plugin' ),
				'type'        => 'text',
				'description' => __( 'This is hash key for live transaction. This hash key can be request from eKashu Support (support@ekashu.com)', 'flo-checkout-plugin' ),
				'default'     => '',
			),
			'log_enabled'             => array(
				'title'       => __( 'Debug Log', 'flo-checkout-plugin' ),
				'label'       => $label,
				'description' => $description,
				'type'        => 'checkbox',
				'default'     => 'no',
			),
		);
	}

	/**
	 * Render payment fields.
	 */
	public function payment_fields(): void {
		if ( $this->description ) {
			echo wp_kses_post( wpautop( $this->description ) );
		}
	}

	/**
	 * Process the payment and return the result.
	 *
	 * @param int $order_id Order ID.
	 *
	 * @return array
	 */
	public function process_payment( $order_id ): array {

		$this->log(
			'Process order payment',
			array( 'order_id' => $order_id ),
			Log_Level_Enum::INFO
		);
		$this->log(
			'process_payment',
			array(
				'order_id' => $order_id,
			),
			Log_Level_Enum::INFO
		);

		$nmi_nonce = wp_create_nonce( 'nmi_callback_mode' );
		update_post_meta( $order_id, '_nmi_payment_page', $nmi_nonce ); // Create security calls.

		return array(
			'result'   => NMI_Hash_Input::SUCCESS,
			'redirect' => get_site_url() . '/?wc-api=nmi_pay_gateway&event_type=process&order_id=' . $order_id,
			// get_site_url() sometime it might cause an issue for network path enable site.
		);
	}

	/**
	 * Callback methods
	 *
	 * @phpcs:disable WordPress.Security.NonceVerification.Missing
	 * @phpcs:disable WordPress.Security.NonceVerification.Recommended
	 *
	 * @throws WC_Data_Exception -- When transaction ID cannot be set.
	 */
	public function nmi_callback_response(): void {

		if ( $_REQUEST['event_type'] ) {
			$event_type = $_REQUEST['event_type'];
			$order_id   = 0;

			if ( isset( $_REQUEST['order_id'] ) ) {
				$order_id = (int) sanitize_text_field( $_REQUEST['order_id'] );
			}

			// assign order id from post object from ekashu return on fail, success or callbacks methods.
			if ( isset( $_POST[ NMI_Hash_Input::EKASHU_REFERENCE ] ) ) {
				$order_id = (int) $_POST[ NMI_Hash_Input::EKASHU_REFERENCE ];
			}

			if ( 'success' === ( $_POST['ekashu_auth_result'] ?? null ) ) {
				$return_result = $this->hash_verification( $_POST );

				if ( $return_result ) {
					$this->callback_success_payment( $order_id, $event_type );
				} else {
					$this->log(
						'Hash verification failed',
						array(
							'order_id'  => $order_id,
							'$_REQUEST' => $_REQUEST,
						),
						Log_Level_Enum::ERROR
					);
				}
			} elseif ( 'fail' === $event_type || 'fail_payment_nmi' === $event_type ) {
				$this->callback_fail_payment( $order_id, $event_type );
			} elseif ( 'process' === $event_type ) {
				$this->log(
					'Callback process payment',
					array( 'order_id' => $order_id ),
					Log_Level_Enum::INFO
				);
				$this->callback_process_payment( $order_id );
			} else {
				$this->log(
					'Unknown NMI callback',
					array(
						'event_type'         => $event_type,
						'ekashu_auth_result' => $_POST['ekashu_auth_result'] ?? null,
						'POST'               => $_POST,
					),
					Log_Level_Enum::ERROR
				);
			}
		}
	}
	// @phpcs:enable

	/**
	 * Verify callback hash.
	 *
	 * @param array $post NMI callback post data.
	 *
	 * @return bool
	 */
	public function hash_verification( array $post ): bool {
		if ( $post['ekashu_seller_id'] !== $this->terminal_id ) {
			return false;
		}

		if ( substr( str_replace( ' ', '', $this->transaction_key ), 0, 8 ) !== $post['ekashu_seller_key'] ) {
			return false;
		}

		$endpoint = $this->is_sandbox
			? 'https://test.ekashu.com'
			: 'https://live.ekashu.com';

		$validated = NMI_Hash_Input::validate_hash( $post, $endpoint );

		if ( true === $validated ) {
			return true;
		} else {
			$this->log(
				'Hash verification failed',
				array(
					'order_id' => $post['ekashu_reference'],
					'$post'    => $post,
					'error'    => $validated,
				),
				Log_Level_Enum::ERROR
			);
			return false;
		}
	}

	/**
	 * Process payment after NMI callback.
	 *
	 * @param int $order_id WC order ID.
	 *
	 * @return void
	 */
	private function callback_process_payment( int $order_id ): void {
		$order      = wc_get_order( $order_id );
		$meta_field = get_post_meta( $order_id, self::ORDER_META_PROCESSED );

		if ( count( $meta_field ) > 0 ) {
			wc_add_notice(
				__( 'This order already been processed, please try again later', 'flo-checkout-plugin' ),
				'error'
			);
			wp_safe_redirect( wc_get_checkout_url() );
		}
		if ( $this->is_sandbox ) {
			$endpoint = 'https://test.ekashu.com';
		} else {
			$endpoint = 'https://live.ekashu.com';
		}

		$ekashu_terminal_id = $this->terminal_id;

		// Only assigning first 8 digits.
		$ekashu_transaction_key = substr( str_replace( ' ', '', $this->transaction_key ), 0, 8 );

		// Get Order ID and Key.
		$ekashu_id = $order_id;

		// Get Order Totals.
		$ekashu_currency    = $order->get_currency();
		$ekashu_total       = (float) $order->get_total();
		$wc_decimal_options = (int) get_option( 'woocommerce_price_num_decimals' );
		if ( 0 === $wc_decimal_options ) {
			$ekashu_total = number_format( $ekashu_total, 2, '.', '' );
		} else {
			$ekashu_total = $this->format_amount( $ekashu_total );
		}

		// Get Order User, Billing & Shipping Addresses.
		$ekashu_email = $order->get_billing_email();
		$ekashu_phone = $order->get_billing_phone();

		// Get Callbacks, success and fail URLs.
		$home = get_site_url();

		$ekashu_success_url          = $home . '/?wc-api=nmi_pay_gateway&event_type=success_payment_nmi';
		$ekashu_failure_url          = $home . '/?wc-api=nmi_pay_gateway&event_type=fail_payment_nmi';
		$ekashu_return_url           = $order->get_cancel_order_url();
		$ekashu_callback_success_url = $home . '/?wc-api=nmi_pay_gateway&event_type=success';
		$ekashu_callback_failure_url = $home . '/?wc-api=nmi_pay_gateway&event_type=fail';

		$site_title       = ( get_bloginfo( 'name' ) !== null )
			? html_entity_decode( get_bloginfo( 'name' ) )
			: ' ';
		$site_description = ( get_bloginfo( 'description' ) !== null )
			? html_entity_decode( get_bloginfo( 'description' ) )
			: ' ';

		$ekashu_pay_page_style = Shared::instance()->package_info->paths->get_url() . 'assets/css/flo_pay_nmi.css';
		$ekashu_shortcut_icon  = Shared::instance()->package_info->paths->get_url() . 'assets/images/northmill-fav.png';
		$ekashu_title          = 'Northmill';

		$ekashu_request_type = 'auth';
		if ( function_exists( 'wcs_order_contains_subscription' ) ) {
			/**
			 * Checked for function existence.
			 *
			 * @noinspection PhpUndefinedFunctionInspection
			 */
			$order_contain_subscription = wcs_order_contains_subscription( $order );
			if ( true === $order_contain_subscription && '0' === $ekashu_total ) {
				$ekashu_request_type = 'preauth';
			}
		}

		$hash_key = $this->hash_key;
		try {
			$hash_input = new NMI_Hash_Input(
				array(
					'ekashu_amount'                   => $ekashu_total,
					'ekashu_currency'                 => $ekashu_currency,
					'ekashu_reference'                => $ekashu_id,
					'ekashu_seller_id'                => $ekashu_terminal_id,
					'ekashu_seller_key'               => $ekashu_transaction_key,

					'ekashu_card_address_editable'    => 'true',

					'ekashu_card_address_verify'      => 'false',
					'ekashu_card_zip_code_verify'     => 'false',
					'ekashu_card_title_mandatory'     => 'false',
					'ekashu_delivery_title_mandatory' => 'false',

					'ekashu_seller_name'              => $site_title,
					'ekashu_description'              => $site_description,

					'ekashu_style_sheet'              => $ekashu_pay_page_style,
					'ekashu_shortcut_icon'            => $ekashu_shortcut_icon,
					'ekashu_title'                    => $ekashu_title,

					'ekashu_callback_include_post'    => 'true',
					'ekashu_include_post'             => 'true',
					'ekashu_auto_confirm'             => 'true',

					'ekashu_viewport'                 => 'device-width, initial-scale=1',

					'ekashu_failure_url'              => $ekashu_failure_url,
					'ekashu_success_url'              => $ekashu_success_url,
					'ekashu_callback_success_url'     => $ekashu_callback_success_url,
					'ekashu_callback_failure_url'     => $ekashu_callback_failure_url,
					'ekashu_request_type'             => $ekashu_request_type,
				)
			);
		} catch ( Exception $e ) {
			wc_add_notice( __( 'Error during checkout. Please choose another payment option.', 'flo-checkout-plugin' ), 'error' );
			$this->log(
				'Error during checkout',
				array(
					'order_id' => $order_id,
					'error'    => $e->getMessage(),
				),
				Log_Level_Enum::ERROR
			);
			wp_safe_redirect( wc_get_checkout_url() );
			exit;
		}

		$computed_hash = $hash_input->get_hash( $hash_key );

		echo '<p class="payinstruction">' . esc_html__( 'Please wait! You are now redirected to a secure payment page', 'flo-checkout-plugin' ) . '</p>';
		echo '<form id="payment_page_form" action=' . esc_url( $endpoint ) . ' method="post">';
		foreach ( $hash_input->get_form_data() as $key => $val ) {
			if ( is_null( $val ) ) {
				continue;
			}
			if ( is_bool( $val ) ) {
				echo '<input type="hidden" name="' . esc_attr( $key ) . '" value="' . ( $val ? 'true' : 'false' ) . '"/>';
			} else {
				echo '<input type="hidden" name="' . esc_attr( $key ) . '" value="' . esc_attr( $val ) . '"/>';
			}
		}
		echo '<input type="hidden" name="ekashu_hash_code" value="' . esc_attr( $computed_hash ) . '"/>';
		echo '<input type="hidden" name="ekashu_return_url" value="' . esc_attr( $ekashu_return_url ) . '"/>';
		echo '<input type="hidden" name="ekashu_card_name_required" value="true"/>';
		echo '<input type="hidden" name="ekashu_card_name_mandatory" value="true"/>';
		echo '<input type="hidden" name="ekashu_card_phone_number" value="' . esc_attr( $ekashu_phone ) . '"/>';
		echo '<input type="hidden" name="ekashu_delivery_email_mandatory" value="false"/>';
		echo '<input type="hidden" name="ekashu_valid_from_required" value="false" />';
		echo '<input type="hidden" name="ekashu_card_email_address" value="' . esc_attr( $ekashu_email ) . '" />';
		echo '<input style="display:none" type="submit" value="Pay"/>';
		echo '<style>#payment_page_form::before {height: 1em; width: 1em;display: block;position: absolute;
            top: 50%;left: 50%;margin-left: -0.5em;margin-top: -0.5em;content: "";animation: spin 1s ease-in-out infinite;
            background: url("' . esc_url( Shared::instance()->package_info->paths->get_url() . 'assets/images/loader.svg' ) . '") center center;background-size: cover;line-height: 1;text-align: center;
            font-size: 2em;color: rgba(0, 0, 0, 0.75)}@keyframes spin {from {transform:rotate(0deg);}to {transform:rotate(360deg);}}
            p.payinstruction{text-align:center;margin: 0 !important;}</style>';
		echo '<script type="text/javascript">
            document.getElementById("payment_page_form").submit();
            </script>
            </form>';
		update_post_meta( $order_id, self::ORDER_META_PROCESSED, 'processed' ); // Create security calls.
		wp_die();
	}

	/**
	 * Callback on failed payment.
	 *
	 * @param int    $order_id   WC order ID.
	 * @param string $event_type Event type.
	 *
	 * @return void
	 */
	private function callback_fail_payment( int $order_id, string $event_type ): void {
		$order = new WC_Order( $order_id );

		$current_order_status = $order->get_status();
		if ( WC_FAILED_STATUS !== $current_order_status ) { // This is stop update again the status.
			$this->log(
				'Payment failure',
				array( 'order_id' => $order_id ),
				Log_Level_Enum::ERROR
			);
			$order->update_status( WC_FAILED_STATUS );
		}

		if ( 'fail_payment_nmi' === $event_type ) {
			$this->log(
				'Fail payment redirect',
				array( 'order_id' => $order_id ),
				Log_Level_Enum::ERROR
			);
			wc_add_notice( __( 'Payment failed. Please contact bank for information', 'flo-checkout-plugin' ), 'error' );
			wp_safe_redirect( wc_get_checkout_url() );
		}

		// Callback fail method.
		if ( 'fail' === $event_type ) {
			$this->log(
				'Fail callback response received',
				array( 'order_id' => $order_id ),
				Log_Level_Enum::ERROR
			);
			$return_result                            = array();
			$return_result[ NMI_Hash_Input::SUCCESS ] = true;
			$return_result['errorCode']               = '200';

			wp_send_json( $return_result, $return_result['errorCode'] ); // Send received notification to server.
		}
	}

	/**
	 * Callback on successful payment.
	 *
	 * @param int    $order_id   WC order ID.
	 * @param string $event_type Event type.
	 *
	 * @return void
	 *
	 * @phpcs:disable WordPress.Security.NonceVerification.Missing -- Hash verification has been done.
	 * @throws WC_Data_Exception -- Thrown on failure to set transaction ID.
	 */
	private function callback_success_payment( int $order_id, string $event_type ): void {
		$order = new WC_Order( $order_id );

		// Adding transaction id.
		update_post_meta( $order_id, NMI_Hash_Input::EKASHU_TRANSACTION_ID, $_POST[ NMI_Hash_Input::EKASHU_TRANSACTION_ID ] );

		$this->handle_successful_subscriptions_order( $order_id );

		if ( isset( $_POST[ NMI_Hash_Input::EKASHU_TRANSACTION_ID ] ) ) {
			$order->set_transaction_id( $_POST[ NMI_Hash_Input::EKASHU_TRANSACTION_ID ] );
		}
		$order->payment_complete();
		$order->add_order_note( __( 'Order payment received', 'flo-checkout-plugin' ) );

		$this->log(
			'Payment completed',
			array( 'order_id' => $order_id ),
			Log_Level_Enum::INFO
		);

		if ( 'success_payment_nmi' === $event_type ) {
			$this->log(
				'Successful payment redirect',
				array( 'order_id' => $order_id ),
				Log_Level_Enum::INFO
			);

			WC()->cart->empty_cart();
			$thank_you_url = $order->get_checkout_order_received_url();
			wp_safe_redirect( $thank_you_url );
		}

		// Callback success method.
		if ( NMI_Hash_Input::SUCCESS === $event_type ) {
			$this->log(
				'Successful callback response received',
				array( 'order_id' => $order_id ),
				Log_Level_Enum::INFO
			);

			$return_result                            = array();
			$return_result[ NMI_Hash_Input::SUCCESS ] = true;
			$return_result['successCode']             = '200';

			wp_send_json( $return_result, $return_result['successCode'] );
		}
	}
	// @phpcs:enable

	/**
	 * Handle subscription order in success callback.
	 *
	 * @param int $order_id WC order ID.
	 *
	 * @return void
	 *
	 * @noinspection PhpUndefinedFunctionInspection -- Checked for subscription function existence
	 *
	 * @phpcs:disable WordPress.Security.NonceVerification.Missing -- Hash verification has been done.
	 * @phpcs:disable WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
	 */
	public function handle_successful_subscriptions_order( int $order_id ): void {
		$order = new WC_Order( $order_id );

		if ( function_exists( 'wcs_order_contains_subscription' ) ) {
			$order_contains_subscription = wcs_order_contains_subscription( $order );
			if ( true === $order_contains_subscription ) {
				$this->log(
					'Order contain subscriptions products',
					array( 'order_id' => $order_id ),
					Log_Level_Enum::INFO
				);

				$subs_order_id = '';
				$subscriptions = wcs_get_subscriptions_for_order( $order_id, array( NMI_Hash_Input::ORDER_TYPE => 'any' ) );
				foreach ( $subscriptions as $subscription_obj ) {
					$subs_order_id = $subscription_obj->get_id();
				}

				$hash_key               = $this->hash_key;
				$card_hash              = $_POST['ekashu_card_hash'];
				$card_ref               = $_POST['ekashu_card_reference'];
				$computed_card_hash     = base64_encode( $subs_order_id . $card_hash );
				$computed_card_ref_hash = base64_encode( $hash_key . $card_ref );

				update_post_meta( $order_id, 'ekashu_enc_hash', $computed_card_hash );
				update_post_meta( $order_id, 'ekashu_enc_reference', $computed_card_ref_hash );

				$this->log(
					'Order contain subscriptions products',
					array(
						'order_id'        => $order_id,
						'subscription_id' => $subs_order_id,
					),
					Log_Level_Enum::INFO
				);

				unset( $computed_card_hash );
				unset( $computed_card_ref_hash );
			}
		}
	}
	// @phpcs:enable

	/**
	 * Handle scheduled subscription payment.
	 *
	 * @param float    $amount_to_charge Amount to charge.
	 * @param WC_Order $renewal_order    WC order object.
	 *
	 * @return void
	 */
	public function scheduled_NMI_subscription_payment( float $amount_to_charge, WC_Order $renewal_order ): void {
		$response = $this->process_NMI_subscription_payment( $renewal_order, $amount_to_charge );
		$order_id = $renewal_order->get_id();
		if ( '0' === (string) $response->Result->LocalResult ) {
			$renewal_order->add_order_note( __( 'Order renewal payment received', 'flo-checkout-plugin' ) );

			if ( isset( $response->TransactionDetails->CardEaseReference ) ) {
				$payment_transaction_id = (string) $response->TransactionDetails->CardEaseReference;
				$renewal_order->payment_complete( $payment_transaction_id );
				update_post_meta( $order_id, NMI_Hash_Input::EKASHU_TRANSACTION_ID, $payment_transaction_id );
			} else {
				$renewal_order->payment_complete();
			}

			$this->log(
				'Order renewal completed',
				array( 'order_id' => $order_id ),
				Log_Level_Enum::INFO
			);
		}
		if ( '1' === (string) $response->Result->LocalResult ) {
			$renewal_order->add_order_note(
				sprintf(
					// translators: %s: error message.
					__(
						'Order renewal is cancelled due to %s reason',
						'flo-checkout-plugin'
					),
					(string) $response->Result->Errors->Error
				)
			);
			$renewal_order->update_status( 'wc-cancelled' );

			$this->log(
				'Order renewal is cancelled',
				array(
					'order_id'    => $order_id,
					'result_code' => $response->Result->LocalResult,
					'error'       => $response->Result->Errors->Error,
				),
				Log_Level_Enum::ERROR
			);
		}
		if ( '2' === (string) $response->Result->LocalResult ) {
			$renewal_order->add_order_note(
				sprintf(
					// translators: %s: error message.
					__( 'Order renewal pending due to %s reason', 'flo-checkout-plugin' ),
					(string) $response->Result->Errors->Error
				)
			);

			$renewal_order->update_status( 'wc-pending' );

			$this->log(
				'Order renewal pending',
				array(
					'order_id'    => $order_id,
					'result_code' => $response->Result->LocalResult,
					'error'       => $response->Result->Errors->Error,
				),
				Log_Level_Enum::WARNING
			);
		}
		if ( '9' === (string) $response->Result->LocalResult ) {
			$renewal_order->add_order_note(
				sprintf(
						// translators: %s: error message.
					__(
						'Sorry, we are unable to process your payment at this time. Please reactivate and Please retry later. Order renewal update as pending due to %s reason',
						'flo-checkout-plugin'
					),
					(string) $response->Result->Errors->Error
				)
			);
			$renewal_order->update_status( 'wc-pending' );

			$this->log(
				'Order renewal pending',
				array(
					'order_id'    => $order_id,
					'result_code' => $response->Result->LocalResult,
					'error'       => $response->Result->Errors->Error,
				),
				Log_Level_Enum::WARNING
			);
		}
	}

	/**
	 * Handle subscription payment.
	 *
	 * @param WC_Order $order           WC order object.
	 * @param float    $amount_to_charge Amount to charge.
	 *
	 * @return SimpleXMLElement|stdClass
	 *
	 * @noinspection PhpUndefinedFunctionInspection
	 *
	 * @phpcs:disable WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
	 */
	public function process_NMI_subscription_payment( WC_Order $order, float $amount_to_charge ): SimpleXMLElement|stdClass {
		$hash_key        = $this->hash_key;
		$order_id        = $order->get_id();
		$order_currency  = $order->get_currency();
		$subscriptions   = wcs_get_subscriptions_for_order( $order_id, array( NMI_Hash_Input::ORDER_TYPE => 'any' ) );
		$parent_id       = '';
		$subscription_id = '';

		$this->log(
			'Start processing NMI subscription',
			array( 'order_id' => $order_id ),
			Log_Level_Enum::INFO
		);

		foreach ( $subscriptions as $subscription_obj ) {
			$subscription_id = $subscription_obj->get_id();
			$parent_id       = $subscription_obj->get_parent_id();
		}

		$card_hash      = '';
		$card_reference = '';

		if ( ! empty( $parent_id ) ) {
			$card_hash      = get_post_meta( $parent_id, 'ekashu_enc_hash', true );
			$card_reference = get_post_meta( $parent_id, 'ekashu_enc_reference', true );
		}

		$ekashu_card_hash = str_replace( $subscription_id, '', base64_decode( $card_hash ) );
		$ekashu_card_ref  = str_replace( $hash_key, '', base64_decode( $card_reference ) );

		$this->log(
			'Processing order renewal payment request',
			array(
				'order_id'         => $order_id,
				'amount_to_charge' => $amount_to_charge,
				'parent_id'        => $parent_id,
				'subscription_id'  => $subscription_id,
			),
			Log_Level_Enum::INFO
		);

		$xml_string = $this->prepare_xml_object( $ekashu_card_hash, $ekashu_card_ref, $amount_to_charge, $order_currency );

		$response = $this->start_transaction( $xml_string );
		libxml_use_internal_errors( true );
		$xml_object = simplexml_load_string( $response );
		if ( ! $xml_object ) {
			libxml_clear_errors();
			if ( $this->retry_interval <= 3 ) {
				$this->log(
					'Retry NMI subscription',
					array( 'order_id' => $order_id ),
					Log_Level_Enum::WARNING
				);

				sleep( $this->retry_interval );
				++$this->retry_interval;

				return $this->process_NMI_subscription_payment( $order, $amount_to_charge );
			}
			$xml_object                        = new stdClass();
			$xml_object->Result->LocalResult   = 9;
			$xml_object->Result->Errors->Error = $response;
		}

		$this->log(
			'Completed processing NMI subscription',
			array( 'order_id' => $order_id ),
			Log_Level_Enum::INFO
		);

		return $xml_object;
	}
	// @phpcs:enable

	/**
	 * Send transaction.
	 *
	 * @param string $xml_string XML string to send.
	 *
	 * @return string
	 *
	 * @phpcs:disable WordPress.WP.AlternativeFunctions -- Allow cURL.
	 */
	public function start_transaction( string $xml_string ): string {
		if ( $this->is_sandbox ) {
			$url = 'https://test.cardeasexml.com/generic.cex';
		} else {
			$url = 'https://live.cardeasexml.com/generic.cex';
		}

		$headers = array(
			'Content-type: text/xml',
			'Content-length: ' . strlen( $xml_string ),
			'Connection: close',
		);

		$ch = curl_init();
		curl_setopt( $ch, CURLOPT_URL, $url );
		curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
		curl_setopt( $ch, CURLOPT_TIMEOUT, 10 );
		curl_setopt( $ch, CURLOPT_POST, true );
		curl_setopt( $ch, CURLOPT_POSTFIELDS, $xml_string );
		curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers );

		$data = curl_exec( $ch );
		if ( curl_errno( $ch ) ) {
			$response = curl_error( $ch );
		} else {
			curl_close( $ch );
			$response = $data;
		}

		return $response;
	}
	// @phpcs:enable

	/**
	 * Prepare XML object for subscription payment.
	 *
	 * @param string $ekashu_card_hash Card hash.
	 * @param string $ekashu_card_ref  Card reference hash.
	 * @param float  $amount_to_charge Amount to charge.
	 * @param string $order_currency   Order currency.
	 *
	 * @return string
	 */
	public function prepare_xml_object( string $ekashu_card_hash, string $ekashu_card_ref, float $amount_to_charge, string $order_currency ): string {
		$ekashu_terminal_id     = $this->terminal_id;
		$ekashu_transaction_key = $this->transaction_key;

		$amount_to_charge   = $this->format_amount( $amount_to_charge );
		$wc_decimal_options = get_option( 'woocommerce_price_num_decimals' );

		if ( $wc_decimal_options > 0 ) {
			$amount_to_charge = str_replace( '.', '', $amount_to_charge );
		}

		return '<?xml version="1.0" encoding="us-ascii" standalone="yes"?>
                <Request type="CardEaseXML" version="1.0.0">
                    <TransactionDetails>
                        <MessageType autoconfirm="true">Auth</MessageType>
                        <Amount>' . $amount_to_charge . '</Amount>
                        <CurrencyCode>' . $order_currency . '</CurrencyCode>
                    </TransactionDetails>
                    <TerminalDetails>
                        <TerminalID>' . $ekashu_terminal_id . '</TerminalID>
                        <TransactionKey>' . $ekashu_transaction_key . '</TransactionKey>
                        <Software version="1.0.0">CardEaseXMLClient</Software>
                    </TerminalDetails>
                    <CardDetails>
                        <Manual type="cnp">
                            <CardReference>' . $ekashu_card_ref . '</CardReference>
                            <CardHash>' . $ekashu_card_hash . '</CardHash>
                        </Manual>
                    </CardDetails>
                </Request>';
	}

	/**
	 * Process refund.
	 *
	 * @param int    $order_id Order ID.
	 * @param float  $amount   Refund amount.
	 * @param string $reason   Refund reason.
	 *
	 * @return bool|WP_Error True or false based on success, or a WP_Error object.
	 *
	 * @throws Exception If the response XML data could not be parsed.
	 *
	 * @phpcs:disable WordPress.WP.AlternativeFunctions -- Allow cURL.
	 */
	public function process_refund( $order_id, $amount = null, $reason = '' ): WP_Error|bool {
		$url = ( $this->is_sandbox )
			? 'https://test.cardeasexml.com/generic.cex'
			: 'https://live.cardeasexml.com/generic.cex';

		if ( ! class_exists( '\SimpleXMLElement' ) ) {
			return new WP_Error(
				'flo-checkout-nmi-pay',
				sprintf(
					// translators: %s: String class name 'SimpleXMLElement'.
					__(
						'%s is not installed on this server. Please contact your server administrator.',
						'flo-checkout-plugin'
					),
					'SimpleXMLElement'
				) . ' ' . __( 'Please log in to NMI to perform refunds.', 'flo-checkout-plugin' )
			);
		}
		if ( ! function_exists( 'curl_init' ) ) {
			return new WP_Error(
				'flo-checkout-nmi-pay',
				sprintf(
						// translators: %s: Curl.
					__(
						'%s is not installed on this server. Please contact your server administrator.',
						'flo-checkout-plugin'
					),
					'Curl'
				) . ' ' . __( 'Please log in to NMI to perform refunds.', 'flo-checkout-plugin' )
			);
		}

		$ekashu_transaction_id = get_post_meta( $order_id, 'ekashu_transaction_id', true );
		if ( ! $ekashu_transaction_id ) {
			return new WP_Error(
				'flo-checkout-nmi-pay',
				__(
					'Ekashu transaction ID is missing.',
					'flo-checkout-plugin'
				) . ' ' . __( 'Please log in to NMI to perform refunds.', 'flo-checkout-plugin' )
			);
		}

		if ( is_null( $amount ) ) {
			return new WP_Error(
				'flo-checkout-nmi-pay',
				__(
					'Amount to refund missing.',
					'flo-checkout-plugin'
				) . ' ' .
				__( 'Please log in to NMI to perform refunds.', 'flo-checkout-plugin' )
			);
		}

		$order          = wc_get_order( $order_id );
		$order_currency = $order->get_currency();

		$amount             = $this->format_amount( $amount );
		$wc_decimals_option = get_option( 'woocommerce_price_num_decimals' );

		if ( $wc_decimals_option > 0 ) {
			$amount = str_replace( '.', '', $amount );
		}

		$request_xml = sprintf(
			"<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
            <Request type='CardEaseXML' version='1.2.0'>
                <TransactionDetails>
                    <MessageType>Refund</MessageType>
                    <CardEaseReference>%s</CardEaseReference>
                    %s
                </TransactionDetails>
                <TerminalDetails>
                    <TerminalID>%s</TerminalID>
                    <TransactionKey>%s</TransactionKey>
                    <Software version='1.3.6'>MoreFloCheckout</Software>
                    <Component version='1.0.0'>NmiPayGateway</Component>
                </TerminalDetails>
            </Request>",
			$ekashu_transaction_id,
			"<Amount>$amount</Amount><CurrencyCode>$order_currency</CurrencyCode>",
			$this->terminal_id,
			$this->transaction_key
		);

		$ch = curl_init();
		curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, 100 );
		curl_setopt( $ch, CURLOPT_HTTPHEADER, array( 'Content-Type: text/xml' ) );
		curl_setopt( $ch, CURLOPT_POST, true );
		curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
		curl_setopt( $ch, CURLOPT_TIMEOUT, 60 );
		curl_setopt( $ch, CURLOPT_URL, $url );
		curl_setopt( $ch, CURLOPT_USERAGENT, 'MoreFloCheckout NmiPayGateway' );
		curl_setopt( $ch, CURLOPT_POSTFIELDS, $request_xml );

		$response = curl_exec( $ch );
		if ( curl_errno( $ch ) ) {
			$this->log(
				'Error refunding order',
				array(
					'order_id' => $order_id,
					'request'  => $request_xml,
					'error'    => curl_error( $ch ),
				),
				Log_Level_Enum::ERROR
			);

			return new WP_Error( 'flo-checkout-nmi-pay', __( 'Unknown error while refunding.', 'flo-checkout-plugin' ) . ' ' . __( 'Please log in to NMI to perform refunds.', 'flo-checkout-plugin' ) );
		}

		curl_close( $ch );

		// Parse response.
		$xml    = new SimpleXMLElement( $response );
		$result = (string) $xml->Result->LocalResult;
		if ( '0' !== $result ) {
			// Errors from NMI, abort.
			$errors = array();
			foreach ( $xml->Result->Errors->Error as $error ) {
				$errors[] = sprintf(
					// translators: %1$s: error code, %2$s: error message.
					__(
						'Error %1$s: %2$s',
						'flo-checkout-plugin'
					),
					(string) $error['code'],
					(string) $error
				);
			}

			return new WP_Error( 'flo-checkout-nmi-pay', implode( '; ', $errors ) );
		}

		// Refund success, save reference.
		add_post_meta( $order_id, 'mf_checkout_nmi_refunds', $response );

		return true;
	}
	// @phpcs:enable

	/**
	 * Format amount based on WooCommerce settings.
	 *
	 * @param float $amount Amount to format.
	 *
	 * @return string
	 */
	public function format_amount( float $amount ): string {
		// Adding decimal point zeros if required based on WooCommerce settings.
		$wc_decimals = (int) get_option( 'woocommerce_price_num_decimals' );

		$nmi_amount_formats = array(
			0 => '00',
			1 => '0',
		);
		foreach ( $nmi_amount_formats as $key => $value ) {
			if ( $key === $wc_decimals ) {
				$amount = $amount . $value;
			}
		}
		if ( $wc_decimals >= 3 ) {
			$amount = substr( $amount, 0, - ( $wc_decimals - 2 ) );
		}

		return (string) $amount;
	}
}
