Completed
Push — master ( b352f5...4ff6e2 )
by Roy
02:36
created

WC_Gateway_Stripe::payment_scripts()   C

Complexity

Conditions 7
Paths 6

Size

Total Lines 45
Code Lines 25

Duplication

Lines 4
Ratio 8.89 %

Importance

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