Completed
Push — master ( 92e0cd...602bf5 )
by Roy
02:15
created

WC_Stripe_Apple_Pay::single_scripts()   D

Complexity

Conditions 9
Paths 18

Size

Total Lines 37
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 37
rs 4.909
c 0
b 0
f 0
cc 9
eloc 24
nc 18
nop 0
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
		} else {
80
			add_action( 'woocommerce_after_add_to_cart_quantity', array( $this, 'display_apple_pay_button' ), 1 );
81
		}
82
83
		add_action( 'woocommerce_proceed_to_checkout', array( $this, 'display_apple_pay_button' ), 1 );
84
		add_action( 'woocommerce_proceed_to_checkout', array( $this, 'display_apple_pay_separator_html' ), 2 );
85
		add_action( 'woocommerce_checkout_before_customer_details', array( $this, 'display_apple_pay_button' ), 1 );
86
		add_action( 'woocommerce_checkout_before_customer_details', array( $this, 'display_apple_pay_separator_html' ), 2 );
87
		add_action( 'wc_ajax_wc_stripe_apple_pay', array( $this, 'process_apple_pay' ) );
88
		add_action( 'wc_ajax_wc_stripe_generate_apple_pay_cart', array( $this, 'generate_apple_pay_cart' ) );
89
		add_action( 'wc_ajax_wc_stripe_generate_apple_pay_single', array( $this, 'generate_apple_pay_single' ) );
90
		add_action( 'wc_ajax_wc_stripe_apple_pay_get_shipping_methods', array( $this, 'get_shipping_methods' ) );
91
		add_action( 'wc_ajax_wc_stripe_apple_pay_update_shipping_method', array( $this, 'update_shipping_method' ) );
92
		add_filter( 'woocommerce_gateway_title', array( $this, 'filter_gateway_title' ), 10, 2 );
93
	}
94
95
	/**
96
	 * Filters the gateway title to reflect Apple Pay.
97
	 *
98
	 */
99
	public function filter_gateway_title( $title, $id ) {
100
		global $post;
101
102
		if ( ! is_object( $post ) ) {
103
			return $title;
104
		}
105
106
		$method_title = get_post_meta( $post->ID, '_payment_method_title', true );
107
108
		if ( 'stripe' === $id && ! empty( $method_title ) ) {
109
			return $method_title;
110
		}
111
112
		return $title;
113
	}
114
115
	/**
116
	 * Enqueue JS scripts and styles for single product page.
117
	 *
118
	 * @since 3.1.0
119
	 * @version 3.1.0
120
	 */
121
	public function single_scripts() {
122
		if ( ! is_single() ) {
123
			return;
124
		}
125
126
		global $post;
127
128
		$product = wc_get_product( $post->ID );
129
130
		if ( ! is_object( $product ) || ! in_array( ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->product_type : $product->get_type() ), $this->supported_product_types() ) ) {
131
			return;
132
		}
133
134
		$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
135
136
		wp_enqueue_style( 'stripe_apple_pay', plugins_url( 'assets/css/stripe-apple-pay.css', WC_STRIPE_MAIN_FILE ), array(), WC_STRIPE_VERSION );
137
138
		wp_enqueue_script( 'stripe', 'https://js.stripe.com/v2/', '', '1.0', true );
139
		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 );
140
141
		$publishable_key = 'yes' === $this->_gateway_settings['testmode'] ? $this->_gateway_settings['test_publishable_key'] : $this->_gateway_settings['publishable_key'];
142
143
		$stripe_params = array(
144
			'key'                                           => $publishable_key,
145
			'currency_code'                                 => get_woocommerce_currency(),
146
			'country_code'                                  => substr( get_option( 'woocommerce_default_country' ), 0, 2 ),
147
			'label'                                         => $this->statement_descriptor,
148
			'ajaxurl'                                       => WC_AJAX::get_endpoint( '%%endpoint%%' ),
149
			'stripe_apple_pay_nonce'                        => wp_create_nonce( '_wc_stripe_apple_pay_nonce' ),
150
			'stripe_apple_pay_cart_nonce'                   => wp_create_nonce( '_wc_stripe_apple_pay_cart_nonce' ),
151
			'stripe_apple_pay_get_shipping_methods_nonce'   => wp_create_nonce( '_wc_stripe_apple_pay_get_shipping_methods_nonce' ),
152
			'stripe_apple_pay_update_shipping_method_nonce' => wp_create_nonce( '_wc_stripe_apple_pay_update_shipping_method_nonce' ),
153
			'needs_shipping'                                => WC()->cart->needs_shipping() ? 'yes' : 'no',
154
		);
155
156
		wp_localize_script( 'woocommerce_stripe_apple_pay_single', 'wc_stripe_apple_pay_single_params', apply_filters( 'wc_stripe_apple_pay_single_params', $stripe_params ) );
157
	}
158
159
	/**
160
	 * Enqueue JS scripts and styles for the cart/checkout.
161
	 *
162
	 * @since 3.1.0
163
	 * @version 3.1.0
164
	 */
165
	public function cart_scripts() {
166
		if ( ! is_cart() && ! is_checkout() && ! isset( $_GET['pay_for_order'] ) ) {
167
			return;
168
		}
169
170
		$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
171
172
		wp_enqueue_style( 'stripe_apple_pay', plugins_url( 'assets/css/stripe-apple-pay.css', WC_STRIPE_MAIN_FILE ), array(), WC_STRIPE_VERSION );
173
174
		wp_enqueue_script( 'stripe', 'https://js.stripe.com/v2/', '', '1.0', true );
175
		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 );
176
177
		$publishable_key = 'yes' === $this->_gateway_settings['testmode'] ? $this->_gateway_settings['test_publishable_key'] : $this->_gateway_settings['publishable_key'];
178
179
		$stripe_params = array(
180
			'key'                                           => $publishable_key,
181
			'currency_code'                                 => get_woocommerce_currency(),
182
			'country_code'                                  => substr( get_option( 'woocommerce_default_country' ), 0, 2 ),
183
			'label'                                         => $this->statement_descriptor,
184
			'ajaxurl'                                       => WC_AJAX::get_endpoint( '%%endpoint%%' ),
185
			'stripe_apple_pay_nonce'                        => wp_create_nonce( '_wc_stripe_apple_pay_nonce' ),
186
			'stripe_apple_pay_cart_nonce'                   => wp_create_nonce( '_wc_stripe_apple_pay_cart_nonce' ),
187
			'stripe_apple_pay_get_shipping_methods_nonce'   => wp_create_nonce( '_wc_stripe_apple_pay_get_shipping_methods_nonce' ),
188
			'stripe_apple_pay_update_shipping_method_nonce' => wp_create_nonce( '_wc_stripe_apple_pay_update_shipping_method_nonce' ),
189
			'needs_shipping'                                => WC()->cart->needs_shipping() ? 'yes' : 'no',
190
			'is_cart_page'                                  => is_cart() ? 'yes' : 'no',
191
		);
192
193
		wp_localize_script( 'woocommerce_stripe_apple_pay', 'wc_stripe_apple_pay_params', apply_filters( 'wc_stripe_apple_pay_params', $stripe_params ) );
194
	}
195
196
	/**
197
	 * Checks to make sure product type is supported by Apple Pay.
198
	 *
199
	 */
200
	public function supported_product_types() {
201
		return array(
202
			'simple',
203
			'variable',
204
		);
205
	}
206
207
	/**
208
	 * Display Apple Pay button on the cart page
209
	 *
210
	 * @since 3.1.0
211
	 * @version 3.1.0
212
	 */
213
	public function display_apple_pay_button() {
214
		$gateways = WC()->payment_gateways->get_available_payment_gateways();
215
216
		/**
217
		 * In order for the Apple Pay button to show on cart page,
218
		 * Apple Pay must be enabled and Stripe gateway must be enabled.
219
		 */
220
		if (
221
			'yes' !== $this->_gateway_settings['apple_pay']
222
			|| ! isset( $gateways['stripe'] )
223
		) {
224
			return;
225
		}
226
227 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...
228
			global $post;
229
230
			$product = wc_get_product( $post->ID );
231
232
			if ( ! in_array( ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->product_type : $product->get_type() ), $this->supported_product_types() ) ) {
233
				return;
234
			}
235
		}
236
237
		$apple_pay_button = ! empty( $this->_gateway_settings['apple_pay_button'] ) ? $this->_gateway_settings['apple_pay_button'] : 'black';
238
		$button_lang      = ! empty( $this->_gateway_settings['apple_pay_button_lang'] ) ? strtolower( $this->_gateway_settings['apple_pay_button_lang'] ) : 'en';
239
		?>
240
		<div class="apple-pay-button-wrapper">
241
			<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>
242
		</div>
243
		<?php
244
	}
245
246
	/**
247
	 * Display Apple Pay button on the cart page
248
	 *
249
	 * @since 3.1.0
250
	 * @version 3.1.0
251
	 */
252
	public function display_apple_pay_separator_html() {
253
		$gateways = WC()->payment_gateways->get_available_payment_gateways();
254
255
		/**
256
		 * In order for the Apple Pay button to show on cart page,
257
		 * Apple Pay must be enabled and Stripe gateway must be enabled.
258
		 */
259
		if (
260
			'yes' !== $this->_gateway_settings['apple_pay']
261
			|| ! isset( $gateways['stripe'] )
262
		) {
263
			return;
264
		}
265
266 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...
267
			global $post;
268
269
			$product = wc_get_product( $post->ID );
270
271
			if ( ! in_array( ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->product_type : $product->get_type() ), $this->supported_product_types() ) ) {
272
				return;
273
			}
274
		}
275
		?>
276
		<p class="apple-pay-button-checkout-separator">- <?php esc_html_e( 'Or', 'woocommerce-gateway-stripe' ); ?> -</p>
277
		<?php
278
	}
279
280
	/**
281
	 * Generates the Apple Pay single cart.
282
	 *
283
	 * @since 3.1.0
284
	 * @version 3.1.0
285
	 */
286
	public function generate_apple_pay_single() {
287
		if ( ! wp_verify_nonce( $_POST['nonce'], '_wc_stripe_apple_pay_cart_nonce' ) ) {
288
			wp_die( __( 'Cheatin&#8217; huh?', 'woocommerce-gateway-stripe' ) );
289
		}
290
291
		if ( ! defined( 'WOOCOMMERCE_CART' ) ) {
292
			define( 'WOOCOMMERCE_CART', true );
293
		}
294
295
		WC()->shipping->reset_shipping();
296
297
		global $post;
298
299
		$product = wc_get_product( $post->ID );
300
		$qty     = absint( $_POST['qty'] );
301
302
		/**
303
		 * If this page is single product page, we need to simulate
304
		 * adding the product to the cart taken account if it is a
305
		 * simple or variable product.
306
		 */
307
		if ( is_single() ) {
308
			// First empty the cart to prevent wrong calculation.
309
			WC()->cart->empty_cart();
310
311
			if ( 'variable' === ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->product_type : $product->get_type() ) && isset( $_POST['attributes'] ) ) {
312
				$attributes = array_map( 'wc_clean', $_POST['attributes'] );
313
314
				$variation_id = $product->get_matching_variation( $attributes );
315
316
				WC()->cart->add_to_cart( $product->get_id(), $qty, $variation_id, $attributes );
317
			}
318
319
			if ( 'simple' === ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->product_type : $product->get_type() ) ) {
320
				WC()->cart->add_to_cart( $product->get_id(), $qty );
321
			}
322
		}
323
324
		WC()->cart->calculate_totals();
325
326
		wp_send_json( array( 'line_items' => $this->build_line_items(), 'total' => WC()->cart->total ) );
327
	}
328
329
	/**
330
	 * Generates the Apple Pay cart.
331
	 *
332
	 * @since 3.1.0
333
	 * @version 3.1.0
334
	 */
335
	public function generate_apple_pay_cart() {
336
		if ( ! wp_verify_nonce( $_POST['nonce'], '_wc_stripe_apple_pay_cart_nonce' ) ) {
337
			wp_die( __( 'Cheatin&#8217; huh?', 'woocommerce-gateway-stripe' ) );
338
		}
339
340
		wp_send_json( array( 'line_items' => $this->build_line_items(), 'total' => WC()->cart->total ) );
341
	}
342
343
	/**
344
	 * Calculate and set shipping method.
345
	 *
346
	 * @since 3.1.0
347
	 * @version 3.1.0
348
	 * @param array $address
349
	 */
350
	public function calculate_shipping( $address = array() ) {
351
		$country  = strtoupper( $address['countryCode'] );
352
		$state    = strtoupper( $address['administrativeArea'] );
353
		$postcode = $address['postalCode'];
354
		$city     = $address['locality'];
355
356
		WC()->shipping->reset_shipping();
357
358
		if ( $postcode && ! WC_Validation::is_postcode( $postcode, $country ) ) {
359
			throw new Exception( __( 'Please enter a valid postcode/ZIP.', 'woocommerce-gateway-stripe' ) );
360
		} elseif ( $postcode ) {
361
			$postcode = wc_format_postcode( $postcode, $country );
362
		}
363
364 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...
365
			WC()->customer->set_location( $country, $state, $postcode, $city );
366
			WC()->customer->set_shipping_location( $country, $state, $postcode, $city );
367
		} else {
368
			WC()->customer->set_to_base();
369
			WC()->customer->set_shipping_to_base();
370
		}
371
372
		WC()->customer->calculated_shipping( true );
373
374
		/**
375
		 * Set the shipping package.
376
		 *
377
		 * Note that address lines are not provided at this point
378
		 * because Apple Pay does not supply that until after
379
		 * authentication via passcode or Touch ID. We will need to
380
		 * capture this information when we process the payment.
381
		 */
382
383
		$packages = array();
384
385
		$packages[0]['contents']                 = WC()->cart->get_cart();
386
		$packages[0]['contents_cost']            = 0;
387
		$packages[0]['applied_coupons']          = WC()->cart->applied_coupons;
388
		$packages[0]['user']['ID']               = get_current_user_id();
389
		$packages[0]['destination']['country']   = $country;
390
		$packages[0]['destination']['state']     = $state;
391
		$packages[0]['destination']['postcode']  = $postcode;
392
		$packages[0]['destination']['city']      = $city;
393
394 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...
395
			if ( $item['data']->needs_shipping() ) {
396
				if ( isset( $item['line_total'] ) ) {
397
					$packages[0]['contents_cost'] += $item['line_total'];
398
				}
399
			}
400
		}
401
402
		$packages = apply_filters( 'woocommerce_cart_shipping_packages', $packages );
403
404
		WC()->shipping->calculate_shipping( $packages );
405
	}
406
407
	/**
408
	 * Gets shipping for Apple Pay Payment sheet.
409
	 *
410
	 * @since 3.1.0
411
	 * @version 3.1.0
412
	 */
413
	public function get_shipping_methods() {
414
		if ( ! wp_verify_nonce( $_POST['nonce'], '_wc_stripe_apple_pay_get_shipping_methods_nonce' ) ) {
415
			wp_die( __( 'Cheatin&#8217; huh?', 'woocommerce-gateway-stripe' ) );
416
		}
417
418
		if ( ! defined( 'WOOCOMMERCE_CART' ) ) {
419
			define( 'WOOCOMMERCE_CART', true );
420
		}
421
422
		try {
423
			$address = array_map( 'wc_clean', $_POST['address'] );
424
425
			$this->calculate_shipping( $address );
426
427
			// Set the shipping options.
428
			$currency = get_woocommerce_currency();
429
			$data     = array();
430
431
			$packages = WC()->shipping->get_packages();
432
433
			if ( ! empty( $packages ) && WC()->customer->has_calculated_shipping() ) {
434
				foreach ( $packages as $package_key => $package ) {
435
					if ( empty( $package['rates'] ) ) {
436
						throw new Exception( __( 'Unable to find shipping method for address.', 'woocommerce-gateway-stripe' ) );
437
					}
438
439
					foreach ( $package['rates'] as $key => $rate ) {
440
						$data[] = array(
441
							'id'       => $rate->id,
442
							'label'    => $rate->label,
443
							'amount'   => array(
444
								'currency' => $currency,
445
								'value'    => $rate->cost,
446
							),
447
							'selected' => false,
448
						);
449
					}
450
				}
451
452
				// Auto select the first shipping method.
453
				WC()->session->set( 'chosen_shipping_methods', array( $data[0]['id'] ) );
454
455
				WC()->cart->calculate_totals();
456
457
				wp_send_json( array( 'success' => 'true', 'shipping_methods' => $this->build_shipping_methods( $data ), 'line_items' => $this->build_line_items(), 'total' => WC()->cart->total ) );
458
			} else {
459
				throw new Exception( __( 'Unable to find shipping method for address.', 'woocommerce-gateway-stripe' ) );
460
			}
461
		} catch ( Exception $e ) {
462
			wp_send_json( array( 'success' => 'false', 'shipping_methods' => array(), 'line_items' => $this->build_line_items(), 'total' => WC()->cart->total ) );
463
		}
464
	}
465
466
	/**
467
	 * Updates shipping method on cart session.
468
	 *
469
	 * @since 3.1.0
470
	 * @version 3.1.0
471
	 */
472
	public function update_shipping_method() {
473
		if ( ! defined( 'WOOCOMMERCE_CART' ) ) {
474
			define( 'WOOCOMMERCE_CART', true );
475
		}
476
477
		if ( ! wp_verify_nonce( $_POST['nonce'], '_wc_stripe_apple_pay_update_shipping_method_nonce' ) ) {
478
			wp_die( __( 'Cheatin&#8217; huh?', 'woocommerce-gateway-stripe' ) );
479
		}
480
481
		$selected_shipping_method = array_map( 'wc_clean', $_POST['selected_shipping_method'] );
482
483
		WC()->session->set( 'chosen_shipping_methods', array( $selected_shipping_method['identifier'] ) );
484
485
		WC()->cart->calculate_totals();
486
487
		// Send back the new cart total.
488
		$currency  = get_woocommerce_currency();
489
		$tax_total = max( 0, round( WC()->cart->tax_total + WC()->cart->shipping_tax_total, WC()->cart->dp ) );
490
		$data      = array(
491
			'total' => WC()->cart->total,
492
		);
493
494
		// Include fees and taxes as displayItems.
495 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...
496
			$data['items'][] = array(
497
				'label'  => $fee->name,
498
				'amount' => array(
499
					'currency' => $currency,
500
					'value'    => $fee->amount,
501
				),
502
			);
503
		}
504 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...
505
			$data['items'][] = array(
506
				'label'  => __( 'Tax', 'woocommerce-gateway-stripe' ),
507
				'amount' => array(
508
					'currency' => $currency,
509
					'value'    => $tax_total,
510
				),
511
			);
512
		}
513
514
		wp_send_json( array( 'success' => 'true', 'line_items' => $this->build_line_items(), 'total' => WC()->cart->total ) );
515
	}
516
517
	/**
518
	 * Handles the Apple Pay processing via AJAX
519
	 *
520
	 * @access public
521
	 * @since 3.1.0
522
	 * @version 3.1.0
523
	 */
524
	public function process_apple_pay() {
525
		if ( ! wp_verify_nonce( $_POST['nonce'], '_wc_stripe_apple_pay_nonce' ) ) {
526
			wp_die( __( 'Cheatin&#8217; huh?', 'woocommerce-gateway-stripe' ) );
527
		}
528
529
		try {
530
			$result = array_map( 'wc_clean', $_POST['result'] );
531
532
			$order = $this->create_order( $result );
533
534
			$order_id = version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->id : $order->get_id();
535
536
			// Handle payment.
537
			if ( $order->get_total() > 0 ) {
538
539 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...
540
					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 ) ) );
541
				}
542
543
				WC_Stripe::log( "Info: Begin processing payment for order {$order_id} for the amount of {$order->get_total()}" );
544
545
				// Make the request.
546
				$response = WC_Stripe_API::request( $this->generate_payment_request( $order, $result['token']['id'] ) );
547
548
				if ( is_wp_error( $response ) ) {
549
					$localized_messages = $this->get_localized_messages();
550
551
					throw new Exception( ( isset( $localized_messages[ $response->get_error_code() ] ) ? $localized_messages[ $response->get_error_code() ] : $response->get_error_message() ) );
552
				}
553
554
				// Process valid response.
555
				$this->process_response( $response, $order );
556
			} else {
557
				$order->payment_complete();
558
			}
559
560
			// Remove cart.
561
			WC()->cart->empty_cart();
562
563
			update_post_meta( $order_id, '_customer_user', get_current_user_id() );
564
			update_post_meta( $order_id, '_payment_method_title', __( 'Apple Pay (Stripe)', 'woocommerce-gateway-stripe' ) );
565
566
			// Return thank you page redirect.
567
			wp_send_json( array(
568
				'success'  => 'true',
569
				'redirect' => $this->get_return_url( $order ),
570
			) );
571
572
		} catch ( Exception $e ) {
573
			WC()->session->set( 'refresh_totals', true );
574
			WC_Stripe::log( sprintf( __( 'Error: %s', 'woocommerce-gateway-stripe' ), $e->getMessage() ) );
575
576
			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...
577
				$this->send_failed_order_email( $order_id );
578
			}
579
580
			wp_send_json( array( 'success' => 'false', 'msg' => $e->getMessage() ) );
581
		}
582
	}
583
584
	/**
585
	 * Generate the request for the payment.
586
	 * @param  WC_Order $order
587
	 * @param string $source token
588
	 * @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...
589
	 */
590
	protected function generate_payment_request( $order, $source ) {
591
		$post_data                = array();
592
		$post_data['currency']    = strtolower( version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->get_order_currency() : $order->get_currency() );
593
		$post_data['amount']      = $this->get_stripe_amount( $order->get_total(), $post_data['currency'] );
594
		$post_data['description'] = sprintf( __( '%1$s - Order %2$s', 'woocommerce-gateway-stripe' ), $this->statement_descriptor, $order->get_order_number() );
595
		$post_data['capture']     = 'yes' === $this->_gateway_settings['capture'] ? 'true' : 'false';
596
597
		$billing_email      = version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_email : $order->get_billing_email();
598
599
		if ( ! empty( $billing_email ) && apply_filters( 'wc_stripe_send_stripe_receipt', false ) ) {
600
			$post_data['receipt_email'] = $billing_email;
601
		}
602
603
		$post_data['expand[]']    = 'balance_transaction';
604
		$post_data['source']      = $source;
605
606
		/**
607
		 * Filter the return value of the WC_Payment_Gateway_CC::generate_payment_request.
608
		 *
609
		 * @since 3.1.0
610
		 * @param array $post_data
611
		 * @param WC_Order $order
612
		 * @param object $source
613
		 */
614
		return apply_filters( 'wc_stripe_generate_payment_request', $post_data, $order );
615
	}
616
617
	/**
618
	 * Builds the shippings methods to pass to Apple Pay.
619
	 *
620
	 * @since 3.1.0
621
	 * @version 3.1.0
622
	 */
623
	public function build_shipping_methods( $shipping_methods ) {
624
		if ( empty( $shipping_methods ) ) {
625
			return array();
626
		}
627
628
		$shipping = array();
629
630
		foreach ( $shipping_methods as $method ) {
631
			$shipping[] = array(
632
				'label'      => $method['label'],
633
				'detail'     => '',
634
				'amount'     => $method['amount']['value'],
635
				'identifier' => $method['id'],
636
			);
637
		}
638
639
		return $shipping;
640
	}
641
642
	/**
643
	 * Builds the line items to pass to Apple Pay.
644
	 *
645
	 * @since 3.1.0
646
	 * @version 3.1.0
647
	 */
648
	public function build_line_items() {
649
		if ( ! defined( 'WOOCOMMERCE_CART' ) ) {
650
			define( 'WOOCOMMERCE_CART', true );
651
		}
652
653
		$decimals = apply_filters( 'wc_stripe_apple_pay_decimals', 2 );
654
		
655
		$items    = array();
656
		$subtotal = 0;
657
658
		foreach ( WC()->cart->get_cart() as $cart_item_key => $values ) {
659
			$amount         = wc_format_decimal( $values['line_subtotal'], $decimals );
660
			$subtotal       += $values['line_subtotal'];
661
			$quantity_label = 1 < $values['quantity'] ? ' (x' . $values['quantity'] . ')' : '';
662
663
			$item = array(
664
				'type'   => 'final',
665
				'label'  => $values['data']->post->post_title . $quantity_label,
666
				'amount' => wc_format_decimal( $amount, $decimals ),
667
			);
668
669
			$items[] = $item;
670
		}
671
672
		// Default show only subtotal instead of itemization.
673
		if ( apply_filters( 'wc_stripe_apple_pay_disable_itemization', true ) ) {
674
			$items = array();
675
			$items[] = array(
676
				'type'   => 'final',
677
				'label'  => __( 'Sub-Total', 'woocommerce-gateway-stripe' ),
678
				'amount' => wc_format_decimal( $subtotal, $decimals ),
679
			);
680
		}
681
682
		$discounts   = wc_format_decimal( WC()->cart->get_cart_discount_total(), $decimals );
683
		$tax         = wc_format_decimal( WC()->cart->tax_total + WC()->cart->shipping_tax_total, $decimals );
684
		$shipping    = wc_format_decimal( WC()->cart->shipping_total, $decimals );
685
		$item_total  = wc_format_decimal( WC()->cart->cart_contents_total, $decimals ) + $discounts;
686
		$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...
687
688
		if ( wc_tax_enabled() ) {
689
			$items[] = array(
690
				'type'   => 'final',
691
				'label'  => __( 'Tax', 'woocommerce-gateway-stripe' ),
692
				'amount' => $tax,
693
			);
694
		}
695
696 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...
697
			$items[] = array(
698
				'type'   => 'final',
699
				'label'  => __( 'Shipping', 'woocommerce-gateway-stripe' ),
700
				'amount' => $shipping,
701
			);
702
		}
703
704 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...
705
			$items[] = array(
706
				'type'   => 'final',
707
				'label'  => __( 'Discount', 'woocommerce-gateway-stripe' ),
708
				'amount' => '-' . $discounts,
709
			);
710
		}
711
712
		return $items;
713
	}
714
715
	/**
716
	 * Create order programatically.
717
	 *
718
	 * @since 3.1.0
719
	 * @version 3.1.0
720
	 * @param array $data
721
	 * @return object $order
722
	 */
723
	public function create_order( $data = array() ) {
724
		if ( empty( $data ) ) {
725
			throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-gateway-stripe' ), 520 ) );
726
		}
727
728
		$order = wc_create_order();
729
		$order_id = version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->id : $order->get_id();
730
731
		if ( is_wp_error( $order ) ) {
732
			throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-gateway-stripe' ), 520 ) );
733
		} elseif ( false === $order ) {
734
			throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-gateway-stripe' ), 521 ) );
735
		} else {
736
			do_action( 'woocommerce_new_order', $order_id );
737
		}
738
739
		// Store the line items to the new/resumed order
740
		foreach ( WC()->cart->get_cart() as $cart_item_key => $values ) {
741
			$item_id = $order->add_product(
742
				$values['data'],
743
				$values['quantity'],
744
				array(
745
					'variation' => $values['variation'],
746
					'totals'    => array(
747
						'subtotal'     => $values['line_subtotal'],
748
						'subtotal_tax' => $values['line_subtotal_tax'],
749
						'total'        => $values['line_total'],
750
						'tax'          => $values['line_tax'],
751
						'tax_data'     => $values['line_tax_data'], // Since 2.2
752
					),
753
				)
754
			);
755
756
			if ( ! $item_id ) {
757
				throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-gateway-stripe' ), 525 ) );
758
			}
759
760
			// Allow plugins to add order item meta
761
			do_action( 'woocommerce_add_order_item_meta', $item_id, $values, $cart_item_key );
762
		}
763
764
		// Store fees
765
		foreach ( WC()->cart->get_fees() as $fee_key => $fee ) {
766
			$item_id = $order->add_fee( $fee );
767
768
			if ( ! $item_id ) {
769
				throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-gateway-stripe' ), 526 ) );
770
			}
771
772
			// Allow plugins to add order item meta to fees
773
			do_action( 'woocommerce_add_order_fee_meta', $order_id, $item_id, $fee, $fee_key );
774
		}
775
776
		// Store tax rows
777
		foreach ( array_keys( WC()->cart->taxes + WC()->cart->shipping_taxes ) as $tax_rate_id ) {
778
			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 ) {
779
				throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-gateway-stripe' ), 528 ) );
780
			}
781
		}
782
783
		// Store coupons
784
		foreach ( WC()->cart->get_coupons() as $code => $coupon ) {
785
			if ( ! $order->add_coupon( $code, WC()->cart->get_coupon_discount_amount( $code ), WC()->cart->get_coupon_discount_tax_amount( $code ) ) ) {
786
				throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-gateway-stripe' ), 529 ) );
787
			}
788
		}
789
790
		// Billing address
791
		$billing_address = array();
792
		if ( ! empty( $data['token']['card'] ) ) {
793
			// Name from Stripe is a full name string.
794
			$name                          = explode( ' ', $data['token']['card']['name'] );
795
			$lastname                      = array_pop( $name );
796
			$firstname                     = implode( ' ', $name );
797
			$billing_address['first_name'] = $firstname;
798
			$billing_address['last_name']  = $lastname;
799
			$billing_address['email']      = $data['shippingContact']['emailAddress'];
800
			$billing_address['phone']      = $data['shippingContact']['phoneNumber'];
801
			$billing_address['country']    = $data['token']['card']['country'];
802
			$billing_address['address_1']  = $data['token']['card']['address_line1'];
803
			$billing_address['address_2']  = $data['token']['card']['address_line2'];
804
			$billing_address['city']       = $data['token']['card']['address_city'];
805
			$billing_address['state']      = $data['token']['card']['address_state'];
806
			$billing_address['postcode']   = $data['token']['card']['address_zip'];
807
		}
808
809
		// Shipping address.
810
		$shipping_address = array();
811
		if ( WC()->cart->needs_shipping() && ! empty( $data['shippingContact'] ) ) {
812
			$shipping_address['first_name'] = $data['shippingContact']['givenName'];
813
			$shipping_address['last_name']  = $data['shippingContact']['familyName'];
814
			$shipping_address['email']      = $data['shippingContact']['emailAddress'];
815
			$shipping_address['phone']      = $data['shippingContact']['phoneNumber'];
816
			$shipping_address['country']    = $data['shippingContact']['countryCode'];
817
			$shipping_address['address_1']  = $data['shippingContact']['addressLines'][0];
818
			$shipping_address['address_2']  = $data['shippingContact']['addressLines'][1];
819
			$shipping_address['city']       = $data['shippingContact']['locality'];
820
			$shipping_address['state']      = $data['shippingContact']['administrativeArea'];
821
			$shipping_address['postcode']   = $data['shippingContact']['postalCode'];
822
		} elseif ( ! empty( $data['shippingContact'] ) ) {
823
			$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...
824
			$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...
825
			$shipping_address['email']      = $data['shippingContact']['emailAddress'];
826
			$shipping_address['phone']      = $data['shippingContact']['phoneNumber'];
827
			$shipping_address['country']    = $data['token']['card']['country'];
828
			$shipping_address['address_1']  = $data['token']['card']['address_line1'];
829
			$shipping_address['address_2']  = $data['token']['card']['address_line2'];
830
			$shipping_address['city']       = $data['token']['card']['address_city'];
831
			$shipping_address['state']      = $data['token']['card']['address_state'];
832
			$shipping_address['postcode']   = $data['token']['card']['address_zip'];
833
		}
834
835
		$order->set_address( $billing_address, 'billing' );
836
		$order->set_address( $shipping_address, 'shipping' );
837
838
		WC()->shipping->calculate_shipping( WC()->cart->get_shipping_packages() );
839
840
		// Get the rate object selected by user.
841
		foreach ( WC()->shipping->get_packages() as $package_key => $package ) {
842
			foreach ( $package['rates'] as $key => $rate ) {
843
				// Loop through user chosen shipping methods.
844
				foreach ( WC()->session->get( 'chosen_shipping_methods' ) as $method ) {
845
					if ( $method === $key ) {
846
						$order->add_shipping( $rate );
847
					}
848
				}
849
			}
850
		}
851
852
		$available_gateways = WC()->payment_gateways->get_available_payment_gateways();
853
		$order->set_payment_method( $available_gateways['stripe'] );
854
		$order->set_total( WC()->cart->shipping_total, 'shipping' );
855
		$order->set_total( WC()->cart->get_cart_discount_total(), 'cart_discount' );
856
		$order->set_total( WC()->cart->get_cart_discount_tax_total(), 'cart_discount_tax' );
857
		$order->set_total( WC()->cart->tax_total, 'tax' );
858
		$order->set_total( WC()->cart->shipping_tax_total, 'shipping_tax' );
859
		$order->set_total( WC()->cart->total );
860
861
		// If we got here, the order was created without problems!
862
		wc_transaction_query( 'commit' );
863
864
		return $order;
865
	}
866
}
867
868
new WC_Stripe_Apple_Pay();
869