Cancelled
Push — master ( 86817a...a519f9 )
by Roy
38s queued 38s
created

includes/class-wc-gateway-stripe.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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