Completed
Push — master ( e0634c...a8a1a1 )
by Roy
02:22
created

WC_Stripe_Apple_Pay   D

Complexity

Total Complexity 108

Size/Duplication

Total Lines 781
Duplicated Lines 6.27 %

Coupling/Cohesion

Components 2
Dependencies 3

Importance

Changes 0
Metric Value
wmc 108
lcom 2
cbo 3
dl 49
loc 781
rs 4.4444
c 0
b 0
f 0

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 2
A instance() 0 3 1
A init() 0 10 1
C process_apple_pay_domain() 0 42 8
B _register_apple_pay_domain() 0 27 4
A filter_gateway_title() 0 15 4
D payment_scripts() 0 34 10
A is_supported_product_type() 0 9 3
B display_apple_pay_button() 0 24 6
A generate_apple_pay_cart() 0 7 2
B calculate_shipping() 14 56 8
C get_shipping_methods() 0 52 9
B update_shipping_method() 18 44 5
B process_apple_pay() 3 57 8
B generate_payment_request() 0 24 5
A build_shipping_methods() 0 18 3
B build_line_items() 14 54 7
F create_order() 0 143 22

How to fix   Duplicated Code    Complexity   

Duplicated Code

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

Common duplication problems, and corresponding solutions are:

Complex Class

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

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

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

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

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