Completed
Push — master ( e4283c...272f75 )
by Roy
02:12
created

postal_code_validation()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 25
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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