Completed
Push — master ( d2c6b4...e94018 )
by Roy
02:15
created

WC_Stripe_Payment_Request::set_session()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 5
nc 3
nop 0
dl 0
loc 9
rs 9.6666
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
		add_action( 'woocommerce_init', array( $this, 'init' ), 100 );
92
	}
93
94
	/**
95
	 * Initialize hooks.
96
	 *
97
	 * @since 4.0.0
98
	 * @version 4.0.0
99
	 */
100
	public function init() {
101
		add_action( 'wp_enqueue_scripts', array( $this, 'scripts' ) );
102
103
		/*
104
		 * In order to display the Payment Request button in the correct position,
105
		 * a new hook was added to WooCommerce 3.0. In older versions of WooCommerce,
106
		 * CSS is used to position the button.
107
		 */
108
		if ( WC_Stripe_Helper::is_pre_30() ) {
109
			add_action( 'woocommerce_after_add_to_cart_button', array( $this, 'display_payment_request_button_html' ), 1 );
110
			add_action( 'woocommerce_after_add_to_cart_button', array( $this, 'display_payment_request_button_separator_html' ), 2 );
111
		} else {
112
			add_action( 'woocommerce_after_add_to_cart_quantity', array( $this, 'display_payment_request_button_html' ), 1 );
113
			add_action( 'woocommerce_after_add_to_cart_quantity', array( $this, 'display_payment_request_button_separator_html' ), 2 );
114
		}
115
116
		add_action( 'woocommerce_proceed_to_checkout', array( $this, 'display_payment_request_button_html' ), 1 );
117
		add_action( 'woocommerce_proceed_to_checkout', array( $this, 'display_payment_request_button_separator_html' ), 2 );
118
119
		add_action( 'woocommerce_checkout_before_customer_details', array( $this, 'display_payment_request_button_html' ), 1 );
120
		add_action( 'woocommerce_checkout_before_customer_details', array( $this, 'display_payment_request_button_separator_html' ), 2 );
121
122
		add_action( 'wc_ajax_wc_stripe_get_cart_details', array( $this, 'ajax_get_cart_details' ) );
123
		add_action( 'wc_ajax_wc_stripe_get_shipping_options', array( $this, 'ajax_get_shipping_options' ) );
124
		add_action( 'wc_ajax_wc_stripe_update_shipping_method', array( $this, 'ajax_update_shipping_method' ) );
125
		add_action( 'wc_ajax_wc_stripe_create_order', array( $this, 'ajax_create_order' ) );
126
		add_action( 'wc_ajax_wc_stripe_add_to_cart', array( $this, 'ajax_add_to_cart' ) );
127
		add_action( 'wc_ajax_wc_stripe_get_selected_product_data', array( $this, 'ajax_get_selected_product_data' ) );
128
		add_action( 'wc_ajax_wc_stripe_clear_cart', array( $this, 'ajax_clear_cart' ) );
129
		add_action( 'wc_ajax_wc_stripe_log_errors', array( $this, 'ajax_log_errors' ) );
130
131
		add_filter( 'woocommerce_gateway_title', array( $this, 'filter_gateway_title' ), 10, 2 );
132
		add_filter( 'woocommerce_validate_postcode', array( $this, 'postal_code_validation' ), 10, 3 );
133
134
		add_action( 'woocommerce_checkout_order_processed', array( $this, 'add_order_meta' ), 10, 3 );
135
	}
136
137
	/**
138
	 * Gets the button type.
139
	 *
140
	 * @since 4.0.0
141
	 * @version 4.0.0
142
	 * @return string
143
	 */
144
	public function get_button_type() {
145
		return isset( $this->stripe_settings['payment_request_button_type'] ) ? $this->stripe_settings['payment_request_button_type'] : 'default';
146
	}
147
148
	/**
149
	 * Gets the button theme.
150
	 *
151
	 * @since 4.0.0
152
	 * @version 4.0.0
153
	 * @return string
154
	 */
155
	public function get_button_theme() {
156
		return isset( $this->stripe_settings['payment_request_button_theme'] ) ? $this->stripe_settings['payment_request_button_theme'] : 'dark';
157
	}
158
159
	/**
160
	 * Gets the button height.
161
	 *
162
	 * @since 4.0.0
163
	 * @version 4.0.0
164
	 * @return string
165
	 */
166
	public function get_button_height() {
167
		return isset( $this->stripe_settings['payment_request_button_height'] ) ? str_replace( 'px', '', $this->stripe_settings['payment_request_button_height'] ) : '64';
168
	}
169
170
	/**
171
	 * Gets the product data for the currently viewed page
172
	 *
173
	 * @since 4.0.0
174
	 * @version 4.0.0
175
	 */
176
	public function get_product_data() {
177
		if ( ! is_product() ) {
178
			return false;
179
		}
180
181
		global $post;
182
183
		$product = wc_get_product( $post->ID );
184
185
		$data  = array();
186
		$items = array();
187
188
		$items[] = array(
189
			'label'  => WC_Stripe_Helper::is_pre_30() ? $product->name : $product->get_name(),
190
			'amount' => WC_Stripe_Helper::get_stripe_amount( WC_Stripe_Helper::is_pre_30() ? $product->price : $product->get_price() ),
191
		);
192
193 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...
194
			$items[] = array(
195
				'label'   => __( 'Tax', 'woocommerce-gateway-stripe' ),
196
				'amount'  => 0,
197
				'pending' => true,
198
			);
199
		}
200
201 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...
202
			$items[] = array(
203
				'label'   => __( 'Shipping', 'woocommerce-gateway-stripe' ),
204
				'amount'  => 0,
205
				'pending' => true,
206
			);
207
208
			$data['shippingOptions']  = array(
209
				'id'     => 'pending',
210
				'label'  => __( 'Pending', 'woocommerce-gateway-stripe' ),
211
				'detail' => '',
212
				'amount' => 0,
213
			);
214
		}
215
216
		$data['displayItems'] = $items;
217
		$data['total'] = array(
218
			'label'   => apply_filters( 'wc_stripe_payment_request_total_label', $this->total_label ),
219
			'amount'  => WC_Stripe_Helper::get_stripe_amount( WC_Stripe_Helper::is_pre_30() ? $product->price : $product->get_price() ),
220
			'pending' => true,
221
		);
222
223
		$data['requestShipping'] = ( wc_shipping_enabled() && $product->needs_shipping() );
224
		$data['currency']        = strtolower( get_woocommerce_currency() );
225
		$data['country_code']    = substr( get_option( 'woocommerce_default_country' ), 0, 2 );
226
227
		return apply_filters( 'wc_stripe_payment_request_product_data', $data, $product );
228
	}
229
230
	/**
231
	 * Filters the gateway title to reflect Payment Request type
232
	 *
233
	 */
234
	public function filter_gateway_title( $title, $id ) {
235
		global $post;
236
237
		if ( ! is_object( $post ) ) {
238
			return $title;
239
		}
240
241
		if ( WC_Stripe_Helper::is_pre_30() ) {
242
			$method_title = get_post_meta( $post->ID, '_payment_method_title', true );
243
		} else {
244
			$order        = wc_get_order( $post->ID );
245
			$method_title = is_object( $order ) ? $order->get_payment_method_title() : '';
246
		}
247
248
		if ( 'stripe' === $id && ! empty( $method_title ) && 'Apple Pay (Stripe)' === $method_title ) {
249
			return $method_title;
250
		}
251
252
		if ( 'stripe' === $id && ! empty( $method_title ) && 'Chrome Payment Request (Stripe)' === $method_title ) {
253
			return $method_title;
254
		}
255
256
		return $title;
257
	}
258
259
	/**
260
	 * Removes postal code validation from WC.
261
	 *
262
	 * @since 3.1.4
263
	 * @version 4.0.0
264
	 */
265
	public function postal_code_validation( $valid, $postcode, $country ) {
266
		$gateways = WC()->payment_gateways->get_available_payment_gateways();
267
268
		if ( ! isset( $gateways['stripe'] ) ) {
269
			return $valid;
270
		}
271
272
		$payment_request_type = wc_clean( $_POST['payment_request_type'] );
273
274
		if ( 'apple_pay' !== $payment_request_type ) {
275
			return $valid;
276
		}
277
278
		/**
279
		 * Currently Apple Pay truncates postal codes from UK and Canada to first 3 characters
280
		 * when passing it back from the shippingcontactselected object. This causes WC to invalidate
281
		 * the order and not let it go through. The remedy for now is just to remove this validation.
282
		 * Note that this only works with shipping providers that don't validate full postal codes.
283
		 */
284
		if ( 'GB' === $country || 'CA' === $country ) {
285
			return true;
286
		}
287
288
		return $valid;
289
	}
290
291
	/**
292
	 * Add needed order meta
293
	 *
294
	 * @since 4.0.0
295
	 * @version 4.0.0
296
	 * @param int $order_id
297
	 * @param array $posted_data The posted data from checkout form.
298
	 * @param object $order
299
	 */
300
	public function add_order_meta( $order_id, $posted_data, $order ) {
301
		if ( empty( $_POST['payment_request_type'] ) ) {
302
			return;
303
		}
304
305
		$payment_request_type = wc_clean( $_POST['payment_request_type'] );
306
307 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...
308
			if ( WC_Stripe_Helper::is_pre_30() ) {
309
				update_post_meta( $order_id, '_payment_method_title', 'Apple Pay (Stripe)' );
310
			} else {
311
				$order->set_payment_method_title( 'Apple Pay (Stripe)' );
312
				$order->save();
313
			}
314
		}
315
316 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...
317
			if ( WC_Stripe_Helper::is_pre_30() ) {
318
				update_post_meta( $order_id, '_payment_method_title', 'Chrome Payment Request (Stripe)' );
319
			} else {
320
				$order->set_payment_method_title( 'Chrome Payment Request (Stripe)' );
321
				$order->save();
322
			}
323
		}
324
	}
325
326
	/**
327
	 * Checks to make sure product type is supported.
328
	 *
329
	 * @since 3.1.0
330
	 * @version 4.0.0
331
	 * @return array
332
	 */
333
	public function supported_product_types() {
334
		return apply_filters( 'wc_stripe_payment_request_supported_types', array(
335
			'simple',
336
			'variable',
337
			'variation',
338
		) );
339
	}
340
341
	/**
342
	 * Checks the cart to see if all items are allowed to used.
343
	 *
344
	 * @since 3.1.4
345
	 * @version 4.0.0
346
	 * @return bool
347
	 */
348
	public function allowed_items_in_cart() {
349
		foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
350
			$_product = apply_filters( 'woocommerce_cart_item_product', $cart_item['data'], $cart_item, $cart_item_key );
351
352
			if ( ! in_array( ( WC_Stripe_Helper::is_pre_30() ? $_product->product_type : $_product->get_type() ), $this->supported_product_types() ) ) {
353
				return false;
354
			}
355
356
			// Pre Orders compatbility where we don't support charge upon release.
357
			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() ) ) {
358
				return false;
359
			}
360
		}
361
362
		return true;
363
	}
364
365
	/**
366
	 * Load public scripts and styles.
367
	 *
368
	 * @since 3.1.0
369
	 * @version 4.0.0
370
	 */
371
	public function scripts() {
372
		if ( ! is_product() && ! is_cart() && ! is_checkout() && ! isset( $_GET['pay_for_order'] ) ) {
373
			return;
374
		}
375
376
		if ( is_product() ) {
377
			global $post;
378
379
			$product = wc_get_product( $post->ID );
380
381
			if ( ! is_object( $product ) || ! in_array( ( WC_Stripe_Helper::is_pre_30() ? $product->product_type : $product->get_type() ), $this->supported_product_types() ) ) {
382
				return;
383
			}
384
385
			if ( apply_filters( 'wc_stripe_hide_payment_request_on_product_page', false ) ) {
386
				return;
387
			}
388
		}
389
390
		$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
391
392
		wp_register_script( 'stripe', 'https://js.stripe.com/v3/', '', '3.0', true );
393
		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 );
394
395
		wp_localize_script(
396
			'wc_stripe_payment_request',
397
			'wc_stripe_payment_request_params',
398
			array(
399
				'ajax_url' => WC_AJAX::get_endpoint( '%%endpoint%%' ),
400
				'stripe'   => array(
401
					'key'                => $this->publishable_key,
402
					'allow_prepaid_card' => apply_filters( 'wc_stripe_allow_prepaid_card', true ) ? 'yes' : 'no',
403
				),
404
				'nonce'    => array(
405
					'payment'                        => wp_create_nonce( 'wc-stripe-payment-request' ),
406
					'shipping'                       => wp_create_nonce( 'wc-stripe-payment-request-shipping' ),
407
					'update_shipping'                => wp_create_nonce( 'wc-stripe-update-shipping-method' ),
408
					'checkout'                       => wp_create_nonce( 'woocommerce-process_checkout' ),
409
					'add_to_cart'                    => wp_create_nonce( 'wc-stripe-add-to-cart' ),
410
					'get_selected_product_data'      => wp_create_nonce( 'wc-stripe-get-selected-product-data' ),
411
					'log_errors'                     => wp_create_nonce( 'wc-stripe-log-errors' ),
412
					'clear_cart'                     => wp_create_nonce( 'wc-stripe-clear-cart' ),
413
				),
414
				'i18n'     => array(
415
					'no_prepaid_card'  => __( 'Sorry, we\'re not accepting prepaid cards at this time.', 'woocommerce-gateway-stripe' ),
416
					/* translators: Do not translate the [option] placeholder */
417
					'unknown_shipping' => __( 'Unknown shipping option "[option]".', 'woocommerce-gateway-stripe' ),
418
				),
419
				'checkout' => array(
420
					'url'            => wc_get_checkout_url(),
421
					'currency_code'  => strtolower( get_woocommerce_currency() ),
422
					'country_code'   => substr( get_option( 'woocommerce_default_country' ), 0, 2 ),
423
					'needs_shipping' => WC()->cart->needs_shipping() ? 'yes' : 'no',
424
				),
425
				'button' => array(
426
					'type'   => $this->get_button_type(),
427
					'theme'  => $this->get_button_theme(),
428
					'height' => $this->get_button_height(),
429
					'locale' => substr( get_locale(), 0, 2 ), // Default format is en_US.
430
				),
431
				'is_product_page' => is_product(),
432
				'product'         => $this->get_product_data(),
433
			)
434
		);
435
436
		wp_enqueue_script( 'wc_stripe_payment_request' );
437
	}
438
439
	/**
440
	 * Display the payment request button.
441
	 *
442
	 * @since 4.0.0
443
	 * @version 4.0.0
444
	 */
445 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...
446
		$gateways = WC()->payment_gateways->get_available_payment_gateways();
447
448
		if ( ! isset( $gateways['stripe'] ) ) {
449
			return;
450
		}
451
452
		if ( ! is_cart() && ! is_checkout() && ! is_product() && ! isset( $_GET['pay_for_order'] ) ) {
453
			return;
454
		}
455
456
		if ( is_product() && apply_filters( 'wc_stripe_hide_payment_request_on_product_page', false ) ) {
457
			return;
458
		}
459
460
		if ( is_checkout() && ! apply_filters( 'wc_stripe_show_payment_request_on_checkout', false ) ) {
461
			return;
462
		}
463
464
		if ( is_product() ) {
465
			global $post;
466
467
			$product = wc_get_product( $post->ID );
468
469
			if ( ! is_object( $product ) || ! in_array( ( WC_Stripe_Helper::is_pre_30() ? $product->product_type : $product->get_type() ), $this->supported_product_types() ) ) {
470
				return;
471
			}
472
473
			// Pre Orders charge upon release not supported.
474
			if ( class_exists( 'WC_Pre_Orders_Order' ) && WC_Pre_Orders_Product::product_is_charged_upon_release( $product ) ) {
475
				WC_Stripe_Logger::log( 'Pre Order charge upon release is not supported. ( Payment Request button disabled )' );
476
				return;
477
			}
478
		} else {
479
			if ( ! $this->allowed_items_in_cart() ) {
480
				WC_Stripe_Logger::log( 'Items in the cart has unsupported product type ( Payment Request button disabled )' );
481
				return;
482
			}
483
		}
484
		?>
485
		<div id="wc-stripe-payment-request-wrapper" style="clear:both;padding-top:1.5em;">
486
			<div id="wc-stripe-payment-request-button">
487
				<!-- A Stripe Element will be inserted here. -->
488
			</div>
489
		</div>
490
		<?php
491
	}
492
493
	/**
494
	 * Display payment request button separator.
495
	 *
496
	 * @since 4.0.0
497
	 * @version 4.0.0
498
	 */
499 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...
500
		$gateways = WC()->payment_gateways->get_available_payment_gateways();
501
502
		if ( ! isset( $gateways['stripe'] ) ) {
503
			return;
504
		}
505
506
		if ( ! is_cart() && ! is_checkout() && ! is_product() && ! isset( $_GET['pay_for_order'] ) ) {
507
			return;
508
		}
509
510
		if ( is_product() && apply_filters( 'wc_stripe_hide_payment_request_on_product_page', false ) ) {
511
			return;
512
		}
513
514
		if ( is_checkout() && ! apply_filters( 'wc_stripe_show_payment_request_on_checkout', false ) ) {
515
			return;
516
		}
517
518
		if ( is_product() ) {
519
			global $post;
520
521
			$product = wc_get_product( $post->ID );
522
523
			if ( ! is_object( $product ) || ! in_array( ( WC_Stripe_Helper::is_pre_30() ? $product->product_type : $product->get_type() ), $this->supported_product_types() ) ) {
524
				return;
525
			}
526
527
			// Pre Orders charge upon release not supported.
528
			if ( class_exists( 'WC_Pre_Orders_Order' ) && WC_Pre_Orders_Product::product_is_charged_upon_release( $product ) ) {
529
				WC_Stripe_Logger::log( 'Pre Order charge upon release is not supported. ( Payment Request button disabled )' );
530
				return;
531
			}
532
		} else {
533
			if ( ! $this->allowed_items_in_cart() ) {
534
				WC_Stripe_Logger::log( 'Items in the cart has unsupported product type ( Payment Request button disabled )' );
535
				return;
536
			}
537
		}
538
		?>
539
		<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>
540
		<?php
541
	}
542
543
	/**
544
	 * Log errors coming from Payment Request
545
	 *
546
	 * @since 3.1.4
547
	 * @version 4.0.0
548
	 */
549
	public function ajax_log_errors() {
550
		check_ajax_referer( 'wc-stripe-log-errors', 'security' );
551
552
		$errors = wc_clean( stripslashes( $_POST['errors'] ) );
553
554
		WC_Stripe_Logger::log( $errors );
555
556
		exit;
557
	}
558
559
	/**
560
	 * Clears cart.
561
	 *
562
	 * @since 3.1.4
563
	 * @version 4.0.0
564
	 */
565
	public function ajax_clear_cart() {
566
		check_ajax_referer( 'wc-stripe-clear-cart', 'security' );
567
568
		WC()->cart->empty_cart();
569
		exit;
570
	}
571
572
	/**
573
	 * Get cart details.
574
	 */
575
	public function ajax_get_cart_details() {
576
		check_ajax_referer( 'wc-stripe-payment-request', 'security' );
577
578
		if ( ! defined( 'WOOCOMMERCE_CART' ) ) {
579
			define( 'WOOCOMMERCE_CART', true );
580
		}
581
582
		WC()->cart->calculate_totals();
583
584
		$currency = get_woocommerce_currency();
585
586
		// Set mandatory payment details.
587
		$data = array(
588
			'shipping_required' => WC()->cart->needs_shipping(),
589
			'order_data'        => array(
590
				'currency'        => strtolower( $currency ),
591
				'country_code'    => substr( get_option( 'woocommerce_default_country' ), 0, 2 ),
592
			),
593
		);
594
595
		$data['order_data'] += $this->build_display_items();
596
597
		wp_send_json( $data );
598
	}
599
600
	/**
601
	 * Get shipping options.
602
	 *
603
	 * @see WC_Cart::get_shipping_packages().
604
	 * @see WC_Shipping::calculate_shipping().
605
	 * @see WC_Shipping::get_packages().
606
	 */
607
	public function ajax_get_shipping_options() {
608
		check_ajax_referer( 'wc-stripe-payment-request-shipping', 'security' );
609
610
		try {
611
			// Set the shipping package.
612
			$posted = filter_input_array( INPUT_POST, array(
613
				'country'   => FILTER_SANITIZE_STRING,
614
				'state'     => FILTER_SANITIZE_STRING,
615
				'postcode'  => FILTER_SANITIZE_STRING,
616
				'city'      => FILTER_SANITIZE_STRING,
617
				'address'   => FILTER_SANITIZE_STRING,
618
				'address_2' => FILTER_SANITIZE_STRING,
619
			) );
620
621
			$this->calculate_shipping( $posted );
622
623
			// Set the shipping options.
624
			$data     = array();
625
			$packages = WC()->shipping->get_packages();
626
627
			if ( ! empty( $packages ) && WC()->customer->has_calculated_shipping() ) {
628
				foreach ( $packages as $package_key => $package ) {
629
					if ( empty( $package['rates'] ) ) {
630
						throw new Exception( __( 'Unable to find shipping method for address.', 'woocommerce-gateway-stripe' ) );
631
					}
632
633
					foreach ( $package['rates'] as $key => $rate ) {
634
						$data['shipping_options'][] = array(
635
							'id'       => $rate->id,
636
							'label'    => $rate->label,
637
							'detail'   => '',
638
							'amount'   => WC_Stripe_Helper::get_stripe_amount( $rate->cost ),
639
						);
640
					}
641
				}
642
			} else {
643
				throw new Exception( __( 'Unable to find shipping method for address.', 'woocommerce-gateway-stripe' ) );
644
			}
645
646
			if ( isset( $data[0] ) ) {
647
				// Auto select the first shipping method.
648
				WC()->session->set( 'chosen_shipping_methods', array( $data[0]['id'] ) );
649
			}
650
651
			WC()->cart->calculate_totals();
652
653
			$data += $this->build_display_items();
654
			$data['result'] = 'success';
655
656
			wp_send_json( $data );
657
		} catch ( Exception $e ) {
658
			$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...
659
			$data['result'] = 'invalid_shipping_address';
660
661
			wp_send_json( $data );
662
		}
663
	}
664
665
	/**
666
	 * Update shipping method.
667
	 */
668
	public function ajax_update_shipping_method() {
669
		check_ajax_referer( 'wc-stripe-update-shipping-method', 'security' );
670
671
		if ( ! defined( 'WOOCOMMERCE_CART' ) ) {
672
			define( 'WOOCOMMERCE_CART', true );
673
		}
674
675
		$chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );
676
		$shipping_method         = filter_input( INPUT_POST, 'shipping_method', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY );
677
678
		if ( is_array( $shipping_method ) ) {
679
			foreach ( $shipping_method as $i => $value ) {
680
				$chosen_shipping_methods[ $i ] = wc_clean( $value );
681
			}
682
		}
683
684
		WC()->session->set( 'chosen_shipping_methods', $chosen_shipping_methods );
685
686
		WC()->cart->calculate_totals();
687
688
		$data = array();
689
		$data += $this->build_display_items();
690
		$data['result'] = 'success';
691
692
		wp_send_json( $data );
693
	}
694
695
	/**
696
	 * Gets the selected product data.
697
	 *
698
	 * @since 4.0.0
699
	 * @version 4.0.0
700
	 * @return array $data
701
	 */
702
	public function ajax_get_selected_product_data() {
703
		check_ajax_referer( 'wc-stripe-get-selected-product-data', 'security' );
704
705
		$product_id = absint( $_POST['product_id'] );
706
		$qty = ! isset( $_POST['qty'] ) ? 1 : absint( $_POST['qty'] );
707
708
		$product = wc_get_product( $product_id );
709
710
		if ( 'variable' === ( WC_Stripe_Helper::is_pre_30() ? $product->product_type : $product->get_type() ) && isset( $_POST['attributes'] ) ) {
711
			$attributes = array_map( 'wc_clean', $_POST['attributes'] );
712
713 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...
714
				$variation_id = $product->get_matching_variation( $attributes );
715
			} else {
716
				$data_store = WC_Data_Store::load( 'product' );
717
				$variation_id = $data_store->find_matching_product_variation( $product, $attributes );
718
			}
719
720
			if ( ! empty( $variation_id ) ) {
721
				$product = wc_get_product( $variation_id );
722
			}
723
		} elseif ( 'simple' === ( WC_Stripe_Helper::is_pre_30() ? $product->product_type : $product->get_type() ) ) {
724
			$product = wc_get_product( $product_id );
725
		}
726
727
		$total = $qty * ( WC_Stripe_Helper::is_pre_30() ? $product->price : $product->get_price() );
728
729
		$quantity_label = 1 < $qty ? ' (x' . $qty . ')' : '';
730
731
		$data  = array();
732
		$items = array();
733
734
		$items[] = array(
735
			'label'  => ( WC_Stripe_Helper::is_pre_30() ? $product->name : $product->get_name() ) . $quantity_label,
736
			'amount' => WC_Stripe_Helper::get_stripe_amount( $total ),
737
		);
738
739 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...
740
			$items[] = array(
741
				'label'   => __( 'Tax', 'woocommerce-gateway-stripe' ),
742
				'amount'  => 0,
743
				'pending' => true,
744
			);
745
		}
746
747 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...
748
			$items[] = array(
749
				'label'   => __( 'Shipping', 'woocommerce-gateway-stripe' ),
750
				'amount'  => 0,
751
				'pending' => true,
752
			);
753
754
			$data['shippingOptions']  = array(
755
				'id'     => 'pending',
756
				'label'  => __( 'Pending', 'woocommerce-gateway-stripe' ),
757
				'detail' => '',
758
				'amount' => 0,
759
			);
760
		}
761
762
		$data['displayItems'] = $items;
763
		$data['total'] = array(
764
			'label'   => $this->total_label,
765
			'amount'  => WC_Stripe_Helper::get_stripe_amount( $total ),
766
			'pending' => true,
767
		);
768
769
		$data['requestShipping'] = ( wc_shipping_enabled() && $product->needs_shipping() );
770
		$data['currency']        = strtolower( get_woocommerce_currency() );
771
		$data['country_code']    = substr( get_option( 'woocommerce_default_country' ), 0, 2 );
772
773
		wp_send_json( $data );
774
	}
775
776
	/**
777
	 * Adds the current product to the cart. Used on product detail page.
778
	 *
779
	 * @since 4.0.0
780
	 * @version 4.0.0
781
	 * @return array $data
782
	 */
783
	public function ajax_add_to_cart() {
784
		check_ajax_referer( 'wc-stripe-add-to-cart', 'security' );
785
786
		if ( ! defined( 'WOOCOMMERCE_CART' ) ) {
787
			define( 'WOOCOMMERCE_CART', true );
788
		}
789
790
		WC()->shipping->reset_shipping();
791
792
		$product_id = absint( $_POST['product_id'] );
793
		$qty = ! isset( $_POST['qty'] ) ? 1 : absint( $_POST['qty'] );
794
795
		$product = wc_get_product( $product_id );
796
797
		// First empty the cart to prevent wrong calculation.
798
		WC()->cart->empty_cart();
799
800
		if ( 'variable' === ( WC_Stripe_Helper::is_pre_30() ? $product->product_type : $product->get_type() ) && isset( $_POST['attributes'] ) ) {
801
			$attributes = array_map( 'wc_clean', $_POST['attributes'] );
802
803 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...
804
				$variation_id = $product->get_matching_variation( $attributes );
805
			} else {
806
				$data_store = WC_Data_Store::load( 'product' );
807
				$variation_id = $data_store->find_matching_product_variation( $product, $attributes );
808
			}
809
810
			WC()->cart->add_to_cart( $product->get_id(), $qty, $variation_id, $attributes );
811
		}
812
813
		if ( 'simple' === ( WC_Stripe_Helper::is_pre_30() ? $product->product_type : $product->get_type() ) ) {
814
			WC()->cart->add_to_cart( $product->get_id(), $qty );
815
		}
816
817
		WC()->cart->calculate_totals();
818
819
		$data = array();
820
		$data += $this->build_display_items();
821
		$data['result'] = 'success';
822
823
		wp_send_json( $data );
824
	}
825
826
	/**
827
	 * Normalizes the state/county field because in some
828
	 * cases, the state/county field is formatted differently from
829
	 * what WC is expecting and throws an error. An example
830
	 * for Ireland the county dropdown in Chrome shows "Co. Clare" format
831
	 *
832
	 * @since 4.0.0
833
	 * @version 4.0.0
834
	 */
835
	public function normalize_state() {
836
		$billing_country  = ! empty( $_POST['billing_country'] ) ? wc_clean( $_POST['billing_country'] ) : '';
837
		$shipping_country = ! empty( $_POST['shipping_country'] ) ? wc_clean( $_POST['shipping_country'] ) : '';
838
		$billing_state    = ! empty( $_POST['billing_state'] ) ? wc_clean( $_POST['billing_state'] ) : '';
839
		$shipping_state   = ! empty( $_POST['shipping_state'] ) ? wc_clean( $_POST['shipping_state'] ) : '';
840
841 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...
842
			$valid_states = WC()->countries->get_states( $billing_country );
843
844
			// Valid states found for country.
845
			if ( ! empty( $valid_states ) && is_array( $valid_states ) && sizeof( $valid_states ) > 0 ) {
846
				foreach ( $valid_states as $state_abbr => $state ) {
847
					if ( preg_match( '/' . preg_quote( $state ) . '/i', $billing_state ) ) {
848
						$_POST['billing_state'] = $state_abbr;
849
					}
850
				}
851
			}
852
		}
853
854 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...
855
			$valid_states = WC()->countries->get_states( $shipping_country );
856
857
			// Valid states found for country.
858
			if ( ! empty( $valid_states ) && is_array( $valid_states ) && sizeof( $valid_states ) > 0 ) {
859
				foreach ( $valid_states as $state_abbr => $state ) {
860
					if ( preg_match( '/' . preg_quote( $state ) . '/i', $shipping_state ) ) {
861
						$_POST['shipping_state'] = $state_abbr;
862
					}
863
				}
864
			}
865
		}
866
	}
867
868
	/**
869
	 * Create order. Security is handled by WC.
870
	 *
871
	 * @since 3.1.0
872
	 * @version 4.0.0
873
	 */
874
	public function ajax_create_order() {
875
		if ( WC()->cart->is_empty() ) {
876
			wp_send_json_error( __( 'Empty cart', 'woocommerce-gateway-stripe' ) );
877
		}
878
879
		if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) {
880
			define( 'WOOCOMMERCE_CHECKOUT', true );
881
		}
882
883
		$this->normalize_state();
884
885
		WC()->checkout()->process_checkout();
886
887
		die( 0 );
888
	}
889
890
	/**
891
	 * Calculate and set shipping method.
892
	 *
893
	 * @since 3.1.0
894
	 * @version 4.0.0
895
	 * @param array $address
896
	 */
897
	protected function calculate_shipping( $address = array() ) {
898
		global $states;
899
900
		$country   = $address['country'];
901
		$state     = $address['state'];
902
		$postcode  = $address['postcode'];
903
		$city      = $address['city'];
904
		$address_1 = $address['address'];
905
		$address_2 = $address['address_2'];
906
907
		$country_class = new WC_Countries();
908
		$country_class->load_country_states();
909
910
		/**
911
		 * In some versions of Chrome, state can be a full name. So we need
912
		 * to convert that to abbreviation as WC is expecting that.
913
		 */
914
		if ( 2 < strlen( $state ) ) {
915
			$state = array_search( ucfirst( strtolower( $state ) ), $states[ $country ] );
916
		}
917
918
		WC()->shipping->reset_shipping();
919
920
		if ( $postcode && WC_Validation::is_postcode( $postcode, $country ) ) {
921
			$postcode = wc_format_postcode( $postcode, $country );
922
		}
923
924
		if ( $country ) {
925
			WC()->customer->set_location( $country, $state, $postcode, $city );
926
			WC()->customer->set_shipping_location( $country, $state, $postcode, $city );
927
		} else {
928
			WC_Stripe_Helper::is_pre_30() ? WC()->customer->set_to_base() : WC()->customer->set_billing_address_to_base();
929
			WC_Stripe_Helper::is_pre_30() ? WC()->customer->set_shipping_to_base() : WC()->customer->set_shipping_address_to_base();
930
		}
931
932
		if ( WC_Stripe_Helper::is_pre_30() ) {
933
			WC()->customer->calculated_shipping( true );
934
		} else {
935
			WC()->customer->set_calculated_shipping( true );
936
			WC()->customer->save();
937
		}
938
939
		$packages = array();
940
941
		$packages[0]['contents']                 = WC()->cart->get_cart();
942
		$packages[0]['contents_cost']            = 0;
943
		$packages[0]['applied_coupons']          = WC()->cart->applied_coupons;
944
		$packages[0]['user']['ID']               = get_current_user_id();
945
		$packages[0]['destination']['country']   = $country;
946
		$packages[0]['destination']['state']     = $state;
947
		$packages[0]['destination']['postcode']  = $postcode;
948
		$packages[0]['destination']['city']      = $city;
949
		$packages[0]['destination']['address']   = $address_1;
950
		$packages[0]['destination']['address_2'] = $address_2;
951
952
		foreach ( WC()->cart->get_cart() as $item ) {
953
			if ( $item['data']->needs_shipping() ) {
954
				if ( isset( $item['line_total'] ) ) {
955
					$packages[0]['contents_cost'] += $item['line_total'];
956
				}
957
			}
958
		}
959
960
		$packages = apply_filters( 'woocommerce_cart_shipping_packages', $packages );
961
962
		WC()->shipping->calculate_shipping( $packages );
963
	}
964
965
	/**
966
	 * Builds the shippings methods to pass to Payment Request
967
	 *
968
	 * @since 3.1.0
969
	 * @version 4.0.0
970
	 */
971
	protected function build_shipping_methods( $shipping_methods ) {
972
		if ( empty( $shipping_methods ) ) {
973
			return array();
974
		}
975
976
		$shipping = array();
977
978
		foreach ( $shipping_methods as $method ) {
979
			$shipping[] = array(
980
				'id'         => $method['id'],
981
				'label'      => $method['label'],
982
				'detail'     => '',
983
				'amount'     => WC_Stripe_Helper::get_stripe_amount( $method['amount']['value'] ),
984
			);
985
		}
986
987
		return $shipping;
988
	}
989
990
	/**
991
	 * Builds the line items to pass to Payment Request
992
	 *
993
	 * @since 3.1.0
994
	 * @version 4.0.0
995
	 */
996
	protected function build_display_items() {
997
		if ( ! defined( 'WOOCOMMERCE_CART' ) ) {
998
			define( 'WOOCOMMERCE_CART', true );
999
		}
1000
1001
		$items    = array();
1002
		$subtotal = 0;
1003
1004
		// Default show only subtotal instead of itemization.
1005
		if ( ! apply_filters( 'wc_stripe_payment_request_hide_itemization', true ) ) {
1006
			foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
1007
				$amount         = $cart_item['line_subtotal'];
1008
				$subtotal       += $cart_item['line_subtotal'];
1009
				$quantity_label = 1 < $cart_item['quantity'] ? ' (x' . $cart_item['quantity'] . ')' : '';
1010
1011
				$product_name = WC_Stripe_Helper::is_pre_30() ? $cart_item['data']->post->post_title : $cart_item['data']->get_name();
1012
1013
				$item = array(
1014
					'label'  => $product_name . $quantity_label,
1015
					'amount' => WC_Stripe_Helper::get_stripe_amount( $amount ),
1016
				);
1017
1018
				$items[] = $item;
1019
			}
1020
		}
1021
1022
		$discounts   = wc_format_decimal( WC()->cart->get_cart_discount_total(), WC()->cart->dp );
1023
		$tax         = wc_format_decimal( WC()->cart->tax_total + WC()->cart->shipping_tax_total, WC()->cart->dp );
1024
		$shipping    = wc_format_decimal( WC()->cart->shipping_total, WC()->cart->dp );
1025
		$items_total = wc_format_decimal( WC()->cart->cart_contents_total, WC()->cart->dp ) + $discounts;
1026
		$order_total = wc_format_decimal( $items_total + $tax + $shipping - $discounts, WC()->cart->dp );
1027
1028
		if ( wc_tax_enabled() ) {
1029
			$items[] = array(
1030
				'label'  => esc_html( __( 'Tax', 'woocommerce-gateway-stripe' ) ),
1031
				'amount' => WC_Stripe_Helper::get_stripe_amount( $tax ),
1032
			);
1033
		}
1034
1035 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...
1036
			$items[] = array(
1037
				'label'  => esc_html( __( 'Shipping', 'woocommerce-gateway-stripe' ) ),
1038
				'amount' => WC_Stripe_Helper::get_stripe_amount( $shipping ),
1039
			);
1040
		}
1041
1042 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...
1043
			$items[] = array(
1044
				'label'  => esc_html( __( 'Discount', 'woocommerce-gateway-stripe' ) ),
1045
				'amount' => WC_Stripe_Helper::get_stripe_amount( $discounts ),
1046
			);
1047
		}
1048
1049
		if ( version_compare( WC_VERSION, '3.2', '<' ) ) {
1050
			$cart_fees = WC()->cart->fees;
1051
		} else {
1052
			$cart_fees = WC()->cart->get_fees();
1053
		}
1054
1055
		// Include fees and taxes as display items.
1056
		foreach ( $cart_fees as $key => $fee ) {
1057
			$items[] = array(
1058
				'label'  => $fee->name,
1059
				'amount' => WC_Stripe_Helper::get_stripe_amount( $fee->amount ),
1060
			);
1061
		}
1062
1063
		return array(
1064
			'displayItems' => $items,
1065
			'total'      => array(
1066
				'label'   => $this->total_label,
1067
				'amount'  => max( 0, apply_filters( 'woocommerce_stripe_calculated_total', WC_Stripe_Helper::get_stripe_amount( $order_total ), $order_total, WC()->cart ) ),
1068
				'pending' => false,
1069
			),
1070
		);
1071
	}
1072
}
1073
1074
new WC_Stripe_Payment_Request();
1075