<?php
/**
 * Class WC_Payments_Express_Checkout_Button_Handler
 * Adds support for Apple Pay, Google Pay and ECE API buttons.
 * Utilizes the Stripe Express Checkout Element to support checkout from the product detail and cart pages.
 *
 * @package WooCommerce\Payments
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

use Automattic\WooCommerce\Blocks\Package;
use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry;
use WCPay\Fraud_Prevention\Fraud_Prevention_Service;

/**
 * WC_Payments_Express_Checkout_Button_Handler class.
 */
class WC_Payments_Express_Checkout_Button_Handler {
	const DEFAULT_BORDER_RADIUS_IN_PX = 4;

	/**
	 * WC_Payments_Account instance to get information about the account
	 *
	 * @var WC_Payments_Account
	 */
	private $account;

	/**
	 * WC_Payment_Gateway_WCPay instance.
	 *
	 * @var WC_Payment_Gateway_WCPay
	 */
	private $gateway;

	/**
	 * Express Checkout Ajax Handle instance.
	 *
	 * @var WC_Payments_Express_Checkout_Button_Helper
	 */
	private $express_checkout_helper;

	/**
	 * Express Checkout Helper instance.
	 *
	 * @var WC_Payments_Express_Checkout_Ajax_Handler
	 */
	private $express_checkout_ajax_handler;

	/**
	 * Initialize class actions.
	 *
	 * @param WC_Payments_Account                        $account Account information.
	 * @param WC_Payment_Gateway_WCPay                   $gateway WCPay gateway.
	 * @param WC_Payments_Express_Checkout_Button_Helper $express_checkout_helper Express checkout helper.
	 * @param WC_Payments_Express_Checkout_Ajax_Handler  $express_checkout_ajax_handler Express checkout ajax handler.
	 */
	public function __construct( WC_Payments_Account $account, WC_Payment_Gateway_WCPay $gateway, WC_Payments_Express_Checkout_Button_Helper $express_checkout_helper, WC_Payments_Express_Checkout_Ajax_Handler $express_checkout_ajax_handler ) {
		$this->account                       = $account;
		$this->gateway                       = $gateway;
		$this->express_checkout_helper       = $express_checkout_helper;
		$this->express_checkout_ajax_handler = $express_checkout_ajax_handler;
	}

	/**
	 * Initialize hooks.
	 *
	 * @return  void
	 */
	public function init() {
		// Checks if WCPay is enabled.
		if ( ! $this->gateway->is_enabled() ) {
			return;
		}

		// Checks if at least one express checkout method is enabled.
		if ( ! $this->gateway->is_payment_request_enabled() && ! $this->express_checkout_helper->can_use_amazon_pay() ) {
			return;
		}

		// Don't load for change payment method page.
		if ( isset( $_GET['change_payment_method'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
			return;
		}

		add_action( 'template_redirect', [ $this, 'set_session' ] );
		add_action( 'wcpay_payment_fields_js_config', [ $this, 'payment_fields_js_config' ] );
		add_action( 'template_redirect', [ $this, 'handle_express_checkout_redirect' ] );
		add_filter( 'woocommerce_login_redirect', [ $this, 'get_login_redirect_url' ], 10, 3 );
		add_filter( 'woocommerce_registration_redirect', [ $this, 'get_login_redirect_url' ], 10, 3 );
		add_filter( 'woocommerce_cart_needs_shipping_address', [ $this, 'filter_cart_needs_shipping_address' ], 11, 1 );
		add_action( 'wp_enqueue_scripts', [ $this, 'scripts' ] );
		add_filter( 'woocommerce_gateway_title', [ $this, 'filter_gateway_title' ], 10, 2 );

		$this->express_checkout_ajax_handler->init();

		if ( is_admin() && current_user_can( 'manage_woocommerce' ) ) {
			$this->register_ece_data_for_block_editor();
		}
	}

	/**
	 * Appends express-checkout-related data to the JS configuration used during checkout.
	 *
	 * @param array $config The configuration to be provided to the JS.
	 *
	 * @return mixed
	 */
	public function payment_fields_js_config( $config ) {
		$config['isPaymentRequestEnabled'] = $this->gateway->is_payment_request_enabled() && $this->express_checkout_helper->is_express_checkout_method_enabled_at( 'checkout', 'payment_request' );
		$config['isAmazonPayEnabled']      = $this->express_checkout_helper->can_use_amazon_pay() && $this->express_checkout_helper->is_express_checkout_method_enabled_at( 'checkout', 'amazon_pay' );

		return $config;
	}

	/**
	 * The settings for the `button` attribute - they depend on the "grouped settings" flag value.
	 *
	 * @return array
	 */
	public function get_button_settings() {
		$button_type                      = $this->gateway->get_option( 'payment_request_button_type' );
		$common_settings                  = $this->express_checkout_helper->get_common_button_settings();
		$express_checkout_button_settings = [
			// Default format is en_US.
			'locale'       => apply_filters( 'wcpay_payment_request_button_locale', substr( get_locale(), 0, 2 ) ),
			'branded_type' => 'default' === $button_type ? 'short' : 'long',
		];

		return array_merge( $common_settings, $express_checkout_button_settings );
	}

	/**
	 * Settings array for the user authentication dialog and redirection.
	 *
	 * @return array|false
	 */
	public function get_login_confirmation_settings() {
		if ( is_user_logged_in() || ! $this->is_authentication_required() ) {
			return false;
		}

		/* translators: The text encapsulated in `**` can be replaced with "Apple Pay" or "Google Pay". Please translate this text, but don't remove the `**`. */
		$message      = __( 'To complete your transaction with **the selected payment method**, you must log in or create an account with our site.', 'woocommerce-payments' );
		$redirect_url = add_query_arg(
			[
				'_wpnonce'                            => wp_create_nonce( 'wcpay-set-redirect-url' ),
				'wcpay_express_checkout_redirect_url' => rawurlencode( home_url( add_query_arg( [] ) ) ),
				// Current URL to redirect to after login.
			],
			home_url()
		);

		return [ // nosemgrep: audit.php.wp.security.xss.query-arg -- home_url passed in to add_query_arg.
			'message'      => $message,
			'redirect_url' => $redirect_url,
		];
	}

	/**
	 * Checks whether authentication is required for checkout.
	 *
	 * @return bool
	 */
	public function is_authentication_required() {
		// If guest checkout is disabled and account creation is not possible, authentication is required.
		if ( 'no' === get_option( 'woocommerce_enable_guest_checkout', 'yes' ) && ! $this->is_account_creation_possible() ) {
			return true;
		}
		// If cart contains subscription and account creation is not posible, authentication is required.
		if ( $this->has_subscription_product() && ! $this->is_account_creation_possible() ) {
			return true;
		}

		return false;
	}

	/**
	 * Checks whether cart contains a subscription product or this is a subscription product page.
	 *
	 * @return boolean
	 */
	public function has_subscription_product() {
		if ( ! class_exists( 'WC_Subscriptions_Product' ) || ! class_exists( 'WC_Subscriptions_Cart' ) ) {
			return false;
		}

		if ( $this->express_checkout_helper->is_product() ) {
			$product = $this->express_checkout_helper->get_product();
			if ( WC_Subscriptions_Product::is_subscription( $product ) ) {
				return true;
			}
		}

		if ( $this->express_checkout_helper->is_checkout() || $this->express_checkout_helper->is_cart() ) {
			if ( WC_Subscriptions_Cart::cart_contains_subscription() ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Checks whether account creation is possible during checkout.
	 *
	 * @return bool
	 */
	public function is_account_creation_possible() {
		$is_signup_from_checkout_allowed = 'yes' === get_option( 'woocommerce_enable_signup_and_login_from_checkout', 'no' );

		// If a subscription is being purchased, check if account creation is allowed for subscriptions.
		if ( ! $is_signup_from_checkout_allowed && $this->has_subscription_product() ) {
			$is_signup_from_checkout_allowed = 'yes' === get_option( 'woocommerce_enable_signup_from_checkout_for_subscriptions', 'no' );
		}

		// If automatically generate username/password are disabled, the Express Checkout API
		// can't include any of those fields, so account creation is not possible.
		return (
			$is_signup_from_checkout_allowed &&
			'yes' === get_option( 'woocommerce_registration_generate_username', 'yes' ) &&
			'yes' === get_option( 'woocommerce_registration_generate_password', 'yes' )
		);
	}

	/**
	 * Gets the parameters needed for Express Checkout functionality.
	 *
	 * @return array Parameters for Express Checkout.
	 */
	public function get_express_checkout_params() {
		/**
		 * Allowing some specific configuration to be tweaked by 3pd.
		 *
		 * @since 9.5.0
		 */
		return array_merge(
			apply_filters(
				'wcpay_express_checkout_js_params',
				[
					'ajax_url'           => admin_url( 'admin-ajax.php' ),
					'wc_ajax_url'        => WC_AJAX::get_endpoint( '%%endpoint%%' ),
					'nonce'              => [
						'platform_tracker'             => wp_create_nonce( 'platform_tracks_nonce' ),
						// needed to communicate via the Store API.
						'tokenized_cart_nonce'         => wp_create_nonce( 'woopayments_tokenized_cart_nonce' ),
						'tokenized_cart_session_nonce' => wp_create_nonce( 'woopayments_tokenized_cart_session_nonce' ),
						'store_api_nonce'              => wp_create_nonce( 'wc_store_api' ),
					],
					'checkout'           => [
						'currency_code'              => strtolower( get_woocommerce_currency() ),
						'currency_decimals'          => WC_Payments::get_localization_service()->get_currency_format( get_woocommerce_currency() )['num_decimals'],
						'country_code'               => substr( get_option( 'woocommerce_default_country' ), 0, 2 ),
						'needs_shipping'             => WC()->cart->needs_shipping(),
						// Defaults to 'required' to match how core initializes this option.
						'needs_payer_phone'          => 'required' === get_option( 'woocommerce_checkout_phone_field', 'required' ),
						'allowed_shipping_countries' => array_keys( WC()->countries->get_shipping_countries() ?? [] ),
						'display_prices_with_tax'    => 'incl' === get_option( 'woocommerce_tax_display_cart' ),
					],
					'has_subscription'   => $this->has_subscription_product(),
					'button'             => $this->get_button_settings(),
					'login_confirmation' => $this->get_login_confirmation_settings(),
					'button_context'     => $this->express_checkout_helper->get_button_context(),
					'has_block'          => has_block( 'woocommerce/cart' ) || has_block( 'woocommerce/checkout' ),
					'product'            => $this->express_checkout_helper->get_product_data(),
					'store_name'         => get_bloginfo( 'name' ),
					'enabled_methods'    => $this->express_checkout_helper->get_enabled_express_checkout_methods_for_context(),
				]
			),
			[
				// placing these outside of the filter to prevent modification of the values.
				'stripe' => [
					'publishableKey' => $this->account->get_publishable_key( WC_Payments::mode()->is_test() ),
					'accountId'      => $this->account->get_stripe_account_id(),
					'locale'         => WC_Payments_Utils::convert_to_stripe_locale( get_locale() ),
				],
				'flags'  => [
					'isEceUsingConfirmationTokens' => WC_Payments_Features::is_ece_confirmation_tokens_enabled(),
				],
			]
		);
	}

	/**
	 * Load public scripts and styles.
	 */
	public function scripts() {
		// Don't load scripts if page is not supported.
		if ( ! $this->express_checkout_helper->should_show_express_checkout_button() ) {
			return;
		}

		$express_checkout_params = $this->get_express_checkout_params();

		WC_Payments::register_script_with_dependencies(
			'WCPAY_EXPRESS_CHECKOUT_ECE',
			'dist/express-checkout',
			[
				'jquery',
				'stripe',
			]
		);

		WC_Payments_Utils::enqueue_style(
			'WCPAY_EXPRESS_CHECKOUT_ECE',
			plugins_url( 'dist/express-checkout.css', WCPAY_PLUGIN_FILE ),
			[],
			WC_Payments::get_file_version( 'dist/express-checkout.css' )
		);

		wp_localize_script( 'WCPAY_EXPRESS_CHECKOUT_ECE', 'wcpayExpressCheckoutParams', $express_checkout_params );
		wp_localize_script( 'WCPAY_BLOCKS_CHECKOUT', 'wcpayExpressCheckoutParams', $express_checkout_params );

		wp_set_script_translations( 'WCPAY_EXPRESS_CHECKOUT_ECE', 'woocommerce-payments' );

		wp_enqueue_script( 'WCPAY_EXPRESS_CHECKOUT_ECE' );

		Fraud_Prevention_Service::maybe_append_fraud_prevention_token();

		$gateways = WC()->payment_gateways->get_available_payment_gateways();
		if ( isset( $gateways['woocommerce_payments'] ) ) {
			WC_Payments::get_wc_payments_checkout()->register_scripts();
		}
	}

	/**
	 * Display the express checkout button.
	 */
	public function display_express_checkout_button_html() {
		if ( ! $this->express_checkout_helper->should_show_express_checkout_button() ) {
			return;
		}
		?>
		<div id="wcpay-express-checkout-element"></div>
		<?php
	}

	/**
	 * Sets the WC customer session if one is not set.
	 * This is needed so nonces can be verified by AJAX Request.
	 *
	 * @return void
	 */
	public function set_session() {
		// Skip if there's already an active WC session. Otherwise, only set session cookies on checkout and cart pages.
		// This helps with caching as product pages can be cached when no cookies are present.
		$has_active_session = isset( WC()->session ) && WC()->session->has_session();
		if ( $has_active_session ) {
			return;
		}

		$is_checkout_or_cart = $this->express_checkout_helper->is_checkout() || $this->express_checkout_helper->is_cart();
		if ( ! $is_checkout_or_cart ) {
			return;
		}

		WC()->session->set_customer_session_cookie( true );
	}

	/**
	 * Handles express checkout redirect when the redirect dialog "Continue" button is clicked.
	 */
	public function handle_express_checkout_redirect() {
		if (
			! empty( $_GET['wcpay_express_checkout_redirect_url'] )
			&& ! empty( $_GET['_wpnonce'] )
			&& wp_verify_nonce( $_GET['_wpnonce'], 'wcpay-set-redirect-url' ) // @codingStandardsIgnoreLine
		) {
			$url = rawurldecode( esc_url_raw( wp_unslash( $_GET['wcpay_express_checkout_redirect_url'] ) ) );
			// Sets a redirect URL cookie for 10 minutes, which we will redirect to after authentication.
			// Users will have a 10 minute timeout to login/create account, otherwise redirect URL expires.
			wc_setcookie( 'wcpay_express_checkout_redirect_url', $url, time() + MINUTE_IN_SECONDS * 10 );
			// Redirects to "my-account" page.
			wp_safe_redirect( get_permalink( get_option( 'woocommerce_myaccount_page_id' ) ) );
		}
	}

	/**
	 * Returns the login redirect URL.
	 *
	 * @param string $redirect Default redirect URL.
	 *
	 * @return string Redirect URL.
	 */
	public function get_login_redirect_url( $redirect ) {
		$url = esc_url_raw( wp_unslash( $_COOKIE['wcpay_express_checkout_redirect_url'] ?? '' ) );

		if ( empty( $url ) ) {
			return $redirect;
		}
		wc_setcookie( 'wcpay_express_checkout_redirect_url', '' );

		return $url;
	}


	/**
	 * Determine whether to filter the cart needs shipping address.
	 *
	 * @param boolean $needs_shipping_address Whether the cart needs a shipping address.
	 */
	public function filter_cart_needs_shipping_address( $needs_shipping_address ) {
		if ( $this->has_subscription_product() && wc_get_shipping_method_count( true, true ) === 0 ) {
			return false;
		}

		return $needs_shipping_address;
	}

	/**
	 * Filters the gateway title to reflect the button type used.
	 *
	 * @param string $title Gateway title.
	 * @param string $id Gateway ID.
	 */
	public function filter_gateway_title( $title, $id ) {
		if ( 'woocommerce_payments' !== $id || ! is_admin() ) {
			return $title;
		}

		$order        = $this->express_checkout_helper->get_current_order();
		$method_title = is_object( $order ) ? $order->get_payment_method_title() : '';

		if ( ! empty( $method_title ) ) {
			if (
				strpos( $method_title, 'Apple Pay' ) === 0
				|| strpos( $method_title, 'Google Pay' ) === 0
				|| strpos( $method_title, 'Payment Request' ) === 0 // Legacy PRB title.
			) {
				return $method_title;
			}
		}

		return $title;
	}

	/**
	 * Add ECE data to `wcSettings` to allow it to be accessed by the front-end JS script in the Block editor.
	 *
	 * @return void
	 */
	private function register_ece_data_for_block_editor() {
		$data_registry = Package::container()->get( AssetDataRegistry::class );

		if ( $data_registry->exists( 'ece_data' ) ) {
			return;
		}

		$data_registry->add(
			'ece_data',
			[
				'button' => $this->get_button_settings(),
			]
		);
	}
}
