Completed
Push — master ( d1b3b7...33527c )
by Roy
02:50
created

WC_Stripe_Apple_Pay::build_shipping_methods()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 18
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 11
nc 3
nop 1
1
<?php
2
if ( ! defined( 'ABSPATH' ) ) {
3
	exit;
4
}
5
6
/**
7
 * WC_Stripe_Apple_Pay class.
8
 *
9
 * @extends WC_Gateway_Stripe
10
 */
11
class WC_Stripe_Apple_Pay extends WC_Gateway_Stripe {
12
	/**
13
	 * This Instance.
14
	 *
15
	 * @var
16
	 */
17
	private static $_this;
18
19
	/**
20
	 * Gateway.
21
	 *
22
	 * @var
23
	 */
24
	private $_gateway;
0 ignored issues
show
Unused Code introduced by
The property $_gateway is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
25
26
	/**
27
	 * Statement Description
28
	 *
29
	 * @var
30
	 */
31
	public $statement_descriptor;
32
33
	/**
34
	 * Gateway settings.
35
	 *
36
	 * @var
37
	 */
38
	private $_gateway_settings;
39
40
	/**
41
	 * Constructor.
42
	 *
43
	 * @access public
44
	 * @since 3.1.0
45
	 * @version 3.1.0
46
	 */
47
	public function __construct() {
48
		self::$_this = $this;
49
50
		$this->_gateway_settings = get_option( 'woocommerce_stripe_settings', '' );
51
52
		$this->statement_descriptor = ! empty( $this->_gateway_settings['statement_descriptor'] ) ? $this->_gateway_settings['statement_descriptor'] : wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES );
53
54
		$this->init();
55
	}
56
57
	public function instance() {
58
		return self::$_this;
59
	}
60
61
	/**
62
	 * Initialize.
63
	 *
64
	 * @access public
65
	 * @since 3.1.0
66
	 * @version 3.1.0
67
	 */
68
	public function init() {
69
		add_action( 'wp_enqueue_scripts', array( $this, 'cart_scripts' ) );
70
		add_action( 'wp_enqueue_scripts', array( $this, 'single_scripts' ) );
71
72
		/**
73
		 * In order to display the Apple Pay button in the correct position,
74
		 * a new hook was added to WooCommerce 2.7. In older versions of WooCommerce,
75
		 * CSS is used to position the button.
76
		 */
77
		if ( version_compare( WC_VERSION, '3.0.0', '<' ) ) {
78
			add_action( 'woocommerce_after_add_to_cart_button', array( $this, 'display_apple_pay_button' ), 1 );
79
			add_filter( 'body_class', array( $this, 'body_class' ) );
80
		} else {
81
			add_action( 'woocommerce_after_add_to_cart_quantity', array( $this, 'display_apple_pay_button' ), 1 );
82
		}
83
84
		add_action( 'woocommerce_proceed_to_checkout', array( $this, 'display_apple_pay_button' ), 1 );
85
		add_action( 'woocommerce_proceed_to_checkout', array( $this, 'display_apple_pay_separator_html' ), 2 );
86
		add_action( 'woocommerce_checkout_before_customer_details', array( $this, 'display_apple_pay_button' ), 1 );
87
		add_action( 'woocommerce_checkout_before_customer_details', array( $this, 'display_apple_pay_separator_html' ), 2 );
88
		add_action( 'wc_ajax_wc_stripe_apple_pay', array( $this, 'process_apple_pay' ) );
89
		add_action( 'wc_ajax_wc_stripe_generate_apple_pay_cart', array( $this, 'generate_apple_pay_cart' ) );
90
		add_action( 'wc_ajax_wc_stripe_generate_apple_pay_single', array( $this, 'generate_apple_pay_single' ) );
91
		add_action( 'wc_ajax_wc_stripe_apple_pay_get_shipping_methods', array( $this, 'get_shipping_methods' ) );
92
		add_action( 'wc_ajax_wc_stripe_apple_pay_update_shipping_method', array( $this, 'update_shipping_method' ) );
93
		add_filter( 'woocommerce_gateway_title', array( $this, 'filter_gateway_title' ), 10, 2 );
94
	}
95
96
	/**
97
	 * Filters the gateway title to reflect Apple Pay.
98
	 *
99
	 */
100
	public function filter_gateway_title( $title, $id ) {
101
		global $post;
102
103
		if ( ! is_object( $post ) ) {
104
			return $title;
105
		}
106
107
		$method_title = get_post_meta( $post->ID, '_payment_method_title', true );
108
109
		if ( 'stripe' === $id && ! empty( $method_title ) ) {
110
			return $method_title;
111
		}
112
113
		return $title;
114
	}
115
116
	/**
117
	 * Enqueue JS scripts and styles for single product page.
118
	 *
119
	 * @since 3.1.0
120
	 * @version 3.1.0
121
	 */
122
	public function single_scripts() {
123
		if ( ! is_single() ) {
124
			return;
125
		}
126
127
		global $post;
128
129
		$product = wc_get_product( $post->ID );
130
131
		if ( ! in_array( ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->product_type : $product->get_type() ), $this->supported_product_types() ) ) {
132
			return;
133
		}
134
135
		$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
136
137
		wp_enqueue_style( 'stripe_apple_pay', plugins_url( 'assets/css/stripe-apple-pay.css', WC_STRIPE_MAIN_FILE ), array(), WC_STRIPE_VERSION );
138
139
		wp_enqueue_script( 'stripe', 'https://js.stripe.com/v2/', '', '1.0', true );
140
		wp_enqueue_script( 'woocommerce_stripe_apple_pay_single', plugins_url( 'assets/js/stripe-apple-pay-single' . $suffix . '.js', WC_STRIPE_MAIN_FILE ), array( 'stripe' ), WC_STRIPE_VERSION, true );
141
142
		$publishable_key = 'yes' === $this->_gateway_settings['testmode'] ? $this->_gateway_settings['test_publishable_key'] : $this->_gateway_settings['publishable_key'];
143
144
		$stripe_params = array(
145
			'key'                                           => $publishable_key,
146
			'currency_code'                                 => get_woocommerce_currency(),
147
			'country_code'                                  => substr( get_option( 'woocommerce_default_country' ), 0, 2 ),
148
			'label'                                         => $this->statement_descriptor,
149
			'ajaxurl'                                       => WC_AJAX::get_endpoint( '%%endpoint%%' ),
150
			'stripe_apple_pay_nonce'                        => wp_create_nonce( '_wc_stripe_apple_pay_nonce' ),
151
			'stripe_apple_pay_cart_nonce'                   => wp_create_nonce( '_wc_stripe_apple_pay_cart_nonce' ),
152
			'stripe_apple_pay_get_shipping_methods_nonce'   => wp_create_nonce( '_wc_stripe_apple_pay_get_shipping_methods_nonce' ),
153
			'stripe_apple_pay_update_shipping_method_nonce' => wp_create_nonce( '_wc_stripe_apple_pay_update_shipping_method_nonce' ),
154
			'needs_shipping'                                => WC()->cart->needs_shipping() ? 'yes' : 'no',
155
		);
156
157
		wp_localize_script( 'woocommerce_stripe_apple_pay_single', 'wc_stripe_apple_pay_single_params', apply_filters( 'wc_stripe_apple_pay_single_params', $stripe_params ) );
158
	}
159
160
	/**
161
	 * Enqueue JS scripts and styles for the cart/checkout.
162
	 *
163
	 * @since 3.1.0
164
	 * @version 3.1.0
165
	 */
166
	public function cart_scripts() {
167
		if ( ! is_cart() && ! is_checkout() && ! isset( $_GET['pay_for_order'] ) ) {
168
			return;
169
		}
170
171
		$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
172
173
		wp_enqueue_style( 'stripe_apple_pay', plugins_url( 'assets/css/stripe-apple-pay.css', WC_STRIPE_MAIN_FILE ), array(), WC_STRIPE_VERSION );
174
175
		wp_enqueue_script( 'stripe', 'https://js.stripe.com/v2/', '', '1.0', true );
176
		wp_enqueue_script( 'woocommerce_stripe_apple_pay', plugins_url( 'assets/js/stripe-apple-pay' . $suffix . '.js', WC_STRIPE_MAIN_FILE ), array( 'stripe' ), WC_STRIPE_VERSION, true );
177
178
		$publishable_key = 'yes' === $this->_gateway_settings['testmode'] ? $this->_gateway_settings['test_publishable_key'] : $this->_gateway_settings['publishable_key'];
179
180
		$stripe_params = array(
181
			'key'                                           => $publishable_key,
182
			'currency_code'                                 => get_woocommerce_currency(),
183
			'country_code'                                  => substr( get_option( 'woocommerce_default_country' ), 0, 2 ),
184
			'label'                                         => $this->statement_descriptor,
185
			'ajaxurl'                                       => WC_AJAX::get_endpoint( '%%endpoint%%' ),
186
			'stripe_apple_pay_nonce'                        => wp_create_nonce( '_wc_stripe_apple_pay_nonce' ),
187
			'stripe_apple_pay_cart_nonce'                   => wp_create_nonce( '_wc_stripe_apple_pay_cart_nonce' ),
188
			'stripe_apple_pay_get_shipping_methods_nonce'   => wp_create_nonce( '_wc_stripe_apple_pay_get_shipping_methods_nonce' ),
189
			'stripe_apple_pay_update_shipping_method_nonce' => wp_create_nonce( '_wc_stripe_apple_pay_update_shipping_method_nonce' ),
190
			'needs_shipping'                                => WC()->cart->needs_shipping() ? 'yes' : 'no',
191
			'is_cart_page'                                  => is_cart() ? 'yes' : 'no',
192
		);
193
194
		wp_localize_script( 'woocommerce_stripe_apple_pay', 'wc_stripe_apple_pay_params', apply_filters( 'wc_stripe_apple_pay_params', $stripe_params ) );
195
	}
196
197
	/**
198
	 * Checks to make sure product type is supported by Apple Pay.
199
	 *
200
	 */
201
	public function supported_product_types() {
202
		return array(
203
			'simple',
204
			'variable',
205
		);
206
	}
207
208
	/**
209
	 * Display Apple Pay button on the cart page
210
	 *
211
	 * @since 3.1.0
212
	 * @version 3.1.0
213
	 */
214
	public function display_apple_pay_button() {
215
		$gateways = WC()->payment_gateways->get_available_payment_gateways();
216
217
		/**
218
		 * In order for the Apple Pay button to show on cart page,
219
		 * Apple Pay must be enabled and Stripe gateway must be enabled.
220
		 */
221
		if (
222
			'yes' !== $this->_gateway_settings['apple_pay']
223
			|| ! isset( $gateways['stripe'] )
224
		) {
225
			return;
226
		}
227
228 View Code Duplication
		if ( is_single() ) {
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...
229
			global $post;
230
231
			$product = wc_get_product( $post->ID );
232
233
			if ( ! in_array( ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->product_type : $product->get_type() ), $this->supported_product_types() ) ) {
234
				return;
235
			}
236
		}
237
238
		$apple_pay_button = ! empty( $this->_gateway_settings['apple_pay_button'] ) ? $this->_gateway_settings['apple_pay_button'] : 'black';
239
		$button_lang      = ! empty( $this->_gateway_settings['apple_pay_button_lang'] ) ? strtolower( $this->_gateway_settings['apple_pay_button_lang'] ) : 'en';
240
		?>
241
		<div class="apple-pay-button-wrapper">
242
			<button class="apple-pay-button" lang="<?php echo esc_attr( $button_lang ); ?>" style="-webkit-appearance: -apple-pay-button; -apple-pay-button-type: buy; -apple-pay-button-style: <?php echo esc_attr( $apple_pay_button ); ?>;"></button>
243
		</div>
244
		<?php
245
	}
246
247
	/**
248
	 * Display Apple Pay button on the cart page
249
	 *
250
	 * @since 3.1.0
251
	 * @version 3.1.0
252
	 */
253
	public function display_apple_pay_separator_html() {
254
		$gateways = WC()->payment_gateways->get_available_payment_gateways();
255
256
		/**
257
		 * In order for the Apple Pay button to show on cart page,
258
		 * Apple Pay must be enabled and Stripe gateway must be enabled.
259
		 */
260
		if (
261
			'yes' !== $this->_gateway_settings['apple_pay']
262
			|| ! isset( $gateways['stripe'] )
263
		) {
264
			return;
265
		}
266
267 View Code Duplication
		if ( is_single() ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
268
			global $post;
269
270
			$product = wc_get_product( $post->ID );
271
272
			if ( ! in_array( ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->product_type : $product->get_type() ), $this->supported_product_types() ) ) {
273
				return;
274
			}
275
		}
276
		?>
277
		<p class="apple-pay-button-checkout-separator">- <?php esc_html_e( 'Or', 'woocommerce-gateway-stripe' ); ?> -</p>
278
		<?php
279
	}
280
281
	/**
282
	 * Add legacy WooCommerce body class.
283
	 *
284
	 * @since 3.1.0
285
	 * @version 3.1.0
286
	 * @param array $classes
287
	 * @return array $classes
288
	 */
289
	public function body_class( $classes ) {
290
		if ( 'yes' === $this->_gateway_settings['apple_pay'] && isset( $gateways['stripe'] ) ) {
0 ignored issues
show
Bug introduced by
The variable $gateways seems to never exist, and therefore isset should always return false. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
291
			$classes[] = 'wc-stripe-legacy';
292
		}
293
		return $classes;
294
	}
295
296
	/**
297
	 * Generates the Apple Pay single cart.
298
	 *
299
	 * @since 3.1.0
300
	 * @version 3.1.0
301
	 */
302
	public function generate_apple_pay_single() {
303
		if ( ! wp_verify_nonce( $_POST['nonce'], '_wc_stripe_apple_pay_cart_nonce' ) ) {
304
			wp_die( __( 'Cheatin&#8217; huh?', 'woocommerce-gateway-stripe' ) );
305
		}
306
307
		if ( ! defined( 'WOOCOMMERCE_CART' ) ) {
308
			define( 'WOOCOMMERCE_CART', true );
309
		}
310
311
		WC()->shipping->reset_shipping();
312
313
		global $post;
314
315
		$product = wc_get_product( $post->ID );
316
		$qty     = absint( $_POST['qty'] );
317
318
		/**
319
		 * If this page is single product page, we need to simulate
320
		 * adding the product to the cart taken account if it is a
321
		 * simple or variable product.
322
		 */
323
		if ( is_single() ) {
324
			// First empty the cart to prevent wrong calculation.
325
			WC()->cart->empty_cart();
326
327
			if ( 'variable' === ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->product_type : $product->get_type() ) && isset( $_POST['attributes'] ) ) {
328
				$attributes = array_map( 'wc_clean', $_POST['attributes'] );
329
330
				$variation_id = $product->get_matching_variation( $attributes );
331
332
				WC()->cart->add_to_cart( $product->get_id(), $qty, $variation_id, $attributes );
333
			}
334
335
			if ( 'simple' === ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->product_type : $product->get_type() ) ) {
336
				WC()->cart->add_to_cart( $product->get_id(), $qty );
337
			}
338
		}
339
340
		WC()->cart->calculate_totals();
341
342
		wp_send_json( array( 'line_items' => $this->build_line_items(), 'total' => WC()->cart->total ) );
343
	}
344
345
	/**
346
	 * Generates the Apple Pay cart.
347
	 *
348
	 * @since 3.1.0
349
	 * @version 3.1.0
350
	 */
351
	public function generate_apple_pay_cart() {
352
		if ( ! wp_verify_nonce( $_POST['nonce'], '_wc_stripe_apple_pay_cart_nonce' ) ) {
353
			wp_die( __( 'Cheatin&#8217; huh?', 'woocommerce-gateway-stripe' ) );
354
		}
355
356
		wp_send_json( array( 'line_items' => $this->build_line_items(), 'total' => WC()->cart->total ) );
357
	}
358
359
	/**
360
	 * Calculate and set shipping method.
361
	 *
362
	 * @since 3.1.0
363
	 * @version 3.1.0
364
	 * @param array $address
365
	 */
366
	public function calculate_shipping( $address = array() ) {
367
		$country  = strtoupper( $address['countryCode'] );
368
		$state    = strtoupper( $address['administrativeArea'] );
369
		$postcode = $address['postalCode'];
370
		$city     = $address['locality'];
371
372
		WC()->shipping->reset_shipping();
373
374
		if ( $postcode && ! WC_Validation::is_postcode( $postcode, $country ) ) {
375
			throw new Exception( __( 'Please enter a valid postcode/ZIP.', 'woocommerce-gateway-stripe' ) );
376
		} elseif ( $postcode ) {
377
			$postcode = wc_format_postcode( $postcode, $country );
378
		}
379
380 View Code Duplication
		if ( $country ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
381
			WC()->customer->set_location( $country, $state, $postcode, $city );
382
			WC()->customer->set_shipping_location( $country, $state, $postcode, $city );
383
		} else {
384
			WC()->customer->set_to_base();
385
			WC()->customer->set_shipping_to_base();
386
		}
387
388
		WC()->customer->calculated_shipping( true );
389
390
		/**
391
		 * Set the shipping package.
392
		 *
393
		 * Note that address lines are not provided at this point
394
		 * because Apple Pay does not supply that until after
395
		 * authentication via passcode or Touch ID. We will need to
396
		 * capture this information when we process the payment.
397
		 */
398
399
		$packages = array();
400
401
		$packages[0]['contents']                 = WC()->cart->get_cart();
402
		$packages[0]['contents_cost']            = 0;
403
		$packages[0]['applied_coupons']          = WC()->cart->applied_coupons;
404
		$packages[0]['user']['ID']               = get_current_user_id();
405
		$packages[0]['destination']['country']   = $country;
406
		$packages[0]['destination']['state']     = $state;
407
		$packages[0]['destination']['postcode']  = $postcode;
408
		$packages[0]['destination']['city']      = $city;
409
410 View Code Duplication
		foreach ( WC()->cart->get_cart() as $item ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
411
			if ( $item['data']->needs_shipping() ) {
412
				if ( isset( $item['line_total'] ) ) {
413
					$packages[0]['contents_cost'] += $item['line_total'];
414
				}
415
			}
416
		}
417
418
		$packages = apply_filters( 'woocommerce_cart_shipping_packages', $packages );
419
420
		WC()->shipping->calculate_shipping( $packages );
421
	}
422
423
	/**
424
	 * Gets shipping for Apple Pay Payment sheet.
425
	 *
426
	 * @since 3.1.0
427
	 * @version 3.1.0
428
	 */
429
	public function get_shipping_methods() {
430
		if ( ! wp_verify_nonce( $_POST['nonce'], '_wc_stripe_apple_pay_get_shipping_methods_nonce' ) ) {
431
			wp_die( __( 'Cheatin&#8217; huh?', 'woocommerce-gateway-stripe' ) );
432
		}
433
434
		if ( ! defined( 'WOOCOMMERCE_CART' ) ) {
435
			define( 'WOOCOMMERCE_CART', true );
436
		}
437
438
		try {
439
			$address = array_map( 'wc_clean', $_POST['address'] );
440
441
			$this->calculate_shipping( $address );
442
443
			// Set the shipping options.
444
			$currency = get_woocommerce_currency();
445
			$data     = array();
446
447
			$packages = WC()->shipping->get_packages();
448
449
			if ( ! empty( $packages ) && WC()->customer->has_calculated_shipping() ) {
450
				foreach ( $packages as $package_key => $package ) {
451
					if ( empty( $package['rates'] ) ) {
452
						throw new Exception( __( 'Unable to find shipping method for address.', 'woocommerce-gateway-stripe' ) );
453
					}
454
455
					foreach ( $package['rates'] as $key => $rate ) {
456
						$data[] = array(
457
							'id'       => $rate->id,
458
							'label'    => $rate->label,
459
							'amount'   => array(
460
								'currency' => $currency,
461
								'value'    => $rate->cost,
462
							),
463
							'selected' => false,
464
						);
465
					}
466
				}
467
468
				// Auto select the first shipping method.
469
				WC()->session->set( 'chosen_shipping_methods', array( $data[0]['id'] ) );
470
471
				WC()->cart->calculate_totals();
472
473
				wp_send_json( array( 'success' => 'true', 'shipping_methods' => $this->build_shipping_methods( $data ), 'line_items' => $this->build_line_items(), 'total' => WC()->cart->total ) );
474
			} else {
475
				throw new Exception( __( 'Unable to find shipping method for address.', 'woocommerce-gateway-stripe' ) );
476
			}
477
		} catch ( Exception $e ) {
478
			wp_send_json( array( 'success' => 'false', 'shipping_methods' => array(), 'line_items' => $this->build_line_items(), 'total' => WC()->cart->total ) );
479
		}
480
	}
481
482
	/**
483
	 * Updates shipping method on cart session.
484
	 *
485
	 * @since 3.1.0
486
	 * @version 3.1.0
487
	 */
488
	public function update_shipping_method() {
489
		if ( ! defined( 'WOOCOMMERCE_CART' ) ) {
490
			define( 'WOOCOMMERCE_CART', true );
491
		}
492
493
		if ( ! wp_verify_nonce( $_POST['nonce'], '_wc_stripe_apple_pay_update_shipping_method_nonce' ) ) {
494
			wp_die( __( 'Cheatin&#8217; huh?', 'woocommerce-gateway-stripe' ) );
495
		}
496
497
		$selected_shipping_method = array_map( 'wc_clean', $_POST['selected_shipping_method'] );
498
499
		WC()->session->set( 'chosen_shipping_methods', array( $selected_shipping_method['identifier'] ) );
500
501
		WC()->cart->calculate_totals();
502
503
		// Send back the new cart total.
504
		$currency  = get_woocommerce_currency();
505
		$tax_total = max( 0, round( WC()->cart->tax_total + WC()->cart->shipping_tax_total, WC()->cart->dp ) );
506
		$data      = array(
507
			'total' => WC()->cart->total,
508
		);
509
510
		// Include fees and taxes as displayItems.
511 View Code Duplication
		foreach ( WC()->cart->fees as $key => $fee ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
512
			$data['items'][] = array(
513
				'label'  => $fee->name,
514
				'amount' => array(
515
					'currency' => $currency,
516
					'value'    => $fee->amount,
517
				),
518
			);
519
		}
520 View Code Duplication
		if ( 0 < $tax_total ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
521
			$data['items'][] = array(
522
				'label'  => __( 'Tax', 'woocommerce-gateway-stripe' ),
523
				'amount' => array(
524
					'currency' => $currency,
525
					'value'    => $tax_total,
526
				),
527
			);
528
		}
529
530
		wp_send_json( array( 'success' => 'true', 'line_items' => $this->build_line_items(), 'total' => WC()->cart->total ) );
531
	}
532
533
	/**
534
	 * Handles the Apple Pay processing via AJAX
535
	 *
536
	 * @access public
537
	 * @since 3.1.0
538
	 * @version 3.1.0
539
	 */
540
	public function process_apple_pay() {
541
		if ( ! wp_verify_nonce( $_POST['nonce'], '_wc_stripe_apple_pay_nonce' ) ) {
542
			wp_die( __( 'Cheatin&#8217; huh?', 'woocommerce-gateway-stripe' ) );
543
		}
544
545
		try {
546
			$result = array_map( 'wc_clean', $_POST['result'] );
547
548
			$order = $this->create_order( $result );
549
550
			$order_id = version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->id : $order->get_id();
551
552
			// Handle payment.
553
			if ( $order->get_total() > 0 ) {
554
555 View Code Duplication
				if ( $order->get_total() * 100 < WC_Stripe::get_minimum_amount() ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
556
					return new WP_Error( 'stripe_error', sprintf( __( 'Sorry, the minimum allowed order total is %1$s to use this payment method.', 'woocommerce-gateway-stripe' ), wc_price( WC_Stripe::get_minimum_amount() / 100 ) ) );
557
				}
558
559
				WC_Stripe::log( "Info: Begin processing payment for order {$order_id} for the amount of {$order->get_total()}" );
560
561
				// Make the request.
562
				$response = WC_Stripe_API::request( $this->generate_payment_request( $order, $result['token']['id'] ) );
563
564
				if ( is_wp_error( $response ) ) {
565
					$localized_messages = $this->get_localized_messages();
566
567
					throw new Exception( ( isset( $localized_messages[ $response->get_error_code() ] ) ? $localized_messages[ $response->get_error_code() ] : $response->get_error_message() ) );
568
				}
569
570
				// Process valid response.
571
				$this->process_response( $response, $order );
572
			} else {
573
				$order->payment_complete();
574
			}
575
576
			// Remove cart.
577
			WC()->cart->empty_cart();
578
579
			update_post_meta( $order_id, '_customer_user', get_current_user_id() );
580
			update_post_meta( $order_id, '_payment_method_title', __( 'Apple Pay (Stripe)', 'woocommerce-gateway-stripe' ) );
581
582
			// Return thank you page redirect.
583
			wp_send_json( array(
584
				'success'  => 'true',
585
				'redirect' => $this->get_return_url( $order ),
586
			) );
587
588
		} catch ( Exception $e ) {
589
			WC()->session->set( 'refresh_totals', true );
590
			WC_Stripe::log( sprintf( __( 'Error: %s', 'woocommerce-gateway-stripe' ), $e->getMessage() ) );
591
592
			if ( is_object( $order ) && isset( $order_id ) && $order->has_status( array( 'pending', 'failed' ) ) ) {
0 ignored issues
show
Bug introduced by
The variable $order does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
593
				$this->send_failed_order_email( $order_id );
594
			}
595
596
			wp_send_json( array( 'success' => 'false', 'msg' => $e->getMessage() ) );
597
		}
598
	}
599
600
	/**
601
	 * Generate the request for the payment.
602
	 * @param  WC_Order $order
603
	 * @param string $source token
604
	 * @return array()
0 ignored issues
show
Documentation introduced by
The doc-type array() could not be parsed: Expected "|" or "end of type", but got "(" at position 5. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
605
	 */
606
	protected function generate_payment_request( $order, $source ) {
607
		$post_data                = array();
608
		$post_data['currency']    = strtolower( version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->get_order_currency() : $order->get_currency() );
609
		$post_data['amount']      = $this->get_stripe_amount( $order->get_total(), $post_data['currency'] );
610
		$post_data['description'] = sprintf( __( '%1$s - Order %2$s', 'woocommerce-gateway-stripe' ), $this->statement_descriptor, $order->get_order_number() );
611
		$post_data['capture']     = 'yes' === $this->_gateway_settings['capture'] ? 'true' : 'false';
612
613
		$billing_email      = version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_email : $order->get_billing_email();
614
615
		if ( ! empty( $billing_email ) && apply_filters( 'wc_stripe_send_stripe_receipt', false ) ) {
616
			$post_data['receipt_email'] = $billing_email;
617
		}
618
619
		$post_data['expand[]']    = 'balance_transaction';
620
		$post_data['source']      = $source;
621
622
		/**
623
		 * Filter the return value of the WC_Payment_Gateway_CC::generate_payment_request.
624
		 *
625
		 * @since 3.1.0
626
		 * @param array $post_data
627
		 * @param WC_Order $order
628
		 * @param object $source
629
		 */
630
		return apply_filters( 'wc_stripe_generate_payment_request', $post_data, $order );
631
	}
632
633
	/**
634
	 * Builds the shippings methods to pass to Apple Pay.
635
	 *
636
	 * @since 3.1.0
637
	 * @version 3.1.0
638
	 */
639
	public function build_shipping_methods( $shipping_methods ) {
640
		if ( empty( $shipping_methods ) ) {
641
			return array();
642
		}
643
644
		$shipping = array();
645
646
		foreach ( $shipping_methods as $method ) {
647
			$shipping[] = array(
648
				'label'      => $method['label'],
649
				'detail'     => '',
650
				'amount'     => $method['amount']['value'],
651
				'identifier' => $method['id'],
652
			);
653
		}
654
655
		return $shipping;
656
	}
657
658
	/**
659
	 * Builds the line items to pass to Apple Pay.
660
	 *
661
	 * @since 3.1.0
662
	 * @version 3.1.0
663
	 */
664
	public function build_line_items() {
665
		if ( ! defined( 'WOOCOMMERCE_CART' ) ) {
666
			define( 'WOOCOMMERCE_CART', true );
667
		}
668
669
		$decimals = apply_filters( 'wc_stripe_apple_pay_decimals', 2 );
670
		
671
		$items    = array();
672
		$subtotal = 0;
673
674
		foreach ( WC()->cart->get_cart() as $cart_item_key => $values ) {
675
			$amount         = wc_format_decimal( $values['line_subtotal'], $decimals );
676
			$subtotal       += $values['line_subtotal']; 
677
			$quantity_label = 1 < $values['quantity'] ? ' (x' . $values['quantity'] . ')' : '';
678
679
			$item = array(
680
				'type'   => 'final',
681
				'label'  => $values['data']->post->post_title . $quantity_label,
682
				'amount' => wc_format_decimal( $amount, $decimals ),
683
			);
684
685
			$items[] = $item;
686
		}
687
688
		// Default show only subtotal instead of itemization.
689
		if ( apply_filters( 'wc_stripe_apple_pay_disable_itemization', true ) ) {
690
			$items = array();
691
			$items[] = array(
692
				'type'   => 'final',
693
				'label'  => __( 'Sub-Total', 'woocommerce-gateway-stripe' ),
694
				'amount' => wc_format_decimal( $subtotal, $decimals ),
695
			);
696
		}
697
698
		$discounts   = wc_format_decimal( WC()->cart->get_cart_discount_total(), $decimals );
699
		$tax         = wc_format_decimal( WC()->cart->tax_total + WC()->cart->shipping_tax_total, $decimals );
700
		$shipping    = wc_format_decimal( WC()->cart->shipping_total, $decimals );
701
		$item_total  = wc_format_decimal( WC()->cart->cart_contents_total, $decimals ) + $discounts;
702
		$order_total = wc_format_decimal( $item_total + $tax + $shipping, $decimals );
0 ignored issues
show
Unused Code introduced by
$order_total is not used, you could remove the assignment.

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

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

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

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

Loading history...
703
704
		if ( wc_tax_enabled() ) {
705
			$items[] = array(
706
				'type'   => 'final',
707
				'label'  => __( 'Tax', 'woocommerce-gateway-stripe' ),
708
				'amount' => $tax,
709
			);
710
		}
711
712 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...
713
			$items[] = array(
714
				'type'   => 'final',
715
				'label'  => __( 'Shipping', 'woocommerce-gateway-stripe' ),
716
				'amount' => $shipping,
717
			);
718
		}
719
720 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...
721
			$items[] = array(
722
				'type'   => 'final',
723
				'label'  => __( 'Discount', 'woocommerce-gateway-stripe' ),
724
				'amount' => '-' . $discounts,
725
			);
726
		}
727
728
		return $items;
729
	}
730
731
	/**
732
	 * Create order programatically.
733
	 *
734
	 * @since 3.1.0
735
	 * @version 3.1.0
736
	 * @param array $data
737
	 * @return object $order
738
	 */
739
	public function create_order( $data = array() ) {
740
		if ( empty( $data ) ) {
741
			throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-gateway-stripe' ), 520 ) );
742
		}
743
744
		$order = wc_create_order();
745
		$order_id = version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->id : $order->get_id();
746
747
		if ( is_wp_error( $order ) ) {
748
			throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-gateway-stripe' ), 520 ) );
749
		} elseif ( false === $order ) {
750
			throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-gateway-stripe' ), 521 ) );
751
		} else {
752
			do_action( 'woocommerce_new_order', $order_id );
753
		}
754
755
		// Store the line items to the new/resumed order
756
		foreach ( WC()->cart->get_cart() as $cart_item_key => $values ) {
757
			$item_id = $order->add_product(
758
				$values['data'],
759
				$values['quantity'],
760
				array(
761
					'variation' => $values['variation'],
762
					'totals'    => array(
763
						'subtotal'     => $values['line_subtotal'],
764
						'subtotal_tax' => $values['line_subtotal_tax'],
765
						'total'        => $values['line_total'],
766
						'tax'          => $values['line_tax'],
767
						'tax_data'     => $values['line_tax_data'], // Since 2.2
768
					),
769
				)
770
			);
771
772
			if ( ! $item_id ) {
773
				throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-gateway-stripe' ), 525 ) );
774
			}
775
776
			// Allow plugins to add order item meta
777
			do_action( 'woocommerce_add_order_item_meta', $item_id, $values, $cart_item_key );
778
		}
779
780
		// Store fees
781
		foreach ( WC()->cart->get_fees() as $fee_key => $fee ) {
782
			$item_id = $order->add_fee( $fee );
783
784
			if ( ! $item_id ) {
785
				throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-gateway-stripe' ), 526 ) );
786
			}
787
788
			// Allow plugins to add order item meta to fees
789
			do_action( 'woocommerce_add_order_fee_meta', $order_id, $item_id, $fee, $fee_key );
790
		}
791
792
		// Store tax rows
793
		foreach ( array_keys( WC()->cart->taxes + WC()->cart->shipping_taxes ) as $tax_rate_id ) {
794
			if ( $tax_rate_id && ! $order->add_tax( $tax_rate_id, WC()->cart->get_tax_amount( $tax_rate_id ), WC()->cart->get_shipping_tax_amount( $tax_rate_id ) ) && apply_filters( 'woocommerce_cart_remove_taxes_zero_rate_id', 'zero-rated' ) !== $tax_rate_id ) {
795
				throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-gateway-stripe' ), 528 ) );
796
			}
797
		}
798
799
		// Store coupons
800
		foreach ( WC()->cart->get_coupons() as $code => $coupon ) {
801
			if ( ! $order->add_coupon( $code, WC()->cart->get_coupon_discount_amount( $code ), WC()->cart->get_coupon_discount_tax_amount( $code ) ) ) {
802
				throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-gateway-stripe' ), 529 ) );
803
			}
804
		}
805
806
		// Billing address
807
		$billing_address = array();
808
		if ( ! empty( $data['token']['card'] ) ) {
809
			// Name from Stripe is a full name string.
810
			$name                          = explode( ' ', $data['token']['card']['name'] );
811
			$lastname                      = array_pop( $name );
812
			$firstname                     = implode( ' ', $name );
813
			$billing_address['first_name'] = $firstname;
814
			$billing_address['last_name']  = $lastname;
815
			$billing_address['email']      = $data['shippingContact']['emailAddress'];
816
			$billing_address['phone']      = $data['shippingContact']['phoneNumber'];
817
			$billing_address['country']    = $data['token']['card']['country'];
818
			$billing_address['address_1']  = $data['token']['card']['address_line1'];
819
			$billing_address['address_2']  = $data['token']['card']['address_line2'];
820
			$billing_address['city']       = $data['token']['card']['address_city'];
821
			$billing_address['state']      = $data['token']['card']['address_state'];
822
			$billing_address['postcode']   = $data['token']['card']['address_zip'];
823
		}
824
825
		// Shipping address.
826
		$shipping_address = array();
827
		if ( WC()->cart->needs_shipping() && ! empty( $data['shippingContact'] ) ) {
828
			$shipping_address['first_name'] = $data['shippingContact']['givenName'];
829
			$shipping_address['last_name']  = $data['shippingContact']['familyName'];
830
			$shipping_address['email']      = $data['shippingContact']['emailAddress'];
831
			$shipping_address['phone']      = $data['shippingContact']['phoneNumber'];
832
			$shipping_address['country']    = $data['shippingContact']['countryCode'];
833
			$shipping_address['address_1']  = $data['shippingContact']['addressLines'][0];
834
			$shipping_address['address_2']  = $data['shippingContact']['addressLines'][1];
835
			$shipping_address['city']       = $data['shippingContact']['locality'];
836
			$shipping_address['state']      = $data['shippingContact']['administrativeArea'];
837
			$shipping_address['postcode']   = $data['shippingContact']['postalCode'];
838
		} elseif ( ! empty( $data['shippingContact'] ) ) {
839
			$shipping_address['first_name'] = $firstname;
0 ignored issues
show
Bug introduced by
The variable $firstname does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
840
			$shipping_address['last_name']  = $lastname;
0 ignored issues
show
Bug introduced by
The variable $lastname does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
841
			$shipping_address['email']      = $data['shippingContact']['emailAddress'];
842
			$shipping_address['phone']      = $data['shippingContact']['phoneNumber'];
843
			$shipping_address['country']    = $data['token']['card']['country'];
844
			$shipping_address['address_1']  = $data['token']['card']['address_line1'];
845
			$shipping_address['address_2']  = $data['token']['card']['address_line2'];
846
			$shipping_address['city']       = $data['token']['card']['address_city'];
847
			$shipping_address['state']      = $data['token']['card']['address_state'];
848
			$shipping_address['postcode']   = $data['token']['card']['address_zip'];
849
		}
850
851
		$order->set_address( $billing_address, 'billing' );
852
		$order->set_address( $shipping_address, 'shipping' );
853
854
		WC()->shipping->calculate_shipping( WC()->cart->get_shipping_packages() );
855
856
		// Get the rate object selected by user.
857
		foreach ( WC()->shipping->get_packages() as $package_key => $package ) {
858
			foreach ( $package['rates'] as $key => $rate ) {
859
				// Loop through user chosen shipping methods.
860
				foreach ( WC()->session->get( 'chosen_shipping_methods' ) as $method ) {
861
					if ( $method === $key ) {
862
						$order->add_shipping( $rate );
863
					}
864
				}
865
			}
866
		}
867
868
		$available_gateways = WC()->payment_gateways->get_available_payment_gateways();
869
		$order->set_payment_method( $available_gateways['stripe'] );
870
		$order->set_total( WC()->cart->shipping_total, 'shipping' );
871
		$order->set_total( WC()->cart->get_cart_discount_total(), 'cart_discount' );
872
		$order->set_total( WC()->cart->get_cart_discount_tax_total(), 'cart_discount_tax' );
873
		$order->set_total( WC()->cart->tax_total, 'tax' );
874
		$order->set_total( WC()->cart->shipping_tax_total, 'shipping_tax' );
875
		$order->set_total( WC()->cart->total );
876
877
		// If we got here, the order was created without problems!
878
		wc_transaction_query( 'commit' );
879
880
		return $order;
881
	}
882
}
883
884
new WC_Stripe_Apple_Pay();
885