Completed
Pull Request — master (#468)
by Caleb
02:34
created

WC_Stripe_Payment_Request::get_publishable_key()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 6
nc 6
nop 0
dl 0
loc 11
rs 8.8571
c 0
b 0
f 0
1
<?php
2
/**
3
 * Stripe Payment Request API
4
 *
5
 * @package WooCommerce_Stripe/Classes/Payment_Request
6
 * @since   3.1.0
7
 */
8
9
if ( ! defined( 'ABSPATH' ) ) {
10
	exit;
11
}
12
13
/**
14
 * WC_Stripe_Payment_Request class.
15
 */
16
class WC_Stripe_Payment_Request {
17
	/**
18
	 * Enabled.
19
	 *
20
	 * @var
21
	 */
22
	public $stripe_settings;
23
24
	/**
25
	 * Stripe Checkout enabled.
26
	 *
27
	 * @var
28
	 */
29
	public $stripe_checkout_enabled;
30
31
	/**
32
	 * Total label
33
	 *
34
	 * @var
35
	 */
36
	public $total_label;
37
38
	/**
39
	 * Key
40
	 *
41
	 * @var
42
	 */
43
	public $publishable_key;
44
45
	/**
46
	 * Is test mode active?
47
	 *
48
	 * @var bool
49
	 */
50
	public $testmode;
51
52
	/**
53
	 * Initialize class actions.
54
	 *
55
	 * @since 3.0.0
56
	 * @version 4.0.0
57
	 */
58
	public function __construct() {
59
		$this->stripe_settings         = get_option( 'woocommerce_stripe_settings', array() );
60
		$this->testmode                = ( ! empty( $this->stripe_settings['testmode'] ) && 'yes' === $this->stripe_settings['testmode'] ) ? true : false;
61
		$this->publishable_key         = ! empty( $this->stripe_settings['publishable_key'] ) ? $this->stripe_settings['publishable_key'] : '';
62
		$this->stripe_checkout_enabled = isset( $this->stripe_settings['stripe_checkout'] ) && 'yes' === $this->stripe_settings['stripe_checkout'];
63
		$this->total_label             = ! empty( $this->stripe_settings['statement_descriptor'] ) ? WC_Stripe_Helper::clean_statement_descriptor( $this->stripe_settings['statement_descriptor'] ) : '';
64
65
		if ( $this->testmode ) {
66
			$this->publishable_key = ! empty( $this->stripe_settings['test_publishable_key'] ) ? $this->stripe_settings['test_publishable_key'] : '';
67
		}
68
69
		// If both site title and statement descriptor is not set. Fallback.
70
		if ( empty( $this->total_label ) ) {
71
			$this->total_label = $_SERVER['SERVER_NAME'];
72
		}
73
74
		$this->total_label = str_replace( "'", '', $this->total_label ) . apply_filters( 'wc_stripe_payment_request_total_label_suffix', ' (via WooCommerce)' );
75
76
		// Checks if Stripe Gateway is enabled.
77
		if ( empty( $this->stripe_settings ) || ( isset( $this->stripe_settings['enabled'] ) && 'yes' !== $this->stripe_settings['enabled'] ) ) {
78
			return;
79
		}
80
81
		// Checks if Payment Request is enabled.
82
		if ( ! isset( $this->stripe_settings['payment_request'] ) || 'yes' !== $this->stripe_settings['payment_request'] ) {
83
			return;
84
		}
85
86
		// Don't load for change payment method page.
87
		if ( isset( $_GET['change_payment_method'] ) ) {
88
			return;
89
		}
90
91
		$this->init();
92
	}
93
94
	/**
95
	 * Initialize hooks.
96
	 *
97
	 * @since 4.0.0
98
	 * @version 4.0.0
99
	 */
100
	protected function init() {
101
		add_action( 'wp_enqueue_scripts', array( $this, 'scripts' ) );
102
		add_action( 'wp', array( $this, 'set_session' ) );
103
104
		/*
105
		 * In order to display the Payment Request button in the correct position,
106
		 * a new hook was added to WooCommerce 3.0. In older versions of WooCommerce,
107
		 * CSS is used to position the button.
108
		 */
109
		if ( WC_Stripe_Helper::is_pre_30() ) {
110
			add_action( 'woocommerce_after_add_to_cart_button', array( $this, 'display_payment_request_button_html' ), 1 );
111
			add_action( 'woocommerce_after_add_to_cart_button', array( $this, 'display_payment_request_button_separator_html' ), 2 );
112
		} else {
113
			add_action( 'woocommerce_after_add_to_cart_quantity', array( $this, 'display_payment_request_button_html' ), 1 );
114
			add_action( 'woocommerce_after_add_to_cart_quantity', array( $this, 'display_payment_request_button_separator_html' ), 2 );
115
		}
116
117
		add_action( 'woocommerce_proceed_to_checkout', array( $this, 'display_payment_request_button_html' ), 1 );
118
		add_action( 'woocommerce_proceed_to_checkout', array( $this, 'display_payment_request_button_separator_html' ), 2 );
119
120
		if ( apply_filters( 'wc_stripe_show_payment_request_on_checkout', false ) ) {
121
			add_action( 'woocommerce_checkout_before_customer_details', array( $this, 'display_payment_request_button_html' ), 1 );
122
			add_action( 'woocommerce_checkout_before_customer_details', array( $this, 'display_payment_request_button_separator_html' ), 2 );
123
		}
124
125
		add_action( 'wc_ajax_wc_stripe_get_cart_details', array( $this, 'ajax_get_cart_details' ) );
126
		add_action( 'wc_ajax_wc_stripe_get_shipping_options', array( $this, 'ajax_get_shipping_options' ) );
127
		add_action( 'wc_ajax_wc_stripe_update_shipping_method', array( $this, 'ajax_update_shipping_method' ) );
128
		add_action( 'wc_ajax_wc_stripe_create_order', array( $this, 'ajax_create_order' ) );
129
		add_action( 'wc_ajax_wc_stripe_add_to_cart', array( $this, 'ajax_add_to_cart' ) );
130
		add_action( 'wc_ajax_wc_stripe_get_selected_product_data', array( $this, 'ajax_get_selected_product_data' ) );
131
		add_action( 'wc_ajax_wc_stripe_clear_cart', array( $this, 'ajax_clear_cart' ) );
132
		add_action( 'wc_ajax_wc_stripe_log_errors', array( $this, 'ajax_log_errors' ) );
133
134
		add_filter( 'woocommerce_gateway_title', array( $this, 'filter_gateway_title' ), 10, 2 );
135
		add_filter( 'woocommerce_validate_postcode', array( $this, 'postal_code_validation' ), 10, 3 );
136
137
		add_action( 'woocommerce_checkout_order_processed', array( $this, 'add_order_meta' ), 10, 3 );
138
	}
139
140
	/**
141
	 * Sets the WC customer session if one is not set.
142
	 * This is needed so nonces can be verified.
143
	 *
144
	 * @since 4.0.0
145
	 */
146
	public function set_session() {
147
		if ( ! is_user_logged_in() ) {
148
			$wc_session = new WC_Session_Handler();
149
150
			if ( ! $wc_session->has_session() ) {
151
				$wc_session->set_customer_session_cookie( true );
152
			}
153
		}
154
	}
155
156
	/**
157
	 * Gets the button type.
158
	 *
159
	 * @since 4.0.0
160
	 * @version 4.0.0
161
	 * @return string
162
	 */
163
	public function get_button_type() {
164
		return isset( $this->stripe_settings['payment_request_button_type'] ) ? $this->stripe_settings['payment_request_button_type'] : 'default';
165
	}
166
167
	/**
168
	 * Gets the button theme.
169
	 *
170
	 * @since 4.0.0
171
	 * @version 4.0.0
172
	 * @return string
173
	 */
174
	public function get_button_theme() {
175
		return isset( $this->stripe_settings['payment_request_button_theme'] ) ? $this->stripe_settings['payment_request_button_theme'] : 'dark';
176
	}
177
178
	/**
179
	 * Gets the button height.
180
	 *
181
	 * @since 4.0.0
182
	 * @version 4.0.0
183
	 * @return string
184
	 */
185
	public function get_button_height() {
186
		return isset( $this->stripe_settings['payment_request_button_height'] ) ? str_replace( 'px', '', $this->stripe_settings['payment_request_button_height'] ) : '64';
187
	}
188
189
	/**
190
	 * Gets the product data for the currently viewed page
191
	 *
192
	 * @since 4.0.0
193
	 * @version 4.0.0
194
	 */
195
	public function get_product_data() {
196
		if ( ! is_product() ) {
197
			return false;
198
		}
199
200
		global $post;
201
202
		$product = wc_get_product( $post->ID );
203
204
		$data  = array();
205
		$items = array();
206
207
		$items[] = array(
208
			'label'  => WC_Stripe_Helper::is_pre_30() ? $product->name : $product->get_name(),
209
			'amount' => WC_Stripe_Helper::get_stripe_amount( WC_Stripe_Helper::is_pre_30() ? $product->price : $product->get_price() ),
210
		);
211
212 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...
213
			$items[] = array(
214
				'label'   => __( 'Tax', 'woocommerce-gateway-stripe' ),
215
				'amount'  => 0,
216
				'pending' => true,
217
			);
218
		}
219
220 View Code Duplication
		if ( wc_shipping_enabled() && $product->needs_shipping() ) {
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...
221
			$items[] = array(
222
				'label'   => __( 'Shipping', 'woocommerce-gateway-stripe' ),
223
				'amount'  => 0,
224
				'pending' => true,
225
			);
226
227
			$data['shippingOptions']  = array(
228
				'id'     => 'pending',
229
				'label'  => __( 'Pending', 'woocommerce-gateway-stripe' ),
230
				'detail' => '',
231
				'amount' => 0,
232
			);
233
		}
234
235
		$data['displayItems'] = $items;
236
		$data['total'] = array(
237
			'label'   => $this->total_label,
238
			'amount'  => WC_Stripe_Helper::get_stripe_amount( WC_Stripe_Helper::is_pre_30() ? $product->price : $product->get_price() ),
239
			'pending' => true,
240
		);
241
242
		$data['requestShipping'] = ( wc_shipping_enabled() && $product->needs_shipping() );
243
		$data['currency']        = strtolower( get_woocommerce_currency() );
244
		$data['country_code']    = substr( get_option( 'woocommerce_default_country' ), 0, 2 );
245
246
		return $data;
247
	}
248
249
	/**
250
	 * Filters the gateway title to reflect Payment Request type
251
	 *
252
	 */
253
	public function filter_gateway_title( $title, $id ) {
254
		global $post;
255
256
		if ( ! is_object( $post ) ) {
257
			return $title;
258
		}
259
260
		if ( WC_Stripe_Helper::is_pre_30() ) {
261
			$method_title = get_post_meta( $post->ID, '_payment_method_title', true );
262
		} else {
263
			$order        = wc_get_order( $post->ID );
264
			$method_title = is_object( $order ) ? $order->get_payment_method_title() : '';
265
		}
266
267
		if ( 'stripe' === $id && ! empty( $method_title ) && 'Apple Pay (Stripe)' === $method_title ) {
268
			return $method_title;
269
		}
270
271
		if ( 'stripe' === $id && ! empty( $method_title ) && 'Chrome Payment Request (Stripe)' === $method_title ) {
272
			return $method_title;
273
		}
274
275
		return $title;
276
	}
277
278
	/**
279
	 * Removes postal code validation from WC.
280
	 *
281
	 * @since 3.1.4
282
	 * @version 4.0.0
283
	 */
284
	public function postal_code_validation( $valid, $postcode, $country ) {
285
		$gateways = WC()->payment_gateways->get_available_payment_gateways();
286
287
		if ( ! isset( $gateways['stripe'] ) ) {
288
			return $valid;
289
		}
290
291
		/**
292
		 * Currently Apple Pay truncates postal codes from UK and Canada to first 3 characters
293
		 * when passing it back from the shippingcontactselected object. This causes WC to invalidate
294
		 * the order and not let it go through. The remedy for now is just to remove this validation.
295
		 * Note that this only works with shipping providers that don't validate full postal codes.
296
		 */
297
		if ( 'GB' === $country || 'CA' === $country ) {
298
			return true;
299
		}
300
301
		return $valid;
302
	}
303
304
	/**
305
	 * Add needed order meta
306
	 *
307
	 * @since 4.0.0
308
	 * @version 4.0.0
309
	 * @param int $order_id
310
	 * @param array $posted_data The posted data from checkout form.
311
	 * @param object $order
312
	 */
313
	public function add_order_meta( $order_id, $posted_data, $order ) {
314
		if ( empty( $_POST['payment_request_type'] ) ) {
315
			return;
316
		}
317
318
		$payment_request_type = wc_clean( $_POST['payment_request_type'] );
319
320 View Code Duplication
		if ( 'apple_pay' === $payment_request_type ) {
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...
321
			if ( WC_Stripe_Helper::is_pre_30() ) {
322
				update_post_meta( $order_id, '_payment_method_title', 'Apple Pay (Stripe)' );
323
			} else {
324
				$order->set_payment_method_title( 'Apple Pay (Stripe)' );
325
				$order->save();
326
			}
327
		}
328
329 View Code Duplication
		if ( 'payment_request_api' === $payment_request_type ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
330
			if ( WC_Stripe_Helper::is_pre_30() ) {
331
				update_post_meta( $order_id, '_payment_method_title', 'Chrome Payment Request (Stripe)' );
332
			} else {
333
				$order->set_payment_method_title( 'Chrome Payment Request (Stripe)' );
334
				$order->save();
335
			}
336
		}
337
	}
338
339
	/**
340
	 * Checks to make sure product type is supported.
341
	 *
342
	 * @since 3.1.0
343
	 * @version 4.0.0
344
	 * @return array
345
	 */
346
	public function supported_product_types() {
347
		return apply_filters( 'wc_stripe_payment_request_supported_types', array(
348
			'simple',
349
			'variable',
350
			'variation',
351
		) );
352
	}
353
354
	/**
355
	 * Checks the cart to see if all items are allowed to used.
356
	 *
357
	 * @since 3.1.4
358
	 * @version 4.0.0
359
	 * @return bool
360
	 */
361
	public function allowed_items_in_cart() {
362
		foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
363
			$_product = apply_filters( 'woocommerce_cart_item_product', $cart_item['data'], $cart_item, $cart_item_key );
364
365
			if ( ! in_array( ( WC_Stripe_Helper::is_pre_30() ? $_product->product_type : $_product->get_type() ), $this->supported_product_types() ) ) {
366
				return false;
367
			}
368
369
			// Pre Orders compatbility where we don't support charge upon release.
370
			if ( class_exists( 'WC_Pre_Orders_Order' ) && WC_Pre_Orders_Cart::cart_contains_pre_order() && WC_Pre_Orders_Product::product_is_charged_upon_release( WC_Pre_Orders_Cart::get_pre_order_product() ) ) {
371
				return false;
372
			}
373
		}
374
375
		return true;
376
	}
377
378
	/**
379
	 * Load public scripts and styles.
380
	 *
381
	 * @since 3.1.0
382
	 * @version 4.0.0
383
	 */
384
	public function scripts() {
385
		if ( ! is_product() && ! is_cart() && ! is_checkout() && ! isset( $_GET['pay_for_order'] ) ) {
386
			return;
387
		}
388
389
		if ( is_product() ) {
390
			global $post;
391
392
			$product = wc_get_product( $post->ID );
393
394
			if ( ! is_object( $product ) || ! in_array( ( WC_Stripe_Helper::is_pre_30() ? $product->product_type : $product->get_type() ), $this->supported_product_types() ) ) {
395
				return;
396
			}
397
398
			if ( apply_filters( 'wc_stripe_hide_payment_request_on_product_page', false ) ) {
399
				return;
400
			}
401
		}
402
403
		$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
404
405
		wp_register_script( 'stripe', 'https://js.stripe.com/v3/', '', '3.0', true );
406
		wp_register_script( 'wc_stripe_payment_request', plugins_url( 'assets/js/stripe-payment-request' . $suffix . '.js', WC_STRIPE_MAIN_FILE ), array( 'jquery', 'stripe' ), WC_STRIPE_VERSION, true );
407
408
		wp_localize_script(
409
			'wc_stripe_payment_request',
410
			'wc_stripe_payment_request_params',
411
			array(
412
				'ajax_url' => WC_AJAX::get_endpoint( '%%endpoint%%' ),
413
				'stripe'   => array(
414
					'key'                => $this->publishable_key,
415
					'allow_prepaid_card' => apply_filters( 'wc_stripe_allow_prepaid_card', true ) ? 'yes' : 'no',
416
				),
417
				'nonce'    => array(
418
					'payment'                        => wp_create_nonce( 'wc-stripe-payment-request' ),
419
					'shipping'                       => wp_create_nonce( 'wc-stripe-payment-request-shipping' ),
420
					'update_shipping'                => wp_create_nonce( 'wc-stripe-update-shipping-method' ),
421
					'checkout'                       => wp_create_nonce( 'woocommerce-process_checkout' ),
422
					'add_to_cart'                    => wp_create_nonce( 'wc-stripe-add-to-cart' ),
423
					'get_selected_product_data'      => wp_create_nonce( 'wc-stripe-get-selected-product-data' ),
424
					'log_errors'                     => wp_create_nonce( 'wc-stripe-log-errors' ),
425
					'clear_cart'                     => wp_create_nonce( 'wc-stripe-clear-cart' ),
426
				),
427
				'i18n'     => array(
428
					'no_prepaid_card'  => __( 'Sorry, we\'re not accepting prepaid cards at this time.', 'woocommerce-gateway-stripe' ),
429
					/* translators: Do not translate the [option] placeholder */
430
					'unknown_shipping' => __( 'Unknown shipping option "[option]".', 'woocommerce-gateway-stripe' ),
431
				),
432
				'checkout' => array(
433
					'url'            => wc_get_checkout_url(),
434
					'currency_code'  => strtolower( get_woocommerce_currency() ),
435
					'country_code'   => substr( get_option( 'woocommerce_default_country' ), 0, 2 ),
436
					'needs_shipping' => WC()->cart->needs_shipping() ? 'yes' : 'no',
437
				),
438
				'button' => array(
439
					'type'   => $this->get_button_type(),
440
					'theme'  => $this->get_button_theme(),
441
					'height' => $this->get_button_height(),
442
					'locale' => substr( get_locale(), 0, 2 ), // Default format is en_US.
443
				),
444
				'is_product_page' => is_product(),
445
				'product'         => $this->get_product_data(),
446
			)
447
		);
448
449
		wp_enqueue_script( 'wc_stripe_payment_request' );
450
	}
451
452
	/**
453
	 * Display the payment request button.
454
	 *
455
	 * @since 4.0.0
456
	 * @version 4.0.0
457
	 */
458 View Code Duplication
	public function display_payment_request_button_html() {
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...
459
		$gateways = WC()->payment_gateways->get_available_payment_gateways();
460
461
		if ( ! isset( $gateways['stripe'] ) ) {
462
			return;
463
		}
464
465
		if ( ! is_cart() && ! is_checkout() && ! is_product() && ! isset( $_GET['pay_for_order'] ) ) {
466
			return;
467
		}
468
469
		if ( is_product() && apply_filters( 'wc_stripe_hide_payment_request_on_product_page', false ) ) {
470
			return;
471
		}
472
473
		if ( is_product() ) {
474
			global $post;
475
476
			$product = wc_get_product( $post->ID );
477
478
			if ( ! is_object( $product ) || ! in_array( ( WC_Stripe_Helper::is_pre_30() ? $product->product_type : $product->get_type() ), $this->supported_product_types() ) ) {
479
				return;
480
			}
481
482
			// Pre Orders charge upon release not supported.
483
			if ( class_exists( 'WC_Pre_Orders_Order' ) && WC_Pre_Orders_Product::product_is_charged_upon_release( $product ) ) {
484
				WC_Stripe_Logger::log( 'Pre Order charge upon release is not supported. ( Payment Request button disabled )' );
485
				return;
486
			}
487
		} else {
488
			if ( ! $this->allowed_items_in_cart() ) {
489
				WC_Stripe_Logger::log( 'Items in the cart has unsupported product type ( Payment Request button disabled )' );
490
				return;
491
			}
492
		}
493
		?>
494
		<div id="wc-stripe-payment-request-wrapper" style="clear:both;padding-top:1.5em;">
495
			<div id="wc-stripe-payment-request-button">
496
				<!-- A Stripe Element will be inserted here. -->
497
			</div>
498
		</div>
499
		<?php
500
	}
501
502
	/**
503
	 * Display payment request button separator.
504
	 *
505
	 * @since 4.0.0
506
	 * @version 4.0.0
507
	 */
508 View Code Duplication
	public function display_payment_request_button_separator_html() {
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...
509
		$gateways = WC()->payment_gateways->get_available_payment_gateways();
510
511
		if ( ! isset( $gateways['stripe'] ) ) {
512
			return;
513
		}
514
515
		if ( ! is_cart() && ! is_checkout() && ! is_product() && ! isset( $_GET['pay_for_order'] ) ) {
516
			return;
517
		}
518
519
		if ( is_product() && apply_filters( 'wc_stripe_hide_payment_request_on_product_page', false ) ) {
520
			return;
521
		}
522
523
		if ( is_product() ) {
524
			global $post;
525
526
			$product = wc_get_product( $post->ID );
527
528
			if ( ! is_object( $product ) || ! in_array( ( WC_Stripe_Helper::is_pre_30() ? $product->product_type : $product->get_type() ), $this->supported_product_types() ) ) {
529
				return;
530
			}
531
532
			// Pre Orders charge upon release not supported.
533
			if ( class_exists( 'WC_Pre_Orders_Order' ) && WC_Pre_Orders_Product::product_is_charged_upon_release( $product ) ) {
534
				WC_Stripe_Logger::log( 'Pre Order charge upon release is not supported. ( Payment Request button disabled )' );
535
				return;
536
			}
537
		} else {
538
			if ( ! $this->allowed_items_in_cart() ) {
539
				WC_Stripe_Logger::log( 'Items in the cart has unsupported product type ( Payment Request button disabled )' );
540
				return;
541
			}
542
		}
543
		?>
544
		<p id="wc-stripe-payment-request-button-separator" style="margin-top:1.5em;text-align:center;display:none;">- <?php esc_html_e( 'OR', 'woocommerce-gateway-stripe' ); ?> -</p>
545
		<?php
546
	}
547
548
	/**
549
	 * Log errors coming from Payment Request
550
	 *
551
	 * @since 3.1.4
552
	 * @version 4.0.0
553
	 */
554
	public function ajax_log_errors() {
555
		check_ajax_referer( 'wc-stripe-log-errors', 'security' );
556
557
		$errors = wc_clean( stripslashes( $_POST['errors'] ) );
558
559
		WC_Stripe_Logger::log( $errors );
560
561
		exit;
562
	}
563
564
	/**
565
	 * Clears cart.
566
	 *
567
	 * @since 3.1.4
568
	 * @version 4.0.0
569
	 */
570
	public function ajax_clear_cart() {
571
		check_ajax_referer( 'wc-stripe-clear-cart', 'security' );
572
573
		WC()->cart->empty_cart();
574
		exit;
575
	}
576
577
	/**
578
	 * Get cart details.
579
	 */
580
	public function ajax_get_cart_details() {
581
		check_ajax_referer( 'wc-stripe-payment-request', 'security' );
582
583
		if ( ! defined( 'WOOCOMMERCE_CART' ) ) {
584
			define( 'WOOCOMMERCE_CART', true );
585
		}
586
587
		WC()->cart->calculate_totals();
588
589
		$currency = get_woocommerce_currency();
590
591
		// Set mandatory payment details.
592
		$data = array(
593
			'shipping_required' => WC()->cart->needs_shipping(),
594
			'order_data'        => array(
595
				'currency'        => strtolower( $currency ),
596
				'country_code'    => substr( get_option( 'woocommerce_default_country' ), 0, 2 ),
597
			),
598
		);
599
600
		$data['order_data'] += $this->build_display_items();
601
602
		wp_send_json( $data );
603
	}
604
605
	/**
606
	 * Get shipping options.
607
	 *
608
	 * @see WC_Cart::get_shipping_packages().
609
	 * @see WC_Shipping::calculate_shipping().
610
	 * @see WC_Shipping::get_packages().
611
	 */
612
	public function ajax_get_shipping_options() {
613
		check_ajax_referer( 'wc-stripe-payment-request-shipping', 'security' );
614
615
		try {
616
			// Set the shipping package.
617
			$posted = filter_input_array( INPUT_POST, array(
618
				'country'   => FILTER_SANITIZE_STRING,
619
				'state'     => FILTER_SANITIZE_STRING,
620
				'postcode'  => FILTER_SANITIZE_STRING,
621
				'city'      => FILTER_SANITIZE_STRING,
622
				'address'   => FILTER_SANITIZE_STRING,
623
				'address_2' => FILTER_SANITIZE_STRING,
624
			) );
625
626
			$this->calculate_shipping( $posted );
627
628
			// Set the shipping options.
629
			$data     = array();
630
			$packages = WC()->shipping->get_packages();
631
632
			if ( ! empty( $packages ) && WC()->customer->has_calculated_shipping() ) {
633
				foreach ( $packages as $package_key => $package ) {
634
					if ( empty( $package['rates'] ) ) {
635
						throw new Exception( __( 'Unable to find shipping method for address.', 'woocommerce-gateway-stripe' ) );
636
					}
637
638
					foreach ( $package['rates'] as $key => $rate ) {
639
						$data['shipping_options'][] = array(
640
							'id'       => $rate->id,
641
							'label'    => $rate->label,
642
							'detail'   => '',
643
							'amount'   => WC_Stripe_Helper::get_stripe_amount( $rate->cost ),
644
						);
645
					}
646
				}
647
			} else {
648
				throw new Exception( __( 'Unable to find shipping method for address.', 'woocommerce-gateway-stripe' ) );
649
			}
650
651
			if ( isset( $data[0] ) ) {
652
				// Auto select the first shipping method.
653
				WC()->session->set( 'chosen_shipping_methods', array( $data[0]['id'] ) );
654
			}
655
656
			WC()->cart->calculate_totals();
657
658
			$data += $this->build_display_items();
659
			$data['result'] = 'success';
660
661
			wp_send_json( $data );
662
		} catch ( Exception $e ) {
663
			$data += $this->build_display_items();
0 ignored issues
show
Bug introduced by
The variable $data 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...
664
			$data['result'] = 'invalid_shipping_address';
665
666
			wp_send_json( $data );
667
		}
668
	}
669
670
	/**
671
	 * Update shipping method.
672
	 */
673
	public function ajax_update_shipping_method() {
674
		check_ajax_referer( 'wc-stripe-update-shipping-method', 'security' );
675
676
		if ( ! defined( 'WOOCOMMERCE_CART' ) ) {
677
			define( 'WOOCOMMERCE_CART', true );
678
		}
679
680
		$chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );
681
		$shipping_method         = filter_input( INPUT_POST, 'shipping_method', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY );
682
683
		if ( is_array( $shipping_method ) ) {
684
			foreach ( $shipping_method as $i => $value ) {
685
				$chosen_shipping_methods[ $i ] = wc_clean( $value );
686
			}
687
		}
688
689
		WC()->session->set( 'chosen_shipping_methods', $chosen_shipping_methods );
690
691
		WC()->cart->calculate_totals();
692
693
		$data = array();
694
		$data += $this->build_display_items();
695
		$data['result'] = 'success';
696
697
		wp_send_json( $data );
698
	}
699
700
	/**
701
	 * Gets the selected product data.
702
	 *
703
	 * @since 4.0.0
704
	 * @version 4.0.0
705
	 * @return array $data
706
	 */
707
	public function ajax_get_selected_product_data() {
708
		check_ajax_referer( 'wc-stripe-get-selected-product-data', 'security' );
709
710
		$product_id = absint( $_POST['product_id'] );
711
		$qty = ! isset( $_POST['qty'] ) ? 1 : absint( $_POST['qty'] );
712
713
		$product = wc_get_product( $product_id );
714
715
		if ( 'variable' === ( WC_Stripe_Helper::is_pre_30() ? $product->product_type : $product->get_type() ) && isset( $_POST['attributes'] ) ) {
716
			$attributes = array_map( 'wc_clean', $_POST['attributes'] );
717
718 View Code Duplication
			if ( WC_Stripe_Helper::is_pre_30() ) {
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...
719
				$variation_id = $product->get_matching_variation( $attributes );
720
			} else {
721
				$data_store = WC_Data_Store::load( 'product' );
722
				$variation_id = $data_store->find_matching_product_variation( $product, $attributes );
723
			}
724
725
			if ( ! empty( $variation_id ) ) {
726
				$product = wc_get_product( $variation_id );
727
			}
728
		} elseif ( 'simple' === ( WC_Stripe_Helper::is_pre_30() ? $product->product_type : $product->get_type() ) ) {
729
			$product = wc_get_product( $product_id );
730
		}
731
732
		$total = $qty * ( WC_Stripe_Helper::is_pre_30() ? $product->price : $product->get_price() );
733
734
		$quantity_label = 1 < $qty ? ' (x' . $qty . ')' : '';
735
736
		$data  = array();
737
		$items = array();
738
739
		$items[] = array(
740
			'label'  => ( WC_Stripe_Helper::is_pre_30() ? $product->name : $product->get_name() ) . $quantity_label,
741
			'amount' => WC_Stripe_Helper::get_stripe_amount( $total ),
742
		);
743
744 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...
745
			$items[] = array(
746
				'label'   => __( 'Tax', 'woocommerce-gateway-stripe' ),
747
				'amount'  => 0,
748
				'pending' => true,
749
			);
750
		}
751
752 View Code Duplication
		if ( wc_shipping_enabled() && $product->needs_shipping() ) {
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...
753
			$items[] = array(
754
				'label'   => __( 'Shipping', 'woocommerce-gateway-stripe' ),
755
				'amount'  => 0,
756
				'pending' => true,
757
			);
758
759
			$data['shippingOptions']  = array(
760
				'id'     => 'pending',
761
				'label'  => __( 'Pending', 'woocommerce-gateway-stripe' ),
762
				'detail' => '',
763
				'amount' => 0,
764
			);
765
		}
766
767
		$data['displayItems'] = $items;
768
		$data['total'] = array(
769
			'label'   => $this->total_label,
770
			'amount'  => WC_Stripe_Helper::get_stripe_amount( $total ),
771
			'pending' => true,
772
		);
773
774
		$data['requestShipping'] = ( wc_shipping_enabled() && $product->needs_shipping() );
775
		$data['currency']        = strtolower( get_woocommerce_currency() );
776
		$data['country_code']    = substr( get_option( 'woocommerce_default_country' ), 0, 2 );
777
778
		wp_send_json( $data );
779
	}
780
781
	/**
782
	 * Adds the current product to the cart. Used on product detail page.
783
	 *
784
	 * @since 4.0.0
785
	 * @version 4.0.0
786
	 * @return array $data
787
	 */
788
	public function ajax_add_to_cart() {
789
		check_ajax_referer( 'wc-stripe-add-to-cart', 'security' );
790
791
		if ( ! defined( 'WOOCOMMERCE_CART' ) ) {
792
			define( 'WOOCOMMERCE_CART', true );
793
		}
794
795
		WC()->shipping->reset_shipping();
796
797
		$product_id = absint( $_POST['product_id'] );
798
		$qty = ! isset( $_POST['qty'] ) ? 1 : absint( $_POST['qty'] );
799
800
		$product = wc_get_product( $product_id );
801
802
		// First empty the cart to prevent wrong calculation.
803
		WC()->cart->empty_cart();
804
805
		if ( 'variable' === ( WC_Stripe_Helper::is_pre_30() ? $product->product_type : $product->get_type() ) && isset( $_POST['attributes'] ) ) {
806
			$attributes = array_map( 'wc_clean', $_POST['attributes'] );
807
808 View Code Duplication
			if ( WC_Stripe_Helper::is_pre_30() ) {
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...
809
				$variation_id = $product->get_matching_variation( $attributes );
810
			} else {
811
				$data_store = WC_Data_Store::load( 'product' );
812
				$variation_id = $data_store->find_matching_product_variation( $product, $attributes );
813
			}
814
815
			WC()->cart->add_to_cart( $product->get_id(), $qty, $variation_id, $attributes );
816
		}
817
818
		if ( 'simple' === ( WC_Stripe_Helper::is_pre_30() ? $product->product_type : $product->get_type() ) ) {
819
			WC()->cart->add_to_cart( $product->get_id(), $qty );
820
		}
821
822
		WC()->cart->calculate_totals();
823
824
		$data = array();
825
		$data += $this->build_display_items();
826
		$data['result'] = 'success';
827
828
		wp_send_json( $data );
829
	}
830
831
	/**
832
	 * Normalizes the state/county field because in some
833
	 * cases, the state/county field is formatted differently from
834
	 * what WC is expecting and throws an error. An example
835
	 * for Ireland the county dropdown in Chrome shows "Co. Clare" format
836
	 *
837
	 * @since 4.0.0
838
	 * @version 4.0.0
839
	 */
840
	public function normalize_state() {
841
		$billing_country  = ! empty( $_POST['billing_country'] ) ? wc_clean( $_POST['billing_country'] ) : '';
842
		$shipping_country = ! empty( $_POST['shipping_country'] ) ? wc_clean( $_POST['shipping_country'] ) : '';
843
		$billing_state    = ! empty( $_POST['billing_state'] ) ? wc_clean( $_POST['billing_state'] ) : '';
844
		$shipping_state   = ! empty( $_POST['shipping_state'] ) ? wc_clean( $_POST['shipping_state'] ) : '';
845
846 View Code Duplication
		if ( $billing_state && $billing_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...
847
			$valid_states = WC()->countries->get_states( $billing_country );
848
849
			// Valid states found for country.
850
			if ( ! empty( $valid_states ) && is_array( $valid_states ) && sizeof( $valid_states ) > 0 ) {
851
				foreach ( $valid_states as $state_abbr => $state ) {
852
					if ( preg_match( '/' . preg_quote( $state ) . '/i', $billing_state ) ) {
853
						$_POST['billing_state'] = $state_abbr;
854
					}
855
				}
856
			}
857
		}
858
859 View Code Duplication
		if ( $shipping_state && $shipping_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...
860
			$valid_states = WC()->countries->get_states( $shipping_country );
861
862
			// Valid states found for country.
863
			if ( ! empty( $valid_states ) && is_array( $valid_states ) && sizeof( $valid_states ) > 0 ) {
864
				foreach ( $valid_states as $state_abbr => $state ) {
865
					if ( preg_match( '/' . preg_quote( $state ) . '/i', $shipping_state ) ) {
866
						$_POST['shipping_state'] = $state_abbr;
867
					}
868
				}
869
			}
870
		}
871
	}
872
873
	/**
874
	 * Create order. Security is handled by WC.
875
	 *
876
	 * @since 3.1.0
877
	 * @version 4.0.0
878
	 */
879
	public function ajax_create_order() {
880
		if ( WC()->cart->is_empty() ) {
881
			wp_send_json_error( __( 'Empty cart', 'woocommerce-gateway-stripe' ) );
882
		}
883
884
		if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) {
885
			define( 'WOOCOMMERCE_CHECKOUT', true );
886
		}
887
888
		$this->normalize_state();
889
890
		WC()->checkout()->process_checkout();
891
892
		die( 0 );
893
	}
894
895
	/**
896
	 * Calculate and set shipping method.
897
	 *
898
	 * @since 3.1.0
899
	 * @version 4.0.0
900
	 * @param array $address
901
	 */
902
	protected function calculate_shipping( $address = array() ) {
903
		global $states;
904
905
		$country   = $address['country'];
906
		$state     = $address['state'];
907
		$postcode  = $address['postcode'];
908
		$city      = $address['city'];
909
		$address_1 = $address['address'];
910
		$address_2 = $address['address_2'];
911
912
		$country_class = new WC_Countries();
913
		$country_class->load_country_states();
914
915
		/**
916
		 * In some versions of Chrome, state can be a full name. So we need
917
		 * to convert that to abbreviation as WC is expecting that.
918
		 */
919
		if ( 2 < strlen( $state ) ) {
920
			$state = array_search( ucfirst( strtolower( $state ) ), $states[ $country ] );
921
		}
922
923
		WC()->shipping->reset_shipping();
924
925
		if ( $postcode && WC_Validation::is_postcode( $postcode, $country ) ) {
926
			$postcode = wc_format_postcode( $postcode, $country );
927
		}
928
929
		if ( $country ) {
930
			WC()->customer->set_location( $country, $state, $postcode, $city );
931
			WC()->customer->set_shipping_location( $country, $state, $postcode, $city );
932
		} else {
933
			WC_Stripe_Helper::is_pre_30() ? WC()->customer->set_to_base() : WC()->customer->set_billing_address_to_base();
934
			WC_Stripe_Helper::is_pre_30() ? WC()->customer->set_shipping_to_base() : WC()->customer->set_shipping_address_to_base();
935
		}
936
937
		if ( WC_Stripe_Helper::is_pre_30() ) {
938
			WC()->customer->calculated_shipping( true );
939
		} else {
940
			WC()->customer->set_calculated_shipping( true );
941
			WC()->customer->save();
942
		}
943
944
		$packages = array();
945
946
		$packages[0]['contents']                 = WC()->cart->get_cart();
947
		$packages[0]['contents_cost']            = 0;
948
		$packages[0]['applied_coupons']          = WC()->cart->applied_coupons;
949
		$packages[0]['user']['ID']               = get_current_user_id();
950
		$packages[0]['destination']['country']   = $country;
951
		$packages[0]['destination']['state']     = $state;
952
		$packages[0]['destination']['postcode']  = $postcode;
953
		$packages[0]['destination']['city']      = $city;
954
		$packages[0]['destination']['address']   = $address_1;
955
		$packages[0]['destination']['address_2'] = $address_2;
956
957
		foreach ( WC()->cart->get_cart() as $item ) {
958
			if ( $item['data']->needs_shipping() ) {
959
				if ( isset( $item['line_total'] ) ) {
960
					$packages[0]['contents_cost'] += $item['line_total'];
961
				}
962
			}
963
		}
964
965
		$packages = apply_filters( 'woocommerce_cart_shipping_packages', $packages );
966
967
		WC()->shipping->calculate_shipping( $packages );
968
	}
969
970
	/**
971
	 * Builds the shippings methods to pass to Payment Request
972
	 *
973
	 * @since 3.1.0
974
	 * @version 4.0.0
975
	 */
976
	protected function build_shipping_methods( $shipping_methods ) {
977
		if ( empty( $shipping_methods ) ) {
978
			return array();
979
		}
980
981
		$shipping = array();
982
983
		foreach ( $shipping_methods as $method ) {
984
			$shipping[] = array(
985
				'id'         => $method['id'],
986
				'label'      => $method['label'],
987
				'detail'     => '',
988
				'amount'     => WC_Stripe_Helper::get_stripe_amount( $method['amount']['value'] ),
989
			);
990
		}
991
992
		return $shipping;
993
	}
994
995
	/**
996
	 * Builds the line items to pass to Payment Request
997
	 *
998
	 * @since 3.1.0
999
	 * @version 4.0.0
1000
	 */
1001
	protected function build_display_items() {
1002
		if ( ! defined( 'WOOCOMMERCE_CART' ) ) {
1003
			define( 'WOOCOMMERCE_CART', true );
1004
		}
1005
1006
		$items    = array();
1007
		$subtotal = 0;
1008
1009
		// Default show only subtotal instead of itemization.
1010
		if ( ! apply_filters( 'wc_stripe_payment_request_hide_itemization', true ) ) {
1011
			foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
1012
				$amount         = $cart_item['line_subtotal'];
1013
				$subtotal       += $cart_item['line_subtotal'];
1014
				$quantity_label = 1 < $cart_item['quantity'] ? ' (x' . $cart_item['quantity'] . ')' : '';
1015
1016
				$product_name = WC_Stripe_Helper::is_pre_30() ? $cart_item['data']->post->post_title : $cart_item['data']->get_name();
1017
1018
				$item = array(
1019
					'label'  => $product_name . $quantity_label,
1020
					'amount' => WC_Stripe_Helper::get_stripe_amount( $amount ),
1021
				);
1022
1023
				$items[] = $item;
1024
			}
1025
		}
1026
1027
		$discounts   = wc_format_decimal( WC()->cart->get_cart_discount_total(), WC()->cart->dp );
1028
		$tax         = wc_format_decimal( WC()->cart->tax_total + WC()->cart->shipping_tax_total, WC()->cart->dp );
1029
		$shipping    = wc_format_decimal( WC()->cart->shipping_total, WC()->cart->dp );
1030
		$items_total = wc_format_decimal( WC()->cart->cart_contents_total, WC()->cart->dp ) + $discounts;
1031
		$order_total = wc_format_decimal( $items_total + $tax + $shipping - $discounts, WC()->cart->dp );
1032
1033
		if ( wc_tax_enabled() ) {
1034
			$items[] = array(
1035
				'label'  => esc_html( __( 'Tax', 'woocommerce-gateway-stripe' ) ),
1036
				'amount' => WC_Stripe_Helper::get_stripe_amount( $tax ),
1037
			);
1038
		}
1039
1040 View Code Duplication
		if ( WC()->cart->needs_shipping() ) {
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...
1041
			$items[] = array(
1042
				'label'  => esc_html( __( 'Shipping', 'woocommerce-gateway-stripe' ) ),
1043
				'amount' => WC_Stripe_Helper::get_stripe_amount( $shipping ),
1044
			);
1045
		}
1046
1047 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...
1048
			$items[] = array(
1049
				'label'  => esc_html( __( 'Discount', 'woocommerce-gateway-stripe' ) ),
1050
				'amount' => WC_Stripe_Helper::get_stripe_amount( $discounts ),
1051
			);
1052
		}
1053
1054
		if ( version_compare( WC_VERSION, '3.2', '<' ) ) {
1055
			$cart_fees = WC()->cart->fees;
1056
		} else {
1057
			$cart_fees = WC()->cart->get_fees();
1058
		}
1059
1060
		// Include fees and taxes as display items.
1061
		foreach ( $cart_fees as $key => $fee ) {
1062
			$items[] = array(
1063
				'label'  => $fee->name,
1064
				'amount' => WC_Stripe_Helper::get_stripe_amount( $fee->amount ),
1065
			);
1066
		}
1067
1068
		return array(
1069
			'displayItems' => $items,
1070
			'total'      => array(
1071
				'label'   => $this->total_label,
1072
				'amount'  => max( 0, apply_filters( 'woocommerce_stripe_calculated_total', WC_Stripe_Helper::get_stripe_amount( $order_total ), $order_total, WC()->cart ) ),
1073
				'pending' => false,
1074
			),
1075
		);
1076
	}
1077
}
1078
1079
new WC_Stripe_Payment_Request();
1080