Completed
Push — master ( 352120...2afc1b )
by Roy
02:29
created

WC_Gateway_Stripe   D

Complexity

Total Complexity 121

Size/Duplication

Total Lines 865
Duplicated Lines 15.14 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 0
Metric Value
wmc 121
lcom 1
cbo 3
dl 131
loc 865
rs 4.4444
c 0
b 0
f 0

19 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 65 6
B get_icon() 20 20 5
D get_stripe_amount() 29 29 17
B admin_notices() 3 10 11
B is_available() 12 12 7
B init_form_fields() 0 76 1
C payment_fields() 6 56 15
A get_localized_messages() 0 16 1
C payment_scripts() 4 45 7
C generate_payment_request() 0 38 7
C get_source() 12 42 11
B get_order_source() 20 20 5
C process_payment() 8 82 9
A save_source() 9 9 3
B process_response() 8 37 6
B add_payment_method() 0 29 4
C process_refund() 0 33 7
A send_failed_order_email() 0 6 3
A log() 0 5 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_Gateway_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_Gateway_Stripe, and based on these observations, apply Extract Interface, too.

1
<?php
2
if ( ! defined( 'ABSPATH' ) ) {
3
	exit;
4
}
5
6
/**
7
 * WC_Gateway_Stripe class.
8
 *
9
 * @extends WC_Payment_Gateway
10
 */
11
class WC_Gateway_Stripe extends WC_Payment_Gateway_CC {
12
13
	/**
14
	 * Should we capture Credit cards
15
	 *
16
	 * @var bool
17
	 */
18
	public $capture;
19
20
	/**
21
	 * Alternate credit card statement name
22
	 *
23
	 * @var bool
24
	 */
25
	public $statement_descriptor;
26
27
	/**
28
	 * Checkout enabled
29
	 *
30
	 * @var bool
31
	 */
32
	public $stripe_checkout;
33
34
	/**
35
	 * Checkout Locale
36
	 *
37
	 * @var string
38
	 */
39
	public $stripe_checkout_locale;
40
41
	/**
42
	 * Credit card image
43
	 *
44
	 * @var string
45
	 */
46
	public $stripe_checkout_image;
47
48
	/**
49
	 * Should we store the users credit cards?
50
	 *
51
	 * @var bool
52
	 */
53
	public $saved_cards;
54
55
	/**
56
	 * API access secret key
57
	 *
58
	 * @var string
59
	 */
60
	public $secret_key;
61
62
	/**
63
	 * Api access publishable key
64
	 *
65
	 * @var string
66
	 */
67
	public $publishable_key;
68
69
	/**
70
	 * Do we accept bitcoin?
71
	 *
72
	 * @var bool
73
	 */
74
	public $bitcoin;
75
76
	/**
77
	 * Alow Remember me setting for Stripe Checkout
78
	 *
79
	 * @var bool
80
	 */
81
	public $allow_remember_me;
82
83
	/**
84
	 * Do we accept Apple Pay?
85
	 *
86
	 * @var bool
87
	 */
88
	public $apple_pay;
89
90
	/**
91
	 * Apple Pay button style.
92
	 *
93
	 * @var bool
94
	 */
95
	public $apple_pay_button;
96
97
	/**
98
	 * Is test mode active?
99
	 *
100
	 * @var bool
101
	 */
102
	public $testmode;
103
104
	/**
105
	 * Logging enabled?
106
	 *
107
	 * @var bool
108
	 */
109
	public $logging;
110
111
	/**
112
	 * Constructor
113
	 */
114
	public function __construct() {
115
		$this->id                   = 'stripe';
116
		$this->method_title         = __( 'Stripe', 'woocommerce-gateway-stripe' );
117
		$this->method_description   = __( 'Stripe works by adding credit card fields on the checkout and then sending the details to Stripe for verification.', 'woocommerce-gateway-stripe' );
118
		$this->has_fields           = true;
119
		$this->view_transaction_url = 'https://dashboard.stripe.com/payments/%s';
120
		$this->supports             = array(
121
			'subscriptions',
122
			'products',
123
			'refunds',
124
			'subscription_cancellation',
125
			'subscription_reactivation',
126
			'subscription_suspension',
127
			'subscription_amount_changes',
128
			'subscription_payment_method_change', // Subs 1.n compatibility.
129
			'subscription_payment_method_change_customer',
130
			'subscription_payment_method_change_admin',
131
			'subscription_date_changes',
132
			'multiple_subscriptions',
133
			'pre-orders',
134
			'tokenization',
135
			'add_payment_method',
136
		);
137
138
		// Load the form fields.
139
		$this->init_form_fields();
140
141
		// Load the settings.
142
		$this->init_settings();
143
144
		// Get setting values.
145
		$this->title                  = $this->get_option( 'title' );
146
		$this->description            = $this->get_option( 'description' );
147
		$this->enabled                = $this->get_option( 'enabled' );
148
		$this->testmode               = 'yes' === $this->get_option( 'testmode' );
149
		$this->capture                = 'yes' === $this->get_option( 'capture', 'yes' );
150
		$this->statement_descriptor   = $this->get_option( 'statement_descriptor', wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ) );
151
		$this->stripe_checkout        = 'yes' === $this->get_option( 'stripe_checkout' );
152
		$this->stripe_checkout_locale = $this->get_option( 'stripe_checkout_locale' );
153
		$this->stripe_checkout_image  = $this->get_option( 'stripe_checkout_image', '' );
154
		$this->saved_cards            = 'yes' === $this->get_option( 'saved_cards' );
155
		$this->secret_key             = $this->testmode ? $this->get_option( 'test_secret_key' ) : $this->get_option( 'secret_key' );
156
		$this->publishable_key        = $this->testmode ? $this->get_option( 'test_publishable_key' ) : $this->get_option( 'publishable_key' );
157
		$this->bitcoin                = 'USD' === strtoupper( get_woocommerce_currency() ) && 'yes' === $this->get_option( 'stripe_bitcoin' );
158
		$this->apple_pay              = 'yes' === $this->get_option( 'apple_pay' );
159
		$this->apple_pay_button       = $this->get_option( 'apple_pay_button', 'black' );
160
		$this->logging                = 'yes' === $this->get_option( 'logging' );
161
		$this->allow_remember_me      = 'yes' === $this->get_option( 'allow_remember_me', 'no' );
162
163
		if ( $this->stripe_checkout ) {
164
			$this->order_button_text = __( 'Continue to payment', 'woocommerce-gateway-stripe' );
165
		}
166
167
		if ( $this->testmode ) {
168
			$this->description .= ' ' . sprintf( __( 'TEST MODE ENABLED. In test mode, you can use the card number 4242424242424242 with any CVC and a valid expiration date or check the documentation "<a href="%s">Testing Stripe</a>" for more card numbers.', 'woocommerce-gateway-stripe' ), 'https://stripe.com/docs/testing' );
169
			$this->description  = trim( $this->description );
170
		}
171
172
		WC_Stripe_API::set_secret_key( $this->secret_key );
173
174
		// Hooks.
175
		add_action( 'wp_enqueue_scripts', array( $this, 'payment_scripts' ) );
176
		add_action( 'admin_notices', array( $this, 'admin_notices' ) );
177
		add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );
178
	}
179
180
	/**
181
	 * Get_icon function.
182
	 *
183
	 * @access public
184
	 * @return string
185
	 */
186 View Code Duplication
	public function get_icon() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
187
		$ext   = version_compare( WC()->version, '2.6', '>=' ) ? '.svg' : '.png';
188
		$style = version_compare( WC()->version, '2.6', '>=' ) ? 'style="margin-left: 0.3em"' : '';
189
190
		$icon  = '<img src="' . WC_HTTPS::force_https_url( WC()->plugin_url() . '/assets/images/icons/credit-cards/visa' . $ext ) . '" alt="Visa" width="32" ' . $style . ' />';
191
		$icon .= '<img src="' . WC_HTTPS::force_https_url( WC()->plugin_url() . '/assets/images/icons/credit-cards/mastercard' . $ext ) . '" alt="Mastercard" width="32" ' . $style . ' />';
192
		$icon .= '<img src="' . WC_HTTPS::force_https_url( WC()->plugin_url() . '/assets/images/icons/credit-cards/amex' . $ext ) . '" alt="Amex" width="32" ' . $style . ' />';
193
194
		if ( 'USD' === get_woocommerce_currency() ) {
195
			$icon .= '<img src="' . WC_HTTPS::force_https_url( WC()->plugin_url() . '/assets/images/icons/credit-cards/discover' . $ext ) . '" alt="Discover" width="32" ' . $style . ' />';
196
			$icon .= '<img src="' . WC_HTTPS::force_https_url( WC()->plugin_url() . '/assets/images/icons/credit-cards/jcb' . $ext ) . '" alt="JCB" width="32" ' . $style . ' />';
197
			$icon .= '<img src="' . WC_HTTPS::force_https_url( WC()->plugin_url() . '/assets/images/icons/credit-cards/diners' . $ext ) . '" alt="Diners" width="32" ' . $style . ' />';
198
		}
199
200
		if ( $this->bitcoin && $this->stripe_checkout ) {
201
			$icon .= '<img src="' . WC_HTTPS::force_https_url( plugins_url( '/assets/images/bitcoin' . $ext, WC_STRIPE_MAIN_FILE ) ) . '" alt="Bitcoin" width="24" ' . $style . ' />';
202
		}
203
204
		return apply_filters( 'woocommerce_gateway_icon', $icon, $this->id );
205
	}
206
207
	/**
208
	 * Get Stripe amount to pay
209
	 *
210
	 * @param float  $total Amount due.
211
	 * @param string $currency Accepted currency.
212
	 *
213
	 * @return float|int
214
	 */
215 View Code Duplication
	public function get_stripe_amount( $total, $currency = '' ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
216
		if ( ! $currency ) {
217
			$currency = get_woocommerce_currency();
218
		}
219
		switch ( strtoupper( $currency ) ) {
220
			// Zero decimal currencies.
221
			case 'BIF' :
222
			case 'CLP' :
223
			case 'DJF' :
224
			case 'GNF' :
225
			case 'JPY' :
226
			case 'KMF' :
227
			case 'KRW' :
228
			case 'MGA' :
229
			case 'PYG' :
230
			case 'RWF' :
231
			case 'VND' :
232
			case 'VUV' :
233
			case 'XAF' :
234
			case 'XOF' :
235
			case 'XPF' :
236
				$total = absint( $total );
237
				break;
238
			default :
239
				$total = round( $total, 2 ) * 100; // In cents.
240
				break;
241
		}
242
		return $total;
243
	}
244
245
	/**
246
	 * Check if SSL is enabled and notify the user
247
	 */
248
	public function admin_notices() {
249
		if ( 'no' === $this->enabled ) {
250
			return;
251
		}
252
253
		// Show message if enabled and FORCE SSL is disabled and WordpressHTTPS plugin is not detected.
254 View Code Duplication
		if ( ( function_exists( 'wc_site_is_https' ) && ! wc_site_is_https() ) && ( 'no' === get_option( 'woocommerce_force_ssl_checkout' ) && ! class_exists( 'WordPressHTTPS' ) ) ) {
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...
255
			echo '<div class="error stripe-ssl-message"><p>' . sprintf( __( 'Stripe is enabled, but the <a href="%s">force SSL option</a> is disabled; your checkout may not be secure! Please enable SSL and ensure your server has a valid SSL certificate - Stripe will only work in test mode.', 'woocommerce-gateway-stripe' ), admin_url( 'admin.php?page=wc-settings&tab=checkout' ) ) . '</p></div>';
256
		}
257
	}
258
259
	/**
260
	 * Check if this gateway is enabled
261
	 */
262 View Code Duplication
	public function is_available() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
263
		if ( 'yes' === $this->enabled ) {
264
			if ( ! $this->testmode && is_checkout() && ! is_ssl() ) {
265
				return false;
266
			}
267
			if ( ! $this->secret_key || ! $this->publishable_key ) {
268
				return false;
269
			}
270
			return true;
271
		}
272
		return false;
273
	}
274
275
	/**
276
	 * Initialise Gateway Settings Form Fields
277
	 */
278
	public function init_form_fields() {
279
		if ( ! isset( $_GET['page'] ) || ! isset( $_GET['tab'] ) || ! isset( $_GET['section'] ) ) {
280
			return;
281
		}
282
		
283
		if ( 'wc-settings' !== $_GET['page'] || 'checkout' !== $_GET['tab'] || 'stripe' !== $_GET['section'] ) {
284
			return;
285
		}
286
287
		$this->form_fields = include( 'settings-stripe.php' );
288
289
		wc_enqueue_js( "
290
			jQuery( function( $ ) {
291
				$( document.body ).on( 'change', '#woocommerce_stripe_testmode', function() {
292
					var test_secret_key = $( '#woocommerce_stripe_test_secret_key' ).parents( 'tr' ).eq( 0 ),
293
						test_publishable_key = $( '#woocommerce_stripe_test_publishable_key' ).parents( 'tr' ).eq( 0 ),
294
						live_secret_key = $( '#woocommerce_stripe_secret_key' ).parents( 'tr' ).eq( 0 ),
295
						live_publishable_key = $( '#woocommerce_stripe_publishable_key' ).parents( 'tr' ).eq( 0 );
296
297
					if ( $( this ).is( ':checked' ) ) {
298
						test_secret_key.show();
299
						test_publishable_key.show();
300
						live_secret_key.hide();
301
						live_publishable_key.hide();
302
					} else {
303
						test_secret_key.hide();
304
						test_publishable_key.hide();
305
						live_secret_key.show();
306
						live_publishable_key.show();
307
					}
308
				} );
309
310
				$( '#woocommerce_stripe_testmode' ).change();
311
312
				$( '#woocommerce_stripe_stripe_checkout' ).change( function() {
313
					if ( $( this ).is( ':checked' ) ) {
314
						$( '#woocommerce_stripe_stripe_checkout_locale, #woocommerce_stripe_stripe_bitcoin, #woocommerce_stripe_stripe_checkout_image, #woocommerce_stripe_allow_remember_me' ).closest( 'tr' ).show();
315
						$( '#woocommerce_stripe_request_payment_api' ).closest( 'tr' ).hide();
316
					} else {
317
						$( '#woocommerce_stripe_stripe_checkout_locale, #woocommerce_stripe_stripe_bitcoin, #woocommerce_stripe_stripe_checkout_image, #woocommerce_stripe_allow_remember_me' ).closest( 'tr' ).hide();
318
						$( '#woocommerce_stripe_request_payment_api' ).closest( 'tr' ).show();
319
					}
320
				}).change();
321
322
				$( '#woocommerce_stripe_apple_pay' ).change( function() {
323
					if ( $( this ).is( ':checked' ) ) {
324
						$( '#woocommerce_stripe_apple_pay_button, #woocommerce_stripe_apple_pay_button_lang' ).closest( 'tr' ).show();
325
					} else {
326
						$( '#woocommerce_stripe_apple_pay_button, #woocommerce_stripe_apple_pay_button_lang' ).closest( 'tr' ).hide();
327
					}
328
				}).change();
329
330
				$( '#woocommerce_stripe_secret_key, #woocommerce_stripe_publishable_key' ).change( function() {
331
					var value = $( this ).val();
332
333
					if ( value.indexOf( '_test_' ) >= 0 ) {
334
						$( this ).css( 'border-color', 'red' ).after( '<span class=\"description stripe-error-description\" style=\"color:red; display:block;\">" . __( 'This is not a valid live key. Live keys start with "sk_live_" and "pk_live_".', 'woocommerce-gateway-stripe' ) . "</span>' );
335
					} else {
336
						$( this ).css( 'border-color', '' );
337
						$( '.stripe-error-description', $( this ).parent() ).remove();
338
					}
339
				}).change();
340
341
				$( '#woocommerce_stripe_test_secret_key, #woocommerce_stripe_test_publishable_key' ).change( function() {
342
					var value = $( this ).val();
343
344
					if ( value.indexOf( '_live_' ) >= 0 ) {
345
						$( this ).css( 'border-color', 'red' ).after( '<span class=\"description stripe-error-description\" style=\"color:red; display:block;\">" . __( 'This is not a valid test key. Test keys start with "sk_test_" and "pk_test_".', 'woocommerce-gateway-stripe' ) . "</span>' );
346
					} else {
347
						$( this ).css( 'border-color', '' );
348
						$( '.stripe-error-description', $( this ).parent() ).remove();
349
					}
350
				}).change();
351
			});
352
		" );
353
	}
354
355
	/**
356
	 * Payment form on checkout page
357
	 */
358
	public function payment_fields() {
359
		$user                 = wp_get_current_user();
360
		$display_tokenization = $this->supports( 'tokenization' ) && is_checkout() && $this->saved_cards;
361
		$total                = WC()->cart->total;
362
363
		// If paying from order, we need to get total from order not cart.
364
		if ( isset( $_GET['pay_for_order'] ) && isset( $_GET['key'] ) ) {
365
			$order = wc_get_order( wc_get_order_id_by_order_key( wc_clean( $_GET['key'] ) ) );
366
			$total = $order->get_total();
367
		}
368
369 View Code Duplication
		if ( $user->ID ) {
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...
370
			$user_email = get_user_meta( $user->ID, 'billing_email', true );
371
			$user_email = $user_email ? $user_email : $user->user_email;
372
		} else {
373
			$user_email = '';
374
		}
375
376
		if ( is_add_payment_method_page() ) {
377
			$pay_button_text = __( 'Add Card', 'woocommerce-gateway-stripe' );
378
		} else {
379
			$pay_button_text = '';
380
		}
381
382
		echo '<div
383
			id="stripe-payment-data"
384
			data-panel-label="' . esc_attr( $pay_button_text ) . '"
385
			data-description=""
386
			data-email="' . esc_attr( $user_email ) . '"
387
			data-amount="' . esc_attr( $this->get_stripe_amount( $total ) ) . '"
388
			data-name="' . esc_attr( $this->statement_descriptor ) . '"
389
			data-currency="' . esc_attr( strtolower( get_woocommerce_currency() ) ) . '"
390
			data-image="' . esc_attr( $this->stripe_checkout_image ) . '"
391
			data-bitcoin="' . esc_attr( $this->bitcoin ? 'true' : 'false' ) . '"
392
			data-locale="' . esc_attr( $this->stripe_checkout_locale ? $this->stripe_checkout_locale : 'en' ) . '"
393
			data-allow-remember-me="' . esc_attr( $this->allow_remember_me ? 'true' : 'false' ) . '">';
394
395
		if ( $this->description ) {
396
			echo apply_filters( 'wc_stripe_description', wpautop( wp_kses_post( $this->description ) ) );
397
		}
398
399
		if ( $display_tokenization ) {
400
			$this->tokenization_script();
401
			$this->saved_payment_methods();
402
		}
403
404
		if ( ! $this->stripe_checkout ) {
405
			$this->form();
406
407
			if ( $display_tokenization ) {
408
				$this->save_payment_method_checkbox();
409
			}
410
		}
411
412
		echo '</div>';
413
	}
414
415
	/**
416
	 * Localize Stripe messages based on code
417
	 *
418
	 * @since 3.0.6
419
	 * @version 3.0.6
420
	 * @return array
421
	 */
422
	public function get_localized_messages() {
423
		return apply_filters( 'wc_stripe_localized_messages', array(
424
			'invalid_number'        => __( 'The card number is not a valid credit card number.', 'woocommerce-gateway-stripe' ),
425
			'invalid_expiry_month'  => __( 'The card\'s expiration month is invalid.', 'woocommerce-gateway-stripe' ),
426
			'invalid_expiry_year'   => __( 'The card\'s expiration year is invalid.', 'woocommerce-gateway-stripe' ),
427
			'invalid_cvc'           => __( 'The card\'s security code is invalid.', 'woocommerce-gateway-stripe' ),
428
			'incorrect_number'      => __( 'The card number is incorrect.', 'woocommerce-gateway-stripe' ),
429
			'expired_card'          => __( 'The card has expired.', 'woocommerce-gateway-stripe' ),
430
			'incorrect_cvc'         => __( 'The card\'s security code is incorrect.', 'woocommerce-gateway-stripe' ),
431
			'incorrect_zip'         => __( 'The card\'s zip code failed validation.', 'woocommerce-gateway-stripe' ),
432
			'card_declined'         => __( 'The card was declined.', 'woocommerce-gateway-stripe' ),
433
			'missing'               => __( 'There is no card on a customer that is being charged.', 'woocommerce-gateway-stripe' ),
434
			'processing_error'      => __( 'An error occurred while processing the card.', 'woocommerce-gateway-stripe' ),
435
			'invalid_request_error' => __( 'Could not find payment information.', 'woocommerce-gateway-stripe' ),
436
		) );
437
	}
438
439
	/**
440
	 * payment_scripts function.
441
	 *
442
	 * Outputs scripts used for stripe payment
443
	 *
444
	 * @access public
445
	 */
446
	public function payment_scripts() {
447
		if ( ! is_cart() && ! is_checkout() && ! isset( $_GET['pay_for_order'] ) ) {
448
			return;
449
		}
450
451
		$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
452
453
		if ( $this->stripe_checkout ) {
454
			wp_enqueue_script( 'stripe_checkout', 'https://checkout.stripe.com/v2/checkout.js', '', '2.0', true );
455
			wp_enqueue_script( 'woocommerce_stripe', plugins_url( 'assets/js/stripe-checkout' . $suffix . '.js', WC_STRIPE_MAIN_FILE ), array( 'stripe' ), WC_STRIPE_VERSION, true );
456 View Code Duplication
		} else {
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...
457
			wp_enqueue_script( 'stripe', 'https://js.stripe.com/v2/', '', '1.0', true );
458
			wp_enqueue_script( 'woocommerce_stripe', plugins_url( 'assets/js/stripe' . $suffix . '.js', WC_STRIPE_MAIN_FILE ), array( 'jquery-payment', 'stripe' ), WC_STRIPE_VERSION, true );
459
		}
460
461
		$stripe_params = array(
462
			'key'                  => $this->publishable_key,
463
			'i18n_terms'           => __( 'Please accept the terms and conditions first', 'woocommerce-gateway-stripe' ),
464
			'i18n_required_fields' => __( 'Please fill in required checkout fields first', 'woocommerce-gateway-stripe' ),
465
		);
466
467
		// If we're on the pay page we need to pass stripe.js the address of the order.
468
		if ( isset( $_GET['pay_for_order'] ) && 'true' === $_GET['pay_for_order'] ) {
469
			$order_id = wc_get_order_id_by_order_key( urldecode( $_GET['key'] ) );
470
			$order    = wc_get_order( $order_id );
471
472
			$stripe_params['billing_first_name'] = $order->billing_first_name;
473
			$stripe_params['billing_last_name']  = $order->billing_last_name;
474
			$stripe_params['billing_address_1']  = $order->billing_address_1;
475
			$stripe_params['billing_address_2']  = $order->billing_address_2;
476
			$stripe_params['billing_state']      = $order->billing_state;
477
			$stripe_params['billing_city']       = $order->billing_city;
478
			$stripe_params['billing_postcode']   = $order->billing_postcode;
479
			$stripe_params['billing_country']    = $order->billing_country;
480
		}
481
482
		$stripe_params['no_prepaid_card_msg']                     = __( 'Sorry, we\'re not accepting prepaid cards at this time.', 'woocommerce-gateway-stripe' );
483
		$stripe_params['allow_prepaid_card']                      = apply_filters( 'wc_stripe_allow_prepaid_card', true ) ? 'yes' : 'no';
484
		$stripe_params['stripe_checkout_require_billing_address'] = apply_filters( 'wc_stripe_checkout_require_billing_address', false ) ? 'yes' : 'no';
485
486
		// merge localized messages to be use in JS
487
		$stripe_params = array_merge( $stripe_params, $this->get_localized_messages() );
488
489
		wp_localize_script( 'woocommerce_stripe', 'wc_stripe_params', apply_filters( 'wc_stripe_params', $stripe_params ) );
490
	}
491
492
	/**
493
	 * Generate the request for the payment.
494
	 * @param  WC_Order $order
495
	 * @param  object $source
496
	 * @return array()
0 ignored issues
show
Documentation introduced by
The doc-type array() could not be parsed: Expected "|" or "end of type", but got "(" at position 5. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
497
	 */
498
	protected function generate_payment_request( $order, $source ) {
499
		$post_data                = array();
500
		$post_data['currency']    = strtolower( $order->get_order_currency() ? $order->get_order_currency() : get_woocommerce_currency() );
501
		$post_data['amount']      = $this->get_stripe_amount( $order->get_total(), $post_data['currency'] );
502
		$post_data['description'] = sprintf( __( '%1$s - Order %2$s', 'woocommerce-gateway-stripe' ), $this->statement_descriptor, $order->get_order_number() );
503
		$post_data['capture']     = $this->capture ? 'true' : 'false';
504
505
		if ( ! empty( $order->billing_email ) && apply_filters( 'wc_stripe_send_stripe_receipt', false ) ) {
506
			$post_data['receipt_email'] = $order->billing_email;
507
		}
508
509
		$post_data['expand[]']    = 'balance_transaction';
510
511
		$metadata = array(
512
			__( 'Customer Name', 'woocommerce-gateway-stripe' ) => sanitize_text_field( $order->billing_first_name ) . ' ' . sanitize_text_field( $order->billing_last_name ),
513
			__( 'Customer Email', 'woocommerce-gateway-stripe' ) => sanitize_email( $order->billing_email ),
514
		);
515
516
		$post_data['metadata'] = apply_filters( 'wc_stripe_payment_metadata', $metadata, $order, $source );
517
518
		if ( $source->customer ) {
519
			$post_data['customer'] = $source->customer;
520
		}
521
522
		if ( $source->source ) {
523
			$post_data['source'] = $source->source;
524
		}
525
526
		/**
527
		 * Filter the return value of the WC_Payment_Gateway_CC::generate_payment_request.
528
		 *
529
		 * @since 3.1.0
530
		 * @param array $post_data
531
		 * @param WC_Order $order
532
		 * @param object $source
533
		 */
534
		return apply_filters( 'wc_stripe_generate_payment_request', $post_data, $order, $source );
535
	}
536
537
	/**
538
	 * Get payment source. This can be a new token or existing card.
539
	 *
540
	 * @param string $user_id
541
	 * @param bool  $force_customer Should we force customer creation.
542
	 *
543
	 * @throws Exception When card was not added or for and invalid card.
544
	 * @return object
545
	 */
546
	protected function get_source( $user_id, $force_customer = false ) {
547
		$stripe_customer = new WC_Stripe_Customer( $user_id );
548
		$stripe_source   = false;
549
		$token_id        = false;
550
551
		// New CC info was entered and we have a new token to process
552
		if ( isset( $_POST['stripe_token'] ) ) {
553
			$stripe_token     = wc_clean( $_POST['stripe_token'] );
554
			$maybe_saved_card = isset( $_POST['wc-stripe-new-payment-method'] ) && ! empty( $_POST['wc-stripe-new-payment-method'] );
555
556
			// This is true if the user wants to store the card to their account.
557 View Code Duplication
			if ( ( $user_id && $this->saved_cards && $maybe_saved_card ) || $force_customer ) {
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...
558
				$stripe_source = $stripe_customer->add_card( $stripe_token );
559
560
				if ( is_wp_error( $stripe_source ) ) {
561
					throw new Exception( $stripe_source->get_error_message() );
562
				}
563
			} else {
564
				// Not saving token, so don't define customer either.
565
				$stripe_source   = $stripe_token;
566
				$stripe_customer = false;
567
			}
568
		} elseif ( isset( $_POST['wc-stripe-payment-token'] ) && 'new' !== $_POST['wc-stripe-payment-token'] ) {
569
			// Use an existing token, and then process the payment
570
571
			$token_id = wc_clean( $_POST['wc-stripe-payment-token'] );
572
			$token    = WC_Payment_Tokens::get( $token_id );
573
574
			if ( ! $token || $token->get_user_id() !== get_current_user_id() ) {
575
				WC()->session->set( 'refresh_totals', true );
576
				throw new Exception( __( 'Invalid payment method. Please input a new card number.', 'woocommerce-gateway-stripe' ) );
577
			}
578
579
			$stripe_source = $token->get_token();
580
		}
581
582
		return (object) array(
583
			'token_id' => $token_id,
584
			'customer' => $stripe_customer ? $stripe_customer->get_id() : false,
585
			'source'   => $stripe_source,
586
		);
587
	}
588
589
	/**
590
	 * Get payment source from an order. This could be used in the future for
591
	 * a subscription as an example, therefore using the current user ID would
592
	 * not work - the customer won't be logged in :)
593
	 *
594
	 * Not using 2.6 tokens for this part since we need a customer AND a card
595
	 * token, and not just one.
596
	 *
597
	 * @param object $order
598
	 * @return object
599
	 */
600 View Code Duplication
	protected function get_order_source( $order = null ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
601
		$stripe_customer = new WC_Stripe_Customer();
602
		$stripe_source   = false;
603
		$token_id        = false;
604
605
		if ( $order ) {
606
			if ( $meta_value = get_post_meta( $order->id, '_stripe_customer_id', true ) ) {
607
				$stripe_customer->set_id( $meta_value );
608
			}
609
			if ( $meta_value = get_post_meta( $order->id, '_stripe_card_id', true ) ) {
610
				$stripe_source = $meta_value;
611
			}
612
		}
613
614
		return (object) array(
615
			'token_id' => $token_id,
616
			'customer' => $stripe_customer ? $stripe_customer->get_id() : false,
617
			'source'   => $stripe_source,
618
		);
619
	}
620
621
	/**
622
	 * Process the payment
623
	 *
624
	 * @param int  $order_id Reference.
625
	 * @param bool $retry Should we retry on fail.
626
	 * @param bool $force_customer Force user creation.
627
	 *
628
	 * @throws Exception If payment will not be accepted.
629
	 *
630
	 * @return array|void
631
	 */
632
	public function process_payment( $order_id, $retry = true, $force_customer = false ) {
633
		try {
634
			$order  = wc_get_order( $order_id );
635
			$source = $this->get_source( get_current_user_id(), $force_customer );
636
637 View Code Duplication
			if ( empty( $source->source ) && empty( $source->customer ) ) {
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...
638
				$error_msg = __( 'Please enter your card details to make a payment.', 'woocommerce-gateway-stripe' );
639
				$error_msg .= ' ' . __( 'Developers: Please make sure that you are including jQuery and there are no JavaScript errors on the page.', 'woocommerce-gateway-stripe' );
640
				throw new Exception( $error_msg );
641
			}
642
643
			// Store source to order meta.
644
			$this->save_source( $order, $source );
645
646
			// Handle payment.
647
			if ( $order->get_total() > 0 ) {
648
649 View Code Duplication
				if ( $order->get_total() * 100 < WC_Stripe::get_minimum_amount() ) {
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...
650
					throw new Exception( sprintf( __( 'Sorry, the minimum allowed order total is %1$s to use this payment method.', 'woocommerce-gateway-stripe' ), wc_price( WC_Stripe::get_minimum_amount() / 100 ) ) );
651
				}
652
653
				$this->log( "Info: Begin processing payment for order $order_id for the amount of {$order->get_total()}" );
654
655
				// Make the request.
656
				$response = WC_Stripe_API::request( $this->generate_payment_request( $order, $source ) );
657
658
				if ( is_wp_error( $response ) ) {
659
					// Customer param wrong? The user may have been deleted on stripe's end. Remove customer_id. Can be retried without.
660
					if ( 'customer' === $response->get_error_code() && $retry ) {
661
						delete_user_meta( get_current_user_id(), '_stripe_customer_id' );
662
						return $this->process_payment( $order_id, false, $force_customer );
663
						// Source param wrong? The CARD may have been deleted on stripe's end. Remove token and show message.
664
					} elseif ( 'source' === $response->get_error_code() && $source->token_id ) {
665
						$token = WC_Payment_Tokens::get( $source->token_id );
666
						$token->delete();
667
						$message = __( 'This card is no longer available and has been removed.', 'woocommerce-gateway-stripe' );
668
						$order->add_order_note( $message );
669
						throw new Exception( $message );
670
					}
671
672
					$localized_messages = $this->get_localized_messages();
673
674
					$message = isset( $localized_messages[ $response->get_error_code() ] ) ? $localized_messages[ $response->get_error_code() ] : $response->get_error_message();
675
676
					$order->add_order_note( $message );
677
678
					throw new Exception( $message );
679
				}
680
681
				// Process valid response.
682
				$this->process_response( $response, $order );
683
			} else {
684
				$order->payment_complete();
685
			}
686
687
			// Remove cart.
688
			WC()->cart->empty_cart();
689
690
			do_action( 'wc_gateway_stripe_process_payment', $response, $order );
0 ignored issues
show
Bug introduced by
The variable $response does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
691
692
			// Return thank you page redirect.
693
			return array(
694
				'result'   => 'success',
695
				'redirect' => $this->get_return_url( $order ),
696
			);
697
698
		} catch ( Exception $e ) {
699
			wc_add_notice( $e->getMessage(), 'error' );
700
			$this->log( sprintf( __( 'Error: %s', 'woocommerce-gateway-stripe' ), $e->getMessage() ) );
701
702
			if ( $order->has_status( array( 'pending', 'failed' ) ) ) {
703
				$this->send_failed_order_email( $order_id );
704
			}
705
706
			do_action( 'wc_gateway_stripe_process_payment_error', $e, $order );
707
708
			return array(
709
				'result'   => 'fail',
710
				'redirect' => '',
711
			);
712
		}
713
	}
714
715
	/**
716
	 * Save source to order.
717
	 *
718
	 * @param WC_Order $order For to which the source applies.
719
	 * @param stdClass $source Source information.
720
	 */
721 View Code Duplication
	protected function save_source( $order, $source ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
722
		// Store source in the order.
723
		if ( $source->customer ) {
724
			update_post_meta( $order->id, '_stripe_customer_id', $source->customer );
725
		}
726
		if ( $source->source ) {
727
			update_post_meta( $order->id, '_stripe_card_id', $source->source );
728
		}
729
	}
730
731
	/**
732
	 * Store extra meta data for an order from a Stripe Response.
733
	 */
734
	public function process_response( $response, $order ) {
735
		$this->log( 'Processing response: ' . print_r( $response, true ) );
736
737
		// Store charge data
738
		update_post_meta( $order->id, '_stripe_charge_id', $response->id );
739
		update_post_meta( $order->id, '_stripe_charge_captured', $response->captured ? 'yes' : 'no' );
740
741
		// Store other data such as fees
742 View Code Duplication
		if ( isset( $response->balance_transaction ) && isset( $response->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...
743
			// Fees and Net needs to both come from Stripe to be accurate as the returned
744
			// values are in the local currency of the Stripe account, not from WC.
745
			$fee = ! empty( $response->balance_transaction->fee ) ? number_format( $response->balance_transaction->fee / 100, 2, '.', '' ) : 0;
746
			$net = ! empty( $response->balance_transaction->net ) ? number_format( $response->balance_transaction->net / 100, 2, '.', '' ) : 0;
747
			update_post_meta( $order->id, 'Stripe Fee', $fee );
748
			update_post_meta( $order->id, 'Net Revenue From Stripe', $net );
749
		}
750
751
		if ( $response->captured ) {
752
			$order->payment_complete( $response->id );
753
754
			$message = sprintf( __( 'Stripe charge complete (Charge ID: %s)', 'woocommerce-gateway-stripe' ), $response->id );
755
			$order->add_order_note( $message );
756
			$this->log( 'Success: ' . $message );
757
758
		} else {
759
			add_post_meta( $order->id, '_transaction_id', $response->id, true );
760
761
			if ( $order->has_status( array( 'pending', 'failed' ) ) ) {
762
				$order->reduce_order_stock();
763
			}
764
765
			$order->update_status( 'on-hold', sprintf( __( 'Stripe charge authorized (Charge ID: %s). Process order to take payment, or cancel to remove the pre-authorization.', 'woocommerce-gateway-stripe' ), $response->id ) );
766
			$this->log( "Successful auth: $response->id" );
767
		}
768
769
		return $response;
770
	}
771
772
	/**
773
	 * Add payment method via account screen.
774
	 * We don't store the token locally, but to the Stripe API.
775
	 * @since 3.0.0
776
	 */
777
	public function add_payment_method() {
778
		if ( empty( $_POST['stripe_token'] ) || ! is_user_logged_in() ) {
779
			wc_add_notice( __( 'There was a problem adding the card.', 'woocommerce-gateway-stripe' ), 'error' );
780
			return;
781
		}
782
783
		$stripe_customer = new WC_Stripe_Customer( get_current_user_id() );
784
		$card            = $stripe_customer->add_card( wc_clean( $_POST['stripe_token'] ) );
785
786
		if ( is_wp_error( $card ) ) {
787
			$localized_messages = $this->get_localized_messages();
788
			$error_msg = __( 'There was a problem adding the card.', 'woocommerce-gateway-stripe' );
789
790
			// loop through the errors to find matching localized message
791
			foreach ( $card->errors as $error => $msg ) {
792
				if ( isset( $localized_messages[ $error ] ) ) {
793
					$error_msg = $localized_messages[ $error ];
794
				}
795
			}
796
797
			wc_add_notice( $error_msg, 'error' );
798
			return;
799
		}
800
801
		return array(
802
			'result'   => 'success',
803
			'redirect' => wc_get_endpoint_url( 'payment-methods' ),
804
		);
805
	}
806
807
	/**
808
	 * Refund a charge
809
	 * @param  int $order_id
810
	 * @param  float $amount
811
	 * @return bool
812
	 */
813
	public function process_refund( $order_id, $amount = null, $reason = '' ) {
814
		$order = wc_get_order( $order_id );
815
816
		if ( ! $order || ! $order->get_transaction_id() ) {
817
			return false;
818
		}
819
820
		$body = array();
821
822
		if ( ! is_null( $amount ) ) {
823
			$body['amount']	= $this->get_stripe_amount( $amount );
824
		}
825
826
		if ( $reason ) {
827
			$body['metadata'] = array(
828
				'reason'	=> $reason,
829
			);
830
		}
831
832
		$this->log( "Info: Beginning refund for order $order_id for the amount of {$amount}" );
833
834
		$response = WC_Stripe_API::request( $body, 'charges/' . $order->get_transaction_id() . '/refunds' );
835
836
		if ( is_wp_error( $response ) ) {
837
			$this->log( 'Error: ' . $response->get_error_message() );
838
			return $response;
839
		} elseif ( ! empty( $response->id ) ) {
840
			$refund_message = sprintf( __( 'Refunded %1$s - Refund ID: %2$s - Reason: %3$s', 'woocommerce-gateway-stripe' ), wc_price( $response->amount / 100 ), $response->id, $reason );
841
			$order->add_order_note( $refund_message );
842
			$this->log( 'Success: ' . html_entity_decode( strip_tags( $refund_message ) ) );
843
			return true;
844
		}
845
	}
846
847
	/**
848
	 * Sends the failed order email to admin
849
	 *
850
	 * @version 3.1.0
851
	 * @since 3.1.0
852
	 * @param int $order_id
853
	 * @return null
854
	 */
855
	public function send_failed_order_email( $order_id ) {
856
		$emails = WC()->mailer()->get_emails();
857
		if ( ! empty( $emails ) && ! empty( $order_id ) ) {
858
			$emails['WC_Email_Failed_Order']->trigger( $order_id );
859
		}
860
	}
861
862
	/**
863
	 * Logs
864
	 *
865
	 * @since 3.1.0
866
	 * @version 3.1.0
867
	 *
868
	 * @param string $message
869
	 */
870
	public function log( $message ) {
871
		if ( $this->logging ) {
872
			WC_Stripe::log( $message );
873
		}
874
	}
875
}
876