Completed
Pull Request — master (#1267)
by
unknown
02:08 queued 17s
created

deauthorize_stripe_account()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
if ( ! defined( 'ABSPATH' ) ) {
3
	exit;
4
}
5
6
if ( ! defined( 'WOOCOMMERCE_CONNECT_SERVER_URL' ) ) {
7
	define( 'WOOCOMMERCE_CONNECT_SERVER_URL', 'https://api.woocommerce.com/' );
8
}
9
10
if ( ! class_exists( 'WC_Stripe_Connect_API' ) ) {
11
	/**
12
	 * Stripe Connect API class.
13
	 */
14
	class WC_Stripe_Connect_API {
15
16
		const WOOCOMMERCE_CONNECT_SERVER_API_VERSION = '3';
17
18
		/**
19
		 * Send GET request for Stripe account details
20
		 *
21
		 * @return array
22
		 */
23
		public function get_stripe_account_details() {
24
			return $this->request( 'GET', '/stripe/account' );
25
		}
26
27
		/**
28
		 * Send request to Connect Server to initiate Stripe OAuth
29
		 *
30
		 * @param  string $return_url return address.
31
		 *
32
		 * @return array
33
		 */
34
		public function get_stripe_oauth_init( $return_url ) {
35
36
			$current_user                   = wp_get_current_user();
37
			$business_data                  = array();
38
			$business_data['url']           = get_site_url();
39
			$business_data['business_name'] = html_entity_decode( get_bloginfo( 'name' ), ENT_QUOTES );
40
			$business_data['first_name']    = $current_user->user_firstname;
41
			$business_data['last_name']     = $current_user->user_lastname;
42
			$business_data['phone']         = '';
43
			$business_data['currency']      = get_woocommerce_currency();
44
45
			$wc_countries = WC()->countries;
46
47
			if ( method_exists( $wc_countries, 'get_base_address' ) ) {
48
				$business_data['country']        = $wc_countries->get_base_country();
49
				$business_data['street_address'] = $wc_countries->get_base_address();
50
				$business_data['city']           = $wc_countries->get_base_city();
51
				$business_data['state']          = $wc_countries->get_base_state();
52
				$business_data['zip']            = $wc_countries->get_base_postcode();
53
			} else {
54
				$base_location                   = wc_get_base_location();
55
				$business_data['country']        = $base_location['country'];
56
				$business_data['street_address'] = '';
57
				$business_data['city']           = '';
58
				$business_data['state']          = $base_location['state'];
59
				$business_data['zip']            = '';
60
			}
61
62
			$request = array(
63
				'returnUrl'    => $return_url,
64
				'businessData' => $business_data,
65
			);
66
67
			return $this->request( 'POST', '/stripe/oauth-init', $request );
68
		}
69
70
		/**
71
		 * Send request to Connect Server for Stripe keys
72
		 *
73
		 * @param  string $code OAuth server code.
74
		 *
75
		 * @return array
76
		 */
77
		public function get_stripe_oauth_keys( $code ) {
78
79
			$request = array( 'code' => $code );
80
81
			return $this->request( 'POST', '/stripe/oauth-keys', $request );
82
		}
83
84
		/**
85
		 * Send request to Connect Server to deauthorize account
86
		 *
87
		 * @return array
88
		 */
89
		public function deauthorize_stripe_account() {
90
91
			return $this->request( 'POST', '/stripe/account/deauthorize' );
92
		}
93
94
		/**
95
		 * General OAuth request method.
96
		 *
97
		 * @param string $method request method.
98
		 * @param string $path   path for request.
99
		 * @param array  $body   request body.
100
		 *
101
		 * @return array|WP_Error
102
		 */
103
		protected function request( $method, $path, $body = array() ) {
104
105
			if ( ! class_exists( 'Jetpack_Data' ) ) {
106
				return new WP_Error(
107
					'jetpack_data_class_not_found',
108
					__( 'Unable to send request to WooCommerce Connect server. Jetpack_Data was not found.', 'woocommerce-gateway-stripe' )
109
				);
110
			}
111
112
			if ( ! method_exists( 'Jetpack_Data', 'get_access_token' ) ) {
113
				return new WP_Error(
114
					'jetpack_data_get_access_token_not_found',
115
					__( 'Unable to send request to WooCommerce Connect server. Jetpack_Data does not implement get_access_token.', 'woocommerce-gateway-stripe' )
116
				);
117
			}
118
119
			if ( ! is_array( $body ) ) {
120
				return new WP_Error(
121
					'request_body_should_be_array',
122
					__( 'Unable to send request to WooCommerce Connect server. Body must be an array.', 'woocommerce-gateway-stripe' )
123
				);
124
			}
125
126
			$url = trailingslashit( WOOCOMMERCE_CONNECT_SERVER_URL );
127
			$url = apply_filters( 'wc_connect_server_url', $url );
128
			$url = trailingslashit( $url ) . ltrim( $path, '/' );
129
130
			// Add useful system information to requests that contain bodies.
131
			if ( in_array( $method, array( 'POST', 'PUT' ), true ) ) {
132
				$body = $this->request_body( $body );
133
				$body = wp_json_encode( apply_filters( 'wc_connect_api_client_body', $body ) );
134
135
				if ( ! $body ) {
136
					return new WP_Error(
137
						'unable_to_json_encode_body',
138
						__( 'Unable to encode body for request to WooCommerce Connect server.', 'woocommerce-gateway-stripe' )
139
					);
140
				}
141
			}
142
143
			$headers = $this->request_headers();
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->request_headers(); of type string|WP_Error|array<string,string|WP_Error> adds the type string to the return on line 145 which is incompatible with the return type documented by WC_Stripe_Connect_API::request of type array|WP_Error.
Loading history...
144
			if ( is_wp_error( $headers ) ) {
145
				return $headers;
146
			}
147
148
			$http_timeout = 60; // 1 minute
149
			if ( function_exists( 'wc_set_time_limit' ) ) {
150
				wc_set_time_limit( $http_timeout + 10 );
151
			}
152
			$args = array(
153
				'headers'     => $headers,
154
				'method'      => $method,
155
				'body'        => $body,
156
				'redirection' => 0,
157
				'compress'    => true,
158
				'timeout'     => $http_timeout,
159
			);
160
161
			$args          = apply_filters( 'wc_connect_request_args', $args );
162
			$response      = wp_remote_request( $url, $args );
163
			$response_code = wp_remote_retrieve_response_code( $response );
164
			$content_type  = wp_remote_retrieve_header( $response, 'content-type' );
165
166
			if ( false === strpos( $content_type, 'application/json' ) ) {
167
				if ( 200 !== $response_code ) {
168
					return new WP_Error(
169
						'wcc_server_error',
170
						sprintf(
171
							// Translators: HTTP error code.
172
							__( 'Error: The WooCommerce Connect server returned HTTP code: %d', 'woocommerce-gateway-stripe' ),
173
							$response_code
174
						)
175
					);
176
				}
177
				return $response;
178
			}
179
180
			$response_body = wp_remote_retrieve_body( $response );
181
			if ( ! empty( $response_body ) ) {
182
				$response_body = json_decode( $response_body );
183
			}
184
185
			if ( 200 !== $response_code ) {
186
				if ( empty( $response_body ) ) {
187
					return new WP_Error(
188
						'wcc_server_empty_response',
189
						sprintf(
190
							// Translators: HTTP error code.
191
							__( 'Error: The WooCommerce Connect server returned ( %d ) and an empty response body.', 'woocommerce-gateway-stripe' ),
192
							$response_code
193
						)
194
					);
195
				}
196
197
				$error   = property_exists( $response_body, 'error' ) ? $response_body->error : '';
198
				$message = property_exists( $response_body, 'message' ) ? $response_body->message : '';
199
				$data    = property_exists( $response_body, 'data' ) ? $response_body->data : '';
200
201
				return new WP_Error(
202
					'wcc_server_error_response',
203
					sprintf(
204
						/* translators: %1$s: error code, %2$s: error message, %3$d: HTTP response code */
205
						__( 'Error: The WooCommerce Connect server returned: %1$s %2$s ( %3$d )', 'woocommerce-gateway-stripe' ),
206
						$error,
207
						$message,
208
						$response_code
209
					),
210
					$data
211
				);
212
			}
213
214
			return $response_body;
215
		}
216
217
		/**
218
		 * Adds useful WP/WC/WCC information to request bodies.
219
		 *
220
		 * @param array $initial_body body of initial request.
221
		 *
222
		 * @return array
223
		 */
224
		protected function request_body( $initial_body = array() ) {
225
226
			$default_body = array(
227
				'settings' => array(),
228
			);
229
230
			$body = array_merge( $default_body, $initial_body );
231
232
			// Add interesting fields to the body of each request.
233
			$body['settings'] = wp_parse_args(
234
				$body['settings'],
235
				array(
236
					'base_city'       => WC()->countries->get_base_city(),
237
					'base_country'    => WC()->countries->get_base_country(),
238
					'base_state'      => WC()->countries->get_base_state(),
239
					'base_postcode'   => WC()->countries->get_base_postcode(),
240
					'currency'        => get_woocommerce_currency(),
241
					'stripe_version'  => WC_STRIPE_VERSION,
242
					'jetpack_version' => JETPACK__VERSION,
243
					'wc_version'      => WC()->version,
244
					'wp_version'      => get_bloginfo( 'version' ),
245
				)
246
			);
247
248
			return $body;
249
		}
250
251
		/**
252
		 * Generates headers for equest to the WooCommerce Connect Server.
253
		 *
254
		 * @return array|WP_Error
255
		 */
256
		protected function request_headers() {
257
258
			$authorization = $this->authorization_header();
259
260
			if ( is_wp_error( $authorization ) ) {
261
				return $authorization;
262
			}
263
264
			$headers                    = array();
265
			$locale                     = strtolower( str_replace( '_', '-', get_locale() ) );
266
			$locale_elements            = explode( '-', $locale );
267
			$lang                       = $locale_elements[0];
268
			$headers['Accept-Language'] = $locale . ',' . $lang;
269
			$headers['Content-Type']    = 'application/json; charset=utf-8';
270
			$headers['Accept']          = 'application/vnd.woocommerce-connect.v' . self::WOOCOMMERCE_CONNECT_SERVER_API_VERSION;
271
			$headers['Authorization']   = $authorization;
272
273
			return $headers;
274
		}
275
276
		/**
277
		 * Generates Jetpack authorization header for request to the WooCommerce Connect Server.
278
		 *
279
		 * @return string|WP_Error
280
		 */
281
		protected function authorization_header() {
282
283
			$token = Jetpack_Data::get_access_token( 0 );
284
			$token = apply_filters( 'wc_connect_jetpack_access_token', $token );
285
286
			if ( ! $token || empty( $token->secret ) ) {
287
				return new WP_Error(
288
					'missing_token',
289
					__( 'Unable to send request to WooCommerce Connect server. Jetpack Token is missing', 'woocommerce-gateway-stripe' )
290
				);
291
			}
292
293
			if ( false === strpos( $token->secret, '.' ) ) {
294
				return new WP_Error(
295
					'invalid_token',
296
					__( 'Unable to send request to WooCommerce Connect server. Jetpack Token is malformed.', 'woocommerce-gateway-stripe' )
297
				);
298
			}
299
300
			list( $token_key, $token_secret ) = explode( '.', $token->secret );
301
302
			$token_key = sprintf( '%s:%d:%d', $token_key, JETPACK__API_VERSION, $token->external_user_id );
303
			$time_diff = (int) Jetpack_Options::get_option( 'time_diff' );
304
			$timestamp = time() + $time_diff;
305
			$nonce     = wp_generate_password( 10, false );
306
			$signature = $this->request_signature( $token_key, $token_secret, $timestamp, $nonce, $time_diff );
307
308
			if ( is_wp_error( $signature ) ) {
309
				return $signature;
310
			}
311
312
			$auth = array(
313
				'token'     => $token_key,
314
				'timestamp' => $timestamp,
315
				'nonce'     => $nonce,
316
				'signature' => $signature,
317
			);
318
319
			$header_pieces = array();
320
321
			foreach ( $auth as $key => $value ) {
322
				$header_pieces[] = sprintf( '%s="%s"', $key, $value );
323
			}
324
325
			$authorization = 'X_JP_Auth ' . join( ' ', $header_pieces );
326
327
			return $authorization;
328
		}
329
330
		/**
331
		 * Generates signature for our request to the WooCommerce Connect Server.
332
		 *
333
		 * @param string $token_key    signature key.
334
		 * @param string $token_secret signature secret.
335
		 * @param string $timestamp    timestamp for signature.
336
		 * @param string $nonce        nonce for signature.
337
		 * @param string $time_diff    Jetpack time_diff option.
338
		 *
339
		 * @return string|WP_Error
340
		 */
341
		protected function request_signature( $token_key, $token_secret, $timestamp, $nonce, $time_diff ) {
342
343
			$local_time = $timestamp - $time_diff;
344
345
			if ( $local_time < time() - 600 || $local_time > time() + 300 ) {
346
				return new WP_Error(
347
					'invalid_signature',
348
					__( 'Unable to send request to WooCommerce Connect server. The timestamp generated for the signature is too old.', 'woocommerce-gateway-stripe' )
349
				);
350
			}
351
352
			$normalized_request_string = join(
353
				"\n",
354
				array(
355
					$token_key,
356
					$timestamp,
357
					$nonce,
358
				)
359
			) . "\n";
360
361
			return base64_encode( hash_hmac( 'sha1', $normalized_request_string, $token_secret, true ) ); //phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
362
		}
363
364
	}
365
}
366