Initial commit
diff --git a/includes/abstracts/abstract-wc-stripe-connect-rest-controller.php b/includes/abstracts/abstract-wc-stripe-connect-rest-controller.php
new file mode 100644
index 0000000..e67baa9
--- /dev/null
+++ b/includes/abstracts/abstract-wc-stripe-connect-rest-controller.php
@@ -0,0 +1,170 @@
+<?php
+
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
+
+// phpcs:disable WordPress.Files.FileName
+
+/**
+ * Stripe Connect base REST controller class.
+ */
+abstract class WC_Stripe_Connect_REST_Controller extends WP_REST_Controller {
+
+	/**
+	 * Endpoint namespace.
+	 *
+	 * @var string
+	 */
+	protected $namespace = 'wc/v1';
+
+	/**
+	 * Stripe connect api.
+	 *
+	 * @var object $api
+	 */
+	private $api;
+
+	/**
+	 * Constructor.
+	 *
+	 * @param WC_Stripe_Connect_API $api stripe connect api.
+	 */
+	public function __construct( WC_Stripe_Connect_API $api ) {
+
+		$this->api = $api;
+	}
+
+	/**
+	 * Registers rest routes for stripe connect functionality
+	 */
+	public function register_routes() {
+
+		if ( method_exists( $this, 'get' ) ) {
+			register_rest_route(
+				$this->namespace,
+				'/' . $this->rest_base,
+				[
+					[
+						'methods'             => 'GET',
+						'callback'            => [ $this, 'get_internal' ],
+						'permission_callback' => [ $this, 'check_permission' ],
+					],
+				]
+			);
+		}
+
+		if ( method_exists( $this, 'post' ) ) {
+			register_rest_route(
+				$this->namespace,
+				'/' . $this->rest_base,
+				[
+					[
+						'methods'             => 'POST',
+						'callback'            => [ $this, 'post_internal' ],
+						'permission_callback' => [ $this, 'check_permission' ],
+					],
+				]
+			);
+		}
+
+		if ( method_exists( $this, 'delete' ) ) {
+			register_rest_route(
+				$this->namespace,
+				'/' . $this->rest_base,
+				[
+					[
+						'methods'             => 'DELETE',
+						'callback'            => [ $this, 'delete_internal' ],
+						'permission_callback' => [ $this, 'check_permission' ],
+					],
+				]
+			);
+		}
+	}
+
+	/**
+	 * Send get request.
+	 *
+	 * @param array $request request.
+	 *
+	 * @return array
+	 */
+	public function get_internal( $request ) {
+
+		$this->prevent_route_caching();
+
+		return $this->get( $request );
+	}
+
+	/**
+	 * Send post request.
+	 *
+	 * @param array $request request.
+	 *
+	 * @return array
+	 */
+	public function post_internal( $request ) {
+
+		$this->prevent_route_caching();
+
+		return $this->post( $request );
+	}
+
+	/**
+	 * Sends delete request.
+	 *
+	 * @param array $request request.
+	 *
+	 * @return array
+	 */
+	public function delete_internal( $request ) {
+
+		$this->prevent_route_caching();
+
+		return $this->delete( $request );
+	}
+
+	/**
+	 * Validate the requester's permissions
+	 *
+	 * @param array $request request.
+	 *
+	 * @return bool
+	 */
+	public function check_permission( $request ) {
+
+		return current_user_can( 'manage_woocommerce' );
+	}
+
+	/**
+	 * Consolidate cache prevention mechanisms.
+	 */
+	public function prevent_route_caching() {
+
+		if ( ! defined( 'DONOTCACHEPAGE' ) ) {
+			define( 'DONOTCACHEPAGE', true ); // Play nice with WP-Super-Cache.
+		}
+
+		// Prevent our REST API endpoint responses from being added to browser cache.
+		add_filter( 'rest_post_dispatch', [ $this, 'send_nocache_header' ], PHP_INT_MAX, 2 );
+	}
+
+	/**
+	 * Send a no-cache header for WCS REST API responses. Prompted by cache issues
+	 * on the Pantheon hosting platform.
+	 *
+	 * See: https://pantheon.io/docs/cache-control/
+	 *
+	 * @param  WP_REST_Response $response REST API response.
+	 * @param  WP_REST_Server   $server   server.
+	 *
+	 * @return WP_REST_Response passthrough $response parameter
+	 */
+	public function send_nocache_header( $response, $server ) {
+
+		$server->send_header( 'Cache-Control', 'no-cache, must-revalidate, max-age=0' );
+
+		return $response;
+	}
+}
diff --git a/includes/abstracts/abstract-wc-stripe-payment-gateway-voucher.php b/includes/abstracts/abstract-wc-stripe-payment-gateway-voucher.php
new file mode 100644
index 0000000..080fead
--- /dev/null
+++ b/includes/abstracts/abstract-wc-stripe-payment-gateway-voucher.php
@@ -0,0 +1,399 @@
+<?php
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
+
+// phpcs:disable WordPress.Files.FileName
+/**
+ * Abstract class that will be inherited by voucher payment methods.
+ * Used by Boleto and OXXO
+ *
+ * @extends WC_Gateway_Stripe
+ *
+ * @since 5.8.0
+ */
+abstract class WC_Stripe_Payment_Gateway_Voucher extends WC_Stripe_Payment_Gateway {
+
+	/**
+	 * ID used by UPE
+	 *
+	 * @var string
+	 */
+	const ID = '';
+
+	/**
+	 * ID used by WooCommerce to identify the payment method
+	 * Override this when extending the class
+	 *
+	 * @var string
+	 */
+	public $id = '';
+
+	/**
+	 * ID used by stripe
+	 * Change this when extending this class
+	 */
+	protected $stripe_id = '';
+
+	/**
+	 * List of accepted currencies
+	 * Change this when extending this class
+	 *
+	 * @var array
+	 */
+	protected $supported_currencies = [];
+
+	/**
+	 * List of accepted countries
+	 * Change this when extending this class
+	 */
+	protected $supported_countries = [];
+
+	/**
+	 * Notices (array)
+	 *
+	 * @var array
+	 */
+	public $notices = [];
+
+	/**
+	 * Is test mode active?
+	 *
+	 * @var bool
+	 */
+	public $testmode;
+
+	/**
+	 * Alternate credit card statement name
+	 *
+	 * @var bool
+	 */
+	public $statement_descriptor;
+
+	/**
+	 * API access secret key
+	 *
+	 * @var string
+	 */
+	public $secret_key;
+
+	/**
+	 * Api access publishable key
+	 *
+	 * @var string
+	 */
+	public $publishable_key;
+
+	/**
+	 * Should we store the users credit cards?
+	 *
+	 * @var bool
+	 */
+	public $saved_cards;
+
+	/**
+	 * Gateway has additional fields during checkout
+	 *
+	 * @var bool
+	 */
+	public $has_fields = true;
+
+	/**
+	 * Constructor
+	 *
+	 * @since 5.8.0
+	 */
+	public function __construct() {
+		$this->method_description = sprintf(
+		/* translators: 1) HTML anchor open tag 2) HTML anchor closing tag */
+			__( 'All other general Stripe settings can be adjusted %1$shere%2$s ', 'woocommerce-gateway-stripe' ),
+			'<a href="' . esc_url( admin_url( 'admin.php?page=wc-settings&tab=checkout&section=stripe' ) ) . '">',
+			'</a>'
+		);
+		$this->supports = [
+			'products',
+		];
+
+		// Load the form fields.
+		$this->init_form_fields();
+
+		// Load the settings.
+		$this->init_settings();
+
+		$main_settings              = get_option( 'woocommerce_stripe_settings' );
+		$this->title                = $this->get_option( 'title' );
+		$this->description          = $this->get_option( 'description' );
+		$this->enabled              = $this->get_option( 'enabled' );
+		$this->testmode             = ( ! empty( $main_settings['testmode'] ) && 'yes' === $main_settings['testmode'] ) ? true : false;
+		$this->publishable_key      = ! empty( $main_settings['publishable_key'] ) ? $main_settings['publishable_key'] : '';
+		$this->secret_key           = ! empty( $main_settings['secret_key'] ) ? $main_settings['secret_key'] : '';
+		$this->statement_descriptor = ! empty( $main_settings['statement_descriptor'] ) ? $main_settings['statement_descriptor'] : '';
+
+		if ( $this->testmode ) {
+			$this->publishable_key = ! empty( $main_settings['test_publishable_key'] ) ? $main_settings['test_publishable_key'] : '';
+			$this->secret_key      = ! empty( $main_settings['test_secret_key'] ) ? $main_settings['test_secret_key'] : '';
+		}
+
+		add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, [ $this, 'process_admin_options' ] );
+		add_action(
+			'wc_ajax_wc_stripe_' . $this->stripe_id . '_update_payment_intent',
+			[
+				$this,
+				'update_payment_intent_ajax',
+			]
+		);
+		add_action( 'wp_enqueue_scripts', [ $this, 'payment_scripts' ] );
+	}
+
+	/**
+	 * Checks to see if all criteria is met before showing payment method.
+	 *
+	 * @return bool
+	 * @return bool
+	 * @since 5.8.0
+	 */
+	public function is_available() {
+		if ( ! in_array( get_woocommerce_currency(), $this->get_supported_currency() ) ) {
+			return false;
+		}
+
+		return parent::is_available();
+	}
+
+	/**
+	 * Hides refund through stripe when payment method does not allow refund
+	 *
+	 * @param WC_Order $order
+	 *
+	 * @return array|bool
+	 */
+	public function can_refund_order( $order ) {
+		return false;
+	}
+
+	/**
+	 * Returns all supported currencies for this payment method.
+	 *
+	 * @return array
+	 * @return array
+	 * @since 5.8.0
+	 */
+	public function get_supported_currency() {
+		return apply_filters(
+			'wc_stripe_' . $this->stripe_id . '_supported_currencies',
+			$this->supported_currencies
+		);
+	}
+
+	/**
+	 * Get_icon function.
+	 *
+	 * @return string
+	 * @since 5.8.0
+	 */
+	public function get_icon() {
+		$icons = $this->payment_icons();
+
+		$icons_str = '';
+
+		$icons_str .= isset( $icons[ $this->stripe_id ] ) ? $icons[ $this->stripe_id ] : '';
+
+		return apply_filters( 'woocommerce_gateway_icon', $icons_str, $this->id );
+	}
+
+	/**
+	 * Payment_scripts function.
+	 *
+	 * @since 5.8.0
+	 */
+	public function payment_scripts() {
+		if ( ! is_cart() && ! is_checkout() && ! isset( $_GET['pay_for_order'] ) && ! is_add_payment_method_page() ) {
+			return;
+		}
+
+		parent::payment_scripts();
+	}
+
+	/**
+	 * Initialize Gateway Settings Form Fields.
+	 *
+	 * @since 5.8.0
+	 */
+	public function init_form_fields() {
+		$this->form_fields = require WC_STRIPE_PLUGIN_PATH . '/includes/admin/stripe-' . $this->stripe_id . '-settings.php';
+	}
+
+	/**
+	 * Process the payment
+	 *
+	 * @param int $order_id Reference.
+	 * @param bool $retry Should we retry on fail.
+	 * @param bool $force_save_source Force payment source to be saved.
+	 *
+	 * @throws Exception If payment will not be accepted.
+	 *
+	 * @since 5.8.0
+	 * @since 5.8.0
+	 */
+	public function process_payment( $order_id, $retry = true, $force_save_save = false ) {
+		try {
+			$order = wc_get_order( $order_id );
+
+			if ( ! in_array( $order->get_billing_country(), $this->supported_countries ) ) {
+				throw new \Exception( __( 'This payment method is not available in the selected country', 'woocommerce-gateway-stripe' ) );
+			}
+
+			$intent = $this->create_or_update_payment_intent( $order );
+
+			$order->update_meta_data( '_stripe_upe_payment_type', $this->stripe_id );
+			$order->update_status( 'pending', __( 'Awaiting payment.', 'woocommerce-gateway-stripe' ) );
+			$order->save();
+
+			WC_Stripe_Helper::add_payment_intent_to_order( $intent->id, $order );
+
+			return [
+				'result'               => 'success',
+				'redirect'             => $this->get_return_url( $order ),
+				'intent_id'            => $intent->id,
+				'client_secret'        => $intent->client_secret,
+				'order_id'             => $order_id,
+				'confirm_payment_data' => $this->get_confirm_payment_data( $order ),
+			];
+		} catch ( WC_Stripe_Exception $e ) {
+			wc_add_notice( $e->getLocalizedMessage(), 'error' );
+			WC_Stripe_Logger::log( 'Error: ' . $e->getMessage() );
+
+			do_action( 'wc_gateway_stripe_process_payment_error', $e, $order );
+
+			$statuses = apply_filters(
+				'wc_stripe_allowed_payment_processing_statuses',
+				[ 'pending', 'failed' ],
+				$order
+			);
+
+			if ( $order->has_status( $statuses ) ) {
+				$this->send_failed_order_email( $order_id );
+			}
+
+			return [
+				'result'   => 'fail',
+				'redirect' => '',
+			];
+		}
+	}
+
+	/**
+	 * Creates payment intent using order and store details.
+	 * If the order already has a Payment Intent it gets updated
+	 *
+	 * @param WC_Order $order The order.
+	 *
+	 * @return object
+	 * @throws Exception - If the create intent call returns with an error.
+	 * @since 5.8.0
+	 */
+	public function create_or_update_payment_intent( $order ) {
+		$amount   = $order->get_total();
+		$currency = $order->get_currency();
+
+		$this->validate_amount_limits( $amount );
+
+		$intent = $this->get_intent_from_order( $order );
+
+		$intent_to_be_updated = '';
+
+		if ( $intent ) {
+			$intent_to_be_updated = '/' . $intent->id;
+		}
+
+		$body = [
+			'amount'               => WC_Stripe_Helper::get_stripe_amount( $amount, strtolower( $currency ) ),
+			'currency'             => strtolower( $currency ),
+			'payment_method_types' => [ $this->stripe_id ],
+			'description'          => __( 'stripe - Order', 'woocommerce-gateway-stripe' ) . ' ' . $order->get_id(),
+		];
+
+		if ( method_exists( $this, 'update_request_body_on_create_or_update_payment_intent' ) ) {
+			$body = $this->update_request_body_on_create_or_update_payment_intent( $body );
+		}
+
+		$payment_intent = WC_Stripe_API::request(
+			$body,
+			'payment_intents' . $intent_to_be_updated
+		);
+
+		if ( ! empty( $payment_intent->error ) ) {
+			throw new Exception( $payment_intent->error->message );
+		}
+
+		return $payment_intent;
+	}
+
+	/**
+	 * Validates the minimum and maximum amount.
+	 * Override this method when extending the class
+	 *
+	 * @param $amount
+	 *
+	 * @throws WC_Stripe_Exception when amount is out of range
+	 * @since 5.8.0
+	 */
+	abstract protected function validate_amount_limits( $amount );
+
+	/**
+	 * Updates the payment intent when trying to pay again via Pay Order Page
+	 */
+	public function update_payment_intent_ajax() {
+		try {
+			$is_nonce_valid = check_ajax_referer( 'wc_stripe_update_payment_intent_nonce', false, false );
+			if ( ! $is_nonce_valid ) {
+				throw new Exception( __( "We're not able to process this payment. Please refresh the page and try again.", 'woocommerce-gateway-stripe' ) );
+			}
+
+			$order_id = isset( $_POST['order_id'] ) ? absint( $_POST['order_id'] ) : null;
+
+			if ( ! $order_id ) {
+				throw new \Exception( __( 'Order Id not found, send an order id', 'woocommerce-gateway-stripe' ) );
+			}
+
+			$order = wc_get_order( $order_id );
+			$order->set_payment_method( $this );
+			$intent = $this->create_or_update_payment_intent( $order );
+
+			$order->update_status( 'pending', __( 'Awaiting payment.', 'woocommerce-gateway-stripe' ) );
+			$order->update_meta_data( '_stripe_upe_payment_type', $this->stripe_id );
+			$order->save();
+
+			wp_send_json(
+				[
+					'redirect'             => $this->get_return_url( $order ),
+					'intent_id'            => $intent->id,
+					'client_secret'        => $intent->client_secret,
+					'order_id'             => $order_id,
+					'result'               => 'success',
+					'confirm_payment_data' => $this->get_confirm_payment_data( $order ),
+				]
+			);
+		} catch ( Exception $e ) {
+			// Send back error so it can be displayed to the customer.
+			wp_send_json(
+				[
+					'result'   => 'fail',
+					'messages' => __( "We're not able to process this payment. Please refresh the page and try again.", 'woocommerce-gateway-stripe' ),
+				]
+			);
+		}
+	}
+
+	/**
+	 * Gather the data necessary to confirm the payment via javascript
+	 * Override this when extending the class
+	 *
+	 * @param WC_Order $order
+	 *
+	 * @return array
+	 */
+	protected function get_confirm_payment_data( $order ) {
+		return [];
+	}
+}
diff --git a/includes/abstracts/abstract-wc-stripe-payment-gateway.php b/includes/abstracts/abstract-wc-stripe-payment-gateway.php
new file mode 100644
index 0000000..d831915
--- /dev/null
+++ b/includes/abstracts/abstract-wc-stripe-payment-gateway.php
@@ -0,0 +1,1866 @@
+<?php
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
+
+// phpcs:disable WordPress.Files.FileName
+
+/**
+ * Abstract class that will be inherited by all payment methods.
+ *
+ * @extends WC_Payment_Gateway_CC
+ *
+ * @since 4.0.0
+ */
+abstract class WC_Stripe_Payment_Gateway extends WC_Payment_Gateway_CC {
+
+	use WC_Stripe_Subscriptions_Trait;
+	use WC_Stripe_Pre_Orders_Trait;
+
+	/**
+	 * The delay between retries.
+	 *
+	 * @var int
+	 */
+	protected $retry_interval = 1;
+
+	/**
+	 * Fallback method to be inherited by all payment methods. Stripe UPE will override it.
+	 *
+	 * @return string[]
+	 */
+	public function get_upe_enabled_payment_method_ids() {
+		return [ 'card' ];
+	}
+
+	/**
+	 * Fallback method to be inherited by all payment methods. Stripe UPE will override it.
+	 *
+	 * @return string[]
+	 */
+	public function get_upe_available_payment_methods() {
+		return [ 'card' ];
+	}
+
+	/**
+	 * Checks whether the gateway is enabled.
+	 *
+	 * @return bool The result.
+	 */
+	public function is_enabled() {
+		return 'yes' === $this->get_option( 'enabled' );
+	}
+
+	/**
+	 * Disables gateway.
+	 */
+	public function disable() {
+		$this->update_option( 'enabled', 'no' );
+	}
+
+	/**
+	 * Enables gateway.
+	 */
+	public function enable() {
+		$this->update_option( 'enabled', 'yes' );
+	}
+
+	/**
+	 * Displays the admin settings webhook description.
+	 *
+	 * @since 4.1.0
+	 * @version 5.0.0
+	 * @return mixed
+	 */
+	public function display_admin_settings_webhook_description() {
+		/* translators: 1) webhook url */
+		$description = sprintf( __( 'You must add the following webhook endpoint <strong style="background-color:#ddd;">&nbsp;%s&nbsp;</strong> to your <a href="https://dashboard.stripe.com/account/webhooks" target="_blank">Stripe account settings</a> (if there isn\'t one already enabled). This will enable you to receive notifications on the charge statuses.', 'woocommerce-gateway-stripe' ), WC_Stripe_Helper::get_webhook_url() );
+
+		$webhook_status = WC_Stripe_Webhook_State::get_webhook_status_message();
+
+		return $description . '<br><br>' . $webhook_status;
+	}
+
+	/**
+	 * Prints the admin options for the gateway.
+	 * Inserts an empty placeholder div feature flag is enabled.
+	 */
+	public function admin_options() {
+		$form_fields = $this->get_form_fields();
+
+		echo '<h2>' . esc_html( $this->get_method_title() );
+		wc_back_link( __( 'Return to payments', 'woocommerce-gateway-stripe' ), admin_url( 'admin.php?page=wc-settings&tab=checkout' ) );
+		echo '</h2>';
+
+		$this->render_upe_settings();
+	}
+
+	/**
+	 * Inserts an empty placeholder div for new account card when Stripe is not connected.
+	 * Inserts an empty placeholder div for UPE opt-in banner within the existing form fields, otherwise.
+	 */
+	public function render_upe_settings() {
+		global $hide_save_button;
+		$hide_save_button    = true;
+		$is_stripe_connected = woocommerce_gateway_stripe()->connect->is_connected();
+
+		echo $is_stripe_connected ? '<div id="wc-stripe-payment-gateway-container"></div>' : '<div id="wc-stripe-new-account-container"></div>';
+	}
+
+	/**
+	 * Displays the save to account checkbox.
+	 *
+	 * @since 4.1.0
+	 * @version 5.6.0
+	 */
+	public function save_payment_method_checkbox( $force_checked = false ) {
+		$id = 'wc-' . $this->id . '-new-payment-method';
+		?>
+		<fieldset <?php echo $force_checked ? 'style="display:none;"' : ''; /* phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped */ ?>>
+			<p class="form-row woocommerce-SavedPaymentMethods-saveNew">
+				<input id="<?php echo esc_attr( $id ); ?>" name="<?php echo esc_attr( $id ); ?>" type="checkbox" value="true" style="width:auto;" <?php echo $force_checked ? 'checked' : ''; /* phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped */ ?> />
+				<label for="<?php echo esc_attr( $id ); ?>" style="display:inline;">
+					<?php echo esc_html( apply_filters( 'wc_stripe_save_to_account_text', __( 'Save payment information to my account for future purchases.', 'woocommerce-gateway-stripe' ) ) ); ?>
+				</label>
+			</p>
+		</fieldset>
+		<?php
+	}
+
+	/**
+	 * Checks to see if request is invalid and that
+	 * they are worth retrying.
+	 *
+	 * @since 4.0.5
+	 * @param array $error
+	 */
+	public function is_retryable_error( $error ) {
+		return (
+			'invalid_request_error' === $error->type ||
+			'idempotency_error' === $error->type ||
+			'rate_limit_error' === $error->type ||
+			'api_connection_error' === $error->type ||
+			'api_error' === $error->type
+		);
+	}
+
+	/**
+	 * Checks to see if error is of same idempotency key
+	 * error due to retries with different parameters.
+	 *
+	 * @since 4.1.0
+	 * @param array $error
+	 */
+	public function is_same_idempotency_error( $error ) {
+		return (
+			$error &&
+			'idempotency_error' === $error->type &&
+			preg_match( '/Keys for idempotent requests can only be used with the same parameters they were first used with./i', $error->message )
+		);
+	}
+
+	/**
+	 * Checks to see if error is of invalid request
+	 * error and it is no such customer.
+	 *
+	 * @since 4.1.0
+	 * @param array $error
+	 */
+	public function is_no_such_customer_error( $error ) {
+		return (
+			$error &&
+			'invalid_request_error' === $error->type &&
+			preg_match( '/No such customer/i', $error->message )
+		);
+	}
+
+	/**
+	 * Checks to see if error is of invalid request
+	 * error and it is no such token.
+	 *
+	 * @since 4.1.0
+	 * @param array $error
+	 */
+	public function is_no_such_token_error( $error ) {
+		return (
+			$error &&
+			'invalid_request_error' === $error->type &&
+			preg_match( '/No such token/i', $error->message )
+		);
+	}
+
+	/**
+	 * Checks to see if error is of invalid request
+	 * error and it is no such source.
+	 *
+	 * @since 4.1.0
+	 * @param array $error
+	 */
+	public function is_no_such_source_error( $error ) {
+		return (
+			$error &&
+			'invalid_request_error' === $error->type &&
+			preg_match( '/No such (source|PaymentMethod)/i', $error->message )
+		);
+	}
+
+	/**
+	 * Checks to see if error is of invalid request
+	 * error and it is no such source linked to customer.
+	 *
+	 * @since 4.1.0
+	 * @param array $error
+	 */
+	public function is_no_linked_source_error( $error ) {
+		return (
+			$error &&
+			'invalid_request_error' === $error->type &&
+			preg_match( '/does not have a linked source with ID/i', $error->message )
+		);
+	}
+
+	/**
+	 * Check to see if we need to update the idempotency
+	 * key to be different from previous charge request.
+	 *
+	 * @since 4.1.0
+	 * @param object $source_object
+	 * @param object $error
+	 * @return bool
+	 */
+	public function need_update_idempotency_key( $source_object, $error ) {
+		return (
+			$error &&
+			1 < $this->retry_interval &&
+			! empty( $source_object ) &&
+			'chargeable' === $source_object->status &&
+			self::is_same_idempotency_error( $error )
+		);
+	}
+
+	/**
+	 * Checks if keys are set and valid.
+	 *
+	 * @since 4.0.6
+	 * @return bool True if the keys are set *and* valid, false otherwise (for example, if keys are empty or the secret key was pasted as publishable key).
+	 */
+	public function are_keys_set() {
+		// NOTE: updates to this function should be added to are_keys_set()
+		// in includes/payment-methods/class-wc-stripe-payment-request.php
+
+		if ( $this->testmode ) {
+			return preg_match( '/^pk_test_/', $this->publishable_key )
+				&& preg_match( '/^[rs]k_test_/', $this->secret_key );
+		} else {
+			return preg_match( '/^pk_live_/', $this->publishable_key )
+				&& preg_match( '/^[rs]k_live_/', $this->secret_key );
+		}
+	}
+
+	/**
+	 * Check if we need to make gateways available.
+	 *
+	 * @since 4.1.3
+	 */
+	public function is_available() {
+		if ( 'yes' === $this->enabled ) {
+			return $this->are_keys_set();
+		}
+
+		return parent::is_available();
+	}
+
+	public function save_payment_method_requested() {
+		$payment_method = isset( $_POST['payment_method'] ) ? wc_clean( wp_unslash( $_POST['payment_method'] ) ) : 'stripe';
+
+		return isset( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] ) && ! empty( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] );
+	}
+
+	/**
+	 * Customer param wrong? The user may have been deleted on stripe's end. Remove customer_id. Can be retried without.
+	 *
+	 * @since 4.2.0
+	 * @param object   $error The error that was returned from Stripe's API.
+	 * @param WC_Order $order The order those payment is being processed.
+	 * @return bool           A flag that indicates that the customer does not exist and should be removed.
+	 */
+	public function maybe_remove_non_existent_customer( $error, $order ) {
+		if ( ! $this->is_no_such_customer_error( $error ) ) {
+			return false;
+		}
+
+		delete_user_option( $order->get_customer_id(), '_stripe_customer_id' );
+		$order->delete_meta_data( '_stripe_customer_id' );
+		$order->save();
+
+		return true;
+	}
+
+	/**
+	 * All payment icons that work with Stripe. Some icons references
+	 * WC core icons.
+	 *
+	 * @since 4.0.0
+	 * @since 4.1.0 Changed to using img with svg (colored) instead of fonts.
+	 * @return array
+	 */
+	public function payment_icons() {
+		return apply_filters(
+			'wc_stripe_payment_icons',
+			[
+				'alipay'     => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/alipay.svg" class="stripe-alipay-icon stripe-icon" alt="Alipay" />',
+				'wechat'     => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/wechat.svg" class="stripe-wechat-icon stripe-icon" alt="Wechat Pay" />',
+				'bancontact' => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/bancontact.svg" class="stripe-bancontact-icon stripe-icon" alt="Bancontact" />',
+				'ideal'      => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/ideal.svg" class="stripe-ideal-icon stripe-icon" alt="iDEAL" />',
+				'p24'        => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/p24.svg" class="stripe-p24-icon stripe-icon" alt="P24" />',
+				'giropay'    => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/giropay.svg" class="stripe-giropay-icon stripe-icon" alt="giropay" />',
+				'eps'        => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/eps.svg" class="stripe-eps-icon stripe-icon" alt="EPS" />',
+				'multibanco' => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/multibanco.svg" class="stripe-multibanco-icon stripe-icon" alt="Multibanco" />',
+				'sofort'     => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/sofort.svg" class="stripe-sofort-icon stripe-icon" alt="Sofort" />',
+				'sepa'       => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/sepa.svg" class="stripe-sepa-icon stripe-icon" alt="SEPA" />',
+				'boleto'     => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/boleto.svg" class="stripe-boleto-icon stripe-icon" alt="Boleto" />',
+				'oxxo'       => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/oxxo.svg" class="stripe-oxxo-icon stripe-icon" alt="OXXO" />',
+			]
+		);
+	}
+
+	/**
+	 * Validates that the order meets the minimum order amount
+	 * set by Stripe.
+	 *
+	 * @since 4.0.0
+	 * @version 4.0.0
+	 * @param object $order
+	 */
+	public function validate_minimum_order_amount( $order ) {
+		if ( $order->get_total() * 100 < WC_Stripe_Helper::get_minimum_amount() ) {
+			/* translators: 1) amount (including currency symbol) */
+			throw new WC_Stripe_Exception( 'Did not meet minimum amount', sprintf( __( 'Sorry, the minimum allowed order total is %1$s to use this payment method.', 'woocommerce-gateway-stripe' ), wc_price( WC_Stripe_Helper::get_minimum_amount() / 100 ) ) );
+		}
+	}
+
+	/**
+	 * Gets the transaction URL linked to Stripe dashboard.
+	 *
+	 * @since 4.0.0
+	 * @version 4.0.0
+	 */
+	public function get_transaction_url( $order ) {
+		if ( $this->testmode ) {
+			$this->view_transaction_url = 'https://dashboard.stripe.com/test/payments/%s';
+		} else {
+			$this->view_transaction_url = 'https://dashboard.stripe.com/payments/%s';
+		}
+
+		return parent::get_transaction_url( $order );
+	}
+
+	/**
+	 * Gets the saved customer id if exists.
+	 *
+	 * @since 4.0.0
+	 * @version 4.0.0
+	 */
+	public function get_stripe_customer_id( $order ) {
+		// Try to get it via the order first.
+		$customer = $order->get_meta( '_stripe_customer_id', true );
+
+		if ( empty( $customer ) ) {
+			$customer = get_user_option( '_stripe_customer_id', $order->get_customer_id() );
+		}
+
+		return $customer;
+	}
+
+	/**
+	 * Builds the return URL from redirects.
+	 *
+	 * @since 4.0.0
+	 * @version 4.0.0
+	 * @param object $order
+	 * @param int    $id Stripe session id.
+	 */
+	public function get_stripe_return_url( $order = null, $id = null ) {
+		if ( is_object( $order ) ) {
+			if ( empty( $id ) ) {
+				$id = uniqid();
+			}
+
+			$order_id = $order->get_id();
+
+			$args = [
+				'utm_nooverride' => '1',
+				'order_id'       => $order_id,
+			];
+
+			return wp_sanitize_redirect( esc_url_raw( add_query_arg( $args, $this->get_return_url( $order ) ) ) );
+		}
+
+		return wp_sanitize_redirect( esc_url_raw( add_query_arg( [ 'utm_nooverride' => '1' ], $this->get_return_url() ) ) );
+	}
+
+	/**
+	 * Generate the request for the payment.
+	 *
+	 * @since 3.1.0
+	 * @version 4.5.4
+	 * @param  WC_Order $order
+	 * @param  object   $prepared_payment_method Stripe Payment Method or Source.
+	 * @return array()
+	 */
+	public function generate_payment_request( $order, $prepared_payment_method ) {
+		$settings                              = get_option( 'woocommerce_stripe_settings', [] );
+		$statement_descriptor                  = ! empty( $settings['statement_descriptor'] ) ? str_replace( "'", '', $settings['statement_descriptor'] ) : '';
+		$short_statement_descriptor            = ! empty( $settings['short_statement_descriptor'] ) ? str_replace( "'", '', $settings['short_statement_descriptor'] ) : '';
+		$is_short_statement_descriptor_enabled = ! empty( $settings['is_short_statement_descriptor_enabled'] ) && 'yes' === $settings['is_short_statement_descriptor_enabled'];
+		$capture                               = ! empty( $settings['capture'] ) && 'yes' === $settings['capture'] ? true : false;
+		$post_data                             = [];
+		$post_data['currency']                 = strtolower( $order->get_currency() );
+		$post_data['amount']                   = WC_Stripe_Helper::get_stripe_amount( $order->get_total(), $post_data['currency'] );
+		/* translators: 1) blog name 2) order number */
+		$post_data['description'] = sprintf( __( '%1$s - Order %2$s', 'woocommerce-gateway-stripe' ), wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ), $order->get_order_number() );
+		$billing_email            = $order->get_billing_email();
+		$billing_first_name       = $order->get_billing_first_name();
+		$billing_last_name        = $order->get_billing_last_name();
+
+		if ( ! empty( $billing_email ) && apply_filters( 'wc_stripe_send_stripe_receipt', false ) ) {
+			$post_data['receipt_email'] = $billing_email;
+		}
+
+		switch ( $order->get_payment_method() ) {
+			case 'stripe':
+				if ( $is_short_statement_descriptor_enabled && ! ( empty( $short_statement_descriptor ) && empty( $statement_descriptor ) ) ) {
+					$post_data['statement_descriptor'] = WC_Stripe_Helper::get_dynamic_statement_descriptor( $short_statement_descriptor, $order, $statement_descriptor );
+				} elseif ( ! empty( $statement_descriptor ) ) {
+					$post_data['statement_descriptor'] = WC_Stripe_Helper::clean_statement_descriptor( $statement_descriptor );
+				}
+
+				$post_data['capture'] = $capture ? 'true' : 'false';
+				break;
+			case 'stripe_sepa':
+				if ( ! empty( $statement_descriptor ) ) {
+					$post_data['statement_descriptor'] = WC_Stripe_Helper::clean_statement_descriptor( $statement_descriptor );
+				}
+				// other payment methods error if we try to add a statement descriptor in the request
+		}
+
+		if ( method_exists( $order, 'get_shipping_postcode' ) && ! empty( $order->get_shipping_postcode() ) ) {
+			$post_data['shipping'] = [
+				'name'    => trim( $order->get_shipping_first_name() . ' ' . $order->get_shipping_last_name() ),
+				'address' => [
+					'line1'       => $order->get_shipping_address_1(),
+					'line2'       => $order->get_shipping_address_2(),
+					'city'        => $order->get_shipping_city(),
+					'country'     => $order->get_shipping_country(),
+					'postal_code' => $order->get_shipping_postcode(),
+					'state'       => $order->get_shipping_state(),
+				],
+			];
+		}
+
+		$post_data['expand[]'] = 'balance_transaction';
+
+		$metadata = [
+			__( 'customer_name', 'woocommerce-gateway-stripe' ) => sanitize_text_field( $billing_first_name ) . ' ' . sanitize_text_field( $billing_last_name ),
+			__( 'customer_email', 'woocommerce-gateway-stripe' ) => sanitize_email( $billing_email ),
+			'order_id' => $order->get_order_number(),
+			'site_url' => esc_url( get_site_url() ),
+		];
+
+		if ( $this->has_subscription( $order->get_id() ) ) {
+			$metadata += [
+				'payment_type' => 'recurring',
+			];
+		}
+
+		$post_data['metadata'] = apply_filters( 'wc_stripe_payment_metadata', $metadata, $order, $prepared_payment_method );
+
+		if ( $prepared_payment_method->customer ) {
+			$post_data['customer'] = $prepared_payment_method->customer;
+		}
+
+		if ( ! empty( $prepared_payment_method->source ) ) {
+			$post_data['source'] = $prepared_payment_method->source;
+		}
+
+		if ( ! empty( $prepared_payment_method->payment_method ) ) {
+			$post_data['payment_method'] = $prepared_payment_method->payment_method;
+		}
+
+		/**
+		 * Filter the return value of the WC_Payment_Gateway_CC::generate_payment_request.
+		 *
+		 * @since 3.1.0
+		 * @param array $post_data
+		 * @param WC_Order $order
+		 * @param object $source
+		 */
+		return apply_filters( 'wc_stripe_generate_payment_request', $post_data, $order, $prepared_payment_method );
+	}
+
+	/**
+	 * Store extra meta data for an order from a Stripe Response.
+	 */
+	public function process_response( $response, $order ) {
+		WC_Stripe_Logger::log( 'Processing response: ' . print_r( $response, true ) );
+
+		$order_id = $order->get_id();
+		$captured = ( isset( $response->captured ) && $response->captured ) ? 'yes' : 'no';
+
+		// Store charge data.
+		$order->update_meta_data( '_stripe_charge_captured', $captured );
+
+		if ( isset( $response->balance_transaction ) ) {
+			$this->update_fees( $order, is_string( $response->balance_transaction ) ? $response->balance_transaction : $response->balance_transaction->id );
+		}
+
+		if ( 'yes' === $captured ) {
+			/**
+			 * Charge can be captured but in a pending state. Payment methods
+			 * that are asynchronous may take couple days to clear. Webhook will
+			 * take care of the status changes.
+			 */
+			if ( 'pending' === $response->status ) {
+				$order_stock_reduced = $order->get_meta( '_order_stock_reduced', true );
+
+				if ( ! $order_stock_reduced ) {
+					wc_reduce_stock_levels( $order_id );
+				}
+
+				$order->set_transaction_id( $response->id );
+				/* translators: transaction id */
+				$order->update_status( 'on-hold', sprintf( __( 'Stripe charge awaiting payment: %s.', 'woocommerce-gateway-stripe' ), $response->id ) );
+			}
+
+			if ( 'succeeded' === $response->status ) {
+				$order->payment_complete( $response->id );
+
+				/* translators: transaction id */
+				$message = sprintf( __( 'Stripe charge complete (Charge ID: %s)', 'woocommerce-gateway-stripe' ), $response->id );
+				$order->add_order_note( $message );
+			}
+
+			if ( 'failed' === $response->status ) {
+				$localized_message = __( 'Payment processing failed. Please retry.', 'woocommerce-gateway-stripe' );
+				$order->add_order_note( $localized_message );
+				throw new WC_Stripe_Exception( print_r( $response, true ), $localized_message );
+			}
+		} else {
+			$order->set_transaction_id( $response->id );
+
+			if ( $order->has_status( [ 'pending', 'failed' ] ) ) {
+				wc_reduce_stock_levels( $order_id );
+			}
+
+			/* translators: transaction id */
+			$order->update_status( 'on-hold', sprintf( __( 'Stripe charge authorized (Charge ID: %s). Process order to take payment, or cancel to remove the pre-authorization. Attempting to refund the order in part or in full will release the authorization and cancel the payment.', 'woocommerce-gateway-stripe' ), $response->id ) );
+		}
+
+		if ( is_callable( [ $order, 'save' ] ) ) {
+			$order->save();
+		}
+
+		do_action( 'wc_gateway_stripe_process_response', $response, $order );
+
+		return $response;
+	}
+
+	/**
+	 * Sends the failed order email to admin.
+	 *
+	 * @since 3.1.0
+	 * @version 4.0.0
+	 * @param int $order_id
+	 * @return null
+	 */
+	public function send_failed_order_email( $order_id ) {
+		$emails = WC()->mailer()->get_emails();
+		if ( ! empty( $emails ) && ! empty( $order_id ) ) {
+			$emails['WC_Email_Failed_Order']->trigger( $order_id );
+		}
+	}
+
+	/**
+	 * Get owner details.
+	 *
+	 * @since 4.0.0
+	 * @version 4.0.0
+	 * @param object $order
+	 * @return object $details
+	 */
+	public function get_owner_details( $order ) {
+		$billing_first_name = $order->get_billing_first_name();
+		$billing_last_name  = $order->get_billing_last_name();
+
+		$details = [];
+
+		$name  = $billing_first_name . ' ' . $billing_last_name;
+		$email = $order->get_billing_email();
+		$phone = $order->get_billing_phone();
+
+		if ( ! empty( $phone ) ) {
+			$details['phone'] = $phone;
+		}
+
+		if ( ! empty( $name ) ) {
+			$details['name'] = $name;
+		}
+
+		if ( ! empty( $email ) ) {
+			$details['email'] = $email;
+		}
+
+		$details['address']['line1']       = $order->get_billing_address_1();
+		$details['address']['line2']       = $order->get_billing_address_2();
+		$details['address']['state']       = $order->get_billing_state();
+		$details['address']['city']        = $order->get_billing_city();
+		$details['address']['postal_code'] = $order->get_billing_postcode();
+		$details['address']['country']     = $order->get_billing_country();
+
+		return (object) apply_filters( 'wc_stripe_owner_details', $details, $order );
+	}
+
+	/**
+	 * Get source object by source id.
+	 *
+	 * @since 4.0.3
+	 * @param string $source_id The source ID to get source object for.
+	 */
+	public function get_source_object( $source_id = '' ) {
+		if ( empty( $source_id ) ) {
+			return '';
+		}
+
+		$source_object = WC_Stripe_API::retrieve( 'sources/' . $source_id );
+
+		if ( ! empty( $source_object->error ) ) {
+			throw new WC_Stripe_Exception( print_r( $source_object, true ), $source_object->error->message );
+		}
+
+		return $source_object;
+	}
+
+	/**
+	 * Checks if card is a prepaid card.
+	 *
+	 * @since 4.0.6
+	 * @param object $source_object
+	 * @return bool
+	 */
+	public function is_prepaid_card( $source_object ) {
+		return (
+			$source_object
+			&& in_array( $source_object->object, [ 'token', 'source', 'payment_method' ], true )
+			&& 'prepaid' === $source_object->card->funding
+		);
+	}
+
+	/**
+	 * Checks if a payment method object represents a prepaid credit card and
+	 * throws an exception if it is one, but that is not allowed.
+	 *
+	 * @since 4.2.0
+	 * @param object $prepared_source The object with source details.
+	 * @throws WC_Stripe_Exception An exception if the card is prepaid, but prepaid cards are not allowed.
+	 */
+	public function maybe_disallow_prepaid_card( $payment_method ) {
+		// Check if we don't allow prepaid credit cards.
+		if ( apply_filters( 'wc_stripe_allow_prepaid_card', true ) || ! $this->is_prepaid_card( $payment_method ) ) {
+			return;
+		}
+
+		$localized_message = __( 'Sorry, we\'re not accepting prepaid cards at this time. Your credit card has not been charged. Please try with alternative payment method.', 'woocommerce-gateway-stripe' );
+		throw new WC_Stripe_Exception( print_r( $payment_method, true ), $localized_message );
+	}
+
+	/**
+	 * Checks if source is of legacy type card.
+	 *
+	 * @since 4.0.8
+	 * @param string $source_id
+	 * @return bool
+	 */
+	public function is_type_legacy_card( $source_id ) {
+		return ( preg_match( '/^card_/', $source_id ) );
+	}
+
+	/**
+	 * Checks if source is payment method (pm_).
+	 *
+	 * @since 5.6.0
+	 * @param string $source_id
+	 * @return bool
+	 */
+	public function is_type_payment_method( $source_id ) {
+		return ( preg_match( '/^pm_/', $source_id ) );
+	}
+
+	/**
+	 * Checks if payment is via saved payment source.
+	 *
+	 * @since 4.1.0
+	 * @return bool
+	 */
+	public function is_using_saved_payment_method() {
+		$payment_method = isset( $_POST['payment_method'] ) ? wc_clean( wp_unslash( $_POST['payment_method'] ) ) : 'stripe';
+
+		return ( isset( $_POST[ 'wc-' . $payment_method . '-payment-token' ] ) && 'new' !== $_POST[ 'wc-' . $payment_method . '-payment-token' ] );
+	}
+
+	/**
+	 * Get payment source. This can be a new token/source or existing WC token.
+	 * If user is logged in and/or has WC account, create an account on Stripe.
+	 * This way we can attribute the payment to the user to better fight fraud.
+	 *
+	 * @since 3.1.0
+	 * @version 4.0.0
+	 * @param string $user_id
+	 * @param bool   $force_save_source Should we force save payment source.
+	 *
+	 * @throws Exception When card was not added or for and invalid card.
+	 * @return object
+	 */
+	public function prepare_source( $user_id, $force_save_source = false, $existing_customer_id = null ) {
+		$customer = new WC_Stripe_Customer( $user_id );
+		if ( ! empty( $existing_customer_id ) ) {
+			$customer->set_id( $existing_customer_id );
+		}
+
+		$force_save_source = apply_filters( 'wc_stripe_force_save_source', $force_save_source, $customer );
+		$source_object     = '';
+		$source_id         = '';
+		$wc_token_id       = false;
+		$payment_method    = isset( $_POST['payment_method'] ) ? wc_clean( wp_unslash( $_POST['payment_method'] ) ) : 'stripe';
+		$is_token          = false;
+
+		// New CC info was entered and we have a new source to process.
+		if ( ! empty( $_POST['stripe_source'] ) ) {
+			$source_object = self::get_source_object( wc_clean( wp_unslash( $_POST['stripe_source'] ) ) );
+			$source_id     = $source_object->id;
+
+			// This checks to see if customer opted to save the payment method to file.
+			$maybe_saved_card = isset( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] ) && ! empty( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] );
+
+			/**
+			 * This is true if the user wants to store the card to their account.
+			 * Criteria to save to file is they are logged in, they opted to save or product requirements and the source is
+			 * actually reusable. Either that or force_save_source is true.
+			 */
+			if ( ( $user_id && $this->saved_cards && $maybe_saved_card && 'reusable' === $source_object->usage ) || $force_save_source ) {
+				$response = $customer->attach_source( $source_object->id );
+
+				if ( ! empty( $response->error ) ) {
+					throw new WC_Stripe_Exception( print_r( $response, true ), $this->get_localized_error_message_from_response( $response ) );
+				}
+				if ( is_wp_error( $response ) ) {
+					throw new WC_Stripe_Exception( $response->get_error_message(), $response->get_error_message() );
+				}
+			}
+		} elseif ( $this->is_using_saved_payment_method() ) {
+			// Use an existing token, and then process the payment.
+			$wc_token_id = isset( $_POST[ 'wc-' . $payment_method . '-payment-token' ] ) ? wc_clean( wp_unslash( $_POST[ 'wc-' . $payment_method . '-payment-token' ] ) ) : '';
+			$wc_token    = WC_Payment_Tokens::get( $wc_token_id );
+
+			if ( ! $wc_token || $wc_token->get_user_id() !== get_current_user_id() ) {
+				WC()->session->set( 'refresh_totals', true );
+				throw new WC_Stripe_Exception( 'Invalid payment method', __( 'Invalid payment method. Please input a new card number.', 'woocommerce-gateway-stripe' ) );
+			}
+
+			$source_id = $wc_token->get_token();
+
+			if ( $this->is_type_legacy_card( $source_id ) || $this->is_type_payment_method( $source_id ) ) {
+				$is_token = true;
+			}
+		} elseif ( isset( $_POST['stripe_token'] ) && 'new' !== $_POST['stripe_token'] ) {
+			$stripe_token     = wc_clean( wp_unslash( $_POST['stripe_token'] ) );
+			$maybe_saved_card = isset( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] ) && ! empty( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] );
+
+			// This is true if the user wants to store the card to their account.
+			if ( ( $user_id && $this->saved_cards && $maybe_saved_card ) || $force_save_source ) {
+				$response = $customer->attach_source( $stripe_token );
+
+				if ( ! empty( $response->error ) ) {
+					throw new WC_Stripe_Exception( print_r( $response, true ), $response->error->message );
+				}
+				if ( is_wp_error( $response ) ) {
+					throw new WC_Stripe_Exception( $response->get_error_message(), $response->get_error_message() );
+				}
+				$source_id = $response->id;
+			} else {
+				$source_id = $stripe_token;
+				$is_token  = true;
+			}
+		}
+
+		$customer_id = $customer->get_id();
+		if ( ! $customer_id ) {
+			$customer->set_id( $customer->create_customer() );
+			$customer_id = $customer->get_id();
+		} else {
+			$customer_id = $customer->update_customer();
+		}
+
+		if ( empty( $source_object ) && ! $is_token ) {
+			$source_object = self::get_source_object( $source_id );
+		}
+
+		return (object) [
+			'token_id'       => $wc_token_id,
+			'customer'       => $customer_id,
+			'source'         => $source_id,
+			'source_object'  => $source_object,
+			'payment_method' => null,
+		];
+	}
+
+	/**
+	 * Get payment source from an order. This could be used in the future for
+	 * a subscription as an example, therefore using the current user ID would
+	 * not work - the customer won't be logged in :)
+	 *
+	 * Not using 2.6 tokens for this part since we need a customer AND a card
+	 * token, and not just one.
+	 *
+	 * @since 3.1.0
+	 * @version 4.0.0
+	 * @param object $order
+	 * @return object
+	 */
+	public function prepare_order_source( $order = null ) {
+		$stripe_customer = new WC_Stripe_Customer();
+		$stripe_source   = false;
+		$token_id        = false;
+		$source_object   = false;
+
+		if ( $order ) {
+			$order_id = $order->get_id();
+
+			$stripe_customer_id = $this->get_stripe_customer_id( $order );
+
+			if ( $stripe_customer_id ) {
+				$stripe_customer->set_id( $stripe_customer_id );
+			}
+
+			$source_id = $order->get_meta( '_stripe_source_id', true );
+
+			// Since 4.0.0, we changed card to source so we need to account for that.
+			if ( empty( $source_id ) ) {
+				$source_id = $order->get_meta( '_stripe_card_id', true );
+
+				// Take this opportunity to update the key name.
+				$order->update_meta_data( '_stripe_source_id', $source_id );
+
+				if ( is_callable( [ $order, 'save' ] ) ) {
+					$order->save();
+				}
+			}
+
+			if ( $source_id ) {
+				$stripe_source = $source_id;
+				$source_object = WC_Stripe_API::retrieve( 'sources/' . $source_id );
+			} elseif ( apply_filters( 'wc_stripe_use_default_customer_source', true ) ) {
+				/*
+				 * We can attempt to charge the customer's default source
+				 * by sending empty source id.
+				 */
+				$stripe_source = '';
+			}
+		}
+
+		return (object) [
+			'token_id'       => $token_id,
+			'customer'       => $stripe_customer ? $stripe_customer->get_id() : false,
+			'source'         => $stripe_source,
+			'source_object'  => $source_object,
+			'payment_method' => null,
+		];
+	}
+
+	/**
+	 * Checks whether a source exists.
+	 *
+	 * @since 4.2.0
+	 * @param  object $prepared_source The source that should be verified.
+	 * @throws WC_Stripe_Exception     An exception if the source ID is missing.
+	 */
+	public function check_source( $prepared_source ) {
+		if ( empty( $prepared_source->source ) ) {
+			$localized_message = __( 'Payment processing failed. Please retry.', 'woocommerce-gateway-stripe' );
+			throw new WC_Stripe_Exception( print_r( $prepared_source, true ), $localized_message );
+		}
+	}
+
+	/**
+	 * Save source to order.
+	 *
+	 * @since 3.1.0
+	 * @version 4.0.0
+	 * @param WC_Order $order For to which the source applies.
+	 * @param stdClass $source Source information.
+	 */
+	public function save_source_to_order( $order, $source ) {
+		// Store source in the order.
+		if ( $source->customer ) {
+			$order->update_meta_data( '_stripe_customer_id', $source->customer );
+		}
+
+		if ( $source->source ) {
+			$order->update_meta_data( '_stripe_source_id', $source->source );
+		}
+
+		if ( is_callable( [ $order, 'save' ] ) ) {
+			$order->save();
+		}
+
+		$this->maybe_update_source_on_subscription_order( $order, $source );
+	}
+
+	/**
+	 * Updates Stripe fees/net.
+	 * e.g usage would be after a refund.
+	 *
+	 * @since 4.0.0
+	 * @version 4.0.6
+	 * @param object $order The order object
+	 * @param int    $balance_transaction_id
+	 */
+	public function update_fees( $order, $balance_transaction_id ) {
+		$balance_transaction = WC_Stripe_API::retrieve( 'balance/history/' . $balance_transaction_id );
+
+		if ( empty( $balance_transaction->error ) ) {
+			if ( isset( $balance_transaction ) && isset( $balance_transaction->fee ) ) {
+				// Fees and Net needs to both come from Stripe to be accurate as the returned
+				// values are in the local currency of the Stripe account, not from WC.
+				$fee_refund = ! empty( $balance_transaction->fee ) ? WC_Stripe_Helper::format_balance_fee( $balance_transaction, 'fee' ) : 0;
+				$net_refund = ! empty( $balance_transaction->net ) ? WC_Stripe_Helper::format_balance_fee( $balance_transaction, 'net' ) : 0;
+
+				// Current data fee & net.
+				$fee_current = WC_Stripe_Helper::get_stripe_fee( $order );
+				$net_current = WC_Stripe_Helper::get_stripe_net( $order );
+
+				// Calculation.
+				$fee = (float) $fee_current + (float) $fee_refund;
+				$net = (float) $net_current + (float) $net_refund;
+
+				WC_Stripe_Helper::update_stripe_fee( $order, $fee );
+				WC_Stripe_Helper::update_stripe_net( $order, $net );
+
+				$currency = ! empty( $balance_transaction->currency ) ? strtoupper( $balance_transaction->currency ) : null;
+				WC_Stripe_Helper::update_stripe_currency( $order, $currency );
+
+				if ( is_callable( [ $order, 'save' ] ) ) {
+					$order->save();
+				}
+			}
+		} else {
+			WC_Stripe_Logger::log( 'Unable to update fees/net meta for order: ' . $order->get_id() );
+		}
+	}
+
+	/**
+	 * Refund a charge.
+	 *
+	 * @since 3.1.0
+	 * @version 4.9.0
+	 * @param  int $order_id
+	 * @param  float $amount
+	 *
+	 * @return bool
+	 * @throws Exception Throws exception when charge wasn't captured.
+	 */
+	public function process_refund( $order_id, $amount = null, $reason = '' ) {
+		$order = wc_get_order( $order_id );
+
+		if ( ! $order ) {
+			return false;
+		}
+
+		$request = [];
+
+		$order_currency = $order->get_currency();
+		$captured       = $order->get_meta( '_stripe_charge_captured', true );
+		$charge_id      = $order->get_transaction_id();
+
+		if ( ! $charge_id ) {
+			return false;
+		}
+
+		if ( ! is_null( $amount ) ) {
+			$request['amount'] = WC_Stripe_Helper::get_stripe_amount( $amount, $order_currency );
+		}
+
+		// If order is only authorized, don't pass amount.
+		if ( 'yes' !== $captured ) {
+			unset( $request['amount'] );
+		}
+
+		if ( $reason ) {
+			// Trim the refund reason to a max of 500 characters due to Stripe limits: https://stripe.com/docs/api/metadata.
+			if ( strlen( $reason ) > 500 ) {
+				$reason = function_exists( 'mb_substr' ) ? mb_substr( $reason, 0, 450 ) : substr( $reason, 0, 450 );
+				// Add some explainer text indicating where to find the full refund reason.
+				$reason = $reason . '... [See WooCommerce order page for full text.]';
+			}
+
+			$request['metadata'] = [
+				'reason' => $reason,
+			];
+		}
+
+		$request['charge'] = $charge_id;
+		WC_Stripe_Logger::log( "Info: Beginning refund for order {$charge_id} for the amount of {$amount}" );
+
+		$request = apply_filters( 'wc_stripe_refund_request', $request, $order );
+
+		$intent           = $this->get_intent_from_order( $order );
+		$intent_cancelled = false;
+		if ( $intent ) {
+			// If the order has a Payment Intent pending capture, then the Intent itself must be refunded (cancelled), not the Charge.
+			if ( ! empty( $intent->error ) ) {
+				$response         = $intent;
+				$intent_cancelled = true;
+			} elseif ( 'requires_capture' === $intent->status ) {
+				$result           = WC_Stripe_API::request(
+					[],
+					'payment_intents/' . $intent->id . '/cancel'
+				);
+				$intent_cancelled = true;
+
+				if ( ! empty( $result->error ) ) {
+					$response = $result;
+				} else {
+					$charge   = end( $result->charges->data );
+					$response = end( $charge->refunds->data );
+				}
+			}
+		}
+
+		if ( ! $intent_cancelled && 'yes' === $captured ) {
+			$response = WC_Stripe_API::request( $request, 'refunds' );
+		}
+
+		if ( ! empty( $response->error ) ) {
+			WC_Stripe_Logger::log( 'Error: ' . $response->error->message );
+
+			return new WP_Error(
+				'stripe_error',
+				sprintf(
+					/* translators: %1$s is a stripe error message */
+					__( 'There was a problem initiating a refund: %1$s', 'woocommerce-gateway-stripe' ),
+					$response->error->message
+				)
+			);
+
+		} elseif ( ! empty( $response->id ) ) {
+			$formatted_amount = wc_price( $response->amount / 100 );
+			if ( in_array( strtolower( $order->get_currency() ), WC_Stripe_Helper::no_decimal_currencies(), true ) ) {
+				$formatted_amount = wc_price( $response->amount );
+			}
+
+			// If charge wasn't captured, skip creating a refund and cancel order.
+			if ( 'yes' !== $captured ) {
+				/* translators: amount (including currency symbol) */
+				$order->add_order_note( sprintf( __( 'Pre-Authorization for %s voided.', 'woocommerce-gateway-stripe' ), $formatted_amount ) );
+				$order->update_status( 'cancelled' );
+				// If amount is set, that means this function was called from the manual refund form.
+				if ( ! is_null( $amount ) ) {
+					// Throw an exception to provide a custom message on why the refund failed.
+					throw new Exception( __( 'The authorization was voided and the order cancelled. Click okay to continue, then refresh the page.', 'woocommerce-gateway-stripe' ) );
+				} else {
+					// If refund was initiaded by changing order status, prevent refund without errors.
+					return false;
+				}
+			}
+
+			$order->update_meta_data( '_stripe_refund_id', $response->id );
+
+			if ( isset( $response->balance_transaction ) ) {
+				$this->update_fees( $order, $response->balance_transaction );
+			}
+
+			/* translators: 1) amount (including currency symbol) 2) transaction id 3) refund message */
+			$refund_message = sprintf( __( 'Refunded %1$s - Refund ID: %2$s - Reason: %3$s', 'woocommerce-gateway-stripe' ), $formatted_amount, $response->id, $reason );
+
+			$order->add_order_note( $refund_message );
+			WC_Stripe_Logger::log( 'Success: ' . html_entity_decode( wp_strip_all_tags( $refund_message ) ) );
+
+			return true;
+		}
+	}
+
+	/**
+	 * Add payment method via account screen.
+	 * We don't store the token locally, but to the Stripe API.
+	 *
+	 * @since 3.0.0
+	 * @version 4.0.0
+	 */
+	public function add_payment_method() {
+		$error     = false;
+		$error_msg = __( 'There was a problem adding the payment method.', 'woocommerce-gateway-stripe' );
+		$source_id = '';
+
+		if ( empty( $_POST['stripe_source'] ) && empty( $_POST['stripe_token'] ) || ! is_user_logged_in() ) {
+			$error = true;
+		}
+
+		$stripe_customer = new WC_Stripe_Customer( get_current_user_id() );
+
+		$source = ! empty( $_POST['stripe_source'] ) ? wc_clean( wp_unslash( $_POST['stripe_source'] ) ) : '';
+
+		$source_object = WC_Stripe_API::retrieve( 'sources/' . $source );
+
+		if ( isset( $source_object ) ) {
+			if ( ! empty( $source_object->error ) ) {
+				$error = true;
+			}
+
+			$source_id = $source_object->id;
+		} elseif ( isset( $_POST['stripe_token'] ) ) {
+			$source_id = wc_clean( wp_unslash( $_POST['stripe_token'] ) );
+		}
+
+		$response = $stripe_customer->add_source( $source_id );
+
+		if ( ! $response || is_wp_error( $response ) || ! empty( $response->error ) ) {
+			$error = true;
+		}
+
+		if ( $error ) {
+			wc_add_notice( $error_msg, 'error' );
+			WC_Stripe_Logger::log( 'Add payment method Error: ' . $error_msg );
+			return;
+		}
+
+		do_action( 'wc_stripe_add_payment_method_' . ( isset( $_POST['payment_method'] ) ? wc_clean( wp_unslash( $_POST['payment_method'] ) ) : '' ) . '_success', $source_id, $source_object );
+
+		return [
+			'result'   => 'success',
+			'redirect' => wc_get_endpoint_url( 'payment-methods' ),
+		];
+	}
+
+	/**
+	 * Gets the locale with normalization that only Stripe accepts.
+	 *
+	 * @since 4.0.6
+	 * @return string $locale
+	 */
+	public function get_locale() {
+		$locale = get_locale();
+
+		/*
+		 * Stripe expects Norwegian to only be passed NO.
+		 * But WP has different dialects.
+		 */
+		if ( 'NO' === substr( $locale, 3, 2 ) ) {
+			$locale = 'no';
+		} else {
+			$locale = substr( get_locale(), 0, 2 );
+		}
+
+		return $locale;
+	}
+
+	/**
+	 * Change the idempotency key so charge can
+	 * process order as a different transaction.
+	 *
+	 * @since 4.0.6
+	 * @param string $idempotency_key
+	 * @param array  $request
+	 */
+	public function change_idempotency_key( $idempotency_key, $request ) {
+		$customer = ! empty( $request['customer'] ) ? $request['customer'] : '';
+		$source   = ! empty( $request['source'] ) ? $request['source'] : $customer;
+		$count    = $this->retry_interval;
+
+		return $request['metadata']['order_id'] . '-' . $count . '-' . $source;
+	}
+
+	/**
+	 * Checks if request is the original to prevent double processing
+	 * on WC side. The original-request header and request-id header
+	 * needs to be the same to mean its the original request.
+	 *
+	 * @since 4.0.6
+	 * @param array $headers
+	 */
+	public function is_original_request( $headers ) {
+		if ( $headers['original-request'] === $headers['request-id'] ) {
+			return true;
+		}
+
+		return false;
+	}
+
+	/**
+	 * Generates the request when creating a new payment intent.
+	 *
+	 * @param WC_Order $order           The order that is being paid for.
+	 * @param object   $prepared_source The source that is used for the payment.
+	 * @return array                    The arguments for the request.
+	 */
+	public function generate_create_intent_request( $order, $prepared_source ) {
+		// The request for a charge contains metadata for the intent.
+		$full_request = $this->generate_payment_request( $order, $prepared_source );
+
+		$payment_method_types = [ 'card' ];
+		if ( WC_Stripe_Feature_Flags::is_upe_checkout_enabled() ) {
+			$payment_method_types = $this->get_upe_enabled_at_checkout_payment_method_ids();
+		} elseif ( isset( $prepared_source->source_object->type ) ) {
+			$payment_method_types = [ $prepared_source->source_object->type ];
+		}
+
+		$request = [
+			'source'               => $prepared_source->source,
+			'amount'               => WC_Stripe_Helper::get_stripe_amount( $order->get_total() ),
+			'currency'             => strtolower( $order->get_currency() ),
+			'description'          => $full_request['description'],
+			'metadata'             => $full_request['metadata'],
+			'capture_method'       => ( 'true' === $full_request['capture'] ) ? 'automatic' : 'manual',
+			'payment_method_types' => $payment_method_types,
+		];
+
+		$force_save_source = apply_filters( 'wc_stripe_force_save_source', false, $prepared_source->source );
+
+		if ( $this->save_payment_method_requested() || $this->has_subscription( $order->get_id() ) || $force_save_source ) {
+			$request['setup_future_usage']              = 'off_session';
+			$request['metadata']['save_payment_method'] = 'true';
+		}
+
+		if ( $prepared_source->customer ) {
+			$request['customer'] = $prepared_source->customer;
+		}
+
+		if ( isset( $full_request['statement_descriptor'] ) ) {
+			$request['statement_descriptor'] = $full_request['statement_descriptor'];
+		}
+
+		if ( isset( $full_request['shipping'] ) ) {
+			$request['shipping'] = $full_request['shipping'];
+		}
+
+		if ( isset( $full_request['receipt_email'] ) ) {
+			$request['receipt_email'] = $full_request['receipt_email'];
+		}
+
+		/**
+		 * Filter the return value of the WC_Payment_Gateway_CC::generate_create_intent_request.
+		 *
+		 * @since 3.1.0
+		 * @param array $request
+		 * @param WC_Order $order
+		 * @param object $source
+		 */
+		return apply_filters( 'wc_stripe_generate_create_intent_request', $request, $order, $prepared_source );
+	}
+
+	/**
+	 * Create the level 3 data array to send to Stripe when making a purchase.
+	 *
+	 * @param WC_Order $order The order that is being paid for.
+	 * @return array          The level 3 data to send to Stripe.
+	 */
+	public function get_level3_data_from_order( $order ) {
+		// Get the order items. Don't need their keys, only their values.
+		// Order item IDs are used as keys in the original order items array.
+		$order_items = array_values( $order->get_items( [ 'line_item', 'fee' ] ) );
+		$currency    = $order->get_currency();
+
+		$stripe_line_items = array_map(
+			function( $item ) use ( $currency ) {
+				if ( is_a( $item, 'WC_Order_Item_Product' ) ) {
+					$product_id = $item->get_variation_id()
+						? $item->get_variation_id()
+						: $item->get_product_id();
+					$subtotal   = $item->get_subtotal();
+				} else {
+					$product_id = substr( sanitize_title( $item->get_name() ), 0, 12 );
+					$subtotal   = $item->get_total();
+				}
+				$product_description = substr( $item->get_name(), 0, 26 );
+				$quantity            = $item->get_quantity();
+				$unit_cost           = WC_Stripe_Helper::get_stripe_amount( ( $subtotal / $quantity ), $currency );
+				$tax_amount          = WC_Stripe_Helper::get_stripe_amount( $item->get_total_tax(), $currency );
+				$discount_amount     = WC_Stripe_Helper::get_stripe_amount( $subtotal - $item->get_total(), $currency );
+
+				return (object) [
+					'product_code'        => (string) $product_id, // Up to 12 characters that uniquely identify the product.
+					'product_description' => $product_description, // Up to 26 characters long describing the product.
+					'unit_cost'           => $unit_cost, // Cost of the product, in cents, as a non-negative integer.
+					'quantity'            => $quantity, // The number of items of this type sold, as a non-negative integer.
+					'tax_amount'          => $tax_amount, // The amount of tax this item had added to it, in cents, as a non-negative integer.
+					'discount_amount'     => $discount_amount, // The amount an item was discounted—if there was a sale,for example, as a non-negative integer.
+				];
+			},
+			$order_items
+		);
+
+		$level3_data = [
+			'merchant_reference' => $order->get_id(), // An alphanumeric string of up to  characters in length. This unique value is assigned by the merchant to identify the order. Also known as an “Order ID”.
+			'shipping_amount'    => WC_Stripe_Helper::get_stripe_amount( (float) $order->get_shipping_total() + (float) $order->get_shipping_tax(), $currency ), // The shipping cost, in cents, as a non-negative integer.
+			'line_items'         => $stripe_line_items,
+		];
+
+		// The customer’s U.S. shipping ZIP code.
+		$shipping_address_zip = $order->get_shipping_postcode();
+		if ( $this->is_valid_us_zip_code( $shipping_address_zip ) ) {
+			$level3_data['shipping_address_zip'] = $shipping_address_zip;
+		}
+
+		// The merchant’s U.S. shipping ZIP code.
+		$store_postcode = get_option( 'woocommerce_store_postcode' );
+		if ( $this->is_valid_us_zip_code( $store_postcode ) ) {
+			$level3_data['shipping_from_zip'] = $store_postcode;
+		}
+
+		return $level3_data;
+	}
+
+	/**
+	 * Create a new PaymentIntent.
+	 *
+	 * @param WC_Order $order           The order that is being paid for.
+	 * @param object   $prepared_source The source that is used for the payment.
+	 * @return object                   An intent or an error.
+	 */
+	public function create_intent( $order, $prepared_source ) {
+		$request = $this->generate_create_intent_request( $order, $prepared_source );
+
+		// Create an intent that awaits an action.
+		$intent = WC_Stripe_API::request( $request, 'payment_intents' );
+		if ( ! empty( $intent->error ) ) {
+			return $intent;
+		}
+
+		$order_id = $order->get_id();
+		WC_Stripe_Logger::log( "Stripe PaymentIntent $intent->id initiated for order $order_id" );
+
+		// Save the intent ID to the order.
+		$this->save_intent_to_order( $order, $intent );
+
+		return $intent;
+	}
+
+	/**
+	 * Updates an existing intent with updated amount, source, and customer.
+	 *
+	 * @param object   $intent          The existing intent object.
+	 * @param WC_Order $order           The order.
+	 * @param object   $prepared_source Currently selected source.
+	 * @return object                   An updated intent.
+	 */
+	public function update_existing_intent( $intent, $order, $prepared_source ) {
+		$request = [];
+
+		if ( $prepared_source->source !== $intent->source ) {
+			$request['source'] = $prepared_source->source;
+		}
+
+		$new_amount = WC_Stripe_Helper::get_stripe_amount( $order->get_total() );
+		if ( $intent->amount !== $new_amount ) {
+			$request['amount'] = $new_amount;
+		}
+
+		if ( $prepared_source->customer && $intent->customer !== $prepared_source->customer ) {
+			$request['customer'] = $prepared_source->customer;
+		}
+
+		$request['payment_method_types'] = [ 'card' ];
+
+		if ( $this->has_subscription( $order->get_id() ) ) {
+			// If this is a failed subscription order payment, the intent should be
+			// prepared for future usage.
+			$request['setup_future_usage'] = 'off_session';
+		}
+
+		if ( empty( $request ) ) {
+			return $intent;
+		}
+
+		/**
+		 * Filter the value of the request.
+		 *
+		 * @since 6.1.0
+		 * @param array $request  Request to send to Stripe API.
+		 * @param WC_Order $order Order that the intent is associated with.
+		 * @param object $source  Currently selected source.
+		 */
+		$request = apply_filters( 'wc_stripe_update_existing_intent_request', $request, $order, $prepared_source );
+
+		$level3_data = $this->get_level3_data_from_order( $order );
+		return WC_Stripe_API::request_with_level3_data(
+			$request,
+			"payment_intents/$intent->id",
+			$level3_data,
+			$order
+		);
+	}
+
+	/**
+	 * Confirms an intent if it is the `requires_confirmation` state.
+	 *
+	 * @since 4.2.1
+	 * @param object   $intent          The intent to confirm.
+	 * @param WC_Order $order           The order that the intent is associated with.
+	 * @param object   $prepared_source The source that is being charged.
+	 * @return object                   Either an error or the updated intent.
+	 */
+	public function confirm_intent( $intent, $order, $prepared_source ) {
+		if ( 'requires_confirmation' !== $intent->status ) {
+			return $intent;
+		}
+
+		// Try to confirm the intent & capture the charge (if 3DS is not required).
+		$confirm_request = [
+			'source' => $prepared_source->source,
+		];
+
+		$level3_data      = $this->get_level3_data_from_order( $order );
+		$confirmed_intent = WC_Stripe_API::request_with_level3_data(
+			$confirm_request,
+			"payment_intents/$intent->id/confirm",
+			$level3_data,
+			$order
+		);
+
+		if ( ! empty( $confirmed_intent->error ) ) {
+			return $confirmed_intent;
+		}
+
+		// Save a note about the status of the intent.
+		$order_id = $order->get_id();
+		if ( 'succeeded' === $confirmed_intent->status ) {
+			WC_Stripe_Logger::log( "Stripe PaymentIntent $intent->id succeeded for order $order_id" );
+		} elseif ( 'requires_action' === $confirmed_intent->status ) {
+			WC_Stripe_Logger::log( "Stripe PaymentIntent $intent->id requires authentication for order $order_id" );
+		}
+
+		return $confirmed_intent;
+	}
+
+	/**
+	 * Saves intent to order.
+	 *
+	 * @since 3.2.0
+	 * @param WC_Order $order For to which the source applies.
+	 * @param stdClass $intent Payment intent information.
+	 */
+	public function save_intent_to_order( $order, $intent ) {
+		if ( 'payment_intent' === $intent->object ) {
+			WC_Stripe_Helper::add_payment_intent_to_order( $intent->id, $order );
+		} elseif ( 'setup_intent' === $intent->object ) {
+			$order->update_meta_data( '_stripe_setup_intent', $intent->id );
+		}
+
+		if ( is_callable( [ $order, 'save' ] ) ) {
+			$order->save();
+		}
+	}
+
+	/**
+	 * Retrieves the payment intent, associated with an order.
+	 *
+	 * @since 4.2
+	 * @param WC_Order $order The order to retrieve an intent for.
+	 * @return obect|bool     Either the intent object or `false`.
+	 */
+	public function get_intent_from_order( $order ) {
+		$intent_id = $order->get_meta( '_stripe_intent_id' );
+
+		if ( $intent_id ) {
+			return $this->get_intent( 'payment_intents', $intent_id );
+		}
+
+		// The order doesn't have a payment intent, but it may have a setup intent.
+		$intent_id = $order->get_meta( '_stripe_setup_intent' );
+
+		if ( $intent_id ) {
+			return $this->get_intent( 'setup_intents', $intent_id );
+		}
+
+		return false;
+	}
+
+	/**
+	 * Retrieves intent from Stripe API by intent id.
+	 *
+	 * @param string $intent_type   Either 'payment_intents' or 'setup_intents'.
+	 * @param string $intent_id     Intent id.
+	 * @return object|bool          Either the intent object or `false`.
+	 * @throws Exception            Throws exception for unknown $intent_type.
+	 */
+	private function get_intent( $intent_type, $intent_id ) {
+		if ( ! in_array( $intent_type, [ 'payment_intents', 'setup_intents' ], true ) ) {
+			throw new Exception( "Failed to get intent of type $intent_type. Type is not allowed" );
+		}
+
+		$response = WC_Stripe_API::request( [], "$intent_type/$intent_id?expand[]=payment_method", 'GET' );
+
+		if ( $response && isset( $response->{ 'error' } ) ) {
+			$error_response_message = print_r( $response, true );
+			WC_Stripe_Logger::log( "Failed to get Stripe intent $intent_type/$intent_id." );
+			WC_Stripe_Logger::log( "Response: $error_response_message" );
+			return false;
+		}
+
+		return $response;
+	}
+
+	/**
+	 * Locks an order for payment intent processing for 5 minutes.
+	 *
+	 * @since 4.2
+	 * @param WC_Order $order  The order that is being paid.
+	 * @param stdClass $intent The intent that is being processed.
+	 * @return bool            A flag that indicates whether the order is already locked.
+	 */
+	public function lock_order_payment( $order, $intent = null ) {
+		$order_id       = $order->get_id();
+		$transient_name = 'wc_stripe_processing_intent_' . $order_id;
+		$processing     = get_transient( $transient_name );
+
+		// Block the process if the same intent is already being handled.
+		if ( '-1' === $processing || ( isset( $intent->id ) && $processing === $intent->id ) ) {
+			return true;
+		}
+
+		// Save the new intent as a transient, eventually overwriting another one.
+		set_transient( $transient_name, empty( $intent ) ? '-1' : $intent->id, 5 * MINUTE_IN_SECONDS );
+
+		return false;
+	}
+
+	/**
+	 * Unlocks an order for processing by payment intents.
+	 *
+	 * @since 4.2
+	 * @param WC_Order $order The order that is being unlocked.
+	 */
+	public function unlock_order_payment( $order ) {
+		$order_id = $order->get_id();
+		delete_transient( 'wc_stripe_processing_intent_' . $order_id );
+	}
+
+	/**
+	 * Given a response from Stripe, check if it's a card error where authentication is required
+	 * to complete the payment.
+	 *
+	 * @param object $response The response from Stripe.
+	 * @return boolean Whether or not it's a 'authentication_required' error
+	 */
+	public function is_authentication_required_for_payment( $response ) {
+		return ( ! empty( $response->error ) && 'authentication_required' === $response->error->code )
+			|| ( ! empty( $response->last_payment_error ) && 'authentication_required' === $response->last_payment_error->code );
+	}
+
+	/**
+	 * Creates a SetupIntent for future payments, and saves it to the order.
+	 *
+	 * @param WC_Order $order           The ID of the (free/pre- order).
+	 * @param object   $prepared_source The source, entered/chosen by the customer.
+	 * @return string|null              The client secret of the intent, used for confirmation in JS.
+	 */
+	public function setup_intent( $order, $prepared_source ) {
+		// SEPA Direct Debit payments do not require any customer action after the source has been created.
+		// Once the customer has provided their IBAN details and accepted the mandate, no further action is needed and the resulting source is directly chargeable.
+		if ( 'sepa_debit' === $prepared_source->source_object->type ) {
+			return;
+		}
+
+		$order_id     = $order->get_id();
+		$setup_intent = WC_Stripe_API::request(
+			[
+				'payment_method' => $prepared_source->source,
+				'customer'       => $prepared_source->customer,
+				'confirm'        => 'true',
+			],
+			'setup_intents'
+		);
+
+		if ( is_wp_error( $setup_intent ) ) {
+			WC_Stripe_Logger::log( "Unable to create SetupIntent for Order #$order_id: " . print_r( $setup_intent, true ) );
+		} elseif ( 'requires_action' === $setup_intent->status ) {
+			$order->update_meta_data( '_stripe_setup_intent', $setup_intent->id );
+			$order->save();
+
+			return $setup_intent->client_secret;
+		}
+	}
+
+	/**
+	 * Create and confirm a new PaymentIntent.
+	 *
+	 * @param WC_Order $order           The order that is being paid for.
+	 * @param object   $prepared_source The source that is used for the payment.
+	 * @param float    $amount          The amount to charge. If not specified, it will be read from the order.
+	 * @return object                   An intent or an error.
+	 */
+	public function create_and_confirm_intent_for_off_session( $order, $prepared_source, $amount = null ) {
+		// The request for a charge contains metadata for the intent.
+		$full_request = $this->generate_payment_request( $order, $prepared_source );
+
+		$payment_method_types = [ 'card' ];
+		if ( WC_Stripe_Feature_Flags::is_upe_checkout_enabled() ) {
+			$payment_method_types = $this->get_upe_enabled_at_checkout_payment_method_ids();
+		} elseif ( isset( $prepared_source->source_object->type ) ) {
+			$payment_method_types = [ $prepared_source->source_object->type ];
+		}
+
+		$request = [
+			'amount'               => $amount ? WC_Stripe_Helper::get_stripe_amount( $amount, $full_request['currency'] ) : $full_request['amount'],
+			'currency'             => $full_request['currency'],
+			'description'          => $full_request['description'],
+			'metadata'             => $full_request['metadata'],
+			'payment_method_types' => $payment_method_types,
+			'off_session'          => 'true',
+			'confirm'              => 'true',
+			'confirmation_method'  => 'automatic',
+		];
+
+		if ( isset( $full_request['statement_descriptor'] ) ) {
+			$request['statement_descriptor'] = $full_request['statement_descriptor'];
+		}
+
+		if ( isset( $full_request['customer'] ) ) {
+			$request['customer'] = $full_request['customer'];
+		}
+
+		if ( isset( $full_request['source'] ) ) {
+			$is_source = 'src_' === substr( $full_request['source'], 0, 4 );
+			$request[ $is_source ? 'source' : 'payment_method' ] = $full_request['source'];
+		}
+
+		/**
+		 * Filter the value of the request.
+		 *
+		 * @since 4.5.0
+		 * @param array $request
+		 * @param WC_Order $order
+		 * @param object $source
+		 */
+		$request = apply_filters( 'wc_stripe_generate_create_intent_request', $request, $order, $prepared_source );
+
+		if ( isset( $full_request['shipping'] ) ) {
+			$request['shipping'] = $full_request['shipping'];
+		}
+
+		$level3_data                = $this->get_level3_data_from_order( $order );
+		$intent                     = WC_Stripe_API::request_with_level3_data(
+			$request,
+			'payment_intents',
+			$level3_data,
+			$order
+		);
+		$is_authentication_required = $this->is_authentication_required_for_payment( $intent );
+
+		if ( ! empty( $intent->error ) && ! $is_authentication_required ) {
+			return $intent;
+		}
+
+		$intent_id      = ( ! empty( $intent->error )
+			? $intent->error->payment_intent->id
+			: $intent->id
+		);
+		$payment_intent = ( ! empty( $intent->error )
+			? $intent->error->payment_intent
+			: $intent
+		);
+		$order_id       = $order->get_id();
+		WC_Stripe_Logger::log( "Stripe PaymentIntent $intent_id initiated for order $order_id" );
+
+		// Save the intent ID to the order.
+		$this->save_intent_to_order( $order, $payment_intent );
+
+		return $intent;
+	}
+
+	/** Verifies whether a certain ZIP code is valid for the US, incl. 4-digit extensions.
+	 *
+	 * @param string $zip The ZIP code to verify.
+	 * @return boolean
+	 */
+	public function is_valid_us_zip_code( $zip ) {
+		return ! empty( $zip ) && preg_match( '/^\d{5,5}(-\d{4,4})?$/', $zip );
+	}
+
+	/**
+	 * Gets a localized message for an error from a response, adds it as a note to the order, and throws it.
+	 *
+	 * @since 4.2.0
+	 * @param  stdClass $response  The response from the Stripe API.
+	 * @param  WC_Order $order     The order to add a note to.
+	 * @throws WC_Stripe_Exception An exception with the right message.
+	 */
+	public function throw_localized_message( $response, $order ) {
+		$localized_message = $this->get_localized_error_message_from_response( $response );
+
+		$order->add_order_note( $localized_message );
+
+		throw new WC_Stripe_Exception( print_r( $response, true ), $localized_message );
+	}
+
+	/**
+	 * Generates a localized message for an error from a response.
+	 *
+	 * @since 4.3.2
+	 *
+	 * @param stdClass $response The response from the Stripe API.
+	 *
+	 * @return string The localized error message.
+	 */
+	public function get_localized_error_message_from_response( $response ) {
+		$localized_messages = WC_Stripe_Helper::get_localized_messages();
+
+		if ( 'card_error' === $response->error->type ) {
+			$localized_message = isset( $localized_messages[ $response->error->code ] ) ? $localized_messages[ $response->error->code ] : $response->error->message;
+		} else {
+			$localized_message = isset( $localized_messages[ $response->error->type ] ) ? $localized_messages[ $response->error->type ] : $response->error->message;
+		}
+
+		return $localized_message;
+	}
+
+	/**
+	 * Payment_scripts function.
+	 *
+	 * Outputs scripts used for stripe payment
+	 *
+	 * @since 3.1.0
+	 * @version 4.0.0
+	 */
+	public function payment_scripts() {
+		if (
+			! is_product()
+			&& ! WC_Stripe_Helper::has_cart_or_checkout_on_current_page()
+			&& ! isset( $_GET['pay_for_order'] ) // wpcs: csrf ok.
+			&& ! is_add_payment_method_page()
+			&& ! isset( $_GET['change_payment_method'] ) // wpcs: csrf ok.
+			&& ! ( ! empty( get_query_var( 'view-subscription' ) ) && is_callable( 'WCS_Early_Renewal_Manager::is_early_renewal_via_modal_enabled' ) && WCS_Early_Renewal_Manager::is_early_renewal_via_modal_enabled() )
+			|| ( is_order_received_page() )
+		) {
+			return;
+		}
+
+		if ( is_product() && ! WC_Stripe_Helper::should_load_scripts_on_product_page() ) {
+			return;
+		}
+
+		if ( is_cart() && ! WC_Stripe_Helper::should_load_scripts_on_cart_page() ) {
+			return;
+		}
+
+		// If Stripe is not enabled bail.
+		if ( 'no' === $this->enabled ) {
+			return;
+		}
+
+		// If keys are not set bail.
+		if ( ! $this->are_keys_set() ) {
+			WC_Stripe_Logger::log( 'Keys are not set correctly.' );
+			return;
+		}
+
+		// If no SSL bail.
+		if ( ! $this->testmode && ! is_ssl() ) {
+			WC_Stripe_Logger::log( 'Stripe live mode requires SSL.' );
+			return;
+		}
+
+		$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
+
+		wp_register_style( 'stripe_styles', plugins_url( 'assets/css/stripe-styles.css', WC_STRIPE_MAIN_FILE ), [], WC_STRIPE_VERSION );
+		wp_enqueue_style( 'stripe_styles' );
+
+		wp_register_script( 'stripe', 'https://js.stripe.com/v3/', '', '3.0', true );
+		wp_register_script( 'woocommerce_stripe', plugins_url( 'assets/js/stripe' . $suffix . '.js', WC_STRIPE_MAIN_FILE ), [ 'jquery-payment', 'stripe' ], WC_STRIPE_VERSION, true );
+
+		wp_localize_script(
+			'woocommerce_stripe',
+			'wc_stripe_params',
+			apply_filters( 'wc_stripe_params', $this->javascript_params() )
+		);
+
+		$this->tokenization_script();
+		wp_enqueue_script( 'woocommerce_stripe' );
+	}
+
+	/**
+	 * Returns the JavaScript configuration object used on the product, cart, and checkout pages.
+	 *
+	 * @return array  The configuration object to be loaded to JS.
+	 */
+	public function javascript_params() {
+		global $wp;
+
+		$order_id = absint( get_query_var( 'order-pay' ) );
+
+		$stripe_params = [
+			'title'                    => $this->title,
+			'key'                      => $this->publishable_key,
+			'i18n_terms'               => __( 'Please accept the terms and conditions first', 'woocommerce-gateway-stripe' ),
+			'i18n_required_fields'     => __( 'Please fill in required checkout fields first', 'woocommerce-gateway-stripe' ),
+			'updateFailedOrderNonce'   => wp_create_nonce( 'wc_stripe_update_failed_order_nonce' ),
+			'updatePaymentIntentNonce' => wp_create_nonce( 'wc_stripe_update_payment_intent_nonce' ),
+			'orderId'                  => $order_id,
+			'checkout_url'             => WC_AJAX::get_endpoint( 'checkout' ),
+		];
+
+		// If we're on the pay page we need to pass stripe.js the address of the order.
+		if ( isset( $_GET['pay_for_order'] ) && 'true' === $_GET['pay_for_order'] ) { // wpcs: csrf ok.
+			$order_id = wc_clean( $wp->query_vars['order-pay'] ); // wpcs: csrf ok, sanitization ok, xss ok.
+			$order    = wc_get_order( $order_id );
+
+			if ( is_a( $order, 'WC_Order' ) ) {
+				$stripe_params['billing_first_name'] = $order->get_billing_first_name();
+				$stripe_params['billing_last_name']  = $order->get_billing_last_name();
+				$stripe_params['billing_address_1']  = $order->get_billing_address_1();
+				$stripe_params['billing_address_2']  = $order->get_billing_address_2();
+				$stripe_params['billing_state']      = $order->get_billing_state();
+				$stripe_params['billing_city']       = $order->get_billing_city();
+				$stripe_params['billing_postcode']   = $order->get_billing_postcode();
+				$stripe_params['billing_country']    = $order->get_billing_country();
+			}
+		}
+
+		$sepa_elements_options = apply_filters(
+			'wc_stripe_sepa_elements_options',
+			[
+				'supportedCountries' => [ 'SEPA' ],
+				'placeholderCountry' => WC()->countries->get_base_country(),
+				'style'              => [ 'base' => [ 'fontSize' => '15px' ] ],
+			]
+		);
+
+		$stripe_params['stripe_locale']               = WC_Stripe_Helper::convert_wc_locale_to_stripe_locale( get_locale() );
+		$stripe_params['no_prepaid_card_msg']         = __( 'Sorry, we\'re not accepting prepaid cards at this time. Your credit card has not been charged. Please try with alternative payment method.', 'woocommerce-gateway-stripe' );
+		$stripe_params['no_sepa_owner_msg']           = __( 'Please enter your IBAN account name.', 'woocommerce-gateway-stripe' );
+		$stripe_params['no_sepa_iban_msg']            = __( 'Please enter your IBAN account number.', 'woocommerce-gateway-stripe' );
+		$stripe_params['payment_intent_error']        = __( 'We couldn\'t initiate the payment. Please try again.', 'woocommerce-gateway-stripe' );
+		$stripe_params['sepa_mandate_notification']   = apply_filters( 'wc_stripe_sepa_mandate_notification', 'email' );
+		$stripe_params['allow_prepaid_card']          = apply_filters( 'wc_stripe_allow_prepaid_card', true ) ? 'yes' : 'no';
+		$stripe_params['inline_cc_form']              = ( isset( $this->inline_cc_form ) && $this->inline_cc_form ) ? 'yes' : 'no';
+		$stripe_params['is_checkout']                 = ( is_checkout() && empty( $_GET['pay_for_order'] ) ) ? 'yes' : 'no'; // wpcs: csrf ok.
+		$stripe_params['return_url']                  = $this->get_stripe_return_url();
+		$stripe_params['ajaxurl']                     = WC_AJAX::get_endpoint( '%%endpoint%%' );
+		$stripe_params['stripe_nonce']                = wp_create_nonce( '_wc_stripe_nonce' );
+		$stripe_params['statement_descriptor']        = $this->statement_descriptor;
+		$stripe_params['elements_options']            = apply_filters( 'wc_stripe_elements_options', [] );
+		$stripe_params['sepa_elements_options']       = $sepa_elements_options;
+		$stripe_params['invalid_owner_name']          = __( 'Billing First Name and Last Name are required.', 'woocommerce-gateway-stripe' );
+		$stripe_params['is_change_payment_page']      = isset( $_GET['change_payment_method'] ) ? 'yes' : 'no'; // wpcs: csrf ok.
+		$stripe_params['is_add_payment_page']         = is_wc_endpoint_url( 'add-payment-method' ) ? 'yes' : 'no';
+		$stripe_params['is_pay_for_order_page']       = is_wc_endpoint_url( 'order-pay' ) ? 'yes' : 'no';
+		$stripe_params['elements_styling']            = apply_filters( 'wc_stripe_elements_styling', false );
+		$stripe_params['elements_classes']            = apply_filters( 'wc_stripe_elements_classes', false );
+		$stripe_params['add_card_nonce']              = wp_create_nonce( 'wc_stripe_create_si' );
+		$stripe_params['create_payment_intent_nonce'] = wp_create_nonce( 'wc_stripe_create_payment_intent_nonce' );
+		$stripe_params['cpf_cnpj_required_msg']       = __( 'CPF/CNPJ is a required field', 'woocommerce-gateway-stripe' );
+
+		// Merge localized messages to be use in JS.
+		$stripe_params = array_merge( $stripe_params, WC_Stripe_Helper::get_localized_messages() );
+
+		return $stripe_params;
+	}
+}
diff --git a/includes/abstracts/index.html b/includes/abstracts/index.html
new file mode 100644
index 0000000..52ed3e8
--- /dev/null
+++ b/includes/abstracts/index.html
@@ -0,0 +1,11 @@
+<html><head><title> - Revision 2844313: /woocommerce-gateway-stripe/trunk/includes/abstracts</title></head>
+<body>
+ <h2> - Revision 2844313: /woocommerce-gateway-stripe/trunk/includes/abstracts</h2>
+ <ul>
+  <li><a href="../">..</a></li>
+  <li><a href="abstract-wc-stripe-connect-rest-controller.php">abstract-wc-stripe-connect-rest-controller.php</a></li>
+  <li><a href="abstract-wc-stripe-payment-gateway-voucher.php">abstract-wc-stripe-payment-gateway-voucher.php</a></li>
+  <li><a href="abstract-wc-stripe-payment-gateway.php">abstract-wc-stripe-payment-gateway.php</a></li>
+ </ul>
+ <hr noshade><em>Powered by <a href="http://subversion.apache.org/">Apache Subversion</a> version 1.9.5 (r1770682).</em>
+</body></html>
\ No newline at end of file