<?php

/**
 * WC_Bankful Gateway Plugin Class
 *
 * @package Bankful with WooCommerce Subscriptions and Simple Products Enabled
 * @since   2.0.3
 */

/**
 * The class itself, please note that it is inside plugins_loaded action hook
 */
class WC_Bankful_WS_Gateway extends WC_Payment_Gateway {
	use Bankful_Security_Checks_Trait;

	/**
	 * API endpoint
	 *
	 * @var string
	 */
	public $version = '3.0.4';

	/**
	 * API endpoint
	 *
	 * @var string
	 */
	public $api_endpoint;

	/**
	 * API endpoint to get customer vault token
	 *
	 * @var string
	 */
	public $api_endpoint_token;

	/**
	 * API username for merchant
	 *
	 * @var string
	 */
	public $req_username;

	/**
	 * API password for merchant
	 *
	 * @var string
	 */
	public $req_password;

	/**
	 * Whether or not Test server is enabled
	 *
	 * @var bool
	 */
	public $testEnv = false;

	/**
	 * Whether or not logging is enabled
	 *
	 * @var bool
	 */
	public static $log_enabled = false;

	/**
	 * Logger instance
	 *
	 * @var WC_Logger
	 */
	public static $log = false;

	/**
	 * Whether the debug mode is enabled.
	 *
	 * @var bool
	 */
	public $debug;

	/**
	 * Whether the Order is Subscription order or not.
	 *
	 * @var bool
	 */
	public $is_subscription = false;

	/**
	 * Class constructor, more about it in Step 3
	 */
	public function __construct() {
		global $WP_SITE_URL;
		global $LIVE_API_SRV;
		$this->id = 'bankful_credit_card'; // payment gateway plugin ID
		$this->icon = plugins_url() . '/bankful/assets/img/bankful_logo_whitebg.svg';
		$this->has_fields = true;
		$this->method_title = 'Bankful (Credit Card)';
		$this->method_description = 'Bankful Gateway with support of WooCommerce Subscriptions Plugin';
		$this->notify_url     = $WP_SITE_URL . '/wp-json/bankful/v1/order-updated';

		$this->is_subscription = false;

		// gateway supports subscriptions, saved payment methods,
		$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',
		);

		// Method with all the options fields.
		$this->init_form_fields();

		// Load the settings.
		$this->init_settings();

		$this->title = $this->get_option('title');
		$this->description = $this->get_option('description');
		$this->enabled = $this->get_option('enabled');
		
		$bankful_options = get_option('bankful_options');
		
		$this->testEnv = ( strtolower($bankful_options['bankful_field_environment'])==strtolower('Test') );

		$this->req_username = isset($bankful_options['bankful_field_live_username']) ? $bankful_options['bankful_field_live_username'] : 'sandbox-test@bankful.com';
		$this->req_password = isset($bankful_options['bankful_field_live_password']) ? $bankful_options['bankful_field_live_password'] : 'tuMcC7m333J4VMsb';

		$this->api_endpoint = $LIVE_API_SRV . 'api/woocommerce-v2/transaction';
		
		$this->api_endpoint_token = $LIVE_API_SRV . 'api/woocommerce-v2/save-woo-card-tokenize';

		$this->debug        = 'yes' === $this->get_option('debug', 'no');

		self::$log_enabled  = $this->debug;

		add_action('woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ));
		add_action('woocommerce_scheduled_subscription_payment_' . $this->id, array( $this, 'scheduled_subscription_payment' ), 0, 2);
		add_action('woocommerce_subscription_payment_method_change_' . $this->id, array( $this, 'change_payment_method' ), 10, 2);
		add_filter('woocommerce_subscription_payment_meta', array( $this, 'add_bankful_subscription_payment_meta' ), 20, 2 );
		add_filter('handle_renew_subscription_action', array( $this, 'handle_renew_subscription_actionhandle_renew_subscription_action_S' ), 0 );
	}

	/**
	 * Logging method.
	 *
	 * @param string $message Log message.
	 * @param string $level   Optional. Default 'info'. Possible values:
	 *                        emergency|alert|critical|error|warning|notice|info|debug.
	 */
	public static function log( $message, $level = 'info' ) {
		if (self::$log_enabled) {
			if (empty(self::$log)) {
				self::$log = wc_get_logger();
			}
			self::$log->log($level, $message, array( 'source' => 'bankful_credit_card' ));
		}
	}

	public function add_bankful_subscription_payment_meta( $payment_meta, $subscription ) {
		$bankful_customer_id       = $subscription->get_meta( '_bankful_customer_id', true );

		$payment_meta[ $this->id ] = array(
			'post_meta' => array(
				'_bankful_customer_id'   => array(
					'value' => $bankful_customer_id,
					'label' => 'Bankful Customer Vault ID',
				),
			),
		);

		return $payment_meta;
	}

	public function change_payment_method( $subscription_id, $new_payment_method ) {
		$subscription = wcs_get_subscription($subscription_id);
		if ($subscription) {
			$subscription->update_payment_method($subscription, $new_payment_method);
		}
	}

	public function is_bankful_subscriptions_enabled() {
		return class_exists( 'WC_Subscriptions' ) || class_exists('WC_Subscriptions_Core_Plugin');
	}

	public function has_bankful_subscription( $order_id ) {
		return ( function_exists( 'wcs_order_contains_subscription' ) && ( wcs_order_contains_subscription( $order_id ) || wcs_is_subscription( $order_id ) || wcs_order_contains_renewal( $order_id ) ) );
	}

	public function is_changing_payment_method_for_bankful_subscription() {
		if ( isset( $_GET['change_payment_method'] ) && function_exists( 'wcs_is_subscription' ) ) { // phpcs:ignore WordPress.Security.NonceVerification
			return wcs_is_subscription( wc_clean( wp_unslash(sanitize_text_field( $_GET['change_payment_method']) ) ) ); // phpcs:ignore WordPress.Security.NonceVerification
		}
		return false;
	}

	public function maybe_change_subscription_payment_method_bankful( $order_id ) {
		return (
			$this->is_bankful_subscriptions_enabled() &&
			$this->has_bankful_subscription( $order_id ) &&
			$this->is_changing_payment_method_for_bankful_subscription()
		);
	}

	/**
	 * Processes and saves options.
	 * If there is an error thrown, will continue to save and validate fields, but will leave the erroring field out.
	 *
	 * @return bool was anything saved?
	 */
	public function process_admin_options() {
		$saved = parent::process_admin_options();

		// Maybe clear logs.
		if ('yes' !== $this->get_option('debug', 'no')) {
			if (empty(self::$log)) {
				self::$log = wc_get_logger();
			}
			self::$log->clear('bankful_credit_card');
		}

		return $saved;
	}

	/**
	 * Plugin options in admin panel
	 * 
	 * @return Form Fields in Gateway Setting Screen
	 */
	public function init_form_fields() {
		$this->form_fields = array(
			'enabled' => array(
				'title'       => 'Enable/Disable',
				'label'       => ( ( !empty($this->get_option('enabled')) && 'yes' == $this->get_option('enabled') ) ? 'Disable Bankful Gateway (Credit Card)' : 'Enable Bankful Gateway (Credit Card)' ),
				'type'        => 'checkbox',
				'description' => '',
				'default'     => 'yes',
			),
			'title' => array(
				'title'       => 'Title',
				'type'        => 'text',
				'description' => 'This controls the title which the user sees during checkout.',
				'default'     => 'Bankful (Credit Card)',
				'desc_tip'    => true,
			),
			'description' => array(
				'title'       => 'Description',
				'type'        => 'textarea',
				'description' => 'This controls the description which the user sees during checkout.',
				'default'     => 'Pay with your credit card via Bankful payment gateway.',
			),      
			'debug' => array(
				'title'       => 'Enable Debug Mode',
				'label'       => 'Debugging',
				'type'        => 'checkbox',
				'description' => 'Place the payment gateway in debug mode and write logs.',
				'default'     => 'no',
				'desc_tip'    => true,
			),
		);
		return $this->form_fields;
	}

	public function generateMonthOptions() {
		$months = array(
			'Jan' => '01',
			'Feb' => '02',
			'Mar' => '03',
			'Apr' => '04',
			'May' => '05',
			'Jun' => '06',
			'Jul' => '07',
			'Aug' => '08',
			'Sep' => '09',
			'Oct' => '10',
			'Nov' => '11',
			'Dec' => '12',
		);

		$currentMonthNumber = gmdate('m');

		$output = '';
	
		foreach ($months as $monthName => $monthNumber) {
			$output .= '<option ' . ( $currentMonthNumber === $monthNumber ? 'selected' : '' ) . ' value="' . esc_attr($monthNumber) . '">' . esc_html($monthName) . '</option>';
		}
	
		return $output;
	}

	public function generateYearOptions( $numYears = 20 ) {
		$currentYear = gmdate('Y');
		$endYear = $currentYear + $numYears;
	
		$output = '';
	
		for ($year = $currentYear; $year <= $endYear; $year++) {
			$output .= '<option value="' . esc_attr($year) . '">' . esc_html($year) . '</option>';
		}
	
		return $output;
	}

	/**
	 * Payment Fields to show at Checkout Page
	 * 
	 */
	public function payment_fields() {
		$allowed_html = array(
			'p' => array(),
		);
		// Display some description before the payment form
		if ($this->description) {
			if ($this->testEnv) {
				$this->description .= '<p>TEST ENVIRONMENT ENABLED. In test mode, you can use Test card numbers.</p>';
				$this->description  = trim($this->description);
			}
			// display the description with <p> tags etc.
			echo wp_kses($this->description, $allowed_html);
		}

		echo '<fieldset id="wc-' . esc_attr($this->id) . '-cc-form" class="wc-credit-card-form wc-payment-form" style="background:transparent;">';

		/**
		 * To add something before Bankful Credit card form.
		 *
		 * @since bankful version 2.0.0
		 */
		do_action('woocommerce_credit_card_form_start', $this->id);

		$form_nonce = wp_create_nonce('bf_cc_form_nonce');

		$allowed_html = array(
			'option' => array(
				'value' => array(),
				'selected' => array(),
			),
		);

		echo '<div class="fb_full_row form-row form-row-wide">
					<label class="bf_cc_labels">Card Number <span class="required">*</span></label>
					<input class="cc_input_full" id="bfws_cc_number" name="bfws_cc_number" type="text" autocomplete="off" pattern="[0-9]{12,19}" placeholder="Please enter valid Card Number">
				</div>
				<div class="fb_half_row form-row form-row-first validate-required">
					<label class="bf_cc_labels" for="bfws_expiration">Expiry Date <span class="required">*</span></label>
					<select id="cc-exp-month" class="txt" name="bfws_exp_month" class="cc_input_half">
						' . wp_kses($this->generateMonthOptions(), $allowed_html) . '
					</select>
					<select id="cc-exp-year" class="txt" name="bfws_exp_year" class="cc_input_half">
						' . wp_kses($this->generateYearOptions(20), $allowed_html) . '
					</select>
				</div>
				<div class="fb_half_row form-row form-row-last">
					<label class="bf_cc_labels">Card Code (CVV) <span class="required">*</span></label>
					<input id="bfws_cvv" name="bfws_cvv" type="password" maxlength="4" size="4" pattern="[0-9]+" required autocomplete="off" placeholder="CVV" class="cc_input_full">
				</div>
				<input type="hidden" value="' . esc_attr($form_nonce) . '" name="bf_cc_nonce" >
				<div class="clear"></div>';

		/**
		 * To add something after Bankful Credit card form.
		 *
		 * @since bankful version 2.0.0
		 */
		do_action('woocommerce_credit_card_form_end', $this->id);

		echo '<div class="clear"></div></fieldset>';
	}

	/**
	 * Validate Expiry Date entered in Checkout Card Fields
	 * 
	 * @param string||date $card_date date entered
	 * 
	 * @return boolean
	 */
	public function validate_expirydate( $card_date = null ) {
		$today = gmdate('Ym');
		$this->log(' card_date: ' . $card_date, 'info');
		$today_dt = new DateTime($today);
		$expire_dt = new DateTime($card_date);

		if ($expire_dt < $today_dt) {
			return false;
		} else {
			return true;
		}
	}

	/**
	 * Get Cart Total from Cart Object
	 * 
	 * @return float
	 */
	public function get_cart_total() {
		$final_array = array();
		$cart = WC()->cart;
		$totalAmount = $cart->total;
		$final_array['amount'] = floatval($totalAmount);
		return $final_array;
	}

	/**
	 * Get Extra Versioning Options
	 * 
	 * @return float
	 */
	public function get_versions() {
		global $woocommerce;
		global $wp_version;
		global $BF_PLUGIN_SOURCE;
		$versions_array = array();
		$versions_array['woo_version'] = $woocommerce->version;
		$versions_array['wp_version'] = $wp_version;
		$versions_array['php_version'] = phpversion();
		$versions_array['bf_plugin_version'] = $this->version;
		$versions_array['bf_plugin_name'] = 'Bankful Payments Native';
		$versions_array['bf_plugin_source'] = $BF_PLUGIN_SOURCE; // bankful_direct || woo_market
		return $versions_array;
	}

	/**
	 * Get Cart SubTotal from Cart Object
	 * 
	 * @return float
	 */
	public function get_cart_subtotal() {
		$final_array = array();
		$cart = WC()->cart;
		$subTotalAmount = $cart->subtotal;
		$final_array['subtotal_amount'] = floatval($subTotalAmount);
		return $final_array;
	}

	/**
	 * Get Cart Discount Total from Cart Object
	 * 
	 * @return float
	 */
	public function get_discount_total() {
		$final_array = array();
		$cart = WC()->cart;
		$discountAmount = $cart->get_discount_total();
		$final_array['discount_amount'] = floatval($discountAmount);
		return $final_array;
	}

	/**
	 * Get Cart Shipping Total from Cart Object
	 * 
	 * @return float
	 */
	public function get_shipping_total() {
		$final_array = array();
		$cart = WC()->cart;
		$shippingAmount = $cart->get_shipping_total();
		$final_array['shipping_amount'] = floatval($shippingAmount);
		return $final_array;
	}

	/**
	 * Get Merchant Credentials from DB
	 * 
	 * @return array
	 */
	public function merchant_credential() {
		$request_params = array(
			'req_username' => $this->req_username,
			'req_password' => $this->req_password,
		);
		$finalRequestParams = array();

		foreach ($request_params as $reqKey => $reqParamVal) {
			if (empty($request_params[$reqKey])) {
				throw new Exception(
					esc_html(
						'Merchant not configured correctly, please contact Bankful support, Thanks.',
						'bankful_credit_card'
					)
				);
			}

			$finalRequestParams[$reqKey] = trim($reqParamVal);
		}

		return $finalRequestParams;
	}

	/**
	 * Get Merchant Credentials from DB By Using API Key and Secret
	 * 
	 * @return array
	 */
	public function merchant_credential_new() {
		$result = getBankfulToken();
		if ( ! empty( $result ) ) {
			$request_params = array(
				'bf_access_token' => $result['bf_access_token'],
				'bf_public_key' => $result['bf_public_key'],
				'bf_hash_salt' => $result['bf_hash_salt'],
			);
		} else {
			throw new Exception(
				esc_html(
					'Merchant not configured correctly, please contact Bankful support, Thanks.',
					'bankful_credit_card'
				)
			);
		}
		
		$finalRequestParams = array();

		foreach ($request_params as $reqKey => $reqParamVal) {
			if (empty($request_params[$reqKey])) {
				throw new Exception(
					esc_html(
						'Merchant not configured correctly, please contact Bankful support, Thanks.',
						'bankful_credit_card'
					)
				);
			}

			$finalRequestParams[$reqKey] = trim($reqParamVal);
		}

		return $finalRequestParams;
	}

	/**
	 * Encrypt card information using public key 
	 * 
	 * @return array
	 */
	public function encrypt_card_info( $pmt_num, $pmt_key, $pmt_expiry ) {
		$result  = $this->merchant_credential_new();
		$finalRequestParams = array();
		if ( ! empty( $result ) ) {
			$bf_public_key = $result['bf_public_key'];
			$finalRequestParams['pmt_numb']   = encryptPayload($pmt_num, $bf_public_key);
			$finalRequestParams['pmt_key']    = encryptPayload($pmt_key, $bf_public_key);
			$finalRequestParams['pmt_expiry'] = encryptPayload($pmt_expiry, $bf_public_key);
		} else {
			throw new Exception(
				esc_html(
					'Merchant not configured correctly, please contact Bankful support, Thanks.',
					'bankful_credit_card'
				)
			);
		}
		return $finalRequestParams;
	}

	/**
	 * Process the payment and return the result.
	 *
	 * @param int $order_id Order ID.
	 * @param int $amount Order ID.
	 * 
	 * @return bool
	 */
	public function process_refund( $order_id, $amount = null, $reason = '' ) {

		$order  = wc_get_order($order_id);
		$this->log('====================== params PAYMENT order_param Order: ' , 'info');
		
		if (!is_a($order, 'WC_Order')) {
			return new WP_Error('wc-order', __('Provided ID is not a WC Order', 'bankful'));
		}

		$bf_order_id = $order->get_meta('_transaction_id');

		if (empty($bf_order_id)) {
			return new WP_Error('wc-order', __('Bankful Provided Transaction ID not found.', 'bankful'));
		}

		if ('refunded' == $order->get_status()) {
			return new WP_Error('wc-order', __('Order has been already refunded', 'bankful'));
		}

		// $options = get_option( 'bankful_options' );
		$transaction_mode = $this->testEnv ? 'sandbox' : 'live';

		$order_param = array(
			'transaction_type' => 'REFUND',
			'amount' => $amount,
			'request_ref_po_id' => $bf_order_id,
			'transaction_mode' => $transaction_mode,
			'callback_url'	=> $this->notify_url
		);
		
		$params = array_merge($order_param, $this->get_versions());
		$this->log('====================== params PAYMENT order_param Order: ' . $order_param, 'info');
		$merchant_info  = $this->merchant_credential_new();
		$payloadHash    = generateSignatureHash($params, $merchant_info['bf_hash_salt']);

		$url = $this->api_endpoint;

		$args = array(
			'body' => http_build_query($params),
			'headers'     => array(
				'Content-Type' => 'application/x-www-form-urlencoded',
				'Authorization' => 'Bearer ' . $merchant_info['bf_access_token'], 
				'X-Bankful-Hmac-SHA256' => $payloadHash,
			),
		);

		$response = bankful_remote_post($url, $args);

		if (!is_wp_error($response)) {
			$body = wp_remote_retrieve_body($response);
			$bodyArr = $body;
			$body = json_decode($body, false);
			$bodyArr = json_decode($bodyArr, true);
			if (verifyHashSignatureFromRequest($bodyArr)) {
				if (
					isset($body->TRANS_STATUS_NAME)
					&& 'APPROVED' == $body->TRANS_STATUS_NAME
				) {

					$this->log('====================== process_payment PAYMENT APPROVED for Order: ' . $order_id, 'info');

					$order->add_order_note( 'Bankful Payment refund completed. Refund Transaction ID:-' . $body->TRANS_RECORD_ID );

					// Redirect
					return true;
				}else if (
					isset($body->TRANS_STATUS_NAME)
					&& 'PENDING' == $body->TRANS_STATUS_NAME
				) {

					$order->add_order_note(' Payment pending with Bankful and Transaction ID:' . $body->TRANS_ORDER_ID);
					$order->add_meta_data( '_transaction_id', $body->TRANS_ORDER_ID, true );
					$order->update_status('on-hold');

					$this->log('====================== process_payment PAYMENT PENDING for Order: ' . $order_id, 'info');

					// Add order note
					$order->add_order_note(' Payment pending with Bankful and Transaction ID:' . $body->TRANS_ORDER_ID);
					$order->add_meta_data( '_transaction_id', $body->TRANS_ORDER_ID, true );

					$body->TRANS_VALUE = floatval($body->TRANS_VALUE);

					// $order->payment_complete($body->TRANS_ORDER_ID);
					wc_reduce_stock_levels($order->get_id());
					
					// Redirect
					return true;
				} else if (!empty($body->ERROR_MESSAGE) || !empty($body->API_ADVICE)) {

					$error_message = 'Order already refunded or something went wrong';

					// Add note.
					/* translators: %s: Transaction ID */
					$order->add_order_note(sprintf(__('Refund Failed Transaction Order ID %s', 'bankful'), $body->TRANS_ORDER_ID));
					$order->add_meta_data( '_transaction_id', $body->TRANS_ORDER_ID, true );
					
					return new WP_Error('wc-order', __($error_message, 'bankful'));
				}
			} else {
				$error_message = 'Order already refunded or something went wrong';

				// Add note.
				/* translators: %s: Transaction ID */
				$order->add_order_note(sprintf(__('Signature Mismatched Refund Failed Transaction Order ID %s', 'bankful'), $body->TRANS_ORDER_ID));

				$order->add_meta_data( '_transaction_id', $body->TRANS_ORDER_ID, true );
				
				return new WP_Error('wc-order', __($error_message, 'bankful'));
			}
		} else {
			wc_add_notice('Connection error.', 'error');
			return new WP_Error('wc-order', __('Connection error.', 'bankful'));
		}
		return false;
	}

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

		// Block payment processing on insecure connections.
		$ssl_check = $this->enforce_secure_connection( $order_id );
		if ( $ssl_check ) {
			return $ssl_check;
		}

		$this->log('====================== process_payment CAPTURE PAYMENT for Order: ' . $order_id, 'info');

		if ( $this->maybe_change_subscription_payment_method_bankful( $order_id ) ) {
			return $this->process_change_bankful_subscription_payment_method( $order_id );
		}

		if (!isset($_POST['bf_cc_nonce'])) {
			$this->log('Nonce key not found: ' . $order_id, 'error');
			throw new Exception(esc_html( 'Error Processing Credit Card Payment', 'bankful_credit_card'));
			die(esc_html('Security Check', 'bankful_credit_card'));
		}

		if (!wp_verify_nonce(sanitize_key($_POST['bf_cc_nonce']), 'bf_cc_form_nonce')) {
			$this->log('Nonce key verification failed: ' . $order_id, 'error');
			throw new Exception(esc_html( 'Error Processing Credit Card Payment', 'bankful_credit_card'));
			die(esc_html('Security Check', 'bankful_credit_card'));
		}

		$expiry_month = isset($_POST['bfws_exp_month']) ? wc_clean(wp_unslash($_POST['bfws_exp_month'])) : null;
		$expiry_year  = isset($_POST['bfws_exp_year']) ? wc_clean(wp_unslash($_POST['bfws_exp_year'])) : null;

		// we need it to get any order detailes
		$order = wc_get_order($order_id);
		global $woocommerce;

		$card_number = !empty(wc_clean(wp_unslash($_POST['bfws_cc_number'])))
			? str_replace(array( ' ', '-' ), '', wc_clean(wp_unslash($_POST['bfws_cc_number'])))
			: '';
		$card_cvc = !empty(wc_clean(wp_unslash($_POST['bfws_cvv'])))
			? wc_clean(wp_unslash($_POST['bfws_cvv']))
			: '';

		try {
			if (empty($card_number)) {
				throw new Exception(__('Please Enter Card number', 'bankful'));
			} elseif (empty($expiry_month) || empty($expiry_year)) {
				throw new Exception(__('Please Enter Card expiration date', 'bankful'));
			} elseif ($this->validate_expirydate($expiry_year . $expiry_month) == false) {
				throw new Exception(__('Invalid Credit Card Expiration date', 'bankful'));
			} elseif (empty($card_cvc)) { // check expiry date
				throw new Exception(__('Please Enter Card CVV number', 'bankful'));
			}

			if (function_exists('wcs_get_subscriptions_for_order') && function_exists('wcs_order_contains_subscription') && wcs_order_contains_subscription($order_id)) {
				$subscriptions = wcs_get_subscriptions_for_order($order_id);
				$this->is_subscription = true;
			} elseif (function_exists('wcs_get_subscriptions_for_renewal_order') && function_exists('wcs_order_contains_renewal') && wcs_order_contains_renewal($order_id)) {
				$subscriptions = wcs_get_subscriptions_for_renewal_order($order_id);
				$this->is_subscription = true;
			} else {
				$this->is_subscription = false;
			}

			// $options = get_option( 'bankful_options' );
			$transaction_mode = $this->testEnv ? 'sandbox' : 'live';

			$order_param = array(
				'pmt_numb' => $card_number,
				'pmt_key' => $card_cvc,
				'pmt_expiry' => $expiry_month . '/' . $expiry_year,
				'request_currency' => $order->get_currency(),
				'remote_ip' => $order->get_meta('_customer_ip_address'),
				'cust_fname' => sanitize_text_field($order->get_billing_first_name()),
				'cust_lname' => sanitize_text_field($order->get_billing_last_name()),
				'cust_email' => sanitize_text_field($order->get_billing_email()),
				'bill_addr' => sanitize_text_field($order->get_billing_address_1() . ' ' . $order->get_billing_address_2()),
				'bill_addr_zip' => sanitize_text_field($order->get_billing_postcode()),
				'bill_addr_city' => sanitize_text_field($order->get_billing_city()),
				'bill_addr_state' => sanitize_text_field($order->get_billing_state()),
				'bill_addr_country' => sanitize_text_field($order->get_billing_country()),
				'ship_addr_country' => sanitize_text_field($order->get_shipping_country()),
				'ship_addr_city' => sanitize_text_field($order->get_shipping_city()),
				'ship_addr_state' => sanitize_text_field($order->get_shipping_state()),
				'ship_addr_zip' => sanitize_text_field($order->get_shipping_postcode()),
				'ship_addr' => sanitize_text_field($order->get_shipping_address_1() . ', ' . $order->get_shipping_address_2()),
				'cart_name' => 'WooCommerce',
				'order_id' => $order_id,
				'xtl_order_id' => $order_id,
				'request_action' => 'CCAUTHCAP',
				'save_card_on_file' => $this->is_subscription ? 'Y' : 'N',
				'transaction_mode' => $transaction_mode,
				'callback_url'	=> $this->notify_url
			);

			if ($this->is_subscription) {

				$subscription_products = array();

				$site_id = get_option( 'bankful_options_site_id' );

				if ($site_id) {
					
					$fetchObj = new stdClass(); 
					// Extract subscription IDs and product details from the $subscriptions array
					if (!empty($subscriptions)) {
						foreach ($subscriptions as $subscription) {
							$subscription_id = $subscription->get_id();

							// Get subscription items
							$subscription_items = $subscription->get_items();

							foreach ($subscription_items as $item) {
								$product_id = $item->get_product_id();
								$quantity = $item->get_quantity();
								$subscription_products[] = array(
									'subscription_id' => $subscription_id,
									'product_id' => $product_id,
									'quantity' => $quantity,
								);
							}

						}

						$req_url_exp = explode('/api/', $this->api_endpoint, -1);
						$pre_req_url = $req_url_exp[0] . '/api/handle-webhook/cart/woocommerce/verify_rulesV2';

						$fetchObj->site_id = $site_id;
						$fetchObj->products = $subscription_products;
						$fetchObj->cart_id = 10006;

						$params = (array) $fetchObj;
						$merchant_info  = $this->merchant_credential_new();
						$payloadHash    = generateSignatureHash($params, $merchant_info['bf_hash_salt']);
						
						$args = array(
							'method'    => 'POST',
							'body'      => wp_json_encode( $fetchObj ),
							'headers'   => array(
								'Content-Type'  => 'application/json', 
								'Authorization' => 'Bearer ' . $merchant_info['bf_access_token'],
								'X-Bankful-Hmac-SHA256' => $payloadHash,
							),
						);
						
						$response = bankful_remote_post($pre_req_url, $args);

						if (!is_wp_error($response)) {

							$body = wp_remote_retrieve_body($response);

							$body_pre = json_decode($body);
						
							if ($body_pre && $body_pre->allRecurringProducts) {
								$order_param['site_id'] = $fetchObj->site_id;
								$order_param['products_set'] = wp_json_encode($body_pre->allRecurringProducts);
							} else {
								foreach ($subscriptions as $subscription) {
									$subscription->add_order_note('All Product Rules not synced/configured correctly, subscription created and charged successfully but subscription not synced to bankful portal, you can ignore this message if you are not using subscriptions from bankful directly.');
								}
							}

						}

					}

				}
			}

						
			$params         = array_merge($order_param, $this->get_versions(), $this->get_cart_total(), $this->get_cart_subtotal(), $this->get_shipping_total(), $this->get_discount_total());
			$merchant_info  = $this->merchant_credential_new();
			$encrypt_param  = $this->encrypt_card_info($params['pmt_numb'], $params['pmt_key'], $params['pmt_expiry']);
			$params         = array_merge($params, $encrypt_param);
			$payloadHash    = generateSignatureHash($params, $merchant_info['bf_hash_salt']);
			
			$url = $this->api_endpoint;
			
			$args = array(
				'body' => http_build_query($params),
				'headers'     => array(
					'Content-Type' => 'application/x-www-form-urlencoded',
					'Authorization' => 'Bearer ' . $merchant_info['bf_access_token'],
					'X-Bankful-Hmac-SHA256' => $payloadHash,
				),
			);
			
			$response = bankful_remote_post($url, $args);

			if (!is_wp_error($response)) {
				$body = wp_remote_retrieve_body($response);
				$bodyArr = $body;
				$body = json_decode($body, false);

				$bodyArr = json_decode($bodyArr, true);
				if (verifyHashSignatureFromRequest($bodyArr)) {
					if (
						isset($body->TRANS_STATUS_NAME)
						&& 'APPROVED' == $body->TRANS_STATUS_NAME
					) {

						$this->log('====================== process_payment PAYMENT APPROVED for Order: ' . $order_id, 'info');

						// Add order note
						$order->add_order_note(' Payment completed with Bankful and Transaction ID:' . $body->TRANS_ORDER_ID);
						$order->add_meta_data( '_transaction_id', $body->TRANS_ORDER_ID, true );

						$body->TRANS_VALUE = floatval($body->TRANS_VALUE);

						$order->payment_complete($body->TRANS_ORDER_ID);
						wc_reduce_stock_levels($order->get_id());
					
						if ($this->is_subscription) {
							if (isset($body->CUSTOMER_VAULT_ID) && $body->CUSTOMER_VAULT_ID) {
								foreach ($subscriptions as $subscription) {
									$subscription->update_meta_data('_bankful_customer_id', $body->CUSTOMER_VAULT_ID);
									$subscription_id = $subscription->get_id();
									$found_object = new stdClass();
									foreach ($body->RECURRING_SCHEDULES as $schedule) {
										if ($schedule->WOO_SUBSCRIPTION_ID == $subscription_id) {
											$found_object = $schedule;
											break;
										}
									}

									if (isset($found_object->RECURRING_SCHEDULE_ID)) {
										$subscription->update_meta_data('_bankful_recurring_id', $found_object->RECURRING_SCHEDULE_ID);

										$order->add_order_note(' Payment completed with Bankful and Recurring ID:' . $body->RECURRING_SCHEDULE_ID);
									} else {
										$order->add_order_note(' RECURRING_SCHEDULE_ID not found');
									}
									if (class_exists('WC_Subscriptions_Manager') && function_exists('activate_subscriptions_for_order')) {
										WC_Subscriptions_Manager::activate_subscriptions_for_order($order);
									}
									$subscription->save();
								}
							} else {
								$order->add_order_note('Customer Vault ID not found, so next scheduled subscription transaction will not work. Contact Bankful Support for this.');
								foreach ($subscriptions as $subscription) {
									$subscription->add_order_note('Customer Vault ID not found, so next scheduled subscription transaction will not work. Contact Bankful Support for this.');
									$subscription->save();
								}
								
							}
						}

						wc_add_notice('Transaction has been completed successfully.', 'success');

						// Empty cart
						$woocommerce->cart->empty_cart();

						// Redirect to the thank you page
						return array(
							'result' => 'success',
							'redirect' => $this->get_return_url($order),
						);
					} else if (
						isset($body->TRANS_STATUS_NAME)
						&& 'PENDING' == $body->TRANS_STATUS_NAME
					) {
						$order->add_order_note(' Payment pending with Bankful and Transaction ID:' . $body->TRANS_ORDER_ID);
						$order->add_meta_data( '_transaction_id', $body->TRANS_ORDER_ID, true );
						$order->update_status('on-hold');

						$this->log('====================== process_payment PAYMENT PENDING for Order: ' . $order_id, 'info');

						// Add order note
						$order->add_order_note(' Payment pending with Bankful and Transaction ID:' . $body->TRANS_ORDER_ID);
						$order->add_meta_data( '_transaction_id', $body->TRANS_ORDER_ID, true );

						$body->TRANS_VALUE = floatval($body->TRANS_VALUE);

						// $order->payment_complete($body->TRANS_ORDER_ID);
						wc_reduce_stock_levels($order->get_id());
					
						if ($this->is_subscription) {
							if (isset($body->CUSTOMER_VAULT_ID) && $body->CUSTOMER_VAULT_ID) {
								foreach ($subscriptions as $subscription) {
									$subscription->update_meta_data('_bankful_customer_id', $body->CUSTOMER_VAULT_ID);
									$subscription_id = $subscription->get_id();
									$found_object = new stdClass();
									foreach ($body->RECURRING_SCHEDULES as $schedule) {
										if ($schedule->WOO_SUBSCRIPTION_ID == $subscription_id) {
											$found_object = $schedule;
											break;
										}
									}

									if (isset($found_object->RECURRING_SCHEDULE_ID)) {
										$subscription->update_meta_data('_bankful_recurring_id', $found_object->RECURRING_SCHEDULE_ID);

										$order->add_order_note(' Payment pending with Bankful and Recurring ID:' . $body->RECURRING_SCHEDULE_ID);
									} else {
										$order->add_order_note(' RECURRING_SCHEDULE_ID not found');
									}
									if (class_exists('WC_Subscriptions_Manager') && function_exists('activate_subscriptions_for_order')) {
										WC_Subscriptions_Manager::activate_subscriptions_for_order($order);
									}
									$subscription->save();
								}
							} else {
								$order->add_order_note('Customer Vault ID not found, so next scheduled subscription transaction will not work. Contact Bankful Support for this.');
								foreach ($subscriptions as $subscription) {
									$subscription->add_order_note('Customer Vault ID not found, so next scheduled subscription transaction will not work. Contact Bankful Support for this.');
									$subscription->save();
								}
								
							}
						}

						wc_add_notice('Transaction has been pending.', 'success');

						// Empty cart
						$woocommerce->cart->empty_cart();

						// Redirect to the thank you page
						return array(
							'result' => 'success',
							'redirect' => $this->get_return_url($order),
						);
					} else if (!empty($body->ERROR_MESSAGE) || !empty($body->API_ADVICE)) {

						// Add note.
						/* translators: %s: Transaction ID */
						$order->add_order_note(sprintf(__('Transaction ID %s', 'bankful'), $body->TRANS_ORDER_ID));

						$order->add_meta_data( '_transaction_id', $body->TRANS_ORDER_ID, true );

						// Payment failed.
						/* translators: %s: Payment Failed Error */
						$order->update_status('failed', sprintf(__('Card payment failed. Payment was rejected due to an error%s', 'bankful'), $order_id));

						$this->log('Transaction Failed', 'info');
						$this->log(print_r($body, true));

						$error_message = 'Something went wrong, please contact to your service provider.';
						if (isset($body->TRANS_STATUS_NAME) && 'DECLINED' == $body->TRANS_STATUS_NAME) {
							$error_message = ( ( !empty($body->ERROR_MESSAGE) ) ? $body->ERROR_MESSAGE : ( !empty($body->API_ADVICE) ) ) ? $body->API_ADVICE : $error_message;
						}
						wc_add_notice('Payment failed. ', 'error');

						throw new Exception(__($error_message, 'bankful'));
					}
				} else {
					/* translators: %s: Transaction ID */
					$order->add_order_note(sprintf(__('Transaction ID %s', 'bankful'), $body->TRANS_ORDER_ID));

					$order->add_meta_data( '_transaction_id', $body->TRANS_ORDER_ID, true );

					// Payment failed.
					/* translators: %s: Payment Failed Error */
					$order->update_status('failed', sprintf(__('Card payment failed. Payment was rejected due to an error%s', 'bankful'), $order_id));

					$this->log('Transaction Failed', 'info');
					$this->log('Signature Mismatched', 'info');

					$error_message = 'Something went wrong, please contact to your service provider.';
					if (isset($body->TRANS_STATUS_NAME) && 'DECLINED' == $body->TRANS_STATUS_NAME) {
						$error_message = ( ( !empty($body->ERROR_MESSAGE) ) ? $body->ERROR_MESSAGE : ( !empty($body->API_ADVICE) ) ) ? $body->API_ADVICE : $error_message;
					}
					wc_add_notice('Payment failed. Signature Mismatched', 'error');
					$woocommerce->cart->empty_cart();
					throw new Exception(__($error_message, 'bankful'));
				}
			} else {
				wc_add_notice('Connection error.', 'error');
			}
		} catch (Exception $ex) { // Add log
			$this->log('ERROR Throws Exception', 'info');
			$this->log($ex->getMessage(), 'error');

			wc_add_notice($ex->getMessage(), 'error');
			return array(
				'result' => 'failed',
				'redirect' => $this->get_return_url($order),
				'message' => $ex->getMessage(),
			);
		}
		return array(
			'result' => 'failed',
			'redirect' => $this->get_return_url($order),
		);
	}

	/**
	 * Process the Subscription payment and return the result.
	 */
	public function process_subscription_payment( $amount_to_charge, $renewal_order ) { 
		$order = $renewal_order;

		try {
			if (function_exists('wcs_order_contains_subscription') && function_exists('wcs_order_contains_renewal') && wcs_order_contains_renewal($order)) {
				$this->is_subscription = true;
			} else {
				throw new Exception(__('Order has no Subscription to process', 'bankful'));
			}
			
			$order_id = $order->get_id();
			if (function_exists('wcs_get_subscriptions_for_renewal_order')) {
				$subscriptions = wcs_get_subscriptions_for_renewal_order($order);
				$subscription = reset($subscriptions);
				$customer_id = $subscription->get_meta('_bankful_customer_id', true);
				$recurring_schedule_id = $subscription->get_meta('_bankful_recurring_id', true);
				if (empty($customer_id)) {
					$order->add_order_note('No Customer Found : ' . $customer_id);
					$subscription->add_order_note('No Customer Found for this transaction, Contact Bankful Support');
					$subscription->save();
					throw new Exception(__('No Customer Found for this transaction', 'bankful'));
				} else if ( empty($recurring_schedule_id)) {
					$order->add_order_note('No Bankful Recurring ID found');
					$subscription->add_order_note('Transaction created but not synced with bankful recurring.');
					$subscription->save();
				}
			}

			$transaction_mode = $this->testEnv ? 'sandbox' : 'live';

			$order_param = array(
				'request_currency' => $order->get_currency(),
				'cust_fname' => $order->get_billing_first_name(),
				'cust_lname' => $order->get_billing_last_name(),
				'cust_email' => $order->get_billing_email(),
				'bill_addr' => $order->get_billing_address_1() . ' ' . $order->get_billing_address_2(),
				'bill_addr_zip' => $order->get_billing_postcode(),
				'bill_addr_city' => $order->get_billing_city(),
				'bill_addr_state' => $order->get_billing_state(),
				'bill_addr_country' => $order->get_billing_country(),
				'cart_name' => 'WooCommerce',
				'order_id' => $order_id,
				'xtl_order_id' => $order_id,
				'request_action' => 'CCAUTHCAP',
				'amount' => $amount_to_charge,
				'recurringId' => $recurring_schedule_id,
				'transaction_mode' => $transaction_mode,
				'callback_url'	=> $this->notify_url
			);

			
			if (isset($customer_id)) {
				$params = array_merge($order_param, $this->get_versions(), array( 'customer_vault_id'=> $customer_id ));
			} else {
				$params = array_merge($order_param, $this->get_versions());
			}
			
			$merchant_info  = $this->merchant_credential_new();
			$payloadHash = generateSignatureHash($params, $merchant_info['bf_hash_salt']);

			$url  = $this->api_endpoint;
			$args = array(
				'body' => http_build_query($params),
				'headers'     => array(
					'Content-Type' => 'application/x-www-form-urlencoded',
					'Authorization' => 'Bearer ' . $merchant_info['bf_access_token'],
					'X-Bankful-Hmac-SHA256' => $payloadHash,
				),
			);
				
			$response = bankful_remote_post($url, $args);

			// Handle WP_Error (connection failures, DNS errors, timeouts, etc.).
			if ( is_wp_error($response) ) {
				$error_message = sanitize_text_field( $response->get_error_message() );
				$order->add_order_note('Subscription payment failed: ' . $error_message);
				$this->log('process_subscription_payment API error: ' . $error_message, 'error');
				throw new Exception(
					sprintf(
						/* translators: %s: Error message from API */
						__('Bankful API request failed: %s', 'bankful'),
						$error_message
					)
				);
			}

			// Validate the HTTP response status code is in the 2xx success range.
			$response_code = wp_remote_retrieve_response_code( $response );
			if ( $response_code < 200 || $response_code >= 300 ) {
				$error_message = sprintf( 'HTTP %d error: %s', $response_code, wp_remote_retrieve_response_message( $response ) );
				$response_body = wp_remote_retrieve_body( $response );
				$log_body = strlen( $response_body ) > 500 ? substr( $response_body, 0, 500 ) . '...[truncated]' : $response_body;
				$order->add_order_note( 'Subscription payment failed: ' . sanitize_text_field( $error_message ) );
				$this->log( 'process_subscription_payment HTTP error: ' . $error_message . ' | Body: ' . $log_body, 'error' );
				throw new Exception(
					sprintf(
						/* translators: %s: Error details */
						__( 'Bankful API returned error: %s', 'bankful' ),
						$error_message
					)
				);
			}

			// At this point the response is valid — parse the body and verify signature.
			$body = wp_remote_retrieve_body($response);
			$bodyArr = $body;
			$body = json_decode($body, false);
			$bodyArr = json_decode($bodyArr, true);
			if (verifyHashSignatureFromRequest($bodyArr)) {
				if (
				isset($body->TRANS_STATUS_NAME)
				&& 'APPROVED' == $body->TRANS_STATUS_NAME
				) {

					// Add order note
					$order->add_order_note('Subscription Payment completed with Bankful and Transaction ID:' . $body->TRANS_ORDER_ID);
					$order->add_meta_data( '_transaction_id', $body->TRANS_ORDER_ID, true );

					$this->log('====================== process_subscription_payment $body->TRANS_ORDER_ID' . $body->TRANS_ORDER_ID, 'info');

					$body->TRANS_VALUE = floatval($body->TRANS_VALUE);

					// Payment complete add TRANS_ORDER_ID as transaction id in post_meta table
					$order->payment_complete($body->TRANS_ORDER_ID);
					wc_reduce_stock_levels($order->get_id());

					// Redirect to the thank you page
					return array(
						'result' => 'success',
						'redirect' => $this->get_return_url($order),
					);
				} else if (
					isset($body->TRANS_STATUS_NAME)
					&& 'PENDING' == $body->TRANS_STATUS_NAME
				) {

					$order->add_order_note(' Payment pending with Bankful and Transaction ID:' . $body->TRANS_ORDER_ID);
					$order->add_meta_data( '_transaction_id', $body->TRANS_ORDER_ID, true );
					$order->update_status('on-hold');

					$this->log('====================== process_payment PAYMENT PENDING for Order: ' . $order_id, 'info');

					// Add order note
					$order->add_order_note(' Payment pending with Bankful and Transaction ID:' . $body->TRANS_ORDER_ID);
					$order->add_meta_data( '_transaction_id', $body->TRANS_ORDER_ID, true );

					$body->TRANS_VALUE = floatval($body->TRANS_VALUE);

					// $order->payment_complete($body->TRANS_ORDER_ID);
					wc_reduce_stock_levels($order->get_id());

					// Redirect to the thank you page
					return array(
						'result' => 'success',
						'redirect' => $this->get_return_url($order),
					);
				} else if (!empty($body->ERROR_MESSAGE) || !empty($body->API_ADVICE)) {

					$status = 'ERROR';

					// Add note
					/* translators: %s: Payment Failed Transaction ID */
					$order->add_order_note(sprintf(__('Failed Transaction ID %s', 'bankful'), $body->TRANS_ORDER_ID));

					$order->add_meta_data( '_transaction_id', $body->TRANS_ORDER_ID, true );
					$this->log('====================== process_subscription_payment ERROR' . $body->TRANS_ORDER_ID, 'info');
					// Payment failed
					/* translators: %s: Payment Failed Error */
					$order->update_status('failed', sprintf(__('Card payment failed. Payment was rejected due to an error%s', 'bankful'), $order_id));

					$error_message = 'Something went wrong, please contact to your service provider.';
					if (isset($body->TRANS_STATUS_NAME) && 'DECLINED' == $body->TRANS_STATUS_NAME) {
						$error_message = ( ( !empty($body->ERROR_MESSAGE) ) ? $body->ERROR_MESSAGE : ( !empty($body->API_ADVICE) ) ) ? $body->API_ADVICE : $error_message;
					}

					throw new Exception(__($error_message, 'bankful'));
				}
			} else {
				// Add note
				/* translators: %s: Payment Failed Transaction ID */
				$order->add_order_note(sprintf(__('Failed Transaction ID %s', 'bankful'), $body->TRANS_ORDER_ID));

				$order->add_meta_data( '_transaction_id', $body->TRANS_ORDER_ID, true );
				// Payment failed
				/* translators: %s: Payment Failed Error */
				$order->update_status('failed', sprintf(__('Signature Mismatched, Card payment failed. Payment was rejected due to altered data %s', 'bankful'), $order_id));

				$error_message = 'Something went wrong, please contact to your service provider.';
				if (isset($body->TRANS_STATUS_NAME) && 'DECLINED' == $body->TRANS_STATUS_NAME) {
					$error_message = ( ( !empty($body->ERROR_MESSAGE) ) ? $body->ERROR_MESSAGE : ( !empty($body->API_ADVICE) ) ) ? $body->API_ADVICE : $error_message;
				}

				throw new Exception(__($error_message, 'bankful'));
			}
		} catch (Exception $ex) { // Add log
			$this->log('ERROR Ex', 'info');
			$this->log($ex->getMessage(), 'error');

			/* translators: %s: General Failed Transaction Error */
			$order->add_order_note(sprintf(__('Error Message %s', 'bankful'), $ex->getMessage()));
		}
	}

	/**
	 * Process the payment method change for subscriptions.
	 *
	 * @since 5.6.0
	 *
	 * @param int $order_id
	 * @return array|null
	 */
	public function process_change_bankful_subscription_payment_method( $order_id ) {
		$this->log('====================== process_change_bankful_subscription_payment_method CAPTURE CARD for Order: ' . $order_id, 'info');

		if (!isset($_POST['bf_cc_nonce'])) {
			$this->log('Nonce key not found: ' . $order_id, 'error');
			throw new Exception(esc_html( 'Error Processing Credit Card Payment', 'bankful_credit_card'));
			die(esc_html('Security Check', 'bankful_credit_card'));
		}

		if (!wp_verify_nonce(sanitize_key($_POST['bf_cc_nonce']), 'bf_cc_form_nonce')) {
			$this->log('Nonce key verification failed: ' . $order_id, 'error');
			throw new Exception(esc_html( 'Error Processing Credit Card Payment', 'bankful_credit_card'));
			die(esc_html('Security Check', 'bankful_credit_card'));
		}

		$expiry_month = isset($_POST['bfws_exp_month']) ? wc_clean(wp_unslash($_POST['bfws_exp_month'])) : null;
		$expiry_year = isset($_POST['bfws_exp_year']) ? wc_clean(wp_unslash($_POST['bfws_exp_year'])) : null;

		$order = wc_get_order($order_id);

		$card_number = !empty(wc_clean(wp_unslash($_POST['bfws_cc_number'])))
			? str_replace(array( ' ', '-' ), '', wc_clean(wp_unslash($_POST['bfws_cc_number'])))
			: '';
		$card_cvc = !empty(wc_clean(wp_unslash($_POST['bfws_cvv'])))
			? wc_clean(wp_unslash($_POST['bfws_cvv']))
			: '';

		try {
			if (empty($card_number)) {
				throw new Exception(__('Please Enter Card number', 'bankful'));
			} elseif (empty($expiry_month) || empty($expiry_year)) {
				throw new Exception(__('Please Enter Card expiration date', 'bankful'));
			} elseif ($this->validate_expirydate($expiry_year . $expiry_month) == false) {
				throw new Exception(__('Invalid Credit Card Expiration date', 'bankful'));
			} elseif (empty($card_cvc)) { 
				throw new Exception(__('Please Enter Card CVV number', 'bankful'));
			}

			$this->is_subscription = true;

			$order_param = array(
				'pmt_numb' => $card_number,
				'pmt_key' => $card_cvc,
				'pmt_expiry' => $expiry_month . '/' . $expiry_year,
				'request_currency' => $order->get_currency(),
				'remote_ip' => $order->get_customer_ip_address(),
				'cust_fname' => sanitize_text_field($order->get_billing_first_name()),
				'cust_lname' => sanitize_text_field($order->get_billing_last_name()),
				'cust_email' => sanitize_text_field($order->get_billing_email()),
				'bill_addr' => sanitize_text_field($order->get_billing_address_1() . ' ' . $order->get_billing_address_2()),
				'bill_addr_zip' => sanitize_text_field($order->get_billing_postcode()),
				'bill_addr_city' => sanitize_text_field($order->get_billing_city()),
				'bill_addr_state' => sanitize_text_field($order->get_billing_state()),
				'bill_addr_country' => sanitize_text_field($order->get_billing_country()),
				'ship_addr_country' => sanitize_text_field($order->get_shipping_country()),
				'ship_addr_city' => sanitize_text_field($order->get_shipping_city()),
				'ship_addr_state' => sanitize_text_field($order->get_shipping_state()),
				'ship_addr_zip' => sanitize_text_field($order->get_shipping_postcode()),
				'ship_addr' => sanitize_text_field($order->get_shipping_address_1() . ', ' . $order->get_shipping_address_2()),
				'cart_name' => 'WooCommerce',
				'order_id' => $order_id,
				'xtl_order_id' => $order_id,
				'request_action' => 'CCAUTHCAP',
				'save_card_on_file' => 'Y',
				'callback_url'	=> $this->notify_url
			);

				
			$params         = array_merge($order_param, $this->get_versions(), $this->get_cart_total(), $this->get_cart_subtotal(), $this->get_shipping_total(), $this->get_discount_total());
			$merchant_info  = $this->merchant_credential_new();
			$encrypt_param  = $this->encrypt_card_info($params['pmt_numb'], $params['pmt_key'], $params['pmt_expiry']);
			$params         = array_merge($params, $encrypt_param);
			$payloadHash    = generateSignatureHash($params, $merchant_info['bf_hash_salt']);
			
			$url = $this->api_endpoint_token;

			$args = array(
				'body' => http_build_query($params),
				'headers'     => array(
					'Content-Type' => 'application/x-www-form-urlencoded',
					'Authorization' => 'Bearer ' . $merchant_info['bf_access_token'],
					'X-Bankful-Hmac-SHA256' => $payloadHash,
				),
			);
				
			$response = bankful_remote_post($url, $args);

			$body = wp_remote_retrieve_body($response);

			$this->log('====================== Payment Method Updated Body Response: ' . print_r($body, true), 'info');

			if (!is_wp_error($response)) {
				
				$bodyArr = $body;
				$body = json_decode($body, false);
				$bodyArr = json_decode($bodyArr, true);
				if (verifyHashSignatureFromRequest($bodyArr['data'])) {
					if (
							isset($body->status)
							&& isset($body->data)
							&& 'success' == $body->status

						) {
						$body = $body->data;
						$this->log('====================== Payment Method Updated for Order ID : ' . $order_id, 'info');
						$this->log('====================== Payment Method Updated Body Response: ' . print_r($body, true), 'info');
						$this->log('====================== Payment Method Updated $body->CUSTOMER_VAULT_ID1 : ' . $body->CUSTOMER_VAULT_ID, 'info');

						// Add order note
						if (isset($body->CUSTOMER_VAULT_ID) && $body->CUSTOMER_VAULT_ID) {
							$this->log('====================== Payment Method Updated $body->CUSTOMER_VAULT_ID : ' . $body->CUSTOMER_VAULT_ID, 'info');
							$order->update_meta_data( '_bankful_customer_id', $body->CUSTOMER_VAULT_ID );
						
							if ( is_callable( array( $order, 'save' ) ) ) {
								$order->save();
							}
							

							$order_id = $order->get_id();

							// Also store it on the subscriptions being purchased or paid for in the order
							if ( function_exists( 'wcs_order_contains_subscription' ) && wcs_order_contains_subscription( $order_id ) ) {

								$this->log('====================== update Payment in wcs_order_contains_subscription Subscription is True: ', 'info');
								$subscriptions = wcs_get_subscriptions_for_order( $order_id );
								$this->is_subscription = true;

							} elseif ( function_exists( 'wcs_order_contains_renewal' ) && wcs_order_contains_renewal( $order_id ) ) {

								$this->log('====================== update Payment in wcs_order_contains_renewal Subscription is True: ', 'info');
								$subscriptions = wcs_get_subscriptions_for_renewal_order( $order_id );
								$this->is_subscription = true;

							} else {

								$this->log('====================== update Payment in wcs_order_contains_renewal Subscription is False: ', 'info');
								$subscriptions = array();
								$this->is_subscription = false;

							}

							foreach ( $subscriptions as $subscription ) {
								$subscription->add_order_note('Payment Method Updated, New Customer Vault ID:' . $body->CUSTOMER_VAULT_ID);
								$subscription->update_meta_data( '_bankful_customer_id', $body->CUSTOMER_VAULT_ID );
								// Update the payment method.
								if ( ! empty( $this->id ) ) {
									$subscription->set_payment_method( $this->id );
								}
								$subscription->save();
							}

						}
						// Redirect to the thank you page
						return array(
							'result' => 'success',
							'redirect' => $this->get_return_url($order),
						);
					} else if (!empty($body->status) && 'failed' == $body->status) {

						// Add note.
						/* translators: %s: Transaction ID */
						$order->add_order_note(sprintf(__('Payment Method update failed %s', 'bankful'), $body->errorMessage));

						$this->log('Payment Method update failed', 'error');
						$this->log(print_r($body, true), 'error');

						$error_message = 'Something went wrong, please contact to your service provider.';

						throw new Exception(__($error_message, 'bankful'));
					}
				} else {
					// Add note.
						/* translators: %s: Transaction ID */
						$order->add_order_note(sprintf(__('Payment Method update failed %s', 'bankful'), $body->errorMessage));

						$this->log('Payment Method update failed', 'error');
						$this->log(print_r($body, true), 'error');

						$error_message = 'Something went wrong, please contact to your service provider.';

						throw new Exception(__($error_message, 'bankful'));
				}
			} else {
				wc_add_notice('Connection error.', 'error');
			}
		} catch (Exception $ex) { // Add log
			$this->log('ERROR Throws Exception', 'info');
			$this->log($ex->getMessage(), 'error');

			wc_add_notice($ex->getMessage(), 'error');
			return array(
				'result' => 'failed',
				'redirect' => $this->get_return_url($order),
				'message' => $ex->getMessage(),
			);
		}
		return array(
			'result' => 'failed',
			'redirect' => $this->get_return_url($order),
		);
	}

	/**
	 * Schedule Subscription for Payment
	 *
	 * @param int $order_id Order ID.
	 * 
	 */
	public function scheduled_subscription_payment( $amount, $renewal_order ) {
		$this->process_subscription_payment($amount, $renewal_order);
	}

	/**
	 * Method to encrypt large data using RSA (splitting into chunks)
	 *
	 * 
	 */
	public function rsaEncryptLargeData( $data, $publicKey ) {
		$maxChunkSize = 245; // Adjust based on your RSA key size (e.g., 2048-bit key = 245 bytes)
		$chunks = str_split($data, $maxChunkSize); // Split the data into chunks
		$encryptedData = '';
	
		foreach ($chunks as $chunk) {
			$encryptedChunk = null;
			if (!openssl_public_encrypt($chunk, $encryptedChunk, $publicKey)) {
				return false; // Encryption failed
			}
			$encryptedData .= $encryptedChunk;
		}
	
		return $encryptedData;
	}
}
