Completed
Push — master ( 9d6b36...bc8c17 )
by Roy
02:27
created

WC_Stripe::init_gateways()   C

Complexity

Conditions 8
Paths 36

Size

Total Lines 34
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 34
rs 5.3846
c 0
b 0
f 0
cc 8
eloc 21
nc 36
nop 0
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
define( 'WC_STRIPE_PLUGIN_PATH', untrailingslashit( plugin_dir_path( __FILE__ ) ) );
41
42
if ( ! class_exists( 'WC_Stripe' ) ) :
43
44
	class WC_Stripe {
45
46
		/**
47
		 * @var Singleton The reference the *Singleton* instance of this class
48
		 */
49
		private static $instance;
50
51
		/**
52
		 * @var Reference to logging class.
53
		 */
54
		private static $log;
55
56
		/**
57
		 * Returns the *Singleton* instance of this class.
58
		 *
59
		 * @return Singleton The *Singleton* instance.
60
		 */
61
		public static function get_instance() {
62
			if ( null === self::$instance ) {
63
				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...
64
			}
65
			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 65 which is incompatible with the return type documented by WC_Stripe::get_instance of type Singleton.
Loading history...
66
		}
67
68
		/**
69
		 * Private clone method to prevent cloning of the instance of the
70
		 * *Singleton* instance.
71
		 *
72
		 * @return void
73
		 */
74
		private function __clone() {}
75
76
		/**
77
		 * Private unserialize method to prevent unserializing of the *Singleton*
78
		 * instance.
79
		 *
80
		 * @return void
81
		 */
82
		private function __wakeup() {}
83
84
		/**
85
		 * Flag to indicate whether or not we need to load code for / support subscriptions.
86
		 *
87
		 * @var bool
88
		 */
89
		private $subscription_support_enabled = false;
90
91
		/**
92
		 * Flag to indicate whether or not we need to load support for pre-orders.
93
		 *
94
		 * @since 3.0.3
95
		 *
96
		 * @var bool
97
		 */
98
		private $pre_order_enabled = false;
99
100
		/**
101
		 * Notices (array)
102
		 * @var array
103
		 */
104
		public $notices = array();
105
106
		/**
107
		 * Protected constructor to prevent creating a new instance of the
108
		 * *Singleton* via the `new` operator from outside of this class.
109
		 */
110
		protected function __construct() {
111
			add_action( 'admin_init', array( $this, 'check_environment' ) );
112
			add_action( 'admin_notices', array( $this, 'admin_notices' ), 15 );
113
			add_action( 'plugins_loaded', array( $this, 'init' ) );
114
		}
115
116
		/**
117
		 * Init the plugin after plugins_loaded so environment variables are set.
118
		 */
119
		public function init() {
120
			// Don't hook anything else in the plugin if we're in an incompatible environment
121
			if ( self::get_environment_warning() ) {
122
				return;
123
			}
124
125
			include_once( dirname( __FILE__ ) . '/includes/class-wc-stripe-api.php' );
126
			include_once( dirname( __FILE__ ) . '/includes/class-wc-stripe-customer.php' );
127
128
			// Init the gateway itself
129
			$this->init_gateways();
130
131
			add_filter( 'plugin_action_links_' . plugin_basename( __FILE__ ), array( $this, 'plugin_action_links' ) );
132
			add_action( 'woocommerce_order_status_on-hold_to_processing', array( $this, 'capture_payment' ) );
133
			add_action( 'woocommerce_order_status_on-hold_to_completed', array( $this, 'capture_payment' ) );
134
			add_action( 'woocommerce_order_status_on-hold_to_cancelled', array( $this, 'cancel_payment' ) );
135
			add_action( 'woocommerce_order_status_on-hold_to_refunded', array( $this, 'cancel_payment' ) );
136
			add_filter( 'woocommerce_get_customer_payment_tokens', array( $this, 'woocommerce_get_customer_payment_tokens' ), 10, 3 );
137
			add_action( 'woocommerce_payment_token_deleted', array( $this, 'woocommerce_payment_token_deleted' ), 10, 2 );
138
			add_action( 'woocommerce_payment_token_set_default', array( $this, 'woocommerce_payment_token_set_default' ) );
139
140
			include_once( dirname( __FILE__ ) . '/includes/class-wc-stripe-payment-request.php' );
141
		}
142
143
		/**
144
		 * Allow this class and other classes to add slug keyed notices (to avoid duplication)
145
		 */
146
		public function add_admin_notice( $slug, $class, $message ) {
147
			$this->notices[ $slug ] = array(
148
				'class'   => $class,
149
				'message' => $message,
150
			);
151
		}
152
153
		/**
154
		 * The backup sanity check, in case the plugin is activated in a weird way,
155
		 * or the environment changes after activation.
156
		 */
157
		public function check_environment() {
158
			$environment_warning = self::get_environment_warning();
159
160
			if ( $environment_warning && is_plugin_active( plugin_basename( __FILE__ ) ) ) {
161
				$this->add_admin_notice( 'bad_environment', 'error', $environment_warning );
162
			}
163
164
			// Check if secret key present. Otherwise prompt, via notice, to go to
165
			// setting.
166
			if ( ! class_exists( 'WC_Stripe_API' ) ) {
167
				include_once( dirname( __FILE__ ) . '/includes/class-wc-stripe-api.php' );
168
			}
169
170
			$secret = WC_Stripe_API::get_secret_key();
171
172
			if ( empty( $secret ) && ! ( isset( $_GET['page'], $_GET['section'] ) && 'wc-settings' === $_GET['page'] && 'stripe' === $_GET['section'] ) ) {
173
				$setting_link = $this->get_setting_link();
174
				$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 ) );
175
			}
176
		}
177
178
		/**
179
		 * Checks the environment for compatibility problems.  Returns a string with the first incompatibility
180
		 * found or false if the environment has no problems.
181
		 */
182
		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...
183 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...
184
				$message = __( 'WooCommerce Stripe - The minimum PHP version required for this plugin is %1$s. You are running %2$s.', 'woocommerce-gateway-stripe' );
185
186
				return sprintf( $message, WC_STRIPE_MIN_PHP_VER, phpversion() );
187
			}
188
189
			if ( ! defined( 'WC_VERSION' ) ) {
190
				return __( 'WooCommerce Stripe requires WooCommerce to be activated to work.', 'woocommerce-gateway-stripe' );
191
			}
192
193 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...
194
				$message = __( 'WooCommerce Stripe - The minimum WooCommerce version required for this plugin is %1$s. You are running %2$s.', 'woocommerce-gateway-stripe' );
195
196
				return sprintf( $message, WC_STRIPE_MIN_WC_VER, WC_VERSION );
197
			}
198
199
			if ( ! function_exists( 'curl_init' ) ) {
200
				return __( 'WooCommerce Stripe - cURL is not installed.', 'woocommerce-gateway-stripe' );
201
			}
202
203
			return false;
204
		}
205
206
		/**
207
		 * Adds plugin action links
208
		 *
209
		 * @since 1.0.0
210
		 */
211
		public function plugin_action_links( $links ) {
212
			$setting_link = $this->get_setting_link();
213
214
			$plugin_links = array(
215
				'<a href="' . $setting_link . '">' . __( 'Settings', 'woocommerce-gateway-stripe' ) . '</a>',
216
				'<a href="https://docs.woothemes.com/document/stripe/">' . __( 'Docs', 'woocommerce-gateway-stripe' ) . '</a>',
217
				'<a href="http://support.woothemes.com/">' . __( 'Support', 'woocommerce-gateway-stripe' ) . '</a>',
218
			);
219
			return array_merge( $plugin_links, $links );
220
		}
221
222
		/**
223
		 * Get setting link.
224
		 *
225
		 * @since 1.0.0
226
		 *
227
		 * @return string Setting link
228
		 */
229
		public function get_setting_link() {
230
			$use_id_as_section = function_exists( 'WC' ) ? version_compare( WC()->version, '2.6', '>=' ) : false;
231
232
			$section_slug = $use_id_as_section ? 'stripe' : strtolower( 'WC_Gateway_Stripe' );
233
234
			return admin_url( 'admin.php?page=wc-settings&tab=checkout&section=' . $section_slug );
235
		}
236
237
		/**
238
		 * Display any notices we've collected thus far (e.g. for connection, disconnection)
239
		 */
240
		public function admin_notices() {
241
			foreach ( (array) $this->notices as $notice_key => $notice ) {
242
				echo "<div class='" . esc_attr( $notice['class'] ) . "'><p>";
243
				echo wp_kses( $notice['message'], array( 'a' => array( 'href' => array() ) ) );
244
				echo '</p></div>';
245
			}
246
		}
247
248
		/**
249
		 * Initialize the gateway. Called very early - in the context of the plugins_loaded action
250
		 *
251
		 * @since 1.0.0
252
		 */
253
		public function init_gateways() {
254
			if ( class_exists( 'WC_Subscriptions_Order' ) && function_exists( 'wcs_create_renewal_order' ) ) {
255
				$this->subscription_support_enabled = true;
256
			}
257
258
			if ( class_exists( 'WC_Pre_Orders_Order' ) ) {
259
				$this->pre_order_enabled = true;
260
			}
261
262
			if ( ! class_exists( 'WC_Payment_Gateway' ) ) {
263
				return;
264
			}
265
266
			if ( class_exists( 'WC_Payment_Gateway_CC' ) ) {
267
				include_once( dirname( __FILE__ ) . '/includes/class-wc-gateway-stripe.php' );
268
				include_once( dirname( __FILE__ ) . '/includes/class-wc-stripe-apple-pay.php' );
269
			} else {
270
				include_once( dirname( __FILE__ ) . '/includes/legacy/class-wc-gateway-stripe.php' );
271
				include_once( dirname( __FILE__ ) . '/includes/legacy/class-wc-gateway-stripe-saved-cards.php' );
272
			}
273
274
			load_plugin_textdomain( 'woocommerce-gateway-stripe', false, plugin_basename( dirname( __FILE__ ) ) . '/languages' );
275
			add_filter( 'woocommerce_payment_gateways', array( $this, 'add_gateways' ) );
276
			
277
			$load_addons = (
278
				$this->subscription_support_enabled
279
				||
280
				$this->pre_order_enabled
281
			);
282
283
			if ( $load_addons ) {
284
				require_once( dirname( __FILE__ ) . '/includes/class-wc-gateway-stripe-addons.php' );
285
			}
286
		}
287
288
		/**
289
		 * Add the gateways to WooCommerce
290
		 *
291
		 * @since 1.0.0
292
		 */
293
		public function add_gateways( $methods ) {
294
			if ( $this->subscription_support_enabled || $this->pre_order_enabled ) {
295
				$methods[] = 'WC_Gateway_Stripe_Addons';
296
			} else {
297
				$methods[] = 'WC_Gateway_Stripe';
298
			}
299
			return $methods;
300
		}
301
302
		/**
303
		 * Capture payment when the order is changed from on-hold to complete or processing
304
		 *
305
		 * @param  int $order_id
306
		 */
307
		public function capture_payment( $order_id ) {
308
			$order = wc_get_order( $order_id );
309
310
			if ( 'stripe' === $order->payment_method ) {
311
				$charge   = get_post_meta( $order_id, '_stripe_charge_id', true );
312
				$captured = get_post_meta( $order_id, '_stripe_charge_captured', true );
313
314
				if ( $charge && 'no' === $captured ) {
315
					$result = WC_Stripe_API::request( array(
316
						'amount'   => $order->get_total() * 100,
317
						'expand[]' => 'balance_transaction',
318
					), 'charges/' . $charge . '/capture' );
319
320
					if ( is_wp_error( $result ) ) {
321
						$order->add_order_note( __( 'Unable to capture charge!', 'woocommerce-gateway-stripe' ) . ' ' . $result->get_error_message() );
322
					} else {
323
						$order->add_order_note( sprintf( __( 'Stripe charge complete (Charge ID: %s)', 'woocommerce-gateway-stripe' ), $result->id ) );
324
						update_post_meta( $order->id, '_stripe_charge_captured', 'yes' );
325
326
						// Store other data such as fees
327
						update_post_meta( $order->id, 'Stripe Payment ID', $result->id );
328
329 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...
330
							// Fees and Net needs to both come from Stripe to be accurate as the returned
331
							// values are in the local currency of the Stripe account, not from WC.
332
							$fee = ! empty( $result->balance_transaction->fee ) ? number_format( $result->balance_transaction->fee / 100, 2, '.', '' ) : 0;
333
							$net = ! empty( $result->balance_transaction->net ) ? number_format( $result->balance_transaction->net / 100, 2, '.', '' ) : 0;
334
							update_post_meta( $order->id, 'Stripe Fee', $fee );
335
							update_post_meta( $order->id, 'Net Revenue From Stripe', $net );
336
						}
337
					}
338
				}
339
			}
340
		}
341
342
		/**
343
		 * Cancel pre-auth on refund/cancellation
344
		 *
345
		 * @param  int $order_id
346
		 */
347
		public function cancel_payment( $order_id ) {
348
			$order = wc_get_order( $order_id );
349
350
			if ( 'stripe' === $order->payment_method ) {
351
				$charge   = get_post_meta( $order_id, '_stripe_charge_id', true );
352
353
				if ( $charge ) {
354
					$result = WC_Stripe_API::request( array(
355
						'amount' => $order->get_total() * 100,
356
					), 'charges/' . $charge . '/refund' );
357
358
					if ( is_wp_error( $result ) ) {
359
						$order->add_order_note( __( 'Unable to refund charge!', 'woocommerce-gateway-stripe' ) . ' ' . $result->get_error_message() );
360
					} else {
361
						$order->add_order_note( sprintf( __( 'Stripe charge refunded (Charge ID: %s)', 'woocommerce-gateway-stripe' ), $result->id ) );
362
						delete_post_meta( $order->id, '_stripe_charge_captured' );
363
						delete_post_meta( $order->id, '_stripe_charge_id' );
364
					}
365
				}
366
			}
367
		}
368
369
		/**
370
		 * Gets saved tokens from API if they don't already exist in WooCommerce.
371
		 * @param array $tokens
372
		 * @return array
373
		 */
374
		public function woocommerce_get_customer_payment_tokens( $tokens, $customer_id, $gateway_id ) {
375
			if ( is_user_logged_in() && 'stripe' === $gateway_id && class_exists( 'WC_Payment_Token_CC' ) ) {
376
				$stripe_customer = new WC_Stripe_Customer( $customer_id );
377
				$stripe_cards    = $stripe_customer->get_cards();
378
				$stored_tokens   = array();
379
380
				foreach ( $tokens as $token ) {
381
					$stored_tokens[] = $token->get_token();
382
				}
383
384
				foreach ( $stripe_cards as $card ) {
385
					if ( ! in_array( $card->id, $stored_tokens ) ) {
386
						$token = new WC_Payment_Token_CC();
387
						$token->set_token( $card->id );
388
						$token->set_gateway_id( 'stripe' );
389
						$token->set_card_type( strtolower( $card->brand ) );
390
						$token->set_last4( $card->last4 );
391
						$token->set_expiry_month( $card->exp_month );
392
						$token->set_expiry_year( $card->exp_year );
393
						$token->set_user_id( $customer_id );
394
						$token->save();
395
						$tokens[ $token->get_id() ] = $token;
396
					}
397
				}
398
			}
399
			return $tokens;
400
		}
401
402
		/**
403
		 * Delete token from Stripe
404
		 */
405
		public function woocommerce_payment_token_deleted( $token_id, $token ) {
406
			if ( 'stripe' === $token->get_gateway_id() ) {
407
				$stripe_customer = new WC_Stripe_Customer( get_current_user_id() );
408
				$stripe_customer->delete_card( $token->get_token() );
409
			}
410
		}
411
412
		/**
413
		 * Set as default in Stripe
414
		 */
415
		public function woocommerce_payment_token_set_default( $token_id ) {
416
			$token = WC_Payment_Tokens::get( $token_id );
417
			if ( 'stripe' === $token->get_gateway_id() ) {
418
				$stripe_customer = new WC_Stripe_Customer( get_current_user_id() );
419
				$stripe_customer->set_default_card( $token->get_token() );
420
			}
421
		}
422
423
		/**
424
		 * Checks Stripe minimum order value authorized per currency
425
		 */
426
		public static function get_minimum_amount() {
427
			// Check order amount
428
			switch ( get_woocommerce_currency() ) {
429
				case 'USD':
430
				case 'CAD':
431
				case 'EUR':
432
				case 'CHF':
433
				case 'AUD':
434
				case 'SGD':
435
					$minimum_amount = 50;
436
					break;
437
				case 'GBP':
438
					$minimum_amount = 30;
439
					break;
440
				case 'DKK':
441
					$minimum_amount = 250;
442
					break;
443
				case 'NOK':
444
				case 'SEK':
445
					$minimum_amount = 300;
446
					break;
447
				case 'JPY':
448
					$minimum_amount = 5000;
449
					break;
450
				case 'MXN':
451
					$minimum_amount = 1000;
452
					break;
453
				case 'HKD':
454
					$minimum_amount = 400;
455
					break;
456
				default:
457
					$minimum_amount = 50;
458
					break;
459
			}
460
461
			return $minimum_amount;
462
		}
463
464
		/**
465
		 * What rolls down stairs
466
		 * alone or in pairs,
467
		 * and over your neighbor's dog?
468
		 * What's great for a snack,
469
		 * And fits on your back?
470
		 * It's log, log, log
471
		 */
472
		public static function log( $message ) {
473
			if ( empty( self::$log ) ) {
474
				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...
475
			}
476
477
			self::$log->add( 'woocommerce-gateway-stripe', $message );
478
		}
479
	}
480
481
	$GLOBALS['wc_stripe'] = WC_Stripe::get_instance();
482
483
endif;
484