Completed
Push — master ( 1968f4...2723f5 )
by Roy
02:26
created

WC_Gateway_Stripe::log()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 3
nc 2
nop 1
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 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
	 * Constructor
113
	 */
114
	public function __construct() {
115
		$this->id                   = 'stripe';
116
		$this->method_title         = __( 'Stripe', 'woocommerce-gateway-stripe' );
117
		$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' );
118
		$this->has_fields           = true;
119
		$this->view_transaction_url = 'https://dashboard.stripe.com/payments/%s';
120
		$this->supports             = array(
121
			'subscriptions',
122
			'products',
123
			'refunds',
124
			'subscription_cancellation',
125
			'subscription_reactivation',
126
			'subscription_suspension',
127
			'subscription_amount_changes',
128
			'subscription_payment_method_change', // Subs 1.n compatibility.
129
			'subscription_payment_method_change_customer',
130
			'subscription_payment_method_change_admin',
131
			'subscription_date_changes',
132
			'multiple_subscriptions',
133
			'pre-orders',
134
			'tokenization',
135
			'add_payment_method',
136
		);
137
138
		// Load the form fields.
139
		$this->init_form_fields();
140
141
		// Load the settings.
142
		$this->init_settings();
143
144
		// Get setting values.
145
		$this->title                  = $this->get_option( 'title' );
146
		$this->description            = $this->get_option( 'description' );
147
		$this->enabled                = $this->get_option( 'enabled' );
148
		$this->testmode               = 'yes' === $this->get_option( 'testmode' );
149
		$this->capture                = 'yes' === $this->get_option( 'capture', 'yes' );
150
		$this->statement_descriptor   = $this->get_option( 'statement_descriptor', wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ) );
151
		$this->stripe_checkout        = 'yes' === $this->get_option( 'stripe_checkout' );
152
		$this->stripe_checkout_locale = $this->get_option( 'stripe_checkout_locale' );
153
		$this->stripe_checkout_image  = $this->get_option( 'stripe_checkout_image', '' );
154
		$this->saved_cards            = 'yes' === $this->get_option( 'saved_cards' );
155
		$this->secret_key             = $this->testmode ? $this->get_option( 'test_secret_key' ) : $this->get_option( 'secret_key' );
156
		$this->publishable_key        = $this->testmode ? $this->get_option( 'test_publishable_key' ) : $this->get_option( 'publishable_key' );
157
		$this->bitcoin                = 'USD' === strtoupper( get_woocommerce_currency() ) && 'yes' === $this->get_option( 'stripe_bitcoin' );
158
		$this->apple_pay              = 'yes' === $this->get_option( 'apple_pay' );
159
		$this->apple_pay_button       = $this->get_option( 'apple_pay_button', 'black' );
160
		$this->logging                = 'yes' === $this->get_option( 'logging' );
161
		$this->allow_remember_me      = 'yes' === $this->get_option( 'allow_remember_me', 'no' );
162
163
		if ( $this->stripe_checkout ) {
164
			$this->order_button_text = __( 'Continue to payment', 'woocommerce-gateway-stripe' );
165
		}
166
167
		if ( $this->testmode ) {
168
			$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' );
169
			$this->description  = trim( $this->description );
170
		}
171
172
		WC_Stripe_API::set_secret_key( $this->secret_key );
173
174
		// Hooks.
175
		add_action( 'wp_enqueue_scripts', array( $this, 'payment_scripts' ) );
176
		add_action( 'admin_notices', array( $this, 'admin_notices' ) );
177
		add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );
178
	}
179
180
	/**
181
	 * Get_icon function.
182
	 *
183
	 * @access public
184
	 * @return string
185
	 */
186 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...
187
		$ext   = version_compare( WC()->version, '2.6', '>=' ) ? '.svg' : '.png';
188
		$style = version_compare( WC()->version, '2.6', '>=' ) ? 'style="margin-left: 0.3em"' : '';
189
190
		$icon  = '<img src="' . WC_HTTPS::force_https_url( WC()->plugin_url() . '/assets/images/icons/credit-cards/visa' . $ext ) . '" alt="Visa" width="32" ' . $style . ' />';
191
		$icon .= '<img src="' . WC_HTTPS::force_https_url( WC()->plugin_url() . '/assets/images/icons/credit-cards/mastercard' . $ext ) . '" alt="Mastercard" width="32" ' . $style . ' />';
192
		$icon .= '<img src="' . WC_HTTPS::force_https_url( WC()->plugin_url() . '/assets/images/icons/credit-cards/amex' . $ext ) . '" alt="Amex" width="32" ' . $style . ' />';
193
194
		if ( 'USD' === get_woocommerce_currency() ) {
195
			$icon .= '<img src="' . WC_HTTPS::force_https_url( WC()->plugin_url() . '/assets/images/icons/credit-cards/discover' . $ext ) . '" alt="Discover" width="32" ' . $style . ' />';
196
			$icon .= '<img src="' . WC_HTTPS::force_https_url( WC()->plugin_url() . '/assets/images/icons/credit-cards/jcb' . $ext ) . '" alt="JCB" width="32" ' . $style . ' />';
197
			$icon .= '<img src="' . WC_HTTPS::force_https_url( WC()->plugin_url() . '/assets/images/icons/credit-cards/diners' . $ext ) . '" alt="Diners" width="32" ' . $style . ' />';
198
		}
199
200
		if ( $this->bitcoin && $this->stripe_checkout ) {
201
			$icon .= '<img src="' . WC_HTTPS::force_https_url( plugins_url( '/assets/images/bitcoin' . $ext, WC_STRIPE_MAIN_FILE ) ) . '" alt="Bitcoin" width="24" ' . $style . ' />';
202
		}
203
204
		return apply_filters( 'woocommerce_gateway_icon', $icon, $this->id );
205
	}
206
207
	/**
208
	 * Get Stripe amount to pay
209
	 *
210
	 * @param float  $total Amount due.
211
	 * @param string $currency Accepted currency.
212
	 *
213
	 * @return float|int
214
	 */
215 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...
216
		if ( ! $currency ) {
217
			$currency = get_woocommerce_currency();
218
		}
219
		switch ( strtoupper( $currency ) ) {
220
			// Zero decimal currencies.
221
			case 'BIF' :
222
			case 'CLP' :
223
			case 'DJF' :
224
			case 'GNF' :
225
			case 'JPY' :
226
			case 'KMF' :
227
			case 'KRW' :
228
			case 'MGA' :
229
			case 'PYG' :
230
			case 'RWF' :
231
			case 'VND' :
232
			case 'VUV' :
233
			case 'XAF' :
234
			case 'XOF' :
235
			case 'XPF' :
236
				$total = absint( $total );
237
				break;
238
			default :
239
				$total = round( $total, 2 ) * 100; // In cents.
240
				break;
241
		}
242
		return $total;
243
	}
244
245
	/**
246
	 * Check if SSL is enabled and notify the user
247
	 */
248
	public function admin_notices() {
249
		if ( 'no' === $this->enabled ) {
250
			return;
251
		}
252
253
		// Show message if enabled and FORCE SSL is disabled and WordpressHTTPS plugin is not detected.
254 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...
255
			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>';
256
		}
257
	}
258
259
	/**
260
	 * Check if this gateway is enabled
261
	 */
262 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...
263
		if ( 'yes' === $this->enabled ) {
264
			if ( ! $this->testmode && is_checkout() && ! is_ssl() ) {
265
				return false;
266
			}
267
			if ( ! $this->secret_key || ! $this->publishable_key ) {
268
				return false;
269
			}
270
			return true;
271
		}
272
		return false;
273
	}
274
275
	/**
276
	 * Initialise Gateway Settings Form Fields
277
	 */
278
	public function init_form_fields() {
279
		$this->form_fields = include( 'settings-stripe.php' );
280
281
		wc_enqueue_js( "
282
			jQuery( function( $ ) {
283
				$( document.body ).on( 'change', '#woocommerce_stripe_testmode', function() {
284
					var test_secret_key = $( '#woocommerce_stripe_test_secret_key' ).parents( 'tr' ).eq( 0 ),
285
						test_publishable_key = $( '#woocommerce_stripe_test_publishable_key' ).parents( 'tr' ).eq( 0 ),
286
						live_secret_key = $( '#woocommerce_stripe_secret_key' ).parents( 'tr' ).eq( 0 ),
287
						live_publishable_key = $( '#woocommerce_stripe_publishable_key' ).parents( 'tr' ).eq( 0 );
288
289
					if ( $( this ).is( ':checked' ) ) {
290
						test_secret_key.show();
291
						test_publishable_key.show();
292
						live_secret_key.hide();
293
						live_publishable_key.hide();
294
					} else {
295
						test_secret_key.hide();
296
						test_publishable_key.hide();
297
						live_secret_key.show();
298
						live_publishable_key.show();
299
					}
300
				} );
301
302
				$( '#woocommerce_stripe_testmode' ).change();
303
304
				$( '#woocommerce_stripe_stripe_checkout' ).change( function() {
305
					if ( $( this ).is( ':checked' ) ) {
306
						$( '#woocommerce_stripe_stripe_checkout_locale, #woocommerce_stripe_stripe_bitcoin, #woocommerce_stripe_stripe_checkout_image, #woocommerce_stripe_allow_remember_me' ).closest( 'tr' ).show();
307
						$( '#woocommerce_stripe_request_payment_api' ).closest( 'tr' ).hide();
308
					} else {
309
						$( '#woocommerce_stripe_stripe_checkout_locale, #woocommerce_stripe_stripe_bitcoin, #woocommerce_stripe_stripe_checkout_image, #woocommerce_stripe_allow_remember_me' ).closest( 'tr' ).hide();
310
						$( '#woocommerce_stripe_request_payment_api' ).closest( 'tr' ).show();
311
					}
312
				}).change();
313
314
				$( '#woocommerce_stripe_apple_pay' ).change( function() {
315
					if ( $( this ).is( ':checked' ) ) {
316
						$( '#woocommerce_stripe_apple_pay_button' ).closest( 'tr' ).show();
317
					} else {
318
						$( '#woocommerce_stripe_apple_pay_button' ).closest( 'tr' ).hide();
319
					}
320
				}).change();
321
322
				$( '#woocommerce_stripe_secret_key, #woocommerce_stripe_publishable_key' ).change( function() {
323
					var value = $( this ).val();
324
325
					if ( value.indexOf( '_test_' ) >= 0 ) {
326
						$( this ).css( 'border-color', 'red' ).after( '<span class=\"description stripe-error-description\" style=\"color:red; display:block;\">" . __( 'This is not a valid live key. Live keys start with "sk_live_" and "pk_live_".', 'woocommerce-gateway-stripe' ) . "</span>' );
327
					} else {
328
						$( this ).css( 'border-color', '' );
329
						$( '.stripe-error-description', $( this ).parent() ).remove();
330
					}
331
				}).change();
332
333
				$( '#woocommerce_stripe_test_secret_key, #woocommerce_stripe_test_publishable_key' ).change( function() {
334
					var value = $( this ).val();
335
336
					if ( value.indexOf( '_live_' ) >= 0 ) {
337
						$( this ).css( 'border-color', 'red' ).after( '<span class=\"description stripe-error-description\" style=\"color:red; display:block;\">" . __( 'This is not a valid test key. Test keys start with "sk_test_" and "pk_test_".', 'woocommerce-gateway-stripe' ) . "</span>' );
338
					} else {
339
						$( this ).css( 'border-color', '' );
340
						$( '.stripe-error-description', $( this ).parent() ).remove();
341
					}
342
				}).change();
343
			});
344
		" );
345
	}
346
347
	/**
348
	 * Payment form on checkout page
349
	 */
350
	public function payment_fields() {
351
		$user                 = wp_get_current_user();
352
		$display_tokenization = $this->supports( 'tokenization' ) && is_checkout() && $this->saved_cards;
353
		$total                = WC()->cart->total;
354
355
		// If paying from order, we need to get total from order not cart.
356
		if ( isset( $_GET['pay_for_order'] ) && isset( $_GET['key'] ) ) {
357
			$order = wc_get_order( wc_get_order_id_by_order_key( wc_clean( $_GET['key'] ) ) );
358
			$total = $order->get_total();
359
		}
360
361 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...
362
			$user_email = get_user_meta( $user->ID, 'billing_email', true );
363
			$user_email = $user_email ? $user_email : $user->user_email;
364
		} else {
365
			$user_email = '';
366
		}
367
368
		if ( is_add_payment_method_page() ) {
369
			$pay_button_text = __( 'Add Card', 'woocommerce-gateway-stripe' );
370
		} else {
371
			$pay_button_text = '';
372
		}
373
374
		echo '<div
375
			id="stripe-payment-data"
376
			data-panel-label="' . esc_attr( $pay_button_text ) . '"
377
			data-description=""
378
			data-email="' . esc_attr( $user_email ) . '"
379
			data-amount="' . esc_attr( $this->get_stripe_amount( $total ) ) . '"
380
			data-name="' . esc_attr( $this->statement_descriptor ) . '"
381
			data-currency="' . esc_attr( strtolower( get_woocommerce_currency() ) ) . '"
382
			data-image="' . esc_attr( $this->stripe_checkout_image ) . '"
383
			data-bitcoin="' . esc_attr( $this->bitcoin ? 'true' : 'false' ) . '"
384
			data-locale="' . esc_attr( $this->stripe_checkout_locale ? $this->stripe_checkout_locale : 'en' ) . '"
385
			data-allow-remember-me="' . esc_attr( $this->allow_remember_me ? 'true' : 'false' ) . '">';
386
387
		if ( $this->description ) {
388
			echo apply_filters( 'wc_stripe_description', wpautop( wp_kses_post( $this->description ) ) );
389
		}
390
391
		if ( $display_tokenization ) {
392
			$this->tokenization_script();
393
			$this->saved_payment_methods();
394
		}
395
396
		if ( ! $this->stripe_checkout ) {
397
			$this->form();
398
399
			if ( $display_tokenization ) {
400
				$this->save_payment_method_checkbox();
401
			}
402
		}
403
404
		echo '</div>';
405
	}
406
407
	/**
408
	 * Localize Stripe messages based on code
409
	 *
410
	 * @since 3.0.6
411
	 * @version 3.0.6
412
	 * @return array
413
	 */
414
	public function get_localized_messages() {
415
		return apply_filters( 'wc_stripe_localized_messages', array(
416
			'invalid_number'        => __( 'The card number is not a valid credit card number.', 'woocommerce-gateway-stripe' ),
417
			'invalid_expiry_month'  => __( 'The card\'s expiration month is invalid.', 'woocommerce-gateway-stripe' ),
418
			'invalid_expiry_year'   => __( 'The card\'s expiration year is invalid.', 'woocommerce-gateway-stripe' ),
419
			'invalid_cvc'           => __( 'The card\'s security code is invalid.', 'woocommerce-gateway-stripe' ),
420
			'incorrect_number'      => __( 'The card number is incorrect.', 'woocommerce-gateway-stripe' ),
421
			'expired_card'          => __( 'The card has expired.', 'woocommerce-gateway-stripe' ),
422
			'incorrect_cvc'         => __( 'The card\'s security code is incorrect.', 'woocommerce-gateway-stripe' ),
423
			'incorrect_zip'         => __( 'The card\'s zip code failed validation.', 'woocommerce-gateway-stripe' ),
424
			'card_declined'         => __( 'The card was declined.', 'woocommerce-gateway-stripe' ),
425
			'missing'               => __( 'There is no card on a customer that is being charged.', 'woocommerce-gateway-stripe' ),
426
			'processing_error'      => __( 'An error occurred while processing the card.', 'woocommerce-gateway-stripe' ),
427
			'invalid_request_error' => __( 'Could not find payment information.', 'woocommerce-gateway-stripe' ),
428
		) );
429
	}
430
431
	/**
432
	 * payment_scripts function.
433
	 *
434
	 * Outputs scripts used for stripe payment
435
	 *
436
	 * @access public
437
	 */
438
	public function payment_scripts() {
439
		if ( ! is_cart() && ! is_checkout() && ! isset( $_GET['pay_for_order'] ) ) {
440
			return;
441
		}
442
443
		$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
444
445
		if ( $this->stripe_checkout ) {
446
			wp_enqueue_script( 'stripe_checkout', 'https://checkout.stripe.com/v2/checkout.js', '', '2.0', true );
447
			wp_enqueue_script( 'woocommerce_stripe', plugins_url( 'assets/js/stripe_checkout' . $suffix . '.js', WC_STRIPE_MAIN_FILE ), array( 'stripe' ), WC_STRIPE_VERSION, true );
448 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...
449
			wp_enqueue_script( 'stripe', 'https://js.stripe.com/v2/', '', '1.0', true );
450
			wp_enqueue_script( 'woocommerce_stripe', plugins_url( 'assets/js/stripe' . $suffix . '.js', WC_STRIPE_MAIN_FILE ), array( 'jquery-payment', 'stripe' ), WC_STRIPE_VERSION, true );
451
		}
452
453
		$stripe_params = array(
454
			'key'                  => $this->publishable_key,
455
			'i18n_terms'           => __( 'Please accept the terms and conditions first', 'woocommerce-gateway-stripe' ),
456
			'i18n_required_fields' => __( 'Please fill in required checkout fields first', 'woocommerce-gateway-stripe' ),
457
		);
458
459
		// If we're on the pay page we need to pass stripe.js the address of the order.
460
		if ( isset( $_GET['pay_for_order'] ) && 'true' === $_GET['pay_for_order'] ) {
461
			$order_id = wc_get_order_id_by_order_key( urldecode( $_GET['key'] ) );
462
			$order    = wc_get_order( $order_id );
463
464
			$stripe_params['billing_first_name'] = $order->billing_first_name;
465
			$stripe_params['billing_last_name']  = $order->billing_last_name;
466
			$stripe_params['billing_address_1']  = $order->billing_address_1;
467
			$stripe_params['billing_address_2']  = $order->billing_address_2;
468
			$stripe_params['billing_state']      = $order->billing_state;
469
			$stripe_params['billing_city']       = $order->billing_city;
470
			$stripe_params['billing_postcode']   = $order->billing_postcode;
471
			$stripe_params['billing_country']    = $order->billing_country;
472
		}
473
474
		$stripe_params['no_prepaid_card_msg']                     = __( 'Sorry, we\'re not accepting prepaid cards at this time.', 'woocommerce-gateway-stripe' );
475
		$stripe_params['allow_prepaid_card']                      = apply_filters( 'wc_stripe_allow_prepaid_card', true ) ? 'yes' : 'no';
476
		$stripe_params['stripe_checkout_require_billing_address'] = apply_filters( 'wc_stripe_checkout_require_billing_address', false ) ? 'yes' : 'no';
477
478
		// merge localized messages to be use in JS
479
		$stripe_params = array_merge( $stripe_params, $this->get_localized_messages() );
480
481
		wp_localize_script( 'woocommerce_stripe', 'wc_stripe_params', apply_filters( 'wc_stripe_params', $stripe_params ) );
482
	}
483
484
	/**
485
	 * Generate the request for the payment.
486
	 * @param  WC_Order $order
487
	 * @param  object $source
488
	 * @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...
489
	 */
490
	protected function generate_payment_request( $order, $source ) {
491
		$post_data                = array();
492
		$post_data['currency']    = strtolower( $order->get_order_currency() ? $order->get_order_currency() : get_woocommerce_currency() );
493
		$post_data['amount']      = $this->get_stripe_amount( $order->get_total(), $post_data['currency'] );
494
		$post_data['description'] = sprintf( __( '%1$s - Order %2$s', 'woocommerce-gateway-stripe' ), $this->statement_descriptor, $order->get_order_number() );
495
		$post_data['capture']     = $this->capture ? 'true' : 'false';
496
497
		if ( ! empty( $order->billing_email ) && apply_filters( 'wc_stripe_send_stripe_receipt', false ) ) {
498
			$post_data['receipt_email'] = $order->billing_email;
499
		}
500
501
		$post_data['expand[]']    = 'balance_transaction';
502
503
		if ( $source->customer ) {
504
			$post_data['customer'] = $source->customer;
505
		}
506
507
		if ( $source->source ) {
508
			$post_data['source'] = $source->source;
509
		}
510
511
		/**
512
		 * Filter the return value of the WC_Payment_Gateway_CC::generate_payment_request.
513
		 *
514
		 * @since 3.1.0
515
		 * @param array $post_data
516
		 * @param WC_Order $order
517
		 * @param object $source
518
		 */
519
		return apply_filters( 'wc_stripe_generate_payment_request', $post_data, $order, $source );
520
	}
521
522
	/**
523
	 * Get payment source. This can be a new token or existing card.
524
	 *
525
	 * @param string $user_id
526
	 * @param bool  $force_customer Should we force customer creation.
527
	 *
528
	 * @throws Exception When card was not added or for and invalid card.
529
	 * @return object
530
	 */
531
	protected function get_source( $user_id, $force_customer = false ) {
532
		$stripe_customer = new WC_Stripe_Customer( $user_id );
533
		$stripe_source   = false;
534
		$token_id        = false;
535
536
		// New CC info was entered and we have a new token to process
537
		if ( isset( $_POST['stripe_token'] ) ) {
538
			$stripe_token     = wc_clean( $_POST['stripe_token'] );
539
			$maybe_saved_card = isset( $_POST['wc-stripe-new-payment-method'] ) && ! empty( $_POST['wc-stripe-new-payment-method'] );
540
541
			// This is true if the user wants to store the card to their account.
542 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...
543
				$stripe_source = $stripe_customer->add_card( $stripe_token );
544
545
				if ( is_wp_error( $stripe_source ) ) {
546
					throw new Exception( $stripe_source->get_error_message() );
547
				}
548
			} else {
549
				// Not saving token, so don't define customer either.
550
				$stripe_source   = $stripe_token;
551
				$stripe_customer = false;
552
			}
553
		} elseif ( isset( $_POST['wc-stripe-payment-token'] ) && 'new' !== $_POST['wc-stripe-payment-token'] ) {
554
			// Use an existing token, and then process the payment
555
556
			$token_id = wc_clean( $_POST['wc-stripe-payment-token'] );
557
			$token    = WC_Payment_Tokens::get( $token_id );
558
559
			if ( ! $token || $token->get_user_id() !== get_current_user_id() ) {
560
				WC()->session->set( 'refresh_totals', true );
561
				throw new Exception( __( 'Invalid payment method. Please input a new card number.', 'woocommerce-gateway-stripe' ) );
562
			}
563
564
			$stripe_source = $token->get_token();
565
		}
566
567
		return (object) array(
568
			'token_id' => $token_id,
569
			'customer' => $stripe_customer ? $stripe_customer->get_id() : false,
570
			'source'   => $stripe_source,
571
		);
572
	}
573
574
	/**
575
	 * Get payment source from an order. This could be used in the future for
576
	 * a subscription as an example, therefore using the current user ID would
577
	 * not work - the customer won't be logged in :)
578
	 *
579
	 * Not using 2.6 tokens for this part since we need a customer AND a card
580
	 * token, and not just one.
581
	 *
582
	 * @param object $order
583
	 * @return object
584
	 */
585 View Code Duplication
	protected function get_order_source( $order = null ) {
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...
586
		$stripe_customer = new WC_Stripe_Customer();
587
		$stripe_source   = false;
588
		$token_id        = false;
589
590
		if ( $order ) {
591
			if ( $meta_value = get_post_meta( $order->id, '_stripe_customer_id', true ) ) {
592
				$stripe_customer->set_id( $meta_value );
593
			}
594
			if ( $meta_value = get_post_meta( $order->id, '_stripe_card_id', true ) ) {
595
				$stripe_source = $meta_value;
596
			}
597
		}
598
599
		return (object) array(
600
			'token_id' => $token_id,
601
			'customer' => $stripe_customer ? $stripe_customer->get_id() : false,
602
			'source'   => $stripe_source,
603
		);
604
	}
605
606
	/**
607
	 * Process the payment
608
	 *
609
	 * @param int  $order_id Reference.
610
	 * @param bool $retry Should we retry on fail.
611
	 * @param bool $force_customer Force user creation.
612
	 *
613
	 * @throws Exception If payment will not be accepted.
614
	 *
615
	 * @return array|void
616
	 */
617
	public function process_payment( $order_id, $retry = true, $force_customer = false ) {
618
		try {
619
			$order  = wc_get_order( $order_id );
620
			$source = $this->get_source( get_current_user_id(), $force_customer );
621
622 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...
623
				$error_msg = __( 'Please enter your card details to make a payment.', 'woocommerce-gateway-stripe' );
624
				$error_msg .= ' ' . __( 'Developers: Please make sure that you are including jQuery and there are no JavaScript errors on the page.', 'woocommerce-gateway-stripe' );
625
				throw new Exception( $error_msg );
626
			}
627
628
			// Store source to order meta.
629
			$this->save_source( $order, $source );
630
631
			// Handle payment.
632
			if ( $order->get_total() > 0 ) {
633
634 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...
635
					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 ) ) );
636
				}
637
638
				$this->log( "Info: Begin processing payment for order $order_id for the amount of {$order->get_total()}" );
639
640
				// Make the request.
641
				$response = WC_Stripe_API::request( $this->generate_payment_request( $order, $source ) );
642
643
				if ( is_wp_error( $response ) ) {
644
					// Customer param wrong? The user may have been deleted on stripe's end. Remove customer_id. Can be retried without.
645
					if ( 'customer' === $response->get_error_code() && $retry ) {
646
						delete_user_meta( get_current_user_id(), '_stripe_customer_id' );
647
						return $this->process_payment( $order_id, false, $force_customer );
648
						// Source param wrong? The CARD may have been deleted on stripe's end. Remove token and show message.
649
					} elseif ( 'source' === $response->get_error_code() && $source->token_id ) {
650
						$token = WC_Payment_Tokens::get( $source->token_id );
651
						$token->delete();
652
						throw new Exception( __( 'This card is no longer available and has been removed.', 'woocommerce-gateway-stripe' ) );
653
					}
654
					$localized_messages = $this->get_localized_messages();
655
656
					throw new Exception( ( isset( $localized_messages[ $response->get_error_code() ] ) ? $localized_messages[ $response->get_error_code() ] : $response->get_error_message() ) );
657
				}
658
659
				// Process valid response.
660
				$this->process_response( $response, $order );
661
			} else {
662
				$order->payment_complete();
663
			}
664
665
			// Remove cart.
666
			WC()->cart->empty_cart();
667
668
			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...
669
670
			// Return thank you page redirect.
671
			return array(
672
				'result'   => 'success',
673
				'redirect' => $this->get_return_url( $order ),
674
			);
675
676
		} catch ( Exception $e ) {
677
			wc_add_notice( $e->getMessage(), 'error' );
678
			$this->log( sprintf( __( 'Error: %s', 'woocommerce-gateway-stripe' ), $e->getMessage() ) );
679
680
			if ( $order->has_status( array( 'pending', 'failed' ) ) ) {
681
				$this->send_failed_order_email( $order_id );
682
			}
683
684
			do_action( 'wc_gateway_stripe_process_payment_error', $e, $order );
685
686
			return array(
687
				'result'   => 'fail',
688
				'redirect' => '',
689
			);
690
		}
691
	}
692
693
	/**
694
	 * Save source to order.
695
	 *
696
	 * @param WC_Order $order For to which the source applies.
697
	 * @param stdClass $source Source information.
698
	 */
699 View Code Duplication
	protected function save_source( $order, $source ) {
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...
700
		// Store source in the order.
701
		if ( $source->customer ) {
702
			update_post_meta( $order->id, '_stripe_customer_id', $source->customer );
703
		}
704
		if ( $source->source ) {
705
			update_post_meta( $order->id, '_stripe_card_id', $source->source );
706
		}
707
	}
708
709
	/**
710
	 * Store extra meta data for an order from a Stripe Response.
711
	 */
712
	public function process_response( $response, $order ) {
713
		$this->log( 'Processing response: ' . print_r( $response, true ) );
714
715
		// Store charge data
716
		update_post_meta( $order->id, '_stripe_charge_id', $response->id );
717
		update_post_meta( $order->id, '_stripe_charge_captured', $response->captured ? 'yes' : 'no' );
718
719
		// Store other data such as fees
720 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...
721
			// Fees and Net needs to both come from Stripe to be accurate as the returned
722
			// values are in the local currency of the Stripe account, not from WC.
723
			$fee = ! empty( $response->balance_transaction->fee ) ? number_format( $response->balance_transaction->fee / 100, 2, '.', '' ) : 0;
724
			$net = ! empty( $response->balance_transaction->net ) ? number_format( $response->balance_transaction->net / 100, 2, '.', '' ) : 0;
725
			update_post_meta( $order->id, 'Stripe Fee', $fee );
726
			update_post_meta( $order->id, 'Net Revenue From Stripe', $net );
727
		}
728
729
		if ( $response->captured ) {
730
			$order->payment_complete( $response->id );
731
732
			$message = sprintf( __( 'Stripe charge complete (Charge ID: %s)', 'woocommerce-gateway-stripe' ), $response->id );
733
			$order->add_order_note( $message );
734
			$this->log( 'Success: ' . $message );
735
736
		} else {
737
			add_post_meta( $order->id, '_transaction_id', $response->id, true );
738
739
			if ( $order->has_status( array( 'pending', 'failed' ) ) ) {
740
				$order->reduce_order_stock();
741
			}
742
743
			$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 ) );
744
			$this->log( "Successful auth: $response->id" );
745
		}
746
747
		return $response;
748
	}
749
750
	/**
751
	 * Add payment method via account screen.
752
	 * We don't store the token locally, but to the Stripe API.
753
	 * @since 3.0.0
754
	 */
755
	public function add_payment_method() {
756
		if ( empty( $_POST['stripe_token'] ) || ! is_user_logged_in() ) {
757
			wc_add_notice( __( 'There was a problem adding the card.', 'woocommerce-gateway-stripe' ), 'error' );
758
			return;
759
		}
760
761
		$stripe_customer = new WC_Stripe_Customer( get_current_user_id() );
762
		$card            = $stripe_customer->add_card( wc_clean( $_POST['stripe_token'] ) );
763
764
		if ( is_wp_error( $card ) ) {
765
			$localized_messages = $this->get_localized_messages();
766
			$error_msg = __( 'There was a problem adding the card.', 'woocommerce-gateway-stripe' );
767
768
			// loop through the errors to find matching localized message
769
			foreach ( $card->errors as $error => $msg ) {
770
				if ( isset( $localized_messages[ $error ] ) ) {
771
					$error_msg = $localized_messages[ $error ];
772
				}
773
			}
774
775
			wc_add_notice( $error_msg, 'error' );
776
			return;
777
		}
778
779
		return array(
780
			'result'   => 'success',
781
			'redirect' => wc_get_endpoint_url( 'payment-methods' ),
782
		);
783
	}
784
785
	/**
786
	 * Refund a charge
787
	 * @param  int $order_id
788
	 * @param  float $amount
789
	 * @return bool
790
	 */
791
	public function process_refund( $order_id, $amount = null, $reason = '' ) {
792
		$order = wc_get_order( $order_id );
793
794
		if ( ! $order || ! $order->get_transaction_id() ) {
795
			return false;
796
		}
797
798
		$body = array();
799
800
		if ( ! is_null( $amount ) ) {
801
			$body['amount']	= $this->get_stripe_amount( $amount );
802
		}
803
804
		if ( $reason ) {
805
			$body['metadata'] = array(
806
				'reason'	=> $reason,
807
			);
808
		}
809
810
		$this->log( "Info: Beginning refund for order $order_id for the amount of {$amount}" );
811
812
		$response = WC_Stripe_API::request( $body, 'charges/' . $order->get_transaction_id() . '/refunds' );
813
814
		if ( is_wp_error( $response ) ) {
815
			$this->log( 'Error: ' . $response->get_error_message() );
816
			return $response;
817
		} elseif ( ! empty( $response->id ) ) {
818
			$refund_message = sprintf( __( 'Refunded %1$s - Refund ID: %2$s - Reason: %3$s', 'woocommerce-gateway-stripe' ), wc_price( $response->amount / 100 ), $response->id, $reason );
819
			$order->add_order_note( $refund_message );
820
			$this->log( 'Success: ' . html_entity_decode( strip_tags( $refund_message ) ) );
821
			return true;
822
		}
823
	}
824
825
	/**
826
	 * Sends the failed order email to admin
827
	 *
828
	 * @version 3.1.0
829
	 * @since 3.1.0
830
	 * @param int $order_id
831
	 * @return null
832
	 */
833
	public function send_failed_order_email( $order_id ) {
834
		$emails = WC()->mailer()->get_emails();
835
		if ( ! empty( $emails ) && ! empty( $order_id ) ) {
836
			$emails['WC_Email_Failed_Order']->trigger( $order_id );
837
		}
838
	}
839
840
	/**
841
	 * Logs
842
	 *
843
	 * @since 3.1.0
844
	 * @version 3.1.0
845
	 *
846
	 * @param string $message
847
	 */
848
	public function log( $message ) {
849
		if ( $this->logging ) {
850
			WC_Stripe::log( $message );
851
		}
852
	}
853
}
854