Cancelled
Push — master ( 2c2ec3...9b5fb8 )
by Roy
478:14 queued 478:14
created

WC_Gateway_Stripe::process_payment()   C

Complexity

Conditions 9
Paths 25

Size

Total Lines 75
Code Lines 31

Duplication

Lines 5
Ratio 6.67 %

Importance

Changes 0
Metric Value
dl 5
loc 75
rs 5.875
c 0
b 0
f 0
cc 9
eloc 31
nc 25
nop 3

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
	 * Checkout enabled
22
	 *
23
	 * @var bool
24
	 */
25
	public $stripe_checkout;
26
27
	/**
28
	 * Checkout Locale
29
	 *
30
	 * @var string
31
	 */
32
	public $stripe_checkout_locale;
33
34
	/**
35
	 * Credit card image
36
	 *
37
	 * @var string
38
	 */
39
	public $stripe_checkout_image;
40
41
	/**
42
	 * Should we store the users credit cards?
43
	 *
44
	 * @var bool
45
	 */
46
	public $saved_cards;
47
48
	/**
49
	 * API access secret key
50
	 *
51
	 * @var string
52
	 */
53
	public $secret_key;
54
55
	/**
56
	 * Api access publishable key
57
	 *
58
	 * @var string
59
	 */
60
	public $publishable_key;
61
62
	/**
63
	 * Do we accept bitcoin?
64
	 *
65
	 * @var bool
66
	 */
67
	public $bitcoin;
68
69
	/**
70
	 * Is test mode active?
71
	 *
72
	 * @var bool
73
	 */
74
	public $testmode;
75
76
	/**
77
	 * Logging enabled?
78
	 *
79
	 * @var bool
80
	 */
81
	public $logging;
82
83
	/**
84
	 * Constructor
85
	 */
86 View Code Duplication
	public function __construct() {
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...
87
		$this->id                   = 'stripe';
88
		$this->method_title         = __( 'Stripe', 'woocommerce-gateway-stripe' );
89
		$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' );
90
		$this->has_fields           = true;
91
		$this->view_transaction_url = 'https://dashboard.stripe.com/payments/%s';
92
		$this->supports             = array(
93
			'subscriptions',
94
			'products',
95
			'refunds',
96
			'subscription_cancellation',
97
			'subscription_reactivation',
98
			'subscription_suspension',
99
			'subscription_amount_changes',
100
			'subscription_payment_method_change', // Subs 1.n compatibility.
101
			'subscription_payment_method_change_customer',
102
			'subscription_payment_method_change_admin',
103
			'subscription_date_changes',
104
			'multiple_subscriptions',
105
			'pre-orders',
106
			'tokenization',
107
			'add_payment_method'
108
		);
109
110
		// Load the form fields.
111
		$this->init_form_fields();
112
113
		// Load the settings.
114
		$this->init_settings();
115
116
		// Get setting values.
117
		$this->title                  = $this->get_option( 'title' );
118
		$this->description            = $this->get_option( 'description' );
119
		$this->enabled                = $this->get_option( 'enabled' );
120
		$this->testmode               = 'yes' === $this->get_option( 'testmode' );
121
		$this->capture                = 'yes' === $this->get_option( 'capture', 'yes' );
122
		$this->stripe_checkout        = 'yes' === $this->get_option( 'stripe_checkout' );
123
		$this->stripe_checkout_locale = $this->get_option( 'stripe_checkout_locale' );
124
		$this->stripe_checkout_image  = $this->get_option( 'stripe_checkout_image', '' );
125
		$this->saved_cards            = 'yes' === $this->get_option( 'saved_cards' );
126
		$this->secret_key             = $this->testmode ? $this->get_option( 'test_secret_key' ) : $this->get_option( 'secret_key' );
127
		$this->publishable_key        = $this->testmode ? $this->get_option( 'test_publishable_key' ) : $this->get_option( 'publishable_key' );
128
		$this->bitcoin                = 'USD' === strtoupper( get_woocommerce_currency() ) && 'yes' === $this->get_option( 'stripe_bitcoin' );
129
		$this->logging                = 'yes' === $this->get_option( 'logging' );
130
131
		if ( $this->stripe_checkout ) {
132
			$this->order_button_text = __( 'Continue to payment', 'woocommerce-gateway-stripe' );
133
		}
134
135
		if ( $this->testmode ) {
136
			$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' );
137
			$this->description  = trim( $this->description );
138
		}
139
140
		WC_Stripe_API::set_secret_key( $this->secret_key );
141
142
		// Hooks.
143
		add_action( 'wp_enqueue_scripts', array( $this, 'payment_scripts' ) );
144
		add_action( 'admin_notices', array( $this, 'admin_notices' ) );
145
		add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );
146
	}
147
148
	/**
149
	 * Get_icon function.
150
	 *
151
	 * @access public
152
	 * @return string
153
	 */
154
	public function get_icon() {
155
		$ext   = version_compare( WC()->version, '2.6', '>=' ) ? '.svg' : '.png';
156
		$style = version_compare( WC()->version, '2.6', '>=' ) ? 'style="margin-left: 0.3em"' : '';
157
158
		$icon  = '<img src="' . WC_HTTPS::force_https_url( WC()->plugin_url() . '/assets/images/icons/credit-cards/visa' . $ext ) . '" alt="Visa" width="32" ' . $style . ' />';
159
		$icon .= '<img src="' . WC_HTTPS::force_https_url( WC()->plugin_url() . '/assets/images/icons/credit-cards/mastercard' . $ext ) . '" alt="Mastercard" width="32" ' . $style . ' />';
160
		$icon .= '<img src="' . WC_HTTPS::force_https_url( WC()->plugin_url() . '/assets/images/icons/credit-cards/amex' . $ext ) . '" alt="Amex" width="32" ' . $style . ' />';
161
162 View Code Duplication
		if ( 'USD' === get_woocommerce_currency() ) {
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...
163
			$icon .= '<img src="' . WC_HTTPS::force_https_url( WC()->plugin_url() . '/assets/images/icons/credit-cards/discover' . $ext ) . '" alt="Discover" width="32" ' . $style . ' />';
164
			$icon .= '<img src="' . WC_HTTPS::force_https_url( WC()->plugin_url() . '/assets/images/icons/credit-cards/jcb' . $ext ) . '" alt="JCB" width="32" ' . $style . ' />';
165
			$icon .= '<img src="' . WC_HTTPS::force_https_url( WC()->plugin_url() . '/assets/images/icons/credit-cards/diners' . $ext ) . '" alt="Diners" width="32" ' . $style . ' />';
166
		}
167
168
		if ( 'yes' === $this->bitcoin && 'yes' === $this->stripe_checkout ) {
169
			$icon .= '<img src="' . WC_HTTPS::force_https_url( plugins_url( '/assets/images/bitcoin' . $ext, WC_STRIPE_MAIN_FILE ) ) . '" alt="Bitcoin" width="32" ' . $style . ' />';
170
		}
171
172
		return apply_filters( 'woocommerce_gateway_icon', $icon, $this->id );
173
	}
174
175
	/**
176
	 * Get Stripe amount to pay
177
	 *
178
	 * @param float  $total Amount due.
179
	 * @param string $currency Accepted currency.
180
	 *
181
	 * @return float|int
182
	 */
183 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...
184
		if ( ! $currency ) {
185
			$currency = get_woocommerce_currency();
186
		}
187
		switch ( strtoupper( $currency ) ) {
188
			// Zero decimal currencies.
189
			case 'BIF' :
190
			case 'CLP' :
191
			case 'DJF' :
192
			case 'GNF' :
193
			case 'JPY' :
194
			case 'KMF' :
195
			case 'KRW' :
196
			case 'MGA' :
197
			case 'PYG' :
198
			case 'RWF' :
199
			case 'VND' :
200
			case 'VUV' :
201
			case 'XAF' :
202
			case 'XOF' :
203
			case 'XPF' :
204
				$total = absint( $total );
205
				break;
206
			default :
207
				$total = round( $total, 2 ) * 100; // In cents.
208
				break;
209
		}
210
		return $total;
211
	}
212
213
	/**
214
	 * Check if SSL is enabled and notify the user
215
	 */
216
	public function admin_notices() {
217
		if ( 'no' === $this->enabled ) {
218
			return;
219
		}
220
221
		// Show message if enabled and FORCE SSL is disabled and WordpressHTTPS plugin is not detected.
222 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...
223
			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>';
224
		}
225
	}
226
227
	/**
228
	 * Check if this gateway is enabled
229
	 */
230 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...
231
		if ( 'yes' === $this->enabled ) {
232
			if ( ! $this->testmode && is_checkout() && ! is_ssl() ) {
233
				return false;
234
			}
235
			if ( ! $this->secret_key || ! $this->publishable_key ) {
236
				return false;
237
			}
238
			return true;
239
		}
240
		return false;
241
	}
242
243
	/**
244
	 * Initialise Gateway Settings Form Fields
245
	 */
246
	public function init_form_fields() {
247
		$this->form_fields = include( 'settings-stripe.php' );
248
249
		wc_enqueue_js( "
250
			jQuery( function( $ ) {
251
				$( '#woocommerce_stripe_stripe_checkout' ).change(function(){
252
					if ( $( this ).is( ':checked' ) ) {
253
						$( '#woocommerce_stripe_stripe_checkout_locale, #woocommerce_stripe_stripe_bitcoin, #woocommerce_stripe_stripe_checkout_image' ).closest( 'tr' ).show();
254
					} else {
255
						$( '#woocommerce_stripe_stripe_checkout_locale, #woocommerce_stripe_stripe_bitcoin, #woocommerce_stripe_stripe_checkout_image' ).closest( 'tr' ).hide();
256
					}
257
				}).change();
258
259
				$( '#woocommerce_stripe_secret_key, #woocommerce_stripe_publishable_key' ).change(function(){
260
					var value = $( this ).val();
261
262
					if ( value.indexOf( '_test_' ) >= 0 ) {
263
						$( 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>' );
264
					} else {
265
						$( this ).css( 'border-color', '' );
266
						$( '.stripe-error-description', $( this ).parent() ).remove();
267
					}
268
				}).change();
269
270
				$( '#woocommerce_stripe_test_secret_key, #woocommerce_stripe_test_publishable_key' ).change(function(){
271
					var value = $( this ).val();
272
273
					if ( value.indexOf( '_live_' ) >= 0 ) {
274
						$( 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>' );
275
					} else {
276
						$( this ).css( 'border-color', '' );
277
						$( '.stripe-error-description', $( this ).parent() ).remove();
278
					}
279
				}).change();
280
			});
281
		" );
282
	}
283
284
	/**
285
	 * Payment form on checkout page
286
	 */
287
	public function payment_fields() {
288
		$user                 = wp_get_current_user();
289
		$display_tokenization = $this->supports( 'tokenization' ) && is_checkout() && $this->saved_cards;
290
291 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...
292
			$user_email = get_user_meta( $user->ID, 'billing_email', true );
293
			$user_email = $user_email ? $user_email : $user->user_email;
294
		} else {
295
			$user_email = '';
296
		}
297
298
		if ( is_add_payment_method_page() ) {
299
			$pay_button_text = __( 'Add Card', 'woocommerce-gateway-stripe' );
300
		} else {
301
			$pay_button_text = '';
302
		}
303
304
		echo '<div
305
			id="stripe-payment-data"
306
			data-panel-label="' . esc_attr( $pay_button_text ) . '"
307
			data-description=""
308
			data-email="' . esc_attr( $user_email ) . '"
309
			data-amount="' . esc_attr( $this->get_stripe_amount( WC()->cart->total ) ) . '"
310
			data-name="' . esc_attr( get_bloginfo( 'name', 'display' ) ) . '"
311
			data-currency="' . esc_attr( strtolower( get_woocommerce_currency() ) ) . '"
312
			data-image="' . esc_attr( $this->stripe_checkout_image ) . '"
313
			data-bitcoin="' . esc_attr( $this->bitcoin ? 'true' : 'false' ) . '"
314
			data-locale="' . esc_attr( $this->stripe_checkout_locale ? $this->stripe_checkout_locale : 'en' ) . '">';
315
316
		if ( $this->description ) {
317
			echo apply_filters( 'wc_stripe_description', wpautop( wp_kses_post( $this->description ) ) );
318
		}
319
320
		if ( $display_tokenization ) {
321
			$this->tokenization_script();
322
			$this->saved_payment_methods();
323
		}
324
325
		if ( ! $this->stripe_checkout ) {
326
			$this->form();
327
			
328
			if ( $display_tokenization ) {
329
				$this->save_payment_method_checkbox();
330
			}
331
		}
332
333
		echo '</div>';
334
	}
335
336
	/**
337
	 * Localize Stripe messages based on code
338
	 *
339
	 * @since 3.0.6
340
	 * @version 3.0.6
341
	 * @return array
342
	 */
343
	public function get_localized_messages() {
344
		return apply_filters( 'wc_stripe_localized_messages', array(
345
			'invalid_number'        => __( 'The card number is not a valid credit card number.', 'woocommerce-gateway-stripe' ),
346
			'invalid_expiry_month'  => __( 'The card\'s expiration month is invalid.', 'woocommerce-gateway-stripe' ),
347
			'invalid_expiry_year'   => __( 'The card\'s expiration year is invalid.', 'woocommerce-gateway-stripe' ),
348
			'invalid_cvc'           => __( 'The card\'s security code is invalid.', 'woocommerce-gateway-stripe' ),
349
			'incorrect_number'      => __( 'The card number is incorrect.', 'woocommerce-gateway-stripe' ),
350
			'expired_card'          => __( 'The card has expired.', 'woocommerce-gateway-stripe' ),
351
			'incorrect_cvc'         => __( 'The card\'s security code is incorrect.', 'woocommerce-gateway-stripe' ),
352
			'incorrect_zip'         => __( 'The card\'s zip code failed validation.', 'woocommerce-gateway-stripe' ),
353
			'card_declined'         => __( 'The card was declined.', 'woocommerce-gateway-stripe' ),
354
			'missing'               => __( 'There is no card on a customer that is being charged.', 'woocommerce-gateway-stripe' ),
355
			'processing_error'      => __( 'An error occurred while processing the card.', 'woocommerce-gateway-stripe' ),
356
			'invalid_request_error' => __( 'Could not find payment information.', 'woocommerce-gateway-stripe' ),
357
			) );
358
	}
359
360
	/**
361
	 * payment_scripts function.
362
	 *
363
	 * Outputs scripts used for stripe payment
364
	 *
365
	 * @access public
366
	 */
367
	public function payment_scripts() {
368
		$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
369
		
370
		if ( $this->stripe_checkout ) {
371
			wp_enqueue_script( 'stripe', 'https://checkout.stripe.com/v2/checkout.js', '', '2.0', true );
372
			wp_enqueue_script( 'woocommerce_stripe', plugins_url( 'assets/js/stripe_checkout' . $suffix . '.js', WC_STRIPE_MAIN_FILE ), array( 'stripe' ), WC_STRIPE_VERSION, true );
373 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...
374
			wp_enqueue_script( 'stripe', 'https://js.stripe.com/v2/', '', '1.0', true );
375
			wp_enqueue_script( 'woocommerce_stripe', plugins_url( 'assets/js/stripe' . $suffix . '.js', WC_STRIPE_MAIN_FILE ), array( 'jquery-payment', 'stripe' ), WC_STRIPE_VERSION, true );
376
		}
377
378
		$stripe_params = array(
379
			'key'                  => $this->publishable_key,
380
			'i18n_terms'           => __( 'Please accept the terms and conditions first', 'woocommerce-gateway-stripe' ),
381
			'i18n_required_fields' => __( 'Please fill in required checkout fields first', 'woocommerce-gateway-stripe' ),
382
		);
383
384
		// If we're on the pay page we need to pass stripe.js the address of the order.
385
		if ( isset( $_GET['pay_for_order'] ) && 'true' === $_GET['pay_for_order'] ) {
386
			$order_id = wc_get_order_id_by_order_key( urldecode( $_GET['key'] ) );
387
			$order    = wc_get_order( $order_id );
388
389
			$stripe_params['billing_first_name'] = $order->billing_first_name;
390
			$stripe_params['billing_last_name']  = $order->billing_last_name;
391
			$stripe_params['billing_address_1']  = $order->billing_address_1;
392
			$stripe_params['billing_address_2']  = $order->billing_address_2;
393
			$stripe_params['billing_state']      = $order->billing_state;
394
			$stripe_params['billing_city']       = $order->billing_city;
395
			$stripe_params['billing_postcode']   = $order->billing_postcode;
396
			$stripe_params['billing_country']    = $order->billing_country;
397
		}
398
399
		$stripe_params['no_prepaid_card_msg']                     = __( 'Sorry, we\'re not accepting prepaid cards at this time.', 'woocommerce-gateway-stripe' );
400
		$stripe_params['allow_prepaid_card']                      = apply_filters( 'wc_stripe_allow_prepaid_card', true ) ? 'yes' : 'no';
401
		$stripe_params['stripe_checkout_require_billing_address'] = apply_filters( 'wc_stripe_checkout_require_billing_address', false ) ? 'yes' : 'no';
402
403
		// merge localized messages to be use in JS
404
		$stripe_params = array_merge( $stripe_params, $this->get_localized_messages() );
405
406
		wp_localize_script( 'woocommerce_stripe', 'wc_stripe_params', apply_filters( 'wc_stripe_params', $stripe_params ) );
407
	}
408
409
	/**
410
	 * Generate the request for the payment.
411
	 * @param  WC_Order $order
412
	 * @param  object $source
413
	 * @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...
414
	 */
415 View Code Duplication
	protected function generate_payment_request( $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...
416
		$post_data                = array();
417
		$post_data['currency']    = strtolower( $order->get_order_currency() ? $order->get_order_currency() : get_woocommerce_currency() );
418
		$post_data['amount']      = $this->get_stripe_amount( $order->get_total(), $post_data['currency'] );
419
		$post_data['description'] = sprintf( __( '%s - Order %s', 'woocommerce-gateway-stripe' ), wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ), $order->get_order_number() );
420
		$post_data['capture']     = $this->capture ? 'true' : 'false';
421
422
		if ( ! empty( $order->billing_email ) && apply_filters( 'wc_stripe_send_stripe_receipt', false ) ) {
423
			$post_data['receipt_email'] = $order->billing_email;
424
		}
425
426
		$post_data['expand[]']    = 'balance_transaction';
427
428
		if ( $source->customer ) {
429
			$post_data['customer'] = $source->customer;
430
		}
431
432
		if ( $source->source ) {
433
			$post_data['source'] = $source->source;
434
		}
435
436
		/**
437
		 * Filter the return value of the WC_Payment_Gateway_CC::generate_payment_request.
438
		 *
439
		 * @since 3.1.0
440
		 * @param array $post_data
441
		 * @param WC_Order $order
442
		 * @param object $source
443
		 */
444
		return apply_filters( 'wc_stripe_generate_payment_request', $post_data, $order, $source );
445
	}
446
447
	/**
448
	 * Get payment source. This can be a new token or existing card.
449
	 *
450
	 * @param string $user_id
451
	 * @param bool  $force_customer Should we force customer creation.
452
	 *
453
	 * @throws Exception When card was not added or for and invalid card.
454
	 * @return object
455
	 */
456
	protected function get_source( $user_id, $force_customer = false ) {
457
		$stripe_customer = new WC_Stripe_Customer( $user_id );
458
		$stripe_source   = false;
459
		$token_id        = false;
460
461
		// New CC info was entered and we have a new token to process
462
		if ( isset( $_POST['stripe_token'] ) ) {
463
			$stripe_token     = wc_clean( $_POST['stripe_token'] );
464
			$maybe_saved_card = isset( $_POST['wc-stripe-new-payment-method'] ) && ! empty( $_POST['wc-stripe-new-payment-method'] );
465
466
			// This is true if the user wants to store the card to their account.
467 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...
468
				$stripe_source = $stripe_customer->add_card( $stripe_token );
469
470
				if ( is_wp_error( $stripe_source ) ) {
471
					throw new Exception( $stripe_source->get_error_message() );
472
				}
473
474
			} else {
475
				// Not saving token, so don't define customer either.
476
				$stripe_source   = $stripe_token;
477
				$stripe_customer = false;
478
			}
479
		}
480
481
		// Use an existing token, and then process the payment
482
		elseif ( isset( $_POST['wc-stripe-payment-token'] ) && 'new' !== $_POST['wc-stripe-payment-token'] ) {
483
			$token_id = wc_clean( $_POST['wc-stripe-payment-token'] );
484
			$token    = WC_Payment_Tokens::get( $token_id );
485
486
			if ( ! $token || $token->get_user_id() !== get_current_user_id() ) {
487
				WC()->session->set( 'refresh_totals', true );
488
				throw new Exception( __( 'Invalid payment method. Please input a new card number.', 'woocommerce-gateway-stripe' ) );
489
			}
490
491
			$stripe_source = $token->get_token();
492
		}
493
494
		return (object) array(
495
			'token_id' => $token_id,
496
			'customer' => $stripe_customer ? $stripe_customer->get_id() : false,
497
			'source'   => $stripe_source,
498
		);
499
	}
500
501
	/**
502
	 * Get payment source from an order. This could be used in the future for
503
	 * a subscription as an example, therefore using the current user ID would
504
	 * not work - the customer won't be logged in :)
505
	 *
506
	 * Not using 2.6 tokens for this part since we need a customer AND a card
507
	 * token, and not just one.
508
	 *
509
	 * @param object $order
510
	 * @return object
511
	 */
512 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...
513
		$stripe_customer = new WC_Stripe_Customer();
514
		$stripe_source   = false;
515
		$token_id        = false;
516
517
		if ( $order ) {
518
			if ( $meta_value = get_post_meta( $order->id, '_stripe_customer_id', true ) ) {
519
				$stripe_customer->set_id( $meta_value );
520
			}
521
			if ( $meta_value = get_post_meta( $order->id, '_stripe_card_id', true ) ) {
522
				$stripe_source = $meta_value;
523
			}
524
		}
525
526
		return (object) array(
527
			'token_id' => $token_id,
528
			'customer' => $stripe_customer ? $stripe_customer->get_id() : false,
529
			'source'   => $stripe_source,
530
		);
531
	}
532
533
	/**
534
	 * Process the payment
535
	 *
536
	 * @param int  $order_id Reference.
537
	 * @param bool $retry Should we retry on fail.
538
	 * @param bool $force_customer Force user creation.
539
	 *
540
	 * @throws Exception If payment will not be accepted.
541
	 *
542
	 * @return array|void
543
	 */
544
	public function process_payment( $order_id, $retry = true, $force_customer = false ) {
545
		try {
546
			$order  = wc_get_order( $order_id );
547
			$source = $this->get_source( get_current_user_id(), $force_customer );
548
549 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...
550
				$error_msg = __( 'Please enter your card details to make a payment.', 'woocommerce-gateway-stripe' );
551
				$error_msg .= ' ' . __( 'Developers: Please make sure that you are including jQuery and there are no JavaScript errors on the page.', 'woocommerce-gateway-stripe' );
552
				throw new Exception( $error_msg );
553
			}
554
555
			// Store source to order meta.
556
			$this->save_source( $order, $source );
557
558
			// Handle payment.
559
			if ( $order->get_total() > 0 ) {
560
561
				if ( $order->get_total() * 100 < 50 ) {
562
					throw new Exception( __( 'Sorry, the minimum allowed order total is 0.50 to use this payment method.', 'woocommerce-gateway-stripe' ) );
563
				}
564
565
				WC_Stripe::log( "Info: Begin processing payment for order $order_id for the amount of {$order->get_total()}" );
566
567
				// Make the request.
568
				$response = WC_Stripe_API::request( $this->generate_payment_request( $order, $source ) );
569
570
				if ( is_wp_error( $response ) ) {
571
					// Customer param wrong? The user may have been deleted on stripe's end. Remove customer_id. Can be retried without.
572
					if ( 'customer' === $response->get_error_code() && $retry ) {
573
						delete_user_meta( get_current_user_id(), '_stripe_customer_id' );
574
						return $this->process_payment( $order_id, false, $force_customer );
575
						// Source param wrong? The CARD may have been deleted on stripe's end. Remove token and show message.
576
					} elseif ( 'source' === $response->get_error_code() && $source->token_id ) {
577
						$token = WC_Payment_Tokens::get( $source->token_id );
578
						$token->delete();
579
						throw new Exception( __( 'This card is no longer available and has been removed.', 'woocommerce-gateway-stripe' ) );
580
					}
581
					$localized_messages = $this->get_localized_messages();
582
583
					throw new Exception( ( isset( $localized_messages[ $response->get_error_code() ] ) ? $localized_messages[ $response->get_error_code() ] : $response->get_error_message() ) );
584
				}
585
586
				// Process valid response.
587
				$this->process_response( $response, $order );
588
			} else {
589
				$order->payment_complete();
590
			}
591
592
			// Remove cart.
593
			WC()->cart->empty_cart();
594
595
			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...
596
597
			// Return thank you page redirect.
598
			return array(
599
				'result'   => 'success',
600
				'redirect' => $this->get_return_url( $order )
601
			);
602
603
		} catch ( Exception $e ) {
604
			wc_add_notice( $e->getMessage(), 'error' );
605
			WC_Stripe::log( sprintf( __( 'Error: %s', 'woocommerce-gateway-stripe' ), $e->getMessage() ) );
606
607
			if ( $order->has_status( array( 'pending', 'failed' ) ) ) {
608
				$this->send_failed_order_email( $order_id );
609
			}
610
611
			do_action( 'wc_gateway_stripe_process_payment_error', $e, $order );
612
613
			return array(
614
				'result'   => 'fail',
615
				'redirect' => ''
616
			);
617
		}
618
	}
619
620
	/**
621
	 * Save source to order.
622
	 *
623
	 * @param WC_Order $order For to which the source applies.
624
	 * @param stdClass $source Source information.
625
	 */
626 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...
627
		// Store source in the order.
628
		if ( $source->customer ) {
629
			update_post_meta( $order->id, '_stripe_customer_id', $source->customer );
630
		}
631
		if ( $source->source ) {
632
			update_post_meta( $order->id, '_stripe_card_id', $source->source );
633
		}
634
	}
635
636
	/**
637
	 * Store extra meta data for an order from a Stripe Response.
638
	 */
639
	public function process_response( $response, $order ) {
640
		WC_Stripe::log( "Processing response: " . print_r( $response, true ) );
641
642
		// Store charge data
643
		update_post_meta( $order->id, '_stripe_charge_id', $response->id );
644
		update_post_meta( $order->id, '_stripe_charge_captured', $response->captured ? 'yes' : 'no' );
645
646
		// Store other data such as fees
647 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...
648
			// Fees and Net needs to both come from Stripe to be accurate as the returned
649
			// values are in the local currency of the Stripe account, not from WC.
650
			$fee = ! empty( $response->balance_transaction->fee ) ? number_format( $response->balance_transaction->fee / 100, 2, '.', '' ) : 0;
651
			$net = ! empty( $response->balance_transaction->net ) ? number_format( $response->balance_transaction->net / 100, 2, '.', '' ) : 0;
652
			update_post_meta( $order->id, 'Stripe Fee', $fee );
653
			update_post_meta( $order->id, 'Net Revenue From Stripe', $net );
654
		}
655
656
		if ( $response->captured ) {
657
			$order->payment_complete( $response->id );
658
659
			$message = sprintf( __( 'Stripe charge complete (Charge ID: %s)', 'woocommerce-gateway-stripe' ), $response->id );
660
			$order->add_order_note( $message );
661
			WC_Stripe::log( 'Success: ' . $message );
662
663 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...
664
			add_post_meta( $order->id, '_transaction_id', $response->id, true );
665
666
			if ( $order->has_status( array( 'pending', 'failed' ) ) ) {
667
				$order->reduce_order_stock();
668
			}
669
670
			$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 ) );
671
			WC_Stripe::log( "Successful auth: $response->id" );
672
		}
673
674
		return $response;
675
	}
676
677
	/**
678
	 * Add payment method via account screen.
679
	 * We don't store the token locally, but to the Stripe API.
680
	 * @since 3.0.0
681
	 */
682
	public function add_payment_method() {
683
		if ( empty( $_POST['stripe_token'] ) || ! is_user_logged_in() ) {
684
			wc_add_notice( __( 'There was a problem adding the card.', 'woocommerce-gateway-stripe' ), 'error' );
685
			return;
686
		}
687
688
		$stripe_customer = new WC_Stripe_Customer( get_current_user_id() );
689
		$card            = $stripe_customer->add_card( wc_clean( $_POST['stripe_token'] ) );
690
691
		if ( is_wp_error( $card ) ) {
692
			$localized_messages = $this->get_localized_messages();
693
			$error_msg = __( 'There was a problem adding the card.', 'woocommerce-gateway-stripe' );
694
695
			// loop through the errors to find matching localized message
696
			foreach ( $card->errors as $error => $msg ) {
697
				if ( isset( $localized_messages[ $error ] ) ) {
698
					$error_msg = $localized_messages[ $error ];
699
				}
700
			}
701
702
			wc_add_notice( $error_msg, 'error' );
703
			return;
704
		}
705
706
		return array(
707
			'result'   => 'success',
708
			'redirect' => wc_get_endpoint_url( 'payment-methods' ),
709
		);
710
	}
711
712
	/**
713
	 * Refund a charge
714
	 * @param  int $order_id
715
	 * @param  float $amount
716
	 * @return bool
717
	 */
718 View Code Duplication
	public function process_refund( $order_id, $amount = null, $reason = '' ) {
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...
719
		$order = wc_get_order( $order_id );
720
721
		if ( ! $order || ! $order->get_transaction_id() ) {
722
			return false;
723
		}
724
725
		$body = array();
726
727
		if ( ! is_null( $amount ) ) {
728
			$body['amount']	= $this->get_stripe_amount( $amount );
729
		}
730
731
		if ( $reason ) {
732
			$body['metadata'] = array(
733
				'reason'	=> $reason,
734
			);
735
		}
736
737
		WC_Stripe::log( "Info: Beginning refund for order $order_id for the amount of {$amount}" );
738
739
		$response = WC_Stripe_API::request( $body, 'charges/' . $order->get_transaction_id() . '/refunds' );
740
741
		if ( is_wp_error( $response ) ) {
742
			WC_Stripe::log( "Error: " . $response->get_error_message() );
743
			return $response;
744
		} elseif ( ! empty( $response->id ) ) {
745
			$refund_message = sprintf( __( 'Refunded %s - Refund ID: %s - Reason: %s', 'woocommerce-gateway-stripe' ), wc_price( $response->amount / 100 ), $response->id, $reason );
746
			$order->add_order_note( $refund_message );
747
			WC_Stripe::log( "Success: " . html_entity_decode( strip_tags( $refund_message ) ) );
748
			return true;
749
		}
750
	}
751
752
	/**
753
	 * Sends the failed order email to admin
754
	 *
755
	 * @version 3.1.0
756
	 * @since 3.1.0
757
	 * @param int $order_id
758
	 * @return null
759
	 */
760
	public function send_failed_order_email( $order_id ) {
761
		$emails = WC()->mailer()->get_emails();
762
		if ( ! empty( $emails ) && ! empty( $order_id ) ) {
763
			$emails['WC_Email_Failed_Order']->trigger( $order_id );
764
		}
765
	}
766
}
767