<?php
/**
 * Class WC_Gateway_EasyPaymentGateway file.
 *
 * @package Addonpayments
 */

use Automattic\WooCommerce\Admin\Overrides\Order;
use ComerciaGlobalPayments\AddonPayments\SDK\_3DSecure\v2\CardHolderAccountInfo;
use ComerciaGlobalPayments\AddonPayments\SDK\_3DSecure\v2\Enum\AccountAgeEnum;
use ComerciaGlobalPayments\AddonPayments\SDK\_3DSecure\v2\Enum\BillAddressMatchEnum;
use ComerciaGlobalPayments\AddonPayments\SDK\_3DSecure\v2\Enum\ChallengeEnum;
use ComerciaGlobalPayments\AddonPayments\SDK\_3DSecure\v2\Enum\DeliveryTimeFrameEnum;
use ComerciaGlobalPayments\AddonPayments\SDK\_3DSecure\v2\Enum\PreOrderPurchaseEnum;
use ComerciaGlobalPayments\AddonPayments\SDK\_3DSecure\v2\Enum\ReorderItemsEnum;
use ComerciaGlobalPayments\AddonPayments\SDK\_3DSecure\v2\Enum\ShippingMethodEnum;
use ComerciaGlobalPayments\AddonPayments\SDK\_3DSecure\v2\Enum\ShippingNameIndEnum;
use ComerciaGlobalPayments\AddonPayments\SDK\_3DSecure\v2\Enum\SuspiciousAccountActivityIndEnum;
use ComerciaGlobalPayments\AddonPayments\SDK\_3DSecure\v2\Enum\TransactionTypeEnum;
use ComerciaGlobalPayments\AddonPayments\SDK\_3DSecure\v2\RiskValidation;
use ComerciaGlobalPayments\AddonPayments\SDK\_3DSecure\v2\ShippingAddress;
use ComerciaGlobalPayments\AddonPayments\SDK\Api\ApiClientInterface;
use ComerciaGlobalPayments\AddonPayments\SDK\Api\EPGJs;
use ComerciaGlobalPayments\AddonPayments\SDK\Encryption\Cypher\Aes256Ecb;
use ComerciaGlobalPayments\AddonPayments\SDK\Request\Authorization;
use ComerciaGlobalPayments\AddonPayments\SDK\Request\Charge;
use ComerciaGlobalPayments\AddonPayments\SDK\Request\Enum\ChargeTypeEnum;
use ComerciaGlobalPayments\AddonPayments\SDK\Request\Enum\OperationTypeEnum;
use ComerciaGlobalPayments\AddonPayments\SDK\Request\ExpressCheckout;
use ComerciaGlobalPayments\AddonPayments\SDK\Request\Refund;
use ComerciaGlobalPayments\AddonPayments\SDK\Request\Voided;
use ComerciaGlobalPayments\AddonPayments\SDK\Response\ErrorResponse;
use ComerciaGlobalPayments\AddonPayments\SDK\Response\ErrorResponseInterface;
use ComerciaGlobalPayments\AddonPayments\SDK\Response\Operation;
use ComerciaGlobalPayments\AddonPayments\SDK\Response\OperationInterface;
use Hashids\Hashids;
use WooCommerce\Plugin\AddonPayments\Entities\Transaction;
use WooCommerce\Plugin\AddonPayments\Operations\CaptureTransaction;
use WooCommerce\Plugin\AddonPayments\Operations\OrderRefund;
use WooCommerce\Plugin\AddonPayments\Operations\ProcessTransaction;
use WooCommerce\Plugin\AddonPayments\Operations\ValidateOrder;

// @TODO: Sure about using SECURE_AUTH_KEY as the key for encryption
// @TODO: Operation type

/**
 * Easy Payment Gateway
 */
class WC_Gateway_EasyPaymentGateway extends WC_Payment_Gateway {

	public const ORDER_PENDING = 'on-hold';

	public const ORDER_COMPLETED = 'completed';

	public const ORDER_REFUNDED = 'refunded';

	public const ORDER_CANCELLED = 'cancelled';

	public const ORDER_FAILED = 'failed';

	public const ORDER_AWAITING_PAYMENT = 'pending';

	public const ORDER_PROCESSING = 'processing';

	public const PAYMENT_PROCESS_SUCCESS = 'success';

	public const PAYMENT_PROCESS_FAIL = 'failure';

	public const FINANCING_DEFAULT_OPTION = 'instalments';

	public const PAYMENT_STATUS_SUCCESS = 0;

	public const PAYMENT_STATUS_FAILED = 1;

	public const PAYMENT_STATUS_CANCELLED = 2;

	public const PAYMENT_STATUS_AWAITING = 3;

	public const PAYMENT_STATUS_UNKNOWN = 4;

	public const BNPL_NAME = 'quix';

	/**
	 * Time span in milliseconds until the authorization token is expired.
	 */
	public const AUTH_TOKEN_VALIDITY_THRESHOLD = 15 * 60 * 1000;

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

	private $user;

	private $guest;

	private $customer_id;

	/**
	 * Constructor for the gateway.
	 *
	 * @throws Exception
	 */
	public function __construct() {
		$this->id                 = ID_EPG;
		$this->has_fields         = true;
		$this->method_title       = 'Addon Payments';
		$this->method_description = __( 'Integrates Easy Payment Gateway as a payment module', 'addonpayments' );
		$this->supports           = array(
			'products',
			'refunds',
		);

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

		$this->title       = $this->get_option( 'title' );
		$this->icon        = $this->get_option( 'icon' );
		$this->user        = wp_get_current_user();
		$this->guest       = ! $this->user->exists();
		$this->customer_id = $this->guest ? sha1( random_bytes( 16 ) ) : sha1( $this->user->ID . SECURE_AUTH_KEY );

		add_filter( 'woocommerce_order_button_html', array( $this, 'remove_order_button' ) );
		add_filter( 'woocommerce_twenty_twenty_styles', array( $this, 'enqueue_twenty_twenty_styles' ) );
		add_filter( 'admin_body_class', array( $this,'addonpayments_admin_body_class' ) );

		add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ));
		add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
        add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts') );

//		add_action( 'init', array($this,'addonpayments_register_order_status') );
		add_filter( 'wc_order_statuses', array( $this, 'add_order_status_to_wc' ) );

		add_action( 'woocommerce_order_status_changed',	array( $this, 'order_status_change' ), 10, 3 );
		add_action( 'woocommerce_order_edit_status',	array( $this, 'manual_order_status_change' ), 10, 2 );
		add_action( 'woocommerce_payment_complete', array( $this, 'order_received_empty_cart_action' ), 10, 1 );
		add_action( 'woocommerce_after_order_refund_item_name', array( $this, 'set_refund_origin' ), 10, 1 );
		add_action( 'woocommerce_admin_order_items_after_refunds', array( $this, 'show_delete_refund' ), 10, 1 );
		add_action( 'woocommerce_api_ap-callback', array( $this, 'webhook_callback' ) );
		add_action( 'woocommerce_api_ap-status', array( $this, 'webhook_status' ) );
	}

	public function addonpayments_admin_body_class( $classes ) {
		$a = 0;
		$screen = get_current_screen();

		if ( $screen->post_type === 'shop_order' ) {
			$order_id = get_the_id();
			$addonpayments_transaction = get_post_meta( $order_id, '_transaction_id', TRUE );

			if(!empty($addonpayments_transaction)) {
				$classes .= ' addonpayments-order';
			}
		}
		return $classes;
	}

	/**
	 * Add styles and script on admin pages
	 *
	 * @return void
	 */
    public function enqueue_admin_scripts() {
		$current_screen = get_current_screen();
		wp_enqueue_style( 'addonpayments_admin_css', esc_url( plugins_url( 'assets' . DIRECTORY_SEPARATOR . 'css' . DIRECTORY_SEPARATOR . 'admin.css', ADDONPAYMENTS_PLUGIN_FILE ) ), false, ADDONPAYMENTS_VERSION );

		if ($current_screen->id === 'woocommerce_page_wc-settings' && isset($_GET['section']) && $_GET['section'] === 'addonpayments') {

			if (!wp_script_is('addonpayments_admin_js')) {
				wp_enqueue_script( 'addonpayments_admin_js', esc_url( plugins_url( 'assets' . DIRECTORY_SEPARATOR . 'js' . DIRECTORY_SEPARATOR . 'admin.js', ADDONPAYMENTS_PLUGIN_FILE ) ), array( 'jquery' ), ADDONPAYMENTS_VERSION, true );

				$ap_config = array(
					'image_uri' => AP_Config::getDefaultLogoUri(),
				);

				$ap_config = array_merge( AP_Config::getDefaultPaymentConfig(), $ap_config );

				if ( isset( $this->settings[ AP_Config::DEFAULT_CONFIGURATION ] )
				     && AP_Config::DEFAULT_OPTION_CUSTOM === $this->settings[ AP_Config::DEFAULT_CONFIGURATION ]
				) {
					$ap_config['title'] = $this->settings['title'];
					$ap_config['icon']  = $this->settings['icon'];
				}

				wp_localize_script( 'addonpayments_admin_js', 'apConfig', $ap_config );
			}
		}
    }

	/**
	 * Add styles for theme twenty twenty.
	 *
	 * @param $styles
	 *
	 * @return mixed
	 */
	public function enqueue_twenty_twenty_styles( $styles ) {
		$styles['addonpayments-twentytwenty'] = array(
			'src'     => plugins_url( 'assets' . DIRECTORY_SEPARATOR . 'css' . DIRECTORY_SEPARATOR . 'twenty-twenty.css', ADDONPAYMENTS_PLUGIN_FILE ),
			'deps'    => '',
			'version' => ADDONPAYMENTS_VERSION,
			'media'   => 'all',
			'has_rtl' => false,
		);

		return $styles;
	}

	/**
	 * Add front styles.
	 *
	 * @return void
	 */
	public function enqueue_scripts() {
		if ( is_checkout() ) {
			$rendererJsLocation  = EPGJs::getEndpoints( $this->settings['live_mode'] == 'yes')['render'];
			$jsLocation = plugins_url( 'assets' . DIRECTORY_SEPARATOR . 'js' . DIRECTORY_SEPARATOR . 'allPaySol.js', ADDONPAYMENTS_PLUGIN_FILE );

			wp_enqueue_script( 'jquery' );
			wp_enqueue_script( 'epg-renderer', esc_url( $rendererJsLocation ), array( 'jquery' ), '3.10.0', true );
			wp_enqueue_script( 'allPaySol',	esc_url( $jsLocation ), array( 'jquery' ),ADDONPAYMENTS_VERSION, true	);

			wp_enqueue_style( 'cardVaultStyle', esc_url( plugins_url( 'assets' . DIRECTORY_SEPARATOR . 'css' . DIRECTORY_SEPARATOR . 'front.css', ADDONPAYMENTS_PLUGIN_FILE ) ), false, ADDONPAYMENTS_VERSION );

			$locale = substr( get_locale(), 0, 2 );
			$i18n   = $this->getI18n( $locale );

			wp_localize_script(
				'allPaySol',
				'allPaySol_object',
				array(
					'locale'     => $locale,
					'i18n'       => array( $locale => $i18n ),
					'customerId' => $this->customer_id,
					'texts'      => array(
						'unknownError'       => __(
							'An unexpected error occurred and your action cannot be performed. Please try reloading the page and if the problem persists contact us.',
							'addonpayments'
						),
						'reachedPaysolLimit' => __(
							'Sorry, you have reached the registration limit for payment methods.',
							'addonpayments'
						),
					),
				)
			);
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public function init_form_fields() {
		$this->form_fields = include __DIR__ . '/settings-addonpayments.php';
	}

	/**
	 * {@inheritDoc}
	 */
	public function init_settings() {
		parent::init_settings();

		$this->settings['type_financing'] = self::FINANCING_DEFAULT_OPTION;

		if ( ! isset( $this->settings[ AP_Config::DEFAULT_CONFIGURATION ] )  && ! empty( $this->settings['title'] ) ) {
			$this->settings[ AP_Config::DEFAULT_CONFIGURATION ] = AP_Config::DEFAULT_OPTION_CUSTOM;
		}

		if ( empty( $this->settings[ AP_Config::DEFAULT_CONFIGURATION ] ) ) {
			$this->settings['icon'] = AP_Config::getDefaultLogoUri() . 'addonpayments.png';
			$this->settings['title'] = 'Addon Paymens';

		} elseif ( ! empty( $this->settings[ AP_Config::DEFAULT_CONFIGURATION ] )
		           && AP_Config::DEFAULT_OPTION_CUSTOM !== $this->settings[ AP_Config::DEFAULT_CONFIGURATION ]
		) {
			$this->settings['title'] = AP_Config::getDefaultTitle( $this->settings[ AP_Config::DEFAULT_CONFIGURATION ] );
			$this->settings['icon']  = AP_Config::getDefaultLogoUri() . $this->settings[ AP_Config::DEFAULT_CONFIGURATION ] . '.png';
		}

		$cypher = new Aes256Ecb();
		if ( isset( $this->settings['merchant_id'] ) && ! empty( $this->settings['merchant_id'] ) ) {
			$this->settings['merchant_id'] = $cypher->decrypt( $this->settings['merchant_id'], SECURE_AUTH_KEY );
		}
		if ( isset( $this->settings['api_key'] ) && ! empty( $this->settings['api_key'] ) ) {
			$this->settings['api_key'] = $cypher->decrypt( $this->settings['api_key'], SECURE_AUTH_KEY );
		}
		if ( isset( $this->settings['password'] ) && ! empty( $this->settings['password'] ) ) {
			$this->settings['password'] = $cypher->decrypt( $this->settings['password'], SECURE_AUTH_KEY );
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public function generate_text_html( $key, $data ) {
		$field_html = parent::generate_text_html( $key, $data );

		if ( isset( $data['tr_class'] ) && ! empty( $data['tr_class'] ) ) {
			$field_html = str_replace( '<tr', '<tr class="' . $data['tr_class'] . ' "', $field_html );
		}

		return $field_html;
	}

	/**
	 * Icon field for easypayment gateway settings.
	 *
	 * @param $key
	 * @param $value
	 *
	 * @return string
	 */
	public function generate_addonpayments_icon_html( $key, $data ) {
        $field_key = $this->get_field_key( $key );
		$defaults = [
			'title'             => '',
			'disabled'          => FALSE,
			'class'             => '',
			'css'               => '',
			'placeholder'       => '',
			'type'              => 'text',
			'desc_tip'          => FALSE,
			'description'       => '',
			'custom_attributes' => [],
			'tr_class'          => '',
			'readonly'          => false
		];

		$data  = wp_parse_args( $data, $defaults );
		$value = $this->get_option( $key );

		ob_start();
        include EPG_BASE_DIR . '/templates/admin/field-addonpayments-icon.php';
		return ob_get_clean();
	}

	/**
	 * {@inheritDoc}
	 */
	public function process_admin_options() {
		$this->init_settings();
		$post_data = $this->get_post_data();

		foreach ( $this->get_form_fields() as $key => $field ) {
			if ( 'title' !== $this->get_field_type( $field ) ) {
				try {
					$value = $this->get_field_value( $key, $field, $post_data );
					if ( 'addonpayments_icon' === $this->get_field_type( $field ) ) {

						if ( isset( $post_data['addonpayments-icon-filename'] )
						       && ! empty ( $post_data['addonpayments-icon-filename'] ) ) {

							if ( isset( $_FILES['woocommerce_addonpayments_icon'] ) &&
							     isset( $_FILES['woocommerce_addonpayments_icon']['tmp_name'] ) &&
							     ! empty( $_FILES['woocommerce_addonpayments_icon']['tmp_name'] ) ) {

								$file =
									wp_upload_bits( $_FILES['woocommerce_addonpayments_icon']['name'],
										NULL,
										file_get_contents( $_FILES['woocommerce_addonpayments_icon']['tmp_name'] ) );

								if ( $file['error'] ) {
									throw new Exception( $file['error'] );
								}

								$value = $file['url'];
							} else {
								$value = $this->icon;
							}

						}else {
							$value = '';
						}
					}

					$this->settings[ $key ] = $value;

				} catch ( Exception $e ) {
					$this->add_error( $e->getMessage() );
				}
			}
		}

		$this->encode_settings();

		if ( empty( $this->settings[ AP_Config::DEFAULT_CONFIGURATION ] )
		            || AP_Config::DEFAULT_OPTION_CUSTOM !== $this->settings[ AP_Config::DEFAULT_CONFIGURATION ]
		) {
			$this->settings['title'] = '';
			$this->settings['icon']  = '';
		}

		return update_option(
			$this->get_option_key(),
			apply_filters(
				'woocommerce_settings_api_sanitized_fields_' . $this->id,
				$this->settings
			),
			'yes'
		);
	}

	/**
	 * {@inheritDoc}
	 */
	public function update_option( $key, $value = '' ) {
		if ( empty( $this->settings ) ) {
			$this->init_settings();
		}

		$this->encode_settings();
		return parent::update_option( $key, $value );
	}

	public function get_available_payment_solutions() {
		try {
			$country_raw = get_option( 'woocommerce_default_country' );
			$country_arr = explode( ':', $country_raw );
			$country     = reset( $country_arr );
			$currency    = get_option( 'woocommerce_currency' );

			$client  = $this->getApiClient();
			$request = ( new ExpressCheckout() )
				->setCountry( $country )
				->setCurrency( $currency )
				->setOperationType( ChargeTypeEnum::DEBIT )
				->setProductId( $this->settings['product_id'] );

			$response = $client->expressCheckout( $request );

			if ( $response instanceof ErrorResponseInterface ) {
				throw new Exception( $response->getReason() );
			}

			return $response->getPaymentSolutions();

		} catch ( Exception $e ) {
			self::log(
				"Express checkout failed:: {$e->getMessage()}",
				'error'
			);

			return false;
		}
	}

	public function get_solution_name( string $name ) {
		// @TODO This should be configurable.
		$names = array(
			'CreditCards' => __(
				'credit cards',
				'addonpayments'
			),
		);

		return $names[ $name ] ?? $name;
	}

	public function encode_settings() {
		$cypher = new Aes256Ecb();
		if ( $this->settings['merchant_id'] &&
			 ! empty( $this->settings['merchant_id'] ) ) {
			$this->settings['merchant_id'] =
				$cypher->encrypt(
					$this->settings['merchant_id'],
					SECURE_AUTH_KEY
				);
		}
		if ( $this->settings['api_key'] &&
			 ! empty( $this->settings['api_key'] ) ) {
			$this->settings['api_key'] =
				$cypher->encrypt( $this->settings['api_key'], SECURE_AUTH_KEY );
		}
		if ( $this->settings['password'] &&
			 ! empty( $this->settings['password'] ) ) {
			$this->settings['password'] =
				$cypher->encrypt(
					$this->settings['password'],
					SECURE_AUTH_KEY
				);
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public function payment_fields() {
		if ( empty( $this->settings['merchant_id'] ) &&
			 empty( $this->settings['api_key'] ) &&
			 empty( $this->settings['password'] ) &&
			 empty( $this->settings['product_id'] ) ) {
			return;
		}

		$api_client = $this->getApiClient();

		// Defaults to 30 minutes.
		$token_validity = self::AUTH_TOKEN_VALIDITY_THRESHOLD;

		// @TODO: Better instance_id
		//INICIO BloqueDC
		$instance_id   = sha1( microtime() );
		$reference    = $this->customer_id;
		$auth_token   = $this->get_auth_token( $api_client );
		$valid_until  = ( time() * 1000 ) + (int) $token_validity;
		$mep_base_url = $api_client->getEndpoint( 'base' );
		$cashier_mode = 'setShowAllPaysolToRegisterAndQuickDeposit';
		$url          = admin_url( 'admin-ajax.php' );
		$class        = $this->guest ? 'guest' : '';
		$type_financing = $this->settings['type_financing'];
		$data_currency = get_woocommerce_currency();
		$data_locale = get_locale();
		$data_language = substr( get_locale(), 0, 2 );
		$data_decsep = wc_get_price_decimal_separator();
		$data_thosep = wc_get_price_thousand_separator();
		global $woocommerce;
		$data_amount=$woocommerce->cart->total;

		$timeoutText  =
			__(
				'This operation has timed out, please reload the page.',
				'addonpayments'
			);

		echo "<div id='payment-option-easypaymentgateway'
				class='register-quickpay__form $class'
                data-instance='$instance_id'
                data-customer='$this->customer_id'
				data-reference='$reference'
				data-api-base='$mep_base_url'
				data-token='$auth_token'
				data-valid-until='$valid_until'
				data-mode='$cashier_mode'
				data-register-callback='$url'
				data-financing='$type_financing'
				data-currency='$data_currency'
				data-language='$data_language'
				data-locale='$data_locale'
				data-decsep='$data_decsep'
				data-thosep='$data_thosep'
				data-amount='$data_amount'
            >
			    <div class='bootstrap'>
			    	<div id='addonpayments-info' class='module_info info hidden'
                    	 role='alert'></div>
                	<div id='addonpayments-error' class='module_error alert hidden'
                    	 role='alert'></div>
                	<div id='addonpayments-expired' class='module_error alert hidden'
                    	 role='alert'>$timeoutText</div>
			        <div id='epgjs-cashier-div'></div>
			    </div>
            </div>";
	}

	/**
	 * {@inheritDoc}
	 */
	public function validate_fields() {
		return true;
	}

	/**
	 * Register custom order status
	 *
	 * @return void
	 * @since 1.1.8
	 */
	function addonpayments_register_order_status() {
		register_post_status( 'wc-partial-refund', array(
			'label'                     => __( 'Partial refund', 'addonpayments' ),
			'public'                    => true,
			'exclude_from_search'       => false,
			'show_in_admin_all_list'    => true,
			'show_in_admin_status_list' => true,
			'label_count'               => _n_noop( 'Partial refund <span class="count">(%s)</span>',
				'Partial refund <span class="count">(%s)</span>' ),
		) );
	}



	/**
	 * Add custom order status to WC
	 *
	 * @since 1.1.8
	 * @return void
	 */
	public function add_order_status_to_wc($order_statuses) {
		$order_statuses['wc-partial-refund'] = __( 'Partial refund', 'addonpayments' );

		return $order_statuses;
	}

	/**
	 * {@inheritDoc}
	 */
	public function process_payment( $order_id ) {
		global $wp_version;
		$prepay_response = $_POST['prepayResponse'];

		if ( empty( $prepay_response ) || JSON_ERROR_NONE !== json_last_error() ) {

			wc_add_notice(
				__(
					'The payment process has time out, please reload the page.',
					'addonpayments'
				),
				'error'
			);

			return array(
				'result' => self::PAYMENT_PROCESS_FAIL,
			);
		}

		$prepay_response         = json_decode( stripslashes( $prepay_response ), true );
		$order                   = new WC_Order( $order_id );
		$txs                     = Transaction::countByOrder( $order_id );
		$api_client              = $this->getApiClient();
		$customerId              = $prepay_response['customerId'];
		$merchant_transaction_id = $this->get_hash_ids()->encode( $order_id, $txs );
		$payment_method          = $prepay_response['paymentMethod'];
		$statusUrl               = get_site_url() . '?wc-api=ap-status&payment=' . $merchant_transaction_id;
		$endpoints  = EPGJs::getEndpoints( $api_client->isLive() );

		preg_match('/\b\d+(?:\.\d+)*\b/', $endpoints['render'], $matches );

		$request = ( new Charge() )
			->setCustomerId( $customerId )
			->setApiVersion(3)
			->setMerchantTransactionId( $merchant_transaction_id )
			->setCountry( $order->get_billing_country() )
			->setCurrency( $order->get_currency() )
			->setOperationType( ChargeTypeEnum::DEBIT )
			->setAmount( $order->get_total() )
			->setPrepayToken( $prepay_response['prepayToken'] )
			->setProductId( (int) $this->settings['product_id'] )
			->addMerchantParam('CMS_version', $wp_version)
            ->addMerchantParam('Plugin_Version', ADDONPAYMENTS_VERSION)
            ->addMerchantParam('JS_version', $matches[0])
			->setStatusUrl( urlencode( $statusUrl ) )
			->setSuccessUrl( urlencode( $this->get_callback_url( $order_id, $txs, self::PAYMENT_STATUS_SUCCESS ) ) )
			->setCancelUrl( urlencode( $this->get_callback_url( $order_id, $txs, self::PAYMENT_STATUS_CANCELLED ) ) )
			->setErrorUrl( urlencode( $this->get_callback_url( $order_id, $txs, self::PAYMENT_STATUS_FAILED ) ) );


		if ( self::BNPL_NAME === $payment_method ) {

			$sTypeFinancing = $this->settings['type_financing'];

			$lang = get_locale();
			if ( strlen( $lang ) > 0 ) {
				$lang = explode( '_', $lang )[1];
			}

			$request->setpaymentSolution( $payment_method )
				    ->setCustomerCountry( $order->get_billing_country() )
				    ->setCustomerEmail( $order->get_billing_email() )
				    ->setFirstName( $order->get_billing_first_name() )
				    ->setLastName( $order->get_billing_last_name())
			        ->setLanguage( $lang )
				    ->setIpAddress( $order->get_customer_ip_address() )
			        ->setRememberMe( false )
			        ->setAccount( $customerId )
					->setAwaitingURL(urlencode($this->get_callback_url($order_id, $txs, self::PAYMENT_STATUS_SUCCESS)))
					->setpaysolExtendedData( json_encode( $this->paysol_extended_data( $sTypeFinancing, $order ) ) );

		} else {
			$request->add3DSecureV2Object( $this->getRiskValidation( $order ) );
			$request->add3DSecureV2Object( $this->getCardHolderAccountInfo( $order ) );
			$request->add3DSecureV2Object( $this->getShippingAddress( $order ) );
		}

		if ( 'yes' === $this->settings['settle'] ) {
			$request->setAutoCapture( true );
			$order->_payment_response = $api_client->charge( $request );

			return $this->process_response( $order );
		}

		$request->setAutoCapture( false );
		$order->_payment_response = $api_client->preAuth( $request );

		return $this->process_response( $order );
	}

	/**
	 * Process charge response
	 *
	 * @param \WC_Order|NULL $order
	 *
	 * @return array|string[]
	 * @throws \Exception
	 */
	public function process_response( WC_Order $order = null ) {
		$response = $order->_payment_response;
		switch ( true ) {
			case $response instanceof OperationInterface && $response->needsRedirection():

				$this->registerTransactions(
					$response->getOperations(),
					$order
				);

				$target = ( $response->getRedirectionTarget() )
					->setBaseUrl(
						EPGJs::getEndpoints(
							$this->settings['live_mode'] == 'yes'
						)['redirect']
					);

				return array(
					'result'   => self::PAYMENT_PROCESS_SUCCESS,
					'redirect' => (string) $target,
				);

			case $response instanceof OperationInterface && $response->isCancelled():
				$this->registerTransactions(
					$response->getOperations(),
					$order
				);
				$order->update_status(
					self::ORDER_CANCELLED,
					__( 'Payment canceled by user', 'addonpayments' )
				);
				wc_add_notice(
					__(
						'The payment was cancelled',
						'addonpayments'
					),
					'error'
				);
				WC()->session->set( 'reload_checkout', true );

				return array(
					'result' => self::PAYMENT_PROCESS_FAIL,
				);
			case $response instanceof OperationInterface && $response->hasFailed():
				$this->registerTransactions(
					$response->getOperations(),
					$order
				);

				$transaction = $response->getTransaction();
				$trx_message = $transaction->getMessage();
				$pay_sol     = $transaction->getPaymentSolution();

				$default_message = __( 'Error processing payment. The order could not be completed. Try make the payment again if the problem persist, please contact our customer service.', 'addonpayments' );

				$message         = $this->map_error_message( $trx_message );
				$message         = !empty($message) ? $message : $default_message;

				$order->update_status( self::ORDER_FAILED, $message );

				wc_add_notice( $message, 'error' );
				WC()->session->set( 'reload_checkout', true );

				if ( isset( $_GET['pay_for_order'] ) && $_GET['pay_for_order'] ) {
					echo json_encode( array(
						'result'   => self::PAYMENT_PROCESS_FAIL,
						'redirect' => '',
						'messages' => $message,
						'reload'   => TRUE,
					) );

					die();
				}

				return array(
					'result' => self::PAYMENT_PROCESS_FAIL,
				);
			case $response instanceof OperationInterface
			     && OperationInterface::STATUS_SUCCESS === $response->getStatus()
			     && OperationInterface::STATUS_REDIRECTED === $response->getTransaction()->getStatus()
			     && $response->getTransaction()->getPaymentSolution() === self::BNPL_NAME:

				$transaction = $response->getTransaction();
                $extraDetails =  $transaction->getPaymentDetails()->extraDetails;
                $paymentResponse = [];

				 if(property_exists($extraDetails, 'entry')) {
                    $paymentExtraDetails = $extraDetails->entry;

                    foreach ($paymentExtraDetails as $detail) {
                        $paymentResponse[$detail->key] = $detail->value;
                    }

                }else if(property_exists($extraDetails, 'nemuruAuthToken')) {
                    $paymentResponse = (array) $extraDetails;

                }else {
					 wc_add_notice( __( 'Payment failed', 'addonpayments' ), 'error' );
                }

				$trx_count =  Transaction::countByOrder( $order->get_id() );
				$paymentResponse['merchantTrxId'] = $transaction->getMerchantTransactionId();
                $paymentResponse['callback'] = $this->get_callback_url( $order->get_id(), $trx_count, self::PAYMENT_STATUS_UNKNOWN);

				$response = array(
					'result'          => 'success',
					'redirect'        => '',
					'paymentResponse' => $paymentResponse,
					'messages' => ''
				);

				if ( isset( $_GET['pay_for_order'] ) && $_GET['pay_for_order'] ) {
					echo json_encode($response);
					die(200);
				}

				return $response;

			case ( $response instanceof OperationInterface && $response->hasSucceed() ):
				$transactions = $response->getOperations();
				$this->registerTransactions( $transactions, $order );
				( new ValidateOrder() )( $transactions, $order );

				return array(
					'result'   => self::PAYMENT_PROCESS_SUCCESS,
					'redirect' => parent::get_return_url( $order ),
				);

			case ( $response instanceof ErrorResponseInterface ):
				$message = 'Payment failed by unknown error';

				if ( null !== $response->getHttpResponse() ) {
					$body =
						json_decode(
							(string) $response->getHttpResponse()
													   ->getBody(),
							true
						);

					if ( ! empty( $body )
						 && JSON_ERROR_NONE === json_last_error() ) {
						$message = $body['errorMessage'];
					}
				}

				$order->update_status(
					self::ORDER_FAILED,
					__( $message, 'addonpayments' )
				);
				wc_add_notice(
					__(
						'The payment has failed',
						'addonpayments'
					),
					'error'
				);
				WC()->session->set( 'reload_checkout', true );

				return array(
					'result' => self::PAYMENT_PROCESS_FAIL,
				);
			default:
				// At this point if it s a response means is not a valid response
				// just record the transaction and set the status to error.
				if ( $response instanceof OperationInterface ) {
					$this->registerTransactions(
						$response->getOperations(),
						$order
					);
				}

				$order->update_status(
					self::ORDER_FAILED,
					__( 'Payment failed', 'addonpayments' )
				);

				wc_add_notice(
					__(
						'The payment has failed',
						'addonpayments'
					),
					'error'
				);

				WC()->session->set( 'reload_checkout', true );

				return array(
					'result' => self::PAYMENT_PROCESS_FAIL,
				);
		}
	}

	/**
	 * {@inheritdoc}
	 */
	public function process_refund( $order_id, $amount = null, $reason = '' ) {
		try {
			$transactions = Transaction::getRefundableByOrder( $order_id );

			if ( empty( $transactions ) ) {
				throw new Exception(
					__(
						'This order does not contains any refundable transactions',
						'addonpayments'
					)
				);
			}

			$response = (new OrderRefund())($order_id, $amount);

			if ( $response instanceof OperationInterface ) {
				$operations = $response->count();

				foreach ( $response->getOperations() as $transaction ) {
					( new ProcessTransaction() )(
						$transaction,
						$operations,
						$order_id
					);
				}

				$last_refund = $this->get_last_refund( $order_id );
				$operations  = $response->getOperations();
				$transaction = array_pop( $operations );

				add_post_meta(
					$last_refund->get_id(),
					'_transaction_id',
					$transaction->getPayFrexTransactionId(),
					true
				);

				if( $response->hasSucceed() ) {
					$order = wc_get_order($order_id);

					$note = sprintf( __( 'Refunded %s. Transaction ID: %d.', 'addonpayments' ),
						strip_tags( wc_price( $transaction->getAmount() ) ) ,
						$transaction->getPayFrexTransactionId()
					);

					if($reason) {
						$note .= ' ' . sprintf( __( 'Reason: %s', 'addonpayments' ), $reason );
					}

					$order->add_order_note( $note );
				}

				if ( ! $response->hasSucceed() ) {
					$operations = $response->getOperations();
					$last_operation = reset($operations);
					throw new Exception( $last_operation->getMessage() );
				}

				return true;
			}

			if ( $response->getHttpResponse() ) {
				throw new Exception( $response->getReason() );
			}

			$body =
				json_decode(
					(string) $response->getHttpResponse()->getBody(),
					true
				);

			if ( JSON_ERROR_NONE !== json_last_error()
				 || empty( $body['errorMessage'] ) ) {
				throw new Exception( $response->getReason() );
			}

			throw new Exception( $body['errorMessage'] );

		} catch ( Exception $e ) {
			self::log(
				"Refund order $order_id failed: {$e->getMessage()}",
				'error'
			);

			return false;
		}
	}

	/**
	 * {@inheritdoc}
	 */
	public function can_refund_order( $order ) {
		$transactions = Transaction::getRefundableByOrder( $order->get_id() );

		if ( empty($transactions) ) {
			return false;
		}

		$transaction = end($transactions);
		$remaining_amount = Transaction::getRemainingAmountByTransaction( $transaction->payFrexTransactionId );

		if(null === $remaining_amount){
			return true;
		}

		return ( $remaining_amount > 0 );
	}

	public function set_refund_origin( $refund ) {
		$transaction_id = get_post_meta( $refund->get_id(), '_transaction_id', TRUE );

		if(empty($transaction_id)) {
			esc_html_e('(Manual refund)', 'addonpayments');
			return;
		}

		$transaction = Transaction::getByPayFrexTransactionId($transaction_id);
		$transaction = reset($transaction);

		if($transaction->status === OperationInterface::STATUS_SUCCESS) {
			esc_html_e('(Addon Payments refund)', 'addonpayments');
			echo '<input type="hidden" id="addonpayments-refund-'.$transaction_id.'" class="addonpayments-refund" value='.$transaction_id.'">';
		}

	}

	public function show_delete_refund() {
		echo "<script>
			jQuery('.addonpayments-refund').each(function(){
        		var item = jQuery(this);
       			item.parents('tr.refund').find('a.delete_refund').addClass('hidden');
			})
		</script>";
	}

	/**
	 * @param array $transactions
	 *
	 * @return void
	 */
	public function get_refunded_amount( array $transactions ) {
		$amount = 0;

		$operation_types = array(
			'REFUND',
			'REBATE'
		);

		foreach( $transactions as $transaction ) {
			if( in_array( $transaction->operationType, $operation_types, true ) ) {
				$amount += (float) $transaction->amount;
			}
		}

		return $amount;
	}

	public function remove_order_button( $button ) {
		$chosen_payment_method = WC()->session->get( 'chosen_payment_method' );
		if ( $chosen_payment_method == 'addonpayments' ) {
			$button = '';
		}

		return $button;
	}

	/**
	 * Get callback url
	 *
	 * @param $order_id
	 * @param $trx_count
	 * @param $status
	 *
	 * @return string
	 */
	public function get_callback_url($order_id, $trx_count, $status) {
		$forms = [
			'checkout' => 1,
			'order-pay' => 2
		];

		$from = $forms['checkout'];

		if ( isset( $_GET['pay_for_order'] ) && $_GET['pay_for_order'] ) {
			$from = $forms['order-pay'];
		}

		$payload = $this->get_hash_ids()->encode(
			$order_id,
			$trx_count,
			$status,
			$from
		);

		return get_site_url() . '?wc-api=ap-callback&payment=' . $payload;
	}

	private function registerTransactions( array $transactions, WC_Order $order ) {
		$operations = count( $transactions );

		foreach ( $transactions as $transaction ) {
			( new ProcessTransaction() )(
				$transaction,
				$operations,
				$order->get_id()
			);
		}
	}

	public function getApiClient() {
		return new EPGJs(
			array(
				'live'             => $this->settings['live_mode'] === 'yes',
				'merchantId'       => $this->settings['merchant_id'],
				'merchantKey'      => $this->settings['api_key'],
				'merchantSecret'   => $this->settings['password'],
				'defaultProductId' => $this->settings['product_id'],
			)
		);
	}

	public function get_auth_token ( $apiClient = null ) {
		$auth_response = $this->request_authorization( $apiClient );
		return $auth_response ? $auth_response->getAuthToken() : null;
	}

	public function get_authorization () {
		$auth_response = $this->request_authorization();
		$token_validity = ( time() * 1000 )
		                  + (int) self::AUTH_TOKEN_VALIDITY_THRESHOLD;

		return array(
			'token'          => $auth_response ? $auth_response->getAuthToken() : null,
			'token_validity' => $token_validity,
		);
	}

	public function request_authorization( $apiClient = null ) {
		global $woocommerce;
		$current_screen = false;

		if ( function_exists( 'get_current_screen' ) ) {
			$current_screen = get_current_screen();
		}

		try {
			if ( $apiClient === null ) {
				$apiClient = $this->getApiClient();
			}

			if ( ! $this->guest && (empty($current_screen) || 'admin_page_info-page' !== $current_screen->id )) {
				$country = $woocommerce->customer->get_billing_country();
			}

			if ( empty( $country ) ) {
				$country = WC()->countries->get_base_country();
			}

				$auth_request = ( new Authorization() )
					->setCustomerId( $this->customer_id )
                    ->setCurrency( get_woocommerce_currency() )
                    ->setCountry( $country )
                    ->setProductId( $this->settings['product_id'] )
                    ->setOperations( OperationTypeEnum::DEPOSIT )
                    ->setAnonymousCustomer( $this->guest );


			$response = $apiClient->authorization( $auth_request );

			if ( $response instanceof ErrorResponseInterface ) {
				throw new Exception( $response->getReason() );
			}

			return $apiClient->authorization( $auth_request );

		}catch(Exception $e) {
			self::log( $e->getMessage(), 'error' );
		}

		return null;
	}

	/**
	 * Fired when there is a manual status change
	 *
	 * @param $order_id
	 * @param $new_status
	 *
	 * @return void
	 * @since 1.1.8
	 */
	public function manual_order_status_change($order_id, $new_status) {

		$order = wc_get_order( $order_id );
		$current_status = $order->get_status();

		if ( self::ORDER_PENDING === $current_status &&
		     ( self::ORDER_PROCESSING === $new_status || self::ORDER_COMPLETED === $new_status ) ) {

			try{
				$transactions = Transaction::getByOrder( $order_id );
				$transactions_ids = array();

				foreach ( $transactions as $transaction ) {
					if( OperationInterface::STATUS_PENDING === $transaction->status) {
						$transactions_ids[] = $transaction->payFrexTransactionId;
					}
				}

				$transaction_id = reset($transactions_ids);
				/** @var Operation $response */
				$response = (new CaptureTransaction())($transaction_id);

				$this->handle_error_response($response, $transaction_id);

			if ( OperationInterface::STATUS_SUCCESS !== $response->getTransaction()->getStatus() ) {

				$operations = $response->getOperations();
				foreach ( $operations as $operation ) {
					( new ProcessTransaction() )(
						$operation,
						count( $operations ),
						$order_id
					);
				}

				throw new Exception( "[{$transaction_id}] {$response->getTransaction()->getMessage()}" );
			}

			}catch( Exception $e ) {

				$message = "Capture request failed: '{$e->getMessage()}'";
				self::log( $message, 'error' );
				$order->add_order_note(
					__(
						$message,
						'addonpayments'
					)
				);

				wp_redirect(wp_get_referer());
				exit;
			}
		}
	}

	public function order_status_change( $order_id, $old_status, $new_status ) {
		// If the webhook is ap-status, the order state change
		// was issued by the gateway so no further action needs to be taken
		if ( isset( $_GET['wc-api'] ) && 'ap-status' === $_GET['wc-api'] ) {
			return;
		}

		// Skip if the order_id is not present.
		if ( ! $order_id || false === ( $order = wc_get_order( $order_id ) ) ) {
			return;
		}

		// Skip if the order was paid using another method.
		if ( $this->title !== $order->get_payment_method_title() ) {
			return;
		}

		// If order was pre-authorized and is set to be refunded
		// execute a void request.
		if ( self::ORDER_PENDING === $old_status &&
			 self::ORDER_REFUNDED === $new_status ||
			 self::ORDER_PENDING === $old_status &&
			 self::ORDER_CANCELLED === $new_status ) {
			try {
				$transactions = Transaction::getVoidableByOrder( $order_id );
				// Exit if the order has no transactions that can be voided.
				if ( empty( $transactions ) ) {
					return;
				}

				// Get the las voidable transaction.
				$transaction             = reset( $transactions );
				$api_client              = $this->getApiClient();
				$merchant_transaction_id = $transaction->merchantTransactionId;
				$request                 = ( new Voided() )
					->setMerchantTransactionId( $merchant_transaction_id )
					->setPaymentSolution( $transaction->paymentSolution )
					->setTransactionId( $transaction->payFrexTransactionId )
					->setDescription( __( "Void order {$order_id}", 'addonpayments' ) );

				/** @var OperationInterface|ErrorResponse $response */
				$response = $api_client->voided( $request );

				$this->handle_error_response($response, $transaction->payFrexTransactionId);

//				if ( $response instanceof ErrorResponseInterface ) {
//					if ( null === $response->getHttpResponse() ) {
//						throw new Exception( "[{$transaction->payfrexTransactionId}] {$response->getReason()}" );
//					}
//					// Response payload is a stream, cast it to string.
//					$responsePayload =
//						(string) $response->getHttpResponse()->getBody();
//					// Decode content to get the true error message.
//					$body = json_decode(
//						$responsePayload,
//						true
//					);
//					// If no payload or cannot be recovered throw generic error.
//					if ( JSON_ERROR_NONE !== json_last_error()
//					     || empty( $body['errorMessage'] ) ) {
//						throw new Exception( "[{$transaction->payfrexTransactionId}] {$response->getReason()}" );
//					}
//
//
//					throw new Exception( "[{$transaction->payfrexTransactionId}] {$body['errorMessage']}" );
//				}

				$operations = $response->getOperations();
				foreach ( $operations as $operation ) {
					( new ProcessTransaction() )(
						$operation,
						count( $operations ),
						$order_id
					);
				}

			} catch ( Exception $e ) {
				$message = "Void request failed: '{$e->getMessage()}'";
				self::log( $message, 'error' );
				$order->add_order_note(
					__(
						$message,
						'addonpayments'
					)
				);
			}
		}

//		elseif ( self::ORDER_PROCESSING === $old_status &&
//				   self::ORDER_REFUNDED === $new_status ||
//				   self::ORDER_COMPLETED === $old_status &&
//				   self::ORDER_REFUNDED === $new_status ) {
//
//			$this->process_refund( $order_id, null, '' );
//		}

	}

	private function handle_error_response($response, $transaction_id) {

		if ( $response instanceof ErrorResponseInterface ) {
			if ( null === $response->getHttpResponse() ) {
				throw new Exception( "[{$transaction_id}] {$response->getReason()}" );
			}
			// Response payload is a stream, cast it to string.
			$responsePayload
				= (string) $response->getHttpResponse()->getBody();
			// Decode content to get the true error message.
			$body = json_decode( $responsePayload, true );
			// If no payload or cannot be recovered throw generic error.
			if ( JSON_ERROR_NONE !== json_last_error()
			     || empty( $body['errorMessage'] )
			) {
				throw new Exception( "[{$transaction_id}] {$response->getReason()}" );
			}

			throw new Exception( "[{$transaction_id}] {$body['errorMessage']}" );
		}

	}

	public function get_last_refund( $order_id ) {
		$order   = wc_get_order( $order_id );
		$refunds = $order->get_refunds();

		return reset( $refunds );
	}

	/**
	 * Gets error message from transaction or response
	 *
	 * @param OperationInterface $response response object.
	 * @param string $message Default error message.
	 *
	 * @return string
	 */
	public function map_error_message( string $message ) {
		$translated_message = "";
		$message            = str_replace( '"', '', $message );

		$data = [
            "Transaction has been DESISTED/CANCELED" => __('Transaction has been CANCELED.', 'addonpayments'),
            "pricing conditions are not allowed" => __('The price conditions are not allowed, it is too low or too high.', 'addonpayments'),
            "does not have any pricing allowed. Please add an allowed pricing to this agent" => __('The price conditions are not allowed, it is too low or too high.', 'addonpayments'),
            "/shipping/phone_number:The data must match the 'phone-number' format,/billing/phone_number:The data must match the 'phone-number' format" => __('The phone number is invalid.', 'addonpayments'),
            "/shipping/phone_number:The data must match the 'phone-number' format" => __('The shipping phone number is invalid.', 'addonpayments'),
            "/billing/phone_number:The data must match the 'phone-number' format" => __('The billing phone number is invalid.', 'addonpayments'),
            "Transaction has been DENIED" => __('Transaction has been DENIED. Please, try selecting another and if the problem persists contact us.', 'addonpayments' )
        ];

		 $filteredData = array_filter($data, function ($key) use ($message) {
                return strpos($message, $key) !== FALSE;
            },ARRAY_FILTER_USE_KEY
        );

		 if(!empty($filteredData)) {
            $translated_message = reset($filteredData);
        }

        return $translated_message;

//		$resp_code   = $transaction->getRespCode();
//
//		if ( ! empty( $resp_code ) && '0000' !== $resp_code->code ) {
//			$message = $resp_code->message;
//		} else {
//			$message = $response->getTransaction()->getMessage();
//		}

//		return $message;
	}

	/**
	 * Empty cart on payment complete
	 *
	 * @param $order_id
	 */
	function order_received_empty_cart_action( $order_id ) {
		$order = wc_get_order( $order_id );
		$user  = $order->get_user_id();

		if ( ! get_current_user_id() ) {
			wp_set_current_user( $user );
		}

		WC()->cart->empty_cart();
		delete_user_meta(
			$user,
			'_woocommerce_persistent_cart_' . get_current_blog_id()
		);
	}

	// 3DS //////////////

	// Risk validation //////////////
	private function getRiskValidation( $order ) {
		$rv = new RiskValidation();
		// @TODO: Implement sort of method to get the challenge value.
		$rv->setChallenge( (string) new ChallengeEnum() );
		$rv->setDeliveryTimeframe( $this->getDeliveryTimeFrame( $order ) );
		$rv->setPreOrderPurchase( $this->getPreOrderDate( $order ) );
		$rv->setReorderItems( $this->getReorderItems() );
		$rv->setShippingMethod( $this->getShippingMethod( $order ) );
		// @TODO: Get the transaction type some how.
		$rv->setTransactionTypeId( (string) new TransactionTypeEnum() );

		return $rv;
	}

	private function getDeliveryTimeFrame( $order ) {
		foreach ( $order->get_items() as $item_id => $item ) {
			$product = wc_get_product( $item['product_id'] );
			if ( $product->is_downloadable() || $product->is_virtual() ) {
				return (string) new DeliveryTimeFrameEnum( DeliveryTimeFrameEnum::ELECTRONIC_DELIVERY );
			}
		}

		// @Uncompatible: Woocommerce needs external plugins to manage delivery dates
		return (string) new DeliveryTimeFrameEnum( DeliveryTimeFrameEnum::TWO_DAY_OR_MORE );
	}

	private function getPreOrderDate( $order ) {
		foreach ( $order->get_items() as $item_id => $item ) {
			$product = wc_get_product( $item['product_id'] );
			if ( ! $product->is_in_stock() ) {
				return (string) new PreOrderPurchaseEnum( PreOrderPurchaseEnum::FUTURE_AVAILABILITY );
			}
		}

		return (string) new PreOrderPurchaseEnum( PreOrderPurchaseEnum::MERCHANDISE_AVAILABLE );
	}

	// @TODO: Check how to know if items are reordered
	private function getReorderItems() {
		$reordered = false;

		return (string) ( new ReorderItemsEnum(
			$reordered
			? ReorderItemsEnum::REORDERED : ReorderItemsEnum::FIRST_TIME
		) );
	}

	private function getShippingMethod( WC_Order $order ) {
		foreach ( $order->get_items() as $item_id => $item ) {
			$product = wc_get_product( $item['product_id'] );
			if ( $product->is_downloadable() || $product->is_virtual() ) {
				return (string) new ShippingMethodEnum( ShippingMethodEnum::DIGITAL_GOODS );
			}
		}

		// @TODO: Rest of the methods
		$shipping_method = $order->get_shipping_method();
		if ( $shipping_method === 'WC_Shipping_Local_Pickup' ) {
			return (string) new ShippingMethodEnum( ShippingMethodEnum::SHIP_TO_STORE );
		}
		if ( $order->get_shipping_address_1() == $order->get_billing_address_1()
			 && $order->get_shipping_address_2()
				== $order->get_billing_address_2()
			 && $order->get_shipping_country() == $order->get_billing_country()
			 && $order->get_shipping_city() == $order->get_billing_city()
			 && $order->get_shipping_postcode()
				== $order->get_billing_postcode()
		) {
			return (string) new ShippingMethodEnum( ShippingMethodEnum::BILLING_ADDRESS );
		}

		return (string) new ShippingMethodEnum( ShippingMethodEnum::DIFFERENT_THAN_BILLING_ADDRESS );
	}

	// Card Holder Account Info //////////////

	private function getCardHolderAccountInfo( $order ) {
		$info = new CardHolderAccountInfo();
		if ( $this->guest ) {
			$info->setAccountAge( (string) new AccountAgeEnum( AccountAgeEnum::NO_ACCOUNT ) );

			return $info;
		}

		$user_data     = get_userdata( $this->user->ID );
		$account_date  = new DateTime( $user_data->user_registered );
		$customer      = new WC_Customer();
		$date_modified = $customer->get_date_modified();

		$info->setAccountAge( $this->getAccountAge( $account_date ) );

		$output_format = 'Y-m-d';

		$info->setAccountCreationDate( $account_date->format( $output_format ) );
		if ( $date_modified ) {
			$info->setAccountChangeDate( $date_modified->format( $output_format ) );
			// @Uncompatible WordPress doesnt seem to have a data of when the password was changed
			$info->setPasswordChangeDate( $date_modified->format( $output_format ) );
		}
		// @TODO: Implement CardAtempts
		/*
		$info->provisioningAttemptsDay = CardAttempts::getAttemptsByUserBetweenDates(
			$customer->id,
			new DateTime(),
			(new DateTime())->sub(new DateInterval('P1D'))
		);
		$info->purchasesLast6Months = CardAttempts::getAttemptsByUserBetweenDates(
			$customer->id,
			new DateTime(),
			(new DateTime())->sub(new DateInterval('P6M'))
		);*/
		// @TODO: Implement
		// $info->shippingAddressUsage = (new ShippingAddressUsage($this->getCart()))();
		$info->setShippingNameInd( $this->getShippingAddressNameInd( $order ) );
		$info->setSuspiciousAccountActivityInd( (string) new SuspiciousAccountActivityIndEnum() );
		// @TODO: Implement the transaction methods
		/*
		$info->txnActivityDay = Transaction::getTransactionsAttemptsByUserBetweenDates(
			$customer->id,
			(new DateTime()),
			(new DateTime())->sub(new DateInterval('P1D'))
		);
		$info->txnActivityYear = Transaction::getTransactionsAttemptsByUserBetweenDates(
			$customer->id,
			(new DateTime()),
			(new DateTime())->sub(new DateInterval('P1Y'))
		);*/

		return $info;
	}

	private function getAccountAge( $accountDate ) {
		$diff = $accountDate->diff( new DateTime() );
		if ( $diff->d > 60 ) {
			return (string) new AccountAgeEnum( AccountAgeEnum::MORE_THAN60_DAYS );
		}
		if ( $diff->d >= 30 ) {
			return (string) new AccountAgeEnum( AccountAgeEnum::FROM30_TO60_DAYS );
		}
		if ( $diff->d > 1 || $diff->h >= 1 ) {
			return (string) new AccountAgeEnum( AccountAgeEnum::LESS_THAN30_DAYS );
		}

		return (string) new AccountAgeEnum( AccountAgeEnum::CREATED_DURING_TRANSACTION );
	}

	private function getShippingAddressNameInd( $order ) {
		if ( $order->get_billing_first_name()
		     === $order->get_shipping_first_name()
		     && $order->get_billing_last_name()
		        === $order->get_shipping_last_name() ) {
			return (string) new ShippingNameIndEnum( ShippingNameIndEnum::IDENTICAL );
		}

		return (string) new ShippingNameIndEnum( ShippingNameIndEnum::DIFFERENT );
	}

	// Shipping address //////////////

	/**
	 * Get shipping address
	 *
	 * @param Order $order
	 *
	 * @return ShippingAddress
	 */
	private function getShippingAddress( $order ) {
		$shipping_address = new ShippingAddress();

		$shipping_address->setAddress1( $order->get_shipping_address_1() );
		$shipping_address->setAddress2( $order->get_shipping_address_2() );
		$shipping_address->setCity( $order->get_shipping_city() );
		$shipping_address->setZipCode( $order->get_shipping_postcode() );
		$shipping_address->setCountry( $order->get_shipping_country() );
		$shipping_address->setPhone( $order->get_billing_phone() );

		$shipping_method = $this->getShippingMethod( $order );
		if ( ShippingMethodEnum::DIGITAL_GOODS === $shipping_method ) {
			$shipping_address->setDeliveryEmail( $order->get_billing_email() );
		}

		$shipping_address->setBillAddressMatchYN(
			(string) new BillAddressMatchEnum(
				ShippingMethodEnum::BILLING_ADDRESS === $shipping_method
				? BillAddressMatchEnum::YES
				: BillAddressMatchEnum::NO
			)
		);

		return $shipping_address;
	}

	/**
	 * Get id of customer
	 *
	 * @return string
	 */
	public function get_customer_id() {
		return $this->customer_id;
	}

	/**
	 * Get guest
	 */
	public function get_guest() {
		return $this->guest;
	}

	/**
	 * Check for line mode
	 *
	 * @return bool
	 */
	public function getLiveMode() {
		return 'yes' === $this->settings['live_mode'];
	}

	/**
	 * @return string
	 */
	public function getBackOfficeLink() {
		return $this->getLiveMode()
			? ApiClientInterface::BACKOFFICE_LIVE
			: ApiClientInterface::BACKOFFICE_STAGING;
	}

	/**
	 * Return i18n data
	 *
	 * @param string $langcode Current lang-code.
	 *
	 * @return mixed
	 */
	public function getI18n( string $langcode ) {
		$i18n = EPG_BASE_DIR . '/data/i18n/' . $langcode . '.json';

		if ( ! is_readable( $i18n ) ) {
			$i18n = EPG_BASE_DIR . '/data/i18n/en.json';
		}

		return json_decode( file_get_contents( $i18n ), true );
	}

	/**
	 * Check product_id settings
	 *
	 * @return bool
	 */
	public function is_configured() {
		return ! empty( $this->settings['product_id'] );
	}

	/**
	 * Check for epgjs library.
	 *
	 * @return bool
	 */
	public function hc_js_library() {
		$url = EPGJs::getEndpoints( $this->getLiveMode() )['render'];

		$context = stream_context_create(
			array(
				'http' => array(
					'method' => 'HEAD',
				),
			)
		);
		$headers = get_headers( $url, false, $context );

		return false !== stripos( $headers[0], '200 OK' );
	}

	/**
	 * Check for authorization
	 *
	 * @return bool
	 */
	public function hc_web_api(): bool {
		return null !== $this->get_auth_token();
	}

	/**
	 * Return Hashids instance.
	 *
	 * @return Hashids
	 */
	private function get_hash_ids(): Hashids {
		return new Hashids( SECURE_AUTH_SALT );
	}

	/**
	 * 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 ( WP_DEBUG_LOG ) {
			if ( empty( self::$log ) ) {
				self::$log = wc_get_logger();
			}
			self::$log->log(
				$level,
				$message,
				array( 'source' => 'addonpayments' )
			);
		}
	}

	/**
	 * PaysolExtendedData for Nemuru
	 *
	 * @param $typeFinancing
	 * @param $order
	 *
	 * @return array
	 */
	public function paysol_extended_data($typeFinancing, $order): array {
		$wcHelper = new AP_Quix_Helper();
		$cart 		= $wcHelper->getArrCart($order);
		$billing	= $wcHelper->getArrBilling($order);
		$shipping	= $wcHelper->getArrShipping($order);

		return [
			'confirmation_cart_data'	=> 	new stdClass(),
			'product'					=> 	$typeFinancing,
			'cart'						=> 	$cart,
			'billing'					=>	$billing,
			'shipping'					=> 	$shipping
		];
	}

	/**
	 * Webhook for callback (status, error, cancel)
	 *
	 * @return void
	 */
	public function webhook_callback()  {

		if ( ! isset( $_GET['payment'] ) || empty( $_GET['payment'] ) ) {
			throw new Exception( 'The context is not valid' );
		}

		[ $order_id, $trx_count, $status, $from ] = $this->get_hash_ids()->decode( $_GET['payment'] );
		$merchant_trx_id = $this->get_hash_ids()->encode( $order_id, $trx_count );

		(new AP_Callback_Controller($this))($merchant_trx_id, $status, $from, $order_id);
	}

	/**
	 * Webhook for status
	 *
	 * @return void
	 */
	public function webhook_status()  {

		if ( ! isset( $_GET['payment'] ) || empty( $_GET['payment'] ) ) {
			throw new Exception( 'The context is not valid' );
		}

		[ $order_id, $trx ] = $this->get_hash_ids()->decode( $_GET['payment'] );

		(new AP_Status_Controller())( $order_id, $trx );

	}
}
