Completed
Push — master ( 919814...abaa50 )
by Roy
02:23
created

WC_Gateway_Stripe::admin_notices()   C

Complexity

Conditions 11
Paths 21

Size

Total Lines 23
Code Lines 15

Duplication

Lines 3
Ratio 13.04 %

Importance

Changes 0
Metric Value
dl 3
loc 23
rs 5.3929
c 0
b 0
f 0
cc 11
eloc 15
nc 21
nop 0

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
if ( ! defined( 'ABSPATH' ) ) {
3
	exit;
4
}
5
6
/**
7
 * WC_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
	 * Do we accept Apple Pay?
78
	 *
79
	 * @var bool
80
	 */
81
	public $apple_pay;
82
83
	/**
84
	 * Apple Pay Domain Set.
85
	 *
86
	 * @var bool
87
	 */
88
	public $apple_pay_domain_set;
89
90
	/**
91
	 * Apple Pay button style.
92
	 *
93
	 * @var bool
94
	 */
95
	public $apple_pay_button;
96
97
	/**
98
	 * Is test mode active?
99
	 *
100
	 * @var bool
101
	 */
102
	public $testmode;
103
104
	/**
105
	 * Logging enabled?
106
	 *
107
	 * @var bool
108
	 */
109
	public $logging;
110
111
	/**
112
	 * Stores Apple Pay domain verification issues.
113
	 *
114
	 * @var string
115
	 */
116
	public $apple_pay_verify_notice;
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->apple_pay_verify_notice = '';
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
			$parsed_response = json_decode( $response['body'] );
303
304
			$this->apple_pay_verify_notice = $parsed_response->error->message;
305
306
			throw new Exception( sprintf( __( 'Unable to verify domain - %s', 'woocommerce-gateway-stripe' ), $parsed_response->error->message ) );
307
		}
308
	}
309
310
	/**
311
	 * Processes the Apple Pay domain verification.
312
	 *
313
	 * @since 3.1.0
314
	 * @version 3.1.0
315
	 */
316
	public function process_apple_pay_verification() {
317
		$gateway_settings = get_option( 'woocommerce_stripe_settings', '' );
318
319
		try {
320
			$path     = untrailingslashit( preg_replace( "!${_SERVER['SCRIPT_NAME']}$!", '', $_SERVER['SCRIPT_FILENAME'] ) );
321
			$dir      = '.well-known';
322
			$file     = 'apple-developer-merchantid-domain-association';
323
			$fullpath = $path . '/' . $dir . '/' . $file;
324
325
			if ( ! empty( $gateway_settings['apple_pay_domain_set'] ) && 'yes' === $gateway_settings['apple_pay_domain_set'] && file_exists( $fullpath ) ) {
326
				return;
327
			}
328
329
			if ( ! file_exists( $path . '/' . $dir ) ) {
330
				if ( ! @mkdir( $path . '/' . $dir, 0755 ) ) {
331
					throw new Exception( __( 'Unable to create domain association folder to domain root.', 'woocommerce-gateway-stripe' ) );
332
				}
333
			}
334
335
			if ( ! file_exists( $fullpath ) ) {
336
				if ( ! @copy( WC_STRIPE_PLUGIN_PATH . '/' . $file, $fullpath ) ) {
337
					throw new Exception( __( 'Unable to copy domain association file to domain root.', 'woocommerce-gateway-stripe' ) );
338
				}
339
			}
340
341
			// At this point then the domain association folder and file should be available.
342
			// Proceed to verify/and or verify again.
343
			$this->register_apple_pay_domain( $this->secret_key );
344
345
			// No errors to this point, verification success!
346
			$gateway_settings['apple_pay_domain_set'] = 'yes';
347
			$this->apple_pay_domain_set = true;
348
349
			update_option( 'woocommerce_stripe_settings', $gateway_settings );
350
351
			$this->log( __( 'Your domain has been verified with Apple Pay!', 'woocommerce-gateway-stripe' ) );
352
353
		} catch ( Exception $e ) {
354
			$gateway_settings['apple_pay_domain_set'] = 'no';
355
356
			update_option( 'woocommerce_stripe_settings', $gateway_settings );
357
358
			$this->log( sprintf( __( 'Error: %s', 'woocommerce-gateway-stripe' ), $e->getMessage() ) );
359
		}
360
	}
361
362
	/**
363
	 * Check if SSL is enabled and notify the user
364
	 */
365
	public function admin_notices() {
366
		if ( 'no' === $this->enabled ) {
367
			return;
368
		}
369
370
		if ( $this->apple_pay && ! empty( $this->apple_pay_verify_notice ) ) {
371
			echo '<div class="error stripe-apple-pay-message"><p>' . wp_kses( make_clickable( $this->apple_pay_verify_notice ) ) . '</p></div>';
372
		}
373
374
		/**
375
		 * Apple pay is enabled by default and domain verification initializes 
376
		 * when setting screen is displayed. So if domain verification is not set,
377
		 * something went wrong so lets notify user.
378
		 */
379
		if ( ! empty( $this->secret_key ) && $this->apple_pay && ! $this->apple_pay_domain_set ) {
380
			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. (Logging must be enabled to see recorded logs)', 'woocommerce-gateway-stripe' ), '<a href="' . admin_url( 'admin.php?page=wc-status&tab=logs' ) . '">', '</a>' ) . '</p></div>';
381
		}
382
383
		// Show message if enabled and FORCE SSL is disabled and WordpressHTTPS plugin is not detected.
384 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...
385
			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>';
386
		}
387
	}
388
389
	/**
390
	 * Check if this gateway is enabled
391
	 */
392 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...
393
		if ( 'yes' === $this->enabled ) {
394
			if ( ! $this->testmode && is_checkout() && ! is_ssl() ) {
395
				return false;
396
			}
397
			if ( ! $this->secret_key || ! $this->publishable_key ) {
398
				return false;
399
			}
400
			return true;
401
		}
402
		return false;
403
	}
404
405
	/**
406
	 * Initialise Gateway Settings Form Fields
407
	 */
408
	public function init_form_fields() {
409
		$this->form_fields = include( 'settings-stripe.php' );
410
	}
411
412
	/**
413
	 * Payment form on checkout page
414
	 */
415
	public function payment_fields() {
416
		$user                 = wp_get_current_user();
417
		$display_tokenization = $this->supports( 'tokenization' ) && is_checkout() && $this->saved_cards;
418
		$total                = WC()->cart->total;
419
420
		// If paying from order, we need to get total from order not cart.
421
		if ( isset( $_GET['pay_for_order'] ) && ! empty( $_GET['key'] ) ) {
422
			$order = wc_get_order( wc_get_order_id_by_order_key( wc_clean( $_GET['key'] ) ) );
423
			$total = $order->get_total();
424
		}
425
426 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...
427
			$user_email = get_user_meta( $user->ID, 'billing_email', true );
428
			$user_email = $user_email ? $user_email : $user->user_email;
429
		} else {
430
			$user_email = '';
431
		}
432
433
		if ( is_add_payment_method_page() ) {
434
			$pay_button_text = __( 'Add Card', 'woocommerce-gateway-stripe' );
435
		} else {
436
			$pay_button_text = '';
437
		}
438
439
		echo '<div
440
			id="stripe-payment-data"
441
			data-panel-label="' . esc_attr( $pay_button_text ) . '"
442
			data-description=""
443
			data-email="' . esc_attr( $user_email ) . '"
444
			data-amount="' . esc_attr( $this->get_stripe_amount( $total ) ) . '"
445
			data-name="' . esc_attr( $this->statement_descriptor ) . '"
446
			data-currency="' . esc_attr( strtolower( get_woocommerce_currency() ) ) . '"
447
			data-image="' . esc_attr( $this->stripe_checkout_image ) . '"
448
			data-bitcoin="' . esc_attr( $this->bitcoin ? 'true' : 'false' ) . '"
449
			data-locale="' . esc_attr( $this->stripe_checkout_locale ? $this->stripe_checkout_locale : 'en' ) . '"
450
			data-allow-remember-me="' . esc_attr( $this->saved_cards ? 'true' : 'false' ) . '">';
451
452
		if ( $this->description ) {
453
			echo apply_filters( 'wc_stripe_description', wpautop( wp_kses_post( $this->description ) ) );
454
		}
455
456
		if ( $display_tokenization ) {
457
			$this->tokenization_script();
458
			$this->saved_payment_methods();
459
		}
460
461
		if ( ! $this->stripe_checkout ) {
462
			$this->form();
463
464
			if ( $display_tokenization ) {
465
				$this->save_payment_method_checkbox();
466
			}
467
		}
468
469
		echo '</div>';
470
	}
471
472
	/**
473
	 * Localize Stripe messages based on code
474
	 *
475
	 * @since 3.0.6
476
	 * @version 3.0.6
477
	 * @return array
478
	 */
479
	public function get_localized_messages() {
480
		return apply_filters( 'wc_stripe_localized_messages', array(
481
			'invalid_number'        => __( 'The card number is not a valid credit card number.', 'woocommerce-gateway-stripe' ),
482
			'invalid_expiry_month'  => __( 'The card\'s expiration month is invalid.', 'woocommerce-gateway-stripe' ),
483
			'invalid_expiry_year'   => __( 'The card\'s expiration year is invalid.', 'woocommerce-gateway-stripe' ),
484
			'invalid_cvc'           => __( 'The card\'s security code is invalid.', 'woocommerce-gateway-stripe' ),
485
			'incorrect_number'      => __( 'The card number is incorrect.', 'woocommerce-gateway-stripe' ),
486
			'expired_card'          => __( 'The card has expired.', 'woocommerce-gateway-stripe' ),
487
			'incorrect_cvc'         => __( 'The card\'s security code is incorrect.', 'woocommerce-gateway-stripe' ),
488
			'incorrect_zip'         => __( 'The card\'s zip code failed validation.', 'woocommerce-gateway-stripe' ),
489
			'card_declined'         => __( 'The card was declined.', 'woocommerce-gateway-stripe' ),
490
			'missing'               => __( 'There is no card on a customer that is being charged.', 'woocommerce-gateway-stripe' ),
491
			'processing_error'      => __( 'An error occurred while processing the card.', 'woocommerce-gateway-stripe' ),
492
			'invalid_request_error' => __( 'Could not find payment information.', 'woocommerce-gateway-stripe' ),
493
		) );
494
	}
495
496
	/**
497
	 * Load admin scripts.
498
	 *
499
	 * @since 3.1.0
500
	 * @version 3.1.0
501
	 */
502
	public function admin_scripts() {
503
		if ( 'woocommerce_page_wc-settings' !== get_current_screen()->id ) {
504
			return;
505
		}
506
507
		$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
508
509
		wp_enqueue_script( 'woocommerce_stripe_admin', plugins_url( 'assets/js/stripe-admin' . $suffix . '.js', WC_STRIPE_MAIN_FILE ), array(), WC_STRIPE_VERSION, true );
510
511
		$stripe_admin_params = array(
512
			'localized_messages' => array(
513
				'not_valid_live_key_msg' => __( 'This is not a valid live key. Live keys start with "sk_live_" and "pk_live_".', 'woocommerce-gateway-stripe' ),
514
				'not_valid_test_key_msg' => __( 'This is not a valid test key. Test keys start with "sk_test_" and "pk_test_".', 'woocommerce-gateway-stripe' ),
515
				're_verify_button_text'  => __( 'Re-verify Domain', 'woocommerce-gateway-stripe' ),
516
				'missing_secret_key'     => __( 'Missing Secret Key. Please set the secret key field above and re-try.', 'woocommerce-gateway-stripe' ),
517
			),
518
			'ajaxurl'            => admin_url( 'admin-ajax.php' ),
519
			'nonce'              => array( 
520
				'apple_pay_domain_nonce' => wp_create_nonce( '_wc_stripe_apple_pay_domain_nonce' ),
521
			),
522
		);
523
524
		wp_localize_script( 'woocommerce_stripe_admin', 'wc_stripe_admin_params', apply_filters( 'wc_stripe_admin_params', $stripe_admin_params ) );
525
	}
526
527
	/**
528
	 * payment_scripts function.
529
	 *
530
	 * Outputs scripts used for stripe payment
531
	 *
532
	 * @access public
533
	 */
534
	public function payment_scripts() {
535
		if ( ! is_cart() && ! is_checkout() && ! isset( $_GET['pay_for_order'] ) && ! is_add_payment_method_page() ) {
536
			return;
537
		}
538
539
		$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
540
541
		if ( $this->stripe_checkout ) {
542
			wp_enqueue_script( 'stripe_checkout', 'https://checkout.stripe.com/v2/checkout.js', '', '2.0', true );
543
			wp_enqueue_script( 'woocommerce_stripe', plugins_url( 'assets/js/stripe-checkout' . $suffix . '.js', WC_STRIPE_MAIN_FILE ), array( 'stripe_checkout' ), WC_STRIPE_VERSION, true );
544 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...
545
			wp_enqueue_script( 'stripe', 'https://js.stripe.com/v2/', '', '1.0', true );
546
			wp_enqueue_script( 'woocommerce_stripe', plugins_url( 'assets/js/stripe' . $suffix . '.js', WC_STRIPE_MAIN_FILE ), array( 'jquery-payment', 'stripe' ), WC_STRIPE_VERSION, true );
547
		}
548
549
		$stripe_params = array(
550
			'key'                  => $this->publishable_key,
551
			'i18n_terms'           => __( 'Please accept the terms and conditions first', 'woocommerce-gateway-stripe' ),
552
			'i18n_required_fields' => __( 'Please fill in required checkout fields first', 'woocommerce-gateway-stripe' ),
553
		);
554
555
		// If we're on the pay page we need to pass stripe.js the address of the order.
556
		if ( isset( $_GET['pay_for_order'] ) && 'true' === $_GET['pay_for_order'] ) {
557
			$order_id = wc_get_order_id_by_order_key( urldecode( $_GET['key'] ) );
558
			$order    = wc_get_order( $order_id );
559
560
			$stripe_params['billing_first_name'] = version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_first_name : $order->get_billing_first_name();
561
			$stripe_params['billing_last_name']  = version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_last_name : $order->get_billing_last_name();
562
			$stripe_params['billing_address_1']  = version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_address_1 : $order->get_billing_address_1();
563
			$stripe_params['billing_address_2']  = version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_address_2 : $order->get_billing_address_2();
564
			$stripe_params['billing_state']      = version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_state : $order->get_billing_state();
565
			$stripe_params['billing_city']       = version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_city : $order->get_billing_city();
566
			$stripe_params['billing_postcode']   = version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_postcode : $order->get_billing_postcode();
567
			$stripe_params['billing_country']    = version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_country : $order->get_billing_country();
568
		}
569
570
		$stripe_params['no_prepaid_card_msg']                     = __( 'Sorry, we\'re not accepting prepaid cards at this time.', 'woocommerce-gateway-stripe' );
571
		$stripe_params['allow_prepaid_card']                      = apply_filters( 'wc_stripe_allow_prepaid_card', true ) ? 'yes' : 'no';
572
		$stripe_params['stripe_checkout_require_billing_address'] = apply_filters( 'wc_stripe_checkout_require_billing_address', false ) ? 'yes' : 'no';
573
574
		// merge localized messages to be use in JS
575
		$stripe_params = array_merge( $stripe_params, $this->get_localized_messages() );
576
577
		wp_localize_script( 'woocommerce_stripe', 'wc_stripe_params', apply_filters( 'wc_stripe_params', $stripe_params ) );
578
	}
579
580
	/**
581
	 * Generate the request for the payment.
582
	 * @param  WC_Order $order
583
	 * @param  object $source
584
	 * @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...
585
	 */
586
	protected function generate_payment_request( $order, $source ) {
587
		$post_data                = array();
588
		$post_data['currency']    = strtolower( version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->get_order_currency() : $order->get_currency() );
589
		$post_data['amount']      = $this->get_stripe_amount( $order->get_total(), $post_data['currency'] );
590
		$post_data['description'] = sprintf( __( '%1$s - Order %2$s', 'woocommerce-gateway-stripe' ), $this->statement_descriptor, $order->get_order_number() );
591
		$post_data['capture']     = $this->capture ? 'true' : 'false';
592
593
		$billing_email      = version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_email : $order->get_billing_email();
594
		$billing_first_name = version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_first_name : $order->get_billing_first_name();
595
		$billing_last_name  = version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_last_name : $order->get_billing_last_name();
596
597
		if ( ! empty( $billing_email ) && apply_filters( 'wc_stripe_send_stripe_receipt', false ) ) {
598
			$post_data['receipt_email'] = $billing_email;
599
		}
600
601
		$post_data['expand[]']    = 'balance_transaction';
602
603
		$metadata = array(
604
			__( 'Customer Name', 'woocommerce-gateway-stripe' ) => sanitize_text_field( $billing_first_name ) . ' ' . sanitize_text_field( $billing_last_name ),
605
			__( 'Customer Email', 'woocommerce-gateway-stripe' ) => sanitize_email( $billing_email ),
606
		);
607
608
		$post_data['metadata'] = apply_filters( 'wc_stripe_payment_metadata', $metadata, $order, $source );
609
610
		if ( $source->customer ) {
611
			$post_data['customer'] = $source->customer;
612
		}
613
614
		if ( $source->source ) {
615
			$post_data['source'] = $source->source;
616
		}
617
618
		/**
619
		 * Filter the return value of the WC_Payment_Gateway_CC::generate_payment_request.
620
		 *
621
		 * @since 3.1.0
622
		 * @param array $post_data
623
		 * @param WC_Order $order
624
		 * @param object $source
625
		 */
626
		return apply_filters( 'wc_stripe_generate_payment_request', $post_data, $order, $source );
627
	}
628
629
	/**
630
	 * Get payment source. This can be a new token or existing card.
631
	 *
632
	 * @param string $user_id
633
	 * @param bool  $force_customer Should we force customer creation.
634
	 *
635
	 * @throws Exception When card was not added or for and invalid card.
636
	 * @return object
637
	 */
638
	protected function get_source( $user_id, $force_customer = false ) {
639
		$stripe_customer = new WC_Stripe_Customer( $user_id );
640
		$stripe_source   = false;
641
		$token_id        = false;
642
643
		// New CC info was entered and we have a new token to process
644
		if ( isset( $_POST['stripe_token'] ) ) {
645
			$stripe_token     = wc_clean( $_POST['stripe_token'] );
646
			$maybe_saved_card = isset( $_POST['wc-stripe-new-payment-method'] ) && ! empty( $_POST['wc-stripe-new-payment-method'] );
647
648
			// This is true if the user wants to store the card to their account.
649 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...
650
				$stripe_source = $stripe_customer->add_card( $stripe_token );
651
652
				if ( is_wp_error( $stripe_source ) ) {
653
					throw new Exception( $stripe_source->get_error_message() );
654
				}
655
			} else {
656
				// Not saving token, so don't define customer either.
657
				$stripe_source   = $stripe_token;
658
				$stripe_customer = false;
659
			}
660
		} elseif ( isset( $_POST['wc-stripe-payment-token'] ) && 'new' !== $_POST['wc-stripe-payment-token'] ) {
661
			// Use an existing token, and then process the payment
662
663
			$token_id = wc_clean( $_POST['wc-stripe-payment-token'] );
664
			$token    = WC_Payment_Tokens::get( $token_id );
665
666
			if ( ! $token || $token->get_user_id() !== get_current_user_id() ) {
667
				WC()->session->set( 'refresh_totals', true );
668
				throw new Exception( __( 'Invalid payment method. Please input a new card number.', 'woocommerce-gateway-stripe' ) );
669
			}
670
671
			$stripe_source = $token->get_token();
672
		}
673
674
		return (object) array(
675
			'token_id' => $token_id,
676
			'customer' => $stripe_customer ? $stripe_customer->get_id() : false,
677
			'source'   => $stripe_source,
678
		);
679
	}
680
681
	/**
682
	 * Get payment source from an order. This could be used in the future for
683
	 * a subscription as an example, therefore using the current user ID would
684
	 * not work - the customer won't be logged in :)
685
	 *
686
	 * Not using 2.6 tokens for this part since we need a customer AND a card
687
	 * token, and not just one.
688
	 *
689
	 * @param object $order
690
	 * @return object
691
	 */
692
	protected function get_order_source( $order = null ) {
693
		$stripe_customer = new WC_Stripe_Customer();
694
		$stripe_source   = false;
695
		$token_id        = false;
696
697
		if ( $order ) {
698
			$order_id = version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->id : $order->get_id();
699
700
			if ( $meta_value = get_post_meta( $order_id, '_stripe_customer_id', true ) ) {
701
				$stripe_customer->set_id( $meta_value );
702
			}
703
704
			if ( $meta_value = get_post_meta( $order_id, '_stripe_card_id', true ) ) {
705
				$stripe_source = $meta_value;
706
			}
707
		}
708
709
		return (object) array(
710
			'token_id' => $token_id,
711
			'customer' => $stripe_customer ? $stripe_customer->get_id() : false,
712
			'source'   => $stripe_source,
713
		);
714
	}
715
716
	/**
717
	 * Process the payment
718
	 *
719
	 * @param int  $order_id Reference.
720
	 * @param bool $retry Should we retry on fail.
721
	 * @param bool $force_customer Force user creation.
722
	 *
723
	 * @throws Exception If payment will not be accepted.
724
	 *
725
	 * @return array|void
726
	 */
727
	public function process_payment( $order_id, $retry = true, $force_customer = false ) {
728
		try {
729
			$order  = wc_get_order( $order_id );
730
			$source = $this->get_source( get_current_user_id(), $force_customer );
731
732 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...
733
				$error_msg = __( 'Please enter your card details to make a payment.', 'woocommerce-gateway-stripe' );
734
				$error_msg .= ' ' . __( 'Developers: Please make sure that you are including jQuery and there are no JavaScript errors on the page.', 'woocommerce-gateway-stripe' );
735
				throw new Exception( $error_msg );
736
			}
737
738
			// Store source to order meta.
739
			$this->save_source( $order, $source );
740
741
			// Handle payment.
742
			if ( $order->get_total() > 0 ) {
743
744 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...
745
					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 ) ) );
746
				}
747
748
				$this->log( "Info: Begin processing payment for order $order_id for the amount of {$order->get_total()}" );
749
750
				// Make the request.
751
				$response = WC_Stripe_API::request( $this->generate_payment_request( $order, $source ) );
752
753
				if ( is_wp_error( $response ) ) {
754
					// Customer param wrong? The user may have been deleted on stripe's end. Remove customer_id. Can be retried without.
755
					if ( 'customer' === $response->get_error_code() && $retry ) {
756
						delete_user_meta( get_current_user_id(), '_stripe_customer_id' );
757
						return $this->process_payment( $order_id, false, $force_customer );
758
						// Source param wrong? The CARD may have been deleted on stripe's end. Remove token and show message.
759
					} elseif ( 'source' === $response->get_error_code() && $source->token_id ) {
760
						$token = WC_Payment_Tokens::get( $source->token_id );
761
						$token->delete();
762
						$message = __( 'This card is no longer available and has been removed.', 'woocommerce-gateway-stripe' );
763
						$order->add_order_note( $message );
764
						throw new Exception( $message );
765
					}
766
767
					$localized_messages = $this->get_localized_messages();
768
769
					$message = isset( $localized_messages[ $response->get_error_code() ] ) ? $localized_messages[ $response->get_error_code() ] : $response->get_error_message();
770
771
					$order->add_order_note( $message );
772
773
					throw new Exception( $message );
774
				}
775
776
				// Process valid response.
777
				$this->process_response( $response, $order );
778
			} else {
779
				$order->payment_complete();
780
			}
781
782
			// Remove cart.
783
			WC()->cart->empty_cart();
784
785
			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...
786
787
			// Return thank you page redirect.
788
			return array(
789
				'result'   => 'success',
790
				'redirect' => $this->get_return_url( $order ),
791
			);
792
793
		} catch ( Exception $e ) {
794
			wc_add_notice( $e->getMessage(), 'error' );
795
			$this->log( sprintf( __( 'Error: %s', 'woocommerce-gateway-stripe' ), $e->getMessage() ) );
796
797
			if ( $order->has_status( array( 'pending', 'failed' ) ) ) {
798
				$this->send_failed_order_email( $order_id );
799
			}
800
801
			do_action( 'wc_gateway_stripe_process_payment_error', $e, $order );
802
803
			return array(
804
				'result'   => 'fail',
805
				'redirect' => '',
806
			);
807
		}
808
	}
809
810
	/**
811
	 * Save source to order.
812
	 *
813
	 * @param WC_Order $order For to which the source applies.
814
	 * @param stdClass $source Source information.
815
	 */
816
	protected function save_source( $order, $source ) {
817
		$order_id = version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->id : $order->get_id();
818
819
		// Store source in the order.
820
		if ( $source->customer ) {
821
			update_post_meta( $order_id, '_stripe_customer_id', $source->customer );
822
		}
823
		if ( $source->source ) {
824
			update_post_meta( $order_id, '_stripe_card_id', $source->source );
825
		}
826
	}
827
828
	/**
829
	 * Store extra meta data for an order from a Stripe Response.
830
	 */
831
	public function process_response( $response, $order ) {
832
		$this->log( 'Processing response: ' . print_r( $response, true ) );
833
834
		$order_id = version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->id : $order->get_id();
835
836
		// Store charge data
837
		update_post_meta( $order_id, '_stripe_charge_id', $response->id );
838
		update_post_meta( $order_id, '_stripe_charge_captured', $response->captured ? 'yes' : 'no' );
839
840
		// Store other data such as fees
841 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...
842
			// Fees and Net needs to both come from Stripe to be accurate as the returned
843
			// values are in the local currency of the Stripe account, not from WC.
844
			$fee = ! empty( $response->balance_transaction->fee ) ? WC_Stripe::format_number( $response->balance_transaction, 'fee' ) : 0;
845
			$net = ! empty( $response->balance_transaction->net ) ? WC_Stripe::format_number( $response->balance_transaction, 'net' ) : 0;
846
			update_post_meta( $order_id, 'Stripe Fee', $fee );
847
			update_post_meta( $order_id, 'Net Revenue From Stripe', $net );
848
		}
849
850
		if ( $response->captured ) {
851
			$order->payment_complete( $response->id );
852
853
			$message = sprintf( __( 'Stripe charge complete (Charge ID: %s)', 'woocommerce-gateway-stripe' ), $response->id );
854
			$order->add_order_note( $message );
855
			$this->log( 'Success: ' . $message );
856
857
		} else {
858
			add_post_meta( $order_id, '_transaction_id', $response->id, true );
859
860
			if ( $order->has_status( array( 'pending', 'failed' ) ) ) {
861
				version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->reduce_order_stock() : wc_reduce_stock_levels( $order_id );
862
			}
863
864
			$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 ) );
865
			$this->log( "Successful auth: $response->id" );
866
		}
867
868
		return $response;
869
	}
870
871
	/**
872
	 * Add payment method via account screen.
873
	 * We don't store the token locally, but to the Stripe API.
874
	 * @since 3.0.0
875
	 */
876
	public function add_payment_method() {
877
		if ( empty( $_POST['stripe_token'] ) || ! is_user_logged_in() ) {
878
			wc_add_notice( __( 'There was a problem adding the card.', 'woocommerce-gateway-stripe' ), 'error' );
879
			return;
880
		}
881
882
		$stripe_customer = new WC_Stripe_Customer( get_current_user_id() );
883
		$card            = $stripe_customer->add_card( wc_clean( $_POST['stripe_token'] ) );
884
885
		if ( is_wp_error( $card ) ) {
886
			$localized_messages = $this->get_localized_messages();
887
			$error_msg = __( 'There was a problem adding the card.', 'woocommerce-gateway-stripe' );
888
889
			// loop through the errors to find matching localized message
890
			foreach ( $card->errors as $error => $msg ) {
891
				if ( isset( $localized_messages[ $error ] ) ) {
892
					$error_msg = $localized_messages[ $error ];
893
				}
894
			}
895
896
			wc_add_notice( $error_msg, 'error' );
897
			return;
898
		}
899
900
		return array(
901
			'result'   => 'success',
902
			'redirect' => wc_get_endpoint_url( 'payment-methods' ),
903
		);
904
	}
905
906
	/**
907
	 * Refund a charge
908
	 * @param  int $order_id
909
	 * @param  float $amount
910
	 * @return bool
911
	 */
912
	public function process_refund( $order_id, $amount = null, $reason = '' ) {
913
		$order = wc_get_order( $order_id );
914
915
		if ( ! $order || ! $order->get_transaction_id() ) {
916
			return false;
917
		}
918
919
		$body = array();
920
921
		if ( ! is_null( $amount ) ) {
922
			$body['amount']	= $this->get_stripe_amount( $amount );
923
		}
924
925
		if ( $reason ) {
926
			$body['metadata'] = array(
927
				'reason'	=> $reason,
928
			);
929
		}
930
931
		$this->log( "Info: Beginning refund for order $order_id for the amount of {$amount}" );
932
933
		$response = WC_Stripe_API::request( $body, 'charges/' . $order->get_transaction_id() . '/refunds' );
934
935
		if ( is_wp_error( $response ) ) {
936
			$this->log( 'Error: ' . $response->get_error_message() );
937
			return $response;
938
		} elseif ( ! empty( $response->id ) ) {
939
			$refund_message = sprintf( __( 'Refunded %1$s - Refund ID: %2$s - Reason: %3$s', 'woocommerce-gateway-stripe' ), wc_price( $response->amount / 100 ), $response->id, $reason );
940
			$order->add_order_note( $refund_message );
941
			$this->log( 'Success: ' . html_entity_decode( strip_tags( $refund_message ) ) );
942
			return true;
943
		}
944
	}
945
946
	/**
947
	 * Sends the failed order email to admin
948
	 *
949
	 * @version 3.1.0
950
	 * @since 3.1.0
951
	 * @param int $order_id
952
	 * @return null
953
	 */
954
	public function send_failed_order_email( $order_id ) {
955
		$emails = WC()->mailer()->get_emails();
956
		if ( ! empty( $emails ) && ! empty( $order_id ) ) {
957
			$emails['WC_Email_Failed_Order']->trigger( $order_id );
958
		}
959
	}
960
961
	/**
962
	 * Logs
963
	 *
964
	 * @since 3.1.0
965
	 * @version 3.1.0
966
	 *
967
	 * @param string $message
968
	 */
969
	public function log( $message ) {
970
		if ( $this->logging ) {
971
			WC_Stripe::log( $message );
972
		}
973
	}
974
}
975