Initial commit
diff --git a/includes/payment-methods/class-wc-stripe-upe-payment-method.php b/includes/payment-methods/class-wc-stripe-upe-payment-method.php
new file mode 100644
index 0000000..c681f17
--- /dev/null
+++ b/includes/payment-methods/class-wc-stripe-upe-payment-method.php
@@ -0,0 +1,346 @@
+<?php
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
+
+/**
+ * Abstract UPE Payment Method class
+ *
+ * Handles general functionality for UPE payment methods
+ */
+
+
+/**
+ * Extendable abstract class for payment methods.
+ */
+abstract class WC_Stripe_UPE_Payment_Method {
+
+	use WC_Stripe_Subscriptions_Utilities_Trait;
+	use WC_Stripe_Pre_Orders_Trait;
+
+	/**
+	 * Stripe key name
+	 *
+	 * @var string
+	 */
+	protected $stripe_id;
+
+	/**
+	 * Display title
+	 *
+	 * @var string
+	 */
+	protected $title;
+
+	/**
+	 * Method label
+	 *
+	 * @var string
+	 */
+	protected $label;
+
+	/**
+	 * Method description
+	 *
+	 * @var string
+	 */
+	protected $description;
+
+	/**
+	 * Can payment method be saved or reused?
+	 *
+	 * @var bool
+	 */
+	protected $is_reusable;
+
+	/**
+	 * Array of currencies supported by this UPE method
+	 *
+	 * @var array
+	 */
+	protected $supported_currencies;
+
+	/**
+	 * Can this payment method be refunded?
+	 *
+	 * @var array
+	 */
+	protected $can_refund = true;
+
+	/**
+	 * Wether this UPE method is enabled
+	 *
+	 * @var bool
+	 */
+	protected $enabled;
+
+	/**
+	 * List of supported countries
+	 *
+	 * @var array
+	 */
+	protected $supported_countries;
+
+	/**
+	 * Create instance of payment method
+	 */
+	public function __construct() {
+		$main_settings = get_option( 'woocommerce_stripe_settings' );
+
+		if ( isset( $main_settings['upe_checkout_experience_accepted_payments'] ) ) {
+			$enabled_upe_methods = $main_settings['upe_checkout_experience_accepted_payments'];
+		} else {
+			$enabled_upe_methods = [ WC_Stripe_UPE_Payment_Method_CC::STRIPE_ID ];
+		}
+
+		$this->enabled = in_array( static::STRIPE_ID, $enabled_upe_methods, true );
+	}
+
+	/**
+	 * Returns payment method ID
+	 *
+	 * @return string
+	 */
+	public function get_id() {
+		return $this->stripe_id;
+	}
+
+	/**
+	 * Returns true if the UPE method is enabled.
+	 *
+	 * @return bool
+	 */
+	public function is_enabled() {
+		return $this->enabled;
+	}
+
+	/**
+	 * Returns true if the UPE method is available.
+	 *
+	 * @return bool
+	 */
+	public function is_available() {
+		return true;
+	}
+
+	/**
+	 * Returns payment method title
+	 *
+	 * @param array|bool $payment_details Optional payment details from charge object.
+	 *
+	 * @return string
+	 */
+	public function get_title( $payment_details = false ) {
+		return $this->title;
+	}
+
+	/**
+	 * Returns payment method label
+	 *
+	 * @return string
+	 */
+	public function get_label() {
+		return $this->label;
+	}
+
+	/**
+	 * Returns payment method description
+	 *
+	 * @return string
+	 */
+	public function get_description() {
+		return $this->description;
+	}
+
+	/**
+	 * Returns boolean dependent on whether payment method
+	 * can be used at checkout
+	 *
+	 * @param int|null $order_id
+	 * @return bool
+	 */
+	public function is_enabled_at_checkout( $order_id = null ) {
+		// Check capabilities first.
+		if ( ! $this->is_capability_active() ) {
+			return false;
+		}
+
+		// Check currency compatibility.
+		$currencies = $this->get_supported_currencies();
+		if ( ! empty( $currencies ) && ! in_array( $this->get_woocommerce_currency(), $currencies, true ) ) {
+			return false;
+		}
+
+		// If cart or order contains subscription, enable payment method if it's reusable.
+		if ( $this->is_subscription_item_in_cart() || ( ! empty( $order_id ) && $this->has_subscription( $order_id ) ) ) {
+			return $this->is_reusable();
+		}
+
+		// If cart or order contains pre-order, enable payment method if it's reusable.
+		if ( $this->is_pre_order_item_in_cart() || ( ! empty( $order_id ) && $this->has_pre_order( $order_id ) ) ) {
+			return $this->is_reusable();
+		}
+
+		return true;
+	}
+
+	/**
+	 * Validates if a payment method is available on a given country
+	 *
+	 * @param string $country a two-letter country code
+	 *
+	 * @return bool Will return true if supported_countries is empty on payment method
+	 */
+	public function is_allowed_on_country( $country ) {
+		if ( ! empty( $this->supported_countries ) ) {
+			return in_array( $country, $this->supported_countries );
+		}
+
+		return true;
+	}
+
+	/**
+	 * Returns boolean dependent on whether payment method
+	 * will support saved payments/subscription payments
+	 *
+	 * @return bool
+	 */
+	public function is_reusable() {
+		return $this->is_reusable;
+	}
+
+	/**
+	 * Returns boolean dependent on whether capability
+	 * for site account is enabled for payment method.
+	 *
+	 * @return bool
+	 */
+	public function is_capability_active() {
+		// Treat all capabilities as active when in test mode.
+		$plugin_settings   = get_option( 'woocommerce_stripe_settings' );
+		$test_mode_setting = ! empty( $plugin_settings['testmode'] ) ? $plugin_settings['testmode'] : 'no';
+
+		if ( 'yes' === $test_mode_setting ) {
+			return true;
+		}
+
+		// Otherwise, make sure the capability is available.
+		$capabilities = $this->get_capabilities_response();
+		if ( empty( $capabilities ) ) {
+			return false;
+		}
+		$key = $this->get_id() . '_payments';
+		return isset( $capabilities[ $key ] ) && 'active' === $capabilities[ $key ];
+	}
+
+	/**
+	 * Returns capabilities response object for site account.
+	 *
+	 * @return object
+	 */
+	public function get_capabilities_response() {
+		$account = WC_Stripe::get_instance()->account;
+		$data    = $account->get_cached_account_data();
+		if ( empty( $data ) || ! isset( $data['capabilities'] ) ) {
+			return [];
+		}
+		return $data['capabilities'];
+	}
+
+	/**
+	 * Returns string representing payment method type
+	 * to query to retrieve saved payment methods from Stripe.
+	 */
+	public function get_retrievable_type() {
+		return $this->is_reusable() ? WC_Stripe_UPE_Payment_Method_Sepa::STRIPE_ID : null;
+	}
+
+	/**
+	 * Create new WC payment token and add to user.
+	 *
+	 * @param int $user_id        WP_User ID
+	 * @param object $payment_method Stripe payment method object
+	 *
+	 * @return WC_Payment_Token_SEPA
+	 */
+	public function create_payment_token_for_user( $user_id, $payment_method ) {
+		$token = new WC_Payment_Token_SEPA();
+		$token->set_last4( $payment_method->sepa_debit->last4 );
+		$token->set_gateway_id( WC_Stripe_UPE_Payment_Gateway::ID );
+		$token->set_token( $payment_method->id );
+		$token->set_payment_method_type( $this->get_id() );
+		$token->set_user_id( $user_id );
+		$token->save();
+		return $token;
+	}
+
+	/**
+	 * Returns the currencies this UPE method supports.
+	 *
+	 * @return array|null
+	 */
+	public function get_supported_currencies() {
+		return apply_filters(
+			'wc_stripe_' . static::STRIPE_ID . '_upe_supported_currencies',
+			$this->supported_currencies
+		);
+	}
+
+	/**
+	 * Wrapper function for get_woocommerce_currency global function
+	 */
+	public function get_woocommerce_currency() {
+		return get_woocommerce_currency();
+	}
+
+	/**
+	 * Returns whether the payment method requires automatic capture.
+	 * By default all the UPE payment methods require automatic capture, except for "card".
+	 *
+	 * @return bool
+	 */
+	public function requires_automatic_capture() {
+		return true;
+	}
+
+	/**
+	 * Returns the HTML for the subtext messaging in the old settings UI.
+	 *
+	 * @param string $stripe_method_status (optional) Status of this payment method based on the Stripe's account capabilities
+	 * @return string
+	 */
+	public function get_subtext_messages( $stripe_method_status ) {
+		// can be either a `currency` or `activation` messaging, to be displayed in the old settings UI.
+		$messages = [];
+
+		if ( ! empty( $stripe_method_status ) && 'active' !== $stripe_method_status ) {
+			$text            = __( 'Pending activation', 'woocommerce-gateway-stripe' );
+			$tooltip_content = sprintf(
+				/* translators: %1: Payment method name */
+				esc_attr__( '%1$s won\'t be visible to your customers until you provide the required information. Follow the instructions Stripe has sent to your e-mail address.', 'woocommerce-gateway-stripe' ),
+				$this->get_label()
+			);
+			$messages[] = $text . '<span class="tips" data-tip="' . $tooltip_content . '"><span class="woocommerce-help-tip" style="margin-top: 0;"></span></span>';
+		}
+
+		$currencies = $this->get_supported_currencies();
+		if ( ! empty( $currencies ) && ! in_array( get_woocommerce_currency(), $currencies, true ) ) {
+			/* translators: %s: List of comma-separated currencies. */
+			$tooltip_content = sprintf( esc_attr__( 'In order to be used at checkout, the payment method requires the store currency to be set to one of: %s', 'woocommerce-gateway-stripe' ), implode( ', ', $currencies ) );
+			$text            = __( 'Requires currency', 'woocommerce-gateway-stripe' );
+
+			$messages[] = $text . '<span class="tips" data-tip="' . $tooltip_content . '"><span class="woocommerce-help-tip" style="margin-top: 0;"></span></span>';
+		}
+
+		return count( $messages ) > 0 ? join( '&nbsp;–&nbsp;', $messages ) : '';
+	}
+
+	/**
+	 * Checks if payment method allows refund via stripe
+	 *
+	 * @return bool
+	 */
+	public function can_refund_via_stripe() {
+		return $this->can_refund;
+	}
+}