<?php
/**
 * PayPal Reference Transaction API Class
 *
 * Performs reference transaction related transactions requests via the PayPal Express Checkout API,
 * including the creation of a billing agreement and processing renewal payments using that billing
 * agremeent's ID in a reference tranasction.
 *
 * Also hijacks checkout when PayPal Standard is chosen as the payment method, but Reference Transactions
 * are enabled on the store's PayPal account, to go via Express Checkout approval flow instead of the
 * PayPal Standard checkout flow.
 *
 * Heavily inspired by the WC_Paypal_Express_API class developed by the masterful SkyVerge team
 *
 * @package   smart-offers/modules/buy-now/php/gateways/paypal-standard/includes/
 * @since     4.0.0
 * @version   1.0.0
 *
 * Credit: Prospress
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly.
}

/**
 * Class for performing reference transaction related transactions requests via the PayPal Express Checkout API,
 * including the creation of a billing agreement and processing renewal payments using that billing
 * agremeent's ID in a reference tranasction.
 */
class SA_Buy_Now_WC_PayPal_Reference_Transaction_API extends SA_Buy_Now_WC_SV_API_Base {

	/** The production endpoint */
	const PRODUCTION_ENDPOINT = 'https://api-3t.paypal.com/nvp';

	/** The sandbox endpoint */
	const SANDBOX_ENDPOINT = 'https://api-3t.sandbox.paypal.com/nvp';

	/** NVP API version */
	const VERSION = '124';

	/**
	 * URL-decoded and parsed parameters
	 *
	 * @var array $parameters
	 */
	private $parameters = array();

	/**
	 * Constructor - setup request object and set endpoint
	 *
	 * @param string $gateway_id gateway ID for this request.
	 * @param string $api_environment the API environment.
	 * @param string $api_username the API username.
	 * @param string $api_password the API password.
	 * @param string $api_signature the API signature.
	 * @since 2.0
	 */
	public function __construct( $gateway_id = '', $api_environment = '', $api_username = '', $api_password = '', $api_signature = '' ) {

		// tie API to gateway.
		$this->gateway_id = $gateway_id;

		// request URI does not vary per-request.
		$this->request_uri = ( 'production' === $api_environment ) ? self::PRODUCTION_ENDPOINT : self::SANDBOX_ENDPOINT;

		// PayPal requires HTTP 1.1.
		$this->request_http_version = '1.1';

		$this->api_username  = $api_username;
		$this->api_password  = $api_password;
		$this->api_signature = $api_signature;
	}

	/**
	 * Get PayPal URL parameters for the checkout URL
	 *
	 * @param array    $paypal_args Paypal arguments for transaction request.
	 * @param WC_Order $order WooCommerce order object.
	 * @return array
	 * @since 2.0
	 */
	public function get_paypal_args( $paypal_args = array(), $order = object ) {

		$request = $this->get_new_request();

		// First we need to request an express checkout token for setting up a billing agreement, to do that, we need to pull details of the transaction from the PayPal Standard args and massage them into the Express Checkout params.
		$response = $this->set_express_checkout(
			array(
				'currency'   => $paypal_args['currency_code'],
				'return_url' => $this->get_callback_url( 'create_billing_agreement' ),
				'cancel_url' => $paypal_args['cancel_return'],
				'notify_url' => $paypal_args['notify_url'],
				'custom'     => $paypal_args['custom'],
				'order'      => $order,
			)
		);

		$paypal_args = array(
			'cmd'   => '_express-checkout',
			'token' => $response->get_token(),
		);

		return $paypal_args;
	}

	/**
	 * Check account for reference transaction support
	 *
	 * For reference transactions to be enabled, we need to be able to setup a dummy SetExpressCheckout request without receiving any APIs errors.
	 * This ensures there are no API credentials errors (e.g. error code 10008: "Security header is not valid") as well as testing the account for
	 * reference transaction support. If the account does not have reference transaction support enabled, PayPal will return the error code
	 * error code 11452: "Merchant not enabled for reference transactions".
	 *
	 * @link https://developer.paypal.com/docs/classic/api/errorcodes/#id09C3G0PJ0N9__id5e8c50e9-4f1b-462a-8586-399b63b07f1a
	 *
	 * @return bool
	 * @since 2.0
	 */
	public function are_reference_transactions_enabled() {

		$request = $this->get_new_request();

		$request->set_express_checkout(
			array(
				// As we are only testing for whether billing agreements are allowed, we don't need to se any other details.
				'currency'   => get_woocommerce_currency(),
				'return_url' => $this->get_callback_url( 'reference_transaction_account_check' ),
				'cancel_url' => $this->get_callback_url( 'reference_transaction_account_check' ),
				'notify_url' => $this->get_callback_url( 'reference_transaction_account_check' ),
			)
		);

		$this->set_response_handler( 'SA_Buy_Now_WC_PayPal_Reference_Transaction_API_Response' );

		try {
			$response = $this->perform_request( $request );

			if ( ! $response->has_api_error() ) {
				$reference_transactions_enabled = true;
			} else {
				$reference_transactions_enabled = false;

				// And set a flag to display invalid credentials notice.
				if ( $response->has_api_error_for_credentials() ) {
					update_option( 'sa_bn_wc_paypal_credentials_error', 'yes', 'no' );
				}
			}
		} catch ( Exception $e ) {
			$reference_transactions_enabled = false;
		}

		return $reference_transactions_enabled;
	}

	/**
	 * Set Express Checkout
	 *
	 * @param array $args @see WCS_PayPal_Reference_Transaction_API_Request::set_express_checkout() for details.
	 * @throws Exception Network timeouts, etc.
	 * @return SA_Buy_Now_WC_PayPal_Reference_Transaction_API_Response_Checkout response object
	 * @since 2.0
	 */
	public function set_express_checkout( $args = array() ) {

		$request = $this->get_new_request();

		$request->set_express_checkout( $args );

		$this->set_response_handler( 'SA_Buy_Now_WC_PayPal_Reference_Transaction_API_Response_Checkout' );

		return $this->perform_request( $request );
	}

	/**
	 * Create a billing agreement, required when a subscription sign-up has no initial payment
	 *
	 * @link https://developer.paypal.com/docs/classic/express-checkout/integration-guide/ECReferenceTxns/#id094TB0Y0J5Z__id094TB4003HS
	 * @link https://developer.paypal.com/docs/classic/api/merchant/CreateBillingAgreement_API_Operation_NVP/
	 *
	 * @param string $token token from SetExpressCheckout response.
	 * @return SA_Buy_Now_WC_PayPal_Reference_Transaction_API_Response_Billing_Agreement response object
	 * @since 2.0
	 */
	public function create_billing_agreement( $token = '' ) {

		$request = $this->get_new_request();

		$request->create_billing_agreement( $token );

		$this->set_response_handler( 'SA_Buy_Now_WC_PayPal_Reference_Transaction_API_Response_Billing_Agreement' );

		return $this->perform_request( $request );
	}

	/**
	 * Get Express Checkout Details
	 *
	 * @param string $token Token from set_express_checkout response.
	 * @return WC_PayPal_Reference_Transaction_API_Checkout_Response response object
	 * @throws Exception Network timeouts, etc.
	 * @since 2.0
	 */
	public function get_express_checkout_details( $token = '' ) {

		$request = $this->get_new_request();

		$request->get_express_checkout_details( $token );

		$this->set_response_handler( 'SA_Buy_Now_WC_PayPal_Reference_Transaction_API_Response_Checkout' );

		return $this->perform_request( $request );
	}

	/**
	 * Process an express checkout payment and billing agreement creation
	 *
	 * @param string   $token PayPal Express Checkout token returned by SetExpressCheckout operation.
	 * @param WC_Order $order order object.
	 * @param array    $args arguments for express checkout request.
	 * @return SA_Buy_Now_WC_PayPal_Reference_Transaction_API_Response_Payment refund response
	 * @since 2.0.9
	 */
	public function do_express_checkout( $token = '', $order = object, $args = array() ) {

		$this->order = $order;

		$request = $this->get_new_request();

		$request->do_express_checkout( $token, $order, $args );

		$this->set_response_handler( 'SA_Buy_Now_WC_PayPal_Reference_Transaction_API_Response_Payment' );

		return $this->perform_request( $request );
	}

	/**
	 * Perform a reference transaction for the given order
	 *
	 * @see SV_WC_Payment_Gateway_API::refund()
	 * @param string   $reference_id billing agreement id.
	 * @param WC_Order $order order object.
	 * @param array    $args request array.
	 * @return SV_WC_Payment_Gateway_API_Response refund response
	 * @throws SV_WC_Payment_Gateway_Exception Network timeouts, etc.
	 * @since 2.0
	 */
	public function do_reference_transaction( $reference_id = '', $order = object, $args = array() ) {

		$this->order = $order;

		$request = $this->get_new_request();

		$request->do_reference_transaction( $reference_id, $order, $args );

		// This class handles prefix issue in do reference transaction request since they do not have any prefix as compared to do express checkout response.
		$this->set_response_handler( 'SA_Buy_Now_WC_PayPal_Do_Reference_Transaction_API_Response_Payment' );

		return $this->perform_request( $request );
	}

	/**
	 * Change the status of a subscription for a given order/profile ID
	 *
	 * @since 2.0.0
	 * @see SV_WC_Payment_Gateway_API::refund()
	 * @param string   $profile_id billing agreement id.
	 * @param string   $new_status new profile status.
	 * @param WC_Order $order order object.
	 * @return SV_WC_Payment_Gateway_API_Response refund response
	 * @throws SV_WC_Payment_Gateway_Exception Network timeouts, etc.
	 */
	public function manage_recurring_payments_profile_status( $profile_id = '', $new_status = '', $order = object ) {

		$this->order = $order;

		$request = $this->get_new_request();

		$request->manage_recurring_payments_profile_status( $profile_id, $new_status, $order );

		$this->set_response_handler( 'SA_Buy_Now_WC_PayPal_Reference_Transaction_API_Response_Checkout' );

		return $this->perform_request( $request );
	}

	/** Helper methods ******************************************************/

	/**
	 * Get the wc-api URL to redirect to
	 *
	 * @param string $action checkout action, either `set_express_checkout or `get_express_checkout_details`.
	 * @return string URL
	 * @since 2.0
	 */
	public function get_callback_url( $action = '' ) {
		return add_query_arg( 'action', $action, WC()->api_request_url( 'sa_bn_wc_paypal' ) );
	}

	/**
	 * Builds and returns a new API request object
	 *
	 * @see \WCS_SV_API_Base::get_new_request()
	 * @param array $args request arguments.
	 * @return WC_PayPal_Reference_Transaction_API_Request API request object
	 * @since 2.0
	 */
	protected function get_new_request( $args = array() ) {
		return new SA_Buy_Now_WC_PayPal_Reference_Transaction_API_Request( $this->api_username, $this->api_password, $this->api_signature, self::VERSION );
	}

	/**
	 * Supposed to return the main gatewya plugin class, but we don't have one of those
	 *
	 * @see \WCS_SV_API_Base::get_plugin()
	 * @return object
	 * @since 2.0
	 */
	protected function get_plugin() {
		return SA_Buy_Now_WC_Paypal_Standard::instance();
	}
}
