Completed
Push — master ( 1968f4...2723f5 )
by Roy
02:26
created

WC_Stripe   C

Complexity

Total Complexity 78

Size/Duplication

Total Lines 436
Duplicated Lines 4.13 %

Coupling/Cohesion

Components 3
Dependencies 2

Importance

Changes 0
Metric Value
wmc 78
lcom 3
cbo 2
dl 18
loc 436
rs 5.4563
c 0
b 0
f 0

20 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 23 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 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 get_setting_link() 0 7 3
C get_minimum_amount() 0 37 14
A log() 0 7 2

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
			include_once( dirname( __FILE__ ) . '/includes/class-wc-stripe-payment-request.php' );
140
		}
141
142
		/**
143
		 * Allow this class and other classes to add slug keyed notices (to avoid duplication)
144
		 */
145
		public function add_admin_notice( $slug, $class, $message ) {
146
			$this->notices[ $slug ] = array(
147
				'class'   => $class,
148
				'message' => $message,
149
			);
150
		}
151
152
		/**
153
		 * The backup sanity check, in case the plugin is activated in a weird way,
154
		 * or the environment changes after activation.
155
		 */
156
		public function check_environment() {
157
			$environment_warning = self::get_environment_warning();
158
159
			if ( $environment_warning && is_plugin_active( plugin_basename( __FILE__ ) ) ) {
160
				$this->add_admin_notice( 'bad_environment', 'error', $environment_warning );
161
			}
162
163
			// Check if secret key present. Otherwise prompt, via notice, to go to
164
			// setting.
165
			if ( ! class_exists( 'WC_Stripe_API' ) ) {
166
				include_once( dirname( __FILE__ ) . '/includes/class-wc-stripe-api.php' );
167
			}
168
169
			$secret = WC_Stripe_API::get_secret_key();
170
171
			if ( empty( $secret ) && ! ( isset( $_GET['page'], $_GET['section'] ) && 'wc-settings' === $_GET['page'] && 'stripe' === $_GET['section'] ) ) {
172
				$setting_link = $this->get_setting_link();
173
				$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 ) );
174
			}
175
		}
176
177
		/**
178
		 * Checks the environment for compatibility problems.  Returns a string with the first incompatibility
179
		 * found or false if the environment has no problems.
180
		 */
181
		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...
182 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...
183
				$message = __( 'WooCommerce Stripe - The minimum PHP version required for this plugin is %1$s. You are running %2$s.', 'woocommerce-gateway-stripe' );
184
185
				return sprintf( $message, WC_STRIPE_MIN_PHP_VER, phpversion() );
186
			}
187
188
			if ( ! defined( 'WC_VERSION' ) ) {
189
				return __( 'WooCommerce Stripe requires WooCommerce to be activated to work.', 'woocommerce-gateway-stripe' );
190
			}
191
192 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...
193
				$message = __( 'WooCommerce Stripe - The minimum WooCommerce version required for this plugin is %1$s. You are running %2$s.', 'woocommerce-gateway-stripe' );
194
195
				return sprintf( $message, WC_STRIPE_MIN_WC_VER, WC_VERSION );
196
			}
197
198
			if ( ! function_exists( 'curl_init' ) ) {
199
				return __( 'WooCommerce Stripe - cURL is not installed.', 'woocommerce-gateway-stripe' );
200
			}
201
202
			return false;
203
		}
204
205
		/**
206
		 * Adds plugin action links
207
		 *
208
		 * @since 1.0.0
209
		 */
210
		public function plugin_action_links( $links ) {
211
			$setting_link = $this->get_setting_link();
212
213
			$plugin_links = array(
214
				'<a href="' . $setting_link . '">' . __( 'Settings', 'woocommerce-gateway-stripe' ) . '</a>',
215
				'<a href="https://docs.woothemes.com/document/stripe/">' . __( 'Docs', 'woocommerce-gateway-stripe' ) . '</a>',
216
				'<a href="http://support.woothemes.com/">' . __( 'Support', 'woocommerce-gateway-stripe' ) . '</a>',
217
			);
218
			return array_merge( $plugin_links, $links );
219
		}
220
221
		/**
222
		 * Get setting link.
223
		 *
224
		 * @since 1.0.0
225
		 *
226
		 * @return string Setting link
227
		 */
228
		public function get_setting_link() {
229
			$use_id_as_section = class_exists( 'WC' ) ? version_compare( WC()->version, '2.6', '>=' ) : false;
230
231
			$section_slug = $use_id_as_section ? 'stripe' : strtolower( 'WC_Gateway_Stripe' );
232
233
			return admin_url( 'admin.php?page=wc-settings&tab=checkout&section=' . $section_slug );
234
		}
235
236
		/**
237
		 * Display any notices we've collected thus far (e.g. for connection, disconnection)
238
		 */
239
		public function admin_notices() {
240
			foreach ( (array) $this->notices as $notice_key => $notice ) {
241
				echo "<div class='" . esc_attr( $notice['class'] ) . "'><p>";
242
				echo wp_kses( $notice['message'], array( 'a' => array( 'href' => array() ) ) );
243
				echo '</p></div>';
244
			}
245
		}
246
247
		/**
248
		 * Initialize the gateway. Called very early - in the context of the plugins_loaded action
249
		 *
250
		 * @since 1.0.0
251
		 */
252
		public function init_gateways() {
253
			if ( class_exists( 'WC_Subscriptions_Order' ) && function_exists( 'wcs_create_renewal_order' ) ) {
254
				$this->subscription_support_enabled = true;
255
			}
256
257
			if ( class_exists( 'WC_Pre_Orders_Order' ) ) {
258
				$this->pre_order_enabled = true;
259
			}
260
261
			if ( ! class_exists( 'WC_Payment_Gateway' ) ) {
262
				return;
263
			}
264
265
			if ( class_exists( 'WC_Payment_Gateway_CC' ) ) {
266
				include_once( dirname( __FILE__ ) . '/includes/class-wc-gateway-stripe.php' );
267
				include_once( dirname( __FILE__ ) . '/includes/class-wc-stripe-apple-pay.php' );
268
			} else {
269
				include_once( dirname( __FILE__ ) . '/includes/legacy/class-wc-gateway-stripe.php' );
270
				include_once( dirname( __FILE__ ) . '/includes/legacy/class-wc-gateway-stripe-saved-cards.php' );
271
			}
272
273
			load_plugin_textdomain( 'woocommerce-gateway-stripe', false, plugin_basename( dirname( __FILE__ ) ) . '/languages' );
274
			add_filter( 'woocommerce_payment_gateways', array( $this, 'add_gateways' ) );
275
276
			$load_addons = (
277
				$this->subscription_support_enabled
278
				||
279
				$this->pre_order_enabled
280
			);
281
282
			if ( $load_addons ) {
283
				require_once( dirname( __FILE__ ) . '/includes/class-wc-gateway-stripe-addons.php' );
284
			}
285
		}
286
287
		/**
288
		 * Add the gateways to WooCommerce
289
		 *
290
		 * @since 1.0.0
291
		 */
292
		public function add_gateways( $methods ) {
293
			if ( $this->subscription_support_enabled || $this->pre_order_enabled ) {
294
				$methods[] = 'WC_Gateway_Stripe_Addons';
295
			} else {
296
				$methods[] = 'WC_Gateway_Stripe';
297
			}
298
			return $methods;
299
		}
300
301
		/**
302
		 * Capture payment when the order is changed from on-hold to complete or processing
303
		 *
304
		 * @param  int $order_id
305
		 */
306
		public function capture_payment( $order_id ) {
307
			$order = wc_get_order( $order_id );
308
309
			if ( 'stripe' === $order->payment_method ) {
310
				$charge   = get_post_meta( $order_id, '_stripe_charge_id', true );
311
				$captured = get_post_meta( $order_id, '_stripe_charge_captured', true );
312
313
				if ( $charge && 'no' === $captured ) {
314
					$result = WC_Stripe_API::request( array(
315
						'amount'   => $order->get_total() * 100,
316
						'expand[]' => 'balance_transaction',
317
					), 'charges/' . $charge . '/capture' );
318
319
					if ( is_wp_error( $result ) ) {
320
						$order->add_order_note( __( 'Unable to capture charge!', 'woocommerce-gateway-stripe' ) . ' ' . $result->get_error_message() );
321
					} else {
322
						$order->add_order_note( sprintf( __( 'Stripe charge complete (Charge ID: %s)', 'woocommerce-gateway-stripe' ), $result->id ) );
323
						update_post_meta( $order->id, '_stripe_charge_captured', 'yes' );
324
325
						// Store other data such as fees
326
						update_post_meta( $order->id, 'Stripe Payment ID', $result->id );
327
328 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...
329
							// Fees and Net needs to both come from Stripe to be accurate as the returned
330
							// values are in the local currency of the Stripe account, not from WC.
331
							$fee = ! empty( $result->balance_transaction->fee ) ? number_format( $result->balance_transaction->fee / 100, 2, '.', '' ) : 0;
332
							$net = ! empty( $result->balance_transaction->net ) ? number_format( $result->balance_transaction->net / 100, 2, '.', '' ) : 0;
333
							update_post_meta( $order->id, 'Stripe Fee', $fee );
334
							update_post_meta( $order->id, 'Net Revenue From Stripe', $net );
335
						}
336
					}
337
				}
338
			}
339
		}
340
341
		/**
342
		 * Cancel pre-auth on refund/cancellation
343
		 *
344
		 * @param  int $order_id
345
		 */
346
		public function cancel_payment( $order_id ) {
347
			$order = wc_get_order( $order_id );
348
349
			if ( 'stripe' === $order->payment_method ) {
350
				$charge   = get_post_meta( $order_id, '_stripe_charge_id', true );
351
352
				if ( $charge ) {
353
					$result = WC_Stripe_API::request( array(
354
						'amount' => $order->get_total() * 100,
355
					), 'charges/' . $charge . '/refund' );
356
357
					if ( is_wp_error( $result ) ) {
358
						$order->add_order_note( __( 'Unable to refund charge!', 'woocommerce-gateway-stripe' ) . ' ' . $result->get_error_message() );
359
					} else {
360
						$order->add_order_note( sprintf( __( 'Stripe charge refunded (Charge ID: %s)', 'woocommerce-gateway-stripe' ), $result->id ) );
361
						delete_post_meta( $order->id, '_stripe_charge_captured' );
362
						delete_post_meta( $order->id, '_stripe_charge_id' );
363
					}
364
				}
365
			}
366
		}
367
368
		/**
369
		 * Gets saved tokens from API if they don't already exist in WooCommerce.
370
		 * @param array $tokens
371
		 * @return array
372
		 */
373
		public function woocommerce_get_customer_payment_tokens( $tokens, $customer_id, $gateway_id ) {
374
			if ( is_user_logged_in() && 'stripe' === $gateway_id && class_exists( 'WC_Payment_Token_CC' ) ) {
375
				$stripe_customer = new WC_Stripe_Customer( $customer_id );
376
				$stripe_cards    = $stripe_customer->get_cards();
377
				$stored_tokens   = array();
378
379
				foreach ( $tokens as $token ) {
380
					$stored_tokens[] = $token->get_token();
381
				}
382
383
				foreach ( $stripe_cards as $card ) {
384
					if ( ! in_array( $card->id, $stored_tokens ) ) {
385
						$token = new WC_Payment_Token_CC();
386
						$token->set_token( $card->id );
387
						$token->set_gateway_id( 'stripe' );
388
						$token->set_card_type( strtolower( $card->brand ) );
389
						$token->set_last4( $card->last4 );
390
						$token->set_expiry_month( $card->exp_month );
391
						$token->set_expiry_year( $card->exp_year );
392
						$token->set_user_id( $customer_id );
393
						$token->save();
394
						$tokens[ $token->get_id() ] = $token;
395
					}
396
				}
397
			}
398
			return $tokens;
399
		}
400
401
		/**
402
		 * Delete token from Stripe
403
		 */
404
		public function woocommerce_payment_token_deleted( $token_id, $token ) {
405
			if ( 'stripe' === $token->get_gateway_id() ) {
406
				$stripe_customer = new WC_Stripe_Customer( get_current_user_id() );
407
				$stripe_customer->delete_card( $token->get_token() );
408
			}
409
		}
410
411
		/**
412
		 * Set as default in Stripe
413
		 */
414
		public function woocommerce_payment_token_set_default( $token_id ) {
415
			$token = WC_Payment_Tokens::get( $token_id );
416
			if ( 'stripe' === $token->get_gateway_id() ) {
417
				$stripe_customer = new WC_Stripe_Customer( get_current_user_id() );
418
				$stripe_customer->set_default_card( $token->get_token() );
419
			}
420
		}
421
422
		/**
423
		 * Checks Stripe minimum order value authorized per currency
424
		 */
425
		public static function get_minimum_amount() {
426
			// Check order amount
427
			switch ( get_woocommerce_currency() ) {
428
				case 'USD':
429
				case 'CAD':
430
				case 'EUR':
431
				case 'CHF':
432
				case 'AUD':
433
				case 'SGD':
434
					$minimum_amount = 50;
435
					break;
436
				case 'GBP':
437
					$minimum_amount = 30;
438
					break;
439
				case 'DKK':
440
					$minimum_amount = 250;
441
					break;
442
				case 'NOK':
443
				case 'SEK':
444
					$minimum_amount = 300;
445
					break;
446
				case 'JPY':
447
					$minimum_amount = 5000;
448
					break;
449
				case 'MXN':
450
					$minimum_amount = 1000;
451
					break;
452
				case 'HKD':
453
					$minimum_amount = 400;
454
					break;
455
				default:
456
					$minimum_amount = 50;
457
					break;
458
			}
459
460
			return $minimum_amount;
461
		}
462
463
		/**
464
		 * What rolls down stairs
465
		 * alone or in pairs,
466
		 * and over your neighbor's dog?
467
		 * What's great for a snack,
468
		 * And fits on your back?
469
		 * It's log, log, log
470
		 */
471
		public static function log( $message ) {
472
			if ( empty( self::$log ) ) {
473
				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...
474
			}
475
476
			self::$log->add( 'woocommerce-gateway-stripe', $message );
477
		}
478
	}
479
480
	$GLOBALS['wc_stripe'] = WC_Stripe::get_instance();
481
482
endif;
483