Completed
Push — master ( 35a813...8583ac )
by Roy
02:32
created

WC_Stripe_Apple_Pay::is_shipping_enabled()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
cc 2
eloc 5
nc 2
nop 0
1
<?php
2
if ( ! defined( 'ABSPATH' ) ) {
3
	exit;
4
}
5
6
/**
7
 * WC_Stripe_Apple_Pay class.
8
 *
9
 * @extends WC_Gateway_Stripe
10
 */
11
class WC_Stripe_Apple_Pay extends WC_Gateway_Stripe {
12
	/**
13
	 * This Instance.
14
	 *
15
	 * @var
16
	 */
17
	private static $_this;
18
19
	/**
20
	 * Statement Description
21
	 *
22
	 * @var
23
	 */
24
	public $statement_descriptor;
25
26
	/**
27
	 * Check if we capture the transaction immediately.
28
	 *
29
	 * @var bool
30
	 */
31
	public $capture;
32
33
	/**
34
	 * Do we accept Apple Pay?
35
	 *
36
	 * @var bool
37
	 */
38
	public $apple_pay;
39
40
	/**
41
	 * Apple Pay button style.
42
	 *
43
	 * @var bool
44
	 */
45
	public $apple_pay_button;
46
47
	/**
48
	 * Apple Pay button language.
49
	 *
50
	 * @var bool
51
	 */
52
	public $apple_pay_button_lang;
53
54
	/**
55
	 * Is test mode active?
56
	 *
57
	 * @var bool
58
	 */
59
	public $testmode;
60
61
	/**
62
	 * Logging enabled?
63
	 *
64
	 * @var bool
65
	 */
66
	public $logging;
67
68
	/**
69
	 * Should we store the users credit cards?
70
	 *
71
	 * @var bool
72
	 */
73
	public $saved_cards;
74
75
	/**
76
	 * Publishable key credentials.
77
	 *
78
	 * @var bool
79
	 */
80
	public $publishable_key;
81
82
	/**
83
	 * Is shipping enabled?
84
	 *
85
	 * @var bool
86
	 */
87
	public $is_shipping_enabled;
88
89
	/**
90
	 * Constructor.
91
	 *
92
	 * @access public
93
	 * @since 3.1.0
94
	 * @version 3.1.0
95
	 */
96
	public function __construct() {
97
		self::$_this = $this;
98
99
		$gateway_settings = get_option( 'woocommerce_stripe_settings', '' );
100
101
		$this->statement_descriptor = ! empty( $gateway_settings['statement_descriptor'] ) ? $gateway_settings['statement_descriptor'] : wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES );
102
103
		// If both site title and statement descriptor is not set. Fallback.
104
		if ( empty( $this->statement_descriptor ) ) {
105
			$this->statement_descriptor = $_SERVER['SERVER_NAME'];
106
		}
107
108
		$this->enabled               = ( ! empty( $gateway_settings['enabled'] ) && 'yes' === $gateway_settings['enabled'] ) ? true : false;
109
		$this->testmode              = ( ! empty( $gateway_settings['testmode'] ) && 'yes' === $gateway_settings['testmode'] ) ? true : false;
110
		$this->capture               = ( ! empty( $gateway_settings['capture'] ) && 'yes' === $gateway_settings['capture'] ) ? true : false;
111
		$this->saved_cards           = ( ! empty( $gateway_settings['saved_cards'] ) && 'yes' === $gateway_settings['saved_cards'] ) ? true : false;
112
		$this->apple_pay             = ( ! empty( $gateway_settings['apple_pay'] ) && 'yes' === $gateway_settings['apple_pay'] ) ? true : false;
113
		$this->apple_pay_button      = ! empty( $gateway_settings['apple_pay_button'] ) ? $gateway_settings['apple_pay_button'] : 'black';
114
		$this->apple_pay_button_lang = ! empty( $gateway_settings['apple_pay_button_lang'] ) ? $gateway_settings['apple_pay_button_lang'] : 'en';
115
		$this->logging               = ( ! empty( $gateway_settings['logging'] ) && 'yes' === $gateway_settings['logging'] ) ? true : false;
116
		$this->publishable_key       = ! empty( $gateway_settings['publishable_key'] ) ? $gateway_settings['publishable_key'] : '';
117
		$this->is_shipping_enabled   = $this->is_shipping_enabled();
118
119
		if ( $this->testmode ) {
120
			$this->publishable_key = ! empty( $gateway_settings['test_publishable_key'] ) ? $gateway_settings['test_publishable_key'] : '';
121
		}
122
123
		$this->init();
124
	}
125
126
	public function instance() {
127
		return self::$_this;
128
	}
129
130
	/**
131
	 * Initialize.
132
	 *
133
	 * @access public
134
	 * @since 3.1.0
135
	 * @version 3.1.4
136
	 */
137
	public function init() {
138
		// If Apple Pay is not enabled no need to proceed further.
139
		if ( ! $this->apple_pay ) {
140
			return;
141
		}
142
143
		add_action( 'wp_enqueue_scripts', array( $this, 'cart_scripts' ) );
144
		add_action( 'wp_enqueue_scripts', array( $this, 'single_scripts' ) );
145
146
		/**
147
		 * In order to display the Apple Pay button in the correct position,
148
		 * a new hook was added to WooCommerce 3.0. In older versions of WooCommerce,
149
		 * CSS is used to position the button.
150
		 */
151
		if ( version_compare( WC_VERSION, '3.0.0', '<' ) ) {
152
			add_action( 'woocommerce_after_add_to_cart_button', array( $this, 'display_apple_pay_button' ), 1 );
153
		} else {
154
			add_action( 'woocommerce_after_add_to_cart_quantity', array( $this, 'display_apple_pay_button' ), 1 );
155
		}
156
157
		add_action( 'woocommerce_proceed_to_checkout', array( $this, 'display_apple_pay_button' ), 1 );
158
		add_action( 'woocommerce_proceed_to_checkout', array( $this, 'display_apple_pay_separator_html' ), 2 );
159
		add_action( 'woocommerce_checkout_before_customer_details', array( $this, 'display_apple_pay_button' ), 1 );
160
		add_action( 'woocommerce_checkout_before_customer_details', array( $this, 'display_apple_pay_separator_html' ), 2 );
161
		add_action( 'wc_ajax_wc_stripe_log_apple_pay_errors', array( $this, 'log_apple_pay_errors' ) );
162
		add_action( 'wc_ajax_wc_stripe_apple_pay', array( $this, 'process_apple_pay' ) );
163
		add_action( 'wc_ajax_wc_stripe_generate_apple_pay_cart', array( $this, 'generate_apple_pay_cart' ) );
164
		add_action( 'wc_ajax_wc_stripe_apple_pay_clear_cart', array( $this, 'clear_cart' ) );
165
		add_action( 'wc_ajax_wc_stripe_generate_apple_pay_single', array( $this, 'generate_apple_pay_single' ) );
166
		add_action( 'wc_ajax_wc_stripe_apple_pay_get_shipping_methods', array( $this, 'get_shipping_methods' ) );
167
		add_action( 'wc_ajax_wc_stripe_apple_pay_update_shipping_method', array( $this, 'update_shipping_method' ) );
168
		add_filter( 'woocommerce_gateway_title', array( $this, 'filter_gateway_title' ), 10, 2 );
169
		add_filter( 'woocommerce_validate_postcode', array( $this, 'postal_code_validation' ), 10, 3 );
170
	}
171
172
	/**
173
	 * Filters the gateway title to reflect Apple Pay.
174
	 *
175
	 */
176
	public function filter_gateway_title( $title, $id ) {
177
		global $post;
178
179
		if ( ! is_object( $post ) ) {
180
			return $title;
181
		}
182
183
		$method_title = get_post_meta( $post->ID, '_payment_method_title', true );
184
185
		if ( 'stripe' === $id && ! empty( $method_title ) && 'Apple Pay (Stripe)' === $method_title ) {
186
			return $method_title;
187
		}
188
189
		return $title;
190
	}
191
192
	/**
193
	 * Log errors coming from Apple Pay.
194
	 *
195
	 * @since 3.1.4
196
	 * @version 3.1.4
197
	 */
198
	public function log_apple_pay_errors() {
199
		if ( ! wp_verify_nonce( $_POST['nonce'], '_wc_stripe_apple_pay_nonce' ) && ! wp_verify_nonce( $_POST['nonce'], '_wc_stripe_apple_pay_cart_nonce' ) ) {
200
			wp_die( __( 'Cheatin&#8217; huh?', 'woocommerce-gateway-stripe' ) );
201
		}
202
203
		$errors = wc_clean( stripslashes( $_POST['errors'] ) );
204
205
		$this->log( $errors );
206
207
		exit;
208
	}
209
210
	/**
211
	 * Removes postal code validation from WC.
212
	 *
213
	 * @since 3.1.4
214
	 * @version 3.1.4
215
	 */
216
	public function postal_code_validation( $valid, $postcode, $country ) {
217
		$gateways = WC()->payment_gateways->get_available_payment_gateways();
218
219
		if ( ! $this->apple_pay || ! isset( $gateways['stripe'] ) ) {
220
			return $valid;
221
		}
222
223
		/**
224
		 * Currently Apple Pay truncates postal codes from UK and Canada to first 3 characters
225
		 * when passing it back from the shippingcontactselected object. This causes WC to invalidate
226
		 * the order and not let it go through. The remedy for now is just to remove this validation.
227
		 */
228
		if ( 'GB' === $country || 'CA' === $country ) {
229
			return true;
230
		}
231
232
		return $valid;
233
	}
234
235
	/**
236
	 * Checks if shipping is enabled for the store.
237
	 *
238
	 * @since 3.1.6
239
	 * @version 3.1.6
240
	 * @return bool
241
	 */
242
	public function is_shipping_enabled() {
243
		$shipping_enabled = get_option( 'woocommerce_ship_to_countries', '' );
244
245
		if ( 'disabled' === $shipping_enabled ) {
246
			return false;
247
		}
248
249
		return true;
250
	}
251
252
	/**
253
	 * Enqueue JS scripts and styles for single product page.
254
	 *
255
	 * @since 3.1.0
256
	 * @version 3.1.4
257
	 */
258
	public function single_scripts() {
259
		if ( ! is_single() ) {
260
			return;
261
		}
262
263
		global $post;
264
265
		$product = wc_get_product( $post->ID );
266
267 View Code Duplication
		if ( ! is_object( $product ) || ! in_array( ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->product_type : $product->get_type() ), $this->supported_product_types() ) ) {
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...
268
			return;
269
		}
270
271
		$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
272
273
		wp_enqueue_style( 'stripe_apple_pay', plugins_url( 'assets/css/stripe-apple-pay.css', WC_STRIPE_MAIN_FILE ), array(), WC_STRIPE_VERSION );
274
275
		wp_enqueue_script( 'stripe', 'https://js.stripe.com/v2/', '', '1.0', true );
276
		wp_enqueue_script( 'woocommerce_stripe_apple_pay_single', plugins_url( 'assets/js/stripe-apple-pay-single' . $suffix . '.js', WC_STRIPE_MAIN_FILE ), array( 'stripe' ), WC_STRIPE_VERSION, true );
277
278
		$stripe_params = array(
279
			'key'                                           => $this->publishable_key,
280
			'currency_code'                                 => get_woocommerce_currency(),
281
			'country_code'                                  => substr( get_option( 'woocommerce_default_country' ), 0, 2 ),
282
			'label'                                         => $this->statement_descriptor,
283
			'ajaxurl'                                       => WC_AJAX::get_endpoint( '%%endpoint%%' ),
284
			'stripe_apple_pay_nonce'                        => wp_create_nonce( '_wc_stripe_apple_pay_nonce' ),
285
			'stripe_apple_pay_cart_nonce'                   => wp_create_nonce( '_wc_stripe_apple_pay_cart_nonce' ),
286
			'stripe_apple_pay_get_shipping_methods_nonce'   => wp_create_nonce( '_wc_stripe_apple_pay_get_shipping_methods_nonce' ),
287
			'stripe_apple_pay_update_shipping_method_nonce' => wp_create_nonce( '_wc_stripe_apple_pay_update_shipping_method_nonce' ),
288
			'needs_shipping'                                => ( $product->needs_shipping() && $this->is_shipping_enabled ) ? 'yes' : 'no',
289
			'i18n'                                          => array(
290
				'sub_total' => __( 'Sub-Total', 'woocommerce-gateway-stripe' ),
291
			),
292
		);
293
294
		wp_localize_script( 'woocommerce_stripe_apple_pay_single', 'wc_stripe_apple_pay_single_params', apply_filters( 'wc_stripe_apple_pay_single_params', $stripe_params ) );
295
	}
296
297
	/**
298
	 * Enqueue JS scripts and styles for the cart/checkout.
299
	 *
300
	 * @since 3.1.0
301
	 * @version 3.1.0
302
	 */
303
	public function cart_scripts() {
304
		if ( ! is_cart() && ! is_checkout() && ! isset( $_GET['pay_for_order'] ) ) {
305
			return;
306
		}
307
308
		$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
309
310
		wp_enqueue_style( 'stripe_apple_pay', plugins_url( 'assets/css/stripe-apple-pay.css', WC_STRIPE_MAIN_FILE ), array(), WC_STRIPE_VERSION );
311
312
		wp_enqueue_script( 'stripe', 'https://js.stripe.com/v2/', '', '1.0', true );
313
		wp_enqueue_script( 'woocommerce_stripe_apple_pay', plugins_url( 'assets/js/stripe-apple-pay' . $suffix . '.js', WC_STRIPE_MAIN_FILE ), array( 'stripe' ), WC_STRIPE_VERSION, true );
314
315
		$stripe_params = array(
316
			'key'                                           => $this->publishable_key,
317
			'currency_code'                                 => get_woocommerce_currency(),
318
			'country_code'                                  => substr( get_option( 'woocommerce_default_country' ), 0, 2 ),
319
			'label'                                         => $this->statement_descriptor,
320
			'ajaxurl'                                       => WC_AJAX::get_endpoint( '%%endpoint%%' ),
321
			'stripe_apple_pay_nonce'                        => wp_create_nonce( '_wc_stripe_apple_pay_nonce' ),
322
			'stripe_apple_pay_cart_nonce'                   => wp_create_nonce( '_wc_stripe_apple_pay_cart_nonce' ),
323
			'stripe_apple_pay_get_shipping_methods_nonce'   => wp_create_nonce( '_wc_stripe_apple_pay_get_shipping_methods_nonce' ),
324
			'stripe_apple_pay_update_shipping_method_nonce' => wp_create_nonce( '_wc_stripe_apple_pay_update_shipping_method_nonce' ),
325
			'needs_shipping'                                => ( WC()->cart->needs_shipping() && $this->is_shipping_enabled ) ? 'yes' : 'no',
326
			'is_cart_page'                                  => is_cart() ? 'yes' : 'no',
327
		);
328
329
		wp_localize_script( 'woocommerce_stripe_apple_pay', 'wc_stripe_apple_pay_params', apply_filters( 'wc_stripe_apple_pay_params', $stripe_params ) );
330
	}
331
332
	/**
333
	 * Checks to make sure product type is supported by Apple Pay.
334
	 *
335
	 * @since 3.1.0
336
	 * @version 3.1.0
337
	 * @return array
338
	 */
339
	public function supported_product_types() {
340
		return array(
341
			'simple',
342
			'variable',
343
			'variation',
344
		);
345
	}
346
347
	/**
348
	 * Checks the cart to see if all items are allowed to use
349
	 * Apple Pay.
350
	 *
351
	 * @since 3.1.4
352
	 * @version 3.1.4
353
	 * @return bool
354
	 */
355
	public function allowed_items_in_cart() {
356
		foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
357
			$_product = apply_filters( 'woocommerce_cart_item_product', $cart_item['data'], $cart_item, $cart_item_key );
358
359 View Code Duplication
			if ( ! in_array( ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $_product->product_type : $_product->get_type() ), $this->supported_product_types() ) ) {
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...
360
				return false;
361
			}
362
		}
363
364
		return true;
365
	}
366
367
	/**
368
	 * Display Apple Pay button on the cart page
369
	 *
370
	 * @since 3.1.0
371
	 * @version 3.1.0
372
	 */
373
	public function display_apple_pay_button() {
374
		$gateways = WC()->payment_gateways->get_available_payment_gateways();
375
376
		/**
377
		 * In order for the Apple Pay button to show on product detail page,
378
		 * Apple Pay must be enabled and Stripe gateway must be enabled.
379
		 */
380
		if ( ! $this->apple_pay || ! isset( $gateways['stripe'] ) ) {
381
			$this->log( 'Apple Pay not enabled or Stripe is not an available gateway ( Apple Pay button disabled )' );
382
			return;
383
		}
384
385
		if ( is_single() ) {
386
			global $post;
387
388
			$product = wc_get_product( $post->ID );
389
390 View Code Duplication
			if ( ! is_object( $product ) || ! in_array( ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->product_type : $product->get_type() ), $this->supported_product_types() ) ) {
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...
391
				return;
392
			}
393
		} else {
394
			if ( ! $this->allowed_items_in_cart() ) {
395
				$this->log( 'Items in the cart has unsupported product type ( Apple Pay button disabled )' );
396
				return;
397
			}
398
		}
399
400
		?>
401
		<div class="apple-pay-button-wrapper">
402
			<button class="apple-pay-button" lang="<?php echo esc_attr( $this->apple_pay_button_lang ); ?>" style="-webkit-appearance: -apple-pay-button; -apple-pay-button-type: buy; -apple-pay-button-style: <?php echo esc_attr( $this->apple_pay_button ); ?>;"></button>
403
		</div>
404
		<?php
405
	}
406
407
	/**
408
	 * Display Apple Pay button on the cart page
409
	 *
410
	 * @since 3.1.0
411
	 * @version 3.1.0
412
	 */
413
	public function display_apple_pay_separator_html() {
414
		$gateways = WC()->payment_gateways->get_available_payment_gateways();
415
416
		/**
417
		 * In order for the Apple Pay button to show on cart page,
418
		 * Apple Pay must be enabled and Stripe gateway must be enabled.
419
		 */
420
		if ( ! $this->apple_pay || ! isset( $gateways['stripe'] ) ) {
421
			$this->log( 'Apple Pay not enabled or Stripe is not an available gateway ( Apple Pay button disabled )' );
422
			return;
423
		}
424
425
		if ( ! $this->allowed_items_in_cart() ) {
426
			$this->log( 'Items in the cart has unsupported product type ( Apple Pay button disabled )' );
427
			return;
428
		}
429
		?>
430
		<p class="apple-pay-button-checkout-separator">- <?php esc_html_e( 'Or', 'woocommerce-gateway-stripe' ); ?> -</p>
431
		<?php
432
	}
433
434
	/**
435
	 * Generates the Apple Pay single cart.
436
	 *
437
	 * @since 3.1.0
438
	 * @version 3.1.0
439
	 */
440
	public function generate_apple_pay_single() {
441
		if ( ! wp_verify_nonce( $_POST['nonce'], '_wc_stripe_apple_pay_cart_nonce' ) ) {
442
			wp_die( __( 'Cheatin&#8217; huh?', 'woocommerce-gateway-stripe' ) );
443
		}
444
445
		if ( ! defined( 'WOOCOMMERCE_CART' ) ) {
446
			define( 'WOOCOMMERCE_CART', true );
447
		}
448
449
		WC()->shipping->reset_shipping();
450
451
		global $post;
452
453
		$product = wc_get_product( $post->ID );
454
		$qty     = ! isset( $_POST['qty'] ) ? 1 : absint( $_POST['qty'] );
455
456
		/**
457
		 * If this page is single product page, we need to simulate
458
		 * adding the product to the cart taken account if it is a
459
		 * simple or variable product.
460
		 */
461
		if ( is_single() ) {
462
			// First empty the cart to prevent wrong calculation.
463
			WC()->cart->empty_cart();
464
465
			if ( 'variable' === ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->product_type : $product->get_type() ) && isset( $_POST['attributes'] ) ) {
466
				$attributes = array_map( 'wc_clean', $_POST['attributes'] );
467
468
				if ( version_compare( WC_VERSION, '3.0.0', '<' ) ) {
469
					$variation_id = $product->get_matching_variation( $attributes );
470
				} else {
471
					$data_store = WC_Data_Store::load( 'product' );
472
					$variation_id = $data_store->find_matching_product_variation( $product, $attributes );
473
				}
474
475
				WC()->cart->add_to_cart( $product->get_id(), $qty, $variation_id, $attributes );
476
			}
477
478
			if ( 'simple' === ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->product_type : $product->get_type() ) ) {
479
				WC()->cart->add_to_cart( $product->get_id(), $qty );
480
			}
481
		}
482
483
		WC()->cart->calculate_totals();
484
485
		wp_send_json( array( 'line_items' => $this->build_line_items(), 'total' => WC()->cart->total ) );
486
	}
487
488
	/**
489
	 * Generates the Apple Pay cart.
490
	 *
491
	 * @since 3.1.0
492
	 * @version 3.1.0
493
	 */
494
	public function generate_apple_pay_cart() {
495
		if ( ! wp_verify_nonce( $_POST['nonce'], '_wc_stripe_apple_pay_cart_nonce' ) ) {
496
			wp_die( __( 'Cheatin&#8217; huh?', 'woocommerce-gateway-stripe' ) );
497
		}
498
499
		wp_send_json( array( 'line_items' => $this->build_line_items(), 'total' => WC()->cart->total ) );
500
	}
501
502
	/**
503
	 * Clears Apple Pay cart.
504
	 *
505
	 * @since 3.1.4
506
	 * @version 3.1.4
507
	 */
508
	public function clear_cart() {
509
		WC()->cart->empty_cart();
510
		exit;
511
	}
512
513
	/**
514
	 * Calculate and set shipping method.
515
	 *
516
	 * @since 3.1.0
517
	 * @version 3.1.0
518
	 * @param array $address
519
	 */
520
	public function calculate_shipping( $address = array() ) {
521
		$country  = strtoupper( $address['countryCode'] );
522
		$state    = strtoupper( $address['administrativeArea'] );
523
		$postcode = $address['postalCode'];
524
		$city     = $address['locality'];
525
526
		WC()->shipping->reset_shipping();
527
528
		if ( $postcode && ! WC_Validation::is_postcode( $postcode, $country ) ) {
529
			throw new Exception( __( 'Please enter a valid postcode/ZIP.', 'woocommerce-gateway-stripe' ) );
530
		} elseif ( $postcode ) {
531
			$postcode = wc_format_postcode( $postcode, $country );
532
		}
533
534 View Code Duplication
		if ( $country ) {
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...
535
			WC()->customer->set_location( $country, $state, $postcode, $city );
536
			WC()->customer->set_shipping_location( $country, $state, $postcode, $city );
537
		} else {
538
			WC()->customer->set_to_base();
539
			WC()->customer->set_shipping_to_base();
540
		}
541
542
		WC()->customer->calculated_shipping( true );
543
544
		/**
545
		 * Set the shipping package.
546
		 *
547
		 * Note that address lines are not provided at this point
548
		 * because Apple Pay does not supply that until after
549
		 * authentication via passcode or Touch ID. We will need to
550
		 * capture this information when we process the payment.
551
		 */
552
553
		$packages = array();
554
555
		$packages[0]['contents']                 = WC()->cart->get_cart();
556
		$packages[0]['contents_cost']            = 0;
557
		$packages[0]['applied_coupons']          = WC()->cart->applied_coupons;
558
		$packages[0]['user']['ID']               = get_current_user_id();
559
		$packages[0]['destination']['country']   = $country;
560
		$packages[0]['destination']['state']     = $state;
561
		$packages[0]['destination']['postcode']  = $postcode;
562
		$packages[0]['destination']['city']      = $city;
563
564 View Code Duplication
		foreach ( WC()->cart->get_cart() as $item ) {
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...
565
			if ( $item['data']->needs_shipping() ) {
566
				if ( isset( $item['line_total'] ) ) {
567
					$packages[0]['contents_cost'] += $item['line_total'];
568
				}
569
			}
570
		}
571
572
		$packages = apply_filters( 'woocommerce_cart_shipping_packages', $packages );
573
574
		WC()->shipping->calculate_shipping( $packages );
575
	}
576
577
	/**
578
	 * Gets shipping for Apple Pay Payment sheet.
579
	 *
580
	 * @since 3.1.0
581
	 * @version 3.1.0
582
	 */
583
	public function get_shipping_methods() {
584
		if ( ! wp_verify_nonce( $_POST['nonce'], '_wc_stripe_apple_pay_get_shipping_methods_nonce' ) ) {
585
			wp_die( __( 'Cheatin&#8217; huh?', 'woocommerce-gateway-stripe' ) );
586
		}
587
588
		if ( ! defined( 'WOOCOMMERCE_CART' ) ) {
589
			define( 'WOOCOMMERCE_CART', true );
590
		}
591
592
		try {
593
			$address = array_map( 'wc_clean', $_POST['address'] );
594
595
			$this->calculate_shipping( $address );
596
597
			// Set the shipping options.
598
			$currency = get_woocommerce_currency();
599
			$data     = array();
600
601
			$packages = WC()->shipping->get_packages();
602
603
			if ( ! empty( $packages ) && WC()->customer->has_calculated_shipping() ) {
604
				foreach ( $packages as $package_key => $package ) {
605
					if ( empty( $package['rates'] ) ) {
606
						throw new Exception( __( 'Unable to find shipping method for address.', 'woocommerce-gateway-stripe' ) );
607
					}
608
609 View Code Duplication
					foreach ( $package['rates'] as $key => $rate ) {
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...
610
						$data[] = array(
611
							'id'       => $rate->id,
612
							'label'    => $rate->label,
613
							'amount'   => array(
614
								'currency' => $currency,
615
								'value'    => $rate->cost,
616
							),
617
							'selected' => false,
618
						);
619
					}
620
				}
621
622
				// Auto select the first shipping method.
623
				WC()->session->set( 'chosen_shipping_methods', array( $data[0]['id'] ) );
624
625
				WC()->cart->calculate_totals();
626
627
				wp_send_json( array( 'success' => 'true', 'shipping_methods' => $this->build_shipping_methods( $data ), 'line_items' => $this->build_line_items(), 'total' => WC()->cart->total ) );
628
			} else {
629
				throw new Exception( __( 'Unable to find shipping method for address.', 'woocommerce-gateway-stripe' ) );
630
			}
631
		} catch ( Exception $e ) {
632
			wp_send_json( array( 'success' => 'false', 'shipping_methods' => array(), 'line_items' => $this->build_line_items(), 'total' => WC()->cart->total ) );
633
		}
634
	}
635
636
	/**
637
	 * Updates shipping method on cart session.
638
	 *
639
	 * @since 3.1.0
640
	 * @version 3.1.0
641
	 */
642
	public function update_shipping_method() {
643
		if ( ! defined( 'WOOCOMMERCE_CART' ) ) {
644
			define( 'WOOCOMMERCE_CART', true );
645
		}
646
647
		if ( ! wp_verify_nonce( $_POST['nonce'], '_wc_stripe_apple_pay_update_shipping_method_nonce' ) ) {
648
			wp_die( __( 'Cheatin&#8217; huh?', 'woocommerce-gateway-stripe' ) );
649
		}
650
651
		$selected_shipping_method = array_map( 'wc_clean', $_POST['selected_shipping_method'] );
652
653
		WC()->session->set( 'chosen_shipping_methods', array( $selected_shipping_method['identifier'] ) );
654
655
		WC()->cart->calculate_totals();
656
657
		// Send back the new cart total.
658
		$currency  = get_woocommerce_currency();
659
		$tax_total = max( 0, round( WC()->cart->tax_total + WC()->cart->shipping_tax_total, WC()->cart->dp ) );
660
		$data      = array(
661
			'total' => WC()->cart->total,
662
		);
663
664
		// Include fees and taxes as displayItems.
665 View Code Duplication
		foreach ( WC()->cart->fees as $key => $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...
666
			$data['items'][] = array(
667
				'label'  => $fee->name,
668
				'amount' => array(
669
					'currency' => $currency,
670
					'value'    => $fee->amount,
671
				),
672
			);
673
		}
674 View Code Duplication
		if ( 0 < $tax_total ) {
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...
675
			$data['items'][] = array(
676
				'label'  => __( 'Tax', 'woocommerce-gateway-stripe' ),
677
				'amount' => array(
678
					'currency' => $currency,
679
					'value'    => $tax_total,
680
				),
681
			);
682
		}
683
684
		wp_send_json( array( 'success' => 'true', 'line_items' => $this->build_line_items(), 'total' => WC()->cart->total ) );
685
	}
686
687
	/**
688
	 * Handles the Apple Pay processing via AJAX
689
	 *
690
	 * @access public
691
	 * @since 3.1.0
692
	 * @version 3.1.0
693
	 */
694
	public function process_apple_pay() {
695
		if ( ! wp_verify_nonce( $_POST['nonce'], '_wc_stripe_apple_pay_nonce' ) ) {
696
			wp_die( __( 'Cheatin&#8217; huh?', 'woocommerce-gateway-stripe' ) );
697
		}
698
699
		try {
700
			$result = array_map( 'wc_clean', $_POST['result'] );
701
702
			$order = $this->create_order( $result );
703
704
			$order_id = version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->id : $order->get_id();
705
706
			// Handle payment.
707
			if ( $order->get_total() > 0 ) {
708
709 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...
710
					return new WP_Error( 'stripe_error', 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 ) ) );
711
				}
712
713
				$this->log( "Info: Begin processing payment for order {$order_id} for the amount of {$order->get_total()}" );
714
715
				// Make the request.
716
				$response = WC_Stripe_API::request( $this->generate_payment_request( $order, $result['token']['id'] ) );
717
718
				if ( is_wp_error( $response ) ) {
719
					$localized_messages = $this->get_localized_messages();
720
721
					throw new Exception( ( isset( $localized_messages[ $response->get_error_code() ] ) ? $localized_messages[ $response->get_error_code() ] : $response->get_error_message() ) );
722
				}
723
724
				// Process valid response.
725
				$this->process_response( $response, $order );
726
			} else {
727
				$order->payment_complete();
728
			}
729
730
			// Remove cart.
731
			WC()->cart->empty_cart();
732
733
			update_post_meta( $order_id, '_customer_user', get_current_user_id() );
734
			update_post_meta( $order_id, '_payment_method_title', 'Apple Pay (Stripe)' );
735
736
			// Return thank you page redirect.
737
			wp_send_json( array(
738
				'success'  => 'true',
739
				'redirect' => $this->get_return_url( $order ),
740
			) );
741
742
		} catch ( Exception $e ) {
743
			WC()->session->set( 'refresh_totals', true );
744
			$this->log( sprintf( __( 'Error: %s', 'woocommerce-gateway-stripe' ), $e->getMessage() ) );
745
746
			if ( is_object( $order ) && isset( $order_id ) && $order->has_status( array( 'pending', 'failed' ) ) ) {
0 ignored issues
show
Bug introduced by
The variable $order 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...
747
				$this->send_failed_order_email( $order_id );
748
			}
749
750
			wp_send_json( array( 'success' => 'false', 'msg' => $e->getMessage() ) );
751
		}
752
	}
753
754
	/**
755
	 * Generate the request for the payment.
756
	 * @param  WC_Order $order
757
	 * @param string $source token
758
	 * @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...
759
	 */
760
	protected function generate_payment_request( $order, $source ) {
761
		$post_data                = array();
762
		$post_data['currency']    = strtolower( version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->get_order_currency() : $order->get_currency() );
763
		$post_data['amount']      = $this->get_stripe_amount( $order->get_total(), $post_data['currency'] );
764
		$post_data['description'] = sprintf( __( '%1$s - Order %2$s', 'woocommerce-gateway-stripe' ), $this->statement_descriptor, $order->get_order_number() );
765
		$post_data['capture']     = $this->capture ? 'true' : 'false';
766
767
		$billing_email      = version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_email : $order->get_billing_email();
768
769
		if ( ! empty( $billing_email ) && apply_filters( 'wc_stripe_send_stripe_receipt', false ) ) {
770
			$post_data['receipt_email'] = $billing_email;
771
		}
772
773
		$post_data['expand[]']    = 'balance_transaction';
774
		$post_data['source']      = $source;
775
776
		/**
777
		 * Filter the return value of the WC_Payment_Gateway_CC::generate_payment_request.
778
		 *
779
		 * @since 3.1.0
780
		 * @param array $post_data
781
		 * @param WC_Order $order
782
		 * @param object $source
783
		 */
784
		return apply_filters( 'wc_stripe_generate_payment_request', $post_data, $order );
785
	}
786
787
	/**
788
	 * Builds the shippings methods to pass to Apple Pay.
789
	 *
790
	 * @since 3.1.0
791
	 * @version 3.1.0
792
	 */
793
	public function build_shipping_methods( $shipping_methods ) {
794
		if ( empty( $shipping_methods ) ) {
795
			return array();
796
		}
797
798
		$shipping = array();
799
800
		foreach ( $shipping_methods as $method ) {
801
			$shipping[] = array(
802
				'label'      => $method['label'],
803
				'detail'     => '',
804
				'amount'     => $method['amount']['value'],
805
				'identifier' => $method['id'],
806
			);
807
		}
808
809
		return $shipping;
810
	}
811
812
	/**
813
	 * Builds the line items to pass to Apple Pay.
814
	 *
815
	 * @since 3.1.0
816
	 * @version 3.1.0
817
	 */
818
	public function build_line_items() {
819
		if ( ! defined( 'WOOCOMMERCE_CART' ) ) {
820
			define( 'WOOCOMMERCE_CART', true );
821
		}
822
823
		$decimals = apply_filters( 'wc_stripe_apple_pay_decimals', 2 );
824
		
825
		$items    = array();
826
		$subtotal = 0;
827
828
		foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
829
			$amount         = wc_format_decimal( $cart_item['line_subtotal'], $decimals );
830
			$subtotal       += $cart_item['line_subtotal'];
831
			$quantity_label = 1 < $cart_item['quantity'] ? ' (x' . $cart_item['quantity'] . ')' : '';
832
833
			$item = array(
834
				'type'   => 'final',
835
				'label'  => $cart_item['data']->post->post_title . $quantity_label,
836
				'amount' => wc_format_decimal( $amount, $decimals ),
837
			);
838
839
			$items[] = $item;
840
		}
841
842
		// Default show only subtotal instead of itemization.
843
		if ( apply_filters( 'wc_stripe_apple_pay_disable_itemization', true ) ) {
844
			$items = array();
845
			$items[] = array(
846
				'type'   => 'final',
847
				'label'  => esc_html( __( 'Sub-Total', 'woocommerce-gateway-stripe' ) ),
848
				'amount' => wc_format_decimal( $subtotal, $decimals ),
849
			);
850
		}
851
852
		$discounts   = wc_format_decimal( WC()->cart->get_cart_discount_total(), $decimals );
853
		$tax         = wc_format_decimal( WC()->cart->tax_total + WC()->cart->shipping_tax_total, $decimals );
854
		$shipping    = wc_format_decimal( WC()->cart->shipping_total, $decimals );
855
		$item_total  = wc_format_decimal( WC()->cart->cart_contents_total, $decimals ) + $discounts;
856
		$order_total = wc_format_decimal( $item_total + $tax + $shipping, $decimals );
0 ignored issues
show
Unused Code introduced by
$order_total is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
857
858 View Code Duplication
		if ( wc_tax_enabled() ) {
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...
859
			$items[] = array(
860
				'type'   => 'final',
861
				'label'  => esc_html( __( 'Tax', 'woocommerce-gateway-stripe' ) ),
862
				'amount' => $tax,
863
			);
864
		}
865
866
		if ( WC()->cart->needs_shipping() && $this->is_shipping_enabled ) {
867
			$items[] = array(
868
				'type'   => 'final',
869
				'label'  => esc_html( __( 'Shipping', 'woocommerce-gateway-stripe' ) ),
870
				'amount' => $shipping,
871
			);
872
		}
873
874 View Code Duplication
		if ( WC()->cart->has_discount() ) {
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...
875
			$items[] = array(
876
				'type'   => 'final',
877
				'label'  => esc_html( __( 'Discount', 'woocommerce-gateway-stripe' ) ),
878
				'amount' => '-' . $discounts,
879
			);
880
		}
881
882
		return $items;
883
	}
884
885
	/**
886
	 * Create order programatically.
887
	 *
888
	 * @since 3.1.0
889
	 * @version 3.1.0
890
	 * @param array $data
891
	 * @return object $order
892
	 */
893
	public function create_order( $data = array() ) {
894
		if ( empty( $data ) ) {
895
			throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-gateway-stripe' ), 520 ) );
896
		}
897
898
		$order = wc_create_order();
899
		$order_id = version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->id : $order->get_id();
900
901
		if ( is_wp_error( $order ) ) {
902
			throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-gateway-stripe' ), 520 ) );
903
		} elseif ( false === $order ) {
904
			throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-gateway-stripe' ), 521 ) );
905
		} else {
906
			do_action( 'woocommerce_new_order', $order_id );
907
		}
908
909
		// Store the line items to the new/resumed order
910
		foreach ( WC()->cart->get_cart() as $cart_item_key => $values ) {
911
			$item_id = $order->add_product(
912
				$values['data'],
913
				$values['quantity'],
914
				array(
915
					'variation' => $values['variation'],
916
					'totals'    => array(
917
						'subtotal'     => $values['line_subtotal'],
918
						'subtotal_tax' => $values['line_subtotal_tax'],
919
						'total'        => $values['line_total'],
920
						'tax'          => $values['line_tax'],
921
						'tax_data'     => $values['line_tax_data'], // Since 2.2
922
					),
923
				)
924
			);
925
926
			if ( ! $item_id ) {
927
				throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-gateway-stripe' ), 525 ) );
928
			}
929
930
			// Allow plugins to add order item meta
931 View Code Duplication
			if ( version_compare( WC_VERSION, '3.0', '<' ) ) {
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...
932
				do_action( 'woocommerce_add_order_item_meta', $item_id, $values, $cart_item_key );
933
			} else {
934
				do_action( 'woocommerce_new_order_item', $item_id, wc_get_product( $item_id ), $order->get_id() );
935
			}
936
		}
937
938
		// Store fees
939
		foreach ( WC()->cart->get_fees() as $fee_key => $fee ) {
940
			if ( version_compare( WC_VERSION, '3.0', '<' ) ) {
941
				$item_id = $order->add_fee( $fee );
942
			} else {
943
				$item = new WC_Order_Item_Fee();
944
				$item->set_props( array(
945
					'name'      => $fee->name,
946
					'tax_class' => $fee->taxable ? $fee->tax_class : 0,
947
					'total'     => $fee->amount,
948
					'total_tax' => $fee->tax,
949
					'taxes'     => array(
950
						'total' => $fee->tax_data,
951
					),
952
					'order_id'  => $order->get_id(),
953
				) );
954
				$item_id = $item->save();
955
				$order->add_item( $item );
956
			}
957
958
			if ( ! $item_id ) {
959
				throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-gateway-stripe' ), 526 ) );
960
			}
961
962
			// Allow plugins to add order item meta to fees
963 View Code Duplication
			if ( version_compare( WC_VERSION, '3.0', '<' ) ) {
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...
964
				do_action( 'woocommerce_add_order_fee_meta', $order_id, $item_id, $fee, $fee_key );
965
			} else {
966
				do_action( 'woocommerce_new_order_item', $item_id, $fee, $order->get_id() );
967
			}
968
		}
969
970
		// Store tax rows
971
		foreach ( array_keys( WC()->cart->taxes + WC()->cart->shipping_taxes ) as $tax_rate_id ) {
972
			$tax_amount = WC()->cart->get_tax_amount( $tax_rate_id );
973
			$shipping_tax_amount = WC()->cart->get_shipping_tax_amount( $tax_rate_id );
974
975
			if ( version_compare( WC_VERSION, '3.0', '<' ) ) {
976
				$item_id = $order->add_tax( $tax_rate_id, $tax_amount, $shipping_tax_amount );
977
			} else {
978
				$item = new WC_Order_Item_Tax();
979
				$item->set_props( array(
980
					'rate_id'            => $tax_rate_id,
981
					'tax_total'          => $tax_amount,
982
					'shipping_tax_total' => $shipping_tax_amount,
983
				) );
984
				$item->set_rate( $tax_rate_id );
985
				$item->set_order_id( $order->get_id() );
986
				$item_id = $item->save();
987
				$order->add_item( $item );
988
			}
989
990
			if ( $tax_rate_id && ! $item_id && apply_filters( 'woocommerce_cart_remove_taxes_zero_rate_id', 'zero-rated' ) !== $tax_rate_id ) {
991
				throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-gateway-stripe' ), 528 ) );
992
			}
993
		}
994
995
		// Store coupons
996
		$discount = WC()->cart->get_coupon_discount_amount( $code );
0 ignored issues
show
Bug introduced by
The variable $code seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
997
		$discount_tax = WC()->cart->get_coupon_discount_tax_amount( $code );
0 ignored issues
show
Bug introduced by
The variable $code seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
998
999
		foreach ( WC()->cart->get_coupons() as $code => $coupon ) {
1000
			if ( version_compare( WC_VERSION, '3.0', '<' ) ) {
1001
				$coupon_id = $order->add_coupon( $code, $discount, $discount_tax );
1002
			} else {
1003
				$item = new WC_Order_Item_Coupon();
1004
				$item->set_props( array(
1005
					'code'         => $code,
1006
					'discount'     => $discount,
1007
					'discount_tax' => $discount_tax,
1008
					'order_id'     => $order->get_id(),
1009
				) );
1010
				$coupon_id = $item->save();
1011
				$order->add_item( $item );
1012
			}
1013
1014
			if ( ! $coupon_id ) {
1015
				throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-gateway-stripe' ), 529 ) );
1016
			}
1017
		}
1018
1019
		// Billing address
1020
		$billing_address = array();
1021
		if ( ! empty( $data['token']['card'] ) ) {
1022
			// Name from Stripe is a full name string.
1023
			$name                          = explode( ' ', $data['token']['card']['name'] );
1024
			$lastname                      = array_pop( $name );
1025
			$firstname                     = implode( ' ', $name );
1026
			$billing_address['first_name'] = $firstname;
1027
			$billing_address['last_name']  = $lastname;
1028
			$billing_address['email']      = $data['shippingContact']['emailAddress'];
1029
			$billing_address['phone']      = $data['shippingContact']['phoneNumber'];
1030
			$billing_address['country']    = $data['token']['card']['country'];
1031
			$billing_address['address_1']  = $data['token']['card']['address_line1'];
1032
			$billing_address['address_2']  = $data['token']['card']['address_line2'];
1033
			$billing_address['city']       = $data['token']['card']['address_city'];
1034
			$billing_address['state']      = $data['token']['card']['address_state'];
1035
			$billing_address['postcode']   = $data['token']['card']['address_zip'];
1036
		}
1037
1038
		// Shipping address.
1039
		$shipping_address = array();
1040
		if ( WC()->cart->needs_shipping() && $this->is_shipping_enabled && ! empty( $data['shippingContact'] ) ) {
1041
			$shipping_address['first_name'] = $data['shippingContact']['givenName'];
1042
			$shipping_address['last_name']  = $data['shippingContact']['familyName'];
1043
			$shipping_address['email']      = $data['shippingContact']['emailAddress'];
1044
			$shipping_address['phone']      = $data['shippingContact']['phoneNumber'];
1045
			$shipping_address['country']    = $data['shippingContact']['countryCode'];
1046
			$shipping_address['address_1']  = $data['shippingContact']['addressLines'][0];
1047
			$shipping_address['address_2']  = $data['shippingContact']['addressLines'][1];
1048
			$shipping_address['city']       = $data['shippingContact']['locality'];
1049
			$shipping_address['state']      = $data['shippingContact']['administrativeArea'];
1050
			$shipping_address['postcode']   = $data['shippingContact']['postalCode'];
1051
		} elseif ( ! empty( $data['shippingContact'] ) ) {
1052
			$shipping_address['first_name'] = $firstname;
0 ignored issues
show
Bug introduced by
The variable $firstname 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...
1053
			$shipping_address['last_name']  = $lastname;
0 ignored issues
show
Bug introduced by
The variable $lastname 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...
1054
			$shipping_address['email']      = $data['shippingContact']['emailAddress'];
1055
			$shipping_address['phone']      = $data['shippingContact']['phoneNumber'];
1056
			$shipping_address['country']    = $data['token']['card']['country'];
1057
			$shipping_address['address_1']  = $data['token']['card']['address_line1'];
1058
			$shipping_address['address_2']  = $data['token']['card']['address_line2'];
1059
			$shipping_address['city']       = $data['token']['card']['address_city'];
1060
			$shipping_address['state']      = $data['token']['card']['address_state'];
1061
			$shipping_address['postcode']   = $data['token']['card']['address_zip'];
1062
		}
1063
1064
		$order->set_address( $billing_address, 'billing' );
1065
		$order->set_address( $shipping_address, 'shipping' );
1066
1067
		WC()->shipping->calculate_shipping( WC()->cart->get_shipping_packages() );
1068
1069
		// Get the rate object selected by user.
1070
		foreach ( WC()->shipping->get_packages() as $package_key => $package ) {
1071
			foreach ( $package['rates'] as $key => $rate ) {
1072
				// Loop through user chosen shipping methods.
1073
				foreach ( WC()->session->get( 'chosen_shipping_methods' ) as $method ) {
1074
					if ( $method === $key ) {
1075
						if ( version_compare( WC_VERSION, '3.0', '<' ) ) {
1076
							$order->add_shipping( $rate );
1077
						} else {
1078
							$item = new WC_Order_Item_Shipping();
1079
							$item->set_props( array(
1080
								'method_title' => $rate->label,
1081
								'method_id'    => $rate->id,
1082
								'total'        => wc_format_decimal( $rate->cost ),
1083
								'taxes'        => $rate->taxes,
1084
								'order_id'     => $order->get_id(),
1085
							) );
1086
							foreach ( $rate->get_meta_data() as $key => $value ) {
1087
								$item->add_meta_data( $key, $value, true );
1088
							}
1089
							$item->save();
1090
							$order->add_item( $item );
1091
						}
1092
					}
1093
				}
1094
			}
1095
		}
1096
1097
		$available_gateways = WC()->payment_gateways->get_available_payment_gateways();
1098
		$order->set_payment_method( $available_gateways['stripe'] );
1099
		$order->set_total( WC()->cart->shipping_total, 'shipping' );
1100
		$order->set_total( WC()->cart->get_cart_discount_total(), 'cart_discount' );
1101
		$order->set_total( WC()->cart->get_cart_discount_tax_total(), 'cart_discount_tax' );
1102
		$order->set_total( WC()->cart->tax_total, 'tax' );
1103
		$order->set_total( WC()->cart->shipping_tax_total, 'shipping_tax' );
1104
		$order->set_total( WC()->cart->total );
1105
1106
		// If we got here, the order was created without problems!
1107
		wc_transaction_query( 'commit' );
1108
1109
		return $order;
1110
	}
1111
1112
	/**
1113
	 * Logs
1114
	 *
1115
	 * @since 3.1.0
1116
	 * @version 3.1.0
1117
	 *
1118
	 * @param string $message
1119
	 */
1120
	public function log( $message ) {
1121
		if ( $this->logging ) {
1122
			WC_Stripe::log( 'Apple Pay: ' . $message );
1123
		}
1124
	}
1125
}
1126
1127
new WC_Stripe_Apple_Pay();
1128