Completed
Push — master ( 25ea90...4ea63e )
by Roy
02:24
created

process_apple_pay_verification()   C

Complexity

Conditions 8
Paths 32

Size

Total Lines 45
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 24
nc 32
nop 0
dl 0
loc 45
rs 5.3846
c 0
b 0
f 0
1
<?php
2
if ( ! defined( 'ABSPATH' ) ) {
3
	exit;
4
}
5
6
/**
7
 * WC_Gateway_Stripe class.
8
 *
9
 * @extends WC_Payment_Gateway
10
 */
11
class WC_Gateway_Stripe extends WC_Payment_Gateway_CC {
12
13
	/**
14
	 * Should we capture Credit cards
15
	 *
16
	 * @var bool
17
	 */
18
	public $capture;
19
20
	/**
21
	 * Alternate credit card statement name
22
	 *
23
	 * @var bool
24
	 */
25
	public $statement_descriptor;
26
27
	/**
28
	 * Checkout enabled
29
	 *
30
	 * @var bool
31
	 */
32
	public $stripe_checkout;
33
34
	/**
35
	 * Checkout Locale
36
	 *
37
	 * @var string
38
	 */
39
	public $stripe_checkout_locale;
40
41
	/**
42
	 * Credit card image
43
	 *
44
	 * @var string
45
	 */
46
	public $stripe_checkout_image;
47
48
	/**
49
	 * Should we store the users credit cards?
50
	 *
51
	 * @var bool
52
	 */
53
	public $saved_cards;
54
55
	/**
56
	 * API access secret key
57
	 *
58
	 * @var string
59
	 */
60
	public $secret_key;
61
62
	/**
63
	 * Api access publishable key
64
	 *
65
	 * @var string
66
	 */
67
	public $publishable_key;
68
69
	/**
70
	 * Do we accept bitcoin?
71
	 *
72
	 * @var bool
73
	 */
74
	public $bitcoin;
75
76
	/**
77
	 * Alow Remember me setting for Stripe Checkout
78
	 *
79
	 * @var bool
80
	 */
81
	public $allow_remember_me;
82
83
	/**
84
	 * Do we accept Apple Pay?
85
	 *
86
	 * @var bool
87
	 */
88
	public $apple_pay;
89
90
	/**
91
	 * Apple Pay Domain Set.
92
	 *
93
	 * @var bool
94
	 */
95
	public $apple_pay_domain_set;
96
97
	/**
98
	 * Apple Pay button style.
99
	 *
100
	 * @var bool
101
	 */
102
	public $apple_pay_button;
103
104
	/**
105
	 * Is test mode active?
106
	 *
107
	 * @var bool
108
	 */
109
	public $testmode;
110
111
	/**
112
	 * Logging enabled?
113
	 *
114
	 * @var bool
115
	 */
116
	public $logging;
117
118
	/**
119
	 * Constructor
120
	 */
121
	public function __construct() {
122
		$this->id                   = 'stripe';
123
		$this->method_title         = __( 'Stripe', 'woocommerce-gateway-stripe' );
124
		$this->method_description   = __( 'Stripe works by adding credit card fields on the checkout and then sending the details to Stripe for verification.', 'woocommerce-gateway-stripe' );
125
		$this->has_fields           = true;
126
		$this->view_transaction_url = 'https://dashboard.stripe.com/payments/%s';
127
		$this->supports             = array(
128
			'subscriptions',
129
			'products',
130
			'refunds',
131
			'subscription_cancellation',
132
			'subscription_reactivation',
133
			'subscription_suspension',
134
			'subscription_amount_changes',
135
			'subscription_payment_method_change', // Subs 1.n compatibility.
136
			'subscription_payment_method_change_customer',
137
			'subscription_payment_method_change_admin',
138
			'subscription_date_changes',
139
			'multiple_subscriptions',
140
			'pre-orders',
141
			'tokenization',
142
			'add_payment_method',
143
		);
144
145
		// Load the form fields.
146
		$this->init_form_fields();
147
148
		// Load the settings.
149
		$this->init_settings();
150
151
		// Get setting values.
152
		$this->title                  = $this->get_option( 'title' );
153
		$this->description            = $this->get_option( 'description' );
154
		$this->enabled                = $this->get_option( 'enabled' );
155
		$this->testmode               = 'yes' === $this->get_option( 'testmode' );
156
		$this->capture                = 'yes' === $this->get_option( 'capture', 'yes' );
157
		$this->statement_descriptor   = $this->get_option( 'statement_descriptor', wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ) );
158
		$this->stripe_checkout        = 'yes' === $this->get_option( 'stripe_checkout' );
159
		$this->stripe_checkout_locale = $this->get_option( 'stripe_checkout_locale' );
160
		$this->stripe_checkout_image  = $this->get_option( 'stripe_checkout_image', '' );
161
		$this->saved_cards            = 'yes' === $this->get_option( 'saved_cards' );
162
		$this->secret_key             = $this->testmode ? $this->get_option( 'test_secret_key' ) : $this->get_option( 'secret_key' );
163
		$this->publishable_key        = $this->testmode ? $this->get_option( 'test_publishable_key' ) : $this->get_option( 'publishable_key' );
164
		$this->bitcoin                = 'USD' === strtoupper( get_woocommerce_currency() ) && 'yes' === $this->get_option( 'stripe_bitcoin' );
165
		$this->apple_pay              = 'yes' === $this->get_option( 'apple_pay', 'yes' );
166
		$this->apple_pay_domain_set   = 'yes' === $this->get_option( 'apple_pay_domain_set', 'no' );
167
		$this->apple_pay_button       = $this->get_option( 'apple_pay_button', 'black' );
168
		$this->logging                = 'yes' === $this->get_option( 'logging' );
169
		$this->allow_remember_me      = 'yes' === $this->get_option( 'allow_remember_me', 'no' );
170
171
		if ( $this->stripe_checkout ) {
172
			$this->order_button_text = __( 'Continue to payment', 'woocommerce-gateway-stripe' );
173
		}
174
175
		if ( $this->testmode ) {
176
			$this->description .= ' ' . sprintf( __( 'TEST MODE ENABLED. In test mode, you can use the card number 4242424242424242 with any CVC and a valid expiration date or check the documentation "<a href="%s">Testing Stripe</a>" for more card numbers.', 'woocommerce-gateway-stripe' ), 'https://stripe.com/docs/testing' );
177
			$this->description  = trim( $this->description );
178
		}
179
180
		WC_Stripe_API::set_secret_key( $this->secret_key );
181
182
		$this->init_apple_pay();
183
184
		// Hooks.
185
		add_action( 'wp_enqueue_scripts', array( $this, 'payment_scripts' ) );
186
		add_action( 'admin_enqueue_scripts', array( $this, 'admin_scripts' ) );
187
		add_action( 'admin_notices', array( $this, 'admin_notices' ) );
188
		add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );
189
	}
190
191
	/**
192
	 * Get_icon function.
193
	 *
194
	 * @access public
195
	 * @return string
196
	 */
197 View Code Duplication
	public function get_icon() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
198
		$ext   = version_compare( WC()->version, '2.6', '>=' ) ? '.svg' : '.png';
199
		$style = version_compare( WC()->version, '2.6', '>=' ) ? 'style="margin-left: 0.3em"' : '';
200
201
		$icon  = '<img src="' . WC_HTTPS::force_https_url( WC()->plugin_url() . '/assets/images/icons/credit-cards/visa' . $ext ) . '" alt="Visa" width="32" ' . $style . ' />';
202
		$icon .= '<img src="' . WC_HTTPS::force_https_url( WC()->plugin_url() . '/assets/images/icons/credit-cards/mastercard' . $ext ) . '" alt="Mastercard" width="32" ' . $style . ' />';
203
		$icon .= '<img src="' . WC_HTTPS::force_https_url( WC()->plugin_url() . '/assets/images/icons/credit-cards/amex' . $ext ) . '" alt="Amex" width="32" ' . $style . ' />';
204
205
		if ( 'USD' === get_woocommerce_currency() ) {
206
			$icon .= '<img src="' . WC_HTTPS::force_https_url( WC()->plugin_url() . '/assets/images/icons/credit-cards/discover' . $ext ) . '" alt="Discover" width="32" ' . $style . ' />';
207
			$icon .= '<img src="' . WC_HTTPS::force_https_url( WC()->plugin_url() . '/assets/images/icons/credit-cards/jcb' . $ext ) . '" alt="JCB" width="32" ' . $style . ' />';
208
			$icon .= '<img src="' . WC_HTTPS::force_https_url( WC()->plugin_url() . '/assets/images/icons/credit-cards/diners' . $ext ) . '" alt="Diners" width="32" ' . $style . ' />';
209
		}
210
211
		if ( $this->bitcoin && $this->stripe_checkout ) {
212
			$icon .= '<img src="' . WC_HTTPS::force_https_url( plugins_url( '/assets/images/bitcoin' . $ext, WC_STRIPE_MAIN_FILE ) ) . '" alt="Bitcoin" width="24" ' . $style . ' />';
213
		}
214
215
		return apply_filters( 'woocommerce_gateway_icon', $icon, $this->id );
216
	}
217
218
	/**
219
	 * Get Stripe amount to pay
220
	 *
221
	 * @param float  $total Amount due.
222
	 * @param string $currency Accepted currency.
223
	 *
224
	 * @return float|int
225
	 */
226 View Code Duplication
	public function get_stripe_amount( $total, $currency = '' ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
227
		if ( ! $currency ) {
228
			$currency = get_woocommerce_currency();
229
		}
230
		switch ( strtoupper( $currency ) ) {
231
			// Zero decimal currencies.
232
			case 'BIF' :
233
			case 'CLP' :
234
			case 'DJF' :
235
			case 'GNF' :
236
			case 'JPY' :
237
			case 'KMF' :
238
			case 'KRW' :
239
			case 'MGA' :
240
			case 'PYG' :
241
			case 'RWF' :
242
			case 'VND' :
243
			case 'VUV' :
244
			case 'XAF' :
245
			case 'XOF' :
246
			case 'XPF' :
247
				$total = absint( $total );
248
				break;
249
			default :
250
				$total = round( $total, 2 ) * 100; // In cents.
251
				break;
252
		}
253
		return $total;
254
	}
255
256
	/**
257
	 * Initializes Apple Pay process on settings page.
258
	 *
259
	 * @since 3.1.0
260
	 * @version 3.1.0
261
	 */
262
	public function init_apple_pay() {
263
		if ( 
264
			is_admin() && 
265
			isset( $_GET['page'] ) && 'wc-settings' === $_GET['page'] && 
266
			isset( $_GET['tab'] ) && 'checkout' === $_GET['tab'] &&
267
			isset( $_GET['section'] ) && 'stripe' === $_GET['section']
268
		) {
269
			$this->process_apple_pay_verification();
270
		}
271
	}
272
273
	/**
274
	 * Registers the domain with Stripe/Apple Pay
275
	 *
276
	 * @since 3.1.0
277
	 * @version 3.1.0
278
	 * @param string $secret_key
279
	 */
280
	private function _register_apple_pay_domain( $secret_key = '' ) {
281
		if ( empty( $secret_key ) ) {
282
			throw new Exception( __( 'Unable to verify domain - missing secret key.', 'woocommerce-gateway-stripe' ) );
283
		}
284
285
		$endpoint = 'https://api.stripe.com/v1/apple_pay/domains';
286
287
		$data = array(
288
			'domain_name' => $_SERVER['HTTP_HOST'],
289
		);
290
291
		$headers = array(
292
			'User-Agent'    => 'WooCommerce Stripe Apple Pay',
293
			'Authorization' => 'Bearer ' . $secret_key,
294
		);
295
296
		$response = wp_remote_post( $endpoint, array(
297
			'headers' => $headers,
298
			'body'    => http_build_query( $data ),
299
		) );
300
301
		if ( 200 !== $response['response']['code'] ) {
302
			throw new Exception( sprintf( __( 'Unable to verify domain - %s', 'woocommerce-gateway-stripe' ), $response['response']['message'] ) );
303
		}
304
	}
305
306
	/**
307
	 * Processes the Apple Pay domain verification.
308
	 *
309
	 * @since 3.1.0
310
	 * @version 3.1.0
311
	 */
312
	public function process_apple_pay_verification() {
313
		$gateway_settings = get_option( 'woocommerce_stripe_settings', '' );
314
315
		try {
316
			$path     = untrailingslashit( preg_replace( "!${_SERVER['SCRIPT_NAME']}$!", '', $_SERVER['SCRIPT_FILENAME'] ) );
317
			$dir      = '.well-known';
318
			$file     = 'apple-developer-merchantid-domain-association';
319
			$fullpath = $path . '/' . $dir . '/' . $file;
320
321
			if ( 'yes' === $gateway_settings['apple_pay_domain_set'] && file_exists( $fullpath ) ) {
322
				return;
323
			}
324
325
			if ( ! file_exists( $path . '/' . $dir ) ) {
326
				if ( ! mkdir( $path . '/' . $dir, 0755 ) ) {
327
					throw new Exception( __( 'Unable to create domain association folder to domain root.', 'woocommerce-gateway-stripe' ) );
328
				}
329
			}
330
331
			if ( ! file_exists( $fullpath ) ) {
332
				if ( ! copy( WC_STRIPE_PLUGIN_PATH . '/' . $file, $fullpath ) ) {
333
					throw new Exception( __( 'Unable to copy domain association file to domain root.', 'woocommerce-gateway-stripe' ) );
334
				}
335
			}
336
337
			// At this point then the domain association folder and file should be available.
338
			// Proceed to verify/and or verify again.
339
			$this->_register_apple_pay_domain( $this->secret_key );
340
341
			// No errors to this point, verification success!
342
			$gateway_settings['apple_pay_domain_set'] = 'yes';
343
			$this->apple_pay_domain_set = true;
344
345
			update_option( 'woocommerce_stripe_settings', $gateway_settings );
346
347
			$this->log( __( 'Your domain has been verified with Apple Pay!', 'woocommerce-gateway-stripe' ) );
348
349
		} catch ( Exception $e ) {
350
			$gateway_settings['apple_pay_domain_set'] = 'no';
351
352
			update_option( 'woocommerce_stripe_settings', $gateway_settings );
353
354
			$this->log( sprintf( __( 'Error: %s', 'woocommerce-gateway-stripe' ), $e->getMessage() ) );
355
		}
356
	}
357
358
	/**
359
	 * Check if SSL is enabled and notify the user
360
	 */
361
	public function admin_notices() {
362
		if ( 'no' === $this->enabled ) {
363
			return;
364
		}
365
366
		/**
367
		 * Apple pay is enabled by default and domain verification initializes 
368
		 * when setting screen is displayed. So if domain verification is not set,
369
		 * something went wrong so lets notify user.
370
		 */
371
		if ( ! empty( $this->secret_key ) && $this->apple_pay && ! $this->apple_pay_domain_set ) {
372
			echo '<div class="error stripe-apple-pay-message"><p>' . sprintf( __( 'Apple Pay domain verification failed. Please check the %1$slog%2$s to see the issue.', 'woocommerce-gateway-stripe' ), '<a href="' . admin_url( 'admin.php?page=wc-status&tab=logs' ) . '">', '</a>' ) . '</p></div>';
373
		}
374
375
		// Show message if enabled and FORCE SSL is disabled and WordpressHTTPS plugin is not detected.
376 View Code Duplication
		if ( ( function_exists( 'wc_site_is_https' ) && ! wc_site_is_https() ) && ( 'no' === get_option( 'woocommerce_force_ssl_checkout' ) && ! class_exists( 'WordPressHTTPS' ) ) ) {
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...
377
			echo '<div class="error stripe-ssl-message"><p>' . sprintf( __( 'Stripe is enabled, but the <a href="%s">force SSL option</a> is disabled; your checkout may not be secure! Please enable SSL and ensure your server has a valid SSL certificate - Stripe will only work in test mode.', 'woocommerce-gateway-stripe' ), admin_url( 'admin.php?page=wc-settings&tab=checkout' ) ) . '</p></div>';
378
		}
379
	}
380
381
	/**
382
	 * Check if this gateway is enabled
383
	 */
384 View Code Duplication
	public function is_available() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
385
		if ( 'yes' === $this->enabled ) {
386
			if ( ! $this->testmode && is_checkout() && ! is_ssl() ) {
387
				return false;
388
			}
389
			if ( ! $this->secret_key || ! $this->publishable_key ) {
390
				return false;
391
			}
392
			return true;
393
		}
394
		return false;
395
	}
396
397
	/**
398
	 * Initialise Gateway Settings Form Fields
399
	 */
400
	public function init_form_fields() {
401
		$this->form_fields = include( 'settings-stripe.php' );
402
	}
403
404
	/**
405
	 * Payment form on checkout page
406
	 */
407
	public function payment_fields() {
408
		$user                 = wp_get_current_user();
409
		$display_tokenization = $this->supports( 'tokenization' ) && is_checkout() && $this->saved_cards;
410
		$total                = WC()->cart->total;
411
412
		// If paying from order, we need to get total from order not cart.
413
		if ( isset( $_GET['pay_for_order'] ) && isset( $_GET['key'] ) ) {
414
			$order = wc_get_order( wc_get_order_id_by_order_key( wc_clean( $_GET['key'] ) ) );
415
			$total = $order->get_total();
416
		}
417
418 View Code Duplication
		if ( $user->ID ) {
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...
419
			$user_email = get_user_meta( $user->ID, 'billing_email', true );
420
			$user_email = $user_email ? $user_email : $user->user_email;
421
		} else {
422
			$user_email = '';
423
		}
424
425
		if ( is_add_payment_method_page() ) {
426
			$pay_button_text = __( 'Add Card', 'woocommerce-gateway-stripe' );
427
		} else {
428
			$pay_button_text = '';
429
		}
430
431
		echo '<div
432
			id="stripe-payment-data"
433
			data-panel-label="' . esc_attr( $pay_button_text ) . '"
434
			data-description=""
435
			data-email="' . esc_attr( $user_email ) . '"
436
			data-amount="' . esc_attr( $this->get_stripe_amount( $total ) ) . '"
437
			data-name="' . esc_attr( $this->statement_descriptor ) . '"
438
			data-currency="' . esc_attr( strtolower( get_woocommerce_currency() ) ) . '"
439
			data-image="' . esc_attr( $this->stripe_checkout_image ) . '"
440
			data-bitcoin="' . esc_attr( $this->bitcoin ? 'true' : 'false' ) . '"
441
			data-locale="' . esc_attr( $this->stripe_checkout_locale ? $this->stripe_checkout_locale : 'en' ) . '"
442
			data-allow-remember-me="' . esc_attr( $this->allow_remember_me ? 'true' : 'false' ) . '">';
443
444
		if ( $this->description ) {
445
			echo apply_filters( 'wc_stripe_description', wpautop( wp_kses_post( $this->description ) ) );
446
		}
447
448
		if ( $display_tokenization ) {
449
			$this->tokenization_script();
450
			$this->saved_payment_methods();
451
		}
452
453
		if ( ! $this->stripe_checkout ) {
454
			$this->form();
455
456
			if ( $display_tokenization ) {
457
				$this->save_payment_method_checkbox();
458
			}
459
		}
460
461
		echo '</div>';
462
	}
463
464
	/**
465
	 * Localize Stripe messages based on code
466
	 *
467
	 * @since 3.0.6
468
	 * @version 3.0.6
469
	 * @return array
470
	 */
471
	public function get_localized_messages() {
472
		return apply_filters( 'wc_stripe_localized_messages', array(
473
			'invalid_number'        => __( 'The card number is not a valid credit card number.', 'woocommerce-gateway-stripe' ),
474
			'invalid_expiry_month'  => __( 'The card\'s expiration month is invalid.', 'woocommerce-gateway-stripe' ),
475
			'invalid_expiry_year'   => __( 'The card\'s expiration year is invalid.', 'woocommerce-gateway-stripe' ),
476
			'invalid_cvc'           => __( 'The card\'s security code is invalid.', 'woocommerce-gateway-stripe' ),
477
			'incorrect_number'      => __( 'The card number is incorrect.', 'woocommerce-gateway-stripe' ),
478
			'expired_card'          => __( 'The card has expired.', 'woocommerce-gateway-stripe' ),
479
			'incorrect_cvc'         => __( 'The card\'s security code is incorrect.', 'woocommerce-gateway-stripe' ),
480
			'incorrect_zip'         => __( 'The card\'s zip code failed validation.', 'woocommerce-gateway-stripe' ),
481
			'card_declined'         => __( 'The card was declined.', 'woocommerce-gateway-stripe' ),
482
			'missing'               => __( 'There is no card on a customer that is being charged.', 'woocommerce-gateway-stripe' ),
483
			'processing_error'      => __( 'An error occurred while processing the card.', 'woocommerce-gateway-stripe' ),
484
			'invalid_request_error' => __( 'Could not find payment information.', 'woocommerce-gateway-stripe' ),
485
		) );
486
	}
487
488
	/**
489
	 * Load admin scripts.
490
	 *
491
	 * @since 3.1.0
492
	 * @version 3.1.0
493
	 */
494
	public function admin_scripts() {
495
		if ( 'woocommerce_page_wc-settings' !== get_current_screen()->id ) {
496
			return;
497
		}
498
499
		$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
500
501
		wp_enqueue_script( 'woocommerce_stripe_admin', plugins_url( 'assets/js/stripe-admin' . $suffix . '.js', WC_STRIPE_MAIN_FILE ), array(), WC_STRIPE_VERSION, true );
502
503
		$stripe_admin_params = array(
504
			'localized_messages' => array(
505
				'not_valid_live_key_msg' => __( 'This is not a valid live key. Live keys start with "sk_live_" and "pk_live_".', 'woocommerce-gateway-stripe' ),
506
				'not_valid_test_key_msg' => __( 'This is not a valid test key. Test keys start with "sk_test_" and "pk_test_".', 'woocommerce-gateway-stripe' ),
507
				're_verify_button_text'  => __( 'Re-verify Domain', 'woocommerce-gateway-stripe' ),
508
				'missing_secret_key'     => __( 'Missing Secret Key. Please set the secret key field above and re-try.', 'woocommerce-gateway-stripe' ),
509
			),
510
			'ajaxurl'            => admin_url( 'admin-ajax.php' ),
511
			'nonce'              => array( 
512
				'apple_pay_domain_nonce' => wp_create_nonce( '_wc_stripe_apple_pay_domain_nonce' ),
513
			),
514
		);
515
516
		wp_localize_script( 'woocommerce_stripe_admin', 'wc_stripe_admin_params', apply_filters( 'wc_stripe_admin_params', $stripe_admin_params ) );
517
	}
518
519
	/**
520
	 * payment_scripts function.
521
	 *
522
	 * Outputs scripts used for stripe payment
523
	 *
524
	 * @access public
525
	 */
526
	public function payment_scripts() {
527
		if ( ! is_cart() && ! is_checkout() && ! isset( $_GET['pay_for_order'] ) ) {
528
			return;
529
		}
530
531
		$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
532
533
		if ( $this->stripe_checkout ) {
534
			wp_enqueue_script( 'stripe_checkout', 'https://checkout.stripe.com/v2/checkout.js', '', '2.0', true );
535
			wp_enqueue_script( 'woocommerce_stripe', plugins_url( 'assets/js/stripe-checkout' . $suffix . '.js', WC_STRIPE_MAIN_FILE ), array( 'stripe' ), WC_STRIPE_VERSION, true );
536 View Code Duplication
		} else {
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...
537
			wp_enqueue_script( 'stripe', 'https://js.stripe.com/v2/', '', '1.0', true );
538
			wp_enqueue_script( 'woocommerce_stripe', plugins_url( 'assets/js/stripe' . $suffix . '.js', WC_STRIPE_MAIN_FILE ), array( 'jquery-payment', 'stripe' ), WC_STRIPE_VERSION, true );
539
		}
540
541
		$stripe_params = array(
542
			'key'                  => $this->publishable_key,
543
			'i18n_terms'           => __( 'Please accept the terms and conditions first', 'woocommerce-gateway-stripe' ),
544
			'i18n_required_fields' => __( 'Please fill in required checkout fields first', 'woocommerce-gateway-stripe' ),
545
		);
546
547
		// If we're on the pay page we need to pass stripe.js the address of the order.
548
		if ( isset( $_GET['pay_for_order'] ) && 'true' === $_GET['pay_for_order'] ) {
549
			$order_id = wc_get_order_id_by_order_key( urldecode( $_GET['key'] ) );
550
			$order    = wc_get_order( $order_id );
551
552
			$stripe_params['billing_first_name'] = version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_first_name : $order->get_billing_first_name();
553
			$stripe_params['billing_last_name']  = version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_last_name : $order->get_billing_last_name();
554
			$stripe_params['billing_address_1']  = version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_address_1 : $order->get_billing_address_1();
555
			$stripe_params['billing_address_2']  = version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_address_2 : $order->get_billing_address_2();
556
			$stripe_params['billing_state']      = version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_state : $order->get_billing_state();
557
			$stripe_params['billing_city']       = version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_city : $order->get_billing_city();
558
			$stripe_params['billing_postcode']   = version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_postcode : $order->get_billing_postcode();
559
			$stripe_params['billing_country']    = version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_country : $order->get_billing_country();
560
		}
561
562
		$stripe_params['no_prepaid_card_msg']                     = __( 'Sorry, we\'re not accepting prepaid cards at this time.', 'woocommerce-gateway-stripe' );
563
		$stripe_params['allow_prepaid_card']                      = apply_filters( 'wc_stripe_allow_prepaid_card', true ) ? 'yes' : 'no';
564
		$stripe_params['stripe_checkout_require_billing_address'] = apply_filters( 'wc_stripe_checkout_require_billing_address', false ) ? 'yes' : 'no';
565
566
		// merge localized messages to be use in JS
567
		$stripe_params = array_merge( $stripe_params, $this->get_localized_messages() );
568
569
		wp_localize_script( 'woocommerce_stripe', 'wc_stripe_params', apply_filters( 'wc_stripe_params', $stripe_params ) );
570
	}
571
572
	/**
573
	 * Generate the request for the payment.
574
	 * @param  WC_Order $order
575
	 * @param  object $source
576
	 * @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...
577
	 */
578
	protected function generate_payment_request( $order, $source ) {
579
		$post_data                = array();
580
		$post_data['currency']    = strtolower( version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->get_order_currency() : $order->get_currency() );
581
		$post_data['amount']      = $this->get_stripe_amount( $order->get_total(), $post_data['currency'] );
582
		$post_data['description'] = sprintf( __( '%1$s - Order %2$s', 'woocommerce-gateway-stripe' ), $this->statement_descriptor, $order->get_order_number() );
583
		$post_data['capture']     = $this->capture ? 'true' : 'false';
584
585
		$billing_email      = version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_email : $order->get_billing_email();
586
		$billing_first_name = version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_first_name : $order->get_billing_first_name();
587
		$billing_last_name  = version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_last_name : $order->get_billing_last_name();
588
589
		if ( ! empty( $billing_email ) && apply_filters( 'wc_stripe_send_stripe_receipt', false ) ) {
590
			$post_data['receipt_email'] = $billing_email;
591
		}
592
593
		$post_data['expand[]']    = 'balance_transaction';
594
595
		$metadata = array(
596
			__( 'Customer Name', 'woocommerce-gateway-stripe' ) => sanitize_text_field( $billing_first_name ) . ' ' . sanitize_text_field( $billing_last_name ),
597
			__( 'Customer Email', 'woocommerce-gateway-stripe' ) => sanitize_email( $billing_email ),
598
		);
599
600
		$post_data['metadata'] = apply_filters( 'wc_stripe_payment_metadata', $metadata, $order, $source );
601
602
		if ( $source->customer ) {
603
			$post_data['customer'] = $source->customer;
604
		}
605
606
		if ( $source->source ) {
607
			$post_data['source'] = $source->source;
608
		}
609
610
		/**
611
		 * Filter the return value of the WC_Payment_Gateway_CC::generate_payment_request.
612
		 *
613
		 * @since 3.1.0
614
		 * @param array $post_data
615
		 * @param WC_Order $order
616
		 * @param object $source
617
		 */
618
		return apply_filters( 'wc_stripe_generate_payment_request', $post_data, $order, $source );
619
	}
620
621
	/**
622
	 * Get payment source. This can be a new token or existing card.
623
	 *
624
	 * @param string $user_id
625
	 * @param bool  $force_customer Should we force customer creation.
626
	 *
627
	 * @throws Exception When card was not added or for and invalid card.
628
	 * @return object
629
	 */
630
	protected function get_source( $user_id, $force_customer = false ) {
631
		$stripe_customer = new WC_Stripe_Customer( $user_id );
632
		$stripe_source   = false;
633
		$token_id        = false;
634
635
		// New CC info was entered and we have a new token to process
636
		if ( isset( $_POST['stripe_token'] ) ) {
637
			$stripe_token     = wc_clean( $_POST['stripe_token'] );
638
			$maybe_saved_card = isset( $_POST['wc-stripe-new-payment-method'] ) && ! empty( $_POST['wc-stripe-new-payment-method'] );
639
640
			// This is true if the user wants to store the card to their account.
641 View Code Duplication
			if ( ( $user_id && $this->saved_cards && $maybe_saved_card ) || $force_customer ) {
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...
642
				$stripe_source = $stripe_customer->add_card( $stripe_token );
643
644
				if ( is_wp_error( $stripe_source ) ) {
645
					throw new Exception( $stripe_source->get_error_message() );
646
				}
647
			} else {
648
				// Not saving token, so don't define customer either.
649
				$stripe_source   = $stripe_token;
650
				$stripe_customer = false;
651
			}
652
		} elseif ( isset( $_POST['wc-stripe-payment-token'] ) && 'new' !== $_POST['wc-stripe-payment-token'] ) {
653
			// Use an existing token, and then process the payment
654
655
			$token_id = wc_clean( $_POST['wc-stripe-payment-token'] );
656
			$token    = WC_Payment_Tokens::get( $token_id );
657
658
			if ( ! $token || $token->get_user_id() !== get_current_user_id() ) {
659
				WC()->session->set( 'refresh_totals', true );
660
				throw new Exception( __( 'Invalid payment method. Please input a new card number.', 'woocommerce-gateway-stripe' ) );
661
			}
662
663
			$stripe_source = $token->get_token();
664
		}
665
666
		return (object) array(
667
			'token_id' => $token_id,
668
			'customer' => $stripe_customer ? $stripe_customer->get_id() : false,
669
			'source'   => $stripe_source,
670
		);
671
	}
672
673
	/**
674
	 * Get payment source from an order. This could be used in the future for
675
	 * a subscription as an example, therefore using the current user ID would
676
	 * not work - the customer won't be logged in :)
677
	 *
678
	 * Not using 2.6 tokens for this part since we need a customer AND a card
679
	 * token, and not just one.
680
	 *
681
	 * @param object $order
682
	 * @return object
683
	 */
684
	protected function get_order_source( $order = null ) {
685
		$stripe_customer = new WC_Stripe_Customer();
686
		$stripe_source   = false;
687
		$token_id        = false;
688
689
		if ( $order ) {
690
			$order_id = version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->id : $order->get_id();
691
692
			if ( $meta_value = get_post_meta( $order_id, '_stripe_customer_id', true ) ) {
693
				$stripe_customer->set_id( $meta_value );
694
			}
695
696
			if ( $meta_value = get_post_meta( $order_id, '_stripe_card_id', true ) ) {
697
				$stripe_source = $meta_value;
698
			}
699
		}
700
701
		return (object) array(
702
			'token_id' => $token_id,
703
			'customer' => $stripe_customer ? $stripe_customer->get_id() : false,
704
			'source'   => $stripe_source,
705
		);
706
	}
707
708
	/**
709
	 * Process the payment
710
	 *
711
	 * @param int  $order_id Reference.
712
	 * @param bool $retry Should we retry on fail.
713
	 * @param bool $force_customer Force user creation.
714
	 *
715
	 * @throws Exception If payment will not be accepted.
716
	 *
717
	 * @return array|void
718
	 */
719
	public function process_payment( $order_id, $retry = true, $force_customer = false ) {
720
		try {
721
			$order  = wc_get_order( $order_id );
722
			$source = $this->get_source( get_current_user_id(), $force_customer );
723
724 View Code Duplication
			if ( empty( $source->source ) && empty( $source->customer ) ) {
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...
725
				$error_msg = __( 'Please enter your card details to make a payment.', 'woocommerce-gateway-stripe' );
726
				$error_msg .= ' ' . __( 'Developers: Please make sure that you are including jQuery and there are no JavaScript errors on the page.', 'woocommerce-gateway-stripe' );
727
				throw new Exception( $error_msg );
728
			}
729
730
			// Store source to order meta.
731
			$this->save_source( $order, $source );
732
733
			// Handle payment.
734
			if ( $order->get_total() > 0 ) {
735
736 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...
737
					throw new Exception( 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 ) ) );
738
				}
739
740
				$this->log( "Info: Begin processing payment for order $order_id for the amount of {$order->get_total()}" );
741
742
				// Make the request.
743
				$response = WC_Stripe_API::request( $this->generate_payment_request( $order, $source ) );
744
745
				if ( is_wp_error( $response ) ) {
746
					// Customer param wrong? The user may have been deleted on stripe's end. Remove customer_id. Can be retried without.
747
					if ( 'customer' === $response->get_error_code() && $retry ) {
748
						delete_user_meta( get_current_user_id(), '_stripe_customer_id' );
749
						return $this->process_payment( $order_id, false, $force_customer );
750
						// Source param wrong? The CARD may have been deleted on stripe's end. Remove token and show message.
751
					} elseif ( 'source' === $response->get_error_code() && $source->token_id ) {
752
						$token = WC_Payment_Tokens::get( $source->token_id );
753
						$token->delete();
754
						$message = __( 'This card is no longer available and has been removed.', 'woocommerce-gateway-stripe' );
755
						$order->add_order_note( $message );
756
						throw new Exception( $message );
757
					}
758
759
					$localized_messages = $this->get_localized_messages();
760
761
					$message = isset( $localized_messages[ $response->get_error_code() ] ) ? $localized_messages[ $response->get_error_code() ] : $response->get_error_message();
762
763
					$order->add_order_note( $message );
764
765
					throw new Exception( $message );
766
				}
767
768
				// Process valid response.
769
				$this->process_response( $response, $order );
770
			} else {
771
				$order->payment_complete();
772
			}
773
774
			// Remove cart.
775
			WC()->cart->empty_cart();
776
777
			do_action( 'wc_gateway_stripe_process_payment', $response, $order );
0 ignored issues
show
Bug introduced by
The variable $response 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...
778
779
			// Return thank you page redirect.
780
			return array(
781
				'result'   => 'success',
782
				'redirect' => $this->get_return_url( $order ),
783
			);
784
785
		} catch ( Exception $e ) {
786
			wc_add_notice( $e->getMessage(), 'error' );
787
			$this->log( sprintf( __( 'Error: %s', 'woocommerce-gateway-stripe' ), $e->getMessage() ) );
788
789
			if ( $order->has_status( array( 'pending', 'failed' ) ) ) {
790
				$this->send_failed_order_email( $order_id );
791
			}
792
793
			do_action( 'wc_gateway_stripe_process_payment_error', $e, $order );
794
795
			return array(
796
				'result'   => 'fail',
797
				'redirect' => '',
798
			);
799
		}
800
	}
801
802
	/**
803
	 * Save source to order.
804
	 *
805
	 * @param WC_Order $order For to which the source applies.
806
	 * @param stdClass $source Source information.
807
	 */
808
	protected function save_source( $order, $source ) {
809
		$order_id = version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->id : $order->get_id();
810
811
		// Store source in the order.
812
		if ( $source->customer ) {
813
			update_post_meta( $order_id, '_stripe_customer_id', $source->customer );
814
		}
815
		if ( $source->source ) {
816
			update_post_meta( $order_id, '_stripe_card_id', $source->source );
817
		}
818
	}
819
820
	/**
821
	 * Store extra meta data for an order from a Stripe Response.
822
	 */
823
	public function process_response( $response, $order ) {
824
		$this->log( 'Processing response: ' . print_r( $response, true ) );
825
826
		$order_id = version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->id : $order->get_id();
827
828
		// Store charge data
829
		update_post_meta( $order_id, '_stripe_charge_id', $response->id );
830
		update_post_meta( $order_id, '_stripe_charge_captured', $response->captured ? 'yes' : 'no' );
831
832
		// Store other data such as fees
833 View Code Duplication
		if ( isset( $response->balance_transaction ) && isset( $response->balance_transaction->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...
834
			// Fees and Net needs to both come from Stripe to be accurate as the returned
835
			// values are in the local currency of the Stripe account, not from WC.
836
			$fee = ! empty( $response->balance_transaction->fee ) ? WC_Stripe::format_number( $response->balance_transaction, 'fee' ) : 0;
837
			$net = ! empty( $response->balance_transaction->net ) ? WC_Stripe::format_number( $response->balance_transaction, 'net' ) : 0;
838
			update_post_meta( $order_id, 'Stripe Fee', $fee );
839
			update_post_meta( $order_id, 'Net Revenue From Stripe', $net );
840
		}
841
842
		if ( $response->captured ) {
843
			$order->payment_complete( $response->id );
844
845
			$message = sprintf( __( 'Stripe charge complete (Charge ID: %s)', 'woocommerce-gateway-stripe' ), $response->id );
846
			$order->add_order_note( $message );
847
			$this->log( 'Success: ' . $message );
848
849
		} else {
850
			add_post_meta( $order_id, '_transaction_id', $response->id, true );
851
852
			if ( $order->has_status( array( 'pending', 'failed' ) ) ) {
853
				version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->reduce_order_stock() : wc_reduce_stock_levels( $order_id );
854
			}
855
856
			$order->update_status( 'on-hold', sprintf( __( 'Stripe charge authorized (Charge ID: %s). Process order to take payment, or cancel to remove the pre-authorization.', 'woocommerce-gateway-stripe' ), $response->id ) );
857
			$this->log( "Successful auth: $response->id" );
858
		}
859
860
		return $response;
861
	}
862
863
	/**
864
	 * Add payment method via account screen.
865
	 * We don't store the token locally, but to the Stripe API.
866
	 * @since 3.0.0
867
	 */
868
	public function add_payment_method() {
869
		if ( empty( $_POST['stripe_token'] ) || ! is_user_logged_in() ) {
870
			wc_add_notice( __( 'There was a problem adding the card.', 'woocommerce-gateway-stripe' ), 'error' );
871
			return;
872
		}
873
874
		$stripe_customer = new WC_Stripe_Customer( get_current_user_id() );
875
		$card            = $stripe_customer->add_card( wc_clean( $_POST['stripe_token'] ) );
876
877
		if ( is_wp_error( $card ) ) {
878
			$localized_messages = $this->get_localized_messages();
879
			$error_msg = __( 'There was a problem adding the card.', 'woocommerce-gateway-stripe' );
880
881
			// loop through the errors to find matching localized message
882
			foreach ( $card->errors as $error => $msg ) {
883
				if ( isset( $localized_messages[ $error ] ) ) {
884
					$error_msg = $localized_messages[ $error ];
885
				}
886
			}
887
888
			wc_add_notice( $error_msg, 'error' );
889
			return;
890
		}
891
892
		return array(
893
			'result'   => 'success',
894
			'redirect' => wc_get_endpoint_url( 'payment-methods' ),
895
		);
896
	}
897
898
	/**
899
	 * Refund a charge
900
	 * @param  int $order_id
901
	 * @param  float $amount
902
	 * @return bool
903
	 */
904
	public function process_refund( $order_id, $amount = null, $reason = '' ) {
905
		$order = wc_get_order( $order_id );
906
907
		if ( ! $order || ! $order->get_transaction_id() ) {
908
			return false;
909
		}
910
911
		$body = array();
912
913
		if ( ! is_null( $amount ) ) {
914
			$body['amount']	= $this->get_stripe_amount( $amount );
915
		}
916
917
		if ( $reason ) {
918
			$body['metadata'] = array(
919
				'reason'	=> $reason,
920
			);
921
		}
922
923
		$this->log( "Info: Beginning refund for order $order_id for the amount of {$amount}" );
924
925
		$response = WC_Stripe_API::request( $body, 'charges/' . $order->get_transaction_id() . '/refunds' );
926
927
		if ( is_wp_error( $response ) ) {
928
			$this->log( 'Error: ' . $response->get_error_message() );
929
			return $response;
930
		} elseif ( ! empty( $response->id ) ) {
931
			$refund_message = sprintf( __( 'Refunded %1$s - Refund ID: %2$s - Reason: %3$s', 'woocommerce-gateway-stripe' ), wc_price( $response->amount / 100 ), $response->id, $reason );
932
			$order->add_order_note( $refund_message );
933
			$this->log( 'Success: ' . html_entity_decode( strip_tags( $refund_message ) ) );
934
			return true;
935
		}
936
	}
937
938
	/**
939
	 * Sends the failed order email to admin
940
	 *
941
	 * @version 3.1.0
942
	 * @since 3.1.0
943
	 * @param int $order_id
944
	 * @return null
945
	 */
946
	public function send_failed_order_email( $order_id ) {
947
		$emails = WC()->mailer()->get_emails();
948
		if ( ! empty( $emails ) && ! empty( $order_id ) ) {
949
			$emails['WC_Email_Failed_Order']->trigger( $order_id );
950
		}
951
	}
952
953
	/**
954
	 * Logs
955
	 *
956
	 * @since 3.1.0
957
	 * @version 3.1.0
958
	 *
959
	 * @param string $message
960
	 */
961
	public function log( $message ) {
962
		if ( $this->logging ) {
963
			WC_Stripe::log( $message );
964
		}
965
	}
966
}
967