WC_Checkout   F
last analyzed

Complexity

Total Complexity 227

Size/Duplication

Total Lines 1186
Duplicated Lines 5.9 %

Coupling/Cohesion

Components 1
Dependencies 14

Test Coverage

Coverage 1.4%

Importance

Changes 0
Metric Value
dl 70
loc 1186
ccs 7
cts 499
cp 0.014
rs 0.8559
c 0
b 0
f 0
wmc 227
lcom 1
cbo 14

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __clone() 0 3 1
A instance() 0 13 2
A __wakeup() 0 3 1
A check_cart_items() 0 3 1
A checkout_form_billing() 0 3 1
A checkout_form_shipping() 0 3 1
F create_order() 0 90 14
C process_checkout() 4 60 11
A get_posted_address_data() 0 8 5
B get_value() 0 44 9

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like WC_Checkout often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use WC_Checkout, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Checkout functionality
4
 *
5
 * The WooCommerce checkout class handles the checkout process, collecting user data and processing the payment.
6
 *
7
 * @package WooCommerce/Classes
8
 * @version 3.4.0
9
 */
10
11
defined( 'ABSPATH' ) || exit;
12
13
/**
14
 * Checkout class.
15
 */
16
class WC_Checkout {
17
18
	/**
19
	 * The single instance of the class.
20
	 *
21
	 * @var WC_Checkout|null
22
	 */
23
	protected static $instance = null;
24
25
	/**
26
	 * Checkout fields are stored here.
27
	 *
28
	 * @var array|null
29
	 */
30
	protected $fields = null;
31
32
	/**
33
	 * Holds posted data for backwards compatibility.
34
	 *
35
	 * @var array
36
	 */
37
	protected $legacy_posted_data = array();
38
39
	/**
40
	 * Caches customer object. @see get_value.
41
	 *
42
	 * @var WC_Customer
43
	 */
44
	private $logged_in_customer = null;
45
46
	/**
47
	 * Gets the main WC_Checkout Instance.
48
	 *
49
	 * @since 2.1
50
	 * @static
51
	 * @return WC_Checkout Main instance
52
	 */
53 1
	public static function instance() {
54 1
		if ( is_null( self::$instance ) ) {
55 1
			self::$instance = new self();
56
57
			// Hook in actions once.
58 1
			add_action( 'woocommerce_checkout_billing', array( self::$instance, 'checkout_form_billing' ) );
59 1
			add_action( 'woocommerce_checkout_shipping', array( self::$instance, 'checkout_form_shipping' ) );
60
61
			// woocommerce_checkout_init action is ran once when the class is first constructed.
62 1
			do_action( 'woocommerce_checkout_init', self::$instance );
63
		}
64 1
		return self::$instance;
65
	}
66
67
	/**
68
	 * See if variable is set. Used to support legacy public variables which are no longer defined.
69
	 *
70
	 * @param string $key Key.
71
	 * @return bool
72
	 */
73
	public function __isset( $key ) {
74
		return in_array(
75
			$key,
76
			array(
77
				'enable_signup',
78
				'enable_guest_checkout',
79
				'must_create_account',
80
				'checkout_fields',
81
				'posted',
82
				'shipping_method',
83
				'payment_method',
84
				'customer_id',
85
				'shipping_methods',
86
			),
87
			true
88
		);
89
	}
90
91
	/**
92
	 * Sets the legacy public variables for backwards compatibility.
93
	 *
94
	 * @param string $key   Key.
95
	 * @param mixed  $value Value.
96
	 */
97
	public function __set( $key, $value ) {
98
		switch ( $key ) {
99 View Code Duplication
			case 'enable_signup':
100
				$bool_value = wc_string_to_bool( $value );
101
102
				if ( $bool_value !== $this->is_registration_enabled() ) {
103
					remove_filter( 'woocommerce_checkout_registration_enabled', '__return_true', 0 );
104
					remove_filter( 'woocommerce_checkout_registration_enabled', '__return_false', 0 );
105
					add_filter( 'woocommerce_checkout_registration_enabled', $bool_value ? '__return_true' : '__return_false', 0 );
106
				}
107
				break;
108 View Code Duplication
			case 'enable_guest_checkout':
109
				$bool_value = wc_string_to_bool( $value );
110
111
				if ( $bool_value === $this->is_registration_required() ) {
112
					remove_filter( 'woocommerce_checkout_registration_required', '__return_true', 0 );
113
					remove_filter( 'woocommerce_checkout_registration_required', '__return_false', 0 );
114
					add_filter( 'woocommerce_checkout_registration_required', $bool_value ? '__return_false' : '__return_true', 0 );
115
				}
116
				break;
117
			case 'checkout_fields':
118
				$this->fields = $value;
119
				break;
120
			case 'shipping_methods':
121
				WC()->session->set( 'chosen_shipping_methods', $value );
122
				break;
123
			case 'posted':
124
				$this->legacy_posted_data = $value;
0 ignored issues
show
Documentation Bug introduced by
It seems like $value of type * is incompatible with the declared type array of property $legacy_posted_data.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
125
				break;
126
		}
127
	}
128
129
	/**
130
	 * Gets the legacy public variables for backwards compatibility.
131
	 *
132
	 * @param string $key Key.
133
	 * @return array|string
134
	 */
135
	public function __get( $key ) {
136
		if ( in_array( $key, array( 'posted', 'shipping_method', 'payment_method' ), true ) && empty( $this->legacy_posted_data ) ) {
137
			$this->legacy_posted_data = $this->get_posted_data();
138
		}
139
140
		switch ( $key ) {
141
			case 'enable_signup':
142
				return $this->is_registration_enabled();
143
			case 'enable_guest_checkout':
144
				return ! $this->is_registration_required();
145
			case 'must_create_account':
146
				return $this->is_registration_required() && ! is_user_logged_in();
147
			case 'checkout_fields':
148
				return $this->get_checkout_fields();
149
			case 'posted':
150
				wc_doing_it_wrong( 'WC_Checkout->posted', 'Use $_POST directly.', '3.0.0' );
151
				return $this->legacy_posted_data;
152
			case 'shipping_method':
153
				return $this->legacy_posted_data['shipping_method'];
154
			case 'payment_method':
155
				return $this->legacy_posted_data['payment_method'];
156
			case 'customer_id':
157
				return apply_filters( 'woocommerce_checkout_customer_id', get_current_user_id() );
158
			case 'shipping_methods':
159
				return (array) WC()->session->get( 'chosen_shipping_methods' );
160
		}
161
	}
162
163
	/**
164
	 * Cloning is forbidden.
165
	 */
166
	public function __clone() {
167
		wc_doing_it_wrong( __FUNCTION__, __( 'Cloning is forbidden.', 'woocommerce' ), '2.1' );
168
	}
169
170
	/**
171
	 * Unserializing instances of this class is forbidden.
172
	 */
173
	public function __wakeup() {
174
		wc_doing_it_wrong( __FUNCTION__, __( 'Unserializing instances of this class is forbidden.', 'woocommerce' ), '2.1' );
175
	}
176
177
	/**
178
	 * Is registration required to checkout?
179
	 *
180
	 * @since  3.0.0
181
	 * @return boolean
182
	 */
183
	public function is_registration_required() {
184
		return apply_filters( 'woocommerce_checkout_registration_required', 'yes' !== get_option( 'woocommerce_enable_guest_checkout' ) );
185
	}
186
187
	/**
188
	 * Is registration enabled on the checkout page?
189
	 *
190
	 * @since  3.0.0
191
	 * @return boolean
192
	 */
193
	public function is_registration_enabled() {
194
		return apply_filters( 'woocommerce_checkout_registration_enabled', 'yes' === get_option( 'woocommerce_enable_signup_and_login_from_checkout' ) );
195
	}
196
197
	/**
198
	 * Get an array of checkout fields.
199
	 *
200
	 * @param  string $fieldset to get.
201
	 * @return array
202
	 */
203
	public function get_checkout_fields( $fieldset = '' ) {
204
		if ( ! is_null( $this->fields ) ) {
205
			return $fieldset ? $this->fields[ $fieldset ] : $this->fields;
206
		}
207
208
		// Fields are based on billing/shipping country. Grab those values but ensure they are valid for the store before using.
209
		$billing_country   = $this->get_value( 'billing_country' );
210
		$billing_country   = empty( $billing_country ) ? WC()->countries->get_base_country() : $billing_country;
211
		$allowed_countries = WC()->countries->get_allowed_countries();
212
213
		if ( ! array_key_exists( $billing_country, $allowed_countries ) ) {
214
			$billing_country = current( array_keys( $allowed_countries ) );
215
		}
216
217
		$shipping_country  = $this->get_value( 'shipping_country' );
218
		$shipping_country  = empty( $shipping_country ) ? WC()->countries->get_base_country() : $shipping_country;
219
		$allowed_countries = WC()->countries->get_shipping_countries();
220
221
		if ( ! array_key_exists( $shipping_country, $allowed_countries ) ) {
222
			$shipping_country = current( array_keys( $allowed_countries ) );
223
		}
224
225
		$this->fields = array(
226
			'billing'  => WC()->countries->get_address_fields(
227
				$billing_country,
228
				'billing_'
229
			),
230
			'shipping' => WC()->countries->get_address_fields(
231
				$shipping_country,
232
				'shipping_'
233
			),
234
			'account'  => array(),
235
			'order'    => array(
236
				'order_comments' => array(
237
					'type'        => 'textarea',
238
					'class'       => array( 'notes' ),
239
					'label'       => __( 'Order notes', 'woocommerce' ),
240
					'placeholder' => esc_attr__(
241
						'Notes about your order, e.g. special notes for delivery.',
242
						'woocommerce'
243
					),
244
				),
245
			),
246
		);
247
248 View Code Duplication
		if ( 'no' === get_option( 'woocommerce_registration_generate_username' ) ) {
249
			$this->fields['account']['account_username'] = array(
250
				'type'        => 'text',
251
				'label'       => __( 'Account username', 'woocommerce' ),
252
				'required'    => true,
253
				'placeholder' => esc_attr__( 'Username', 'woocommerce' ),
254
			);
255
		}
256
257 View Code Duplication
		if ( 'no' === get_option( 'woocommerce_registration_generate_password' ) ) {
258
			$this->fields['account']['account_password'] = array(
259
				'type'        => 'password',
260
				'label'       => __( 'Create account password', 'woocommerce' ),
261
				'required'    => true,
262
				'placeholder' => esc_attr__( 'Password', 'woocommerce' ),
263
			);
264
		}
265
		$this->fields = apply_filters( 'woocommerce_checkout_fields', $this->fields );
266
267
		foreach ( $this->fields as $field_type => $fields ) {
268
			// Sort each of the checkout field sections based on priority.
269
			uasort( $this->fields[ $field_type ], 'wc_checkout_fields_uasort_comparison' );
270
271
			// Add accessibility labels to fields that have placeholders.
272
			foreach ( $fields as $single_field_type => $field ) {
273
				if ( empty( $field['label'] ) && ! empty( $field['placeholder'] ) ) {
274
					$this->fields[ $field_type ][ $single_field_type ]['label']       = $field['placeholder'];
275
					$this->fields[ $field_type ][ $single_field_type ]['label_class'] = 'screen-reader-text';
276
				}
277
			}
278
		}
279
280
		return $fieldset ? $this->fields[ $fieldset ] : $this->fields;
281
	}
282
283
	/**
284
	 * When we process the checkout, lets ensure cart items are rechecked to prevent checkout.
285
	 */
286
	public function check_cart_items() {
287
		do_action( 'woocommerce_check_cart_items' );
288
	}
289
290
	/**
291
	 * Output the billing form.
292
	 */
293
	public function checkout_form_billing() {
294
		wc_get_template( 'checkout/form-billing.php', array( 'checkout' => $this ) );
295
	}
296
297
	/**
298
	 * Output the shipping form.
299
	 */
300
	public function checkout_form_shipping() {
301
		wc_get_template( 'checkout/form-shipping.php', array( 'checkout' => $this ) );
302
	}
303
304
	/**
305
	 * Create an order. Error codes:
306
	 *      520 - Cannot insert order into the database.
307
	 *      521 - Cannot get order after creation.
308
	 *      522 - Cannot update order.
309
	 *      525 - Cannot create line item.
310
	 *      526 - Cannot create fee item.
311
	 *      527 - Cannot create shipping item.
312
	 *      528 - Cannot create tax item.
313
	 *      529 - Cannot create coupon item.
314
	 *
315
	 * @throws Exception When checkout validation fails.
316
	 * @param  array $data Posted data.
317
	 * @return int|WP_ERROR
318
	 */
319
	public function create_order( $data ) {
320
		// Give plugins the opportunity to create an order themselves.
321
		$order_id = apply_filters( 'woocommerce_create_order', null, $this );
322
		if ( $order_id ) {
323
			return $order_id;
324
		}
325
326
		try {
327
			$order_id           = absint( WC()->session->get( 'order_awaiting_payment' ) );
328
			$cart_hash          = WC()->cart->get_cart_hash();
329
			$available_gateways = WC()->payment_gateways->get_available_payment_gateways();
330
			$order              = $order_id ? wc_get_order( $order_id ) : null;
331
332
			/**
333
			 * If there is an order pending payment, we can resume it here so
334
			 * long as it has not changed. If the order has changed, i.e.
335
			 * different items or cost, create a new order. We use a hash to
336
			 * detect changes which is based on cart items + order total.
337
			 */
338
			if ( $order && $order->has_cart_hash( $cart_hash ) && $order->has_status( array( 'pending', 'failed' ) ) ) {
339
				// Action for 3rd parties.
340
				do_action( 'woocommerce_resume_order', $order_id );
341
342
				// Remove all items - we will re-add them later.
343
				$order->remove_order_items();
344
			} else {
345
				$order = new WC_Order();
346
			}
347
348
			$fields_prefix = array(
349
				'shipping' => true,
350
				'billing'  => true,
351
			);
352
353
			$shipping_fields = array(
354
				'shipping_method' => true,
355
				'shipping_total'  => true,
356
				'shipping_tax'    => true,
357
			);
358
			foreach ( $data as $key => $value ) {
359
				if ( is_callable( array( $order, "set_{$key}" ) ) ) {
360
					$order->{"set_{$key}"}( $value );
361
					// Store custom fields prefixed with wither shipping_ or billing_. This is for backwards compatibility with 2.6.x.
362
				} elseif ( isset( $fields_prefix[ current( explode( '_', $key ) ) ] ) ) {
363
					if ( ! isset( $shipping_fields[ $key ] ) ) {
364
						$order->update_meta_data( '_' . $key, $value );
365
					}
366
				}
367
			}
368
369
			$order->set_created_via( 'checkout' );
370
			$order->set_cart_hash( $cart_hash );
371
			$order->set_customer_id( apply_filters( 'woocommerce_checkout_customer_id', get_current_user_id() ) );
372
			$order_vat_exempt = WC()->cart->get_customer()->get_is_vat_exempt() ? 'yes' : 'no';
373
			$order->add_meta_data( 'is_vat_exempt', $order_vat_exempt );
374
			$order->set_currency( get_woocommerce_currency() );
375
			$order->set_prices_include_tax( 'yes' === get_option( 'woocommerce_prices_include_tax' ) );
376
			$order->set_customer_ip_address( WC_Geolocation::get_ip_address() );
377
			$order->set_customer_user_agent( wc_get_user_agent() );
378
			$order->set_customer_note( isset( $data['order_comments'] ) ? $data['order_comments'] : '' );
379
			$order->set_payment_method( isset( $available_gateways[ $data['payment_method'] ] ) ? $available_gateways[ $data['payment_method'] ] : $data['payment_method'] );
380
			$order->set_shipping_total( WC()->cart->get_shipping_total() );
381
			$order->set_discount_total( WC()->cart->get_discount_total() );
382
			$order->set_discount_tax( WC()->cart->get_discount_tax() );
383
			$order->set_cart_tax( WC()->cart->get_cart_contents_tax() + WC()->cart->get_fee_tax() );
384
			$order->set_shipping_tax( WC()->cart->get_shipping_tax() );
385
			$order->set_total( WC()->cart->get_total( 'edit' ) );
386
			$this->create_order_line_items( $order, WC()->cart );
387
			$this->create_order_fee_lines( $order, WC()->cart );
388
			$this->create_order_shipping_lines( $order, WC()->session->get( 'chosen_shipping_methods' ), WC()->shipping()->get_packages() );
389
			$this->create_order_tax_lines( $order, WC()->cart );
390
			$this->create_order_coupon_lines( $order, WC()->cart );
391
392
			/**
393
			 * Action hook to adjust order before save.
394
			 *
395
			 * @since 3.0.0
396
			 */
397
			do_action( 'woocommerce_checkout_create_order', $order, $data );
398
399
			// Save the order.
400
			$order_id = $order->save();
401
402
			do_action( 'woocommerce_checkout_update_order_meta', $order_id, $data );
403
404
			return $order_id;
405
		} catch ( Exception $e ) {
406
			return new WP_Error( 'checkout-error', $e->getMessage() );
407
		}
408
	}
409
410
	/**
411
	 * Add line items to the order.
412
	 *
413
	 * @param WC_Order $order Order instance.
414
	 * @param WC_Cart  $cart  Cart instance.
415
	 */
416
	public function create_order_line_items( &$order, $cart ) {
417
		foreach ( $cart->get_cart() as $cart_item_key => $values ) {
418
			/**
419
			 * Filter hook to get initial item object.
420
			 *
421
			 * @since 3.1.0
422
			 */
423
			$item                       = apply_filters( 'woocommerce_checkout_create_order_line_item_object', new WC_Order_Item_Product(), $cart_item_key, $values, $order );
424
			$product                    = $values['data'];
425
			$item->legacy_values        = $values; // @deprecated For legacy actions.
426
			$item->legacy_cart_item_key = $cart_item_key; // @deprecated For legacy actions.
427
			$item->set_props(
428
				array(
429
					'quantity'     => $values['quantity'],
430
					'variation'    => $values['variation'],
431
					'subtotal'     => $values['line_subtotal'],
432
					'total'        => $values['line_total'],
433
					'subtotal_tax' => $values['line_subtotal_tax'],
434
					'total_tax'    => $values['line_tax'],
435
					'taxes'        => $values['line_tax_data'],
436
				)
437
			);
438
439
			if ( $product ) {
440
				$item->set_props(
441
					array(
442
						'name'         => $product->get_name(),
443
						'tax_class'    => $product->get_tax_class(),
444
						'product_id'   => $product->is_type( 'variation' ) ? $product->get_parent_id() : $product->get_id(),
445
						'variation_id' => $product->is_type( 'variation' ) ? $product->get_id() : 0,
446
					)
447
				);
448
			}
449
450
			$item->set_backorder_meta();
451
452
			/**
453
			 * Action hook to adjust item before save.
454
			 *
455
			 * @since 3.0.0
456
			 */
457
			do_action( 'woocommerce_checkout_create_order_line_item', $item, $cart_item_key, $values, $order );
458
459
			// Add item to order and save.
460
			$order->add_item( $item );
461
		}
462
	}
463
464
	/**
465
	 * Add fees to the order.
466
	 *
467
	 * @param WC_Order $order Order instance.
468
	 * @param WC_Cart  $cart  Cart instance.
469
	 */
470
	public function create_order_fee_lines( &$order, $cart ) {
471
		foreach ( $cart->get_fees() as $fee_key => $fee ) {
472
			$item                 = new WC_Order_Item_Fee();
473
			$item->legacy_fee     = $fee; // @deprecated For legacy actions.
474
			$item->legacy_fee_key = $fee_key; // @deprecated For legacy actions.
475
			$item->set_props(
476
				array(
477
					'name'      => $fee->name,
478
					'tax_class' => $fee->taxable ? $fee->tax_class : 0,
479
					'amount'    => $fee->amount,
480
					'total'     => $fee->total,
481
					'total_tax' => $fee->tax,
482
					'taxes'     => array(
483
						'total' => $fee->tax_data,
484
					),
485
				)
486
			);
487
488
			/**
489
			 * Action hook to adjust item before save.
490
			 *
491
			 * @since 3.0.0
492
			 */
493
			do_action( 'woocommerce_checkout_create_order_fee_item', $item, $fee_key, $fee, $order );
494
495
			// Add item to order and save.
496
			$order->add_item( $item );
497
		}
498
	}
499
500
	/**
501
	 * Add shipping lines to the order.
502
	 *
503
	 * @param WC_Order $order                   Order Instance.
504
	 * @param array    $chosen_shipping_methods Chosen shipping methods.
505
	 * @param array    $packages                Packages.
506
	 */
507
	public function create_order_shipping_lines( &$order, $chosen_shipping_methods, $packages ) {
508
		foreach ( $packages as $package_key => $package ) {
509
			if ( isset( $chosen_shipping_methods[ $package_key ], $package['rates'][ $chosen_shipping_methods[ $package_key ] ] ) ) {
510
				$shipping_rate            = $package['rates'][ $chosen_shipping_methods[ $package_key ] ];
511
				$item                     = new WC_Order_Item_Shipping();
512
				$item->legacy_package_key = $package_key; // @deprecated For legacy actions.
513
				$item->set_props(
514
					array(
515
						'method_title' => $shipping_rate->label,
516
						'method_id'    => $shipping_rate->method_id,
517
						'instance_id'  => $shipping_rate->instance_id,
518
						'total'        => wc_format_decimal( $shipping_rate->cost ),
519
						'taxes'        => array(
520
							'total' => $shipping_rate->taxes,
521
						),
522
					)
523
				);
524
525
				foreach ( $shipping_rate->get_meta_data() as $key => $value ) {
526
					$item->add_meta_data( $key, $value, true );
527
				}
528
529
				/**
530
				 * Action hook to adjust item before save.
531
				 *
532
				 * @since 3.0.0
533
				 */
534
				do_action( 'woocommerce_checkout_create_order_shipping_item', $item, $package_key, $package, $order );
535
536
				// Add item to order and save.
537
				$order->add_item( $item );
538
			}
539
		}
540
	}
541
542
	/**
543
	 * Add tax lines to the order.
544
	 *
545
	 * @param WC_Order $order Order instance.
546
	 * @param WC_Cart  $cart  Cart instance.
547
	 */
548
	public function create_order_tax_lines( &$order, $cart ) {
549
		foreach ( array_keys( $cart->get_cart_contents_taxes() + $cart->get_shipping_taxes() + $cart->get_fee_taxes() ) as $tax_rate_id ) {
550
			if ( $tax_rate_id && apply_filters( 'woocommerce_cart_remove_taxes_zero_rate_id', 'zero-rated' ) !== $tax_rate_id ) {
551
				$item = new WC_Order_Item_Tax();
552
				$item->set_props(
553
					array(
554
						'rate_id'            => $tax_rate_id,
555
						'tax_total'          => $cart->get_tax_amount( $tax_rate_id ),
556
						'shipping_tax_total' => $cart->get_shipping_tax_amount( $tax_rate_id ),
557
						'rate_code'          => WC_Tax::get_rate_code( $tax_rate_id ),
558
						'label'              => WC_Tax::get_rate_label( $tax_rate_id ),
559
						'compound'           => WC_Tax::is_compound( $tax_rate_id ),
560
						'rate_percent'       => WC_Tax::get_rate_percent_value( $tax_rate_id ),
561
					)
562
				);
563
564
				/**
565
				 * Action hook to adjust item before save.
566
				 *
567
				 * @since 3.0.0
568
				 */
569
				do_action( 'woocommerce_checkout_create_order_tax_item', $item, $tax_rate_id, $order );
570
571
				// Add item to order and save.
572
				$order->add_item( $item );
573
			}
574
		}
575
	}
576
577
	/**
578
	 * Add coupon lines to the order.
579
	 *
580
	 * @param WC_Order $order Order instance.
581
	 * @param WC_Cart  $cart  Cart instance.
582
	 */
583
	public function create_order_coupon_lines( &$order, $cart ) {
584
		foreach ( $cart->get_coupons() as $code => $coupon ) {
585
			$item = new WC_Order_Item_Coupon();
586
			$item->set_props(
587
				array(
588
					'code'         => $code,
589
					'discount'     => $cart->get_coupon_discount_amount( $code ),
590
					'discount_tax' => $cart->get_coupon_discount_tax_amount( $code ),
591
				)
592
			);
593
594
			// Avoid storing used_by - it's not needed and can get large.
595
			$coupon_data = $coupon->get_data();
596
			unset( $coupon_data['used_by'] );
597
			$item->add_meta_data( 'coupon_data', $coupon_data );
598
599
			/**
600
			 * Action hook to adjust item before save.
601
			 *
602
			 * @since 3.0.0
603
			 */
604
			do_action( 'woocommerce_checkout_create_order_coupon_item', $item, $code, $coupon, $order );
605
606
			// Add item to order and save.
607
			$order->add_item( $item );
608
		}
609
	}
610
611
	/**
612
	 * See if a fieldset should be skipped.
613
	 *
614
	 * @since 3.0.0
615
	 * @param string $fieldset_key Fieldset key.
616
	 * @param array  $data         Posted data.
617
	 * @return bool
618
	 */
619
	protected function maybe_skip_fieldset( $fieldset_key, $data ) {
620
		if ( 'shipping' === $fieldset_key && ( ! $data['ship_to_different_address'] || ! WC()->cart->needs_shipping_address() ) ) {
621
			return true;
622
		}
623
624
		if ( 'account' === $fieldset_key && ( is_user_logged_in() || ( ! $this->is_registration_required() && empty( $data['createaccount'] ) ) ) ) {
625
			return true;
626
		}
627
628
		return false;
629
	}
630
631
	/**
632
	 * Get posted data from the checkout form.
633
	 *
634
	 * @since  3.1.0
635
	 * @return array of data.
636
	 */
637
	public function get_posted_data() {
638
		$skipped = array();
639
		$data    = array(
640
			'terms'                              => (int) isset( $_POST['terms'] ), // WPCS: input var ok, CSRF ok.
641
			'createaccount'                      => (int) ! empty( $_POST['createaccount'] ), // WPCS: input var ok, CSRF ok.
642
			'payment_method'                     => isset( $_POST['payment_method'] ) ? wc_clean( wp_unslash( $_POST['payment_method'] ) ) : '', // WPCS: input var ok, CSRF ok.
643
			'shipping_method'                    => isset( $_POST['shipping_method'] ) ? wc_clean( wp_unslash( $_POST['shipping_method'] ) ) : '', // WPCS: input var ok, CSRF ok.
644
			'ship_to_different_address'          => ! empty( $_POST['ship_to_different_address'] ) && ! wc_ship_to_billing_address_only(), // WPCS: input var ok, CSRF ok.
645
			'woocommerce_checkout_update_totals' => isset( $_POST['woocommerce_checkout_update_totals'] ), // WPCS: input var ok, CSRF ok.
646
		);
647
		foreach ( $this->get_checkout_fields() as $fieldset_key => $fieldset ) {
648
			if ( $this->maybe_skip_fieldset( $fieldset_key, $data ) ) {
649
				$skipped[] = $fieldset_key;
650
				continue;
651
			}
652
653
			foreach ( $fieldset as $key => $field ) {
654
				$type = sanitize_title( isset( $field['type'] ) ? $field['type'] : 'text' );
655
656
				switch ( $type ) {
657
					case 'checkbox':
658
						$value = isset( $_POST[ $key ] ) ? 1 : ''; // WPCS: input var ok, CSRF ok.
659
						break;
660
					case 'multiselect':
661
						$value = isset( $_POST[ $key ] ) ? implode( ', ', wc_clean( wp_unslash( $_POST[ $key ] ) ) ) : ''; // WPCS: input var ok, CSRF ok.
662
						break;
663 View Code Duplication
					case 'textarea':
664
						$value = isset( $_POST[ $key ] ) ? wc_sanitize_textarea( wp_unslash( $_POST[ $key ] ) ) : ''; // WPCS: input var ok, CSRF ok.
665
						break;
666 View Code Duplication
					case 'password':
667
						$value = isset( $_POST[ $key ] ) ? wp_unslash( $_POST[ $key ] ) : ''; // WPCS: input var ok, CSRF ok, sanitization ok.
668
						break;
669 View Code Duplication
					default:
670
						$value = isset( $_POST[ $key ] ) ? wc_clean( wp_unslash( $_POST[ $key ] ) ) : ''; // WPCS: input var ok, CSRF ok.
671
						break;
672
				}
673
674
				$data[ $key ] = apply_filters( 'woocommerce_process_checkout_' . $type . '_field', apply_filters( 'woocommerce_process_checkout_field_' . $key, $value ) );
675
			}
676
		}
677
678
		if ( in_array( 'shipping', $skipped, true ) && ( WC()->cart->needs_shipping_address() || wc_ship_to_billing_address_only() ) ) {
679
			foreach ( $this->get_checkout_fields( 'shipping' ) as $key => $field ) {
680
				$data[ $key ] = isset( $data[ 'billing_' . substr( $key, 9 ) ] ) ? $data[ 'billing_' . substr( $key, 9 ) ] : '';
681
			}
682
		}
683
684
		// BW compatibility.
685
		$this->legacy_posted_data = $data;
686
687
		return apply_filters( 'woocommerce_checkout_posted_data', $data );
688
	}
689
690
	/**
691
	 * Validates the posted checkout data based on field properties.
692
	 *
693
	 * @since  3.0.0
694
	 * @param  array    $data   An array of posted data.
695
	 * @param  WP_Error $errors Validation error.
696
	 */
697
	protected function validate_posted_data( &$data, &$errors ) {
698
		foreach ( $this->get_checkout_fields() as $fieldset_key => $fieldset ) {
699
			$validate_fieldset = true;
700
			if ( $this->maybe_skip_fieldset( $fieldset_key, $data ) ) {
701
				$validate_fieldset = false;
702
			}
703
704
			foreach ( $fieldset as $key => $field ) {
705
				if ( ! isset( $data[ $key ] ) ) {
706
					continue;
707
				}
708
				$required    = ! empty( $field['required'] );
709
				$format      = array_filter( isset( $field['validate'] ) ? (array) $field['validate'] : array() );
710
				$field_label = isset( $field['label'] ) ? $field['label'] : '';
711
712
				switch ( $fieldset_key ) {
713
					case 'shipping':
714
						/* translators: %s: field name */
715
						$field_label = sprintf( __( 'Shipping %s', 'woocommerce' ), $field_label );
716
						break;
717
					case 'billing':
718
						/* translators: %s: field name */
719
						$field_label = sprintf( __( 'Billing %s', 'woocommerce' ), $field_label );
720
						break;
721
				}
722
723
				if ( in_array( 'postcode', $format, true ) ) {
724
					$country      = isset( $data[ $fieldset_key . '_country' ] ) ? $data[ $fieldset_key . '_country' ] : WC()->customer->{"get_{$fieldset_key}_country"}();
725
					$data[ $key ] = wc_format_postcode( $data[ $key ], $country );
726
727
					if ( $validate_fieldset && '' !== $data[ $key ] && ! WC_Validation::is_postcode( $data[ $key ], $country ) ) {
728
						switch ( $country ) {
729 View Code Duplication
							case 'IE':
730
								/* translators: %1$s: field name, %2$s finder.eircode.ie URL */
731
								$postcode_validation_notice = sprintf( __( '%1$s is not valid. You can look up the correct Eircode <a target="_blank" href="%2$s">here</a>.', 'woocommerce' ), '<strong>' . esc_html( $field_label ) . '</strong>', 'https://finder.eircode.ie' );
732
								break;
733 View Code Duplication
							default:
734
								/* translators: %s: field name */
735
								$postcode_validation_notice = sprintf( __( '%s is not a valid postcode / ZIP.', 'woocommerce' ), '<strong>' . esc_html( $field_label ) . '</strong>' );
736
						}
737
						$errors->add( 'validation', apply_filters( 'woocommerce_checkout_postcode_validation_notice', $postcode_validation_notice, $country, $data[ $key ] ) );
738
					}
739
				}
740
741 View Code Duplication
				if ( in_array( 'phone', $format, true ) ) {
742
					if ( $validate_fieldset && '' !== $data[ $key ] && ! WC_Validation::is_phone( $data[ $key ] ) ) {
743
						/* translators: %s: phone number */
744
						$errors->add( 'validation', sprintf( __( '%s is not a valid phone number.', 'woocommerce' ), '<strong>' . esc_html( $field_label ) . '</strong>' ) );
745
					}
746
				}
747
748
				if ( in_array( 'email', $format, true ) && '' !== $data[ $key ] ) {
749
					$email_is_valid = is_email( $data[ $key ] );
750
					$data[ $key ]   = sanitize_email( $data[ $key ] );
751
752
					if ( $validate_fieldset && ! $email_is_valid ) {
753
						/* translators: %s: email address */
754
						$errors->add( 'validation', sprintf( __( '%s is not a valid email address.', 'woocommerce' ), '<strong>' . esc_html( $field_label ) . '</strong>' ) );
755
						continue;
756
					}
757
				}
758
759
				if ( '' !== $data[ $key ] && in_array( 'state', $format, true ) ) {
760
					$country      = isset( $data[ $fieldset_key . '_country' ] ) ? $data[ $fieldset_key . '_country' ] : WC()->customer->{"get_{$fieldset_key}_country"}();
761
					$valid_states = WC()->countries->get_states( $country );
762
763
					if ( ! empty( $valid_states ) && is_array( $valid_states ) && count( $valid_states ) > 0 ) {
764
						$valid_state_values = array_map( 'wc_strtoupper', array_flip( array_map( 'wc_strtoupper', $valid_states ) ) );
765
						$data[ $key ]       = wc_strtoupper( $data[ $key ] );
766
767
						if ( isset( $valid_state_values[ $data[ $key ] ] ) ) {
768
							// With this part we consider state value to be valid as well, convert it to the state key for the valid_states check below.
769
							$data[ $key ] = $valid_state_values[ $data[ $key ] ];
770
						}
771
772 View Code Duplication
						if ( $validate_fieldset && ! in_array( $data[ $key ], $valid_state_values, true ) ) {
773
							/* translators: 1: state field 2: valid states */
774
							$errors->add( 'validation', sprintf( __( '%1$s is not valid. Please enter one of the following: %2$s', 'woocommerce' ), '<strong>' . esc_html( $field_label ) . '</strong>', implode( ', ', $valid_states ) ) );
775
						}
776
					}
777
				}
778
779
				if ( $validate_fieldset && $required && '' === $data[ $key ] ) {
780
					/* translators: %s: field name */
781
					$errors->add( 'required-field', apply_filters( 'woocommerce_checkout_required_field_notice', sprintf( __( '%s is a required field.', 'woocommerce' ), '<strong>' . esc_html( $field_label ) . '</strong>' ), $field_label ) );
782
				}
783
			}
784
		}
785
	}
786
787
	/**
788
	 * Validates that the checkout has enough info to proceed.
789
	 *
790
	 * @since  3.0.0
791
	 * @param  array    $data   An array of posted data.
792
	 * @param  WP_Error $errors Validation errors.
793
	 */
794
	protected function validate_checkout( &$data, &$errors ) {
795
		$this->validate_posted_data( $data, $errors );
796
		$this->check_cart_items();
797
798
		if ( empty( $data['woocommerce_checkout_update_totals'] ) && empty( $data['terms'] ) && ! empty( $_POST['terms-field'] ) ) { // WPCS: input var ok, CSRF ok.
799
			$errors->add( 'terms', __( 'Please read and accept the terms and conditions to proceed with your order.', 'woocommerce' ) );
800
		}
801
802
		if ( WC()->cart->needs_shipping() ) {
803
			$shipping_country = WC()->customer->get_shipping_country();
804
805
			if ( empty( $shipping_country ) ) {
806
				$errors->add( 'shipping', __( 'Please enter an address to continue.', 'woocommerce' ) );
807
			} elseif ( ! in_array( WC()->customer->get_shipping_country(), array_keys( WC()->countries->get_shipping_countries() ), true ) ) {
808
				/* translators: %s: shipping location */
809
				$errors->add( 'shipping', sprintf( __( 'Unfortunately <strong>we do not ship %s</strong>. Please enter an alternative shipping address.', 'woocommerce' ), WC()->countries->shipping_to_prefix() . ' ' . WC()->customer->get_shipping_country() ) );
810
			} else {
811
				$chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );
812
813
				foreach ( WC()->shipping()->get_packages() as $i => $package ) {
814
					if ( ! isset( $chosen_shipping_methods[ $i ], $package['rates'][ $chosen_shipping_methods[ $i ] ] ) ) {
815
						$errors->add( 'shipping', __( 'No shipping method has been selected. Please double check your address, or contact us if you need any help.', 'woocommerce' ) );
816
					}
817
				}
818
			}
819
		}
820
821
		if ( WC()->cart->needs_payment() ) {
822
			$available_gateways = WC()->payment_gateways->get_available_payment_gateways();
823
824
			if ( ! isset( $available_gateways[ $data['payment_method'] ] ) ) {
825
				$errors->add( 'payment', __( 'Invalid payment method.', 'woocommerce' ) );
826
			} else {
827
				$available_gateways[ $data['payment_method'] ]->validate_fields();
828
			}
829
		}
830
831
		do_action( 'woocommerce_after_checkout_validation', $data, $errors );
832
	}
833
834
	/**
835
	 * Set address field for customer.
836
	 *
837
	 * @since 3.0.7
838
	 * @param string $field String to update.
839
	 * @param string $key   Field key.
840
	 * @param array  $data  Array of data to get the value from.
841
	 */
842
	protected function set_customer_address_fields( $field, $key, $data ) {
843
		$billing_value  = null;
844
		$shipping_value = null;
845
846
		if ( isset( $data[ "billing_{$field}" ] ) && is_callable( array( WC()->customer, "set_billing_{$field}" ) ) ) {
847
			$billing_value  = $data[ "billing_{$field}" ];
848
			$shipping_value = $data[ "billing_{$field}" ];
849
		}
850
851
		if ( isset( $data[ "shipping_{$field}" ] ) && is_callable( array( WC()->customer, "set_shipping_{$field}" ) ) ) {
852
			$shipping_value = $data[ "shipping_{$field}" ];
853
		}
854
855 View Code Duplication
		if ( ! is_null( $billing_value ) && is_callable( array( WC()->customer, "set_billing_{$field}" ) ) ) {
856
			WC()->customer->{"set_billing_{$field}"}( $billing_value );
857
		}
858
859 View Code Duplication
		if ( ! is_null( $shipping_value ) && is_callable( array( WC()->customer, "set_shipping_{$field}" ) ) ) {
860
			WC()->customer->{"set_shipping_{$field}"}( $shipping_value );
861
		}
862
	}
863
864
	/**
865
	 * Update customer and session data from the posted checkout data.
866
	 *
867
	 * @since 3.0.0
868
	 * @param array $data Posted data.
869
	 */
870
	protected function update_session( $data ) {
871
		// Update both shipping and billing to the passed billing address first if set.
872
		$address_fields = array(
873
			'first_name',
874
			'last_name',
875
			'company',
876
			'email',
877
			'phone',
878
			'address_1',
879
			'address_2',
880
			'city',
881
			'postcode',
882
			'state',
883
			'country',
884
		);
885
886
		array_walk( $address_fields, array( $this, 'set_customer_address_fields' ), $data );
887
		WC()->customer->save();
888
889
		// Update customer shipping and payment method to posted method.
890
		$chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );
891
892
		if ( is_array( $data['shipping_method'] ) ) {
893
			foreach ( $data['shipping_method'] as $i => $value ) {
894
				$chosen_shipping_methods[ $i ] = $value;
895
			}
896
		}
897
898
		WC()->session->set( 'chosen_shipping_methods', $chosen_shipping_methods );
899
		WC()->session->set( 'chosen_payment_method', $data['payment_method'] );
900
901
		// Update cart totals now we have customer address.
902
		WC()->cart->calculate_totals();
903
	}
904
905
906
	/**
907
	 * Process an order that does require payment.
908
	 *
909
	 * @since 3.0.0
910
	 * @param int    $order_id       Order ID.
911
	 * @param string $payment_method Payment method.
912
	 */
913
	protected function process_order_payment( $order_id, $payment_method ) {
914
		$available_gateways = WC()->payment_gateways->get_available_payment_gateways();
915
916
		if ( ! isset( $available_gateways[ $payment_method ] ) ) {
917
			return;
918
		}
919
920
		// Store Order ID in session so it can be re-used after payment failure.
921
		WC()->session->set( 'order_awaiting_payment', $order_id );
922
923
		// Process Payment.
924
		$result = $available_gateways[ $payment_method ]->process_payment( $order_id );
925
926
		// Redirect to success/confirmation/payment page.
927
		if ( isset( $result['result'] ) && 'success' === $result['result'] ) {
928
			$result = apply_filters( 'woocommerce_payment_successful_result', $result, $order_id );
929
930
			if ( ! is_ajax() ) {
931
				wp_redirect( $result['redirect'] );
932
				exit;
933
			}
934
935
			wp_send_json( $result );
936
		}
937
	}
938
939
	/**
940
	 * Process an order that doesn't require payment.
941
	 *
942
	 * @since 3.0.0
943
	 * @param int $order_id Order ID.
944
	 */
945
	protected function process_order_without_payment( $order_id ) {
946
		$order = wc_get_order( $order_id );
947
		$order->payment_complete();
948
		wc_empty_cart();
949
950
		if ( ! is_ajax() ) {
951
			wp_safe_redirect(
952
				apply_filters( 'woocommerce_checkout_no_payment_needed_redirect', $order->get_checkout_order_received_url(), $order )
953
			);
954
			exit;
955
		}
956
957
		wp_send_json(
958
			array(
959
				'result'   => 'success',
960
				'redirect' => apply_filters( 'woocommerce_checkout_no_payment_needed_redirect', $order->get_checkout_order_received_url(), $order ),
961
			)
962
		);
963
	}
964
965
	/**
966
	 * Create a new customer account if needed.
967
	 *
968
	 * @throws Exception When not able to create customer.
969
	 * @param array $data Posted data.
970
	 */
971
	protected function process_customer( $data ) {
972
		$customer_id = apply_filters( 'woocommerce_checkout_customer_id', get_current_user_id() );
973
974
		if ( ! is_user_logged_in() && ( $this->is_registration_required() || ! empty( $data['createaccount'] ) ) ) {
975
			$username    = ! empty( $data['account_username'] ) ? $data['account_username'] : '';
976
			$password    = ! empty( $data['account_password'] ) ? $data['account_password'] : '';
977
			$customer_id = wc_create_new_customer(
978
				$data['billing_email'],
979
				$username,
980
				$password,
981
				array(
982
					'first_name' => ! empty( $data['billing_first_name'] ) ? $data['billing_first_name'] : '',
983
					'last_name'  => ! empty( $data['billing_last_name'] ) ? $data['billing_last_name'] : '',
984
				)
985
			);
986
987
			if ( is_wp_error( $customer_id ) ) {
988
				throw new Exception( $customer_id->get_error_message() );
989
			}
990
991
			wc_set_customer_auth_cookie( $customer_id );
992
993
			// As we are now logged in, checkout will need to refresh to show logged in data.
994
			WC()->session->set( 'reload_checkout', true );
995
996
			// Also, recalculate cart totals to reveal any role-based discounts that were unavailable before registering.
997
			WC()->cart->calculate_totals();
998
		}
999
1000
		// On multisite, ensure user exists on current site, if not add them before allowing login.
1001
		if ( $customer_id && is_multisite() && is_user_logged_in() && ! is_user_member_of_blog() ) {
1002
			add_user_to_blog( get_current_blog_id(), $customer_id, 'customer' );
1003
		}
1004
1005
		// Add customer info from other fields.
1006
		if ( $customer_id && apply_filters( 'woocommerce_checkout_update_customer_data', true, $this ) ) {
1007
			$customer = new WC_Customer( $customer_id );
1008
1009
			if ( ! empty( $data['billing_first_name'] ) && '' === $customer->get_first_name() ) {
1010
				$customer->set_first_name( $data['billing_first_name'] );
1011
			}
1012
1013
			if ( ! empty( $data['billing_last_name'] ) && '' === $customer->get_last_name() ) {
1014
				$customer->set_last_name( $data['billing_last_name'] );
1015
			}
1016
1017
			// If the display name is an email, update to the user's full name.
1018
			if ( is_email( $customer->get_display_name() ) ) {
1019
				$customer->set_display_name( $customer->get_first_name() . ' ' . $customer->get_last_name() );
1020
			}
1021
1022
			foreach ( $data as $key => $value ) {
1023
				// Use setters where available.
1024
				if ( is_callable( array( $customer, "set_{$key}" ) ) ) {
1025
					$customer->{"set_{$key}"}( $value );
1026
1027
					// Store custom fields prefixed with wither shipping_ or billing_.
1028
				} elseif ( 0 === stripos( $key, 'billing_' ) || 0 === stripos( $key, 'shipping_' ) ) {
1029
					$customer->update_meta_data( $key, $value );
1030
				}
1031
			}
1032
1033
			/**
1034
			 * Action hook to adjust customer before save.
1035
			 *
1036
			 * @since 3.0.0
1037
			 */
1038
			do_action( 'woocommerce_checkout_update_customer', $customer, $data );
1039
1040
			$customer->save();
1041
		}
1042
1043
		do_action( 'woocommerce_checkout_update_user_meta', $customer_id, $data );
1044
	}
1045
1046
	/**
1047
	 * If checkout failed during an AJAX call, send failure response.
1048
	 */
1049
	protected function send_ajax_failure_response() {
1050
		if ( is_ajax() ) {
1051
			// Only print notices if not reloading the checkout, otherwise they're lost in the page reload.
1052
			if ( ! isset( WC()->session->reload_checkout ) ) {
1053
				$messages = wc_print_notices( true );
1054
			}
1055
1056
			$response = array(
1057
				'result'   => 'failure',
1058
				'messages' => isset( $messages ) ? $messages : '',
1059
				'refresh'  => isset( WC()->session->refresh_totals ),
1060
				'reload'   => isset( WC()->session->reload_checkout ),
1061
			);
1062
1063
			unset( WC()->session->refresh_totals, WC()->session->reload_checkout );
1064
1065
			wp_send_json( $response );
1066
		}
1067
	}
1068
1069
	/**
1070
	 * Process the checkout after the confirm order button is pressed.
1071
	 *
1072
	 * @throws Exception When validation fails.
1073
	 */
1074
	public function process_checkout() {
1075
		try {
1076
			$nonce_value = wc_get_var( $_REQUEST['woocommerce-process-checkout-nonce'], wc_get_var( $_REQUEST['_wpnonce'], '' ) ); // @codingStandardsIgnoreLine.
1077
1078 View Code Duplication
			if ( empty( $nonce_value ) || ! wp_verify_nonce( $nonce_value, 'woocommerce-process_checkout' ) ) {
1079
				WC()->session->set( 'refresh_totals', true );
1080
				throw new Exception( __( 'We were unable to process your order, please try again.', 'woocommerce' ) );
1081
			}
1082
1083
			wc_maybe_define_constant( 'WOOCOMMERCE_CHECKOUT', true );
1084
			wc_set_time_limit( 0 );
1085
1086
			do_action( 'woocommerce_before_checkout_process' );
1087
1088
			if ( WC()->cart->is_empty() ) {
1089
				/* translators: %s: shop cart url */
1090
				throw new Exception( sprintf( __( 'Sorry, your session has expired. <a href="%s" class="wc-backward">Return to shop</a>', 'woocommerce' ), esc_url( wc_get_page_permalink( 'shop' ) ) ) );
1091
			}
1092
1093
			do_action( 'woocommerce_checkout_process' );
1094
1095
			$errors      = new WP_Error();
1096
			$posted_data = $this->get_posted_data();
1097
1098
			// Update session for customer and totals.
1099
			$this->update_session( $posted_data );
1100
1101
			// Validate posted data and cart items before proceeding.
1102
			$this->validate_checkout( $posted_data, $errors );
1103
1104
			foreach ( $errors->get_error_messages() as $message ) {
1105
				wc_add_notice( $message, 'error' );
1106
			}
1107
1108
			if ( empty( $posted_data['woocommerce_checkout_update_totals'] ) && 0 === wc_notice_count( 'error' ) ) {
1109
				$this->process_customer( $posted_data );
1110
				$order_id = $this->create_order( $posted_data );
1111
				$order    = wc_get_order( $order_id );
1112
1113
				if ( is_wp_error( $order_id ) ) {
1114
					throw new Exception( $order_id->get_error_message() );
1115
				}
1116
1117
				if ( ! $order ) {
1118
					throw new Exception( __( 'Unable to create order.', 'woocommerce' ) );
1119
				}
1120
1121
				do_action( 'woocommerce_checkout_order_processed', $order_id, $posted_data, $order );
1122
1123
				if ( WC()->cart->needs_payment() ) {
1124
					$this->process_order_payment( $order_id, $posted_data['payment_method'] );
1125
				} else {
1126
					$this->process_order_without_payment( $order_id );
1127
				}
1128
			}
1129
		} catch ( Exception $e ) {
1130
			wc_add_notice( $e->getMessage(), 'error' );
1131
		}
1132
		$this->send_ajax_failure_response();
1133
	}
1134
1135
	/**
1136
	 * Get a posted address field after sanitization and validation.
1137
	 *
1138
	 * @param string $key  Field key.
1139
	 * @param string $type Type of address. Available options: 'billing' or 'shipping'.
1140
	 * @return string
1141
	 */
1142
	public function get_posted_address_data( $key, $type = 'billing' ) {
1143
		if ( 'billing' === $type || false === $this->legacy_posted_data['ship_to_different_address'] ) {
1144
			$return = isset( $this->legacy_posted_data[ 'billing_' . $key ] ) ? $this->legacy_posted_data[ 'billing_' . $key ] : '';
1145
		} else {
1146
			$return = isset( $this->legacy_posted_data[ 'shipping_' . $key ] ) ? $this->legacy_posted_data[ 'shipping_' . $key ] : '';
1147
		}
1148
		return $return;
1149
	}
1150
1151
	/**
1152
	 * Gets the value either from POST, or from the customer object. Sets the default values in checkout fields.
1153
	 *
1154
	 * @param string $input Name of the input we want to grab data for. e.g. billing_country.
1155
	 * @return string The default value.
1156
	 */
1157
	public function get_value( $input ) {
1158
		// If the form was posted, get the posted value. This will only tend to happen when JavaScript is disabled client side.
1159
		if ( ! empty( $_POST[ $input ] ) ) { // WPCS: input var ok, CSRF OK.
1160
			return wc_clean( wp_unslash( $_POST[ $input ] ) ); // WPCS: input var ok, CSRF OK.
1161
		}
1162
1163
		// Allow 3rd parties to short circuit the logic and return their own default value.
1164
		$value = apply_filters( 'woocommerce_checkout_get_value', null, $input );
1165
1166
		if ( ! is_null( $value ) ) {
1167
			return $value;
1168
		}
1169
1170
		/**
1171
		 * For logged in customers, pull data from their account rather than the session which may contain incomplete data.
1172
		 * Another reason is that WC sets shipping address to the billing address on the checkout updates unless the
1173
		 * "ship to another address" box is checked. @see issue #20975.
1174
		 */
1175
		$customer_object = false;
1176
1177
		if ( is_user_logged_in() ) {
1178
			// Load customer object, but keep it cached to avoid reloading it multiple times.
1179
			if ( is_null( $this->logged_in_customer ) ) {
1180
				$this->logged_in_customer = new WC_Customer( get_current_user_id(), true );
1181
			}
1182
			$customer_object = $this->logged_in_customer;
1183
		}
1184
1185
		if ( ! $customer_object ) {
1186
			$customer_object = WC()->customer;
1187
		}
1188
1189
		if ( is_callable( array( $customer_object, "get_$input" ) ) ) {
1190
			$value = $customer_object->{"get_$input"}();
1191
		} elseif ( $customer_object->meta_exists( $input ) ) {
1192
			$value = $customer_object->get_meta( $input, true );
1193
		}
1194
1195
		if ( '' === $value ) {
1196
			$value = null;
1197
		}
1198
1199
		return apply_filters( 'default_checkout_' . $input, $value, $input );
1200
	}
1201
}
1202