<?php
/**
 * WooCommerce Global Payments HPP.
 *
 * This source file is subject to the GNU General Public License v3.0
 * that is bundled with this package in the file license.txt.
 * It is also available through the world-wide-web at this URL:
 * http://www.gnu.org/licenses/gpl-3.0.html
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@skyverge.com so we can send you a copy immediately.
 *
 * DISCLAIMER
 *
 * Do not edit or add to this file if you wish to upgrade WooCommerce Global Payments HPP to newer
 * versions in the future. If you wish to customize WooCommerce Global Payments HPP for your
 * needs please refer to https://docs.woocommerce.com/document/woocommerce-global-payments/ for more information.
 *
 * @author      SkyVerge
 * @copyright   Copyright (c) 2012-2024, SkyVerge, Inc.
 * @license     http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
 */

defined( 'ABSPATH' ) or exit;

use SkyVerge\WooCommerce\PluginFramework\v5_12_5 as Framework;
use SkyVerge\WooCommerce\Global_Payments\HPP\Payment_Form_Helper;
use SkyVerge\WooCommerce\Global_Payments\HPP\APM\Alternative_Payment_Method;
use SkyVerge\WooCommerce\Global_Payments\HPP\APM\Alternative_Payment_Methods;
use SkyVerge\WooCommerce\Global_Payments\HPP\APM\Redirect_Response;
use SkyVerge\WooCommerce\Global_Payments\HPP\APM\Status_Response;
use SkyVerge\WooCommerce\Realex_HPP\Blocks\Checkout_Block_Integration;

/**
 * The Realex Redirect gateway class.
 *
 * @since 1.0.0
 *
 * @method \WC_Realex_Redirect get_plugin()
 */
class WC_Gateway_Realex_Redirect extends Framework\SV_WC_Payment_Gateway_Hosted {


	/** production environment HPP URL */
	const HPP_URL = 'https://pay.realexpayments.com/pay';

	/** test environment HPP URL */
	const HPP_URL_TEST = 'https://pay.sandbox.realexpayments.com/pay';

	/** HPP version number */
	const HPP_VERSION = 2;

	/** HPP iFrame form type */
	const FORM_TYPE_IFRAME = 'iframe';

	/** HPP redirect form type */
	const FORM_TYPE_REDIRECT = 'redirect';

	/** HPP PayPal payment type */
	const PAYMENT_TYPE_PAYPAL = 'paypal';


	/** @var Checkout_Block_Integration|null */
	protected ?Checkout_Block_Integration $checkout_block_integration = null;

	/** @var string whether tokenizatin is forced */
	protected $tokenization_forced;

	/** @var string account merchant ID */
	protected $merchant_id;

	/** @var string account shared secret */
	protected $shared_secret;

	/** @var string account rebate password */
	protected $rebate_password;

	/** @var string subaccount */
	protected $subaccount;

	/** @var string test subaccount */
	protected $test_subaccount;

	/** @var string HPP form URL */
	protected $form_url;

	/** @var string test HPP form URL */
	protected $test_form_url;

	/** @var string HPP form type */
	protected $form_type;

	/** @var string HPP form type */
	protected $form_language;

	/** @var string whether AVS should be enabled/checked */
	protected $enable_avs;

	/** @var bool whether APMs are enabled */
	protected $enable_apm;

	/** @var string[] list of selected APMs */
	protected $available_apms;

	/** @var string account refund password */
	protected $refund_password;

	/** @var \WC_Realex_Redirect_API direct API instance */
	protected $api;


	/**
	 * Constructs the class.
	 *
	 * @since 1.0.0
	 */
	public function __construct() {

		parent::__construct(
			WC_Realex_Redirect::GATEWAY_ID,
			wc_realex_redirect(),
			[
				'method_title'       => __( 'Global Payments HPP', 'woocommerce-gateway-realex-redirect' ),
				'method_description' => __( 'Take payments using the Global Payments Hosted Payment Page (HPP). Maximise conversion with a payment page that adapts to any device with 15+ languages with multiple payment methods and currencies. Minimise fraud with 30+ easy to configure rules and reduce the cost of PCI Compliance with our PCI-DSS v3.2 HPP.', 'woocommerce-gateway-realex-redirect' ),
				'supports' => [
					self::FEATURE_PRODUCTS,
					self::FEATURE_CARD_TYPES,
					self::FEATURE_CREDIT_CARD_CHARGE,
					self::FEATURE_CREDIT_CARD_CHARGE_VIRTUAL,
					self::FEATURE_CREDIT_CARD_AUTHORIZATION,
					self::FEATURE_CREDIT_CARD_CAPTURE,
					self::FEATURE_CREDIT_CARD_PARTIAL_CAPTURE,
					self::FEATURE_TOKENIZATION,
					self::FEATURE_CUSTOMER_ID,
					self::FEATURE_DETAILED_CUSTOMER_DECLINE_MESSAGES,
					self::FEATURE_REFUNDS,
					self::FEATURE_VOIDS,
					// TODO: Add Payment Method {CW 2017-09-08}
				],
				'payment_type' => self::PAYMENT_TYPE_CREDIT_CARD,
				'environments' => [
					self::ENVIRONMENT_PRODUCTION => __( 'Production', 'woocommerce-gateway-realex-redirect' ),
					self::ENVIRONMENT_TEST       => __( 'Sandbox', 'woocommerce-gateway-realex-redirect' ),
				],
			]
		);

		// encrypt the shared secrete & rebate password on settings save
		add_filter( "woocommerce_settings_api_sanitized_fields_{$this->id}", [$this, 'encrypt_credentials'] );

		// show a different message for pending APM transactions
		add_action( 'woocommerce_thankyou_order_received_text', [$this, 'maybe_change_thankyou_order_received_text'], 10, 2 );

		// add any previous failure notices
		add_action( 'before_woocommerce_pay', [$this, 'maybe_add_failed_order_notice'], 5 );

		// require checkout registration if tokenization is forced
		add_filter( 'woocommerce_checkout_registration_required', [$this, 'require_checkout_registration'] );

		// add subscription payment method update notice
		add_action( 'wp_loaded', [$this, 'maybe_add_payment_method_update_notice'] );
	}


	/**
	 * Gets the method form fields.
	 *
	 * @see Framework\SV_WC_Payment_Gateway::init_form_fields()
	 *
	 * @since 2.0.0
	 *
	 * @return array
	 */
	protected function get_method_form_fields() {
		return [

			// global fields
			'merchant_id' => [
				'title' => __( 'Merchant ID', 'woocommerce-gateway-realex-redirect' ),
				'type'  => 'text',
			],
			'shared_secret' => [
				'title'    => __( 'Shared Secret', 'woocommerce-gateway-realex-redirect' ),
				'type'     => 'password',
				'desc_tip' => __( 'The shared secret for your account, provided by Global Payments.', 'woocommerce-gateway-realex-redirect' ),
			],
			'rebate_password' => [
				'title'    => __( 'Rebate Password', 'woocommerce-gateway-realex-redirect' ),
				'type'     => 'password',
				'desc_tip' => __( 'The rebate password for your account, provided by Global Payments.', 'woocommerce-gateway-realex-redirect' ),
			],

			// environment fields
			'subaccount' => [
				'title'    => __( 'Default Subaccount', 'woocommerce-gateway-realex-redirect' ),
				'type'     => 'text',
				'class'    => 'environment-field production-field',
			],
			'test_subaccount' => [
				'title'    => __( 'Default Subaccount', 'woocommerce-gateway-realex-redirect' ),
				'type'     => 'text',
				'class'    => 'environment-field test-field',
			],

			'form_url' => [
				'title'   => __( 'Form URL', 'woocommerce-gateway-realex-redirect' ),
				'type'    => 'text',
				'class'   => 'environment-field production-field',
				'default' => self::HPP_URL,
			],
			'test_form_url' => [
				'title'   => __( 'Form URL', 'woocommerce-gateway-realex-redirect' ),
				'type'    => 'text',
				'class'   => 'environment-field test-field',
				'default' => self::HPP_URL_TEST,
			],

			'form_type' => [
				'title'   => __( 'Form Type', 'woocommerce-gateway-realex-redirect' ),
				'type'    => 'select',
				'options' => [
					self::FORM_TYPE_IFRAME   => __( 'iFrame', 'woocommerce-gateway-realex-redirect' ),
					self::FORM_TYPE_REDIRECT => __( 'Redirect', 'woocommerce-gateway-realex-redirect' ),
				],
				'default' => 'iframe',
			],

			'form_language' => [
				'title'       => __( 'Form Language', 'woocommerce-gateway-realex-redirect' ),
				'type'        => 'text',
				'placeholder' => substr( get_locale(), 0, 2 ),
				'description' => sprintf(
					/* translators: Placeholders: %1$s - <a> tag, %2$s - </a> tag */
					__( 'Leave this blank to use your site\'s language, configured in %1$sGeneral Settings%2$s.', 'woocommerce-gateway-realex-redirect' ),
					'<a href="'.esc_url( admin_url( 'options-general.php' ) ).'">', '</a>'
				),
				'desc_tip'    => __( 'Configure to display the payment form in a specific language.', 'woocommerce-gateway-realex-redirect' ),
			],

			'enable_avs' => [
				'title'   => __( 'Address Verification Service (AVS)', 'woocommerce-gateway-realex-redirect' ),
				'label'   => __( 'Perform an AVS check on customers\' billing addresses', 'woocommerce-gateway-realex-redirect' ),
				'type'    => 'checkbox',
				'default' => 'yes',
			],

			'enable_apm' => [
				'title'    => __( 'Enable Alternative Payment Methods', 'woocommerce-gateway-realex-redirect' ),
				'type'     => 'checkbox',
				'desc_tip' => __( 'Enable Alternative Payment Methods (APMs) to let your customers choose from over 30 different payment options.', 'woocommerce-gateway-realex-redirect' ),
				'default'  => 'no',
				'class'    => 'js-enable-apm-field',
			],

			'available_apms' => [
				'title'             => __( 'Available APMs', 'woocommerce-gateway-realex-redirect' ),
				'type'              => 'multiselect',
				'desc_tip'          => __( 'Select APMs to offer your customers or leave blank to enable all. These APMs must first be enabled in your Global Payments account.', 'woocommerce-gateway-realex-redirect' ),
				'default'           => [],
				'options'           => $this->get_available_payment_method_options(),
				'class'             => 'wc-enhanced-select apm-field',
				'css'               => 'min-width: 300px;',
				'custom_attributes' => [
					'data-placeholder' => __( 'Search APMs or leave blank to enable all', 'woocommerce-gateway-realex-redirect' ),
				],
			],

			'refund_password' => [
				'title'    => __( 'Refund Password', 'woocommerce-gateway-realex-redirect' ),
				'type'     => 'password',
				'desc_tip' => __( 'The refund password for your account, provided by Global Payments.', 'woocommerce-gateway-realex-redirect' ),
				'class'    => 'apm-field',
			],

		];
	}


	/**
	 * Gets a list of payment method that merchants can offer as payment options.
	 *
	 * @since 2.4.0
	 *
	 * @return array
	 */
	private function get_available_payment_method_options() {
		$options = [];

		foreach ( Alternative_Payment_Methods::find() as $payment_method) {
			$options[ $payment_method->get_id() ] = $payment_method->get_name();
		}

		return $options;
	}


	/**
	 * Adds the enable Card Security Code form fields.
	 *
	 * Overridden to remove the "Saved Card" CSC setting, as that's not used
	 * for the HPP.
	 *
	 * @since 2.0.0
	 *
	 * @param array $form_fields gateway form fields
	 * @return array
	 */
	protected function add_csc_form_fields( $form_fields ) {

		$form_fields = parent::add_csc_form_fields( $form_fields );

		unset( $form_fields['enable_token_csc'] );

		return $form_fields;
	}


	/**
	 * Adds any tokenization form fields for the settings page.
	 *
	 * @since 2.0.0
	 *
	 * @param array $form_fields gateway form fields
	 * @return array
	 */
	protected function add_tokenization_form_fields( $form_fields ) {

		$form_fields = parent::add_tokenization_form_fields( $form_fields );

		$form_fields['tokenization_forced'] = [
			'title'       => esc_html__( 'Require Tokenization', 'woocommerce-plugin-framework' ),
			'label'       => esc_html__( 'Automatically save customers\' credit card details to their account.', 'woocommerce-gateway-realex-redirect' ),
			'description' => esc_html__( 'If enabled, new customers will be required to create an account at checkout.', 'woocommerce-gateway-realex-redirect' ),
			'type'        => 'checkbox',
			'default'     => 'no',
		];

		return $form_fields;
	}


	/**
	 * Adds some extra JavaScript to show/hide certain settings.
	 *
	 * @since 2.0.0
	 */
	public function admin_options() {
		parent::admin_options();

		// add inline javascript
		ob_start(); ?>
			$( '#woocommerce_<?php echo esc_js( $this->get_id() ); ?>_tokenization' ).change( function() {

				var hidden_setting   = $( '#woocommerce_<?php echo esc_js( $this->get_id() ); ?>_tokenization_forced' ).closest( 'tr' );

				if ( $( this ).is( ':checked' ) ) {
					$( hidden_setting ).show();
				} else {
					$( hidden_setting ).hide();
				}

			} ).change();
		<?php

		wc_enqueue_js( ob_get_clean() );
	}


	/**
	 * Makes registration at checkout required if forced tokenization is enabled.
	 *
	 * @internal
	 *
	 * @since 2.0.0
	 *
	 * @param bool $required whether registration is already required
	 * @return bool
	 */
	public function require_checkout_registration( $required ) {

		return $required || ( $this->is_available() && $this->is_tokenization_forced() );
	}


	/**
	 * Encrypt the Shared Secrete and Rebate Password on settings save.
	 *
	 * @internal
	 *
	 * @since 2.0.0
	 *
	 * @param array $settings gateway settings
	 * @return array
	 */
	public function encrypt_credentials( $settings ) {

		$settings['shared_secret']   = WC_Realex_Redirect::encrypt_credential( $settings['shared_secret'] );
		$settings['rebate_password'] = WC_Realex_Redirect::encrypt_credential( $settings['rebate_password'] );

		if (! empty( $settings['refund_password'] )) {
			$settings['refund_password'] = WC_Realex_Redirect::encrypt_credential( $settings['refund_password'] );
		}

		return $settings;
	}


	/**
	 * Loads the plugin configuration settings.
	 *
	 * Overridden to decrypt the credentials.
	 *
	 * @since 2.0.0
	 */
	public function load_settings() {

		if ( isset( $this->settings['shared_secret'] ) ) {
			$this->settings['shared_secret'] = $this->get_plugin()->decrypt_credential( $this->settings['shared_secret'] );
		}

		if ( isset( $this->settings['rebate_password'] ) ) {
			$this->settings['rebate_password'] = $this->get_plugin()->decrypt_credential( $this->settings['rebate_password'] );
		}

		if ( isset( $this->settings['refund_password'] ) ) {
			$this->settings['refund_password'] = $this->get_plugin()->decrypt_credential( $this->settings['refund_password'] );
		}

		parent::load_settings();
	}


	/**
	 * Gets the default payment method title.
	 *
	 * @since 2.0.0
	 *
	 * @return string
	 */
	protected function get_default_title() {
		return __( 'Debit or Credit Card', 'woocommerce-gateway-realex-redirect' );
	}


	/**
	 * Gets the default payment method description.
	 *
	 * @since 2.0.0
	 *
	 * @return string
	 */
	protected function get_default_description() {
		return __( 'Pay securely using your debit or credit card.', 'woocommerce-gateway-realex-redirect' );
	}


	/**
	 * Determines if the gateway is properly configured to perform transactions.
	 *
	 * @since 2.0.0
	 *
	 * @return bool
	 */
	public function is_configured() {
		return $this->get_merchant_id() && $this->get_shared_secret() && $this->get_hosted_pay_page_url();
	}


	/**
	 * Processes the payment.
	 *
	 * Overridden to add tokenized payment handling.
	 *
	 * @since 2.0.0
	 *
	 * @param int $order_id order ID
	 * @return array
	 */
	public function process_payment( $order_id ) {

		$order = $this->get_order( $order_id );

		// if the payment has a token, try a direct tokenized payment
		if ( $this->tokenization_enabled() && ! empty( $order->payment->token ) ) {
			if ( $this->perform_credit_card_charge( $order ) ) {
				$response = $this->get_api()->credit_card_charge( $order );
			} else {
				$response = $this->get_api()->credit_card_authorization( $order );
			}

			if ( $response->transaction_approved() || $response->transaction_held() ) {
				$this->add_transaction_data( $order, $response );

				$this->add_payment_gateway_transaction_data( $order, $response );

				// handle the order status, etc...
				$this->complete_payment( $order, $response );

				return [
					'result'   => 'success',
					'redirect' => $this->get_return_url( $order ),
				];
			} else {
				return $this->do_transaction_failed_result( $order, $response );
			}
		} else {
			return parent::process_payment( $order_id );
		}
	}


	/**
	 * Validates the transaction response data.
	 *
	 * Overridden to calculate and check the SHA-1 hash.
	 *
	 * @see Framework\SV_WC_Payment_Gateway_Hosted::validate_transaction_response()
	 *
	 * @since 2.0.0
	 *
	 * @param \WC_Order $order order object
	 * @param \WC_Realex_Redirect_API_HPP_Response $response response object
	 *
	 * @throws Framework\SV_WC_Payment_Gateway_Exception
	 */
	protected function validate_transaction_response( $order, $response ) {

		$this->validate_order_payment_status( $order, $response );

		$hash = $this->generate_sha1_hash( $response->get_hash_parameters(), $this->get_shared_secret() );

		if ( ! hash_equals( $hash, $response->get_hash() ) ) {
			throw new Framework\SV_WC_Payment_Gateway_Exception( 'Invalid response data.' );
		}

		// APM Status Responses do not include the payment amount
		if ( $response->has_amount() && ! $response->is_change_payment_order() ) {

			// if an existing customer with saved cards paid for the $0 order, the amount should be $0.02
			// TODO: remove this line and just use $order->payment_total once Realex's OTB bug is fixed {CW 2017-12-06}
			$amount = '0.00' === $order->payment_total && $order->customer_id ? '0.02' : $order->payment_total;

			// check that the response total matches the original order total
			if ( Framework\SV_WC_Helper::number_format( $response->get_amount() ) !== $amount ) {
				throw new Framework\SV_WC_Payment_Gateway_Exception( 'Transaction amount does not match order total.' );
			}
		}

		status_header( 200 );
	}


	/**
	 * Checks whether we can process the given response considering the payment status of the order.
	 *
	 * @since 2.4.0
	 *
	 * @param \WC_Order $order order object
	 * @param \WC_Realex_Redirect_API_HPP_Response $response response object
	 * @return void
	 * @throws Framework\SV_WC_Payment_Gateway_Exception
	 */
	private function validate_order_payment_status( $order, $response ) {

		// bail out If the order is for change payment
		if ( $response->is_change_payment_order() ) {
			return;
		}

		if ( $response instanceof Status_Response ) {

			// we allow APM Status Responses regardless of the current payment status of the order
			return;
		} elseif ( ! $order->needs_payment() && $order->has_status( 'cancelled' ) ) {

			// if the order got cancelled due to "hold stock" timeout settings and has order items still in stock
			$order_note = __( 'Payment has been placed on a cancelled / timed out order and the order items are out of stock. Please perform a refund or process the order.', 'woocommerce-gateway-realex-redirect' );

			if ( $this->has_order_items_in_stock( $order ) ) {
				$order_note = __( 'Payment has been placed on a cancelled / timed out order and the order items are still available in stock.', 'woocommerce-gateway-realex-redirect' );
			}
			$order->add_order_note( $order_note );

		} elseif ( ! $order->needs_payment() && $response instanceof Redirect_Response ) {

			// if the APM transaction is On Hold then we likely already received a Redirect Response for this order
			// if the APM transaction is paid, but not On Hold, it's likely that the Status Response was received first so we should redirect the user without adding a note
			if ( $order->has_status( 'on-hold' )) {
				$order->add_order_note(sprintf(
					/* translators: Placeholders: %s - payment gateway (Global Payments HPP) */
					esc_html__( '%s duplicate APM transaction received', 'woocommerce-gateway-realex-redirect' ),
					$this->get_method_title() )
				);
			}

			throw new Framework\SV_WC_Payment_Gateway_Exception(sprintf(
				/* translators: Placeholders: %s - order number */
				__( 'Order %s is already paid for.', 'woocommerce-plugin-framework' ),
				$order->get_order_number()
			));

		} else {

			// valid APM Redirect Response or a non-APM response
			parent::validate_transaction_response( $order, $response );
		}
	}


	/**
	 * Confirms if the line items (products) in an order are still in stock
	 *
	 * @since 3.0.4
	 *
	 * @param \WC_Order $order order object
	 * @return bool if the order items are out of stock
	 * @throws \Exception
	 */
	protected function has_order_items_in_stock( WC_Order $order ) {

		$line_items = Framework\SV_WC_Helper::get_order_line_items( $order );

		foreach ($line_items as $line_item) {

			$product = $line_item->product;

			if ( ! $product ) {
				continue;
			}

			if ( ! $product->managing_stock() || $product->backorders_allowed() ) {
				continue;
			}

			$stock_quantity = $line_item->product->get_stock_quantity();

			if ( $stock_quantity < $line_item->quantity ) {
				return false;
			}
		}

		return true;
	}


	/**
	 * Processes a transaction response's token data, if any.
	 *
	 * @since 2.0.0
	 *
	 * @param \WC_Order $order order object
	 * @param Framework\SV_WC_Payment_Gateway_Payment_Notification_Tokenization_Response $response response object
	 * @return \WC_Order order object
	 * @throws \Exception
	 */
	protected function process_tokenization_response( WC_Order $order, $response ) {

		$order = parent::process_tokenization_response( $order, $response );

		// if a new customer ref was created, update its Realex Entry based on order data
		if ( $response->customer_creation_successful() && $order->customer_id ) {
			try {
				$this->get_api()->update_payer($order->customer_id, [
					'first_name' => $order->get_billing_first_name( 'edit' ),
					'last_name'  => $order->get_billing_last_name( 'edit' ),
					'company'    => $order->get_billing_company( 'edit' ),
					'address_1'  => $order->get_billing_address_1( 'edit' ),
					'address_2'  => $order->get_billing_address_2( 'edit' ),
					'city'       => $order->get_billing_city( 'edit' ),
					'state'      => $order->get_billing_state( 'edit' ),
					'country'    => $order->get_billing_country( 'edit' ),
					'postcode'   => $order->get_billing_postcode( 'edit' ),
					'phone'      => $order->get_billing_phone( 'edit' ),
					'email'      => $order->get_billing_email( 'edit' ),
				]);
			} catch ( Framework\SV_WC_Plugin_Exception $e ) {
			}
		}

		return $order;
	}


	/**
	 * Adds the transaction data to the order.
	 *
	 * @since 2.0.0
	 *
	 * @param \WC_Order $order order object
	 * @param \WC_Realex_Redirect_API_HPP_Response $response response object
	 */
	public function add_payment_gateway_transaction_data( $order, $response ) {

		$this->update_order_meta( $order, 'realex_id', $response->get_order_number() );
		$this->update_order_meta( $order, 'payment_type', $response->get_payment_type() );

		if ( self::PAYMENT_TYPE_CREDIT_CARD === $response->get_payment_type() ) {
			$this->update_order_meta( $order, 'avs_result', $response->get_avs_result() );
			$this->update_order_meta( $order, 'avs_address_result', $response->get_avs_address_result() );
			$this->update_order_meta( $order, 'csc_result', $response->get_csc_result() );

			// add 3DSecure data if present
			if ( is_callable( [$response, 'is_3dsecure'] ) && $response->is_3dsecure() ) {
				$this->update_order_meta( $order, '3dsecure', 'yes' );

				$this->update_order_meta( $order, '3dsecure_eci', $response->get_eci() );
				$this->update_order_meta( $order, '3dsecure_cavv', $response->get_cavv() );
				$this->update_order_meta( $order, '3dsecure_xid', $response->get_xid() );
			}

			// add the DCC data if present
			if ( is_callable( [$response, 'get_dcc_ccp'] ) && $response->get_dcc_ccp() ) {
				$this->update_order_meta( $order, 'is_dcc', $response->is_dcc() );
				$this->update_order_meta( $order, 'dcc_ccp', $response->get_dcc_ccp() );
				$this->update_order_meta( $order, 'dcc_amount', $response->get_dcc_amount() );
				$this->update_order_meta( $order, 'dcc_currency', $response->get_dcc_currency() );
				$this->update_order_meta( $order, 'dcc_rate', $response->get_dcc_rate() );
			}

			// store data for APM transactions
		} elseif ( $response instanceof Redirect_Response && $payment_method = Alternative_Payment_Methods::get( $response->get_payment_type() ) ) {
			foreach ( $payment_method->get_transaction_data( $response ) as $key => $value ) {
				$this->update_order_meta( $order, $key, $value );
			}
		}

		// set the core transaction ID meta to the Realex "order ID" for easier reference
		if ( $response->get_order_number() && ! $response->is_change_payment_order() ) {
			// @NOTE: use the order CRUD method here, instead of the FW method, to avoid prefixing the meta key
			$order->set_transaction_id( $response->get_order_number() );
			$order->save();
		}
	}


	/**
	 * Gets the order note message for approved credit card transactions.
	 *
	 * This is completely overridden because Realex requires specific information
	 * and formatting for this note.
	 *
	 * @since 2.0.0
	 *
	 * @param \WC_Order $order order object
	 * @param \WC_Realex_Redirect_API_HPP_Credit_Card_Response $response response object
	 * @return string
	 */
	public function get_credit_card_transaction_approved_message( WC_Order $order, $response ) {

		$message = sprintf(
			/* translators: Placeholders: %1$s - payment method title, %2$s - environment ("Test"), %3$s - transaction type (authorization/charge) */
			__( '%1$s %2$s %3$s Approved', 'woocommerce-plugin-framework' ),
			$this->get_method_title(),
			$this->is_test_environment() ? esc_html_x( 'Test', 'noun, software environment', 'woocommerce-plugin-framework' ) : '',
			$this->perform_credit_card_authorization( $order ) ? esc_html_x( 'Authorization', 'credit card transaction type', 'woocommerce-plugin-framework' ) : esc_html_x( 'Charge', 'noun, credit card transaction type', 'woocommerce-plugin-framework' )
		).'<br />';

		// add the transaction result codes
		$message .= $this->get_transaction_result_message( $order, $response );

		return $message;
	}


	/**
	 * Gets the order note message for approved eCheck transactions.
	 *
	 * @since 5.0.0-dev.1
	 *
	 * @param \WC_Order $order order object
	 * @param WC_Realex_Redirect_API_HPP_Response $response response object
	 * @return string
	 */
	public function get_paypal_transaction_approved_message( WC_Order $order, WC_Realex_Redirect_API_HPP_Response $response ) {

		$last_four = ! empty( $order->payment->last_four ) ? $order->payment->last_four : substr( $order->payment->account_number, -4 );

		/* translators: Placeholders: %1$s - payment method title */
		$message = sprintf( __( '%1$s PayPal Transaction Approved', 'woocommerce-gateway-realex-redirect' ), $this->get_method_title() );

		// adds the transaction id (if any) to the order note
		if ( $response->get_transaction_id() ) {
			$message .= ' '.sprintf( esc_html__( '(Transaction ID %s)', 'woocommerce-gateway-realex-redirect' ), $response->get_transaction_id() );
		}

		/*
		 * Filters the order note message for an approved PayPal transaction.
		 *
		 * @since 2.0.0
		 *
		 * @param string $message order note
		 * @param \WC_Order $order order object
		 * @param \WC_Realex_Redirect_API_HPP_Response $response transaction response object
		 * @param \WC_Gateway_Realex_Redirect $this gateway object
		 */
		return apply_filters( 'wc_'.$this->get_id().'_paypal_transaction_approved_order_note', $message, $order, $response, $this );
	}


	/**
	 * Gets the order note message for approved APM transactions.
	 *
	 * @since 2.4.0
	 *
	 * @param \WC_Order $order order object
	 * @param \WC_Realex_Redirect_API_HPP_Response $response response object
	 * @param Alternative_Payment_Method $payment_method payment method object
	 * @return string
	 */
	public function get_apm_transaction_approved_message(WC_Order $order, WC_Realex_Redirect_API_HPP_Response $response, Alternative_Payment_Method $payment_method )  {

		$message = sprintf(
			/* translators: Placeholders: %1$s - payment method title, %2$s - environment ("Test"), %3$s - "Processed" or "Approved", %4$s - payment method name (e.g GiroPay) */
			__( '%1$s APM %2$s Transaction %3$s via %4$s', 'woocommerce-gateway-realex-redirect' ),
			$this->get_method_title(),
			$this->is_test_environment() ? esc_html_x( 'Test', 'noun, software environment', 'woocommerce-plugin-framework' ) : '',
			$response->transaction_held() ? esc_html__( 'Processed', 'woocommerce-gateway-realex-redirect' ) : esc_html__( 'Approved', 'woocommerce-gateway-realex-redirect' ),
			$payment_method->get_name()
		);

		// adds the transaction id (if any) to the order note
		if ( $response->get_transaction_id() ) {
			$message .= ' '.sprintf( esc_html__( '(Transaction ID %s)', 'woocommerce-gateway-realex-redirect' ), $response->get_transaction_id() );
		}

		/*
		 * Filters the order note message for an approved APM transaction.
		 *
		 * @since 2.4.0
		 *
		 * @param string $message order note
		 * @param \WC_Order $order order object
		 * @param \WC_Realex_Redirect_API_HPP_Response $response transaction response object
		 * @param Alternative_Payment_Method $payment_method payment method object
		 * @param \WC_Gateway_Realex_Redirect $this gateway object
		 */
		return apply_filters( 'wc_'.$this->get_id().'_apm_transaction_approved_order_note', $message, $order, $response, $payment_method, $this );
	}


	/**
	 * Handles an approved transaction response.
	 *
	 * @since 2.0.0
	 *
	 * @param \WC_Order $order order object
	 * @param \WC_Realex_Redirect_API_HPP_Response $response response object
	 */
	protected function do_transaction_approved( WC_Order $order, $response ) {

		if ( is_callable( [WC()->cart, 'empty_cart'] ) ) {
			WC()->cart->empty_cart();
		}

		$this->add_transaction_result_order_notes( $order, $response );

		if ( ! $response->is_change_payment_order() ) {
			$this->do_transaction_response_result( $response, $this->get_response_redirect_url( $order ) );
		} else {
			$query_params = ['hpp_change_payment' => $order->has_payment_gateway() ? 'payment_method_updated' : 'payment_method_added'];

			if ( $this->maybe_change_payment_method_for_all_subscription( $order, $response ) ) {
				$query_params['hpp_change_payment'] = 'updated_for_all_subscription';
			}

			$this->do_transaction_response_result( $response, add_query_arg( $query_params, wc_get_endpoint_url( 'view-subscription', $order->get_id(), wc_get_page_permalink( 'myaccount' ) ) ) );
		}
	}


	/**
	 * Handles a held transaction response.
	 *
	 * @since 2.0.0
	 *
	 * @param \WC_Order $order order object
	 * @param \WC_Realex_Redirect_API_HPP_Response $response response object
	 */
	protected function do_transaction_held( WC_Order $order, $response ) {
		if ( is_callable( [WC()->cart, 'empty_cart'] ) ) {
			WC()->cart->empty_cart();
		}

		$this->add_transaction_result_order_notes( $order, $response );

		if ( $response instanceof Redirect_Response && $response->should_wait_for_notification() ) {

			/**
			 * Filters the time the plugin waits for an APM Status Response to arrive before redirecting the customer.
			 *
			 * @since 2.0.0
			 *
			 * @param int $wait_time number of seconds to wait
			 * @param \WC_Order $order order object
			 * @param Redirect_Response $response response object
			 */
			$wait_time = apply_filters( 'wc_realex_redirect_apm_transaction_held_wait_time', 5, $order, $response );

			$redirect_url = $this->wait_for_status_response_and_get_redirect_url( $order, $wait_time );
		} else {
			$redirect_url = $this->get_response_redirect_url( $order );
		}

		$this->do_transaction_response_result( $response, $redirect_url );
	}


	/**
	 * Waits up to the specified number of seconds to see if the status of the order changes and gets the response redirect URL for the new status.
	 *
	 * Some Alternative Payment Methods may deliver the Status Response within five seconds after the user is redirected.
	 * This is an attempt to catch the confirmed payment status before we send the customer to the Order received page.
	 *
	 * @since 2.4.0
	 *
	 * @param \WC_Order $order order object
	 * @param int $wait_time number of seconds to wait
	 * @return string
	 */
	private function wait_for_status_response_and_get_redirect_url( $order, $wait_time = 5 ) {

		$start_time   = microtime( true );
		$redirect_url = $this->get_response_redirect_url( $order );

		do {
			sleep( 1 ); // wait 1 second

			// TODO: is there WooCommerce equivalent to clean the order cache? {WV 2020-0716}
			clean_post_cache( $order->get_id() );

			if ( ! $fresh_order = wc_get_order( $order->get_id() ) ) {
				continue;
			}

			if ( $fresh_order->has_status( 'on-hold' ) ) {
				continue;
			}

			$redirect_url = $this->get_response_redirect_url( $fresh_order );
			break;
		} while ( microtime( true ) - $start_time < $wait_time );

		return $redirect_url;
	}


	/**
	 * Gets the redirect URL based the order's status.
	 *
	 * @since 2.4.0
	 *
	 * @param \WC_Order $order order object
	 * @return string
	 */
	private function get_response_redirect_url( $order ) {

		if ( $order->has_status( 'failed' )) {
			$redirect_url = $order->get_checkout_payment_url();
		} elseif ( $order->has_status( 'cancelled' ) ) {
			$redirect_url = $order->get_cancel_order_url();
		} else {
			$redirect_url = $this->get_return_url( $order );
		}

		return $redirect_url;
	}


	/**
	 * Handles a cancelled transaction response.
	 *
	 * @since 2.0.0
	 *
	 * @param \WC_Order $order order object
	 * @param \WC_Realex_Redirect_API_HPP_Response $response response object
	 */
	protected function do_transaction_cancelled( WC_Order $order, $response ) {

		$this->add_transaction_result_order_notes( $order, $response );

		if ( ! $response->is_change_payment_order() ) {
			$this->do_transaction_response_result( $response, $this->get_response_redirect_url( $order ) );
		} else {
			$query_params = ['hpp_change_payment' => 'payment_method_failed'];

			$this->do_transaction_response_result( $response, add_query_arg( $query_params, wc_get_endpoint_url( 'view-subscription', $order->get_id(), wc_get_page_permalink( 'myaccount' ) ) ) );
		}
	}


	/**
	 * Handles a failed transaction response.
	 *
	 * @since 2.0.0
	 *
	 * @param \WC_Order $order order object
	 * @param \WC_Realex_Redirect_API_HPP_Response $response response object
	 */
	protected function do_transaction_failed( WC_Order $order, $response ) {

		$this->add_failed_order_message( $order, $response );

		$this->add_transaction_result_order_notes( $order, $response );

		if ( ! $response->is_change_payment_order() ) {
			$this->do_transaction_response_result( $response, $this->get_response_redirect_url( $order ) );
		} else {
			$query_params = ['hpp_change_payment' => 'payment_method_failed'];

			$this->do_transaction_response_result( $response, add_query_arg( $query_params, wc_get_endpoint_url( 'view-subscription', $order->get_id(), wc_get_page_permalink( 'myaccount' ) ) ) );
		}
	}


	/**
	 * Handles an invalid transaction response.
	 *
	 * @since 2.0.0
	 *
	 * @param null|\WC_Order $order order object
	 * @param null|\WC_Realex_Redirect_API_HPP_Response $response response object
	 */
	protected function do_invalid_transaction_response( $order = null, $response = null ) {

		if ( $order ) {

			// the order is paid and this might be a duplicate response, so we'll redirect to the Thank You page
			if ( ! $order->needs_payment() ) {
				$redirect_url = $this->get_return_url( $order );

			// if the order still needs payment, then we'll redirect back to the pay page
			} else {
				$this->add_failed_order_message( $order, $response );

				$redirect_url = $order->get_checkout_payment_url();
			}

			// unlikely, in case an order was never found and we have nowhere else to go
		} else {
			$redirect_url = home_url( '/' );
		}

		$this->do_transaction_response_result( $response, $redirect_url );
	}


	/**
	 * Adds the transaction result codes as order notes.
	 *
	 * @since 2.0.0
	 *
	 * @param \WC_Order $order order object
	 * @param \WC_Realex_Redirect_API_HPP_Response $response response object
	 */
	protected function add_transaction_result_order_notes( WC_Order $order, WC_Realex_Redirect_API_HPP_Response $response ) {

		if ( self::PAYMENT_TYPE_CREDIT_CARD === $response->get_payment_type() ) {

			// always add the validation (AVS & CSC) result codes
			$message = sprintf(
				/* translators: Placeholders: %s - validation result codes,  */
				__( 'Global Payments Validation Results:%s', 'woocommerce-gateway-realex-redirect' ),
				'<br />AVS: '.$response->get_avs_result().'<br />AVS Address: '.$response->get_avs_address_result().'<br />CSC: '.$response->get_csc_result()
			);

			$order->add_order_note( $message );

			// add an order note for the Fraud Filter result
			if ( is_callable( [$response, 'get_fraud_result'] ) ) {
				$message = sprintf(
					/* translators: Placeholders: %s - Fraud filter result code, such as "PASS" */
					__( 'Global Payments Fraud Filter Result: %s', 'woocommerce-gateway-realex-redirect' ),
					( $response->get_fraud_result() ) ? $response->get_fraud_result() : __( 'N/A', 'woocommerce-gateway-realex-redirect' )
				);

				$order->add_order_note( $message );
			}

			// add 3DSecure data if present
			if ( is_callable( [$response, 'is_3dsecure'] ) && $response->is_3dsecure() ) {
				$message = sprintf(
					/* translators: Placeholders: %s - 3DSecure result codes,  */
					__( 'Global Payments 3DSecure Results:%s', 'woocommerce-gateway-realex-redirect' ),
					'<br />ECI: '.$response->get_eci().'<br />CAVV: '.$response->get_cavv().'<br />XID: '.$response->get_xid()
				);

				$order->add_order_note( $message );
			}

			// add the DCC data if present
			if ( is_callable( [$response, 'is_dcc'] ) && $response->is_dcc() ) {
				$message = sprintf(
					/* translators: Placeholders: %1$s - payment total, %2$s - currency code, such as USD */
					__( 'Global Payments customer chose to pay a converted total of %1$s %2$s', 'woocommerce-gateway-realex-redirect' ),
					wc_price( $response->get_dcc_amount(), ['currency' => $response->get_dcc_currency()] ),
					$response->get_dcc_currency()
				);

				$order->add_order_note( $message );
			}
		}
	}


	/**
	 * Adds a failed order message to the session for display later.
	 *
	 * @since 2.0.0
	 *
	 * @param null|\WC_Order $order order object
	 * @param null|Framework\SV_WC_Payment_Gateway_API_Payment_Notification_Response $response response object
	 */
	protected function add_failed_order_message( $order = null, $response = null ) {
		if ($order) {
			$message = $response->get_user_message();

			if ( ! $message || ! $this->is_detailed_customer_decline_messages_enabled() ) {
				$message = __( 'The transaction was unsuccessful, please try again or use an alternate form of payment.', 'woocommerce-gateway-realex-redirect' );
			}

			$this->update_order_meta( $order, 'failed_message', $message );
		}
	}


	/**
	 * Handles the final action after the response is processed.
	 *
	 * For an IPN, this means sending a redirect URL via JSON. For a redirect
	 * response, this means a straight redirect.
	 *
	 * @since 2.0.0
	 *
	 * @param \WC_Realex_Redirect_API_HPP_Response $response response object
	 * @param string $redirect_url the final destination after processing
	 */
	protected function do_transaction_response_result( WC_Realex_Redirect_API_HPP_Response $response, $redirect_url ) {

		$this->output_response_redirect_js( $redirect_url );
		exit;
	}


	/**
	 * Renders the JS for handling the redirect after a payment response is posted by the HPP.
	 *
	 * @since 2.0.0
	 *
	 * @param string $url the URL to which to redirect
	 */
	protected function output_response_redirect_js( $url ) {

		echo '<script type="text/javascript">window.top.location.href = "'.esc_url_raw( $url ).'"</script>';
	}


	/**
	 * Changes the thank you order received text for On hold orders processed via APM.
	 *
	 * @since 2.4.0
	 *
	 * @param string $text thank you order received text
	 * @param \WC_Order $order order object
	 */
	public function maybe_change_thankyou_order_received_text( $text, $order ) {

		if ( $order instanceof \WC_Order && $order->has_status( 'on-hold' ) && Alternative_Payment_Methods::get( $this->get_order_meta( $order, 'payment_type' ) ) ) {
			$text = esc_html__( 'Thank you. Your order has been received and is awaiting payment confirmation from your selected payment provider.', 'woocommerce-gateway-realex-redirect' );
		}

		return $text;
	}


	/**
	 * Adds a failed order notice (such as a decline) if one exists.
	 *
	 * @internal
	 *
	 * @since 2.0.0
	 */
	public function maybe_add_failed_order_notice() {

		$order = wc_get_order( $this->get_checkout_pay_page_order_id() );

		if ( ! $order ) {
			return;
		}

		if ( $message = $this->get_order_meta( $order, 'failed_message' ) ) {

			Framework\SV_WC_Helper::wc_add_notice( $message, 'error' );

			$this->delete_order_meta( $order, 'failed_message' );
		}
	}


	/**
	 * Marks the given order as failed and set the order note.
	 *
	 * @since 2.0.0
	 *
	 * @param WC_Order $order the order
	 * @param string $error_message a message to display inside the "Payment Failed" order note
	 * @param Framework\SV_WC_Payment_Gateway_API_Response optional $response the transaction response object
	 */
	public function mark_order_as_failed( $order, $error_message, $response = null ) {

		/* translators: Placeholders: %s - payment gateway title */
		$message = sprintf( esc_html__( '%s Payment Failed:', 'woocommerce-plugin-framework' ), $this->get_method_title() ).'<br />';

		if ( $this->get_transaction_result_message( $order, $response ) ) {
			$message .= $this->get_transaction_result_message( $order, $response );
		} else {
			$message .= $error_message;
		}

		if ( ! $this->order_has_note( $order, $message ) ) {
			$order->add_order_note( $message );
		}

		$order->update_status( 'failed' );

		$this->add_debug_message( $error_message, 'error' );
	}


	/**
	 * Determines whether the given order already has a note with the given content.
	 *
	 * TODO: find a better way to prevent two responses for the same order from adding duplicate notes {WV 2020-07-20}
	 *
	 * @since 2.4.0
	 *
	 * @param \WC_Order $order order object
	 * @param string $content note content
	 * @return bool
	 */
	private function order_has_note( $order, $content ) {

		$has_note = false;

		$notes = wc_get_order_notes( ['order_id' => $order->get_id()] );

		if ( is_array( $notes ) ) {
			foreach ( $notes as $note ) {
				if ( 0 === strcmp( $note->content, $content ) ) {
					$has_note = true;
					break;
				}
			}
		}

		return $has_note;
	}


	/**
	 * Gets the message listing all of the transaction details.
	 *
	 * This is added to the order note, regardless of the outcome.
	 *
	 * @since 2.0.0
	 *
	 * @param \WC_Order $order order object
	 * @param Framework\SV_WC_Payment_Gateway_API_Response $response response object
	 * @return string
	 */
	protected function get_transaction_result_message( WC_Order $order, $response ) {

		$message = '';

		if ( ! $response ) {
			return $message;
		}

		/* translators: Placeholders: %s - Realex order ID */
		$message = sprintf( __( 'Order ID: %s', 'woocommerce-gateway-realex-redirect' ), $response->get_order_number() ).'<br />';
		/* translators: Placeholders: %s - Realex transaction result code */
		$message .= sprintf( __( 'Result: %s', 'woocommerce-gateway-realex-redirect' ), $response->get_status_code() ).'<br />';
		/* translators: Placeholders: %s - Realex transaction result message */
		$message .= sprintf( __( 'Message: %s', 'woocommerce-gateway-realex-redirect' ), $response->get_status_message() ).'<br />';

		// APM transaction responses don't include an authorization code
		if ( $authorization_code = $response->get_authorization_code() ) {
			/* translators: Placeholders: %s - Realex transaction authorization code */
			$message .= sprintf( __( 'Auth Code: %s', 'woocommerce-gateway-realex-redirect' ), $authorization_code ).'<br />';
		}

		if ( self::PAYMENT_TYPE_CREDIT_CARD === $response->get_payment_type() ) {
			if ($order->payment->card_type) {
				/* translators: Placeholders: %s - Credit card type, such as Visa or MasterCard */
				$message .= sprintf( __( 'Card Type: %s', 'woocommerce-gateway-realex-redirect' ), Framework\SV_WC_Payment_Gateway_Helper::payment_type_to_name( $order->payment->card_type ) ).'<br />';
			}

			if ( $order->payment->account_number ) {
				/* translators: Placeholders: %s - Credit card type, such as Visa or MasterCard */
				$message .= sprintf( __( 'Card Digits: %s', 'woocommerce-gateway-realex-redirect' ), $order->payment->account_number ).'<br />';
			}

			if ( $response->get_cardholder_name() ) {
				/* translators: Placeholders: %s - Credit card holder's name */
				$message .= sprintf( __( 'Card Name: %s', 'woocommerce-gateway-realex-redirect' ), $response->get_cardholder_name() ).'<br />';
			}
		}

		return $message;
	}


	/**
	 * Don't empty the cart until successful payment is made.
	 *
	 * @since 2.0.0
	 *
	 * @return bool
	 */
	protected function empty_cart_before_redirect() {
		return false;
	}


	/**
	 * Gets the payment URL: the checkout pay page.
	 *
	 * @since 3.0.3
	 * @param int $order_id the order id
	 * @return string the payment URL, or false if unavailable
	 */
	protected function get_payment_url( $order_id ) {

		if ( $this->use_form_post() ) {
			// the checkout pay page
			$order        = wc_get_order( $order_id );
			$query_params = [];

			// If request is for change payment method
			if ( isset( $_REQUEST['change_payment_method'] ) ) {
				$query_params['gp_change_payment'] = $_REQUEST['change_payment_method'];
			}

			// request for update user all subscription payment method
			if ( isset( $_REQUEST['update_all_subscriptions_payment_method'] ) ) {
				$query_params['update_all_subscriptions_pm'] = $_REQUEST['update_all_subscriptions_payment_method'];
			}

			return esc_url_raw( add_query_arg( $query_params, $order->get_checkout_payment_url( true ) ) );
		} else {

			// setup the order object
			$order = $this->get_order( $order_id );

			// direct-redirect, so append the hosted pay page params to the hosted pay page url
			$pay_page_url = $this->get_hosted_pay_page_url( $order );

			if ( $pay_page_url ) {
				return add_query_arg( $this->get_hosted_pay_page_params( $order ), $pay_page_url );
			}
		}

		return false;
	}


	/**
	 * Renders the iframe pay page form.
	 *
	 * @since 2.0.0
	 *
	 * @param \WC_Order $order order object
	 * @param array $request_params key/value request params
	 */
	public function render_pay_page_form( $order, $request_params ) {

		$plugin_id = $this->get_id();

		// attempt to automatically submit the form and load the iframe
		// TODO: standalone JS file
		wc_enqueue_js('

			$( "body" ).block( {
				message: "",
				overlayCSS: {
					background: "#fff",
					opacity: 0.6
				},
			} );

			$( "#wc_'.esc_js( $plugin_id ).'_iframe_form" ).submit();

			$("#wc_'.esc_js( $plugin_id ).'_iframe").load( function() {

				// HACK: Disable scroll to top on iframe load
				
				$( "body" ).unblock();

			} );

			$( window ).on( "message", function( event ) {

				var origin       = $( "<a>" ).prop( "href", event.originalEvent.origin ).prop( "hostname" );
				var valid_origin = $( "<a>" ).prop( "href", "'.esc_js( $this->get_hosted_pay_page_url() ).'" ).prop( "hostname" );
				var response     = JSON.parse( event.originalEvent.data );

				if ( origin !== valid_origin ) {
					return;
				}

				if ( response && response.iframe ) {
					$( "#wc_'.esc_js( $plugin_id ).'_iframe" ).attr( "height", response.iframe.height );
				}

			} );
		');

		echo '<form id="wc_'.esc_attr( $plugin_id ).'_iframe_form" action="'.esc_url( $this->get_hosted_pay_page_url( $order ) ).'" method="post" target="wc_'.esc_attr( $plugin_id ).'_iframe">';
		echo $this->get_auto_post_form_params_html( $request_params );
		echo '</form>';

		echo '<iframe id="wc_'.esc_attr( $plugin_id ).'_iframe" name="wc_'.esc_attr( $plugin_id ).'_iframe" width="100%" height="360px"></iframe>';

		echo '<a id="wc_'.esc_attr( $plugin_id ).'_iframe_cancel" class="button cancel" href="'.esc_url( $order->get_checkout_payment_url() ).'">'.esc_html__( 'Cancel', 'woocommerce-gateway-realex-redirect' ).'</a>';
	}


	/**
	 * Determines whether to use the auto-post form.
	 *
	 * @since 2.0.0
	 *
	 * @return bool
	 */
	public function use_auto_form_post() {

		return parent::use_auto_form_post() && self::FORM_TYPE_IFRAME !== $this->get_form_type();
	}


	/**
	 * Gets the hosted pay page parameters.
	 *
	 * @since 2.0.0
	 *
	 * @param \WC_Order $order order object
	 * @return array
	 */
	public function get_hosted_pay_page_params( $order ) {

		$params = [
			'TIMESTAMP'                => date( 'Ymdhis' ),
			'MERCHANT_ID'              => $this->get_merchant_id(),
			'ORDER_ID'                 => Framework\SV_WC_Helper::str_truncate( preg_replace( '/[^A-Za-z0-9-_]*/', '', $order->unique_transaction_ref ), 40, '' ),
			'X_ORDER_ID'               => $order->get_id(),
			'AMOUNT'                   => $order->payment_total * 100,
			'CURRENCY'                 => $order->get_currency(),
			'AUTO_SETTLE_FLAG'         => $this->perform_credit_card_authorization( $order ) && $this->is_partial_capture_enabled() ? 'MULTI' : (int) $this->perform_credit_card_charge( $order ),
			'CHANNEL'                  => 'ECOM',
			'COMMENT1'                 => Framework\SV_WC_Helper::str_truncate( preg_replace( '/[^A-Za-z0-9-_+ \'".,]*/', '', $order->description ), 255 ),
			'BILLING_CO'               => $order->get_billing_country( 'edit' ),
			'HPP_LANG'                 => $this->get_form_language(),
			'HPP_VERSION'              => self::HPP_VERSION,
			'HPP_DISPLAY_CVN'          => $this->csc_enabled() ? 'true' : 'false', // yes, this must be a string
			'MERCHANT_RESPONSE_URL'    => $this->get_transaction_response_handler_url(),
		];

		// If rendering payment form for subscription change payment method
		if ( isset( $_REQUEST['gp_change_payment'] ) ) {
			$params['ORDER_ID']                  = sprintf( '%s-%s-%s', 'CPM', Framework\SV_WC_Helper::str_truncate( preg_replace( '/[^A-Za-z0-9-_]*/', '', $order->unique_transaction_ref ), 40, '' ), rand( 100, 10000 ) );
			$params['HPP_CHANGE_PAYMENT_METHOD'] = 'true';
		}

		// add the customer params
		$params = array_merge( $params, Payment_Form_Helper::get_customer_params( $order ) );

		// this indicates whether the customer specified "use billing" at checkout
		$ship_to_different_address = 'yes' === $this->get_order_meta( $order, 'ship_to_different_address' );

		// update all subscription for change payment method

		if ( isset( $_REQUEST['update_all_subscriptions_pm'] ) ) {
			$params['UPDATE_ALL_SUBSCRIPTIONS_PM'] = 'true';
		}

		// add the address params
		$params = array_merge( $params, Payment_Form_Helper::get_address_params( $order, $ship_to_different_address ) );

		if ( $subaccount = $this->get_subaccount() ) {
			$params['ACCOUNT'] = $subaccount;
		}

		// add the AVS params if enabled
		if ( $this->is_avs_enabled() ) {
			$params = array_merge( $params, $this->get_hpp_avs_params( $order ) );
		}

		// add the tokenization params if enabled
		if ( $this->tokenization_enabled() ) {
			$params = array_merge( $params, $this->get_hpp_tokenization_params( $order ) );
		}

		// add the APM params if enabled
		if ( $this->is_apm_enabled() ) {
			$params = $this->add_apm_params( $params, $order );
		}

		if ( is_user_logged_in() ) {
			$params['CUST_NUM'] = get_current_user_id();
		}

		// if configured to be an iframe, add the response URLs for JS events
		if ( self::FORM_TYPE_IFRAME === $this->get_form_type() ) {
			$params['HPP_POST_DIMENSIONS'] = home_url();
		}

		/**
		 * Filters the button text displayed in the hosted payment form.
		 *
		 * The default for this filter is intentionally empty to let Realex
		 * determine the button text based on the account language defaults.
		 *
		 * @since 2.0.0
		 *
		 * @param string $text button text
		 * @param array $params payment form request parameters
		 * @param \WC_Order $order order object
		 */
		$button_text = apply_filters( 'wc_realex_redirect_hosted_payment_form_button_text', '', $params, $order );

		if ( $button_text ) {
			$params['CARD_PAYMENT_BUTTON'] = $button_text;
		}

		/**
		 * Filters the hosted payment form parameters.
		 *
		 * @since 2.0.0
		 *
		 * @param array $params payment form request parameters
		 * @param \WC_Order $order order object
		 */
		$params = apply_filters( 'wc_realex_redirect_hpp_params', $params, $order );

		$hash_params = [
			$params['TIMESTAMP'],
			$params['MERCHANT_ID'],
			$params['ORDER_ID'],
			$params['AMOUNT'],
			$params['CURRENCY'],
		];

		if ( $this->tokenization_enabled() && ( isset( $params['CARD_STORAGE_ENABLE'] ) || isset( $params['PAYER_EXIST'] ) ) ) {
			$hash_params[] = isset( $params['HPP_SELECT_STORED_CARD'] ) ? $params['HPP_SELECT_STORED_CARD'] : $params['PAYER_REF'];
			$hash_params[] = ''; // intentionally blank
		}

		$hash_params[] = $params['HPP_DISPLAY_CVN'];

		$params['SHA1HASH'] = $this->generate_sha1_hash( $hash_params, $this->get_shared_secret() );

		return $params;
	}


	/**
	 * Gets the params used for AVS checking.
	 *
	 * @since 2.0.0
	 *
	 * @param \WC_Order $order order object
	 * @return array
	 */
	protected function get_hpp_avs_params( WC_Order $order ) {

		$params = [];

		$supported_countries = [
			'US',
			'CA',
			'GB',
		];

		$billing_country = $order->get_billing_country( 'edit' );

		if ( in_array( $billing_country, $supported_countries, true ) ) {

			// format the postcode, strip all but numbers
			$postcode = preg_replace( '/[^0-9]/', '', $order->get_billing_postcode( 'edit' ) );
			$postcode = Framework\SV_WC_Helper::str_truncate( $postcode, 5, '' );

			$address = $order->get_billing_address_1( 'edit' );

			// Non-US addresses should be numbers only, maximum of 5 characters
			if ( 'US' !== $billing_country ) {
				$address = Framework\SV_WC_Helper::str_truncate( preg_replace( '/[^0-9]/', '', $address ), 5, '' );
			} else {
				$address = preg_replace( '/[^A-Za-z0-9-_+ "]*/', '', $address );
			}

			// API max (30) minus the formatted postcode & separator
			$address_max_length = 30 - strlen( $postcode ) - 1;

			$address = Framework\SV_WC_Helper::str_truncate( $address, $address_max_length, '' );

			$params['BILLING_CODE'] = "{$postcode}|{$address}";
		}

		return $params;
	}


	/**
	 * Gets the HPP params used for tokenization.
	 *
	 * @since 2.0.0
	 *
	 * @param \WC_Order $order order object
	 * @return array
	 */
	protected function get_hpp_tokenization_params( WC_Order $order ) {

		$order_id = $order->get_id();
		$params   = [];

		$contains_subscription = $this->get_plugin()->is_subscriptions_active() && wcs_order_contains_subscription( $order_id );
		$contains_pre_order    = $this->get_plugin()->is_pre_orders_active() && \WC_Pre_Orders_Order::order_contains_pre_order( $order_id ) && \WC_Pre_Orders_Product::product_is_charged_upon_release( \WC_Pre_Orders_Order::get_pre_order_product( $order_id ) );

		if ( $order->get_user_id() || $contains_subscription || $contains_pre_order ) {
			$params = [
				'OFFER_SAVE_CARD' => (int) ! $this->is_tokenization_forced(),
				'PAYER_EXIST'     => 0,
				'PAYER_REF'       => '',
			];

			if ( $order->customer_id ) {
				$params['PAYER_EXIST']            = 1;
				$params['HPP_SELECT_STORED_CARD'] = $order->customer_id;
			} else {
				$params['CARD_STORAGE_ENABLE'] = 1;
			}

			if ( '0.00' === $order->payment_total ) {
				if (! empty( $params['PAYER_EXIST'] )) { // TODO: remove this check and just set VALIDATE_CARD_ONLY once Realex's OTB bug is fixed {CW 2017-12-06}
					$params['AUTO_SETTLE_FLAG'] = 0;
					$params['AMOUNT']           = 2; // authorize for two pennies
				} else {
					$params['VALIDATE_CARD_ONLY'] = 1;
				}
			}

			if ( $contains_subscription || $contains_pre_order ) {
				$params['OFFER_SAVE_CARD'] = 0;
				$params['PM_METHODS']      = 'cards'; // only allow credit cards for force-tokenized orders
			}
		}

		return $params;
	}


	/**
	 * Updates the HPP params with params used for Alternative Payment Methods.
	 *
	 * No additional params are added if PM_METHODS was defined elsewhere or when the transaction type is set to Authorization.
	 *
	 * @since 2.4.0
	 *
	 * @param array $params hosted page params
	 * @param \WC_Order $order order object
	 * @return array
	 */
	private function add_apm_params( $params, WC_Order $order ) {

		if ( ! isset( $params['PM_METHODS'] ) && isset( $params['AUTO_SETTLE_FLAG'] ) && 1 === $params['AUTO_SETTLE_FLAG'] ) {
			$params['HPP_CUSTOMER_COUNTRY'] = $order->get_billing_country( 'edit' );
			$params['HPP_TX_STATUS_URL']    = $this->get_apm_status_response_handler_url( $order );
			$params['PM_METHODS']           = implode( '|', $this->get_apm_methods_to_request( $order ) );
		}

		return $params;
	}


	/**
	 * Gets the URL for the APM Status Response.
	 *
	 * @since 2.4.0
	 *
	 * @param \WC_Order $order order object
	 * @return string
	 */
	private function get_apm_status_response_handler_url( WC_Order $order ) {

		return add_query_arg(
			[
				// used to differentiate between a Redirect Response and a Status Response
				'status_update' => 'yes',
				// the Status Response URL does not include the X_ORDER_ID parameter
				'x_order_id'    => $order->get_id(),
			],
			$this->get_transaction_response_handler_url()
		);
	}


	/**
	 * Gets a list of payment methods to request.
	 *
	 * @since 2.4.0
	 *
	 * @param \WC_Order $order order object
	 * @return array
	 */
	private function get_apm_methods_to_request( WC_Order $order ) {

		$params = ['cards'];

		if ( $this->is_test_environment() ) {
			$params[] = 'testpay';
		}

		// get selected payment methods that support the order's currency
		$payment_methods = Alternative_Payment_Methods::find( [
			'ids'      => $this->get_available_payment_methods(),
			'currency' => $order->get_currency(),
		] );

		foreach ( $payment_methods as $payment_method ) {
			$params[] = $payment_method->get_id();
		}

		return $params;
	}


	/**
	 * Generates a SHA-1 hash for some payment params.
	 *
	 * @since 2.0.0
	 *
	 * @param array $params payment params to build the hash
	 * @param string $secret shared account secret
	 * @return string
	 */
	public static function generate_sha1_hash( $params, $secret ) {
		$hash = sha1( implode( '.', $params ) );

		return sha1( $hash.'.'.$secret );
	}


	/**
	 * Gets the hosted pay page url to redirect to.
	 *
	 * This method may be called more than once during a single request.
	 *
	 * @see Framework\SV_WC_Payment_Gateway_Hosted::get_hosted_pay_page_params()
	 *
	 * @since 2.0.0
	 *
	 * @param \WC_Order $order order object
	 * @return string
	 */
	public function get_hosted_pay_page_url( $order = null ) {

		if ( self::ENVIRONMENT_TEST === $this->get_environment() ) {
			$url = ( $this->test_form_url ) ? $this->test_form_url : self::HPP_URL_TEST;
		} else {
			$url = ( $this->form_url ) ? $this->form_url : self::HPP_URL;
		}

		/*
		 * Filters the Hosted Payment Page URL.
		 *
		 * @since 2.0.0
		 *
		 * @param string $url HPP URL
		 * @param \WC_Gateway_Realex_Redirect $gateway gateway object
		 */
		return trim( apply_filters( 'wc_realex_redirect_hpp_endpoint_url', $url, $this ) );
	}


	/**
	 * Logs a hosted pay page request.
	 *
	 * @since 2.2.0
	 *
	 * @param array $request request data to log
	 */
	public function log_hosted_pay_page_request( $request ) {

		// mask the calculated hash
		if ( ! empty( $request['body'] ) && preg_match( '/\[SHA1HASH] => \w+/', $request['body'], $matches ) ) {
			$hash = ! empty( $matches[0] ) ? ltrim( $matches[0], '[SHA1HASH] => ' ) : '';

			$request['body'] = preg_replace( '/\[SHA1HASH] => \w+/', '[SHA1HASH] => '.str_repeat( '*', strlen( $hash ) ), $request['body'] );
		}

		parent::log_hosted_pay_page_request( $request );
	}


	/**
	 * Gets an API response object based on response request data.
	 *
	 * @since 2.0.0
	 *
	 * @param array $request_response_data request response data
	 * @return Framework\SV_WC_Payment_Gateway_API_Payment_Notification_Response response object
	 */
	protected function get_transaction_response( $request_response_data ) {

		if ( $payment_method = $this->get_response_payment_method( $request_response_data ) ) {
			if ( self::PAYMENT_TYPE_PAYPAL === $payment_method ) {
				$class = 'WC_Realex_Redirect_API_HPP_PayPal_Response';
			} elseif ( Alternative_Payment_Methods::get( $payment_method ) ) {
				$class = isset( $request_response_data['status_update'] ) ? Status_Response::class : Redirect_Response::class;
			} else {
				$class = 'WC_Realex_Redirect_API_HPP_Response';
			}
		} else {
			$class = $this->tokenization_enabled() ? 'WC_Realex_Redirect_API_HPP_Saved_Card_Response' : 'WC_Realex_Redirect_API_HPP_Credit_Card_Response';
		}

		return new $class( $request_response_data );
	}


	/**
	 * Gets the payment method from the given response request data.
	 *
	 * @since 2.4.0
	 *
	 * @param array $data response request data
	 * @return string
	 */
	private function get_response_payment_method( $data ) {

		if ( isset( $data['PAYMENTMETHOD'] ) ) {
			$payment_method = $data['PAYMENTMETHOD'];
		} elseif ( isset( $data['paymentmethod'] ) ) {
			$payment_method = $data['paymentmethod'];
		} else {
			$payment_method = '';
		}

		return $payment_method;
	}


	/**
	 * Gets the order object with transaction data added.
	 *
	 * @since 2.0.0
	 *
	 * @param int|\WC_Order $order_id order ID or object
	 * @return \WC_Order
	 */
	public function get_order( $order_id ) {

		$order = parent::get_order( $order_id );

		$order->payment->multisettle = $this->is_partial_capture_enabled();

		// set the customer's intent
		if ( isset( $_POST['ship_to_different_address'] ) || ( is_checkout() && ! is_checkout_pay_page() ) ) {
			$this->update_order_meta( $order, 'ship_to_different_address', isset( $_POST['ship_to_different_address'] ) ? 'yes' : 'no' );
		}

		return $order;
	}


	/**
	 * Completes an order payment.
	 *
	 * This method marks the order with an appropriate status, and adds a relevant order note.
	 *
	 * @see Framework\SV_WC_Payment_Gateway::complete_payment()
	 *
	 * @since 2.4.0
	 *
	 * @param \WC_Order $order order object
	 * @param Framework\SV_WC_Payment_Gateway_API_Response $response response object
	 * @throws \Exception
	 */
	protected function complete_payment( WC_Order $order, Framework\SV_WC_Payment_Gateway_API_Response $response ) {

		// If the order for change payment, bail
		if ( $response->is_change_payment_order() ) {
			return;
		}

		if ( $payment_method = Alternative_Payment_Methods::get( $response->get_payment_type() ) ) {
			$order->add_order_note( $this->get_apm_transaction_approved_message( $order, $response, $payment_method ) );
		}

		parent::complete_payment( $order, $response );
	}


	/** Capture Methods *******************************************************/


	/**
	 * Initializes the capture handler instance.
	 *
	 * @since 2.1.2
	 */
	public function init_capture_handler() {

		require_once $this->get_plugin()->get_plugin_path().'/src/Capture.php';

		$this->capture_handler = new \SkyVerge\WooCommerce\Realex_HPP\Capture( $this );
	}


	/**
	 * Gets the order object, prepared for a capture transaction.
	 *
	 * @see Framework\SV_WC_Payment_Gateway::get_order_for_capture()
	 *
	 * @since 2.0.0
	 *
	 * @param \WC_Order|int $order order being processed
	 * @param float $amount amount to capture
	 * @return \WC_Order
	 */
	public function get_order_for_capture( $order, $amount = null ) {

		$order = parent::get_order_for_capture( $order, $amount );

		$order->capture->multi              = $this->is_partial_capture_enabled();
		$order->capture->realex_id          = $this->get_order_meta( $order, 'realex_id' );
		$order->capture->authorization_code = $this->get_order_meta( $order, 'authorization_code' );

		return $order;
	}


	/** Refund Methods ********************************************************/


	/**
	 * Gets the order object, prepared for a refund transaction.
	 *
	 * @see Framework\SV_WC_Payment_Gateway::get_order_for_refund()
	 *
	 * @since 2.0.0
	 *
	 * @param \WC_Order|int $order order being processed
	 * @param float $amount refund amount
	 * @param string $reason optional refund reason text
	 * @return \WC_Order|\WP_Error
	 */
	protected function get_order_for_refund( $order, $amount, $reason ) {

		// only credit card transactions support automatic refunds for now
		if ( self::PAYMENT_TYPE_CREDIT_CARD !== $this->get_order_meta( $order, 'payment_type' ) && ! Alternative_Payment_Methods::get( $this->get_order_meta( $order, 'payment_type' ) ) ) {
			$message = sprintf(
				/* translators: Placeholders: %s - a payment type name, such as Credit Card or PayPal */
				__( 'Automatic refunds are not supported for %s transactions. Please refund manually.', 'woocommerce-gateway-realex-redirect' ),
				Framework\SV_WC_Payment_Gateway_Helper::payment_type_to_name( $this->get_order_meta( $order, 'payment_type' ) )
			);

			return new WP_Error( 'wc_'.$this->get_id().'_refund_unsupported_payment_type', $message );
		}

		if ( 0 == $amount ) {
			return new WP_Error( 'wc_'.$this->get_id().'_refund_invalid_amount', __( 'Please enter a valid refund amount.', 'woocommerce-gateway-realex-redirect' ) );
		}

		$order = parent::get_order_for_refund( $order, $amount, $reason );

		$order->refund->realex_id = $this->get_order_meta( $order, 'realex_id' );

		// if this is a multisettle transaction that has been captured, use that transaction ID
		if ( $this->is_partial_capture_enabled() && $capture_ref = $this->get_order_meta( $order, 'capture_trans_id' ) ) {
			$order->refund->trans_id = $capture_ref;

			$order->refund->realex_id = "_multisettle_{$order->refund->realex_id}";
		}

		// add DCC data to the refund object if the order has DCC meta data
		if ( $this->get_order_meta( $order, 'dcc_amount' ) ) {
			$order = $this->add_dcc_data_for_refund( $order );
		}

		if ( $payment_method = Alternative_Payment_Methods::get( $this->get_order_meta( $order, 'payment_type' ) ) ) {
			$order->refund->payment_method = $payment_method->get_id();
			$order->refund->password       = $this->get_refund_password();
		} else {
			$order->refund->authorization_code = $this->get_order_meta( $order, 'authorization_code' );
			$order->refund->password           = $this->get_rebate_password();
		}

		return $order;
	}


	/**
	 * Add DCC data to an order that is being prepared for a refund transaction.
	 *
	 * @since 2.5.2-dev.2
	 *
	 * @param \WC_Order $order order object
	 * @return \WC_Order
	 */
	private function add_dcc_data_for_refund( $order ) {

		if ( $this->get_order_meta( $order, 'dcc_amount' ) && $this->get_order_meta( $order, 'dcc_currency' ) ) {
			$order->refund->is_dcc       = $this->get_order_meta( $order, 'is_dcc' );
			$order->refund->dcc_ccp      = $this->get_order_meta( $order, 'dcc_ccp' );
			$order->refund->dcc_amount   = $this->get_order_meta( $order, 'dcc_amount' );
			$order->refund->dcc_currency = $this->get_order_meta( $order, 'dcc_currency' );
			$order->refund->dcc_rate     = $this->get_order_meta( $order, 'dcc_rate' );
		}

		return $order;
	}


	/**
	 * Gets the direct API instance.
	 *
	 * @since 2.0.0
	 *
	 * @return \WC_Realex_Redirect_API
	 */
	public function get_api() {

		if ( ! $this->api ) {
			require_once $this->get_plugin()->get_plugin_path().'/src/api/class-wc-realex-redirect-api.php';

			// request classes
			require_once $this->get_plugin()->get_plugin_path().'/src/api/requests/class-wc-realex-redirect-api-request.php';
			require_once $this->get_plugin()->get_plugin_path().'/src/api/requests/class-wc-realex-redirect-api-payment-request.php';
			require_once $this->get_plugin()->get_plugin_path().'/src/api/requests/class-wc-realex-redirect-api-transaction-request.php';
			require_once $this->get_plugin()->get_plugin_path().'/src/api/requests/class-wc-realex-redirect-api-stored-card-request.php';
			require_once $this->get_plugin()->get_plugin_path().'/src/api/APM/Payment_Credit_Request.php';

			// response classes
			require_once $this->get_plugin()->get_plugin_path().'/src/api/responses/class-wc-realex-redirect-api-response.php';
			require_once $this->get_plugin()->get_plugin_path().'/src/api/responses/class-wc-realex-redirect-api-payment-response.php';
			require_once $this->get_plugin()->get_plugin_path().'/src/api/responses/class-wc-realex-redirect-api-transaction-response.php';
			require_once $this->get_plugin()->get_plugin_path().'/src/api/responses/class-wc-realex-redirect-api-stored-card-response.php';

			$this->api = new WC_Realex_Redirect_API( $this->get_environment(), $this->get_merchant_id(), $this->get_subaccount(), $this->get_shared_secret() );
		}

		return $this->api;
	}


	/**
	 * Gets a user's customer ID.
	 *
	 * @see Framework\SV_WC_Payment_Gateway::get_customer_id()
	 *
	 * @since 2.0.0
	 *
	 * @param int $user_id WordPress user ID
	 * @param array $args arguments
	 * @return string
	 */
	public function get_customer_id( $user_id, $args = [] ) {

		$args['autocreate'] = false;

		return parent::get_customer_id( $user_id, $args );
	}


	/**
	 * Determines whether tokenization is forced.
	 *
	 * @since 2.0.0
	 *
	 * @return bool
	 */
	public function is_tokenization_forced() {
		return $this->tokenization_enabled() && 'yes' === $this->tokenization_forced;
	}


	/**
	 * Gets the configured merchant ID.
	 *
	 * @since 2.0.0
	 *
	 * @return string
	 */
	public function get_merchant_id() {
		return $this->merchant_id;
	}


	/**
	 * Gets the configured shared secret.
	 *
	 * @since 2.0.0
	 *
	 * @return string
	 */
	public function get_shared_secret() {
		return $this->shared_secret;
	}


	/**
	 * Gets the configured rebate password.
	 *
	 * @since 2.0.0
	 *
	 * @return string
	 */
	public function get_rebate_password() {
		return $this->rebate_password;
	}


	/**
	 * Gets the configured subaccount.
	 *
	 * @since 2.0.0
	 *
	 * @param string $environment_id environment ID
	 * @return string
	 */
	public function get_subaccount( $environment_id = null ) {

		if ( null === $environment_id ) {
			$environment_id = $this->get_environment();
		}

		$subaccount = self::ENVIRONMENT_TEST === $environment_id ? $this->test_subaccount : $this->subaccount;

		/*
		 * Filters the default subaccount used for Realex API transactions.
		 *
		 * @since 2.0.0
		 *
		 * @param string $subaccount default subaccount
		 * @param \WC_Gateway_Realex_Redirect $gateway gateway object
		 */
		return apply_filters( 'wc_realex_redirect_subaccount', $subaccount, $this );
	}


	/**
	 * Gets the configured form type.
	 *
	 * @since 2.0.0
	 *
	 * @return string
	 */
	public function get_form_type() {
		return $this->form_type;
	}


	/**
	 * Gets the configured form language.
	 *
	 * @since 2.0.0
	 *
	 * @return string
	 */
	public function get_form_language() {

		$language = $this->form_language;

		return ! empty( $language ) ? $language : substr( get_locale(), 0, 2 );
	}


	/**
	 * Determines if this gateway uses a form-post from the pay
	 * page to "redirect" to a hosted payment page.
	 *
	 * @since 2.0.0
	 *
	 * @return bool
	 */
	public function use_form_post() {
		return true;
	}


	/**
	 * Determines whether AVS is enabled.
	 *
	 * @since 2.0.0
	 *
	 * @return bool
	 */
	public function is_avs_enabled() {
		return 'yes' === $this->enable_avs;
	}


	/**
	 * Determines whether Alternative Payment Methods are enabled.
	 *
	 * @since 2.4.0
	 *
	 * @return bool
	 */
	public function is_apm_enabled() {
		return 'yes' === $this->enable_apm;
	}


	/**
	 * Gets the list of selected payment method IDs.
	 *
	 * @since 2.4.0
	 *
	 * @return string[]
	 */
	public function get_available_payment_methods() {
		return is_array( $this->available_apms ) ? $this->available_apms : [];
	}


	/**
	 * Gets the configured refund password.
	 *
	 * @since 2.4.0
	 *
	 * @return string
	 */
	public function get_refund_password() {
		return $this->refund_password;
	}


	/**
	 * Add admin notice for change payment method.
	 *
	 * @since 3.0.3
	 */
	public function maybe_add_payment_method_update_notice() {

		$hpp_change_payment = Framework\SV_WC_Helper::get_requested_value( 'hpp_change_payment' );
		$notice_type        = 'success';

		if ( $hpp_change_payment ) {
			if ( 'updated_for_all_subscription' === $hpp_change_payment ) {
				$notice = __( 'Payment method updated for all your current subscriptions.', 'woocommerce-gateway-realex-redirect' );
			} elseif ( 'payment_method_failed' === $hpp_change_payment ) {
				$notice      = __( 'Changing payment method failed.', 'woocommerce-gateway-realex-redirect' );
				$notice_type = 'error';
			} else {
				$notice = 'payment_method_updated' === $hpp_change_payment ? __( 'Payment method updated.', 'woocommerce-gateway-realex-redirect' ) : __( 'Payment method added.', 'woocommerce-gateway-realex-redirect' );
			}

			wc_add_notice( esc_html( $notice ), $notice_type );
		}
	}


	/**
	 * Update payment method for all subscription.
	 *
	 * @since 3.0.3
	 *
	 * @param \WC_Order $subscription $order object
	 * @param \WC_Realex_Redirect_API_HPP_Response $response response object
	 * @return bool
	 */
	public function maybe_change_payment_method_for_all_subscription( $order, $response ) {

		if ( ! class_exists( 'WC_Subscriptions_Change_Payment_Gateway' ) ) {
			return false;
		}

		$subscription = wcs_get_subscription( (int) $order->get_id() );
		$subscription->set_requires_manual_renewal( false );
		$subscription->save();

		if (
			$response->is_change_payment_for_all_subscription()
			&& \WC_Subscriptions_Change_Payment_Gateway::can_update_all_subscription_payment_methods( $this, $subscription )
			&& \WC_Subscriptions_Change_Payment_Gateway::update_all_payment_methods_from_subscription( $subscription, WC_Realex_Redirect::GATEWAY_ID )
		) {
			return true;
		}

		return false;
	}


	/**
	 * Gets the checkout block integration instance.
	 *
	 * @since 3.3.0
	 *
	 * @return Checkout_Block_Integration
	 */
	public function get_checkout_block_integration_instance(): ?Framework\Payment_Gateway\Blocks\Gateway_Checkout_Block_Integration {

		if ( null === $this->checkout_block_integration ) {

			require_once( $this->get_plugin()->get_plugin_path() . '/src/Blocks/Checkout_Block_Integration.php' );

			$this->checkout_block_integration = new Checkout_Block_Integration( $this->get_plugin(), $this );
		}

		return $this->checkout_block_integration;
	}


}
