Initial commit
diff --git a/includes/connect/class-wc-stripe-connect-api.php b/includes/connect/class-wc-stripe-connect-api.php
new file mode 100644
index 0000000..374f568
--- /dev/null
+++ b/includes/connect/class-wc-stripe-connect-api.php
@@ -0,0 +1,241 @@
+<?php
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
+
+if ( ! defined( 'WOOCOMMERCE_CONNECT_SERVER_URL' ) ) {
+	define( 'WOOCOMMERCE_CONNECT_SERVER_URL', 'https://api.woocommerce.com/' );
+}
+
+if ( ! class_exists( 'WC_Stripe_Connect_API' ) ) {
+	/**
+	 * Stripe Connect API class.
+	 */
+	class WC_Stripe_Connect_API {
+
+		const WOOCOMMERCE_CONNECT_SERVER_API_VERSION = '3';
+
+		/**
+		 * Send request to Connect Server to initiate Stripe OAuth
+		 *
+		 * @param  string $return_url return address.
+		 *
+		 * @return array
+		 */
+		public function get_stripe_oauth_init( $return_url ) {
+
+			$current_user                   = wp_get_current_user();
+			$business_data                  = [];
+			$business_data['url']           = get_site_url();
+			$business_data['business_name'] = html_entity_decode( get_bloginfo( 'name' ), ENT_QUOTES );
+			$business_data['first_name']    = $current_user->user_firstname;
+			$business_data['last_name']     = $current_user->user_lastname;
+			$business_data['phone']         = '';
+			$business_data['currency']      = get_woocommerce_currency();
+
+			$wc_countries = WC()->countries;
+
+			if ( method_exists( $wc_countries, 'get_base_address' ) ) {
+				$business_data['country']        = $wc_countries->get_base_country();
+				$business_data['street_address'] = $wc_countries->get_base_address();
+				$business_data['city']           = $wc_countries->get_base_city();
+				$business_data['state']          = $wc_countries->get_base_state();
+				$business_data['zip']            = $wc_countries->get_base_postcode();
+			} else {
+				$base_location                   = wc_get_base_location();
+				$business_data['country']        = $base_location['country'];
+				$business_data['street_address'] = '';
+				$business_data['city']           = '';
+				$business_data['state']          = $base_location['state'];
+				$business_data['zip']            = '';
+			}
+
+			$request = [
+				'returnUrl'    => $return_url,
+				'businessData' => $business_data,
+			];
+
+			return $this->request( 'POST', '/stripe/oauth-init', $request );
+		}
+
+		/**
+		 * Send request to Connect Server for Stripe keys
+		 *
+		 * @param  string $code OAuth server code.
+		 *
+		 * @return array
+		 */
+		public function get_stripe_oauth_keys( $code ) {
+
+			$request = [ 'code' => $code ];
+
+			return $this->request( 'POST', '/stripe/oauth-keys', $request );
+		}
+
+		/**
+		 * General OAuth request method.
+		 *
+		 * @param string $method request method.
+		 * @param string $path   path for request.
+		 * @param array  $body   request body.
+		 *
+		 * @return array|WP_Error
+		 */
+		protected function request( $method, $path, $body = [] ) {
+
+			if ( ! is_array( $body ) ) {
+				return new WP_Error(
+					'request_body_should_be_array',
+					__( 'Unable to send request to WooCommerce Connect server. Body must be an array.', 'woocommerce-gateway-stripe' )
+				);
+			}
+
+			$url = trailingslashit( WOOCOMMERCE_CONNECT_SERVER_URL );
+			$url = apply_filters( 'wc_connect_server_url', $url );
+			$url = trailingslashit( $url ) . ltrim( $path, '/' );
+
+			// Add useful system information to requests that contain bodies.
+			if ( in_array( $method, [ 'POST', 'PUT' ], true ) ) {
+				$body = $this->request_body( $body );
+				$body = wp_json_encode( apply_filters( 'wc_connect_api_client_body', $body ) );
+
+				if ( ! $body ) {
+					return new WP_Error(
+						'unable_to_json_encode_body',
+						__( 'Unable to encode body for request to WooCommerce Connect server.', 'woocommerce-gateway-stripe' )
+					);
+				}
+			}
+
+			$headers = $this->request_headers();
+			if ( is_wp_error( $headers ) ) {
+				return $headers;
+			}
+
+			$http_timeout = 60; // 1 minute
+			wc_set_time_limit( $http_timeout + 10 );
+			$args = [
+				'headers'     => $headers,
+				'method'      => $method,
+				'body'        => $body,
+				'redirection' => 0,
+				'compress'    => true,
+				'timeout'     => $http_timeout,
+			];
+
+			$args          = apply_filters( 'wc_connect_request_args', $args );
+			$response      = wp_remote_request( $url, $args );
+			$response_code = wp_remote_retrieve_response_code( $response );
+			$content_type  = wp_remote_retrieve_header( $response, 'content-type' );
+
+			if ( false === strpos( $content_type, 'application/json' ) ) {
+				if ( 200 !== $response_code ) {
+					return new WP_Error(
+						'wcc_server_error',
+						sprintf(
+							// Translators: HTTP error code.
+							__( 'Error: The WooCommerce Connect server returned HTTP code: %d', 'woocommerce-gateway-stripe' ),
+							$response_code
+						)
+					);
+				} else {
+					return new WP_Error(
+						'wcc_server_error_content_type',
+						sprintf(
+							// Translators: content-type error code.
+							__( 'Error: The WooCommerce Connect server returned an invalid content-type: %s.', 'woocommerce-gateway-stripe' ),
+							$content_type
+						)
+					);
+				}
+			}
+
+			$response_body = wp_remote_retrieve_body( $response );
+			if ( ! empty( $response_body ) ) {
+				$response_body = json_decode( $response_body );
+			}
+
+			if ( 200 !== $response_code ) {
+				if ( empty( $response_body ) ) {
+					return new WP_Error(
+						'wcc_server_empty_response',
+						sprintf(
+							// Translators: HTTP error code.
+							__( 'Error: The WooCommerce Connect server returned ( %d ) and an empty response body.', 'woocommerce-gateway-stripe' ),
+							$response_code
+						)
+					);
+				}
+
+				$error   = property_exists( $response_body, 'error' ) ? $response_body->error : '';
+				$message = property_exists( $response_body, 'message' ) ? $response_body->message : '';
+				$data    = property_exists( $response_body, 'data' ) ? $response_body->data : '';
+
+				return new WP_Error(
+					'wcc_server_error_response',
+					sprintf(
+						/* translators: %1$s: error code, %2$s: error message, %3$d: HTTP response code */
+						__( 'Error: The WooCommerce Connect server returned: %1$s %2$s ( %3$d )', 'woocommerce-gateway-stripe' ),
+						$error,
+						$message,
+						$response_code
+					),
+					$data
+				);
+			}
+
+			return $response_body;
+		}
+
+		/**
+		 * Adds useful WP/WC/WCC information to request bodies.
+		 *
+		 * @param array $initial_body body of initial request.
+		 *
+		 * @return array
+		 */
+		protected function request_body( $initial_body = [] ) {
+
+			$default_body = [
+				'settings' => [],
+			];
+
+			$body = array_merge( $default_body, $initial_body );
+
+			// Add interesting fields to the body of each request.
+			$body['settings'] = wp_parse_args(
+				$body['settings'],
+				[
+					'base_city'      => WC()->countries->get_base_city(),
+					'base_country'   => WC()->countries->get_base_country(),
+					'base_state'     => WC()->countries->get_base_state(),
+					'base_postcode'  => WC()->countries->get_base_postcode(),
+					'currency'       => get_woocommerce_currency(),
+					'stripe_version' => WC_STRIPE_VERSION,
+					'wc_version'     => WC()->version,
+					'wp_version'     => get_bloginfo( 'version' ),
+				]
+			);
+
+			return $body;
+		}
+
+		/**
+		 * Generates headers for request to the WooCommerce Connect Server.
+		 *
+		 * @return array|WP_Error
+		 */
+		protected function request_headers() {
+
+			$headers                    = [];
+			$locale                     = strtolower( str_replace( '_', '-', get_locale() ) );
+			$locale_elements            = explode( '-', $locale );
+			$lang                       = $locale_elements[0];
+			$headers['Accept-Language'] = $locale . ',' . $lang;
+			$headers['Content-Type']    = 'application/json; charset=utf-8';
+			$headers['Accept']          = 'application/vnd.woocommerce-connect.v' . self::WOOCOMMERCE_CONNECT_SERVER_API_VERSION;
+
+			return $headers;
+		}
+	}
+}
diff --git a/includes/connect/class-wc-stripe-connect-rest-oauth-connect-controller.php b/includes/connect/class-wc-stripe-connect-rest-oauth-connect-controller.php
new file mode 100644
index 0000000..f27a930
--- /dev/null
+++ b/includes/connect/class-wc-stripe-connect-rest-oauth-connect-controller.php
@@ -0,0 +1,69 @@
+<?php
+
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
+
+if ( ! class_exists( 'WC_Stripe_Connect_REST_Oauth_Connect_Controller' ) ) {
+	/**
+	 * Stripe Connect Oauth Connect controller class.
+	 */
+	class WC_Stripe_Connect_REST_Oauth_Connect_Controller extends WC_Stripe_Connect_REST_Controller {
+
+		/**
+		 * REST base.
+		 *
+		 * @var string
+		 */
+		protected $rest_base = 'connect/stripe/oauth/connect';
+
+		/**
+		 * Stripe Connect.
+		 *
+		 * @var WC_Stripe_Connect
+		 */
+		protected $connect;
+
+		/**
+		 * Constructor.
+		 *
+		 * @param WC_Stripe_Connect     $connect stripe connect.
+		 * @param WC_Stripe_Connect_API $api     stripe connect api.
+		 */
+		public function __construct( WC_Stripe_Connect $connect, WC_Stripe_Connect_API $api ) {
+
+			parent::__construct( $api );
+
+			$this->connect = $connect;
+		}
+
+		/**
+		 * OAuth Connection flow.
+		 *
+		 * @param array $request POST request.
+		 *
+		 * @return array|WP_Error
+		 */
+		public function post( $request ) {
+
+			$data     = $request->get_json_params();
+			$response = $this->connect->connect_oauth( $data['state'], $data['code'] );
+
+			if ( is_wp_error( $response ) ) {
+
+				WC_Stripe_Logger::log( $response, __CLASS__ );
+
+				return new WP_Error(
+					$response->get_error_code(),
+					$response->get_error_message(),
+					[ 'status' => 400 ]
+				);
+			}
+
+			return [
+				'success'    => true,
+				'account_id' => $response->accountId, // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+			];
+		}
+	}
+}
diff --git a/includes/connect/class-wc-stripe-connect-rest-oauth-init-controller.php b/includes/connect/class-wc-stripe-connect-rest-oauth-init-controller.php
new file mode 100644
index 0000000..606ffb8
--- /dev/null
+++ b/includes/connect/class-wc-stripe-connect-rest-oauth-init-controller.php
@@ -0,0 +1,69 @@
+<?php
+
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
+
+if ( ! class_exists( 'WC_Stripe_Connect_REST_Oauth_Init_Controller' ) ) {
+	/**
+	 * Stripe Connect Oauth Init controller class.
+	 */
+	class WC_Stripe_Connect_REST_Oauth_Init_Controller extends WC_Stripe_Connect_REST_Controller {
+
+		/**
+		 * REST base.
+		 *
+		 * @var string
+		 */
+		protected $rest_base = 'connect/stripe/oauth/init';
+
+		/**
+		 * Stripe Connect.
+		 *
+		 * @var WC_Stripe_Connect
+		 */
+		protected $connect;
+
+		/**
+		 * Constructor.
+		 *
+		 * @param WC_Stripe_Connect     $connect stripe connect.
+		 * @param WC_Stripe_Connect_API $api     stripe connect api.
+		 */
+		public function __construct( WC_Stripe_Connect $connect, WC_Stripe_Connect_API $api ) {
+
+			parent::__construct( $api );
+
+			$this->connect = $connect;
+		}
+
+		/**
+		 * Initiate OAuth flow.
+		 *
+		 * @param array $request POST request.
+		 *
+		 * @return array|WP_Error
+		 */
+		public function post( $request ) {
+
+			$data     = $request->get_json_params();
+			$response = $this->connect->get_oauth_url( isset( $data['returnUrl'] ) ? $data['returnUrl'] : '' );
+
+			if ( is_wp_error( $response ) ) {
+
+				WC_Stripe_Logger::log( $response, __CLASS__ );
+
+				return new WP_Error(
+					$response->get_error_code(),
+					$response->get_error_message(),
+					[ 'status' => 400 ]
+				);
+			}
+
+			return [
+				'success'  => true,
+				'oauthUrl' => $response,
+			];
+		}
+	}
+}
diff --git a/includes/connect/class-wc-stripe-connect.php b/includes/connect/class-wc-stripe-connect.php
new file mode 100644
index 0000000..471ec3b
--- /dev/null
+++ b/includes/connect/class-wc-stripe-connect.php
@@ -0,0 +1,177 @@
+<?php
+
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
+
+if ( ! class_exists( 'WC_Stripe_Connect' ) ) {
+	/**
+	 * Stripe Connect class.
+	 */
+	class WC_Stripe_Connect {
+
+		const SETTINGS_OPTION = 'woocommerce_stripe_settings';
+
+		/**
+		 * 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;
+
+			add_action( 'admin_init', [ $this, 'maybe_handle_redirect' ] );
+		}
+
+		/**
+		 * Gets the OAuth URL for Stripe onboarding flow
+		 *
+		 * @param  string $return_url url to return to after oauth flow.
+		 *
+		 * @return string|WP_Error
+		 */
+		public function get_oauth_url( $return_url = '' ) {
+
+			if ( empty( $return_url ) ) {
+				$return_url = admin_url( 'admin.php?page=wc-settings&tab=checkout&section=stripe&panel=settings' );
+			}
+
+			if ( substr( $return_url, 0, 8 ) !== 'https://' ) {
+				return new WP_Error( 'invalid_url_protocol', __( 'Your site must be served over HTTPS in order to connect your Stripe account automatically.', 'woocommerce-gateway-stripe' ) );
+			}
+
+			$result = $this->api->get_stripe_oauth_init( $return_url );
+
+			if ( is_wp_error( $result ) ) {
+				return $result;
+			}
+
+			return $result->oauthUrl; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+		}
+
+		/**
+		 * Initiate OAuth connection request to Connect Server
+		 *
+		 * @param  bool $state Stripe onboarding state.
+		 * @param  int  $code  OAuth code.
+		 *
+		 * @return string|WP_Error
+		 */
+		public function connect_oauth( $state, $code ) {
+
+			$response = $this->api->get_stripe_oauth_keys( $code );
+
+			if ( is_wp_error( $response ) ) {
+				return $response;
+			}
+
+			return $this->save_stripe_keys( $response );
+		}
+
+		/**
+		 * Handle redirect back from oauth-init or credentials reset
+		 */
+		public function maybe_handle_redirect() {
+			if ( ! is_admin() ) {
+				return;
+			}
+
+			// redirect from oauth-init
+			if ( isset( $_GET['wcs_stripe_code'], $_GET['wcs_stripe_state'] ) ) {
+
+				$response = $this->connect_oauth( wc_clean( wp_unslash( $_GET['wcs_stripe_state'] ) ), wc_clean( wp_unslash( $_GET['wcs_stripe_code'] ) ) );
+				wp_safe_redirect( esc_url_raw( remove_query_arg( [ 'wcs_stripe_state', 'wcs_stripe_code' ] ) ) );
+				exit;
+			}
+		}
+
+		/**
+		 * Saves stripe keys after OAuth response
+		 *
+		 * @param  array $result OAuth response result.
+		 *
+		 * @return array|WP_Error
+		 */
+		private function save_stripe_keys( $result ) {
+
+			if ( ! isset( $result->publishableKey, $result->secretKey ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+				return new WP_Error( 'Invalid credentials received from WooCommerce Connect server' );
+			}
+
+			$is_test                                = false !== strpos( $result->publishableKey, '_test_' ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+			$prefix                                 = $is_test ? 'test_' : '';
+			$default_options                        = $this->get_default_stripe_config();
+			$options                                = array_merge( $default_options, get_option( self::SETTINGS_OPTION, [] ) );
+			$options['enabled']                     = 'yes';
+			$options['testmode']                    = $is_test ? 'yes' : 'no';
+			$options[ $prefix . 'publishable_key' ] = $result->publishableKey; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+			$options[ $prefix . 'secret_key' ]      = $result->secretKey; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+
+			// While we are at it, let's also clear the account_id and
+			// test_account_id if present.
+			unset( $options['account_id'] );
+			unset( $options['test_account_id'] );
+
+			update_option( self::SETTINGS_OPTION, $options );
+
+			return $result;
+		}
+
+		/**
+		 * Clears keys for test or production (whichever is presently enabled).
+		 */
+		private function clear_stripe_keys() {
+
+			$options = get_option( self::SETTINGS_OPTION, [] );
+
+			if ( 'yes' === $options['testmode'] ) {
+				$options['test_publishable_key'] = '';
+				$options['test_secret_key']      = '';
+				// clear test_account_id if present
+				unset( $options['test_account_id'] );
+			} else {
+				$options['publishable_key'] = '';
+				$options['secret_key']      = '';
+				// clear account_id if present
+				unset( $options['account_id'] );
+			}
+
+			update_option( self::SETTINGS_OPTION, $options );
+
+		}
+
+		/**
+		 * Gets default Stripe settings
+		 */
+		private function get_default_stripe_config() {
+
+			$result  = [];
+			$gateway = new WC_Gateway_Stripe();
+			foreach ( $gateway->form_fields as $key => $value ) {
+				if ( isset( $value['default'] ) ) {
+					$result[ $key ] = $value['default'];
+				}
+			}
+
+			return $result;
+		}
+
+		public function is_connected() {
+
+			$options = get_option( self::SETTINGS_OPTION, [] );
+
+			if ( isset( $options['testmode'] ) && 'yes' === $options['testmode'] ) {
+				return isset( $options['test_publishable_key'], $options['test_secret_key'] ) && trim( $options['test_publishable_key'] ) && trim( $options['test_secret_key'] );
+			} else {
+				return isset( $options['publishable_key'], $options['secret_key'] ) && trim( $options['publishable_key'] ) && trim( $options['secret_key'] );
+			}
+		}
+	}
+}
diff --git a/includes/connect/index.html b/includes/connect/index.html
new file mode 100644
index 0000000..9d3ab7a
--- /dev/null
+++ b/includes/connect/index.html
@@ -0,0 +1,12 @@
+<html><head><title> - Revision 2844313: /woocommerce-gateway-stripe/trunk/includes/connect</title></head>
+<body>
+ <h2> - Revision 2844313: /woocommerce-gateway-stripe/trunk/includes/connect</h2>
+ <ul>
+  <li><a href="../">..</a></li>
+  <li><a href="class-wc-stripe-connect-api.php">class-wc-stripe-connect-api.php</a></li>
+  <li><a href="class-wc-stripe-connect-rest-oauth-connect-controller.php">class-wc-stripe-connect-rest-oauth-connect-controller.php</a></li>
+  <li><a href="class-wc-stripe-connect-rest-oauth-init-controller.php">class-wc-stripe-connect-rest-oauth-init-controller.php</a></li>
+  <li><a href="class-wc-stripe-connect.php">class-wc-stripe-connect.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