Completed
Push — master ( 013109...33910d )
by Roy
02:10
created

WC_Stripe_Apple_Pay   D

Complexity

Total Complexity 90

Size/Duplication

Total Lines 663
Duplicated Lines 7.99 %

Coupling/Cohesion

Components 2
Dependencies 3

Importance

Changes 0
Metric Value
wmc 90
lcom 2
cbo 3
dl 53
loc 663
rs 4.6106
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A instance() 0 3 1
A init() 0 9 1
D payment_scripts() 0 34 10
A is_supported_product_type() 0 9 3
B display_apple_pay_button() 0 24 5
A generate_apple_pay_cart() 0 7 2
B calculate_shipping() 7 56 8
C get_shipping_methods() 11 50 9
B update_shipping_method() 18 44 5
B process_apple_pay() 3 56 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
	 * Gateway settings.
28
	 *
29
	 * @var
30
	 */
31
	private $_gateway_settings;
32
33
	/**
34
	 * Constructor.
35
	 *
36
	 * @access public
37
	 * @since 3.1.0
38
	 * @version 3.1.0
39
	 */
40
	public function __construct() {
41
		self::$_this = $this;
42
43
		$this->_gateway_settings = get_option( 'woocommerce_stripe_settings', '' );
44
45
		$this->init();
46
	}
47
48
	public function instance() {
49
		return self::$_this;
50
	}
51
52
	/**
53
	 * Initialize.
54
	 *
55
	 * @access public
56
	 * @since 3.1.0
57
	 * @version 3.1.0
58
	 */
59
	public function init() {
60
		add_action( 'wp_enqueue_scripts', array( $this, 'payment_scripts' ) );
61
		add_action( 'woocommerce_proceed_to_checkout', array( $this, 'display_apple_pay_button' ), 20 );
62
		add_action( 'woocommerce_checkout_before_customer_details', array( $this, 'display_apple_pay_button' ) );
63
		add_action( 'wc_ajax_wc_stripe_apple_pay', array( $this, 'process_apple_pay' ) );
64
		add_action( 'wc_ajax_wc_stripe_generate_apple_pay_cart', array( $this, 'generate_apple_pay_cart' ) );
65
		add_action( 'wc_ajax_wc_stripe_apple_pay_get_shipping_methods', array( $this, 'get_shipping_methods' ) );
66
		add_action( 'wc_ajax_wc_stripe_apple_pay_update_shipping_method', array( $this, 'update_shipping_method' ) );
67
	}
68
69
	/**
70
	 * Enqueue JS scripts and styles.
71
	 *
72
	 * @since 3.1.0
73
	 * @version 3.1.0
74
	 */
75
	public function payment_scripts() {
76
		if ( ! is_cart() && ! is_checkout() && ! isset( $_GET['pay_for_order'] ) ) {
77
			return;
78
		}
79
80
		if ( ! $this->is_supported_product_type() ) {
81
			return;
82
		}
83
		
84
		$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
85
		
86
		wp_enqueue_style( 'stripe_apple_pay', plugins_url( 'assets/css/stripe-apple-pay.css', WC_STRIPE_MAIN_FILE ), array(), WC_STRIPE_VERSION );
87
88
		wp_enqueue_script( 'stripe', 'https://js.stripe.com/v2/', '', '1.0', true );
89
		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 );
90
91
		$publishable_key = 'yes' === $this->_gateway_settings['testmode'] ? $this->_gateway_settings['test_publishable_key'] : $this->_gateway_settings['publishable_key'];
92
93
		$stripe_params = array(
94
			'key'                                           => $publishable_key,
95
			'currency_code'                                 => get_woocommerce_currency(),
96
			'country_code'                                  => substr( get_option( 'woocommerce_default_country' ), 0, 2 ),
97
			'label'                                         => get_bloginfo( 'name', 'display' ),
98
			'ajaxurl'                                       => WC_AJAX::get_endpoint( '%%endpoint%%' ),
99
			'stripe_apple_pay_nonce'                        => wp_create_nonce( '_wc_stripe_apple_pay_nonce' ),
100
			'stripe_apple_pay_cart_nonce'                   => wp_create_nonce( '_wc_stripe_apple_pay_cart_nonce' ),
101
			'stripe_apple_pay_get_shipping_methods_nonce'   => wp_create_nonce( '_wc_stripe_apple_pay_get_shipping_methods_nonce' ),
102
			'stripe_apple_pay_update_shipping_method_nonce' => wp_create_nonce( '_wc_stripe_apple_pay_update_shipping_method_nonce' ),
103
			'needs_shipping'                                => WC()->cart->needs_shipping() ? 'yes' : 'no',
104
			'is_cart_page'                                  => is_cart() ? 'yes' : 'no',
105
		);
106
107
		wp_localize_script( 'woocommerce_stripe_apple_pay', 'wc_stripe_apple_pay_params', apply_filters( 'wc_stripe_apple_pay_params', $stripe_params ) );
108
	}
109
		
110
	/**
111
	 * Checks to make sure product type is supported by Apple Pay.
112
	 *
113
	 */
114
	public function is_supported_product_type() {
115
		foreach( WC()->cart->get_cart() as $cart_item_key => $values ) {
116
			if ( 'subscription' === $values['data']->product_type ) {
117
				return false;
118
			}
119
		}
120
121
		return true;
122
	}
123
124
	/**
125
	 * Display Apple Pay button on the cart page
126
	 *
127
	 * @since 3.1.0
128
	 * @version 3.1.0
129
	 */
130
	public function display_apple_pay_button() {
131
		$gateways = WC()->payment_gateways->get_available_payment_gateways();
132
133
		/**
134
		 * In order for the Apple Pay button to show on cart page,
135
		 * Apple Pay must be enabled and Stripe gateway must be enabled.
136
		 */
137
		if ( 
138
			'yes' !== $this->_gateway_settings['apple_pay']
139
			|| ! isset( $gateways['stripe'] ) 
140
		) {
141
			return;
142
		}
143
144
		if ( ! $this->is_supported_product_type() ) {
145
			return;
146
		}
147
148
		$apple_pay_button = ! empty( $this->_gateway_settings['apple_pay_button'] ) ? $this->_gateway_settings['apple_pay_button'] : 'black';
149
		$country = strtolower( substr( get_option( 'woocommerce_default_country' ), 0, 2 ) );
150
		?>
151
		<button class="apple-pay-button" lang="<?php echo esc_attr( $country ); ?>" style="-webkit-appearance: -apple-pay-button; -apple-pay-button-type: buy; -apple-pay-button-style: <?php echo esc_attr( $apple_pay_button ); ?>;"></button>
152
		<?php
153
	}
154
155
	/**
156
	 * Generates the Apple Pay cart.
157
	 *
158
	 * @since 3.1.0
159
	 * @version 3.1.0
160
	 */
161
	public function generate_apple_pay_cart() {
162
		if ( ! wp_verify_nonce( $_POST['nonce'], '_wc_stripe_apple_pay_cart_nonce' ) ) {
163
			wp_die( __( 'Cheatin&#8217; huh?', 'woocommerce-gateway-stripe' ) );
164
		}
165
166
		wp_send_json( array( 'line_items' => $this->build_line_items(), 'total' => WC()->cart->total ) );
167
	}
168
169
	/**
170
	 * Calculate and set shipping method.
171
	 *
172
	 * @since 3.1.0
173
	 * @version 3.1.0
174
	 * @param array $address
175
	 */
176
	public function calculate_shipping( $address = array() ) {
177
		$country  = strtoupper( $address['countryCode'] );
178
		$state    = strtoupper( $address['administrativeArea'] );
179
		$postcode = $address['postalCode'];
180
		$city     = $address['locality'];
181
182
		WC()->shipping->reset_shipping();
183
184
		if ( $postcode && ! WC_Validation::is_postcode( $postcode, $country ) ) {
185
			throw new Exception( __( 'Please enter a valid postcode/ZIP.', 'woocommerce-gateway-stripe' ) );
186
		} elseif ( $postcode ) {
187
			$postcode = wc_format_postcode( $postcode, $country );
188
		}
189
190
		if ( $country ) {
191
			WC()->customer->set_location( $country, $state, $postcode, $city );
192
			WC()->customer->set_shipping_location( $country, $state, $postcode, $city );
193
		} else {
194
			WC()->customer->set_to_base();
195
			WC()->customer->set_shipping_to_base();
196
		}
197
198
		WC()->customer->calculated_shipping( true );
199
200
		/** 
201
		 * Set the shipping package.
202
		 *
203
		 * Note that address lines are not provided at this point
204
		 * because Apple Pay does not supply that until after
205
		 * authentication via passcode or Touch ID. We will need to
206
		 * capture this information when we process the payment.
207
		 */
208
209
		$packages = array();
210
211
		$packages[0]['contents']                 = WC()->cart->get_cart();
212
		$packages[0]['contents_cost']            = 0;
213
		$packages[0]['applied_coupons']          = WC()->cart->applied_coupons;
214
		$packages[0]['user']['ID']               = get_current_user_id();
215
		$packages[0]['destination']['country']   = $country;
216
		$packages[0]['destination']['state']     = $state;
217
		$packages[0]['destination']['postcode']  = $postcode;
218
		$packages[0]['destination']['city']      = $city;
219
220 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...
221
			if ( $item['data']->needs_shipping() ) {
222
				if ( isset( $item['line_total'] ) ) {
223
					$packages[0]['contents_cost'] += $item['line_total'];
224
				}
225
			}
226
		}
227
228
		$packages = apply_filters( 'woocommerce_cart_shipping_packages', $packages );
229
230
		WC()->shipping->calculate_shipping( $packages );
231
	}
232
233
	/**
234
	 * Gets shipping for Apple Pay Payment sheet.
235
	 *
236
	 * @since 3.1.0
237
	 * @version 3.1.0
238
	 */
239
	public function get_shipping_methods() {
240
		if ( ! wp_verify_nonce( $_POST['nonce'], '_wc_stripe_apple_pay_get_shipping_methods_nonce' ) ) {
241
			wp_die( __( 'Cheatin&#8217; huh?', 'woocommerce-gateway-stripe' ) );
242
		}
243
244
		if ( ! defined( 'WOOCOMMERCE_CART' ) ) {
245
			define( 'WOOCOMMERCE_CART', true );
246
		}
247
248
		try {
249
			$address = array_map( 'wc_clean', $_POST['address'] );
250
251
			$this->calculate_shipping( $address );
252
253
			// Set the shipping options.
254
			$currency = get_woocommerce_currency();
255
			$data     = array();
256
257
			if ( ! empty( WC()->shipping->get_packages() ) && WC()->customer->has_calculated_shipping() ) {
258
				foreach ( WC()->shipping->get_packages() as $package_key => $package ) {
259
					if ( empty( $package['rates'] ) ) {
260
						throw new Exception( __( 'Unable to find shipping method for address.', 'woocommerce-gateway-stripe' ) );
261
					}
262
263 View Code Duplication
					foreach ( $package['rates'] as $key => $rate ) {
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...
264
						$data[] = array(
265
							'id'       => $rate->id,
266
							'label'    => $rate->label,
267
							'amount'   => array(
268
								'currency' => $currency,
269
								'value'    => $rate->cost,
270
							),
271
							'selected' => false,
272
						);
273
					}
274
				}
275
276
				// Auto select the first shipping method.
277
				WC()->session->set( 'chosen_shipping_methods', array( $data[0]['id'] ) );
278
279
				WC()->cart->calculate_totals();
280
				
281
				wp_send_json( array( 'success' => 'true', 'shipping_methods' => $this->build_shipping_methods( $data ), 'line_items' => $this->build_line_items(), 'total' => WC()->cart->total ) );
282
			} else {
283
				throw new Exception( __( 'Unable to find shipping method for address.', 'woocommerce-gateway-stripe' ) ); 
284
			}
285
		} catch( Exception $e ) {
286
			wp_send_json( array( 'success' => 'false', 'shipping_methods' => array(), 'line_items' => $this->build_line_items(), 'total' => WC()->cart->total ) );			
287
		}
288
	}
289
290
	/**
291
	 * Updates shipping method on cart session.
292
	 *
293
	 * @since 3.1.0
294
	 * @version 3.1.0
295
	 */
296
	public function update_shipping_method() {
297
		if ( ! defined( 'WOOCOMMERCE_CART' ) ) {
298
			define( 'WOOCOMMERCE_CART', true );
299
		}
300
301
		if ( ! wp_verify_nonce( $_POST['nonce'], '_wc_stripe_apple_pay_update_shipping_method_nonce' ) ) {
302
			wp_die( __( 'Cheatin&#8217; huh?', 'woocommerce-gateway-stripe' ) );
303
		}
304
305
		$selected_shipping_method = array_map( 'wc_clean', $_POST['selected_shipping_method'] );
306
307
		WC()->session->set( 'chosen_shipping_methods', array( $selected_shipping_method['identifier'] ) );
308
309
		WC()->cart->calculate_totals();
310
311
		// Send back the new cart total.
312
		$currency  = get_woocommerce_currency();
313
		$tax_total = max( 0, round( WC()->cart->tax_total + WC()->cart->shipping_tax_total, WC()->cart->dp ) );
314
		$data      = array(
315
			'total' => WC()->cart->total,
316
		);
317
318
		// Include fees and taxes as displayItems.
319 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...
320
			$data['items'][] = array(
321
				'label'  => $fee->name,
322
				'amount' => array(
323
					'currency' => $currency,
324
					'value'    => $fee->amount,
325
				),
326
			);
327
		}
328 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...
329
			$data['items'][] = array(
330
				'label'  => __( 'Tax', 'woocommerce-gateway-stripe' ),
331
				'amount' => array(
332
					'currency' => $currency,
333
					'value'    => $tax_total,
334
				),
335
			);
336
		}
337
338
		wp_send_json( array( 'success' => 'true', 'line_items' => $this->build_line_items(), 'total' => WC()->cart->total ) );
339
	}
340
341
	/**
342
	 * Handles the Apple Pay processing via AJAX
343
	 *
344
	 * @access public
345
	 * @since 3.1.0
346
	 * @version 3.1.0
347
	 */
348
	public function process_apple_pay() {
349
		if ( ! wp_verify_nonce( $_POST['nonce'], '_wc_stripe_apple_pay_nonce' ) ) {
350
			wp_die( __( 'Cheatin&#8217; huh?', 'woocommerce-gateway-stripe' ) );
351
		}
352
353
		try {
354
			$result = array_map( 'wc_clean', $_POST['result'] );
355
356
			$order = $this->create_order( $result );
357
358
			// Handle payment.
359
			if ( $order->get_total() > 0 ) {
360
361 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...
362
					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 ) ) );
363
				}
364
365
				WC_Stripe::log( "Info: Begin processing payment for order $order->id for the amount of {$order->get_total()}" );
366
367
				// Make the request.
368
				$response = WC_Stripe_API::request( $this->generate_payment_request( $order, $result['token']['id'] ) );
369
370
				if ( is_wp_error( $response ) ) {
371
					$localized_messages = $this->get_localized_messages();
372
373
					throw new Exception( ( isset( $localized_messages[ $response->get_error_code() ] ) ? $localized_messages[ $response->get_error_code() ] : $response->get_error_message() ) );
374
				}
375
376
				// Process valid response.
377
				$this->process_response( $response, $order );
378
			} else {
379
				$order->payment_complete();
380
			}
381
382
			// Remove cart.
383
			WC()->cart->empty_cart();
384
385
			update_post_meta( $order->id, '_customer_user', get_current_user_id() );
386
387
			// Return thank you page redirect.
388
			wp_send_json( array(
389
				'success'  => 'true',
390
				'redirect' => $this->get_return_url( $order ),
391
			) );
392
393
		} catch ( Exception $e ) {
394
			WC()->session->set( 'refresh_totals', true );
395
			WC_Stripe::log( sprintf( __( 'Error: %s', 'woocommerce-gateway-stripe' ), $e->getMessage() ) );
396
397
			if ( $order->has_status( array( 'pending', 'failed' ) ) ) {
398
				$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...
399
			}
400
401
			wp_send_json( array( 'success' => 'false', 'msg' => $e->getMessage() ) );
402
		}
403
	}
404
405
	/**
406
	 * Generate the request for the payment.
407
	 * @param  WC_Order $order
408
	 * @param string $source token
409
	 * @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...
410
	 */
411
	protected function generate_payment_request( $order, $source ) {
412
		$post_data                = array();
413
		$post_data['currency']    = strtolower( $order->get_order_currency() ? $order->get_order_currency() : get_woocommerce_currency() );
414
		$post_data['amount']      = $this->get_stripe_amount( $order->get_total(), $post_data['currency'] );
415
		$post_data['description'] = sprintf( __( '%s - Order %s', 'woocommerce-gateway-stripe' ), wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ), $order->get_order_number() );
416
		$post_data['capture']     = 'yes' === $this->_gateway_settings['capture'] ? 'true' : 'false';
417
418
		if ( ! empty( $order->billing_email ) && apply_filters( 'wc_stripe_send_stripe_receipt', false ) ) {
419
			$post_data['receipt_email'] = $order->billing_email;
420
		}
421
422
		$post_data['expand[]']    = 'balance_transaction';
423
		$post_data['source']      = $source;
424
425
		/**
426
		 * Filter the return value of the WC_Payment_Gateway_CC::generate_payment_request.
427
		 *
428
		 * @since 3.1.0
429
		 * @param array $post_data
430
		 * @param WC_Order $order
431
		 * @param object $source
432
		 */
433
		return apply_filters( 'wc_stripe_generate_payment_request', $post_data, $order );
434
	}
435
436
	/**
437
	 * Builds the shippings methods to pass to Apple Pay.
438
	 *
439
	 * @since 3.1.0
440
	 * @version 3.1.0
441
	 */	
442
	public function build_shipping_methods( $shipping_methods ) {
443
		if ( empty( $shipping_methods ) ) {
444
			return array();
445
		}
446
447
		$shipping = array();
448
449
		foreach( $shipping_methods as $method ) {
450
			$shipping[] = array(
451
				'label'      => $method['label'],
452
				'detail'     => '',
453
				'amount'     => $method['amount']['value'],
454
				'identifier' => $method['id'],
455
			);
456
		}
457
458
		return $shipping;
459
	}
460
461
	/**
462
	 * Builds the line items to pass to Apple Pay.
463
	 *
464
	 * @since 3.1.0
465
	 * @version 3.1.0
466
	 */	
467
	public function build_line_items() {
468
		if ( ! defined( 'WOOCOMMERCE_CART' ) ) {
469
			define( 'WOOCOMMERCE_CART', true );
470
		}
471
472
		$decimals = apply_filters( 'wc_stripe_apple_pay_decimals', 2 );
473
474
		$items = array();
475
476
		foreach ( WC()->cart->get_cart() as $cart_item_key => $values ) {
477
			$amount         = wc_format_decimal( $values['line_subtotal'], $decimals );
478
			$quantity_label = 1 < $values['quantity'] ? ' (x' . $values['quantity'] . ')' : '';
479
480
			$item = array(
481
				'type'   => 'final',
482
				'label'  => $values['data']->post->post_title . $quantity_label,
483
				'amount' => wc_format_decimal( $amount, $decimals ),
484
			);
485
486
			$items[] = $item;
487
		}
488
489
		$discounts   = wc_format_decimal( WC()->cart->get_cart_discount_total(), $decimals );
490
		$tax         = wc_format_decimal( WC()->cart->tax_total + WC()->cart->shipping_tax_total, $decimals );
491
		$shipping    = wc_format_decimal( WC()->cart->shipping_total, $decimals );
492
		$item_total  = wc_format_decimal( WC()->cart->cart_contents_total, $decimals ) + $discounts;
493
		$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...
494
495
		if ( wc_tax_enabled() ) {
496
			$items[] = array(
497
				'type'   => 'final',
498
				'label'  => __( 'Tax', 'woocommerce-gateway-stripe' ),
499
				'amount' => $tax,
500
			);
501
		}
502
503 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...
504
			$items[] = array(
505
				'type'   => 'final',
506
				'label'  => __( 'Shipping', 'woocommerce-gateway-stripe' ),
507
				'amount' => $shipping,
508
			);
509
		}
510
511 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...
512
			$items[] = array(
513
				'type'   => 'final',
514
				'label'  => __( 'Discount', 'woocommerce-gateway-stripe' ),
515
				'amount' => $discounts,
516
			);
517
		}
518
519
		return $items;
520
	}
521
522
	/**
523
	 * Create order programatically.
524
	 *
525
	 * @since 3.1.0
526
	 * @version 3.1.0
527
	 * @param array $data
528
	 * @return object $order
529
	 */
530
	public function create_order( $data = array() ) {
531
		if ( empty( $data ) ) {
532
			throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-gateway-stripe' ), 520 ) );
533
		}
534
535
		$order = wc_create_order();
536
537
		if ( is_wp_error( $order ) ) {
538
			throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-gateway-stripe' ), 520 ) );
539
		} elseif ( false === $order ) {
540
			throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-gateway-stripe' ), 521 ) );
541
		} else {
542
			$order_id = $order->id;
543
			do_action( 'woocommerce_new_order', $order_id );
544
		}
545
546
		// Store the line items to the new/resumed order
547
		foreach ( WC()->cart->get_cart() as $cart_item_key => $values ) {
548
			$item_id = $order->add_product(
549
				$values['data'],
550
				$values['quantity'],
551
				array(
552
					'variation' => $values['variation'],
553
					'totals'    => array(
554
						'subtotal'     => $values['line_subtotal'],
555
						'subtotal_tax' => $values['line_subtotal_tax'],
556
						'total'        => $values['line_total'],
557
						'tax'          => $values['line_tax'],
558
						'tax_data'     => $values['line_tax_data'] // Since 2.2
559
					)
560
				)
561
			);
562
563
			if ( ! $item_id ) {
564
				throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-gateway-stripe' ), 525 ) );
565
			}
566
567
			// Allow plugins to add order item meta
568
			do_action( 'woocommerce_add_order_item_meta', $item_id, $values, $cart_item_key );
569
		}
570
571
		// Store fees
572
		foreach ( WC()->cart->get_fees() as $fee_key => $fee ) {
573
			$item_id = $order->add_fee( $fee );
574
575
			if ( ! $item_id ) {
576
				throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-gateway-stripe' ), 526 ) );
577
			}
578
579
			// Allow plugins to add order item meta to fees
580
			do_action( 'woocommerce_add_order_fee_meta', $order_id, $item_id, $fee, $fee_key );
581
		}
582
583
		// Store tax rows
584
		foreach ( array_keys( WC()->cart->taxes + WC()->cart->shipping_taxes ) as $tax_rate_id ) {
585
			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 ) {
586
				throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-gateway-stripe' ), 528 ) );
587
			}
588
		}
589
590
		// Store coupons
591
		foreach ( WC()->cart->get_coupons() as $code => $coupon ) {
592
			if ( ! $order->add_coupon( $code, WC()->cart->get_coupon_discount_amount( $code ), WC()->cart->get_coupon_discount_tax_amount( $code ) ) ) {
593
				throw new Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-gateway-stripe' ), 529 ) );
594
			}
595
		}
596
597
		// Billing address
598
		$billing_address = array();
599
		if ( ! empty( $data['token']['card'] ) ) {
600
			// Name from Stripe is a full name string.
601
			$name                          = explode( ' ', $data['token']['card']['name'] );
602
			$lastname                      = array_pop( $name );
603
			$firstname                     = implode( ' ', $name );
604
			$billing_address['first_name'] = $firstname;
605
			$billing_address['last_name']  = $lastname;
606
			$billing_address['email']      = $data['shippingContact']['emailAddress'];
607
			$billing_address['phone']      = $data['shippingContact']['phoneNumber'];
608
			$billing_address['country']    = $data['token']['card']['country'];
609
			$billing_address['address_1']  = $data['token']['card']['address_line1'];
610
			$billing_address['address_2']  = $data['token']['card']['address_line2'];
611
			$billing_address['city']       = $data['token']['card']['address_city'];
612
			$billing_address['state']      = $data['token']['card']['address_state'];
613
			$billing_address['postcode']   = $data['token']['card']['address_zip'];
614
		}
615
616
		// Shipping address.
617
		$shipping_address = array();
618
		if ( WC()->cart->needs_shipping() && ! empty( $data['shippingContact'] ) ) {
619
			$shipping_address['first_name'] = $data['shippingContact']['givenName'];
620
			$shipping_address['last_name']  = $data['shippingContact']['familyName'];
621
			$shipping_address['email']      = $data['shippingContact']['emailAddress'];
622
			$shipping_address['phone']      = $data['shippingContact']['phoneNumber'];
623
			$shipping_address['country']    = $data['shippingContact']['countryCode'];
624
			$shipping_address['address_1']  = $data['shippingContact']['addressLines'][0];
625
			$shipping_address['address_2']  = $data['shippingContact']['addressLines'][1];
626
			$shipping_address['city']       = $data['shippingContact']['locality'];
627
			$shipping_address['state']      = $data['shippingContact']['administrativeArea'];
628
			$shipping_address['postcode']   = $data['shippingContact']['postalCode'];
629
		} elseif ( ! empty( $data['shippingContact'] ) ) {
630
			$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...
631
			$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...
632
			$shipping_address['email']      = $data['shippingContact']['emailAddress'];
633
			$shipping_address['phone']      = $data['shippingContact']['phoneNumber'];
634
			$shipping_address['country']    = $data['token']['card']['country'];
635
			$shipping_address['address_1']  = $data['token']['card']['address_line1'];
636
			$shipping_address['address_2']  = $data['token']['card']['address_line2'];
637
			$shipping_address['city']       = $data['token']['card']['address_city'];
638
			$shipping_address['state']      = $data['token']['card']['address_state'];
639
			$shipping_address['postcode']   = $data['token']['card']['address_zip'];
640
		}
641
642
		$order->set_address( $billing_address, 'billing' );
643
		$order->set_address( $shipping_address, 'shipping' );
644
645
		WC()->shipping->calculate_shipping( WC()->cart->get_shipping_packages() );
646
		
647
		// Get the rate object selected by user.
648
		foreach ( WC()->shipping->get_packages() as $package_key => $package ) {
649
			foreach ( $package['rates'] as $key => $rate ) {
650
				// Loop through user chosen shipping methods.
651
				foreach( WC()->session->get( 'chosen_shipping_methods' ) as $method ) {
652
					if ( $method === $key ) {
653
						$order->add_shipping( $rate );
654
					}
655
				}
656
			}
657
		}
658
659
		$available_gateways = WC()->payment_gateways->get_available_payment_gateways();
660
		$order->set_payment_method( $available_gateways['stripe'] );
661
		$order->set_total( WC()->cart->shipping_total, 'shipping' );
662
		$order->set_total( WC()->cart->get_cart_discount_total(), 'cart_discount' );
663
		$order->set_total( WC()->cart->get_cart_discount_tax_total(), 'cart_discount_tax' );
664
		$order->set_total( WC()->cart->tax_total, 'tax' );
665
		$order->set_total( WC()->cart->shipping_tax_total, 'shipping_tax' );
666
		$order->set_total( WC()->cart->total );
667
668
		// If we got here, the order was created without problems!
669
		wc_transaction_query( 'commit' );
670
671
		return $order;
672
	}
673
}
674
675
new WC_Stripe_Apple_Pay();
676
677