Completed
Push — master ( 80fce1...d70a50 )
by Roy
03:30
created

WC_Stripe_Apple_Pay::__construct()   F

Complexity

Conditions 20
Paths > 20000

Size

Total Lines 28
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 28
rs 2.6863
c 0
b 0
f 0
cc 20
eloc 18
nc 393216
nop 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
	 * Statement Description
21
	 *
22
	 * @var
23
	 */
24
	public $statement_descriptor;
25
26
	/**
27
	 * Check if we capture the transaction immediately.
28
	 *
29
	 * @var bool
30
	 */
31
	public $capture;
32
33
	/**
34
	 * Do we accept Apple Pay?
35
	 *
36
	 * @var bool
37
	 */
38
	public $apple_pay;
39
40
	/**
41
	 * Apple Pay button style.
42
	 *
43
	 * @var bool
44
	 */
45
	public $apple_pay_button;
46
47
	/**
48
	 * Apple Pay button language.
49
	 *
50
	 * @var bool
51
	 */
52
	public $apple_pay_button_lang;
53
54
	/**
55
	 * Is test mode active?
56
	 *
57
	 * @var bool
58
	 */
59
	public $testmode;
60
61
	/**
62
	 * Logging enabled?
63
	 *
64
	 * @var bool
65
	 */
66
	public $logging;
67
68
	/**
69
	 * Should we store the users credit cards?
70
	 *
71
	 * @var bool
72
	 */
73
	public $saved_cards;
74
75
	/**
76
	 * Publishable key credentials.
77
	 *
78
	 * @var bool
79
	 */
80
	public $this->publishable_key;
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected T_OBJECT_OPERATOR, expecting ',' or ';'
Loading history...
81
82
	/**
83
	 * Constructor.
84
	 *
85
	 * @access public
86
	 * @since 3.1.0
87
	 * @version 3.1.0
88
	 */
89
	public function __construct() {
90
		self::$_this = $this;
91
92
		$gateway_settings = get_option( 'woocommerce_stripe_settings', '' );
93
94
		$this->statement_descriptor = ! empty( $gateway_settings['statement_descriptor'] ) ? $gateway_settings['statement_descriptor'] : wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES );
95
96
		// If both site title and statement descriptor is not set. Fallback.
97
		if ( empty( $this->statement_descriptor ) ) {
98
			$this->statement_descriptor = $_SERVER['SERVER_NAME'];
99
		}
100
101
		$this->enabled               = ( ! empty( $gateway_settings['enabled'] ) && 'yes' === $gateway_settings['enabled'] ) ? true : false;
102
		$this->testmode              = ( ! empty( $gateway_settings['testmode'] ) && 'yes' === $gateway_settings['testmode'] ) ? true : false;
103
		$this->capture               = ( ! empty( $gateway_settings['capture'] ) && 'yes' === $gateway_settings['capture'] ) ? true : false;
104
		$this->saved_cards           = ( ! empty( $gateway_settings['saved_cards'] ) && 'yes' === $gateway_settings['saved_cards'] ) ? true : false;
105
		$this->apple_pay             = ( ! empty( $gateway_settings['apple_pay'] ) && 'yes' === $gateway_settings['apple_pay'] ) ? true : false;
106
		$this->apple_pay_button      = ! empty( $gateway_settings['apple_pay_button'] ) ? $gateway_settings['apple_pay_button'] : 'black';
107
		$this->apple_pay_button_lang = ! empty( $gateway_settings['apple_pay_button_lang'] ) ? $gateway_settings['apple_pay_button_lang'] : 'en';
108
		$this->logging               = ( ! empty( $gateway_settings['logging'] ) && 'yes' === $gateway_settings['logging'] ) ? true : false;
109
		$this->publishable_key       = ! empty( $gateway_settings['publishable_key'] ) ? $gateway_settings['publishable_key'] : '';
110
111
		if ( $this->testmode ) {
112
			$this->publishable_key = ! empty( $gateway_settings['test_publishable_key'] ) ? $gateway_settings['test_publishable_key'] : '';
113
		}
114
115
		$this->init();
116
	}
117
118
	public function instance() {
119
		return self::$_this;
120
	}
121
122
	/**
123
	 * Initialize.
124
	 *
125
	 * @access public
126
	 * @since 3.1.0
127
	 * @version 3.1.4
128
	 */
129
	public function init() {
130
		// If Apple Pay is not enabled no need to proceed further.
131
		if ( ! $this->apple_pay ) {
132
			return;
133
		}
134
135
		add_action( 'wp_enqueue_scripts', array( $this, 'cart_scripts' ) );
136
		add_action( 'wp_enqueue_scripts', array( $this, 'single_scripts' ) );
137
138
		/**
139
		 * In order to display the Apple Pay button in the correct position,
140
		 * a new hook was added to WooCommerce 3.0. In older versions of WooCommerce,
141
		 * CSS is used to position the button.
142
		 */
143
		if ( version_compare( WC_VERSION, '3.0.0', '<' ) ) {
144
			add_action( 'woocommerce_after_add_to_cart_button', array( $this, 'display_apple_pay_button' ), 1 );
145
		} else {
146
			add_action( 'woocommerce_after_add_to_cart_quantity', array( $this, 'display_apple_pay_button' ), 1 );
147
		}
148
149
		add_action( 'woocommerce_proceed_to_checkout', array( $this, 'display_apple_pay_button' ), 1 );
150
		add_action( 'woocommerce_proceed_to_checkout', array( $this, 'display_apple_pay_separator_html' ), 2 );
151
		add_action( 'woocommerce_checkout_before_customer_details', array( $this, 'display_apple_pay_button' ), 1 );
152
		add_action( 'woocommerce_checkout_before_customer_details', array( $this, 'display_apple_pay_separator_html' ), 2 );
153
		add_action( 'wc_ajax_wc_stripe_log_apple_pay_errors', array( $this, 'log_apple_pay_errors' ) );
154
		add_action( 'wc_ajax_wc_stripe_apple_pay', array( $this, 'process_apple_pay' ) );
155
		add_action( 'wc_ajax_wc_stripe_generate_apple_pay_cart', array( $this, 'generate_apple_pay_cart' ) );
156
		add_action( 'wc_ajax_wc_stripe_apple_pay_clear_cart', array( $this, 'clear_cart' ) );
157
		add_action( 'wc_ajax_wc_stripe_generate_apple_pay_single', array( $this, 'generate_apple_pay_single' ) );
158
		add_action( 'wc_ajax_wc_stripe_apple_pay_get_shipping_methods', array( $this, 'get_shipping_methods' ) );
159
		add_action( 'wc_ajax_wc_stripe_apple_pay_update_shipping_method', array( $this, 'update_shipping_method' ) );
160
		add_filter( 'woocommerce_gateway_title', array( $this, 'filter_gateway_title' ), 10, 2 );
161
		add_filter( 'woocommerce_validate_postcode', array( $this, 'postal_code_validation' ), 10, 3 );
162
	}
163
164
	/**
165
	 * Filters the gateway title to reflect Apple Pay.
166
	 *
167
	 */
168
	public function filter_gateway_title( $title, $id ) {
169
		global $post;
170
171
		if ( ! is_object( $post ) ) {
172
			return $title;
173
		}
174
175
		$method_title = get_post_meta( $post->ID, '_payment_method_title', true );
176
177
		if ( 'stripe' === $id && ! empty( $method_title ) ) {
178
			return $method_title;
179
		}
180
181
		return $title;
182
	}
183
184
	/**
185
	 * Log errors coming from Apple Pay.
186
	 *
187
	 * @since 3.1.4
188
	 * @version 3.1.4
189
	 */
190
	public function log_apple_pay_errors() {
191
		if ( ! wp_verify_nonce( $_POST['nonce'], '_wc_stripe_apple_pay_nonce' ) && ! wp_verify_nonce( $_POST['nonce'], '_wc_stripe_apple_pay_cart_nonce' ) ) {
192
			wp_die( __( 'Cheatin&#8217; huh?', 'woocommerce-gateway-stripe' ) );
193
		}
194
195
		$errors = wc_clean( stripslashes( $_POST['errors'] ) );
196
197
		$this->log( 'Apple Pay: ' . $errors );
198
199
		exit;
200
	}
201
202
	/**
203
	 * Removes postal code validation from WC.
204
	 *
205
	 * @since 3.1.4
206
	 * @version 3.1.4
207
	 */
208
	public function postal_code_validation( $valid, $postcode, $country ) {
209
		$gateways = WC()->payment_gateways->get_available_payment_gateways();
210
211
		if ( ! $this->apple_pay || ! isset( $gateways['stripe'] ) ) {
212
			return $valid;
213
		}
214
215
		/**
216
		 * Currently Apple Pay truncates postal codes from UK and Canada to first 3 characters
217
		 * when passing it back from the shippingcontactselected object. This causes WC to invalidate
218
		 * the order and not let it go through. The remedy for now is just to remove this validation.
219
		 */
220
		if ( 'GB' === $country || 'CA' === $country ) {
221
			return true;
222
		}
223
224
		return $valid;
225
	}
226
227
	/**
228
	 * Enqueue JS scripts and styles for single product page.
229
	 *
230
	 * @since 3.1.0
231
	 * @version 3.1.4
232
	 */
233
	public function single_scripts() {
234
		if ( ! is_single() ) {
235
			return;
236
		}
237
238
		global $post;
239
240
		$product = wc_get_product( $post->ID );
241
242
		if ( ! is_object( $product ) || ! in_array( ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->product_type : $product->get_type() ), $this->supported_product_types() ) ) {
243
			return;
244
		}
245
246
		$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
247
248
		wp_enqueue_style( 'stripe_apple_pay', plugins_url( 'assets/css/stripe-apple-pay.css', WC_STRIPE_MAIN_FILE ), array(), WC_STRIPE_VERSION );
249
250
		wp_enqueue_script( 'stripe', 'https://js.stripe.com/v2/', '', '1.0', true );
251
		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 );
252
253
		$stripe_params = array(
254
			'key'                                           => $this->publishable_key,
255
			'currency_code'                                 => get_woocommerce_currency(),
256
			'country_code'                                  => substr( get_option( 'woocommerce_default_country' ), 0, 2 ),
257
			'label'                                         => $this->statement_descriptor,
258
			'ajaxurl'                                       => WC_AJAX::get_endpoint( '%%endpoint%%' ),
259
			'stripe_apple_pay_nonce'                        => wp_create_nonce( '_wc_stripe_apple_pay_nonce' ),
260
			'stripe_apple_pay_cart_nonce'                   => wp_create_nonce( '_wc_stripe_apple_pay_cart_nonce' ),
261
			'stripe_apple_pay_get_shipping_methods_nonce'   => wp_create_nonce( '_wc_stripe_apple_pay_get_shipping_methods_nonce' ),
262
			'stripe_apple_pay_update_shipping_method_nonce' => wp_create_nonce( '_wc_stripe_apple_pay_update_shipping_method_nonce' ),
263
			'needs_shipping'                                => $product->needs_shipping() ? 'yes' : 'no',
264
			'i18n'                                          => array(
265
				'sub_total' => __( 'Sub-Total', 'woocommerce-gateway-stripe' ),
266
			),
267
		);
268
269
		wp_localize_script( 'woocommerce_stripe_apple_pay_single', 'wc_stripe_apple_pay_single_params', apply_filters( 'wc_stripe_apple_pay_single_params', $stripe_params ) );
270
	}
271
272
	/**
273
	 * Enqueue JS scripts and styles for the cart/checkout.
274
	 *
275
	 * @since 3.1.0
276
	 * @version 3.1.0
277
	 */
278
	public function cart_scripts() {
279
		if ( ! is_cart() && ! is_checkout() && ! isset( $_GET['pay_for_order'] ) ) {
280
			return;
281
		}
282
283
		$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
284
285
		wp_enqueue_style( 'stripe_apple_pay', plugins_url( 'assets/css/stripe-apple-pay.css', WC_STRIPE_MAIN_FILE ), array(), WC_STRIPE_VERSION );
286
287
		wp_enqueue_script( 'stripe', 'https://js.stripe.com/v2/', '', '1.0', true );
288
		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 );
289
290
		$stripe_params = array(
291
			'key'                                           => $this->publishable_key,
292
			'currency_code'                                 => get_woocommerce_currency(),
293
			'country_code'                                  => substr( get_option( 'woocommerce_default_country' ), 0, 2 ),
294
			'label'                                         => $this->statement_descriptor,
295
			'ajaxurl'                                       => WC_AJAX::get_endpoint( '%%endpoint%%' ),
296
			'stripe_apple_pay_nonce'                        => wp_create_nonce( '_wc_stripe_apple_pay_nonce' ),
297
			'stripe_apple_pay_cart_nonce'                   => wp_create_nonce( '_wc_stripe_apple_pay_cart_nonce' ),
298
			'stripe_apple_pay_get_shipping_methods_nonce'   => wp_create_nonce( '_wc_stripe_apple_pay_get_shipping_methods_nonce' ),
299
			'stripe_apple_pay_update_shipping_method_nonce' => wp_create_nonce( '_wc_stripe_apple_pay_update_shipping_method_nonce' ),
300
			'needs_shipping'                                => WC()->cart->needs_shipping() ? 'yes' : 'no',
301
			'is_cart_page'                                  => is_cart() ? 'yes' : 'no',
302
		);
303
304
		wp_localize_script( 'woocommerce_stripe_apple_pay', 'wc_stripe_apple_pay_params', apply_filters( 'wc_stripe_apple_pay_params', $stripe_params ) );
305
	}
306
307
	/**
308
	 * Checks to make sure product type is supported by Apple Pay.
309
	 *
310
	 */
311
	public function supported_product_types() {
312
		return array(
313
			'simple',
314
			'variable',
315
		);
316
	}
317
318
	/**
319
	 * Display Apple Pay button on the cart page
320
	 *
321
	 * @since 3.1.0
322
	 * @version 3.1.0
323
	 */
324
	public function display_apple_pay_button() {
325
		$gateways = WC()->payment_gateways->get_available_payment_gateways();
326
327
		/**
328
		 * In order for the Apple Pay button to show on product detail page,
329
		 * Apple Pay must be enabled and Stripe gateway must be enabled.
330
		 */
331
		if ( ! $this->apple_pay || ! isset( $gateways['stripe'] ) ) {
332
			return;
333
		}
334
335
		if ( is_single() ) {
336
			global $post;
337
338
			$product = wc_get_product( $post->ID );
339
340
			if ( ! is_object( $product ) || ! in_array( ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->product_type : $product->get_type() ), $this->supported_product_types() ) ) {
341
				return;
342
			}
343
		}
344
345
		?>
346
		<div class="apple-pay-button-wrapper">
347
			<button class="apple-pay-button" lang="<?php echo esc_attr( $this->apple_pay_button_lang ); ?>" style="-webkit-appearance: -apple-pay-button; -apple-pay-button-type: buy; -apple-pay-button-style: <?php echo esc_attr( $this->apple_pay_button ); ?>;"></button>
348
		</div>
349
		<?php
350
	}
351
352
	/**
353
	 * Display Apple Pay button on the cart page
354
	 *
355
	 * @since 3.1.0
356
	 * @version 3.1.0
357
	 */
358
	public function display_apple_pay_separator_html() {
359
		$gateways = WC()->payment_gateways->get_available_payment_gateways();
360
361
		/**
362
		 * In order for the Apple Pay button to show on cart page,
363
		 * Apple Pay must be enabled and Stripe gateway must be enabled.
364
		 */
365
		if ( ! $this->apple_pay || ! isset( $gateways['stripe'] ) ) {
366
			return;
367
		}
368
369
		if ( is_single() ) {
370
			global $post;
371
372
			$product = wc_get_product( $post->ID );
373
374
			if ( ! is_object( $product ) || ! in_array( ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->product_type : $product->get_type() ), $this->supported_product_types() ) ) {
375
				return;
376
			}
377
		}
378
		?>
379
		<p class="apple-pay-button-checkout-separator">- <?php esc_html_e( 'Or', 'woocommerce-gateway-stripe' ); ?> -</p>
380
		<?php
381
	}
382
383
	/**
384
	 * Generates the Apple Pay single cart.
385
	 *
386
	 * @since 3.1.0
387
	 * @version 3.1.0
388
	 */
389
	public function generate_apple_pay_single() {
390
		if ( ! wp_verify_nonce( $_POST['nonce'], '_wc_stripe_apple_pay_cart_nonce' ) ) {
391
			wp_die( __( 'Cheatin&#8217; huh?', 'woocommerce-gateway-stripe' ) );
392
		}
393
394
		if ( ! defined( 'WOOCOMMERCE_CART' ) ) {
395
			define( 'WOOCOMMERCE_CART', true );
396
		}
397
398
		WC()->shipping->reset_shipping();
399
400
		global $post;
401
402
		$product = wc_get_product( $post->ID );
403
		$qty     = ! isset( $_POST['qty'] ) ? 1 : absint( $_POST['qty'] );
404
405
		/**
406
		 * If this page is single product page, we need to simulate
407
		 * adding the product to the cart taken account if it is a
408
		 * simple or variable product.
409
		 */
410
		if ( is_single() ) {
411
			// First empty the cart to prevent wrong calculation.
412
			WC()->cart->empty_cart();
413
414
			if ( 'variable' === ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->product_type : $product->get_type() ) && isset( $_POST['attributes'] ) ) {
415
				$attributes = array_map( 'wc_clean', $_POST['attributes'] );
416
417
				if ( version_compare( WC_VERSION, '3.0.0', '<' ) ) {
418
					$variation_id = $product->get_matching_variation( $attributes );
419
				} else {
420
					$data_store = WC_Data_Store::load( 'product' );
421
					$variation_id = $data_store->find_matching_product_variation( $product, $attributes );
422
				}
423
424
				WC()->cart->add_to_cart( $product->get_id(), $qty, $variation_id, $attributes );
425
			}
426
427
			if ( 'simple' === ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->product_type : $product->get_type() ) ) {
428
				WC()->cart->add_to_cart( $product->get_id(), $qty );
429
			}
430
		}
431
432
		WC()->cart->calculate_totals();
433
434
		wp_send_json( array( 'line_items' => $this->build_line_items(), 'total' => WC()->cart->total ) );
435
	}
436
437
	/**
438
	 * Generates the Apple Pay cart.
439
	 *
440
	 * @since 3.1.0
441
	 * @version 3.1.0
442
	 */
443
	public function generate_apple_pay_cart() {
444
		if ( ! wp_verify_nonce( $_POST['nonce'], '_wc_stripe_apple_pay_cart_nonce' ) ) {
445
			wp_die( __( 'Cheatin&#8217; huh?', 'woocommerce-gateway-stripe' ) );
446
		}
447
448
		wp_send_json( array( 'line_items' => $this->build_line_items(), 'total' => WC()->cart->total ) );
449
	}
450
451
	/**
452
	 * Clears Apple Pay cart.
453
	 *
454
	 * @since 3.1.4
455
	 * @version 3.1.4
456
	 */
457
	public function clear_cart() {
458
		WC()->cart->empty_cart();
459
		exit;
460
	}
461
462
	/**
463
	 * Calculate and set shipping method.
464
	 *
465
	 * @since 3.1.0
466
	 * @version 3.1.0
467
	 * @param array $address
468
	 */
469
	public function calculate_shipping( $address = array() ) {
470
		$country  = strtoupper( $address['countryCode'] );
471
		$state    = strtoupper( $address['administrativeArea'] );
472
		$postcode = $address['postalCode'];
473
		$city     = $address['locality'];
474
475
		WC()->shipping->reset_shipping();
476
477
		if ( $postcode && ! WC_Validation::is_postcode( $postcode, $country ) ) {
478
			throw new Exception( __( 'Please enter a valid postcode/ZIP.', 'woocommerce-gateway-stripe' ) );
479
		} elseif ( $postcode ) {
480
			$postcode = wc_format_postcode( $postcode, $country );
481
		}
482
483
		if ( $country ) {
484
			WC()->customer->set_location( $country, $state, $postcode, $city );
485
			WC()->customer->set_shipping_location( $country, $state, $postcode, $city );
486
		} else {
487
			WC()->customer->set_to_base();
488
			WC()->customer->set_shipping_to_base();
489
		}
490
491
		WC()->customer->calculated_shipping( true );
492
493
		/**
494
		 * Set the shipping package.
495
		 *
496
		 * Note that address lines are not provided at this point
497
		 * because Apple Pay does not supply that until after
498
		 * authentication via passcode or Touch ID. We will need to
499
		 * capture this information when we process the payment.
500
		 */
501
502
		$packages = array();
503
504
		$packages[0]['contents']                 = WC()->cart->get_cart();
505
		$packages[0]['contents_cost']            = 0;
506
		$packages[0]['applied_coupons']          = WC()->cart->applied_coupons;
507
		$packages[0]['user']['ID']               = get_current_user_id();
508
		$packages[0]['destination']['country']   = $country;
509
		$packages[0]['destination']['state']     = $state;
510
		$packages[0]['destination']['postcode']  = $postcode;
511
		$packages[0]['destination']['city']      = $city;
512
513
		foreach ( WC()->cart->get_cart() as $item ) {
514
			if ( $item['data']->needs_shipping() ) {
515
				if ( isset( $item['line_total'] ) ) {
516
					$packages[0]['contents_cost'] += $item['line_total'];
517
				}
518
			}
519
		}
520
521
		$packages = apply_filters( 'woocommerce_cart_shipping_packages', $packages );
522
523
		WC()->shipping->calculate_shipping( $packages );
524
	}
525
526
	/**
527
	 * Gets shipping for Apple Pay Payment sheet.
528
	 *
529
	 * @since 3.1.0
530
	 * @version 3.1.0
531
	 */
532
	public function get_shipping_methods() {
533
		if ( ! wp_verify_nonce( $_POST['nonce'], '_wc_stripe_apple_pay_get_shipping_methods_nonce' ) ) {
534
			wp_die( __( 'Cheatin&#8217; huh?', 'woocommerce-gateway-stripe' ) );
535
		}
536
537
		if ( ! defined( 'WOOCOMMERCE_CART' ) ) {
538
			define( 'WOOCOMMERCE_CART', true );
539
		}
540
541
		try {
542
			$address = array_map( 'wc_clean', $_POST['address'] );
543
544
			$this->calculate_shipping( $address );
545
546
			// Set the shipping options.
547
			$currency = get_woocommerce_currency();
548
			$data     = array();
549
550
			$packages = WC()->shipping->get_packages();
551
552
			if ( ! empty( $packages ) && WC()->customer->has_calculated_shipping() ) {
553
				foreach ( $packages as $package_key => $package ) {
554
					if ( empty( $package['rates'] ) ) {
555
						throw new Exception( __( 'Unable to find shipping method for address.', 'woocommerce-gateway-stripe' ) );
556
					}
557
558
					foreach ( $package['rates'] as $key => $rate ) {
559
						$data[] = array(
560
							'id'       => $rate->id,
561
							'label'    => $rate->label,
562
							'amount'   => array(
563
								'currency' => $currency,
564
								'value'    => $rate->cost,
565
							),
566
							'selected' => false,
567
						);
568
					}
569
				}
570
571
				// Auto select the first shipping method.
572
				WC()->session->set( 'chosen_shipping_methods', array( $data[0]['id'] ) );
573
574
				WC()->cart->calculate_totals();
575
576
				wp_send_json( array( 'success' => 'true', 'shipping_methods' => $this->build_shipping_methods( $data ), 'line_items' => $this->build_line_items(), 'total' => WC()->cart->total ) );
577
			} else {
578
				throw new Exception( __( 'Unable to find shipping method for address.', 'woocommerce-gateway-stripe' ) );
579
			}
580
		} catch ( Exception $e ) {
581
			wp_send_json( array( 'success' => 'false', 'shipping_methods' => array(), 'line_items' => $this->build_line_items(), 'total' => WC()->cart->total ) );
582
		}
583
	}
584
585
	/**
586
	 * Updates shipping method on cart session.
587
	 *
588
	 * @since 3.1.0
589
	 * @version 3.1.0
590
	 */
591
	public function update_shipping_method() {
592
		if ( ! defined( 'WOOCOMMERCE_CART' ) ) {
593
			define( 'WOOCOMMERCE_CART', true );
594
		}
595
596
		if ( ! wp_verify_nonce( $_POST['nonce'], '_wc_stripe_apple_pay_update_shipping_method_nonce' ) ) {
597
			wp_die( __( 'Cheatin&#8217; huh?', 'woocommerce-gateway-stripe' ) );
598
		}
599
600
		$selected_shipping_method = array_map( 'wc_clean', $_POST['selected_shipping_method'] );
601
602
		WC()->session->set( 'chosen_shipping_methods', array( $selected_shipping_method['identifier'] ) );
603
604
		WC()->cart->calculate_totals();
605
606
		// Send back the new cart total.
607
		$currency  = get_woocommerce_currency();
608
		$tax_total = max( 0, round( WC()->cart->tax_total + WC()->cart->shipping_tax_total, WC()->cart->dp ) );
609
		$data      = array(
610
			'total' => WC()->cart->total,
611
		);
612
613
		// Include fees and taxes as displayItems.
614
		foreach ( WC()->cart->fees as $key => $fee ) {
615
			$data['items'][] = array(
616
				'label'  => $fee->name,
617
				'amount' => array(
618
					'currency' => $currency,
619
					'value'    => $fee->amount,
620
				),
621
			);
622
		}
623
		if ( 0 < $tax_total ) {
624
			$data['items'][] = array(
625
				'label'  => __( 'Tax', 'woocommerce-gateway-stripe' ),
626
				'amount' => array(
627
					'currency' => $currency,
628
					'value'    => $tax_total,
629
				),
630
			);
631
		}
632
633
		wp_send_json( array( 'success' => 'true', 'line_items' => $this->build_line_items(), 'total' => WC()->cart->total ) );
634
	}
635
636
	/**
637
	 * Handles the Apple Pay processing via AJAX
638
	 *
639
	 * @access public
640
	 * @since 3.1.0
641
	 * @version 3.1.0
642
	 */
643
	public function process_apple_pay() {
644
		if ( ! wp_verify_nonce( $_POST['nonce'], '_wc_stripe_apple_pay_nonce' ) ) {
645
			wp_die( __( 'Cheatin&#8217; huh?', 'woocommerce-gateway-stripe' ) );
646
		}
647
648
		try {
649
			$result = array_map( 'wc_clean', $_POST['result'] );
650
651
			$order = $this->create_order( $result );
652
653
			$order_id = version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->id : $order->get_id();
654
655
			// Handle payment.
656
			if ( $order->get_total() > 0 ) {
657
658
				if ( $order->get_total() * 100 < WC_Stripe::get_minimum_amount() ) {
659
					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 ) ) );
660
				}
661
662
				$this->log( "Info: Begin processing payment for order {$order_id} for the amount of {$order->get_total()}" );
663
664
				// Make the request.
665
				$response = WC_Stripe_API::request( $this->generate_payment_request( $order, $result['token']['id'] ) );
666
667
				if ( is_wp_error( $response ) ) {
668
					$localized_messages = $this->get_localized_messages();
669
670
					throw new Exception( ( isset( $localized_messages[ $response->get_error_code() ] ) ? $localized_messages[ $response->get_error_code() ] : $response->get_error_message() ) );
671
				}
672
673
				// Process valid response.
674
				$this->process_response( $response, $order );
675
			} else {
676
				$order->payment_complete();
677
			}
678
679
			// Remove cart.
680
			WC()->cart->empty_cart();
681
682
			update_post_meta( $order_id, '_customer_user', get_current_user_id() );
683
			update_post_meta( $order_id, '_payment_method_title', __( 'Apple Pay (Stripe)', 'woocommerce-gateway-stripe' ) );
684
685
			// Return thank you page redirect.
686
			wp_send_json( array(
687
				'success'  => 'true',
688
				'redirect' => $this->get_return_url( $order ),
689
			) );
690
691
		} catch ( Exception $e ) {
692
			WC()->session->set( 'refresh_totals', true );
693
			$this->log( sprintf( __( 'Error: %s', 'woocommerce-gateway-stripe' ), $e->getMessage() ) );
694
695
			if ( is_object( $order ) && isset( $order_id ) && $order->has_status( array( 'pending', 'failed' ) ) ) {
696
				$this->send_failed_order_email( $order_id );
697
			}
698
699
			wp_send_json( array( 'success' => 'false', 'msg' => $e->getMessage() ) );
700
		}
701
	}
702
703
	/**
704
	 * Generate the request for the payment.
705
	 * @param  WC_Order $order
706
	 * @param string $source token
707
	 * @return array()
708
	 */
709
	protected function generate_payment_request( $order, $source ) {
710
		$post_data                = array();
711
		$post_data['currency']    = strtolower( version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->get_order_currency() : $order->get_currency() );
712
		$post_data['amount']      = $this->get_stripe_amount( $order->get_total(), $post_data['currency'] );
713
		$post_data['description'] = sprintf( __( '%1$s - Order %2$s', 'woocommerce-gateway-stripe' ), $this->statement_descriptor, $order->get_order_number() );
714
		$post_data['capture']     = $this->capture ? 'true' : 'false';
715
716
		$billing_email      = version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_email : $order->get_billing_email();
717
718
		if ( ! empty( $billing_email ) && apply_filters( 'wc_stripe_send_stripe_receipt', false ) ) {
719
			$post_data['receipt_email'] = $billing_email;
720
		}
721
722
		$post_data['expand[]']    = 'balance_transaction';
723
		$post_data['source']      = $source;
724
725
		/**
726
		 * Filter the return value of the WC_Payment_Gateway_CC::generate_payment_request.
727
		 *
728
		 * @since 3.1.0
729
		 * @param array $post_data
730
		 * @param WC_Order $order
731
		 * @param object $source
732
		 */
733
		return apply_filters( 'wc_stripe_generate_payment_request', $post_data, $order );
734
	}
735
736
	/**
737
	 * Builds the shippings methods to pass to Apple Pay.
738
	 *
739
	 * @since 3.1.0
740
	 * @version 3.1.0
741
	 */
742
	public function build_shipping_methods( $shipping_methods ) {
743
		if ( empty( $shipping_methods ) ) {
744
			return array();
745
		}
746
747
		$shipping = array();
748
749
		foreach ( $shipping_methods as $method ) {
750
			$shipping[] = array(
751
				'label'      => $method['label'],
752
				'detail'     => '',
753
				'amount'     => $method['amount']['value'],
754
				'identifier' => $method['id'],
755
			);
756
		}
757
758
		return $shipping;
759
	}
760
761
	/**
762
	 * Builds the line items to pass to Apple Pay.
763
	 *
764
	 * @since 3.1.0
765
	 * @version 3.1.0
766
	 */
767
	public function build_line_items() {
768
		if ( ! defined( 'WOOCOMMERCE_CART' ) ) {
769
			define( 'WOOCOMMERCE_CART', true );
770
		}
771
772
		$decimals = apply_filters( 'wc_stripe_apple_pay_decimals', 2 );
773
		
774
		$items    = array();
775
		$subtotal = 0;
776
777
		foreach ( WC()->cart->get_cart() as $cart_item_key => $values ) {
778
			$amount         = wc_format_decimal( $values['line_subtotal'], $decimals );
779
			$subtotal       += $values['line_subtotal'];
780
			$quantity_label = 1 < $values['quantity'] ? ' (x' . $values['quantity'] . ')' : '';
781
782
			$item = array(
783
				'type'   => 'final',
784
				'label'  => $values['data']->post->post_title . $quantity_label,
785
				'amount' => wc_format_decimal( $amount, $decimals ),
786
			);
787
788
			$items[] = $item;
789
		}
790
791
		// Default show only subtotal instead of itemization.
792
		if ( apply_filters( 'wc_stripe_apple_pay_disable_itemization', true ) ) {
793
			$items = array();
794
			$items[] = array(
795
				'type'   => 'final',
796
				'label'  => esc_html( __( 'Sub-Total', 'woocommerce-gateway-stripe' ) ),
797
				'amount' => wc_format_decimal( $subtotal, $decimals ),
798
			);
799
		}
800
801
		$discounts   = wc_format_decimal( WC()->cart->get_cart_discount_total(), $decimals );
802
		$tax         = wc_format_decimal( WC()->cart->tax_total + WC()->cart->shipping_tax_total, $decimals );
803
		$shipping    = wc_format_decimal( WC()->cart->shipping_total, $decimals );
804
		$item_total  = wc_format_decimal( WC()->cart->cart_contents_total, $decimals ) + $discounts;
805
		$order_total = wc_format_decimal( $item_total + $tax + $shipping, $decimals );
806
807
		if ( wc_tax_enabled() ) {
808
			$items[] = array(
809
				'type'   => 'final',
810
				'label'  => esc_html( __( 'Tax', 'woocommerce-gateway-stripe' ) ),
811
				'amount' => $tax,
812
			);
813
		}
814
815
		if ( WC()->cart->needs_shipping() ) {
816
			$items[] = array(
817
				'type'   => 'final',
818
				'label'  => esc_html( __( 'Shipping', 'woocommerce-gateway-stripe' ) ),
819
				'amount' => $shipping,
820
			);
821
		}
822
823
		if ( WC()->cart->has_discount() ) {
824
			$items[] = array(
825
				'type'   => 'final',
826
				'label'  => esc_html( __( 'Discount', 'woocommerce-gateway-stripe' ) ),
827
				'amount' => '-' . $discounts,
828
			);
829
		}
830
831
		return $items;
832
	}
833
834
	/**
835
	 * Create order programatically.
836
	 *
837
	 * @since 3.1.0
838
	 * @version 3.1.0
839
	 * @param array $data
840
	 * @return object $order
841
	 */
842
	public function create_order( $data = array() ) {
843
		if ( empty( $data ) ) {
844
			throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-gateway-stripe' ), 520 ) );
845
		}
846
847
		$order = wc_create_order();
848
		$order_id = version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->id : $order->get_id();
849
850
		if ( is_wp_error( $order ) ) {
851
			throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-gateway-stripe' ), 520 ) );
852
		} elseif ( false === $order ) {
853
			throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-gateway-stripe' ), 521 ) );
854
		} else {
855
			do_action( 'woocommerce_new_order', $order_id );
856
		}
857
858
		// Store the line items to the new/resumed order
859
		foreach ( WC()->cart->get_cart() as $cart_item_key => $values ) {
860
			$item_id = $order->add_product(
861
				$values['data'],
862
				$values['quantity'],
863
				array(
864
					'variation' => $values['variation'],
865
					'totals'    => array(
866
						'subtotal'     => $values['line_subtotal'],
867
						'subtotal_tax' => $values['line_subtotal_tax'],
868
						'total'        => $values['line_total'],
869
						'tax'          => $values['line_tax'],
870
						'tax_data'     => $values['line_tax_data'], // Since 2.2
871
					),
872
				)
873
			);
874
875
			if ( ! $item_id ) {
876
				throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-gateway-stripe' ), 525 ) );
877
			}
878
879
			// Allow plugins to add order item meta
880
			if ( version_compare( WC_VERSION, '3.0', '<' ) ) {
881
				do_action( 'woocommerce_add_order_item_meta', $item_id, $values, $cart_item_key );
882
			} else {
883
				do_action( 'woocommerce_new_order_item', $item_id, wc_get_product( $item_id ), $order->get_id() );
884
			}
885
		}
886
887
		// Store fees
888
		foreach ( WC()->cart->get_fees() as $fee_key => $fee ) {
889
			if ( version_compare( WC_VERSION, '3.0', '<' ) ) {
890
				$item_id = $order->add_fee( $fee );
891
			} else {
892
				$item = new WC_Order_Item_Fee();
893
				$item->set_props( array(
894
					'name'      => $fee->name,
895
					'tax_class' => $fee->taxable ? $fee->tax_class : 0,
896
					'total'     => $fee->amount,
897
					'total_tax' => $fee->tax,
898
					'taxes'     => array(
899
						'total' => $fee->tax_data,
900
					),
901
					'order_id'  => $order->get_id(),
902
				) );
903
				$item_id = $item->save();
904
				$order->add_item( $item );
905
			}
906
907
			if ( ! $item_id ) {
908
				throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-gateway-stripe' ), 526 ) );
909
			}
910
911
			// Allow plugins to add order item meta to fees
912
			if ( version_compare( WC_VERSION, '3.0', '<' ) ) {
913
				do_action( 'woocommerce_add_order_fee_meta', $order_id, $item_id, $fee, $fee_key );
914
			} else {
915
				do_action( 'woocommerce_new_order_item', $item_id, $fee, $order->get_id() );
916
			}
917
		}
918
919
		// Store tax rows
920
		foreach ( array_keys( WC()->cart->taxes + WC()->cart->shipping_taxes ) as $tax_rate_id ) {
921
			$tax_amount = WC()->cart->get_tax_amount( $tax_rate_id );
922
			$shipping_tax_amount = WC()->cart->get_shipping_tax_amount( $tax_rate_id );
923
924
			if ( version_compare( WC_VERSION, '3.0', '<' ) ) {
925
				$item_id = $order->add_tax( $tax_rate_id, $tax_amount, $shipping_tax_amount );
926
			} else {
927
				$item = new WC_Order_Item_Tax();
928
				$item->set_props( array(
929
					'rate_id'            => $tax_rate_id,
930
					'tax_total'          => $tax_amount,
931
					'shipping_tax_total' => $shipping_tax_amount,
932
				) );
933
				$item->set_rate( $tax_rate_id );
934
				$item->set_order_id( $order->get_id() );
935
				$item_id = $item->save();
936
				$order->add_item( $item );
937
			}
938
939
			if ( $tax_rate_id && ! $item_id && apply_filters( 'woocommerce_cart_remove_taxes_zero_rate_id', 'zero-rated' ) !== $tax_rate_id ) {
940
				throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-gateway-stripe' ), 528 ) );
941
			}
942
		}
943
944
		// Store coupons
945
		$discount = WC()->cart->get_coupon_discount_amount( $code );
946
		$discount_tax = WC()->cart->get_coupon_discount_tax_amount( $code );
947
948
		foreach ( WC()->cart->get_coupons() as $code => $coupon ) {
949
			if ( version_compare( WC_VERSION, '3.0', '<' ) ) {
950
				$coupon_id = $order->add_coupon( $code, $discount, $discount_tax );
951
			} else {
952
				$item = new WC_Order_Item_Coupon();
953
				$item->set_props( array(
954
					'code'         => $code,
955
					'discount'     => $discount,
956
					'discount_tax' => $discount_tax,
957
					'order_id'     => $order->get_id(),
958
				) );
959
				$coupon_id = $item->save();
960
				$order->add_item( $item );
961
			}
962
963
			if ( ! $coupon_id ) {
964
				throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-gateway-stripe' ), 529 ) );
965
			}
966
		}
967
968
		// Billing address
969
		$billing_address = array();
970
		if ( ! empty( $data['token']['card'] ) ) {
971
			// Name from Stripe is a full name string.
972
			$name                          = explode( ' ', $data['token']['card']['name'] );
973
			$lastname                      = array_pop( $name );
974
			$firstname                     = implode( ' ', $name );
975
			$billing_address['first_name'] = $firstname;
976
			$billing_address['last_name']  = $lastname;
977
			$billing_address['email']      = $data['shippingContact']['emailAddress'];
978
			$billing_address['phone']      = $data['shippingContact']['phoneNumber'];
979
			$billing_address['country']    = $data['token']['card']['country'];
980
			$billing_address['address_1']  = $data['token']['card']['address_line1'];
981
			$billing_address['address_2']  = $data['token']['card']['address_line2'];
982
			$billing_address['city']       = $data['token']['card']['address_city'];
983
			$billing_address['state']      = $data['token']['card']['address_state'];
984
			$billing_address['postcode']   = $data['token']['card']['address_zip'];
985
		}
986
987
		// Shipping address.
988
		$shipping_address = array();
989
		if ( WC()->cart->needs_shipping() && ! empty( $data['shippingContact'] ) ) {
990
			$shipping_address['first_name'] = $data['shippingContact']['givenName'];
991
			$shipping_address['last_name']  = $data['shippingContact']['familyName'];
992
			$shipping_address['email']      = $data['shippingContact']['emailAddress'];
993
			$shipping_address['phone']      = $data['shippingContact']['phoneNumber'];
994
			$shipping_address['country']    = $data['shippingContact']['countryCode'];
995
			$shipping_address['address_1']  = $data['shippingContact']['addressLines'][0];
996
			$shipping_address['address_2']  = $data['shippingContact']['addressLines'][1];
997
			$shipping_address['city']       = $data['shippingContact']['locality'];
998
			$shipping_address['state']      = $data['shippingContact']['administrativeArea'];
999
			$shipping_address['postcode']   = $data['shippingContact']['postalCode'];
1000
		} elseif ( ! empty( $data['shippingContact'] ) ) {
1001
			$shipping_address['first_name'] = $firstname;
1002
			$shipping_address['last_name']  = $lastname;
1003
			$shipping_address['email']      = $data['shippingContact']['emailAddress'];
1004
			$shipping_address['phone']      = $data['shippingContact']['phoneNumber'];
1005
			$shipping_address['country']    = $data['token']['card']['country'];
1006
			$shipping_address['address_1']  = $data['token']['card']['address_line1'];
1007
			$shipping_address['address_2']  = $data['token']['card']['address_line2'];
1008
			$shipping_address['city']       = $data['token']['card']['address_city'];
1009
			$shipping_address['state']      = $data['token']['card']['address_state'];
1010
			$shipping_address['postcode']   = $data['token']['card']['address_zip'];
1011
		}
1012
1013
		$order->set_address( $billing_address, 'billing' );
1014
		$order->set_address( $shipping_address, 'shipping' );
1015
1016
		WC()->shipping->calculate_shipping( WC()->cart->get_shipping_packages() );
1017
1018
		// Get the rate object selected by user.
1019
		foreach ( WC()->shipping->get_packages() as $package_key => $package ) {
1020
			foreach ( $package['rates'] as $key => $rate ) {
1021
				// Loop through user chosen shipping methods.
1022
				foreach ( WC()->session->get( 'chosen_shipping_methods' ) as $method ) {
1023
					if ( $method === $key ) {
1024
						if ( version_compare( WC_VERSION, '3.0', '<' ) ) {
1025
							$order->add_shipping( $rate );
1026
						} else {
1027
							$item = new WC_Order_Item_Shipping();
1028
							$item->set_props( array(
1029
								'method_title' => $rate->label,
1030
								'method_id'    => $rate->id,
1031
								'total'        => wc_format_decimal( $rate->cost ),
1032
								'taxes'        => $rate->taxes,
1033
								'order_id'     => $order->get_id(),
1034
							) );
1035
							foreach ( $rate->get_meta_data() as $key => $value ) {
1036
								$item->add_meta_data( $key, $value, true );
1037
							}
1038
							$item->save();
1039
							$order->add_item( $item );
1040
						}
1041
					}
1042
				}
1043
			}
1044
		}
1045
1046
		$available_gateways = WC()->payment_gateways->get_available_payment_gateways();
1047
		$order->set_payment_method( $available_gateways['stripe'] );
1048
		$order->set_total( WC()->cart->shipping_total, 'shipping' );
1049
		$order->set_total( WC()->cart->get_cart_discount_total(), 'cart_discount' );
1050
		$order->set_total( WC()->cart->get_cart_discount_tax_total(), 'cart_discount_tax' );
1051
		$order->set_total( WC()->cart->tax_total, 'tax' );
1052
		$order->set_total( WC()->cart->shipping_tax_total, 'shipping_tax' );
1053
		$order->set_total( WC()->cart->total );
1054
1055
		// If we got here, the order was created without problems!
1056
		wc_transaction_query( 'commit' );
1057
1058
		return $order;
1059
	}
1060
1061
	/**
1062
	 * Logs
1063
	 *
1064
	 * @since 3.1.0
1065
	 * @version 3.1.0
1066
	 *
1067
	 * @param string $message
1068
	 */
1069
	public function log( $message ) {
1070
		if ( $this->logging ) {
1071
			WC_Stripe::log( $message );
1072
		}
1073
	}
1074
}
1075
1076
new WC_Stripe_Apple_Pay();
1077