<?php

namespace WPCompanion\ScotiaBank;

if ( !defined( 'ABSPATH' ) ) {
    exit;
}
// Exit if accessed directly.
/**
 * Scotiabank gateway for WooCommerce
 *
 * @class   Gateway
 * @extends \WC_Payment_Gateway
 */
class Gateway extends \WC_Payment_Gateway {
    public $credit_fields = null;

    public $notify_url = null;

    public $timezone = null;

    public $sandbox = null;

    public $store_name = null;

    public $shared_secret = null;

    public $endpoint = null;

    public $debug = null;

    public $log = null;

    public $transaction_type = null;

    public $enable_scotia_api_features = null;

    /**
     * Constructor for the gateway.
     *
     * @access public
     * @return void
     */
    public function __construct() {
        $this->id = 'ig_scotiabank';
        $this->has_fields = false;
        $this->credit_fields = false;
        $this->method_title = __( 'Scotia eCom+™', 'ig-wc-scotiabank' );
        $this->method_description = __( 'Accept credit/debit card payments using Scotia eCom+™.', 'ig-wc-scotiabank' );
        $this->notify_url = home_url() . '/?ig-scotia-return-handler=yes';
        $api_endpoint = 'https://test.ipg-online.com/connect/gateway/processing';
        $this->supports = array('products', 'refunds');
        // Load the form fields.
        $this->init_form_fields();
        // Load the settings.
        $this->init_settings();
        // Get setting values.
        $this->enabled = $this->get_option( 'enabled' );
        $this->title = $this->get_option( 'title' );
        $this->description = $this->get_option( 'description' );
        $this->enable_scotia_api_features = $this->get_option( 'enable_scotia_api_features', 'no' );
        $this->timezone = $this->get_option( 'timezone' );
        $this->transaction_type = $this->get_option( 'transaction_type', 'sale' );
        $this->transaction_type = ( in_array( $this->transaction_type, array('sale', 'preauth'), true ) ? $this->transaction_type : 'sale' );
        $this->sandbox = 'yes';
        $this->store_name = $this->get_option( 'sandbox_store_name' );
        $this->shared_secret = $this->get_option( 'sandbox_shared_secret' );
        $this->endpoint = $api_endpoint;
        $this->debug = $this->get_option( 'debug' );
        $this->log = wc_get_logger();
        $this->view_transaction_url = '';
        if ( is_admin() ) {
            add_action( 'admin_notices', array($this, 'checks') );
            add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array($this, 'process_admin_options') );
            add_filter(
                'woocommerce_order_actions',
                array($this, 'add_order_actions'),
                20,
                2
            );
            add_action( 'woocommerce_order_action_capture_scotiabank', array($this, 'process_order_action_capture_scotiabank') );
            add_action( 'woocommerce_order_action_void_scotiabank', array($this, 'process_order_action_void_scotiabank') );
        }
        // Customer Emails.
        add_action(
            'woocommerce_email_before_order_table',
            array($this, 'email_instructions'),
            10,
            3
        );
        add_filter(
            'woocommerce_thankyou_order_received_text',
            array($this, 'thankyou_order_received_text'),
            10,
            2
        );
    }

    /**
     * Admin Panel Options
     * 
     * @access public
     * @return void
     */
    public function admin_options() {
        include_once ig_scotiabank()->plugin_path() . '/includes/admin-options.php';
    }

    /**
     * Save gateway settings and sync local properties.
     *
     * @access public
     * @return bool|WP_Error
     */
    public function process_admin_options() {
        $save_result = parent::process_admin_options();
        $this->enable_scotia_api_features = $this->get_option( 'enable_scotia_api_features', 'no' );
        return $save_result;
    }

    /**
     * Check if plugin settings are correct.
     *
     * @access public
     */
    public function checks() {
        if ( $this->enabled == 'no' ) {
            return;
        }
        // PHP Version.
        if ( version_compare( phpversion(), '7.3', '<' ) ) {
            /* translators: %s is replaced with the currently used PHP version */
            echo '<div class="error"><p>' . sprintf( esc_html__( 'Scotiabank error: PHP 7.3 or above required. You are using version %s.', 'ig-wc-scotiabank' ), wp_kses_post( phpversion() ) ) . '</p></div>';
            // Check required fields.
        } else {
            if ( !$this->store_name || !$this->shared_secret ) {
                echo '<div class="error"><p>' . esc_html__( 'Scotiabank error: Please enter your storename and shared_secret', 'ig-wc-scotiabank' ) . '</p></div>';
            }
        }
    }

    /**
     * Check if this gateway is enabled.
     *
     * @access public
     */
    public function is_available() {
        if ( $this->enabled == 'no' ) {
            return false;
        }
        if ( !is_ssl() && 'yes' != $this->sandbox ) {
            return false;
        }
        if ( !$this->store_name || !$this->shared_secret ) {
            return false;
        }
        return true;
    }

    /**
     * Check if Scotia API admin features are available.
     *
     * @access public
     * @return bool
     */
    public function is_api_feature_available() {
        if ( 'yes' !== $this->enable_scotia_api_features ) {
            return false;
        }
        return false;
        if ( method_exists( wpcsb_scotiabank(), 'is_plan' ) ) {
            if ( !wpcsb_scotiabank()->is_plan( 'professional' ) && !wpcsb_scotiabank()->is_plan( 'premium' ) ) {
                return false;
            }
        }
        $api_key = ( 'yes' === $this->sandbox ? $this->get_option( 'sandbox_scotia_api_key' ) : $this->get_option( 'scotia_api_key' ) );
        $api_secret = ( 'yes' === $this->sandbox ? $this->get_option( 'sandbox_scotia_api_secret' ) : $this->get_option( 'scotia_api_secret' ) );
        if ( !$api_key || !$api_secret ) {
            return false;
        }
        return true;
    }

    /**
     * Get the Fiserv transaction ID for an order (used for API operations)
     *
     * @param \WC_Order $order Order object
     * @return string Transaction ID or empty string if not found
     */
    public function get_order_transaction_id( $order ) {
        if ( !$order || !is_a( $order, '\\WC_Order' ) ) {
            return '';
        }
        $transaction_id = sanitize_text_field( $order->get_meta( '_ig_scotiabank_transaction_id', true ) );
        return $transaction_id;
    }

    /**
     * Initialise Gateway Settings Form Fields
     *
     * @access public
     */
    public function init_form_fields() {
        $this->form_fields = array(
            'enabled'          => array(
                'title'       => __( 'Enable/Disable', 'ig-wc-scotiabank' ),
                'label'       => __( 'Enable Scotia Ecom+', 'ig-wc-scotiabank' ),
                'type'        => 'checkbox',
                'description' => '',
                'default'     => 'no',
            ),
            'title'            => array(
                'title'       => __( 'Title', 'ig-wc-scotiabank' ),
                'type'        => 'safe_text',
                'description' => __( 'This controls the title which the user sees during checkout.', 'ig-wc-scotiabank' ),
                'default'     => __( 'Pay Securely with your Credit/Debit Card', 'ig-wc-scotiabank' ),
                'desc_tip'    => true,
            ),
            'description'      => array(
                'title'       => __( 'Description', 'ig-wc-scotiabank' ),
                'type'        => 'safe_text',
                'description' => __( 'This controls the description which the user sees during checkout.', 'ig-wc-scotiabank' ),
                'default'     => 'Click the button below to load the payment form',
                'desc_tip'    => true,
            ),
            'timezone'         => array(
                'title'       => __( 'Timezone', 'ig-wc-scotiabank' ),
                'type'        => 'Select',
                'description' => __( 'This controls the timezone used by the gateway.', 'ig-wc-scotiabank' ),
                'default'     => __( 'America/Port_of_Spain', 'ig-wc-scotiabank' ),
                'options'     => array(
                    'America/Anguilla'       => 'America/Anguilla',
                    'America/Antigua'        => 'America/Antigua',
                    'America/Nassau'         => 'America/Nassau',
                    'America/Barbados'       => 'America/Barbados',
                    'America/Belize'         => 'America/Belize',
                    'America/Cayman'         => 'America/Cayman',
                    'America/Dominica'       => 'America/Dominica',
                    'America/Santo_Domingo'  => 'America/Santo_Domingo',
                    'America/Grenada'        => 'America/Grenada',
                    'America/Guyana'         => 'America/Guyana',
                    'America/Port-au-Prince' => 'America/Port-au-Prince',
                    'America/Port_of_Spain'  => 'America/Port_of_Spain',
                    'America/Jamaica'        => 'America/Jamaica',
                    'America/Puerto_Rico'    => 'America/Puerto_Rico',
                    'America/St_Kitts'       => 'America/St_Kitts',
                    'America/St_Lucia'       => 'America/St_Lucia',
                    'America/St_Vincent'     => 'America/St_Vincent',
                    'America/Lower_Princes'  => 'America/Lower_Princes',
                    'America/Grand_Turk'     => 'America/Grand_Turk',
                    'America/Tortola'        => 'America/Tortola',
                    'America/St_Thomas'      => 'America/St_Thomas',
                ),
                'desc_tip'    => true,
            ),
            'transaction_type' => array(
                'title'       => __( 'Transaction Type', 'ig-wc-scotiabank' ),
                'type'        => 'select',
                'description' => __( 'Choose whether to capture funds immediately (sale) or authorize only (preauth). For pre-authorization, you must capture or void the transaction manually in Scotia Merchant Admin and then update the WooCommerce order status manually.', 'ig-wc-scotiabank' ),
                'default'     => 'sale',
                'options'     => array(
                    'sale'    => __( 'Sale (Capture immediately)', 'ig-wc-scotiabank' ),
                    'preauth' => __( 'Pre-Authorization (Authorize only)', 'ig-wc-scotiabank' ),
                ),
                'desc_tip'    => true,
            ),
            'debug'            => array(
                'title'       => __( 'Debug Log', 'ig-wc-scotiabank' ),
                'type'        => 'checkbox',
                'label'       => __( 'Enable logging', 'ig-wc-scotiabank' ),
                'default'     => 'no',
                'description' => sprintf( __( 'Log events at <code>%s</code>', 'ig-wc-scotiabank' ), admin_url( 'admin.php?page=wc-status&tab=logs' ) ),
            ),
        );
        // Free plan.
        $this->form_fields['enable_live'] = array(
            'title' => __( 'Buy our professional plan and enable live payments.', 'ig-wc-scotiabank' ),
            'type'  => 'title',
        );
        $this->form_fields['sandbox_store_name'] = array(
            'title'       => __( 'Sandbox Store ID', 'ig-wc-scotiabank' ),
            'type'        => 'text',
            'description' => __( 'Get your sandbox Store ID from Scotiabank.', 'ig-wc-scotiabank' ),
            'default'     => '',
            'desc_tip'    => true,
        );
        $this->form_fields['sandbox_shared_secret'] = array(
            'title'       => __( 'Sandbox Shared Secret', 'ig-wc-scotiabank' ),
            'type'        => 'text',
            'description' => __( 'Get your sandbox Shared Secret from Scotiabank.', 'ig-wc-scotiabank' ),
            'default'     => '',
            'desc_tip'    => true,
        );
    }

    /**
     * Payment form on checkout page.
     *
     * @access public
     */
    public function payment_fields() {
        $description = $this->get_description();
        $post_form = false;
        $is_preauth_mode = 'preauth' === $this->transaction_type;
        // Pay for page.
        if ( isset( $_GET['ig_scotiabank_form'] ) && 'pay' == $_GET['ig_scotiabank_form'] ) {
            global $wp;
            $order_id = $wp->query_vars['order-pay'];
            $post_form = true;
            $on_checkout = false;
        }
        // Checkout page.
        if ( isset( $_POST['payment_method'] ) && $_POST['payment_method'] === 'ig_scotiabank' ) {
            $posted = urldecode( $_POST['post_data'] );
            parse_str( $posted, $result );
            if ( isset( $result['ig_scotiabank_form'] ) && $result['ig_scotiabank_form'] == 'checkout' || false !== strpos( $result['_wp_http_referer'], '?ig_scotiabank_form=checkout' ) ) {
                $order_id = wc_get_order_id_by_order_key( sanitize_text_field( $result['key'] ) );
                $checkout_order = wc_get_order( $order_id );
                if ( !$checkout_order || !is_a( $checkout_order, '\\WC_Order' ) ) {
                    $post_form = false;
                } else {
                    $post_form = true;
                    $on_checkout = true;
                }
            }
        }
        if ( $post_form ) {
            $description = '
					<div id="ig-scotiabank-loading-icon" style="margin: 0 auto;width: fit-content;">
						<img src="' . plugins_url( '/assets/images/loading.gif', dirname( __FILE__ ) ) . '" width="64px" heigth="64px" style="text-align:center; float:unset; max-height: fit-content;" /> 
					</div>
					<div style="margin: 0 auto;width: fit-content;">
					' . esc_html__( 'Loading Scotia eCom+™ form', 'ig-wc-scotiabank' ) . '
					</div>';
        }
        if ( $this->sandbox == 'yes' ) {
            $description .= '
				<div style="margin: 0 auto; width: fit-content;">
					<strong>' . esc_html__( ' === TEST MODE ENABLED === ', 'ig-wc-scotiabank' ) . '</strong>
				</div>';
        }
        if ( $is_preauth_mode ) {
            $description .= '
				<div style="margin: 0 auto; width: fit-content;">
					<strong>' . esc_html__( 'Authorization Only: ', 'ig-wc-scotiabank' ) . '</strong>
					' . esc_html__( 'Your card will be authorized now and captured later by the merchant in Scotia Merchant Admin.', 'ig-wc-scotiabank' ) . '
				</div>';
        }
        if ( !empty( $description ) ) {
            $description = '
				<div id="ig-scotiabank-form" style="margin: 0 auto;width: fit-content;" >
					' . $description . '
				</div>';
            echo wp_kses_post( wpautop( wptexturize( trim( $description ) ) ) );
        }
        if ( $post_form ) {
            // Let' use our gateway
            $available_gateways = \WC()->payment_gateways->get_available_payment_gateways();
            if ( !isset( $available_gateways['ig_scotiabank'] ) ) {
                $this->log->add( $this->id, 'Scotiabank error: gateway not found in available gateways when rendering payment form for order #' . $order_id . '.' );
                return;
            }
            // Output Scotiabank form which will autosubmit itself
            $available_gateways['ig_scotiabank']->scotiabank_form( $order_id, $on_checkout );
        }
    }

    /**
     * Add content to the WC emails.
     *
     * @access public
     * @param  WC_Order $order
     * @param  bool $sent_to_admin
     * @param  bool $plain_text
     */
    public function email_instructions( $order, $sent_to_admin, $plain_text = false ) {
        if ( !$sent_to_admin && $this->id === $order->get_payment_method() && $order->has_status( 'on-hold' ) ) {
            $order_txntype = sanitize_text_field( $order->get_meta( '_ig_scotiabank_txntype', true ) );
            $is_preauth_order = 'preauth' === $order_txntype;
            if ( $is_preauth_order ) {
                $preauth_notice = esc_html__( 'Payment is authorized only and has not been captured yet. The merchant will complete capture in Scotia Merchant Admin.', 'ig-wc-scotiabank' );
                echo wp_kses_post( wpautop( wptexturize( $preauth_notice ) ) ) . PHP_EOL;
            }
        }
    }

    /**
     * Replace thank you text for pre-authorization orders.
     *
     * @access public
     * @param  string         $text
     * @param  \WC_Order|bool $order
     * @return string
     */
    public function thankyou_order_received_text( $text, $order ) {
        if ( !$order || !is_a( $order, '\\WC_Order' ) ) {
            return $text;
        }
        if ( $this->id !== $order->get_payment_method() ) {
            return $text;
        }
        $order_txntype = sanitize_text_field( $order->get_meta( '_ig_scotiabank_txntype', true ) );
        if ( 'preauth' !== $order_txntype || !$order->has_status( 'on-hold' ) ) {
            return $text;
        }
        return esc_html__( 'Thank you. Your order has been received and payment is currently authorized only. Your order will remain on hold until the merchant captures the payment.', 'ig-wc-scotiabank' );
    }

    /**
     * Process the payment and return the result.
     *
     * @access public
     * @param  int $order_id
     * @return array
     */
    public function process_payment( $order_id ) {
        $order = new \WC_Order($order_id);
        $redirect_url = add_query_arg( array(
            'ig_scotiabank_form' => 'pay',
        ), $order->get_checkout_payment_url() );
        $payment_init_lock = sanitize_text_field( $order->get_meta( '_ig_scotiabank_payment_init_locked', true ) );
        // Checkout requests can be retried/double-clicked, so keep payment initialization idempotent per order.
        if ( 'yes' === $payment_init_lock ) {
            $this->log->add( $this->id, 'Scotiabank process_payment duplicate init ignored for order #' . $order_id . '.' );
            return array(
                'result'   => 'success',
                'order_id' => $order_id,
                'redirect' => $redirect_url,
            );
        }
        $order->update_meta_data( '_ig_scotiabank_payment_init_locked', 'yes' );
        $order->update_meta_data( '_ig_scotiabank_txntype', $this->transaction_type );
        $order->save();
        $order->update_status( 'pending' );
        if ( $this->debug == 'yes' ) {
            $keys = array(
                'response_hash',
                'chargetotal',
                'currency',
                'fail_rc',
                'txndatetime',
                'oid',
                'fail_reason',
                'approval_code',
                'response_code_3dsecure',
                'refnumber',
                'cardnumber',
                'status'
            );
            $data = $this->sanitize_data_array( $keys, $_POST );
            $this->log->add( $this->id, 'Scotiabank selected for order #' . $order_id . ' ' . wc_print_r( $data, true ) );
        }
        // Return pay for order page redirect flagging our gateway is in use
        return array(
            'result'   => 'success',
            'order_id' => $order_id,
            'redirect' => $redirect_url,
        );
    }

    /**
     * 
     * Generate the hash required to process/check the transactions
     * 
     */
    public function generate_hash( $args, $shared_secret ) {
        $extended_hash = implode( '|', $args );
        $hash = hash_hmac(
            'sha256',
            $extended_hash,
            $shared_secret,
            true
        );
        $signature = base64_encode( $hash );
        return $signature;
    }

    /**
     * Get payment currency, either from current order or WC settings
     *
     * @return string three-digit currency code
     */
    function get_payment_currency( $order_id = false ) {
        $currency = get_woocommerce_currency();
        // Gets currency for the current order, that is about to be paid for
        if ( $order_id ) {
            $order = wc_get_order( $order_id );
            $currency = $order->get_currency();
        }
        $currency_numeric_code = $this->get_currency_numeric_code( $currency );
        return $currency_numeric_code;
    }

    /**
     * @return string three-digit currency code
     */
    function get_currency_numeric_code( $currency ) {
        $currency_numeric = '{"AFN":{"minor_unit":"2","numeric_code":"971"},"ALL":{"minor_unit":"2","numeric_code":"008"},"DZD":{"minor_unit":"2","numeric_code":"012"},"USD":{"minor_unit":"2","numeric_code":"840"},"EUR":{"minor_unit":"2","numeric_code":"978"},"AOA":{"minor_unit":"2","numeric_code":"973"},"XCD":{"minor_unit":"2","numeric_code":"951"},"":{"minor_unit":"","numeric_code":""},"ARS":{"minor_unit":"2","numeric_code":"032"},"AMD":{"minor_unit":"2","numeric_code":"051"},"AWG":{"minor_unit":"2","numeric_code":"533"},"AUD":{"minor_unit":"2","numeric_code":"036"},"AZN":{"minor_unit":"2","numeric_code":"944"},"BSD":{"minor_unit":"2","numeric_code":"044"},"BHD":{"minor_unit":"3","numeric_code":"048"},"BDT":{"minor_unit":"2","numeric_code":"050"},"BBD":{"minor_unit":"2","numeric_code":"052"},"BYR":{"minor_unit":"0","numeric_code":"974"},"BZD":{"minor_unit":"2","numeric_code":"084"},"XOF":{"minor_unit":"0","numeric_code":"952"},"BMD":{"minor_unit":"2","numeric_code":"060"},"INR":{"minor_unit":"2","numeric_code":"356"},"BOB":{"minor_unit":"2","numeric_code":"068"},"BAM":{"minor_unit":"2","numeric_code":"977"},"BWP":{"minor_unit":"2","numeric_code":"072"},"NOK":{"minor_unit":"2","numeric_code":"578"},"BRL":{"minor_unit":"2","numeric_code":"986"},"BND":{"minor_unit":"2","numeric_code":"096"},"BGN":{"minor_unit":"2","numeric_code":"975"},"BIF":{"minor_unit":"0","numeric_code":"108"},"KHR":{"minor_unit":"2","numeric_code":"116"},"XAF":{"minor_unit":"0","numeric_code":"950"},"CAD":{"minor_unit":"2","numeric_code":"124"},"CVE":{"minor_unit":"2","numeric_code":"132"},"KYD":{"minor_unit":"2","numeric_code":"136"},"CLP":{"minor_unit":"0","numeric_code":"152"},"CNY":{"minor_unit":"2","numeric_code":"156"},"COP":{"minor_unit":"2","numeric_code":"170"},"KMF":{"minor_unit":"0","numeric_code":"174"},"NZD":{"minor_unit":"2","numeric_code":"554"},"CRC":{"minor_unit":"2","numeric_code":"188"},"HRK":{"minor_unit":"2","numeric_code":"191"},"CUP":{"minor_unit":"2","numeric_code":"192"},"ANG":{"minor_unit":"2","numeric_code":"532"},"CZK":{"minor_unit":"2","numeric_code":"203"},"DKK":{"minor_unit":"2","numeric_code":"208"},"DJF":{"minor_unit":"0","numeric_code":"262"},"DOP":{"minor_unit":"2","numeric_code":"214"},"EGP":{"minor_unit":"2","numeric_code":"818"},"ERN":{"minor_unit":"2","numeric_code":"232"},"ETB":{"minor_unit":"2","numeric_code":"230"},"FKP":{"minor_unit":"2","numeric_code":"238"},"FJD":{"minor_unit":"2","numeric_code":"242"},"XPF":{"minor_unit":"0","numeric_code":"953"},"GMD":{"minor_unit":"2","numeric_code":"270"},"GEL":{"minor_unit":"2","numeric_code":"981"},"GHS":{"minor_unit":"2","numeric_code":"936"},"GIP":{"minor_unit":"2","numeric_code":"292"},"GTQ":{"minor_unit":"2","numeric_code":"320"},"GBP":{"minor_unit":"2","numeric_code":"826"},"GNF":{"minor_unit":"0","numeric_code":"324"},"GYD":{"minor_unit":"2","numeric_code":"328"},"HNL":{"minor_unit":"2","numeric_code":"340"},"HKD":{"minor_unit":"2","numeric_code":"344"},"HUF":{"minor_unit":"2","numeric_code":"348"},"ISK":{"minor_unit":"0","numeric_code":"352"},"IDR":{"minor_unit":"2","numeric_code":"360"},"IRR":{"minor_unit":"2","numeric_code":"364"},"IQD":{"minor_unit":"3","numeric_code":"368"},"ILS":{"minor_unit":"2","numeric_code":"376"},"JMD":{"minor_unit":"2","numeric_code":"388"},"JPY":{"minor_unit":"0","numeric_code":"392"},"JOD":{"minor_unit":"3","numeric_code":"400"},"KZT":{"minor_unit":"2","numeric_code":"398"},"KES":{"minor_unit":"2","numeric_code":"404"},"KPW":{"minor_unit":"2","numeric_code":"408"},"KRW":{"minor_unit":"0","numeric_code":"410"},"KWD":{"minor_unit":"3","numeric_code":"414"},"KGS":{"minor_unit":"2","numeric_code":"417"},"LAK":{"minor_unit":"2","numeric_code":"418"},"LBP":{"minor_unit":"2","numeric_code":"422"},"ZAR":{"minor_unit":"2","numeric_code":"710"},"LRD":{"minor_unit":"2","numeric_code":"430"},"LYD":{"minor_unit":"3","numeric_code":"434"},"CHF":{"minor_unit":"2","numeric_code":"756"},"MOP":{"minor_unit":"2","numeric_code":"446"},"MKD":{"minor_unit":"2","numeric_code":"807"},"MGA":{"minor_unit":"2","numeric_code":"969"},"MWK":{"minor_unit":"2","numeric_code":"454"},"MYR":{"minor_unit":"2","numeric_code":"458"},"MVR":{"minor_unit":"2","numeric_code":"462"},"MRO":{"minor_unit":"2","numeric_code":"478"},"MUR":{"minor_unit":"2","numeric_code":"480"},"MXN":{"minor_unit":"2","numeric_code":"484"},"MDL":{"minor_unit":"2","numeric_code":"498"},"MNT":{"minor_unit":"2","numeric_code":"496"},"MAD":{"minor_unit":"2","numeric_code":"504"},"MZN":{"minor_unit":"2","numeric_code":"943"},"MMK":{"minor_unit":"2","numeric_code":"104"},"NPR":{"minor_unit":"2","numeric_code":"524"},"NIO":{"minor_unit":"2","numeric_code":"558"},"NGN":{"minor_unit":"2","numeric_code":"566"},"OMR":{"minor_unit":"3","numeric_code":"512"},"PKR":{"minor_unit":"2","numeric_code":"586"},"PGK":{"minor_unit":"2","numeric_code":"598"},"PYG":{"minor_unit":"0","numeric_code":"600"},"PEN":{"minor_unit":"2","numeric_code":"604"},"PHP":{"minor_unit":"2","numeric_code":"608"},"PLN":{"minor_unit":"2","numeric_code":"985"},"QAR":{"minor_unit":"2","numeric_code":"634"},"RON":{"minor_unit":"2","numeric_code":"946"},"RUB":{"minor_unit":"2","numeric_code":"643"},"RWF":{"minor_unit":"0","numeric_code":"646"},"SHP":{"minor_unit":"2","numeric_code":"654"},"WST":{"minor_unit":"2","numeric_code":"882"},"STD":{"minor_unit":"2","numeric_code":"678"},"SAR":{"minor_unit":"2","numeric_code":"682"},"RSD":{"minor_unit":"2","numeric_code":"941"},"SCR":{"minor_unit":"2","numeric_code":"690"},"SLL":{"minor_unit":"2","numeric_code":"694"},"SGD":{"minor_unit":"2","numeric_code":"702"},"SBD":{"minor_unit":"2","numeric_code":"090"},"SOS":{"minor_unit":"2","numeric_code":"706"},"SSP":{"minor_unit":"2","numeric_code":"728"},"LKR":{"minor_unit":"2","numeric_code":"144"},"SDG":{"minor_unit":"2","numeric_code":"938"},"SRD":{"minor_unit":"2","numeric_code":"968"},"SZL":{"minor_unit":"2","numeric_code":"748"},"SEK":{"minor_unit":"2","numeric_code":"752"},"SYP":{"minor_unit":"2","numeric_code":"760"},"TWD":{"minor_unit":"2","numeric_code":"901"},"TJS":{"minor_unit":"2","numeric_code":"972"},"TZS":{"minor_unit":"2","numeric_code":"834"},"THB":{"minor_unit":"2","numeric_code":"764"},"TOP":{"minor_unit":"2","numeric_code":"776"},"TTD":{"minor_unit":"2","numeric_code":"780"},"TND":{"minor_unit":"3","numeric_code":"788"},"TRY":{"minor_unit":"2","numeric_code":"949"},"TMT":{"minor_unit":"2","numeric_code":"934"},"UGX":{"minor_unit":"0","numeric_code":"800"},"UAH":{"minor_unit":"2","numeric_code":"980"},"AED":{"minor_unit":"2","numeric_code":"784"},"UYU":{"minor_unit":"2","numeric_code":"858"},"UZS":{"minor_unit":"2","numeric_code":"860"},"VUV":{"minor_unit":"0","numeric_code":"548"},"VEF":{"minor_unit":"2","numeric_code":"937"},"VND":{"minor_unit":"0","numeric_code":"704"},"YER":{"minor_unit":"2","numeric_code":"886"},"ZMW":{"minor_unit":"2","numeric_code":"967"},"ZWL":{"minor_unit":"2","numeric_code":"932"}}';
        $currency_numeric = json_decode( $currency_numeric, true );
        if ( !isset( $currency_numeric[$currency] ) ) {
            $this->log->add( $this->id, 'Scotiabank error: unsupported currency "' . $currency . '". Cannot resolve numeric code.' );
            return '';
        }
        return $currency_numeric[$currency]['numeric_code'];
    }

    /**
     * Output for the order received page.
     *
     * @access public
     * @return void
     */
    public function scotiabank_form( $order_id, $on_checkout ) {
        $timezone = $this->timezone;
        $transaction_type = ( in_array( $this->transaction_type, array('sale', 'preauth'), true ) ? $this->transaction_type : 'sale' );
        $order = wc_get_order( $order_id );
        $order_amount = $order->get_total();
        $billing_fname = ( !empty( $order->get_billing_first_name() ) ? $order->get_billing_first_name() : 'NA' );
        $billing_lname = ( !empty( $order->get_billing_last_name() ) ? $order->get_billing_last_name() : 'NA' );
        $billing_company = ( !empty( $order->get_billing_company() ) ? $order->get_billing_company() : 'NA' );
        $billing_address_1 = ( !empty( $order->get_billing_address_1() ) ? $order->get_billing_address_1() : 'NA' );
        $billing_address_2 = ( !empty( $order->get_billing_address_2() ) ? $order->get_billing_address_2() : 'NA' );
        $billing_city = ( !empty( $order->get_billing_city() ) ? $order->get_billing_city() : 'NA' );
        $billing_state = ( !empty( $order->get_billing_state() ) ? $order->get_billing_state() : 'NA' );
        $billing_zip = ( !empty( $order->get_billing_postcode() ) ? $order->get_billing_postcode() : 'NA' );
        $billing_country = ( !empty( $order->get_billing_country() ) ? $order->get_billing_country() : 'NA' );
        $billing_phone = ( !empty( $order->get_billing_phone() ) ? $order->get_billing_phone() : 'NA' );
        $billing_email = ( !empty( $order->get_billing_email() ) ? $order->get_billing_email() : 'NA' );
        $billing_full_name = $billing_fname . ' ' . $billing_lname;
        $shipping_fname = ( !empty( $order->get_shipping_first_name() ) ? $order->get_shipping_first_name() : 'NA' );
        $shipping_lname = ( !empty( $order->get_shipping_last_name() ) ? $order->get_shipping_last_name() : 'NA' );
        $shipping_address_1 = ( !empty( $order->get_shipping_address_1() ) ? $order->get_shipping_address_1() : 'NA' );
        $shipping_address_2 = ( !empty( $order->get_shipping_address_2() ) ? $order->get_shipping_address_2() : 'NA' );
        $shipping_city = ( !empty( $order->get_shipping_city() ) ? $order->get_shipping_city() : 'NA' );
        $shipping_state = ( !empty( $order->get_shipping_state() ) ? $order->get_shipping_state() : 'NA' );
        $shipping_zip = ( !empty( $order->get_shipping_postcode() ) ? $order->get_shipping_postcode() : 'NA' );
        $shipping_country = ( !empty( $order->get_shipping_country() ) ? $order->get_shipping_country() : 'NA' );
        $shipping_full_name = $shipping_fname . ' ' . $shipping_lname;
        $order_currency = $this->get_payment_currency( $order_id );
        try {
            $date = new \DateTime('now', new \DateTimeZone($timezone));
        } catch ( \Exception $e ) {
            $this->log->add( $this->id, 'Scotiabank error: invalid timezone "' . $timezone . '", falling back to UTC. ' . $e->getMessage() );
            $date = new \DateTime('now', new \DateTimeZone('UTC'));
        }
        $transaction_date_time = $date->format( 'Y:m:d-H:i:s' );
        $parent_uri = $order->get_checkout_payment_url( $on_checkout );
        $response_fail_url = $this->notify_url . '&key=' . $order->get_order_key();
        $response_success_url = $this->notify_url . '&key=' . $order->get_order_key();
        // Helper function to check if value should be excluded
        $is_valid_value = function ( $value ) {
            return !empty( $value ) && $value !== 'NA';
        };
        // Build the single source-of-truth args array, filtering out empty and 'NA' values.
        // The hash is computed from these same filtered fields, so the two can never diverge.
        $args_raw = array(
            'baddr1'                             => $billing_address_1,
            'baddr2'                             => $billing_address_2,
            'bcity'                              => $billing_city,
            'bcompany'                           => $billing_company,
            'bcountry'                           => $billing_country,
            'bname'                              => $billing_full_name,
            'bstate'                             => $billing_state,
            'bzip'                               => $billing_zip,
            'chargetotal'                        => $order_amount,
            'checkoutoption'                     => 'combinedpage',
            'currency'                           => $order_currency,
            'email'                              => $billing_email,
            'hash_algorithm'                     => 'HMACSHA256',
            'language'                           => 'en_GB',
            'oid'                                => $order_id,
            'parentUri'                          => $parent_uri,
            'phone'                              => $billing_phone,
            'responseFailURL'                    => $response_fail_url,
            'responseSuccessURL'                 => $response_success_url,
            'saddr1'                             => $shipping_address_1,
            'saddr2'                             => $shipping_address_2,
            'scity'                              => $shipping_city,
            'scountry'                           => $shipping_country,
            'sname'                              => $shipping_full_name,
            'sstate'                             => $shipping_state,
            'storename'                          => $this->store_name,
            'szip'                               => $shipping_zip,
            'threeDSRequestorChallengeIndicator' => 1,
            'timezone'                           => $timezone,
            'transactionNotificationURL'         => $this->notify_url . '&key=' . $order->get_order_key(),
            'txndatetime'                        => $transaction_date_time,
            'txntype'                            => $transaction_type,
        );
        $args = array();
        foreach ( $args_raw as $key => $value ) {
            if ( $is_valid_value( $value ) ) {
                $args[$key] = $value;
            }
        }
        // Hash is derived from the filtered args that will be submitted — no separate array needed.
        $transaction_hash = $this->generate_hash( $args, $this->shared_secret );
        $args['hashExtended'] = $transaction_hash;
        if ( !$on_checkout ) {
            ?>
			</form>
			<?php 
        }
        ?>
		
			<form action="<?php 
        echo esc_url( $this->endpoint );
        ?>" class="ig-scotiabank-hosted-form loader" name="scotia-payment-form" id="scotia-payment-form" method="POST" target="ig-scotiabank-hosted-frame" style="display: none;">
				<?php 
        if ( isset( $args["baddr1"] ) ) {
            ?>
				<input type="hidden" name="baddr1" value="<?php 
            echo esc_attr( $args["baddr1"] );
            ?>" />
				<?php 
        }
        ?>
				
				<?php 
        if ( isset( $args["baddr2"] ) ) {
            ?>
				<input type="hidden" name="baddr2" value="<?php 
            echo esc_attr( $args["baddr2"] );
            ?>" />
				<?php 
        }
        ?>
				
				<?php 
        if ( isset( $args["bcity"] ) ) {
            ?>
				<input type="hidden" name="bcity" value="<?php 
            echo esc_attr( $args["bcity"] );
            ?>" />
				<?php 
        }
        ?>
				
				<?php 
        if ( isset( $args["bcompany"] ) ) {
            ?>
				<input type="hidden" name="bcompany" value="<?php 
            echo esc_attr( $args["bcompany"] );
            ?>" />
				<?php 
        }
        ?>
				
				<?php 
        if ( isset( $args["bcountry"] ) ) {
            ?>
				<input type="hidden" name="bcountry" value="<?php 
            echo esc_attr( $args["bcountry"] );
            ?>" />
				<?php 
        }
        ?>
				
				<?php 
        if ( isset( $args["bname"] ) ) {
            ?>
				<input type="hidden" name="bname" value="<?php 
            echo esc_attr( $args["bname"] );
            ?>" />
				<?php 
        }
        ?>
				
				<?php 
        if ( isset( $args["bstate"] ) ) {
            ?>
				<input type="hidden" name="bstate" value="<?php 
            echo esc_attr( $args["bstate"] );
            ?>" />
				<?php 
        }
        ?>
				
				<?php 
        if ( isset( $args["bzip"] ) ) {
            ?>
				<input type="hidden" name="bzip" value="<?php 
            echo esc_attr( $args["bzip"] );
            ?>" />
				<?php 
        }
        ?>
				
				<?php 
        if ( isset( $args["chargetotal"] ) ) {
            ?>
				<input type="hidden" name="chargetotal" value="<?php 
            echo esc_attr( $args["chargetotal"] );
            ?>" />
				<?php 
        }
        ?>
				
				<?php 
        if ( isset( $args["checkoutoption"] ) ) {
            ?>
				<input type="hidden" name="checkoutoption" value="<?php 
            echo esc_attr( $args["checkoutoption"] );
            ?>" />
				<?php 
        }
        ?>
				
				<?php 
        if ( isset( $args["currency"] ) ) {
            ?>
				<input type="hidden" name="currency" value="<?php 
            echo esc_attr( $args["currency"] );
            ?>" />
				<?php 
        }
        ?>
				
				<?php 
        if ( isset( $args["email"] ) ) {
            ?>
				<input type="hidden" name="email" value="<?php 
            echo esc_attr( $args["email"] );
            ?>" />
				<?php 
        }
        ?>
				
				<?php 
        if ( isset( $args["hash_algorithm"] ) ) {
            ?>
				<input type="hidden" name="hash_algorithm" value="<?php 
            echo esc_attr( $args["hash_algorithm"] );
            ?>" />
				<?php 
        }
        ?>
				
				<?php 
        if ( isset( $args["hashExtended"] ) ) {
            ?>
				<input type="hidden" name="hashExtended" value="<?php 
            echo esc_attr( $args["hashExtended"] );
            ?>" />
				<?php 
        }
        ?>
				
				<?php 
        if ( isset( $args["parentUri"] ) ) {
            ?>
				<input type="hidden" name="parentUri" value="<?php 
            echo esc_attr( $args["parentUri"] );
            ?>" />
				<?php 
        }
        ?>
				
				<?php 
        if ( isset( $args["phone"] ) ) {
            ?>
				<input type="hidden" name="phone" value="<?php 
            echo esc_attr( $args["phone"] );
            ?>" />
				<?php 
        }
        ?>
				
				<?php 
        if ( isset( $args["language"] ) ) {
            ?>
				<input type="hidden" name="language" value="<?php 
            echo esc_attr( $args["language"] );
            ?>" />
				<?php 
        }
        ?>
				
				<?php 
        if ( isset( $args["oid"] ) ) {
            ?>
				<input type="hidden" name="oid" value="<?php 
            echo esc_attr( $args["oid"] );
            ?>" />
				<?php 
        }
        ?>
				
				<?php 
        if ( isset( $args["responseFailURL"] ) ) {
            ?>
				<input type="hidden" name="responseFailURL" value="<?php 
            echo esc_attr( $args["responseFailURL"] );
            ?>" />
				<?php 
        }
        ?>
				
				<?php 
        if ( isset( $args["responseSuccessURL"] ) ) {
            ?>
				<input type="hidden" name="responseSuccessURL" value="<?php 
            echo esc_attr( $args["responseSuccessURL"] );
            ?>" />
				<?php 
        }
        ?>
				
				<?php 
        if ( isset( $args["saddr1"] ) ) {
            ?>
				<input type="hidden" name="saddr1" value="<?php 
            echo esc_attr( $args["saddr1"] );
            ?>" />
				<?php 
        }
        ?>
				
				<?php 
        if ( isset( $args["saddr2"] ) ) {
            ?>
				<input type="hidden" name="saddr2" value="<?php 
            echo esc_attr( $args["saddr2"] );
            ?>" />
				<?php 
        }
        ?>
				
				<?php 
        if ( isset( $args["scity"] ) ) {
            ?>
				<input type="hidden" name="scity" value="<?php 
            echo esc_attr( $args["scity"] );
            ?>" />
				<?php 
        }
        ?>
				
				<?php 
        if ( isset( $args["scountry"] ) ) {
            ?>
				<input type="hidden" name="scountry" value="<?php 
            echo esc_attr( $args["scountry"] );
            ?>" />
				<?php 
        }
        ?>
				
				<?php 
        if ( isset( $args["sname"] ) ) {
            ?>
				<input type="hidden" name="sname" value="<?php 
            echo esc_attr( $args["sname"] );
            ?>" />
				<?php 
        }
        ?>
				
				<?php 
        if ( isset( $args["sstate"] ) ) {
            ?>
				<input type="hidden" name="sstate" value="<?php 
            echo esc_attr( $args["sstate"] );
            ?>" />
				<?php 
        }
        ?>
				
				<?php 
        if ( isset( $args["storename"] ) ) {
            ?>
				<input type="hidden" name="storename" value="<?php 
            echo esc_attr( $args["storename"] );
            ?>" />
				<?php 
        }
        ?>
				
				<?php 
        if ( isset( $args["szip"] ) ) {
            ?>
				<input type="hidden" name="szip" value="<?php 
            echo esc_attr( $args["szip"] );
            ?>" />
				<?php 
        }
        ?>
				
				<?php 
        if ( isset( $args["threeDSRequestorChallengeIndicator"] ) ) {
            ?>
				<input type="hidden" name="threeDSRequestorChallengeIndicator" value="<?php 
            echo esc_attr( $args["threeDSRequestorChallengeIndicator"] );
            ?>" />
				<?php 
        }
        ?>
				
				<?php 
        if ( isset( $args["timezone"] ) ) {
            ?>
				<input type="hidden" name="timezone" value="<?php 
            echo esc_attr( $args["timezone"] );
            ?>" />
				<?php 
        }
        ?>

				<?php 
        if ( isset( $args["transactionNotificationURL"] ) ) {
            ?>
				<input type="hidden" name="transactionNotificationURL" value="<?php 
            echo esc_attr( $args["transactionNotificationURL"] );
            ?>" />
				<?php 
        }
        ?>

				<?php 
        if ( isset( $args["txndatetime"] ) ) {
            ?>
				<input type="hidden" name="txndatetime" value="<?php 
            echo esc_attr( $args["txndatetime"] );
            ?>" />
				<?php 
        }
        ?>
				
				<?php 
        if ( isset( $args["txntype"] ) ) {
            ?>
				<input type="hidden" name="txntype" value="<?php 
            echo esc_attr( $args["txntype"] );
            ?>" />
				<?php 
        }
        ?>
			</form>

			<iframe name="ig-scotiabank-hosted-frame" id="ig-scotiabank-hosted-frame" style="width:99%; height:55em; display:none; border:0;padding:1em;"></iframe>

			<?php 
        $src = ig_scotiabank()->plugin_url() . '/assets/js/';
        ?>
			<?php 
        $src .= ( $on_checkout ? 'ig_checkout_control.js' : 'ig_pay_control.js' );
        ?>
			
			<script src="<?php 
        echo esc_url( $src );
        ?>" async></script>

		<?php 
        if ( !$on_checkout ) {
            ?>
			<form>
			<?php 
        }
    }

    private function sanitize_data_array( $keys, $posted ) {
        $sanitized_data = array();
        foreach ( $keys as $key ) {
            if ( empty( $posted[$key] ) ) {
                continue;
            }
            $sanitized_key = sanitize_text_field( $key );
            $sanitized_value = sanitize_text_field( $posted[$key] );
            if ( !empty( $sanitized_key ) ) {
                $sanitized_data[$sanitized_key] = sanitize_text_field( $sanitized_value );
            }
        }
        return $sanitized_data;
    }

    private function save_order_meta_and_note( $order, $data ) {
        if ( $data ) {
            $note_lines = array();
            foreach ( $data as $key => $value ) {
                $sanitized_key = sanitize_text_field( $key );
                $sanitized_value = sanitize_text_field( $value );
                if ( '' === $sanitized_key || '' === $sanitized_value ) {
                    continue;
                }
                $label = ucwords( str_replace( '_', ' ', $sanitized_key ) );
                $note_lines[] = $label . ': ' . $sanitized_value;
            }
            if ( !empty( $note_lines ) ) {
                // Keep payment callback notes readable for merchant reconciliation in WooCommerce admin.
                $order->add_order_note( implode( PHP_EOL, $note_lines ) );
            }
            foreach ( $data as $key => $value ) {
                $order->update_meta_data( $key, $value );
            }
            $order->save();
        }
    }

    private function handle_callback_error( $log_message, $notice_message, $redirect_url = '' ) {
        $this->log->add( $this->id, $log_message );
        if ( function_exists( 'wc_add_notice' ) ) {
            wc_add_notice( $notice_message, 'error' );
        }
        $this->redirect_parent_window( $redirect_url );
    }

    private function redirect_parent_window( $redirect_url ) {
        $safe_redirect_url = ( !empty( $redirect_url ) ? $redirect_url : wc_get_checkout_url() );
        // Callback is rendered in an iframe, so redirecting top ensures customer lands on the actual checkout/pay page.
        echo '
		<script>
            window.top.location.href = "' . esc_url( $safe_redirect_url ) . '";
		</script>
		';
        exit;
    }

    private function log_callback_event(
        $event,
        $request_id,
        $order_id = 0,
        $status = '',
        $hash_check = 'not_checked',
        $extra = array()
    ) {
        // Structured JSON logs keep callback diagnostics searchable without storing sensitive raw payload data.
        $context = array(
            'request_id' => sanitize_text_field( $request_id ),
            'order_id'   => absint( $order_id ),
            'status'     => sanitize_text_field( $status ),
            'hash_check' => sanitize_text_field( $hash_check ),
        );
        if ( !empty( $extra ) ) {
            $context['extra'] = $extra;
        }
        $this->log->add( $this->id, 'Scotiabank callback ' . sanitize_text_field( $event ) . ': ' . wp_json_encode( $context ) );
    }

    public function return_handler( $order_key ) {
        $request_id = ( isset( $_SERVER['HTTP_X_REQUEST_ID'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_REQUEST_ID'] ) ) : uniqid( 'ig_scotia_', true ) );
        // Callback endpoint should only process server-to-server payment responses.
        // Rejecting non-POST requests prevents accidental browser hits from mutating order state.
        $request_method = ( isset( $_SERVER['REQUEST_METHOD'] ) ? strtoupper( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) ) : '' );
        if ( 'POST' !== $request_method ) {
            $this->log_callback_event(
                'invalid_method',
                $request_id,
                0,
                '',
                'not_checked',
                array(
                    'request_method' => $request_method,
                )
            );
            $this->handle_callback_error( 'Scotiabank return handler error [' . $request_id . ']: invalid request method ' . $request_method . '.', esc_html__( 'We could not process the payment response. Please try again.', 'ig-wc-scotiabank' ), wc_get_checkout_url() );
        }
        // Scotiabank posts form-encoded fields to this endpoint.
        // Restricting content types reduces malformed/abusive payload handling.
        $content_type = ( isset( $_SERVER['CONTENT_TYPE'] ) ? strtolower( sanitize_text_field( wp_unslash( $_SERVER['CONTENT_TYPE'] ) ) ) : '' );
        $allowed_content_types = array('application/x-www-form-urlencoded', 'multipart/form-data');
        $is_valid_content_type = false;
        foreach ( $allowed_content_types as $allowed_content_type ) {
            if ( 0 === strpos( $content_type, $allowed_content_type ) ) {
                $is_valid_content_type = true;
                break;
            }
        }
        if ( !$is_valid_content_type ) {
            $this->log_callback_event(
                'invalid_content_type',
                $request_id,
                0,
                '',
                'not_checked',
                array(
                    'content_type' => $content_type,
                )
            );
            $this->handle_callback_error( 'Scotiabank return handler error [' . $request_id . ']: invalid content type ' . $content_type . '.', esc_html__( 'We could not process the payment response. Please try again.', 'ig-wc-scotiabank' ), wc_get_checkout_url() );
        }
        // An empty POST means we cannot validate hash/status safely, so fail closed early.
        if ( empty( $_POST ) ) {
            $this->log_callback_event( 'empty_payload', $request_id );
            $this->handle_callback_error( 'Scotiabank return handler error [' . $request_id . ']: empty callback payload.', esc_html__( 'We could not process the payment response. Please try again.', 'ig-wc-scotiabank' ), wc_get_checkout_url() );
        }
        $sanitized_order_key = sanitize_text_field( $order_key );
        if ( empty( $sanitized_order_key ) ) {
            $this->log_callback_event( 'missing_order_key', $request_id );
            $this->handle_callback_error( 'Scotiabank return handler error [' . $request_id . ']: missing order key.', esc_html__( 'We could not process the payment response. Please try again.', 'ig-wc-scotiabank' ), wc_get_checkout_url() );
        }
        $order_id = wc_get_order_id_by_order_key( $sanitized_order_key );
        if ( empty( $order_id ) ) {
            $this->log_callback_event(
                'order_not_found',
                $request_id,
                0,
                '',
                'not_checked',
                array(
                    'order_key' => $sanitized_order_key,
                )
            );
            $this->handle_callback_error( 'Scotiabank return handler error [' . $request_id . ']: order id not found for order key ' . $sanitized_order_key . '.', esc_html__( 'We could not process the payment response. Please try again.', 'ig-wc-scotiabank' ), wc_get_checkout_url() );
        }
        $order = wc_get_order( $order_id );
        if ( !$order || !is_a( $order, '\\WC_Order' ) ) {
            $this->log_callback_event( 'order_object_unavailable', $request_id, $order_id );
            $this->handle_callback_error( 'Scotiabank return handler error [' . $request_id . ']: order object unavailable for order id ' . $order_id . '.', esc_html__( 'We could not process the payment response. Please try again.', 'ig-wc-scotiabank' ), wc_get_checkout_url() );
        }
        $posted_order_id = ( isset( $_POST['oid'] ) ? absint( wp_unslash( $_POST['oid'] ) ) : 0 );
        // Bind callback payload to the order key we resolved so responses cannot be applied to a different order.
        if ( empty( $posted_order_id ) || (int) $posted_order_id !== (int) $order_id ) {
            $this->log_callback_event(
                'order_mismatch',
                $request_id,
                $order_id,
                '',
                'not_checked',
                array(
                    'posted_oid' => $posted_order_id,
                )
            );
            $this->handle_callback_error( 'Scotiabank return handler error [' . $request_id . ']: order id mismatch. Resolved order id ' . $order_id . ', posted oid ' . $posted_order_id . '.', esc_html__( 'We could not verify your payment response. Please try again.', 'ig-wc-scotiabank' ), $order->get_checkout_payment_url() );
        }
        // Payment callbacks can be retried by upstream systems; do not mutate orders already finalized.
        if ( $order->has_status( array('processing', 'completed') ) ) {
            $this->log_callback_event( 'duplicate_paid_order_ignored', $request_id, $order_id );
            $this->redirect_parent_window( $this->get_return_url( $order ) );
        }
        $calculated_hash = 'none';
        $hash_check = 'not_checked';
        $order_payment_status = ( isset( $_POST['status'] ) ? sanitize_text_field( wp_unslash( $_POST['status'] ) ) : '' );
        $required_fields = array(
            'response_hash',
            'chargetotal',
            'currency',
            'txndatetime',
            'status'
        );
        $missing_required_fields = array();
        foreach ( $required_fields as $required_field ) {
            $raw_required_value = ( isset( $_POST[$required_field] ) ? sanitize_text_field( wp_unslash( $_POST[$required_field] ) ) : '' );
            if ( '' === $raw_required_value ) {
                $missing_required_fields[] = $required_field;
            }
        }
        // Fail closed if required fields are missing to avoid applying ambiguous callback payloads.
        if ( !empty( $missing_required_fields ) ) {
            $failure_reason = sprintf( 
                /* translators: %s is replaced with missing callback field names */
                esc_html__( 'Missing required callback fields: %s.', 'ig-wc-scotiabank' ),
                sanitize_text_field( implode( ', ', $missing_required_fields ) )
             );
            $order->update_status( 'failed', $failure_reason );
            $this->log_callback_event(
                'missing_required_fields',
                $request_id,
                $order_id,
                $order_payment_status,
                $hash_check,
                array(
                    'missing_fields' => $missing_required_fields,
                )
            );
            $this->handle_callback_error( 'Scotiabank return handler error [' . $request_id . ']: missing required callback fields for order id ' . $order_id . '.', esc_html__( 'We could not verify your payment response. Please try again.', 'ig-wc-scotiabank' ), $order->get_checkout_payment_url() );
        }
        $this->log_callback_event(
            'callback_received',
            $request_id,
            $order_id,
            $order_payment_status,
            $hash_check
        );
        // Check response_hash to make sure payment is legit.
        $response_hash = ( !empty( $_POST['response_hash'] ) ? sanitize_text_field( wp_unslash( $_POST['response_hash'] ) ) : '' );
        if ( $response_hash ) {
            $hash_args = array(
                'approval_code' => ( isset( $_POST['approval_code'] ) ? sanitize_text_field( wp_unslash( $_POST['approval_code'] ) ) : '' ),
                'chargetotal'   => sanitize_text_field( wp_unslash( $_POST['chargetotal'] ) ),
                'currency'      => sanitize_text_field( wp_unslash( $_POST['currency'] ) ),
                'txndatetime'   => sanitize_text_field( wp_unslash( $_POST['txndatetime'] ) ),
                'storename'     => $this->store_name,
            );
            $calculated_hash = $this->generate_hash( $hash_args, $this->shared_secret );
        }
        $incoming_refnumber = ( !empty( $_POST['refnumber'] ) ? sanitize_text_field( wp_unslash( $_POST['refnumber'] ) ) : '' );
        $stored_refnumber = sanitize_text_field( $order->get_meta( '_ig_scotiabank_refnumber', true ) );
        $callback_txntype = ( isset( $_POST['txntype'] ) ? sanitize_text_field( wp_unslash( $_POST['txntype'] ) ) : sanitize_text_field( $order->get_meta( '_ig_scotiabank_txntype', true ) ) );
        $callback_txntype = ( in_array( $callback_txntype, array('sale', 'preauth'), true ) ? $callback_txntype : 'sale' );
        // A previously seen transaction reference indicates a duplicate callback; skip repeat processing.
        if ( !empty( $incoming_refnumber ) && !empty( $stored_refnumber ) && $incoming_refnumber === $stored_refnumber ) {
            $this->log_callback_event(
                'duplicate_refnumber_ignored',
                $request_id,
                $order_id,
                $order_payment_status,
                $hash_check
            );
            $this->redirect_parent_window( $this->get_return_url( $order ) );
        }
        if ( $calculated_hash != $response_hash ) {
            $hash_check = 'failed';
            // Don't fail if order is already paid
            if ( $order->has_status( array('processing', 'completed') ) ) {
                $this->log_callback_event(
                    'hash_validation_failed_but_order_already_paid',
                    $request_id,
                    $order_id,
                    $order_payment_status,
                    $hash_check
                );
                $this->redirect_parent_window( $this->get_return_url( $order ) );
            } else {
                // Invalid hash for unpaid order
                $order->update_status( 'failed', esc_html__( 'Hash validation failed.', 'ig-wc-scotiabank' ) );
                $this->log_callback_event(
                    'hash_validation_failed',
                    $request_id,
                    $order_id,
                    $order_payment_status,
                    $hash_check
                );
                $this->handle_callback_error( 'Scotiabank return handler error [' . $request_id . ']: hash validation failed for order id ' . $order_id . '.', esc_html__( 'We could not verify your payment response. Please try again.', 'ig-wc-scotiabank' ), $order->get_checkout_payment_url() );
            }
        } else {
            $hash_check = 'passed';
            // Check payment status.
            if ( $order_payment_status == 'FAILED' ) {
                $keys = array(
                    'fail_rc',
                    'txndatetime',
                    'oid',
                    'fail_reason'
                );
                $data = $this->sanitize_data_array( $keys, $_POST );
                $data['txntype'] = $callback_txntype;
                $this->save_order_meta_and_note( $order, $data );
                $failed_reason = ( isset( $_POST['fail_reason'] ) ? sanitize_text_field( wp_unslash( $_POST['fail_reason'] ) ) : '' );
                if ( '' === $failed_reason ) {
                    $failed_reason = esc_html__( 'Payment was declined with no reason returned by gateway.', 'ig-wc-scotiabank' );
                    $this->log_callback_event(
                        'missing_fail_reason',
                        $request_id,
                        $order_id,
                        $order_payment_status,
                        $hash_check
                    );
                }
                $order->update_status( 'failed', $failed_reason );
                $this->log_callback_event(
                    'payment_failed',
                    $request_id,
                    $order_id,
                    $order_payment_status,
                    $hash_check
                );
            } elseif ( $order_payment_status == 'APPROVED' ) {
                // Payment ok, let's proceed.
                $keys = array(
                    'approval_code',
                    'response_code_3dsecure',
                    'refnumber',
                    'txndatetime',
                    'cardnumber',
                    'oid',
                    'ipgTransactionId'
                );
                $data = $this->sanitize_data_array( $keys, $_POST );
                $data['txntype'] = $callback_txntype;
                $this->save_order_meta_and_note( $order, $data );
                // Persist transaction reference so future callbacks with the same ref can be identified as duplicates.
                if ( !empty( $incoming_refnumber ) ) {
                    $order->update_meta_data( '_ig_scotiabank_refnumber', $incoming_refnumber );
                    $order->save();
                }
                // Persist API transaction ID for secondary operations (refunds, captures, voids)
                $incoming_transaction_id = ( !empty( $_POST['ipgTransactionId'] ) ? sanitize_text_field( wp_unslash( $_POST['ipgTransactionId'] ) ) : '' );
                if ( !empty( $incoming_transaction_id ) ) {
                    $order->update_meta_data( '_ig_scotiabank_transaction_id', $incoming_transaction_id );
                    $order->save();
                }
                // Remove cart.
                \WC()->cart->empty_cart();
                if ( 'preauth' === $callback_txntype ) {
                    $preauth_note = esc_html__( 'Scotiabank pre-authorization approved. Authorized only, capture required in Scotia Merchant Admin before fulfillment. Update this WooCommerce order status manually after capture or void.', 'ig-wc-scotiabank' );
                    // Pre-authorization should not mark the order as paid until merchant captures funds manually.
                    $order->update_status( 'on-hold', $preauth_note );
                    $this->log_callback_event(
                        'payment_preauth_authorized',
                        $request_id,
                        $order_id,
                        $order_payment_status,
                        $hash_check
                    );
                } else {
                    // Sale transactions capture immediately and should follow the existing paid-order flow.
                    $order->payment_complete();
                    $this->log_callback_event(
                        'payment_approved',
                        $request_id,
                        $order_id,
                        $order_payment_status,
                        $hash_check
                    );
                }
            } else {
                $keys = array('txndatetime', 'oid', 'status');
                $data = $this->sanitize_data_array( $keys, $_POST );
                $data['txntype'] = $callback_txntype;
                $this->save_order_meta_and_note( $order, $data );
                /* translators: %s is replaced with whatever the payment status name is */
                $order->update_status( 'failed', sprintf( esc_html__( 'Unknown payment status %s.', 'ig-wc-scotiabank' ), $order_payment_status ) );
                $this->log_callback_event(
                    'unknown_payment_status',
                    $request_id,
                    $order_id,
                    $order_payment_status,
                    $hash_check
                );
            }
        }
        // Break out of the iframe.
        $this->redirect_parent_window( $this->get_return_url( $order ) );
    }

    /**
     * Process a refund via Fiserv API
     *
     * @param int    $order_id Order ID
     * @param float  $amount   Refund amount
     * @param string $reason   Refund reason
     * @return bool|WP_Error True on success, WP_Error on failure
     */
    public function process_refund( $order_id, $amount = null, $reason = '' ) {
        // Check if API features are available
        if ( !$this->is_api_feature_available() ) {
            return new \WP_Error('api_not_available', __( 'Scotia API features are not available. Please ensure your license is active.', 'ig-wc-scotiabank' ));
        }
        $order = wc_get_order( $order_id );
        if ( !$order ) {
            return new \WP_Error('invalid_order', __( 'Invalid order ID.', 'ig-wc-scotiabank' ));
        }
        // Get the transaction ID from order meta
        $transaction_id = $this->get_order_transaction_id( $order );
        if ( empty( $transaction_id ) ) {
            return new \WP_Error('no_transaction_id', __( 'No transaction ID found for this order. Refund cannot be processed.', 'ig-wc-scotiabank' ));
        }
        if ( null === $amount || (float) $amount <= 0 ) {
            return new \WP_Error('invalid_amount', __( 'Invalid refund amount.', 'ig-wc-scotiabank' ));
        }
        $amount = (float) $amount;
        // Get order currency
        $currency = $order->get_currency();
        if ( empty( $currency ) ) {
            $currency = get_woocommerce_currency();
        }
        // Build refund payload
        $payload = array(
            'requestType'       => 'ReturnTransaction',
            'transactionAmount' => array(
                'total'    => number_format(
                    $amount,
                    2,
                    '.',
                    ''
                ),
                'currency' => $currency,
            ),
        );
        // Add merchant transaction ID for tracking
        $merchant_txn_id = 'refund_' . $order_id . '_' . time();
        $payload['merchantTransactionId'] = $merchant_txn_id;
        // Add reason if provided
        if ( !empty( $reason ) ) {
            $payload['comments'] = sanitize_text_field( $reason );
        }
        // Initialize API client
        $api_key = ( 'yes' === $this->sandbox ? $this->get_option( 'sandbox_scotia_api_key' ) : $this->get_option( 'scotia_api_key' ) );
        $api_secret = ( 'yes' === $this->sandbox ? $this->get_option( 'sandbox_scotia_api_secret' ) : $this->get_option( 'scotia_api_secret' ) );
        $api_client = new Scotiabank_Fiserv_API(
            $api_key,
            $api_secret,
            $this->store_name,
            $this->log,
            'yes' === $this->sandbox
        );
        // Make the API call
        $response = $api_client->post_secondary_transaction( $transaction_id, $payload );
        if ( is_wp_error( $response ) ) {
            $error_message = $response->get_error_message();
            $this->log->add( $this->id, 'Refund failed for order #' . $order_id . ': ' . $error_message );
            // Add order note about failed refund
            $order->add_order_note( sprintf( 
                /* translators: %s is the error message */
                __( 'Scotia refund failed: %s', 'ig-wc-scotiabank' ),
                $error_message
             ) );
            return $response;
        }
        // Success - add order note
        $refund_note = sprintf(
            /* translators: %1$s is amount, %2$s is currency, %3$s is transaction ID */
            __( 'Scotia refund processed successfully. Amount: %1$s %2$s. Transaction ID: %3$s', 'ig-wc-scotiabank' ),
            number_format( $amount, 2 ),
            $currency,
            $transaction_id
        );
        if ( !empty( $reason ) ) {
            $refund_note .= '. Reason: ' . $reason;
        }
        $order->add_order_note( $refund_note );
        $this->log->add( $this->id, 'Refund successful for order #' . $order_id . ' - Amount: ' . $amount . ' ' . $currency );
        return true;
    }

    /**
     * Add capture order action for Scotia pre-authorizations.
     *
     * @param array      $actions Existing order actions
     * @param \WC_Order $order   Order object
     * @return array Modified order actions
     */
    public function add_order_actions( $actions, $order ) {
        if ( !$order || !is_a( $order, '\\WC_Order' ) ) {
            return $actions;
        }
        if ( $this->id !== $order->get_payment_method() ) {
            return $actions;
        }
        if ( !$this->is_api_feature_available() ) {
            return $actions;
        }
        $order_txntype = sanitize_text_field( $order->get_meta( '_ig_scotiabank_txntype', true ) );
        $transaction_id = $this->get_order_transaction_id( $order );
        if ( 'preauth' === $order_txntype && !empty( $transaction_id ) && $order->has_status( array('on-hold', 'pending') ) ) {
            $actions['capture_scotiabank'] = __( 'Capture Scotia authorization', 'ig-wc-scotiabank' );
            $actions['void_scotiabank'] = __( 'Void Scotia pre-authorization', 'ig-wc-scotiabank' );
        }
        return $actions;
    }

    /**
     * Handle the Scotia capture order action.
     *
     * @param \WC_Order $order Order object
     * @return void
     */
    public function process_order_action_capture_scotiabank( $order ) {
        if ( !$order || !is_a( $order, '\\WC_Order' ) ) {
            return;
        }
        $order_id = $order->get_id();
        $result = $this->process_capture( $order_id );
        if ( is_wp_error( $result ) ) {
            $order->add_order_note( sprintf( __( 'Scotia capture failed: %s', 'ig-wc-scotiabank' ), $result->get_error_message() ) );
            $this->log->add( $this->id, 'Scotia capture failed for order #' . $order_id . ': ' . $result->get_error_message() );
            return;
        }
        if ( is_array( $result ) && !empty( $result['already_performed'] ) ) {
            $this->log->add( $this->id, 'Duplicate Scotia capture skipped for order #' . $order_id . ' because capture was already completed.' );
            return;
        }
        $order->add_order_note( __( 'Scotia pre-authorization was captured successfully.', 'ig-wc-scotiabank' ) );
        $this->log->add( $this->id, 'Scotia capture succeeded for order #' . $order_id );
        if ( !$order->has_status( array('processing', 'completed') ) ) {
            $order->payment_complete();
        }
    }

    /**
     * Handle the Scotia void order action.
     *
     * @param \WC_Order $order Order object
     * @return void
     */
    public function process_order_action_void_scotiabank( $order ) {
        if ( !$order || !is_a( $order, '\\WC_Order' ) ) {
            return;
        }
        $order_id = $order->get_id();
        $result = $this->process_void( $order_id );
        if ( is_wp_error( $result ) ) {
            $order->add_order_note( sprintf( __( 'Scotia void failed: %s', 'ig-wc-scotiabank' ), $result->get_error_message() ) );
            $this->log->add( $this->id, 'Scotia void failed for order #' . $order_id . ': ' . $result->get_error_message() );
            return;
        }
        if ( is_array( $result ) && !empty( $result['already_performed'] ) ) {
            $this->log->add( $this->id, 'Duplicate Scotia void skipped for order #' . $order_id . ' because void was already completed.' );
            return;
        }
        $order->add_order_note( __( 'Scotia pre-authorization was voided successfully.', 'ig-wc-scotiabank' ) );
        $this->log->add( $this->id, 'Scotia pre-authorization void succeeded for order #' . $order_id );
        if ( !$order->has_status( array('cancelled', 'failed') ) ) {
            $order->update_status( 'cancelled', __( 'Scotia pre-authorization voided.', 'ig-wc-scotiabank' ) );
        }
    }

    /**
     * Process a capture via Fiserv API.
     *
     * @param int    $order_id Order ID
     * @param float  $amount   Capture amount (full order total if not provided)
     * @param string $reason   Optional capture reason
     * @return bool|\WP_Error
     */
    public function process_capture( $order_id, $amount = null, $reason = '' ) {
        $order = wc_get_order( $order_id );
        if ( !$order || !is_a( $order, '\\WC_Order' ) ) {
            return new \WP_Error('invalid_order', __( 'Invalid order for capture.', 'ig-wc-scotiabank' ));
        }
        if ( !$this->is_api_feature_available() ) {
            return new \WP_Error('api_not_available', __( 'Scotia API features are not available.', 'ig-wc-scotiabank' ));
        }
        $transaction_id = $this->get_order_transaction_id( $order );
        if ( empty( $transaction_id ) ) {
            return new \WP_Error('missing_transaction_id', __( 'No transaction ID found for this order. Capture cannot be processed.', 'ig-wc-scotiabank' ));
        }
        if ( $order->has_status( array('processing', 'completed') ) || $order->get_meta( '_ig_scotiabank_capture_processed', true ) ) {
            return array(
                'already_performed' => true,
            );
        }
        $amount = ( $amount !== null ? (float) $amount : (float) $order->get_total() );
        if ( $amount <= 0 ) {
            return new \WP_Error('invalid_amount', __( 'Invalid capture amount.', 'ig-wc-scotiabank' ));
        }
        $currency = $order->get_currency();
        $payload = array(
            'requestType'       => 'PostAuthTransaction',
            'transactionAmount' => array(
                'total'    => number_format(
                    $amount,
                    2,
                    '.',
                    ''
                ),
                'currency' => $currency,
            ),
        );
        if ( !empty( $reason ) ) {
            $payload['comments'] = sanitize_text_field( $reason );
        }
        $api_key = ( 'yes' === $this->sandbox ? $this->get_option( 'sandbox_scotia_api_key' ) : $this->get_option( 'scotia_api_key' ) );
        $api_secret = ( 'yes' === $this->sandbox ? $this->get_option( 'sandbox_scotia_api_secret' ) : $this->get_option( 'scotia_api_secret' ) );
        $api_client = new Scotiabank_Fiserv_API(
            $api_key,
            $api_secret,
            $this->store_name,
            $this->log,
            'yes' === $this->sandbox
        );
        $response = $api_client->post_secondary_transaction( $transaction_id, $payload );
        if ( is_wp_error( $response ) ) {
            $error_message = $response->get_error_message();
            $this->log->add( $this->id, 'Capture failed for order #' . $order_id . ': ' . $error_message );
            if ( false !== stripos( $error_message, 'PostAuth already performed' ) ) {
                $order->update_meta_data( '_ig_scotiabank_capture_processed', 'yes' );
                $order->save();
                return array(
                    'already_performed' => true,
                );
            }
            $order->add_order_note( sprintf( __( 'Scotia capture failed: %s', 'ig-wc-scotiabank' ), $error_message ) );
            return $response;
        }
        $success_note = sprintf(
            /* translators: %1$s is amount, %2$s is currency, %3$s is transaction ID */
            __( 'Scotia capture processed successfully. Amount: %1$s %2$s. Transaction ID: %3$s', 'ig-wc-scotiabank' ),
            number_format( $amount, 2 ),
            $currency,
            $transaction_id
        );
        if ( !empty( $reason ) ) {
            $success_note .= '. Reason: ' . $reason;
        }
        $order->add_order_note( $success_note );
        $this->log->add( $this->id, 'Capture successful for order #' . $order_id . ' - Amount: ' . $amount . ' ' . $currency );
        $order->update_meta_data( '_ig_scotiabank_capture_processed', 'yes' );
        $order->save();
        return true;
    }

    /**
     * Process a void via Fiserv API.
     *
     * @param int    $order_id Order ID
     * @param string $reason   Optional void reason
     * @return bool|array|\WP_Error
     */
    public function process_void( $order_id, $reason = '' ) {
        $order = wc_get_order( $order_id );
        if ( !$order || !is_a( $order, '\\WC_Order' ) ) {
            return new \WP_Error('invalid_order', __( 'Invalid order for void.', 'ig-wc-scotiabank' ));
        }
        if ( !$this->is_api_feature_available() ) {
            return new \WP_Error('api_not_available', __( 'Scotia API features are not available.', 'ig-wc-scotiabank' ));
        }
        $transaction_id = $this->get_order_transaction_id( $order );
        if ( empty( $transaction_id ) ) {
            return new \WP_Error('missing_transaction_id', __( 'No transaction ID found for this order. Void cannot be processed.', 'ig-wc-scotiabank' ));
        }
        if ( $order->has_status( array('cancelled', 'failed') ) || $order->get_meta( '_ig_scotiabank_void_processed', true ) ) {
            return array(
                'already_performed' => true,
            );
        }
        $payload = array(
            'requestType' => 'VoidPreAuthTransactions',
        );
        if ( !empty( $reason ) ) {
            $payload['comments'] = sanitize_text_field( $reason );
        }
        $api_key = ( 'yes' === $this->sandbox ? $this->get_option( 'sandbox_scotia_api_key' ) : $this->get_option( 'scotia_api_key' ) );
        $api_secret = ( 'yes' === $this->sandbox ? $this->get_option( 'sandbox_scotia_api_secret' ) : $this->get_option( 'scotia_api_secret' ) );
        $api_client = new Scotiabank_Fiserv_API(
            $api_key,
            $api_secret,
            $this->store_name,
            $this->log,
            'yes' === $this->sandbox
        );
        $response = $api_client->post_secondary_transaction( $transaction_id, $payload );
        if ( is_wp_error( $response ) ) {
            $error_message = $response->get_error_message();
            $this->log->add( $this->id, 'Void failed for order #' . $order_id . ': ' . $error_message );
            if ( false !== stripos( $error_message, 'already performed' ) ) {
                $order->update_meta_data( '_ig_scotiabank_void_processed', 'yes' );
                $order->save();
                return array(
                    'already_performed' => true,
                );
            }
            $order->add_order_note( sprintf( __( 'Scotia void failed: %s', 'ig-wc-scotiabank' ), $error_message ) );
            return $response;
        }
        $order->update_meta_data( '_ig_scotiabank_void_processed', 'yes' );
        $order->save();
        return true;
    }

}
