<?php
/**
 * PeachPay Stripe util class.
 *
 * @package PeachPay
 */

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

/**
 * .
 */
final class PeachPay_Stripe {

	const SUPPORTED_CURRENCIES = array(
		'USD',
		'AED',
		'AFN',
		'ALL',
		'AMD',
		'ANG',
		'AOA',
		'ARS',
		'AUD',
		'AWG',
		'AZN',
		'BAM',
		'BBD',
		'BDT',
		'BGN',
		'BIF',
		'BMD',
		'BND',
		'BOB',
		'BRL',
		'BSD',
		'BWP',
		'BYN',
		'BZD',
		'CAD',
		'CDF',
		'CHF',
		'CLP',
		'CNY',
		'COP',
		'CRC',
		'CVE',
		'CZK',
		'DJF',
		'DKK',
		'DOP',
		'DZD',
		'EGP',
		'ETB',
		'EUR',
		'FJD',
		'FKP',
		'GBP',
		'GEL',
		'GIP',
		'GMD',
		'GNF',
		'GTQ',
		'GYD',
		'HKD',
		'HNL',
		'HRK',
		'HTG',
		'HUF',
		'IDR',
		'ILS',
		'INR',
		'ISK',
		'JMD',
		'JPY',
		'KES',
		'KGS',
		'KHR',
		'KMF',
		'KRW',
		'KYD',
		'KZT',
		'LAK',
		'LBP',
		'LKR',
		'LRD',
		'LSL',
		'MAD',
		'MDL',
		'MGA',
		'MKD',
		'MMK',
		'MNT',
		'MOP',
		'MRO',
		'MUR',
		'MVR',
		'MWK',
		'MXN',
		'MYR',
		'MZN',
		'NAD',
		'NGN',
		'NIO',
		'NOK',
		'NPR',
		'NZD',
		'PAB',
		'PEN',
		'PGK',
		'PHP',
		'PKR',
		'PLN',
		'PYG',
		'QAR',
		'RON',
		'RSD',
		'RUB',
		'RWF',
		'SAR',
		'SBD',
		'SCR',
		'SEK',
		'SGD',
		'SHP',
		'SLE',
		'SLL',
		'SOS',
		'SRD',
		'STD',
		'SZL',
		'THB',
		'TJS',
		'TOP',
		'TRY',
		'TTD',
		'TWD',
		'TZS',
		'UAH',
		'UGX',
		'UYU',
		'UZS',
		'VND',
		'VUV',
		'WST',
		'XAF',
		'XCD',
		'XOF',
		'XPF',
		'YER',
		'ZAR',
		'ZMW',
	);

	const SUPPORTED_ZERO_DECIMAL_CURRENCIES = array(
		'BIF',
		'CLP',
		'DJF',
		'GNF',
		'JPY',
		'KMF',
		'KRW',
		'MGA',
		'PYG',
		'RWF',
		'UGX',
		'VND',
		'VUV',
		'XAF',
		'XOF',
		'XPF',
		'TWD',
	);

	/**
	 * In-request map of pending Stripe refund IDs keyed by WooCommerce order ID.
	 *
	 * Used to link Woo refund records to Stripe refund IDs during admin-initiated refunds.
	 *
	 * @var array<int,string>
	 */
	private static $pending_wc_refund_ids = array();

	/**
	 * Stores a pending Stripe refund ID for a WooCommerce order in the current request.
	 *
	 * @param int    $order_id WooCommerce order ID.
	 * @param string $refund_id Stripe refund ID.
	 */
	public static function set_pending_wc_refund_id( $order_id, $refund_id ) {
		$order_id  = absint( $order_id );
		$refund_id = is_string( $refund_id ) ? trim( $refund_id ) : '';

		if ( 0 === $order_id || '' === $refund_id ) {
			return;
		}

		self::$pending_wc_refund_ids[ $order_id ] = $refund_id;
	}

	/**
	 * Gets and clears a pending Stripe refund ID for a WooCommerce order.
	 *
	 * @param int $order_id WooCommerce order ID.
	 * @return string|null
	 */
	public static function consume_pending_wc_refund_id( $order_id ) {
		$order_id = absint( $order_id );
		if ( 0 === $order_id || ! isset( self::$pending_wc_refund_ids[ $order_id ] ) ) {
			return null;
		}

		$refund_id = self::$pending_wc_refund_ids[ $order_id ];
		unset( self::$pending_wc_refund_ids[ $order_id ] );

		return $refund_id;
	}

	/**
	 * Formats a stripe amount for displaying to merchants/customers.
	 *
	 * @param string $amount Stripe amount.
	 * @param string $currency_code Currency code returned from Stripe API.
	 */
	public static function display_amount( $amount, $currency_code ) {
		$amount = floatval( $amount );

		if ( ! in_array( $currency_code, self::SUPPORTED_ZERO_DECIMAL_CURRENCIES, true ) ) {
			$amount = $amount / 100;
		}

		return $amount;
	}

	/**
	 * Formats the statement descriptor for Stripe transactions by replacing placeholders with the order values.
	 *
	 * @param string   $descriptor_template Template that merchant has entered.
	 * @param WC_Order $order WooCommerce order object.
	 */
	public static function format_descriptor( $descriptor_template, $order ) {
		if ( ! $order instanceof WC_Order ) {
			return $descriptor_template;
		}

		$descriptor_template = str_replace( '{order_number}', $order->get_order_number(), $descriptor_template );

		$descriptor_template = str_replace( '{order_id}', $order->get_id(), $descriptor_template );

		return $descriptor_template;
	}

	/**
	 * Formats an amount to use with stripe API's
	 *
	 * @param string|float $amount The amount to format.
	 * @param string       $currency_code Currency code returned from Stripe API.
	 */
	public static function format_amount( $amount, $currency_code ) {
		$amount = floatval( $amount );

		if ( in_array( $currency_code, self::SUPPORTED_ZERO_DECIMAL_CURRENCIES, true ) ) {
			return round( $amount );
		}

		return round( $amount * 100 );
	}

	/**
	 * Builds a href for linking to a merchants stripe dashboard.
	 *
	 * @param string      $stripe_mode A live or test mode url.
	 * @param string|null $connect_id The stripe account connect id to make the link for. Leave null to take from the registration.
	 * @param string      $path The path of the URL.
	 * @param string      $title The text the link should display.
	 * @param boolean     $echo_href To echo a href of the URL.
	 */
	public static function dashboard_url( $stripe_mode, $connect_id, $path, $title = null, $echo_href = true ) {
		if ( null === $connect_id ) {
			$connect_id = PeachPay_Stripe_Integration::connect_id();
		}

		$url = 'https://dashboard.stripe.com/';

		if ( 'live' === $stripe_mode ) {
			$url = $url . "connect/accounts/$connect_id/$path";
		} else {
			$url = $url . "test/connect/accounts/$connect_id/$path";
		}

		if ( $echo_href ) {
            // PHPCS:ignore
            echo "<a href='$url' target='_blank'>$title</a>";
		}

		return $url;
	}

	/**
	 * Creates a PaymentIntent via the Stripe REST API.
	 *
	 * Replaces the former stripe/stripe-php SDK call ($stripe->paymentIntents->create()).
	 * All API communication now goes through PeachPay_Stripe_HTTP.
	 *
	 * @param WC_Order $order                 The WooCommerce order to create a PaymentIntent for.
	 * @param array    $payment_intent_params Parameters forwarded to POST /v1/payment_intents.
	 * @param array    $order_details         Order details needed to build the params (passed through).
	 * @param string   $mode                  'live' or 'test'.
	 * @return array|null Payment intent details array on success, error array on failure, null when not configured.
	 */
	public static function create_payment( $order, $payment_intent_params, $order_details, $mode ) {
		peachpay_log(
			'debug',
			'Stripe payment intent create request.',
			array(
				'event'     => 'stripe.payment_intent.create.request',
				'component' => 'payments',
				'feature'   => 'payment_intent',
				'operation' => 'create.request',
				'method'    => __METHOD__,
				'line'      => __LINE__,
				'file'      => __FILE__,
				'context'   => array( 'order_id' => $order->get_id(), 'order_number' => $order->get_order_number(), 'mode' => $mode ),
			)
		);

		// Guard: secret key must be present for the requested mode.
		if ( empty( PeachPay_Stripe_Credentials::get_secret_key( $mode ) ) ) {
			peachpay_log(
				'error',
				'Stripe payment intent create not configured.',
				array(
					'event'     => 'stripe.payment_intent.create.not_configured',
					'component' => 'payments',
					'feature'   => 'payment_intent',
					'operation' => 'create.not_configured',
					'outcome'   => 'failed',
					'method'    => __METHOD__,
					'line'      => __LINE__,
					'file'      => __FILE__,
					'context'   => array( 'order_id' => $order->get_id(), 'order_number' => $order->get_order_number(), 'mode' => $mode ),
				)
			);
			$order->update_status( 'failed', __( 'Stripe is not configured. Please add your API keys.', 'peachpay-for-woocommerce' ) );
			return null;
		}

		// Remove null / empty values and application_fee_amount (direct integration — no platform fees).
		$params = array_filter(
			$payment_intent_params,
			function ( $value ) {
				return null !== $value && '' !== $value;
			}
		);
		unset( $params['application_fee_amount'] );

		// POST /v1/payment_intents
		$payment_intent = PeachPay_Stripe_HTTP::post( '/v1/payment_intents', $params, $mode );

		if ( is_wp_error( $payment_intent ) ) {
			$error_message = $payment_intent->get_error_message();
			peachpay_log(
				'error',
				'Stripe payment intent create API error.',
				array(
					'event'     => 'stripe.payment_intent.create.api_error',
					'component' => 'payments',
					'feature'   => 'payment_intent',
					'operation' => 'create.api_error',
					'outcome'   => 'failed',
					'method'    => __METHOD__,
					'line'      => __LINE__,
					'file'      => __FILE__,
					'context'   => array( 'order_id' => $order->get_id(), 'order_number' => $order->get_order_number(), 'error_message' => $error_message, 'mode' => $mode ),
				)
			);
			if ( function_exists( 'wc_add_notice' ) ) {
				wc_add_notice( __( 'Payment error: ', 'peachpay-for-woocommerce' ) . $error_message, 'error' );
			}
			// translators: %1$s Payment method title, %2$s error message.
			$order->update_status( 'failed', sprintf( __( 'Stripe %1$s payment failed: %2$s', 'peachpay-for-woocommerce' ), $order->get_payment_method_title(), $error_message ) );
			return array(
				'error'   => true,
				'message' => $error_message,
			);
		}

		// Build payment details for state calculation.
		$payment_details = array(
			'payment_intent_details' => array(
				'id'                => $payment_intent['id'],
				'client_secret'     => $payment_intent['client_secret'],
				'status'            => $payment_intent['status'],
				'amount'            => $payment_intent['amount'],
				'amount_capturable' => $payment_intent['amount_capturable'] ?? 0,
				'currency'          => $payment_intent['currency'],
				'customer'          => $payment_intent['customer'] ?? null,
				'mode'              => $mode,
			),
		);

		// GET /v1/payment_methods/{id} — fetch payment method details when available.
		if ( ! empty( $payment_intent['payment_method'] ) ) {
			$pm = PeachPay_Stripe_HTTP::get( '/v1/payment_methods/' . $payment_intent['payment_method'], $mode );
			if ( ! is_wp_error( $pm ) ) {
				$payment_details['payment_method_details'] = array(
					'id'       => $pm['id'],
					'type'     => $pm['type'],
					'mode'     => $mode,
					'customer' => $pm['customer'] ?? null,
					'data'     => isset( $pm[ $pm['type'] ] ) ? $pm[ $pm['type'] ] : array(),
				);
			}
		}

		// GET /v1/charges/{id}?expand[]=balance_transaction — fetch charge details when available.
		if ( ! empty( $payment_intent['latest_charge'] ) ) {
			$charge = PeachPay_Stripe_HTTP::get(
				'/v1/charges/' . $payment_intent['latest_charge'],
				$mode,
				array( 'expand' => array( 'balance_transaction' ) )
			);
			if ( ! is_wp_error( $charge ) ) {
				$payment_details['charge_details'] = array(
					'id'                  => $charge['id'],
					'status'              => $charge['status'],
					'amount'              => $charge['amount'],
					'amount_refunded'     => $charge['amount_refunded'],
					'refunded'            => $charge['refunded'],
					'balance_transaction' => $charge['balance_transaction'] ?? null,
				);
			}
		}

		self::calculate_payment_state( $order, $payment_details );

		peachpay_log(
			'info',
			'Stripe payment intent create succeeded.',
			array(
				'event'     => 'stripe.payment_intent.create.success',
				'component' => 'payments',
				'feature'   => 'payment_intent',
				'operation' => 'create',
				'outcome'   => 'success',
				'method'    => __METHOD__,
				'line'      => __LINE__,
				'file'      => __FILE__,
				'context'   => array( 'order_id' => $order->get_id(), 'order_number' => $order->get_order_number(), 'payment_intent_id' => $payment_intent['id'], 'status' => $payment_intent['status'], 'mode' => $mode ),
			)
		);
		return $payment_details['payment_intent_details'];
	}

	/**
	 * Captures a PaymentIntent via the Stripe REST API.
	 *
	 * Replaces the former SDK call ($stripe->paymentIntents->capture()).
	 *
	 * @param WC_Order $order          The order whose PaymentIntent to capture.
	 * @param int      $capture_amount Amount to capture in Stripe smallest-unit format.
	 * @return array Result array with 'success' (bool) and 'message' (string) keys.
	 */
	public static function capture_payment( $order, $capture_amount ) {
		peachpay_log(
			'debug',
			'Stripe payment intent capture request.',
			array(
				'event'     => 'stripe.payment_intent.capture.request',
				'component' => 'payments',
				'feature'   => 'payment_intent',
				'operation' => 'capture.request',
				'method'    => __METHOD__,
				'line'      => __LINE__,
				'file'      => __FILE__,
				'context'   => array( 'order_id' => $order->get_id(), 'order_number' => $order->get_order_number(), 'capture_amount' => $capture_amount ),
			)
		);

		$mode = PeachPay_Stripe_Order_Data::get_payment_intent( $order, 'mode' );

		if ( empty( PeachPay_Stripe_Credentials::get_secret_key( $mode ) ) ) {
			peachpay_log(
				'error',
				'Stripe payment intent capture not configured.',
				array(
					'event'     => 'stripe.payment_intent.capture.not_configured',
					'component' => 'payments',
					'feature'   => 'payment_intent',
					'operation' => 'capture.not_configured',
					'outcome'   => 'failed',
					'method'    => __METHOD__,
					'line'      => __LINE__,
					'file'      => __FILE__,
					'context'   => array( 'order_id' => $order->get_id(), 'order_number' => $order->get_order_number(), 'mode' => $mode ),
				)
			);
			return array(
				'success' => false,
				'message' => __( 'Stripe is not configured.', 'peachpay-for-woocommerce' ),
			);
		}

		$payment_intent_id = PeachPay_Stripe_Order_Data::get_payment_intent( $order, 'id' );

		// POST /v1/payment_intents/{id}/capture
		$payment_intent = PeachPay_Stripe_HTTP::post(
			'/v1/payment_intents/' . $payment_intent_id . '/capture',
			array( 'amount_to_capture' => $capture_amount ),
			$mode
		);

		if ( is_wp_error( $payment_intent ) ) {
			peachpay_log(
				'error',
				'Stripe payment intent capture API error.',
				array(
					'event'     => 'stripe.payment_intent.capture.api_error',
					'component' => 'payments',
					'feature'   => 'payment_intent',
					'operation' => 'capture.api_error',
					'outcome'   => 'failed',
					'method'    => __METHOD__,
					'line'      => __LINE__,
					'file'      => __FILE__,
					'context'   => array( 'order_id' => $order->get_id(), 'order_number' => $order->get_order_number(), 'error_message' => $payment_intent->get_error_message() ),
				)
			);
			return array(
				'success' => false,
				'message' => $payment_intent->get_error_message(),
			);
		}

		// Build payment details.
		$payment_details = array(
			'payment_intent_details' => array(
				'id'                => $payment_intent['id'],
				'status'            => $payment_intent['status'],
				'amount'            => $payment_intent['amount'],
				'amount_capturable' => $payment_intent['amount_capturable'] ?? 0,
				'mode'              => $mode,
			),
		);

		// GET /v1/charges/{id}?expand[]=balance_transaction
		if ( ! empty( $payment_intent['latest_charge'] ) ) {
			$charge = PeachPay_Stripe_HTTP::get(
				'/v1/charges/' . $payment_intent['latest_charge'],
				$mode,
				array( 'expand' => array( 'balance_transaction' ) )
			);
			if ( ! is_wp_error( $charge ) ) {
				$payment_details['charge_details'] = array(
					'id'                  => $charge['id'],
					'status'              => $charge['status'],
					'amount'              => $charge['amount'],
					'balance_transaction' => $charge['balance_transaction'] ?? null,
				);
			}
		}

		self::calculate_payment_state( $order, $payment_details );

		peachpay_log(
			'info',
			'Stripe payment intent capture succeeded.',
			array(
				'event'     => 'stripe.payment_intent.capture.success',
				'component' => 'payments',
				'feature'   => 'payment_intent',
				'operation' => 'capture',
				'outcome'   => 'success',
				'method'    => __METHOD__,
				'line'      => __LINE__,
				'file'      => __FILE__,
				'context'   => array( 'order_id' => $order->get_id(), 'order_number' => $order->get_order_number(), 'payment_intent_id' => $payment_intent_id, 'mode' => $mode ),
			)
		);
		return array(
			'success' => true,
			'message' => 'Success.',
		);
	}

	/**
	 * Voids (cancels) a PaymentIntent via the Stripe REST API.
	 *
	 * Replaces the former SDK call ($stripe->paymentIntents->cancel()).
	 *
	 * @param WC_Order                                                          $order               The order to void.
	 * @param 'abandoned'|'duplicate'|'fraudulent'|'requested_by_customer'|null $cancellation_reason Optional cancellation reason.
	 * @return array Result array with 'success' (bool) and 'message' (string) keys.
	 */
	public static function void_payment( $order, $cancellation_reason = null ) {
		peachpay_log(
			'debug',
			'Stripe payment intent void request.',
			array(
				'event'     => 'stripe.payment_intent.void.request',
				'component' => 'payments',
				'feature'   => 'payment_intent',
				'operation' => 'void.request',
				'method'    => __METHOD__,
				'line'      => __LINE__,
				'file'      => __FILE__,
				'context'   => array( 'order_id' => $order->get_id(), 'order_number' => $order->get_order_number(), 'cancellation_reason' => $cancellation_reason ),
			)
		);

		$mode = PeachPay_Stripe_Order_Data::get_payment_intent( $order, 'mode' );

		if ( empty( PeachPay_Stripe_Credentials::get_secret_key( $mode ) ) ) {
			peachpay_log(
				'error',
				'Stripe payment intent void not configured.',
				array(
					'event'     => 'stripe.payment_intent.void.not_configured',
					'component' => 'payments',
					'feature'   => 'payment_intent',
					'operation' => 'void.not_configured',
					'outcome'   => 'failed',
					'method'    => __METHOD__,
					'line'      => __LINE__,
					'file'      => __FILE__,
					'context'   => array( 'order_id' => $order->get_id(), 'order_number' => $order->get_order_number(), 'mode' => $mode ),
				)
			);
			return array(
				'success' => false,
				'message' => __( 'Stripe is not configured.', 'peachpay-for-woocommerce' ),
			);
		}

		$payment_intent_id = PeachPay_Stripe_Order_Data::get_payment_intent( $order, 'id' );

		$params = array();
		if ( $cancellation_reason ) {
			$params['cancellation_reason'] = $cancellation_reason;
		}

		// POST /v1/payment_intents/{id}/cancel
		$payment_intent = PeachPay_Stripe_HTTP::post(
			'/v1/payment_intents/' . $payment_intent_id . '/cancel',
			$params,
			$mode
		);

		if ( is_wp_error( $payment_intent ) ) {
			peachpay_log(
				'error',
				'Stripe payment intent void API error.',
				array(
					'event'     => 'stripe.payment_intent.void.api_error',
					'component' => 'payments',
					'feature'   => 'payment_intent',
					'operation' => 'void.api_error',
					'outcome'   => 'failed',
					'method'    => __METHOD__,
					'line'      => __LINE__,
					'file'      => __FILE__,
					'context'   => array( 'order_id' => $order->get_id(), 'order_number' => $order->get_order_number(), 'error_message' => $payment_intent->get_error_message() ),
				)
			);
			return array(
				'success' => false,
				'message' => $payment_intent->get_error_message(),
			);
		}

		// Build payment details.
		$payment_details = array(
			'payment_intent_details' => array(
				'id'     => $payment_intent['id'],
				'status' => $payment_intent['status'],
				'mode'   => $mode,
			),
		);

		self::calculate_payment_state( $order, $payment_details );

		peachpay_log(
			'info',
			'Stripe payment intent void succeeded.',
			array(
				'event'     => 'stripe.payment_intent.void.success',
				'component' => 'payments',
				'feature'   => 'payment_intent',
				'operation' => 'void',
				'outcome'   => 'success',
				'method'    => __METHOD__,
				'line'      => __LINE__,
				'file'      => __FILE__,
				'context'   => array( 'order_id' => $order->get_id(), 'order_number' => $order->get_order_number(), 'payment_intent_id' => $payment_intent_id, 'mode' => $mode ),
			)
		);
		return array(
			'success' => true,
			'message' => 'Success.',
		);
	}

	/**
	 * Creates a Stripe Refund via the REST API and updates the WooCommerce order.
	 *
	 * Replaces the former SDK calls ($stripe->refunds->create(),
	 * $stripe->paymentIntents->retrieve(), $stripe->charges->retrieve()).
	 *
	 * @param WC_Order                                               $order  The order to refund.
	 * @param int|null                                               $amount Amount to refund in Stripe smallest-unit format. Null refunds the full remaining amount.
	 * @param 'duplicate'|'fraudulent'|'requested_by_customer'|null $reason Optional refund reason.
	 * @return array Result array with 'success' (bool), 'message' (string), and 'refund_id' (string) keys.
	 */
	public static function refund_payment( $order, $amount = null, $reason = null ) {
		peachpay_log(
			'debug',
			'Stripe payment intent refund request.',
			array(
				'event'     => 'stripe.payment_intent.refund.request',
				'component' => 'payments',
				'feature'   => 'payment_intent',
				'operation' => 'refund.request',
				'method'    => __METHOD__,
				'line'      => __LINE__,
				'file'      => __FILE__,
				'context'   => array( 'order_id' => $order->get_id(), 'order_number' => $order->get_order_number(), 'amount' => $amount, 'reason' => $reason ),
			)
		);

		$mode = PeachPay_Stripe_Order_Data::get_payment_intent( $order, 'mode' );

		if ( empty( PeachPay_Stripe_Credentials::get_secret_key( $mode ) ) ) {
			peachpay_log(
				'error',
				'Stripe payment intent refund not configured.',
				array(
					'event'     => 'stripe.payment_intent.refund.not_configured',
					'component' => 'payments',
					'feature'   => 'payment_intent',
					'operation' => 'refund.not_configured',
					'outcome'   => 'failed',
					'method'    => __METHOD__,
					'line'      => __LINE__,
					'file'      => __FILE__,
					'context'   => array( 'order_id' => $order->get_id(), 'order_number' => $order->get_order_number(), 'mode' => $mode ),
				)
			);
			return array(
				'success' => false,
				'message' => __( 'Stripe is not configured.', 'peachpay-for-woocommerce' ),
			);
		}

		$payment_intent_id = PeachPay_Stripe_Order_Data::get_payment_intent( $order, 'id' );

		$refund_params = array(
			'payment_intent' => $payment_intent_id,
		);

		if ( null !== $amount ) {
			$refund_params['amount'] = $amount;
		}

		if ( $reason ) {
			$refund_params['reason'] = $reason;
		}

		// POST /v1/refunds
		$refund = PeachPay_Stripe_HTTP::post( '/v1/refunds', $refund_params, $mode );

		if ( is_wp_error( $refund ) ) {
			peachpay_log(
				'error',
				'Stripe payment intent refund API error.',
				array(
					'event'     => 'stripe.payment_intent.refund.api_error',
					'component' => 'payments',
					'feature'   => 'payment_intent',
					'operation' => 'refund.api_error',
					'outcome'   => 'failed',
					'method'    => __METHOD__,
					'line'      => __LINE__,
					'file'      => __FILE__,
					'context'   => array( 'order_id' => $order->get_id(), 'order_number' => $order->get_order_number(), 'error_message' => $refund->get_error_message() ),
				)
			);
			return array(
				'success' => false,
				'message' => $refund->get_error_message(),
			);
		}

		// GET /v1/payment_intents/{id} — re-fetch to get latest_charge after refund.
		$payment_intent = PeachPay_Stripe_HTTP::get( '/v1/payment_intents/' . $payment_intent_id, $mode );

		if ( ! is_wp_error( $payment_intent ) && ! empty( $payment_intent['latest_charge'] ) ) {
			// GET /v1/charges/{id}?expand[]=balance_transaction&expand[]=refunds
			$charge = PeachPay_Stripe_HTTP::get(
				'/v1/charges/' . $payment_intent['latest_charge'],
				$mode,
				array( 'expand' => array( 'balance_transaction', 'refunds' ) )
			);

			if ( ! is_wp_error( $charge ) ) {
				$payment_details = array(
					'payment_intent_details' => array(
						'id'     => $payment_intent['id'],
						'status' => $payment_intent['status'],
						'mode'   => $mode,
					),
					'charge_details'         => array(
						'id'                  => $charge['id'],
						'status'              => $charge['status'],
						'amount'              => $charge['amount'],
						'amount_refunded'     => $charge['amount_refunded'],
						'refunded'            => $charge['refunded'],
						'refunds'             => array(
							array(
								'id'       => $refund['id'],
								'amount'   => $refund['amount'],
								'currency' => $refund['currency'],
							),
						),
						'balance_transaction' => $charge['balance_transaction'] ?? null,
					),
				);

				self::calculate_payment_state( $order, $payment_details );
			}
		}

		// Add order note.
		$refund_amount_display = wc_price(
			self::display_amount( $refund['amount'], strtoupper( $refund['currency'] ) ),
			array( 'currency' => strtoupper( $refund['currency'] ) )
		);
		// translators: %1$s Payment method title, %2$s Refund amount, %3$s Refund Id.
		$order->add_order_note( sprintf( __( 'Stripe %1$s payment refunded %2$s (Refund Id: %3$s)', 'peachpay-for-woocommerce' ), $order->get_payment_method_title(), $refund_amount_display, $refund['id'] ) );

		peachpay_log(
			'info',
			'Stripe payment intent refund succeeded.',
			array(
				'event'     => 'stripe.payment_intent.refund.success',
				'component' => 'payments',
				'feature'   => 'payment_intent',
				'operation' => 'refund',
				'outcome'   => 'success',
				'method'    => __METHOD__,
				'line'      => __LINE__,
				'file'      => __FILE__,
				'context'   => array( 'order_id' => $order->get_id(), 'order_number' => $order->get_order_number(), 'payment_intent_id' => $payment_intent_id, 'refund_id' => $refund['id'], 'refund_amount' => $refund['amount'], 'refund_currency' => $refund['currency'], 'mode' => $mode ),
			)
		);
		return array(
			'success'   => true,
			'message'   => 'Success.',
			'refund_id' => $refund['id'],
		);
	}

	/**
	 * Calculates the payment state to change a orders status.
	 *
	 * @param WC_Order $order The WC stripe order to calculate its order status.
	 * @param array    $payment_details The latest stripe payment details.
	 * @param string   $reason An optional message to include with some order status changes. Mostly for indicating error messages.
	 */
	public static function calculate_payment_state( $order, $payment_details = null, $reason = '' ) {
		$old_transaction_status = PeachPay_Stripe_Order_Data::get_payment_intent( $order, 'status' );

		if ( null !== $payment_details ) {
			if ( isset( $payment_details['payment_intent_details'] ) ) {
				PeachPay_Stripe_Order_Data::set_payment_intent_details( $order, $payment_details['payment_intent_details'] );
			}

			if ( isset( $payment_details['payment_method_details'] ) ) {
				PeachPay_Stripe_Order_Data::set_payment_method_details( $order, $payment_details['payment_method_details'] );
				if ( 'card' === PeachPay_Stripe_Order_Data::get_payment_method( $order, 'type' ) ) {
					if ( 'peachpay_stripe_card' === $order->get_payment_method() ) {
						PeachPay_Stripe_Card_Gateway::set_payment_method_title( $order );
					} elseif ( 'peachpay_stripe_applepay' === $order->get_payment_method() ) {
						PeachPay_Stripe_Applepay_Gateway::set_payment_method_title( $order );
					} elseif ( 'peachpay_stripe_googlepay' === $order->get_payment_method() ) {
						PeachPay_Stripe_Googlepay_Gateway::set_payment_method_title( $order );
					}
				}
				if ( 'us_bank_account' === PeachPay_Stripe_Order_Data::get_payment_method( $order, 'type' ) && 'peachpay_stripe_achdebit' === $order->get_payment_method() ) {
					PeachPay_Stripe_Achdebit_Gateway::set_payment_method_title( $order );
				}
			}

			if ( isset( $payment_details['charge_details'] ) ) {
				PeachPay_Stripe_Order_Data::set_charge_details( $order, $payment_details['charge_details'] );
			}
		}

		$order_status   = $order->get_status();
		$payment_status = PeachPay_Stripe_Order_Data::get_payment_intent( $order, 'status' );
		$charge_id      = PeachPay_Stripe_Order_Data::get_charge( $order, 'id' );
		if ( null !== $charge_id ) {
			$order->set_transaction_id( $charge_id );
		}
		$reference_meta_updated = self::sync_order_reference_meta( $order );

		if ( $old_transaction_status === $payment_status ) {
			if ( ! isset( $payment_details['type'] ) ) {
				if ( $reference_meta_updated ) {
					$order->save();
				}
				return;
			}

			if ( 'charge.dispute.created' === $payment_details['type'] && PeachPay_Stripe_Advanced::get_setting( 'dispute_created' ) === 'yes' ) {
				self::handle_dispute_created( $order, $payment_details );
			}

			if ( 'charge.dispute.closed' === $payment_details['type'] && PeachPay_Stripe_Advanced::get_setting( 'dispute_closed' ) === 'yes' ) {
				self::handle_dispute_closed( $order, $payment_details );
			}

			if ( $reference_meta_updated ) {
				$order->save();
			}
			return;
		}

		$save_payment_method = PeachPay_Stripe_Order_Data::is_save_to_account( $order, 'save_to_account' );

		if ( 'succeeded' === $payment_status && ! $order->is_paid() ) {
			$order->payment_complete();
			if ( 'on-hold' === $order_status ) {
				// translators: %1$s Payment method title,  %2$s charge id.
				$order->add_order_note( sprintf( __( 'Stripe %1$s payment captured. Payment is now complete (Charge Id: %2$s)', 'peachpay-for-woocommerce' ), $order->get_payment_method_title(), $charge_id ) );
			} else {
				// translators: %1$s Payment method title,  %2$s charge id.
				$order->add_order_note( sprintf( __( 'Stripe %1$s payment complete (Charge Id: %2$s)', 'peachpay-for-woocommerce' ), $order->get_payment_method_title(), $charge_id ) );
			}

			if ( $save_payment_method ) {
				$user_id   = PeachPay_Stripe_Order_Data::is_save_to_account( $order, 'user_id' );
				$save_card = new PeachPay_Stripe_Card_Gateway();
				$save_card->create_payment_token( $order, $user_id );
				//unset save to account flag
				PeachPay_Stripe_Order_Data::save_to_account( $order, array() );
			}
		} elseif ( 'requires_capture' === $payment_status ) {
			if ( $save_payment_method ) {
				$user_id   = PeachPay_Stripe_Order_Data::is_save_to_account( $order, 'user_id' );
				$save_card = new PeachPay_Stripe_Card_Gateway();
				$save_card->create_payment_token( $order, $user_id );
				//unset save to account flag
				PeachPay_Stripe_Order_Data::save_to_account( $order, array() );
			}
			// translators: %1$s Payment method title, %2$s amount ,%3$s charge id.
			$order->set_status( 'on-hold', sprintf( __( 'Stripe %1$s payment authorized for %2$s (Charge Id: %3$s)', 'peachpay-for-woocommerce' ), $order->get_payment_method_title(), wc_price( $order->get_total(), array( 'currency' => $order->get_currency() ) ), $charge_id ) );
		} elseif ( 'requires_payment_method' === $payment_status ) {
			// translators: %1$s Payment method title, %2$s reason
			$order->set_status( 'failed', sprintf( __( 'Stripe %1$s payment failed. Reason: %2$s', 'peachpay-for-woocommerce' ), $order->get_payment_method_title(), $reason ) );
		} elseif ( 'processing' === $payment_status ) {
			// translators: %1$s Payment method title,  %2$s charge id.
			$order->set_status( 'on-hold', sprintf( __( 'Stripe %1$s payment is processing (Charge Id: %2$s)', 'peachpay-for-woocommerce' ), $order->get_payment_method_title(), $charge_id ) );
		} elseif ( 'canceled' === $payment_status ) {
			// translators: %1$s Payment method title
			$order->set_status( 'cancelled', sprintf( __( 'Stripe %1$s payment was canceled. %2$s', 'peachpay-for-woocommerce' ), $order->get_payment_method_title(), $reason ) );
		}

		$order->save();
	}

	/**
	 * Sync flattened Stripe references for easier order-level querying.
	 *
	 * @param WC_Order $order WooCommerce order.
	 * @return bool True when order meta was updated.
	 */
	private static function sync_order_reference_meta( $order ) {
		$payment_id  = PeachPay_Stripe_Order_Data::get_payment_intent( $order, 'id' );
		$customer_id = PeachPay_Stripe_Order_Data::get_payment_intent( $order, 'customer' );

		if ( empty( $customer_id ) ) {
			$customer_id = PeachPay_Stripe_Order_Data::get_payment_method( $order, 'customer' );
		}

		$updated = false;

		if ( ! empty( $payment_id ) && $order->get_meta( '_peachpay_stripe_card_payment_id' ) !== $payment_id ) {
			$order->update_meta_data( '_peachpay_stripe_card_payment_id', $payment_id );
			$updated = true;
		}

		if ( ! empty( $customer_id ) && $order->get_meta( '_peachpay_stripe_card_customer_id' ) !== $customer_id ) {
			$order->update_meta_data( '_peachpay_stripe_card_customer_id', $customer_id );
			$updated = true;
		}

		return $updated;
	}

	/**
	 * Creates a Stripe SetupIntent (and optionally a Customer) via the REST API.
	 *
	 * Replaces the former SDK calls:
	 *   $stripe->customers->create()
	 *   $stripe->setupIntents->create()
	 *   $stripe->paymentMethods->retrieve()
	 *
	 * @param string $session_id          The PeachPay session ID the SetupIntent belongs to.
	 * @param array  $setup_intent_params Parameters forwarded to POST /v1/setup_intents.
	 * @return array Result array with 'success' (bool) and 'data' (array) / 'message' (string) keys.
	 */
	public static function setup_payment( $session_id, $setup_intent_params ) {
		peachpay_log(
			'debug',
			'Stripe setup intent create request.',
			array(
				'event'     => 'stripe.setup_intent.create.request',
				'component' => 'payments',
				'feature'   => 'setup_intent',
				'operation' => 'create.request',
				'method'    => __METHOD__,
				'line'      => __LINE__,
				'file'      => __FILE__,
				'context'   => array( 'session_id' => $session_id ),
			)
		);

		$mode = PeachPay_Stripe_Integration::mode();

		if ( empty( PeachPay_Stripe_Credentials::get_secret_key( $mode ) ) ) {
			peachpay_log(
				'error',
				'Stripe setup intent create not configured.',
				array(
					'event'     => 'stripe.setup_intent.create.not_configured',
					'component' => 'payments',
					'feature'   => 'setup_intent',
					'operation' => 'create.not_configured',
					'outcome'   => 'failed',
					'method'    => __METHOD__,
					'line'      => __LINE__,
					'file'      => __FILE__,
					'context'   => array( 'session_id' => $session_id, 'mode' => $mode ),
				)
			);
			return array(
				'success' => false,
				'message' => __( 'Stripe is not configured.', 'peachpay-for-woocommerce' ),
			);
		}

		// Resolve or create a Stripe Customer for the logged-in user.
		$customer_id = $setup_intent_params['customer'] ?? null;
		if ( ! $customer_id && get_current_user_id() ) {
			$customer_id = self::get_customer( get_current_user_id() );
			if ( ! $customer_id ) {
				// POST /v1/customers
				$user     = wp_get_current_user();
				$customer = PeachPay_Stripe_HTTP::post(
					'/v1/customers',
					array(
						'email' => $user->user_email,
						'name'  => $user->display_name,
					),
					$mode
				);
				if ( ! is_wp_error( $customer ) ) {
					$customer_id = $customer['id'];
				} else {
					peachpay_log(
						'warning',
						'Stripe setup intent customer create failed - continuing without customer.',
						array(
							'event'     => 'stripe.setup_intent.customer_create_failed',
							'component' => 'payments',
							'feature'   => 'setup_intent',
							'operation' => 'customer_create_failed',
							'outcome'   => 'warning',
							'method'    => __METHOD__,
							'line'      => __LINE__,
							'file'      => __FILE__,
							'context'   => array(
								'session_id'    => $session_id,
								'error_message' => $customer->get_error_message(),
								'mode'          => $mode,
							),
						)
					);
				}
			}
		}

		if ( $customer_id ) {
			$setup_intent_params['customer'] = $customer_id;
		}

		// Remove null / empty values before sending.
		$params = array_filter(
			$setup_intent_params,
			function ( $value ) {
				return null !== $value && '' !== $value;
			}
		);

		// POST /v1/setup_intents
		$setup_intent = PeachPay_Stripe_HTTP::post( '/v1/setup_intents', $params, $mode );

		if ( is_wp_error( $setup_intent ) ) {
			peachpay_log(
				'error',
				'Stripe setup intent create API error.',
				array(
					'event'     => 'stripe.setup_intent.create.api_error',
					'component' => 'payments',
					'feature'   => 'setup_intent',
					'operation' => 'create.api_error',
					'outcome'   => 'failed',
					'method'    => __METHOD__,
					'line'      => __LINE__,
					'file'      => __FILE__,
					'context'   => array( 'session_id' => $session_id, 'error_message' => $setup_intent->get_error_message() ),
				)
			);
			return array(
				'success' => false,
				'message' => $setup_intent->get_error_message(),
			);
		}

		$data = array(
			'setup_intent_details'   => array(
				'id'            => $setup_intent['id'],
				'client_secret' => $setup_intent['client_secret'],
				'status'        => $setup_intent['status'],
				'customer'      => $setup_intent['customer'] ?? null,
				'mode'          => $mode,
			),
			'payment_method_details' => null,
		);

		// GET /v1/payment_methods/{id} — fetch payment method details when available.
		if ( ! empty( $setup_intent['payment_method'] ) ) {
			$pm = PeachPay_Stripe_HTTP::get( '/v1/payment_methods/' . $setup_intent['payment_method'], $mode );
			if ( ! is_wp_error( $pm ) ) {
				$data['payment_method_details'] = array(
					'id'       => $pm['id'],
					'type'     => $pm['type'],
					'mode'     => $mode,
					'customer' => $pm['customer'] ?? null,
					'data'     => isset( $pm[ $pm['type'] ] ) ? $pm[ $pm['type'] ] : array(),
				);
			}
		}

		// Only persist the Stripe customer when one was actually created — never store null,
		// as add_user_meta(..., true) won't overwrite and would permanently block a valid
		// customer from being saved for this user on a future attempt.
		if ( ! empty( $setup_intent['customer'] ) ) {
			self::set_customer( get_current_user_id(), $setup_intent['customer'] );
		}
		if ( WC()->session ) {
			WC()->session->set( 'peachpay_setup_intent_details', $data );
		}

		peachpay_log(
			'info',
			'Stripe Setup intent create - success.',
			array(
				'event'     => 'stripe.setup_intent.create.success',
				'component' => 'payments',
				'feature'   => 'setup_intent',
				'operation' => 'create',
				'outcome'   => 'success',
				'method'    => __METHOD__,
				'line'      => __LINE__,
				'file'      => __FILE__,
				'context'   => array( 'session_id' => $session_id, 'setup_intent_id' => $setup_intent['id'], 'customer_id' => $setup_intent['customer'] ?? null, 'status' => $setup_intent['status'], 'mode' => $mode ),
			)
		);
		return array(
			'success' => true,
			'data'    => $data,
		);
	}

	/**
	 * Gets the stripe customer for the logged in user or returns null if one does not exist.
	 *
	 * @param int                    $user_id The id of the logged in user.
	 * @param "live"|"test"|"detect" $peachpay_mode The mode to get the customer id.
	 */
	public static function get_customer( $user_id, $peachpay_mode = 'detect' ) {

		if ( 0 === $user_id ) {
			return null;
		}

		$peachpay_mode = PeachPay_Stripe_Integration::mode( $peachpay_mode );
		$customer_id   = get_user_meta( $user_id, "_peachpay_stripe_{$peachpay_mode}_customer", true );
		if ( ! $customer_id || ! is_array( $customer_id ) ) {
			delete_user_meta( $user_id, "_peachpay_stripe_{$peachpay_mode}_customer" );
			return null;
		}

		if ( PeachPay_Stripe_Integration::connect_id() !== $customer_id['connect_id'] ) {
			delete_user_meta( $user_id, "_peachpay_stripe_{$peachpay_mode}_customer" );
			return null;
		}

		return $customer_id['customer_id'];
	}

	/**
	 * Adds a stripe customer to a woocommerce user.
	 *
	 * @param int                    $user_id The id of the logged in user.
	 * @param string                 $customer_id The stripe customer id.
	 * @param "live"|"test"|"detect" $peachpay_mode The mode to get the customer id.
	 */
	public static function set_customer( $user_id, $customer_id, $peachpay_mode = 'detect' ) {

		if ( 0 === $user_id ) {
			return false;
		}

		if ( ! empty( self::get_customer( $user_id ) ) ) {
			return false;
		}

		$peachpay_mode = PeachPay_Stripe_Integration::mode( $peachpay_mode );
		return add_user_meta(
			$user_id,
			"_peachpay_stripe_{$peachpay_mode}_customer",
			array(
				'customer_id' => $customer_id,
				'connect_id'  => PeachPay_Stripe_Integration::connect_id(),
			),
			true
		);
	}

	/**
	 * Handles disputed order by displaying additional order notes and changing order status.
	 *
	 * @param WC_Order $order The WC stripe order to calculate its order status.
	 * @param array    $dispute_details The stripe order dispute details.
	 */
	private static function handle_dispute_created( $order, $dispute_details ) {
		$charge_id      = PeachPay_Stripe_Order_Data::get_charge( $order, 'id' );
		$dispute_status = strtoupper( $dispute_details['status'] );
		// translators: %1$s charge id,  %2$s dispute status.
		$order->add_order_note( sprintf( __( 'A dispute has been created for charge Id: %1$s. Dispute status: %2$s.', 'peachpay-for-woocommerce' ), $charge_id, $dispute_status ) );

		PeachPay_Stripe_Order_Data::set_prev_status( $order, array( 'prev_status' => $order->get_status() ) );

		if ( PeachPay_Stripe_Advanced::get_setting( 'dispute_order_status' ) === 'on-hold' ) {
			$order->set_status( 'on-hold' );
		} elseif ( PeachPay_Stripe_Advanced::get_setting( 'dispute_order_status' ) === 'cancelled' ) {
			$order->set_status( 'cancelled' );
		} elseif ( PeachPay_Stripe_Advanced::get_setting( 'dispute_order_status' ) === 'refunded' ) {
			$dispute_message = isset( $payment_details['status_message'] ) ? $payment_details['status_message'] : 'Order was disputed.';
			$refund          = new WC_Order_Refund();
			$refund->set_amount( $order->get_total() - $order->get_total_refunded() );
			$refund->set_parent_id( $order->get_id() );
			$refund->set_reason( $dispute_message );
			$refund->set_refunded_by( get_current_user_id() );
			$refund->save();

			// translators: %1$s the payment method title, %3$s Refund amount, %4$s Refund Id.
			$order->add_order_note( sprintf( __( 'Stripe %1$s payment refunded %2$s (Transaction Id: %3$s)', 'peachpay-for-woocommerce' ), $order->get_payment_method_title(), wc_price( $order->get_total(), array( 'currency' => $order->get_currency() ) ), $charge_id ) );
			$order->set_status( 'refunded' );
		}

		$order->save();
	}

	/**
	 * Handle closed dispute orders.
	 *
	 * @param WC_Order $order The WC stripe order to calculate its order status.
	 * @param array    $dispute_details The stripe order dispute details.
	 */
	private static function handle_dispute_closed( $order, $dispute_details ) {
		$dispute_id = $dispute_details['dispute_id'];
		$result     = $dispute_details['status'];

		// translators: %1$s charge id,  %2$s dispute status.
		$order->add_order_note( sprintf( __( 'Dispute %1$s has been closed. Result: %2$s.', 'peachpay-for-woocommerce' ), $dispute_id, $result ) );

		if ( 'lost' === $result ) {
			$order->set_status( 'failed' );
		} elseif ( 'won' === $result ) {
			if ( $order->get_status() === 'refunded' ) {
				$refunds = $order->get_refunds();
				foreach ( $refunds as $refund ) {
					$refund->delete();
				}
			}
			$status = PeachPay_Stripe_Order_Data::get_prev_status( $order, 'prev_status' );
			$order->set_status( $status );
		}

		$order->save();
	}
}
