Completed
Pull Request — master (#91)
by Roy
02:17
created

WC_Stripe   C

Complexity

Total Complexity 65

Size/Duplication

Total Lines 397
Duplicated Lines 4.53 %

Coupling/Cohesion

Components 3
Dependencies 2

Importance

Changes 0
Metric Value
wmc 65
lcom 3
cbo 2
dl 18
loc 397
rs 5.7894
c 0
b 0
f 0

19 Methods

Rating   Name   Duplication   Size   Complexity  
A get_instance() 0 6 2
A __clone() 0 1 1
A __wakeup() 0 1 1
A __construct() 0 5 1
A init() 0 21 2
A add_admin_notice() 0 6 1
B check_environment() 0 20 8
B get_environment_warning() 10 23 5
A plugin_action_links() 0 10 1
A get_setting_link() 0 7 2
A admin_notices() 0 7 2
C init_gateways() 0 34 8
A add_gateways() 0 8 3
D capture_payment() 8 34 9
A cancel_payment() 0 21 4
C woocommerce_get_customer_payment_tokens() 0 27 7
A woocommerce_payment_token_deleted() 0 6 2
A woocommerce_payment_token_set_default() 0 7 2
A log() 0 11 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like WC_Stripe often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use WC_Stripe, and based on these observations, apply Extract Interface, too.

1
<?php
2
/*
3
 * Plugin Name: WooCommerce Stripe Gateway
4
 * Plugin URI: https://wordpress.org/plugins/woocommerce-gateway-stripe/
5
 * Description: Take credit card payments on your store using Stripe.
6
 * Author: Automattic
7
 * Author URI: https://woocommerce.com/
8
 * Version: 3.0.6
9
 * Text Domain: woocommerce-gateway-stripe
10
 * Domain Path: /languages
11
 *
12
 * Copyright (c) 2016 Automattic
13
 *
14
 * This program is free software: you can redistribute it and/or modify
15
 * it under the terms of the GNU General Public License as published by
16
 * the Free Software Foundation, either version 3 of the License, or
17
 * (at your option) any later version.
18
 *
19
 * This program is distributed in the hope that it will be useful,
20
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22
 * GNU General Public License for more details.
23
 *
24
 * You should have received a copy of the GNU General Public License
25
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
26
*/
27
28
if ( ! defined( 'ABSPATH' ) ) {
29
	exit;
30
}
31
32
/**
33
 * Required minimums and constants
34
 */
35
define( 'WC_STRIPE_VERSION', '3.0.6' );
36
define( 'WC_STRIPE_MIN_PHP_VER', '5.3.0' );
37
define( 'WC_STRIPE_MIN_WC_VER', '2.5.0' );
38
define( 'WC_STRIPE_MAIN_FILE', __FILE__ );
39
define( 'WC_STRIPE_PLUGIN_URL', untrailingslashit( plugins_url( basename( plugin_dir_path( __FILE__ ) ), basename( __FILE__ ) ) ) );
40
41
if ( ! class_exists( 'WC_Stripe' ) ) :
42
43
class WC_Stripe {
44
45
	/**
46
	 * @var Singleton The reference the *Singleton* instance of this class
47
	 */
48
	private static $instance;
49
50
	/**
51
	 * @var Reference to logging class.
52
	 */
53
	private static $log;
54
55
	/**
56
	 * Returns the *Singleton* instance of this class.
57
	 *
58
	 * @return Singleton The *Singleton* instance.
59
	 */
60
	public static function get_instance() {
61
		if ( null === self::$instance ) {
62
			self::$instance = new self();
0 ignored issues
show
Documentation Bug introduced by
It seems like new self() of type object<WC_Stripe> is incompatible with the declared type object<Singleton> of property $instance.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
63
		}
64
		return self::$instance;
0 ignored issues
show
Bug Compatibility introduced by
The expression self::$instance; of type WC_Stripe|Singleton adds the type WC_Stripe to the return on line 64 which is incompatible with the return type documented by WC_Stripe::get_instance of type Singleton.
Loading history...
65
	}
66
67
	/**
68
	 * Private clone method to prevent cloning of the instance of the
69
	 * *Singleton* instance.
70
	 *
71
	 * @return void
72
	 */
73
	private function __clone() {}
74
75
	/**
76
	 * Private unserialize method to prevent unserializing of the *Singleton*
77
	 * instance.
78
	 *
79
	 * @return void
80
	 */
81
	private function __wakeup() {}
82
83
	/**
84
	 * Flag to indicate whether or not we need to load code for / support subscriptions.
85
	 *
86
	 * @var bool
87
	 */
88
	private $subscription_support_enabled = false;
89
90
	/**
91
	 * Flag to indicate whether or not we need to load support for pre-orders.
92
	 *
93
	 * @since 3.0.3
94
	 *
95
	 * @var bool
96
	 */
97
	private $pre_order_enabled = false;
98
99
	/**
100
	 * Notices (array)
101
	 * @var array
102
	 */
103
	public $notices = array();
104
105
	/**
106
	 * Protected constructor to prevent creating a new instance of the
107
	 * *Singleton* via the `new` operator from outside of this class.
108
	 */
109
	protected function __construct() {
110
		add_action( 'admin_init', array( $this, 'check_environment' ) );
111
		add_action( 'admin_notices', array( $this, 'admin_notices' ), 15 );
112
		add_action( 'plugins_loaded', array( $this, 'init' ) );
113
	}
114
115
	/**
116
	 * Init the plugin after plugins_loaded so environment variables are set.
117
	 */
118
	public function init() {
119
		// Don't hook anything else in the plugin if we're in an incompatible environment
120
		if ( self::get_environment_warning() ) {
121
			return;
122
		}
123
124
		include_once( dirname( __FILE__ ) . '/includes/class-wc-stripe-api.php' );
125
		include_once( dirname( __FILE__ ) . '/includes/class-wc-stripe-customer.php' );
126
127
		// Init the gateway itself
128
		$this->init_gateways();
129
130
		add_filter( 'plugin_action_links_' . plugin_basename( __FILE__ ), array( $this, 'plugin_action_links' ) );
131
		add_action( 'woocommerce_order_status_on-hold_to_processing', array( $this, 'capture_payment' ) );
132
		add_action( 'woocommerce_order_status_on-hold_to_completed', array( $this, 'capture_payment' ) );
133
		add_action( 'woocommerce_order_status_on-hold_to_cancelled', array( $this, 'cancel_payment' ) );
134
		add_action( 'woocommerce_order_status_on-hold_to_refunded', array( $this, 'cancel_payment' ) );
135
		add_filter( 'woocommerce_get_customer_payment_tokens', array( $this, 'woocommerce_get_customer_payment_tokens' ), 10, 3 );
136
		add_action( 'woocommerce_payment_token_deleted', array( $this, 'woocommerce_payment_token_deleted' ), 10, 2 );
137
		add_action( 'woocommerce_payment_token_set_default', array( $this, 'woocommerce_payment_token_set_default' ) );	
138
	}
139
140
	/**
141
	 * Allow this class and other classes to add slug keyed notices (to avoid duplication)
142
	 */
143
	public function add_admin_notice( $slug, $class, $message ) {
144
		$this->notices[ $slug ] = array(
145
			'class'   => $class,
146
			'message' => $message
147
		);
148
	}
149
150
	/**
151
	 * The backup sanity check, in case the plugin is activated in a weird way,
152
	 * or the environment changes after activation.
153
	 */
154
	public function check_environment() {
155
		$environment_warning = self::get_environment_warning();
156
157
		if ( $environment_warning && is_plugin_active( plugin_basename( __FILE__ ) ) ) {
158
			$this->add_admin_notice( 'bad_environment', 'error', $environment_warning );
159
		}
160
161
		// Check if secret key present. Otherwise prompt, via notice, to go to
162
		// setting.
163
		if ( ! class_exists( 'WC_Stripe_API' ) ) {
164
			include_once( dirname( __FILE__ ) . '/includes/class-wc-stripe-api.php' );
165
		}
166
167
		$secret = WC_Stripe_API::get_secret_key();
168
169
		if ( empty( $secret ) && ! ( isset( $_GET['page'], $_GET['section'] ) && 'wc-settings' === $_GET['page'] && 'stripe' === $_GET['section'] ) ) {
170
			$setting_link = $this->get_setting_link();
171
			$this->add_admin_notice( 'prompt_connect', 'notice notice-warning', sprintf( __( 'Stripe is almost ready. To get started, <a href="%s">set your Stripe account keys</a>.', 'woocommerce-gateway-stripe' ), $setting_link ) );
172
		}
173
	}
174
175
	/**
176
	 * Checks the environment for compatibility problems.  Returns a string with the first incompatibility
177
	 * found or false if the environment has no problems.
178
	 */
179
	static function get_environment_warning() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
180 View Code Duplication
		if ( version_compare( phpversion(), WC_STRIPE_MIN_PHP_VER, '<' ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
181
			$message = __( 'WooCommerce Stripe - The minimum PHP version required for this plugin is %1$s. You are running %2$s.', 'woocommerce-gateway-stripe', 'woocommerce-gateway-stripe' );
182
183
			return sprintf( $message, WC_STRIPE_MIN_PHP_VER, phpversion() );
184
		}
185
		
186
		if ( ! defined( 'WC_VERSION' ) ) {
187
			return __( 'WooCommerce Stripe requires WooCommerce to be activated to work.', 'woocommerce-gateway-stripe' );
188
		} 
189
190 View Code Duplication
		if ( version_compare( WC_VERSION, WC_STRIPE_MIN_WC_VER, '<' ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
191
			$message = __( 'WooCommerce Stripe - The minimum WooCommerce version required for this plugin is %1$s. You are running %2$s.', 'woocommerce-gateway-stripe', 'woocommerce-gateway-stripe' );
192
193
			return sprintf( $message, WC_STRIPE_MIN_WC_VER, WC_VERSION );
194
		}
195
196
		if ( ! function_exists( 'curl_init' ) ) {
197
			return __( 'WooCommerce Stripe - cURL is not installed.', 'woocommerce-gateway-stripe' );
198
		}
199
200
		return false;
201
	}
202
203
	/**
204
	 * Adds plugin action links
205
	 *
206
	 * @since 1.0.0
207
	 */
208
	public function plugin_action_links( $links ) {
209
		$setting_link = $this->get_setting_link();
210
211
		$plugin_links = array(
212
			'<a href="' . $setting_link . '">' . __( 'Settings', 'woocommerce-gateway-stripe' ) . '</a>',
213
			'<a href="https://docs.woothemes.com/document/stripe/">' . __( 'Docs', 'woocommerce-gateway-stripe' ) . '</a>',
214
			'<a href="http://support.woothemes.com/">' . __( 'Support', 'woocommerce-gateway-stripe' ) . '</a>',
215
		);
216
		return array_merge( $plugin_links, $links );
217
	}
218
219
	/**
220
	 * Get setting link.
221
	 *
222
	 * @since 1.0.0
223
	 *
224
	 * @return string Setting link
225
	 */
226
	public function get_setting_link() {
227
		$use_id_as_section = version_compare( WC()->version, '2.6', '>=' );
228
229
		$section_slug = $use_id_as_section ? 'stripe' : strtolower( 'WC_Gateway_Stripe' );
230
231
		return admin_url( 'admin.php?page=wc-settings&tab=checkout&section=' . $section_slug );
232
	}
233
234
	/**
235
	 * Display any notices we've collected thus far (e.g. for connection, disconnection)
236
	 */
237
	public function admin_notices() {
238
		foreach ( (array) $this->notices as $notice_key => $notice ) {
239
			echo "<div class='" . esc_attr( $notice['class'] ) . "'><p>";
240
			echo wp_kses( $notice['message'], array( 'a' => array( 'href' => array() ) ) );
241
			echo "</p></div>";
242
		}
243
	}
244
245
	/**
246
	 * Initialize the gateway. Called very early - in the context of the plugins_loaded action
247
	 *
248
	 * @since 1.0.0
249
	 */
250
	public function init_gateways() {
251
		if ( class_exists( 'WC_Subscriptions_Order' ) && function_exists( 'wcs_create_renewal_order' ) ) {
252
			$this->subscription_support_enabled = true;
253
		}
254
255
		if ( class_exists( 'WC_Pre_Orders_Order' ) ) {
256
			$this->pre_order_enabled = true;
257
		}
258
259
		if ( ! class_exists( 'WC_Payment_Gateway' ) ) {
260
			return;
261
		}
262
263
		if ( class_exists( 'WC_Payment_Gateway_CC' ) ) {
264
			include_once( dirname( __FILE__ ) . '/includes/class-wc-gateway-stripe.php' );
265
			require_once( dirname( __FILE__ ) . '/includes/class-wc-stripe-apple-pay.php' );
266
		} else {
267
			include_once( dirname( __FILE__ ) . '/includes/legacy/class-wc-gateway-stripe.php' );
268
			include_once( dirname( __FILE__ ) . '/includes/legacy/class-wc-gateway-stripe-saved-cards.php' );
269
		}
270
271
		load_plugin_textdomain( 'woocommerce-gateway-stripe', false, plugin_basename( dirname( __FILE__ ) ) . '/languages' );
272
		add_filter( 'woocommerce_payment_gateways', array( $this, 'add_gateways' ) );
273
274
		$load_addons = (
275
			$this->subscription_support_enabled
276
			||
277
			$this->pre_order_enabled
278
		);
279
280
		if ( $load_addons ) {
281
			require_once( dirname( __FILE__ ) . '/includes/class-wc-gateway-stripe-addons.php' );
282
		}
283
	}
284
285
	/**
286
	 * Add the gateways to WooCommerce
287
	 *
288
	 * @since 1.0.0
289
	 */
290
	public function add_gateways( $methods ) {
291
		if ( $this->subscription_support_enabled || $this->pre_order_enabled ) {
292
			$methods[] = 'WC_Gateway_Stripe_Addons';
293
		} else {
294
			$methods[] = 'WC_Gateway_Stripe';
295
		}
296
		return $methods;
297
	}
298
299
	/**
300
	 * Capture payment when the order is changed from on-hold to complete or processing
301
	 *
302
	 * @param  int $order_id
303
	 */
304
	public function capture_payment( $order_id ) {
305
		$order = wc_get_order( $order_id );
306
307
		if ( 'stripe' === $order->payment_method ) {
308
			$charge   = get_post_meta( $order_id, '_stripe_charge_id', true );
309
			$captured = get_post_meta( $order_id, '_stripe_charge_captured', true );
310
311
			if ( $charge && 'no' === $captured ) {
312
				$result = WC_Stripe_API::request( array(
313
					'amount'   => $order->get_total() * 100,
314
					'expand[]' => 'balance_transaction'
315
				), 'charges/' . $charge . '/capture' );
316
317
				if ( is_wp_error( $result ) ) {
318
					$order->add_order_note( __( 'Unable to capture charge!', 'woocommerce-gateway-stripe' ) . ' ' . $result->get_error_message() );
319
				} else {
320
					$order->add_order_note( sprintf( __( 'Stripe charge complete (Charge ID: %s)', 'woocommerce-gateway-stripe' ), $result->id ) );
321
					update_post_meta( $order->id, '_stripe_charge_captured', 'yes' );
322
323
					// Store other data such as fees
324
					update_post_meta( $order->id, 'Stripe Payment ID', $result->id );
325
326 View Code Duplication
					if ( isset( $result->balance_transaction ) && isset( $result->balance_transaction->fee ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
327
						// Fees and Net needs to both come from Stripe to be accurate as the returned
328
						// values are in the local currency of the Stripe account, not from WC.
329
						$fee = ! empty( $result->balance_transaction->fee ) ? number_format( $result->balance_transaction->fee / 100, 2, '.', '' ) : 0;
330
						$net = ! empty( $result->balance_transaction->net ) ? number_format( $result->balance_transaction->net / 100, 2, '.', '' ) : 0;
331
						update_post_meta( $order->id, 'Stripe Fee', $fee );
332
						update_post_meta( $order->id, 'Net Revenue From Stripe', $net );
333
					}
334
				}
335
			}
336
		}
337
	}
338
339
	/**
340
	 * Cancel pre-auth on refund/cancellation
341
	 *
342
	 * @param  int $order_id
343
	 */
344
	public function cancel_payment( $order_id ) {
345
		$order = wc_get_order( $order_id );
346
347
		if ( 'stripe' === $order->payment_method ) {
348
			$charge   = get_post_meta( $order_id, '_stripe_charge_id', true );
349
350
			if ( $charge ) {
351
				$result = WC_Stripe_API::request( array(
352
					'amount' => $order->get_total() * 100,
353
				), 'charges/' . $charge . '/refund' );
354
355
				if ( is_wp_error( $result ) ) {
356
					$order->add_order_note( __( 'Unable to refund charge!', 'woocommerce-gateway-stripe' ) . ' ' . $result->get_error_message() );
357
				} else {
358
					$order->add_order_note( sprintf( __( 'Stripe charge refunded (Charge ID: %s)', 'woocommerce-gateway-stripe' ), $result->id ) );
359
					delete_post_meta( $order->id, '_stripe_charge_captured' );
360
					delete_post_meta( $order->id, '_stripe_charge_id' );
361
				}
362
			}
363
		}
364
	}
365
366
	/**
367
	 * Gets saved tokens from API if they don't already exist in WooCommerce.
368
	 * @param array $tokens
369
	 * @return array
370
	 */
371
	public function woocommerce_get_customer_payment_tokens( $tokens, $customer_id, $gateway_id ) {
372
		if ( is_user_logged_in() && 'stripe' === $gateway_id && class_exists( 'WC_Payment_Token_CC' ) ) {
373
			$stripe_customer = new WC_Stripe_Customer( $customer_id );
374
			$stripe_cards    = $stripe_customer->get_cards();
375
			$stored_tokens   = array();
376
377
			foreach ( $tokens as $token ) {
378
				$stored_tokens[] = $token->get_token();
379
			}
380
381
			foreach ( $stripe_cards as $card ) {
382
				if ( ! in_array( $card->id, $stored_tokens ) ) {
383
					$token = new WC_Payment_Token_CC();
384
					$token->set_token( $card->id );
385
					$token->set_gateway_id( 'stripe' );
386
					$token->set_card_type( strtolower( $card->brand ) );
387
					$token->set_last4( $card->last4 );
388
					$token->set_expiry_month( $card->exp_month  );
389
					$token->set_expiry_year( $card->exp_year );
390
					$token->set_user_id( $customer_id );
391
					$token->save();
392
					$tokens[ $token->get_id() ] = $token;
393
				}
394
			}
395
		}
396
		return $tokens;
397
	}
398
399
	/**
400
	 * Delete token from Stripe
401
	 */
402
	public function woocommerce_payment_token_deleted( $token_id, $token ) {
403
		if ( 'stripe' === $token->get_gateway_id() ) {
404
			$stripe_customer = new WC_Stripe_Customer( get_current_user_id() );
405
			$stripe_customer->delete_card( $token->get_token() );
406
		}
407
	}
408
409
	/**
410
	 * Set as default in Stripe
411
	 */
412
	public function woocommerce_payment_token_set_default( $token_id ) {
413
		$token = WC_Payment_Tokens::get( $token_id );
414
		if ( 'stripe' === $token->get_gateway_id() ) {
415
			$stripe_customer = new WC_Stripe_Customer( get_current_user_id() );
416
			$stripe_customer->set_default_card( $token->get_token() );
417
		}
418
	}
419
420
	/**
421
	 * What rolls down stairs
422
	 * alone or in pairs,
423
	 * and over your neighbor's dog?
424
	 * What's great for a snack,
425
	 * And fits on your back?
426
	 * It's log, log, log
427
	 */
428
	public static function log( $message ) {
429
		if ( empty( self::$log ) ) {
430
			self::$log = new WC_Logger();
0 ignored issues
show
Documentation Bug introduced by
It seems like new \WC_Logger() of type object<WC_Logger> is incompatible with the declared type object<Reference> of property $log.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
431
		}
432
433
		self::$log->add( 'woocommerce-gateway-stripe', $message );
434
435
		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
436
			error_log( $message );
437
		}
438
	}
439
}
440
441
$GLOBALS['wc_stripe'] = WC_Stripe::get_instance();
442
443
endif;
444