<?php
/**
 * Class for handling reepay hooks
 *
 * @package Reepay\Checkout\OrderFlow
 */

namespace Reepay\Checkout\OrderFlow;

use Exception;
use Reepay\Checkout\Utils\LoggingTrait;
use Reepay\Checkout\Tokens\ReepayTokens;
use WC_Order_Item_Fee;
use WC_Subscriptions_Manager;
use WP_Error;

defined( 'ABSPATH' ) || exit();

/**
 * Class Webhook
 *
 * @package Reepay\Checkout\OrderFlow
 */
class Webhook {
	use LoggingTrait;

	/**
	 * Logging source
	 *
	 * @var string
	 */
	private string $logging_source = 'reepay-webhook';

	/**
	 * Constructor.
	 */
	public function __construct() {
		add_action( 'woocommerce_api_' . strtolower( 'WC_Gateway_Reepay' ), array( $this, 'return_handler' ) );
	}

	/**
	 * Filter sensitive data from arrays before logging.
	 *
	 * @param array $data Data to filter.
	 *
	 * @return array Filtered data.
	 */
	private function filter_sensitive_data( array $data ): array {
		$sensitive_keys = array( 'card_number', 'cvv', 'cvc', 'masked_card', 'card_token', 'pan', 'secret', 'password', 'authorization' );
		$filtered       = array();

		foreach ( $data as $key => $value ) {
			if ( is_array( $value ) ) {
				$filtered[ $key ] = $this->filter_sensitive_data( $value );
			} elseif ( in_array( strtolower( $key ), $sensitive_keys, true ) ) {
				$filtered[ $key ] = '***REDACTED***';
			} else {
				$filtered[ $key ] = $value;
			}
		}

		return $filtered;
	}

	/**
	 * WebHook Callback
	 *
	 * @return void
	 */
	public function return_handler() {
		http_response_code( 200 );

		$raw_body = file_get_contents( 'php://input' );
		$this->log( sprintf( 'WebHook: Initialized %s from %s', sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ?? '' ) ), sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ?? '' ) ) ) );
		$this->log( sprintf( 'WebHook: Post data: %s', wp_json_encode( $this->filter_sensitive_data( json_decode( $raw_body, true ) ?: array() ) ) ) );
		$data = json_decode( $raw_body, true );
		if ( ! $data ) {
			$this->log( 'WebHook: Error: Missing parameters' );

			return;
		}

		$secret = get_transient( 'reepay_webhook_settings_secret' );

		if ( empty( $secret ) ) {
			$result = reepay()->api( $this->logging_source )->request( 'GET', 'https://api.reepay.com/v1/account/webhook_settings' );
			if ( is_wp_error( $result ) ) {
				$this->log( 'WebHook: Error: ' . $result->get_error_message() );
				return;
			}

			$secret = $result['secret'];

			/**
			 * Cache webhook secret for 10 minutes to reduce API calls while limiting exposure window.
			 * Note: For PCI DSS compliance in high-security environments, consider implementing
			 * encryption at rest or disabling caching via the filter below.
			 *
			 * @param int $ttl Time to live in seconds. Default 600 (10 minutes). Set to 0 to disable caching.
			 */
			$cache_ttl = apply_filters( 'reepay_webhook_secret_cache_ttl', 10 * MINUTE_IN_SECONDS );

			if ( $cache_ttl > 0 ) {
				set_transient( 'reepay_webhook_settings_secret', $secret, $cache_ttl );
			}
		}

		$check = bin2hex( hash_hmac( 'sha256', $data['timestamp'] . $data['id'], $secret, true ) );
		if ( $check !== $data['signature'] ) {
			// FIX: Retry with fresh secret in case it was recently rotated.
			delete_transient( 'reepay_webhook_settings_secret' );
			$result = reepay()->api( $this->logging_source )->request( 'GET', 'https://api.reepay.com/v1/account/webhook_settings' );
			if ( ! is_wp_error( $result ) && ! empty( $result['secret'] ) ) {
				$secret = $result['secret'];
				// Use the same cache TTL as above for consistency.
				$cache_ttl = apply_filters( 'reepay_webhook_secret_cache_ttl', 10 * MINUTE_IN_SECONDS );
				if ( $cache_ttl > 0 ) {
					set_transient( 'reepay_webhook_settings_secret', $secret, $cache_ttl );
				}
				$check = bin2hex( hash_hmac( 'sha256', $data['timestamp'] . $data['id'], $secret, true ) );
			}

			if ( $check !== $data['signature'] ) {
				$this->log(
					sprintf(
						'WebHook: Error: Signature verification failed. Event: %s, ID: %s, Remote IP: %s',
						$data['event_type'] ?? 'unknown',
						$data['id'] ?? 'unknown',
						sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ?? 'unknown' ) )
					)
				);

				return;
			}
		}

		try {
			$this->process( $data );
		} catch ( Exception $e ) {
			$this->log( 'WebHook: Error:' . $e->getMessage() );
		}

		http_response_code( 200 );
	}

	/**
	 * Process WebHook.
	 *
	 * @param array $data data from Reepay.
	 *
	 * @return void
	 * @throws Exception If invalid data.
	 *
	 * @todo split switch into methods
	 * @todo remove code duplication
	 */
	public function process( array $data ) {
		// Log caller information to reepay_order_statuses log.
		$caller       = 'unknown';
		$caller_class = '';

		// Only collect backtrace information when debugging is enabled.
		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			$backtrace    = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 3 ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace
			$caller       = isset( $backtrace[1] ) ? $backtrace[1]['function'] : 'unknown';
			$caller_class = isset( $backtrace[1]['class'] ) ? $backtrace[1]['class'] . '::' : '';
		}

		if ( function_exists( 'wc_get_logger' ) ) {
			wc_get_logger()->debug(
				sprintf(
					'Webhook::process called for event %s from %s%s',
					$data['event_type'] ?? 'unknown',
					$caller_class,
					$caller
				),
				array(
					'source'  => 'reepay_order_statuses',
					'_legacy' => true,
				)
			);
		}

		do_action( 'reepay_webhook', $data );

		$this->log(
			sprintf(
				'WebHook %1$s: Data: %2$s',
				$data['event_type'],
				wp_json_encode( $this->filter_sensitive_data( $data ) )
			)
		);

		switch ( $data['event_type'] ) {
			case 'invoice_authorized':
				if ( ! isset( $data['invoice'] ) ) {
					throw new Exception( 'Missing Invoice parameter' );
				}

				if ( isset( $data['subscription'] ) ) {
					$order = rp_get_not_subs_order_by_handle( $data['invoice'] );
				} else {
					$order = rp_get_order_by_handle( $data['invoice'] );
				}

				if ( ! $order ) {
					$this->log( sprintf( 'WebHook: Order is not found. Invoice: %s', $data['invoice'] ) );

					return;
				}

				$needs_reload = self::wait_for_unlock( $order->get_id() );
				if ( $needs_reload ) {
					$order = wc_get_order( $order->get_id() );
				}

				if ( $order->has_status( OrderStatuses::$status_sync_enabled ? OrderStatuses::$status_authorized : 'on-hold' ) ) {
					$this->log(
						sprintf(
							'WebHook: Event type: %s success. But the order had status early: %s',
							$data['event_type'],
							$order->get_status()
						)
					);

					http_response_code( 200 );

					return;
				}

				self::lock_order( $order->get_id() );

				$order->set_transaction_id( $data['transaction'] );
				$order->save();

				$invoice_data = reepay()->api( $order )->get_invoice_by_handle( $data['invoice'] );
				if ( is_wp_error( $invoice_data ) ) {
					$invoice_data = array();
				}

				$this->log( sprintf( 'WebHook: Invoice data: %s', wp_json_encode( $invoice_data, JSON_PRETTY_PRINT ) ) );

				// skip set authorized status for settled charge of the Vipps MobilePay Recurring.
				if ( array_key_exists( 'recurring_payment_method', $invoice_data ) && 'vr_' === substr( $invoice_data['recurring_payment_method'], 0, 3 ) && 'settled' === $invoice_data['state'] ) {
					$order->add_order_note(
						sprintf(
						// translators: %1$s - order amount, %2$s - transaction id.
							__( 'Payment has been authorized. Amount: %1$s. Transaction: %2$s', 'reepay-checkout-gateway' ),
							wc_price( rp_make_initial_amount( $invoice_data['amount'], $order->get_currency() ) ),
							$data['transaction']
						)
					);
					$order->update_meta_data( '_reepay_state_authorized', 1 );
					$order->save_meta_data();
					$order->save();
				} else {
					OrderStatuses::set_authorized_status(
						$order,
						sprintf(
						// translators: %1$s - order amount, %2$s - transaction id.
							__( 'Payment has been authorized. Amount: %1$s. Transaction: %2$s', 'reepay-checkout-gateway' ),
							wc_price( rp_make_initial_amount( $invoice_data['amount'], $order->get_currency() ) ),
							$data['transaction']
						),
						$data['transaction']
					);
				}

				// Save card information from invoice.
				try {
					ReepayTokens::save_card_info_from_invoice( $order, $data['invoice'] );
				} catch ( Exception $e ) {
					// Log error but don't fail webhook processing.
					$this->log(
						sprintf(
							'WebHook invoice_authorized: Failed to save card info. Order: %d, Invoice: %s, Error: %s',
							$order->get_id(),
							$data['invoice'],
							$e->getMessage()
						)
					);
				}

				self::unlock_order( $order->get_id() );

				$data['order_id'] = $order->get_id();
				do_action( 'reepay_webhook_invoice_authorized', $data );

				// Need for analytics.
				$order->set_date_paid( time() );
				$order->save(); // Ensure date_paid is saved for analytics.

				// Trigger order save to ensure analytics are updated.
				$order = wc_get_order( $order->get_id() );
				if ( $order ) {
					// Trigger WooCommerce update hook for analytics.
					do_action( 'woocommerce_update_order', $order->get_id(), $order );
					$order->save();
				}

				if ( ! empty( $invoice_data['order_lines'] ) ) {
					$this->log( sprintf( 'WebHook: event type: %s order: %s checking surcharge fee', $data['event_type'], $invoice_data['handle'] ) );
					foreach ( $invoice_data['order_lines'] as $invoice_line ) {
						if ( 'surcharge_fee' === $invoice_line['origin'] ) {
							$is_exist = false;
							foreach ( $order->get_items( 'fee' ) as $item ) {
								if ( $item->get_name() === $invoice_line['ordertext'] ) {
									$is_exist = true;
									break;
								}
							}
							if ( ! $is_exist ) {
								$this->log( sprintf( 'WebHook: event type: %s  order: %s adding surcharge fee', $data['event_type'], $invoice_data['handle'] ) );
								$fees_item = new WC_Order_Item_Fee();
								$fees_item->set_name( $invoice_line['ordertext'] );
								$fees_item->set_amount( floatval( $invoice_line['unit_amount'] ) / 100 );
								$fees_item->set_total( floatval( $invoice_line['amount'] ) / 100 );
								$fees_item->set_tax_status( 'none' );
								$fees_item->add_meta_data( '_is_card_fee', true );
								$order->add_item( $fees_item );
								$order->calculate_totals( false );
								$order->save();
								$this->log( sprintf( 'WebHook: event type: %s  order: %s surcharge fee added', $data['event_type'], $invoice_data['handle'] ) );
							} else {
								$this->log( sprintf( 'WebHook: event type: %s  order: %s surcharge fee already exists', $data['event_type'], $invoice_data['handle'] ) );
							}
						}
					}
				}

				$this->log( sprintf( 'WebHook: Success event type: %s', $data['event_type'] ) );

				do_action( 'reepay_instant_settle', $order );

				break;
			case 'invoice_settled':
				if ( ! isset( $data['invoice'] ) ) {
					throw new Exception( 'Missing Invoice parameter' );
				}

				if ( isset( $data['subscription'] ) ) {
					$order = rp_get_not_subs_order_by_handle( $data['invoice'] );
				} else {
					$order = rp_get_order_by_handle( $data['invoice'] );
				}

				if ( ! $order ) {
					$this->log( sprintf( 'WebHook: Order is not found. Invoice: %s', $data['invoice'] ) );

					return;
				}

				$needs_reload = self::wait_for_unlock( $order->get_id() );
				if ( $needs_reload ) {
					$order = wc_get_order( $order->get_id() );
				}

				if ( $order->has_status( OrderStatuses::$status_settled ) && ! empty( $order->get_meta( '_reepay_state_settled' ) ) ) {
					$this->log(
						sprintf(
							'WebHook: Event type: %s success. But the order had status early: %s',
							$data['event_type'],
							$order->get_status()
						)
					);

					http_response_code( 200 );

					return;
				}

				self::lock_order( $order->get_id() );

				$invoice_data = reepay()->api( $order )->get_invoice_by_handle( $data['invoice'] );
				if ( is_wp_error( $invoice_data ) ) {
					$invoice_data = array();
				}

				$this->log( sprintf( 'WebHook: Invoice data: %s', var_export( $invoice_data, true ) ) ); //phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export

				if ( ! empty( $invoice_data['id'] ) && ! empty( $data['transaction'] ) ) {
					$transaction = reepay()->api( $order )->request( 'GET', 'https://api.reepay.com/v1/invoice/' . $invoice_data['id'] . '/transaction/' . $data['transaction'] );
					$this->log( sprintf( 'WebHook: Transaction data: %s', var_export( $transaction, true ) ) ); //phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export

					if ( ! empty( $transaction['card_transaction']['card'] ) ) {
						if ( ! empty( $transaction['card_transaction']['error'] ) && ! empty( $transaction['card_transaction']['acquirer_message'] ) ) {
							$order->add_order_note( 'Item settle error: ' . $transaction['card_transaction']['acquirer_message'] );

							return;
						}
					}
				}

				if ( empty( $order->get_meta( '_reepay_subscription_handle' ) ) ) {
					// Log to reepay_order_statuses log.
					if ( function_exists( 'wc_get_logger' ) ) {
						wc_get_logger()->debug(
							sprintf(
								'Webhook::process calling set_settled_status for Order ID %d in invoice_settled case',
								$order->get_id()
							),
							array(
								'source'  => 'reepay_order_statuses',
								'_legacy' => true,
							)
						);
					}

					OrderStatuses::set_settled_status(
						$order,
						'',
						$data['transaction']
					);
				} elseif ( function_exists( 'wc_get_logger' ) ) {
					// Log to reepay_order_statuses log.
					wc_get_logger()->debug(
						sprintf(
							'Webhook::process skipping set_settled_status for Order ID %d (has subscription handle)',
							$order->get_id()
						),
						array(
							'source'  => 'reepay_order_statuses',
							'_legacy' => true,
						)
					);
				}

				// Save card information from invoice.
				try {
					ReepayTokens::save_card_info_from_invoice( $order, $data['invoice'] );
				} catch ( Exception $e ) {
					// Log error but don't fail webhook processing.
					$this->log(
						sprintf(
							'WebHook invoice_settled: Failed to save card info. Order: %d, Invoice: %s, Error: %s',
							$order->get_id(),
							$data['invoice'],
							$e->getMessage()
						)
					);
				}

				// For subscription renewals, also update parent subscription order.
				if ( isset( $data['subscription'] ) ) {
					$subscription_order = rp_get_order_by_subscription_handle( $data['subscription'] );
					if ( $subscription_order && $subscription_order->get_id() !== $order->get_id() ) {
						try {
							ReepayTokens::save_card_info_from_invoice( $subscription_order, $data['invoice'] );
							$this->log(
								sprintf(
									'WebHook invoice_settled: Saved card info for subscription order. Subscription Order: %d, Renewal Order: %d, Invoice: %s',
									$subscription_order->get_id(),
									$order->get_id(),
									$data['invoice']
								)
							);
						} catch ( Exception $e ) {
							// Log error but don't fail webhook processing.
							$this->log(
								sprintf(
									'WebHook invoice_settled: Failed to save card info for subscription order. Subscription Order: %d, Invoice: %s, Error: %s',
									$subscription_order->get_id(),
									$data['invoice'],
									$e->getMessage()
								)
							);
						}
					}
				}

				$order->update_meta_data( '_reepay_capture_transaction', $data['transaction'] );
				$order->save_meta_data();

				self::unlock_order( $order->get_id() );

				$data['order_id'] = $order->get_id();
				do_action( 'reepay_webhook_invoice_settled', $data );

				// Trigger order save to ensure analytics are updated.
				$order = wc_get_order( $order->get_id() );
				if ( $order ) {
					// Trigger WooCommerce update hook for analytics.
					do_action( 'woocommerce_update_order', $order->get_id(), $order );
					$order->save();
				}

				$this->log( sprintf( 'WebHook: Success event type: %s', $data['event_type'] ) );
				break;
			case 'invoice_cancelled':
				if ( ! isset( $data['invoice'] ) ) {
					throw new Exception( 'Missing Invoice parameter' );
				}

				$order = rp_get_order_by_handle( $data['invoice'] );
				if ( ! $order ) {
					$this->log( sprintf( 'WebHook: Order is not found. Invoice: %s', $data['invoice'] ) );

					return;
				}

				if ( $order->has_status( 'cancelled' ) ) {
					$this->log(
						sprintf(
							'WebHook: Event type: %s success. Order status: %s',
							$data['event_type'],
							$order->get_status()
						)
					);

					http_response_code( 200 );

					return;
				}

				$order->update_status(
					'cancelled',
					__( 'Cancelled by WebHook.', 'reepay-checkout-gateway' )
				);

				$order->update_meta_data( '_reepay_cancel_transaction', $data['transaction'] );
				$order->save_meta_data();

				$data['order_id'] = $order->get_id();
				do_action( 'reepay_webhook_invoice_cancelled', $data );

				$this->log( sprintf( 'WebHook: Success event type: %s', $data['event_type'] ) );
				break;
			case 'invoice_refund':
				if ( ! isset( $data['invoice'] ) ) {
					throw new Exception( 'Missing Invoice parameter' );
				}

				$order = rp_get_order_by_handle( $data['invoice'] );

				if ( ! $order ) {
					$this->log( sprintf( 'WebHook: Order is not found. Invoice: %s', $data['invoice'] ) );

					return;
				}

				$sub_order = $order->get_meta( '_reepay_subscription_handle' );
				if ( ! empty( $sub_order ) ) {
					return;
				}

				$invoice_data = reepay()->api( $order )->get_invoice_by_handle( $data['invoice'] );
				if ( is_wp_error( $invoice_data ) ) {
					$invoice_data = array();
				}

				$credit_notes = $invoice_data['credit_notes'];
				foreach ( $credit_notes as $credit_note ) {
					// Get registered credit notes.
					$credit_note_ids = $order->get_meta( '_reepay_credit_note_ids' );
					if ( ! is_array( $credit_note_ids ) ) {
						$credit_note_ids = array();
					}

					// Check is refund already registered.
					if ( in_array( $credit_note['id'], $credit_note_ids, true ) ) {
						continue;
					}

					$credit_note_id = $credit_note['id'];
					$amount         = rp_make_initial_amount( $credit_note['amount'], $order->get_currency() );
					$reason         = sprintf(
					// translators: #%s credit note id.
						__( 'Credit Note Id #%s.', 'reepay-checkout-gateway' ),
						$credit_note_id
					);

					$refund = wc_create_refund(
						array(
							'amount'   => $amount,
							'reason'   => '', // don't add Credit note to refund line.
							'order_id' => $order->get_id(),
						)
					);

					if ( $refund ) {
						$credit_note_ids[] = $credit_note_id;
						$order->update_meta_data( '_reepay_credit_note_ids', $credit_note_ids );
						$order->save_meta_data();

						$order->add_order_note(
							sprintf(
							// translators: %1$s refund amount, %2$s refund reason.
								__( 'Refunded: %1$s. Reason: %2$s', 'reepay-checkout-gateway' ),
								wc_price( $amount ),
								$reason
							)
						);
					}
				}

				$data['order_id'] = $order->get_id();
				do_action( 'reepay_webhook_invoice_refund', $data );

				$this->log( sprintf( 'WebHook: Success event type: %s', $data['event_type'] ) );
				break;
			case 'invoice_created':
				// FIX: Graceful skip for non-subscription invoices that lack these params.
				if ( ! isset( $data['invoice'] ) || ! isset( $data['subscription'] ) ) {
					$this->log(
						sprintf(
							'WebHook: invoice_created event skipped — missing %s. ID: %s',
							! isset( $data['invoice'] ) ? 'invoice' : 'subscription',
							$data['id'] ?? 'unknown'
						)
					);
					break;
				}

				$order = rp_get_order_by_subscription_handle( $data['subscription'] );
				if ( ! $order ) {
					$this->log( sprintf( 'WebHook: Order is not found. Invoice: %s', $data['invoice'] ) );
					do_action( 'reepay_webhook_invoice_created', $data );

					return;
				} else {
					$this->log( sprintf( 'WebHook: Order is found. Order: %s', $order ) );
				}

				$data['order_id'] = $order->get_id();
				do_action( 'reepay_webhook_invoice_created', $data );

				$this->log( sprintf( 'WebHook: Success event type: %s', $data['event_type'] ) );
				break;
			case 'customer_created':
				$customer = $data['customer'];
				$user_id  = rp_get_user_id_by_handle( $customer );
				if ( ! $user_id ) {
					if ( strpos( $customer, 'customer-' ) !== false ) {
						$user_id = (int) str_replace( 'customer-', '', $customer );
						if ( $user_id > 0 ) {
							update_user_meta( $user_id, 'reepay_customer_id', $customer );
							$this->log( sprintf( 'WebHook: Customer created: %s', var_export( $customer, true ) ) ); //phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export
						}
					}

					if ( ! $user_id ) {
						$this->log( sprintf( 'WebHook: Customer doesn\'t exists: %s', var_export( $customer, true ) ) ); //phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export
					}
				}

				$data['user_id'] = $user_id;
				do_action( 'reepay_webhook_customer_created', $data );

				$this->log( sprintf( 'WebHook: Success event type: %s', $data['event_type'] ) );
				break;
			case 'customer_payment_method_added':
				if ( ! empty( $data['payment_method_reference'] ) ) {
					$order = rp_get_order_by_session( $data['payment_method_reference'], $data['customer'] );
					if ( $order && order_contains_subscription( $order ) ) {
						WC_Subscriptions_Manager::activate_subscriptions_for_order( $order );

						if ( ! empty( $data['payment_method'] ) ) {
							try {
								$token = ReepayTokens::reepay_save_token( $order, $data['payment_method'] );
								ReepayTokens::assign_payment_token( $order, $token );
								ReepayTokens::save_card_info_to_order( $order, $token->get_token() );

								$order->payment_complete();
							} catch ( Exception $e ) {
								$order->add_order_note( $e->getMessage() );
								$this->log( sprintf( 'WebHook: Token save error: %s', $e->getMessage() ) );
								return;
							}
						} else {
							wc_get_logger()->debug(
								__METHOD__,
								array(
									'source'  => 'billwerk-token',
									'_legacy' => true,
								)
							);
						}
					} else {
						wc_get_logger()->debug(
							__METHOD__,
							array(
								'source'  => 'billwerk-token',
								'_legacy' => true,
							)
						);
					}
				}

				do_action( 'reepay_webhook_customer_payment_method_added', $data );

				break;
			default:
				global $wp_filter;
				$this->log( sprintf( 'WebHook: %s', $data['event_type'] ) );
				$base_hook_name    = 'reepay_webhook_raw_event';
				$current_hook_name = "{$base_hook_name}_{$data['event_type']}";

				if ( isset( $wp_filter[ $base_hook_name ] ) || isset( $wp_filter[ $current_hook_name ] ) ) {
					do_action( $base_hook_name, $data );
					do_action( $current_hook_name, $data );
				} else {
					$this->log( sprintf( 'WebHook: Unknown event type: %s', $data['event_type'] ) );
					http_response_code( 200 );

					return;
				}
		}
	}

	/**
	 * Lock the order.
	 *
	 * @param int $order_id order id to lock.
	 *
	 * @return void
	 * @see wait_for_unlock()
	 */
	private static function lock_order( int $order_id ) {
		$order = wc_get_order( $order_id );
		$order->update_meta_data( '_reepay_locked', '1' );
		$order->save_meta_data();
	}

	/**
	 * Unlock the order.
	 *
	 * @param mixed $order_id order id to unlock.
	 *
	 * @return void
	 * @see wait_for_unlock()
	 */
	private static function unlock_order( $order_id ) {
		$order = wc_get_order( $order_id );
		$order->delete_meta_data( '_reepay_locked' );
		$order->save_meta_data();
	}

	/**
	 * Wait for unlock.
	 *
	 * @param int $order_id order id to wait.
	 *
	 * @return bool
	 */
	private static function wait_for_unlock( int $order_id ): bool {
		set_time_limit( 0 );

		$order        = wc_get_order( $order_id );
		$is_locked    = (bool) $order->get_meta( '_reepay_locked' );
		$needs_reload = false;
		$attempts     = 0;
		while ( $is_locked ) {
			usleep( 500 );
			++$attempts;
			if ( $attempts > 30 ) {
				break;
			}

			wp_cache_delete( $order_id, 'post_meta' );
			$is_locked = (bool) $order->get_meta( '_reepay_locked' );
			if ( $is_locked ) {
				$needs_reload = true;
				clean_post_cache( $order_id );
			}
		}

		return $needs_reload;
	}
}
