Completed
Push — master ( 2c6ef6...281328 )
by Radoslav
02:07 queued 29s
created

WC_Gateway_Stripe::check_source()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6

Duplication

Lines 4
Ratio 66.67 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 4
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
if ( ! defined( 'ABSPATH' ) ) {
3
	exit;
4
}
5
6
/**
7
 * WC_Gateway_Stripe class.
8
 *
9
 * @extends WC_Payment_Gateway
10
 */
11
class WC_Gateway_Stripe extends WC_Stripe_Payment_Gateway {
12
	/**
13
	 * The delay between retries.
14
	 *
15
	 * @var int
16
	 */
17
	public $retry_interval;
18
19
	/**
20
	 * Should we capture Credit cards
21
	 *
22
	 * @var bool
23
	 */
24
	public $capture;
25
26
	/**
27
	 * Alternate credit card statement name
28
	 *
29
	 * @var bool
30
	 */
31
	public $statement_descriptor;
32
33
	/**
34
	 * Should we store the users credit cards?
35
	 *
36
	 * @var bool
37
	 */
38
	public $saved_cards;
39
40
	/**
41
	 * API access secret key
42
	 *
43
	 * @var string
44
	 */
45
	public $secret_key;
46
47
	/**
48
	 * Api access publishable key
49
	 *
50
	 * @var string
51
	 */
52
	public $publishable_key;
53
54
	/**
55
	 * Do we accept Payment Request?
56
	 *
57
	 * @var bool
58
	 */
59
	public $payment_request;
60
61
	/**
62
	 * Is test mode active?
63
	 *
64
	 * @var bool
65
	 */
66
	public $testmode;
67
68
	/**
69
	 * Inline CC form styling
70
	 *
71
	 * @var string
72
	 */
73
	public $inline_cc_form;
74
75
	/**
76
	 * Pre Orders Object
77
	 *
78
	 * @var object
79
	 */
80
	public $pre_orders;
81
82
	/**
83
	 * Constructor
84
	 */
85
	public function __construct() {
86
		$this->retry_interval = 1;
87
		$this->id             = 'stripe';
88
		$this->method_title   = __( 'Stripe', 'woocommerce-gateway-stripe' );
89
		/* translators: 1) link to Stripe register page 2) link to Stripe api keys page */
90
		$this->method_description = sprintf( __( 'Stripe works by adding payment fields on the checkout and then sending the details to Stripe for verification. <a href="%1$s" target="_blank">Sign up</a> for a Stripe account, and <a href="%2$s" target="_blank">get your Stripe account keys</a>.', 'woocommerce-gateway-stripe' ), 'https://dashboard.stripe.com/register', 'https://dashboard.stripe.com/account/apikeys' );
91
		$this->has_fields         = true;
92
		$this->supports           = array(
93
			'products',
94
			'refunds',
95
			'tokenization',
96
			'add_payment_method',
97
			'subscriptions',
98
			'subscription_cancellation',
99
			'subscription_suspension',
100
			'subscription_reactivation',
101
			'subscription_amount_changes',
102
			'subscription_date_changes',
103
			'subscription_payment_method_change',
104
			'subscription_payment_method_change_customer',
105
			'subscription_payment_method_change_admin',
106
			'multiple_subscriptions',
107
			'pre-orders',
108
		);
109
110
		// Load the form fields.
111
		$this->init_form_fields();
112
113
		// Load the settings.
114
		$this->init_settings();
115
116
		// Get setting values.
117
		$this->title                = $this->get_option( 'title' );
118
		$this->description          = $this->get_option( 'description' );
119
		$this->enabled              = $this->get_option( 'enabled' );
120
		$this->testmode             = 'yes' === $this->get_option( 'testmode' );
121
		$this->inline_cc_form       = 'yes' === $this->get_option( 'inline_cc_form' );
0 ignored issues
show
Documentation Bug introduced by
The property $inline_cc_form was declared of type string, but 'yes' === $this->get_option('inline_cc_form') is of type boolean. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
122
		$this->capture              = 'yes' === $this->get_option( 'capture', 'yes' );
123
		$this->statement_descriptor = WC_Stripe_Helper::clean_statement_descriptor( $this->get_option( 'statement_descriptor' ) );
0 ignored issues
show
Documentation Bug introduced by
The property $statement_descriptor was declared of type boolean, but \WC_Stripe_Helper::clean...statement_descriptor')) is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
124
		$this->saved_cards          = 'yes' === $this->get_option( 'saved_cards' );
125
		$this->secret_key           = $this->testmode ? $this->get_option( 'test_secret_key' ) : $this->get_option( 'secret_key' );
126
		$this->publishable_key      = $this->testmode ? $this->get_option( 'test_publishable_key' ) : $this->get_option( 'publishable_key' );
127
		$this->payment_request      = 'yes' === $this->get_option( 'payment_request', 'yes' );
128
129
		WC_Stripe_API::set_secret_key( $this->secret_key );
130
131
		// Hooks.
132
		add_action( 'wp_enqueue_scripts', array( $this, 'payment_scripts' ) );
133
		add_action( 'admin_enqueue_scripts', array( $this, 'admin_scripts' ) );
134
		add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );
135
		add_action( 'woocommerce_admin_order_totals_after_total', array( $this, 'display_order_fee' ) );
136
		add_action( 'woocommerce_admin_order_totals_after_total', array( $this, 'display_order_payout' ), 20 );
137
		add_action( 'woocommerce_customer_save_address', array( $this, 'show_update_card_notice' ), 10, 2 );
138
		add_filter( 'woocommerce_available_payment_gateways', array( $this, 'prepare_order_pay_page' ) );
139
		add_action( 'woocommerce_account_view-order_endpoint', array( $this, 'check_intent_status_on_order_page' ), 1 );
140
		add_filter( 'woocommerce_payment_successful_result', array( $this, 'modify_successful_payment_result' ), 99999, 2 );
141
		add_action( 'set_logged_in_cookie', array( $this, 'set_cookie_on_current_request' ) );
142
		add_filter( 'woocommerce_get_checkout_payment_url', array( $this, 'get_checkout_payment_url' ), 10, 2 );
143
144 View Code Duplication
		if ( WC_Stripe_Helper::is_pre_orders_exists() ) {
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...
145
			$this->pre_orders = new WC_Stripe_Pre_Orders_Compat();
146
147
			add_action( 'wc_pre_orders_process_pre_order_completion_payment_' . $this->id, array( $this->pre_orders, 'process_pre_order_release_payment' ) );
148
		}
149
	}
150
151
	/**
152
	 * Checks if keys are set.
153
	 *
154
	 * @since 4.0.6
155
	 * @return bool
156
	 */
157
	public function are_keys_set() {
158
		if ( empty( $this->secret_key ) || empty( $this->publishable_key ) ) {
159
			return false;
160
		}
161
162
		return true;
163
	}
164
165
	/**
166
	 * Checks if gateway should be available to use.
167
	 *
168
	 * @since 4.0.2
169
	 */
170
	public function is_available() {
171
		if ( is_add_payment_method_page() && ! $this->saved_cards ) {
172
			return false;
173
		}
174
175
		return parent::is_available();
176
	}
177
178
	/**
179
	 * Adds a notice for customer when they update their billing address.
180
	 *
181
	 * @since 4.1.0
182
	 * @param int    $user_id      The ID of the current user.
183
	 * @param string $load_address The address to load.
184
	 */
185
	public function show_update_card_notice( $user_id, $load_address ) {
186
		if ( ! $this->saved_cards || ! WC_Stripe_Payment_Tokens::customer_has_saved_methods( $user_id ) || 'billing' !== $load_address ) {
187
			return;
188
		}
189
190
		/* translators: 1) Opening anchor tag 2) closing anchor tag */
191
		wc_add_notice( sprintf( __( 'If your billing address has been changed for saved payment methods, be sure to remove any %1$ssaved payment methods%2$s on file and re-add them.', 'woocommerce-gateway-stripe' ), '<a href="' . esc_url( wc_get_endpoint_url( 'payment-methods' ) ) . '" class="wc-stripe-update-card-notice" style="text-decoration:underline;">', '</a>' ), 'notice' );
192
	}
193
194
	/**
195
	 * Get_icon function.
196
	 *
197
	 * @since 1.0.0
198
	 * @version 4.0.0
199
	 * @return string
200
	 */
201
	public function get_icon() {
202
		$icons = $this->payment_icons();
203
204
		$icons_str = '';
205
206
		$icons_str .= isset( $icons['visa'] ) ? $icons['visa'] : '';
207
		$icons_str .= isset( $icons['amex'] ) ? $icons['amex'] : '';
208
		$icons_str .= isset( $icons['mastercard'] ) ? $icons['mastercard'] : '';
209
210
		if ( 'USD' === get_woocommerce_currency() ) {
211
			$icons_str .= isset( $icons['discover'] ) ? $icons['discover'] : '';
212
			$icons_str .= isset( $icons['jcb'] ) ? $icons['jcb'] : '';
213
			$icons_str .= isset( $icons['diners'] ) ? $icons['diners'] : '';
214
		}
215
216
		return apply_filters( 'woocommerce_gateway_icon', $icons_str, $this->id );
217
	}
218
219
	/**
220
	 * Initialise Gateway Settings Form Fields
221
	 */
222
	public function init_form_fields() {
223
		$this->form_fields = require( dirname( __FILE__ ) . '/admin/stripe-settings.php' );
224
	}
225
226
	/**
227
	 * Payment form on checkout page
228
	 */
229
	public function payment_fields() {
230
		$user                 = wp_get_current_user();
231
		$display_tokenization = $this->supports( 'tokenization' ) && is_checkout() && $this->saved_cards;
232
		$total                = WC()->cart->total;
0 ignored issues
show
Unused Code introduced by
$total is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
233
		$user_email           = '';
234
		$description          = $this->get_description();
235
		$description          = ! empty( $description ) ? $description : '';
236
		$firstname            = '';
237
		$lastname             = '';
238
239
		// If paying from order, we need to get total from order not cart.
240
		if ( isset( $_GET['pay_for_order'] ) && ! empty( $_GET['key'] ) ) { // wpcs: csrf ok.
241
			$order      = wc_get_order( wc_get_order_id_by_order_key( wc_clean( $_GET['key'] ) ) ); // wpcs: csrf ok, sanitization ok.
242
			$total      = $order->get_total();
0 ignored issues
show
Unused Code introduced by
$total is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
243
			$user_email = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $order->billing_email : $order->get_billing_email();
244
		} else {
245
			if ( $user->ID ) {
246
				$user_email = get_user_meta( $user->ID, 'billing_email', true );
247
				$user_email = $user_email ? $user_email : $user->user_email;
248
			}
249
		}
250
251
		if ( is_add_payment_method_page() ) {
252
			$firstname       = $user->user_firstname;
253
			$lastname        = $user->user_lastname;
254
		}
255
256
		ob_start();
257
258
		echo '<div
259
			id="stripe-payment-data"
260
			data-email="' . esc_attr( $user_email ) . '"
261
			data-full-name="' . esc_attr( $firstname . ' ' . $lastname ) . '"
262
			data-currency="' . esc_attr( strtolower( get_woocommerce_currency() ) ) . '"
263
		>';
264
265
		if ( $this->testmode ) {
266
			/* translators: link to Stripe testing page */
267
			$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 <a href="%s" target="_blank">Testing Stripe documentation</a> for more card numbers.', 'woocommerce-gateway-stripe' ), 'https://stripe.com/docs/testing' );
268
		}
269
270
		$description = trim( $description );
271
272
		echo apply_filters( 'wc_stripe_description', wpautop( wp_kses_post( $description ) ), $this->id ); // wpcs: xss ok.
273
274
		if ( $display_tokenization ) {
275
			$this->tokenization_script();
276
			$this->saved_payment_methods();
277
		}
278
279
		$this->elements_form();
280
281 View Code Duplication
		if ( apply_filters( 'wc_stripe_display_save_payment_method_checkbox', $display_tokenization ) && ! is_add_payment_method_page() && ! isset( $_GET['change_payment_method'] ) ) { // wpcs: csrf ok.
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...
282
283
			$this->save_payment_method_checkbox();
284
		}
285
286
		do_action( 'wc_stripe_cards_payment_fields', $this->id );
287
288
		echo '</div>';
289
290
		ob_end_flush();
291
	}
292
293
	/**
294
	 * Renders the Stripe elements form.
295
	 *
296
	 * @since 4.0.0
297
	 * @version 4.0.0
298
	 */
299
	public function elements_form() {
300
		?>
301
		<fieldset id="wc-<?php echo esc_attr( $this->id ); ?>-cc-form" class="wc-credit-card-form wc-payment-form" style="background:transparent;">
302
			<?php do_action( 'woocommerce_credit_card_form_start', $this->id ); ?>
303
304
			<?php if ( $this->inline_cc_form ) { ?>
305
				<label for="card-element">
306
					<?php esc_html_e( 'Credit or debit card', 'woocommerce-gateway-stripe' ); ?>
307
				</label>
308
309
				<div id="stripe-card-element" class="wc-stripe-elements-field">
310
				<!-- a Stripe Element will be inserted here. -->
311
				</div>
312
			<?php } else { ?>
313
				<div class="form-row form-row-wide">
314
					<label for="stripe-card-element"><?php esc_html_e( 'Card Number', 'woocommerce-gateway-stripe' ); ?> <span class="required">*</span></label>
315
					<div class="stripe-card-group">
316
						<div id="stripe-card-element" class="wc-stripe-elements-field">
317
						<!-- a Stripe Element will be inserted here. -->
318
						</div>
319
320
						<i class="stripe-credit-card-brand stripe-card-brand" alt="Credit Card"></i>
321
					</div>
322
				</div>
323
324
				<div class="form-row form-row-first">
325
					<label for="stripe-exp-element"><?php esc_html_e( 'Expiry Date', 'woocommerce-gateway-stripe' ); ?> <span class="required">*</span></label>
326
327
					<div id="stripe-exp-element" class="wc-stripe-elements-field">
328
					<!-- a Stripe Element will be inserted here. -->
329
					</div>
330
				</div>
331
332
				<div class="form-row form-row-last">
333
					<label for="stripe-cvc-element"><?php esc_html_e( 'Card Code (CVC)', 'woocommerce-gateway-stripe' ); ?> <span class="required">*</span></label>
334
				<div id="stripe-cvc-element" class="wc-stripe-elements-field">
335
				<!-- a Stripe Element will be inserted here. -->
336
				</div>
337
				</div>
338
				<div class="clear"></div>
339
			<?php } ?>
340
341
			<!-- Used to display form errors -->
342
			<div class="stripe-source-errors" role="alert"></div>
343
			<br />
344
			<?php do_action( 'woocommerce_credit_card_form_end', $this->id ); ?>
345
			<div class="clear"></div>
346
		</fieldset>
347
		<?php
348
	}
349
350
	/**
351
	 * Load admin scripts.
352
	 *
353
	 * @since 3.1.0
354
	 * @version 3.1.0
355
	 */
356
	public function admin_scripts() {
357
		if ( 'woocommerce_page_wc-settings' !== get_current_screen()->id ) {
358
			return;
359
		}
360
361
		$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
362
363
		wp_enqueue_script( 'woocommerce_stripe_admin', plugins_url( 'assets/js/stripe-admin' . $suffix . '.js', WC_STRIPE_MAIN_FILE ), array(), WC_STRIPE_VERSION, true );
364
	}
365
366
	/**
367
	 * Payment_scripts function.
368
	 *
369
	 * Outputs scripts used for stripe payment
370
	 *
371
	 * @since 3.1.0
372
	 * @version 4.0.0
373
	 */
374
	public function payment_scripts() {
375
		if ( ! is_product() && ! is_cart() && ! is_checkout() && ! isset( $_GET['pay_for_order'] ) && ! is_add_payment_method_page() && ! isset( $_GET['change_payment_method'] ) || ( is_order_received_page() ) ) { // wpcs: csrf ok.
376
			return;
377
		}
378
379
		// If Stripe is not enabled bail.
380
		if ( 'no' === $this->enabled ) {
381
			return;
382
		}
383
384
		// If keys are not set bail.
385
		if ( ! $this->are_keys_set() ) {
386
			WC_Stripe_Logger::log( 'Keys are not set correctly.' );
387
			return;
388
		}
389
390
		// If no SSL bail.
391
		if ( ! $this->testmode && ! is_ssl() ) {
392
			WC_Stripe_Logger::log( 'Stripe live mode requires SSL.' );
393
			return;
394
		}
395
396
		$current_theme = wp_get_theme();
0 ignored issues
show
Unused Code introduced by
$current_theme is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
397
398
		$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
399
400
		wp_register_style( 'stripe_styles', plugins_url( 'assets/css/stripe-styles.css', WC_STRIPE_MAIN_FILE ), array(), WC_STRIPE_VERSION );
401
		wp_enqueue_style( 'stripe_styles' );
402
403
		wp_register_script( 'stripe', 'https://js.stripe.com/v3/', '', '3.0', true );
404
		wp_register_script( 'woocommerce_stripe', plugins_url( 'assets/js/stripe' . $suffix . '.js', WC_STRIPE_MAIN_FILE ), array( 'jquery-payment', 'stripe' ), WC_STRIPE_VERSION, true );
405
406
		$stripe_params = array(
407
			'key'                  => $this->publishable_key,
408
			'i18n_terms'           => __( 'Please accept the terms and conditions first', 'woocommerce-gateway-stripe' ),
409
			'i18n_required_fields' => __( 'Please fill in required checkout fields first', 'woocommerce-gateway-stripe' ),
410
		);
411
412
		// If we're on the pay page we need to pass stripe.js the address of the order.
413
		if ( isset( $_GET['pay_for_order'] ) && 'true' === $_GET['pay_for_order'] ) { // wpcs: csrf ok.
414
			$order_id = wc_get_order_id_by_order_key( urldecode( $_GET['key'] ) ); // wpcs: csrf ok, sanitization ok, xss ok.
415
			$order    = wc_get_order( $order_id );
416
417
			if ( is_a( $order, 'WC_Order' ) ) {
418
				$stripe_params['billing_first_name'] = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $order->billing_first_name : $order->get_billing_first_name();
419
				$stripe_params['billing_last_name']  = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $order->billing_last_name : $order->get_billing_last_name();
420
				$stripe_params['billing_address_1']  = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $order->billing_address_1 : $order->get_billing_address_1();
421
				$stripe_params['billing_address_2']  = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $order->billing_address_2 : $order->get_billing_address_2();
422
				$stripe_params['billing_state']      = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $order->billing_state : $order->get_billing_state();
423
				$stripe_params['billing_city']       = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $order->billing_city : $order->get_billing_city();
424
				$stripe_params['billing_postcode']   = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $order->billing_postcode : $order->get_billing_postcode();
425
				$stripe_params['billing_country']    = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $order->billing_country : $order->get_billing_country();
426
			}
427
		}
428
429
		$sepa_elements_options = apply_filters(
430
			'wc_stripe_sepa_elements_options',
431
			array(
432
				'supportedCountries' => array( 'SEPA' ),
433
				'placeholderCountry' => WC()->countries->get_base_country(),
434
				'style'              => array( 'base' => array( 'fontSize' => '15px' ) ),
435
			)
436
		);
437
438
		$stripe_params['no_prepaid_card_msg']       = __( 'Sorry, we\'re not accepting prepaid cards at this time. Your credit card has not been charged. Please try with alternative payment method.', 'woocommerce-gateway-stripe' );
439
		$stripe_params['no_sepa_owner_msg']         = __( 'Please enter your IBAN account name.', 'woocommerce-gateway-stripe' );
440
		$stripe_params['no_sepa_iban_msg']          = __( 'Please enter your IBAN account number.', 'woocommerce-gateway-stripe' );
441
		$stripe_params['payment_intent_error']      = __( 'We couldn\'t initiate the payment. Please try again.', 'woocommerce-gateway-stripe' );
442
		$stripe_params['sepa_mandate_notification'] = apply_filters( 'wc_stripe_sepa_mandate_notification', 'email' );
443
		$stripe_params['allow_prepaid_card']        = apply_filters( 'wc_stripe_allow_prepaid_card', true ) ? 'yes' : 'no';
444
		$stripe_params['inline_cc_form']            = $this->inline_cc_form ? 'yes' : 'no';
445
		$stripe_params['is_checkout']               = ( is_checkout() && empty( $_GET['pay_for_order'] ) ) ? 'yes' : 'no'; // wpcs: csrf ok.
446
		$stripe_params['return_url']                = $this->get_stripe_return_url();
447
		$stripe_params['ajaxurl']                   = WC_AJAX::get_endpoint( '%%endpoint%%' );
448
		$stripe_params['stripe_nonce']              = wp_create_nonce( '_wc_stripe_nonce' );
449
		$stripe_params['statement_descriptor']      = $this->statement_descriptor;
450
		$stripe_params['elements_options']          = apply_filters( 'wc_stripe_elements_options', array() );
451
		$stripe_params['sepa_elements_options']     = $sepa_elements_options;
452
		$stripe_params['invalid_owner_name']        = __( 'Billing First Name and Last Name are required.', 'woocommerce-gateway-stripe' );
453
		$stripe_params['is_change_payment_page']    = isset( $_GET['change_payment_method'] ) ? 'yes' : 'no'; // wpcs: csrf ok.
454
		$stripe_params['is_add_payment_page']       = is_wc_endpoint_url( 'add-payment-method' ) ? 'yes' : 'no';
455
		$stripe_params['is_pay_for_order_page']     = is_wc_endpoint_url( 'order-pay' ) ? 'yes' : 'no';
456
		$stripe_params['elements_styling']          = apply_filters( 'wc_stripe_elements_styling', false );
457
		$stripe_params['elements_classes']          = apply_filters( 'wc_stripe_elements_classes', false );
458
459
		// Merge localized messages to be use in JS.
460
		$stripe_params = array_merge( $stripe_params, WC_Stripe_Helper::get_localized_messages() );
461
462
		wp_localize_script( 'woocommerce_stripe', 'wc_stripe_params', apply_filters( 'wc_stripe_params', $stripe_params ) );
463
464
		$this->tokenization_script();
465
		wp_enqueue_script( 'woocommerce_stripe' );
466
	}
467
468
	/**
469
	 * Checks if a source object represents a prepaid credit card and
470
	 * throws an exception if it is one, but that is not allowed.
471
	 *
472
	 * @since 4.2.0
473
	 * @param object $prepared_source The object with source details.
474
	 * @throws WC_Stripe_Exception An exception if the card is prepaid, but prepaid cards are not allowed.
475
	 */
476
	public function maybe_disallow_prepaid_card( $prepared_source ) {
477
		// Check if we don't allow prepaid credit cards.
478
		if ( apply_filters( 'wc_stripe_allow_prepaid_card', true ) || ! $this->is_prepaid_card( $prepared_source->source_object ) ) {
479
			return;
480
		}
481
482
		$localized_message = __( 'Sorry, we\'re not accepting prepaid cards at this time. Your credit card has not been charged. Please try with alternative payment method.', 'woocommerce-gateway-stripe' );
483
		throw new WC_Stripe_Exception( print_r( $prepared_source->source_object, true ), $localized_message );
484
	}
485
486
	/**
487
	 * Checks whether a source exists.
488
	 *
489
	 * @since 4.2.0
490
	 * @param  object $prepared_source The source that should be verified.
491
	 * @throws WC_Stripe_Exception     An exception if the source ID is missing.
492
	 */
493
	public function check_source( $prepared_source ) {
494 View Code Duplication
		if ( empty( $prepared_source->source ) ) {
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...
495
			$localized_message = __( 'Payment processing failed. Please retry.', 'woocommerce-gateway-stripe' );
496
			throw new WC_Stripe_Exception( print_r( $prepared_source, true ), $localized_message );
497
		}
498
	}
499
500
	/**
501
	 * Customer param wrong? The user may have been deleted on stripe's end. Remove customer_id. Can be retried without.
502
	 *
503
	 * @since 4.2.0
504
	 * @param object   $error The error that was returned from Stripe's API.
505
	 * @param WC_Order $order The order those payment is being processed.
506
	 * @return bool           A flag that indicates that the customer does not exist and should be removed.
507
	 */
508
	public function maybe_remove_non_existent_customer( $error, $order ) {
509
		if ( ! $this->is_no_such_customer_error( $error ) ) {
510
			return false;
511
		}
512
513
		if ( WC_Stripe_Helper::is_wc_lt( '3.0' ) ) {
514
			delete_user_option( $order->customer_user, '_stripe_customer_id' );
515
			delete_post_meta( $order->get_id(), '_stripe_customer_id' );
516
		} else {
517
			delete_user_option( $order->get_customer_id(), '_stripe_customer_id' );
518
			$order->delete_meta_data( '_stripe_customer_id' );
519
			$order->save();
520
		}
521
522
		return true;
523
	}
524
525
	/**
526
	 * Completes an order without a positive value.
527
	 *
528
	 * @since 4.2.0
529
	 * @param WC_Order $order             The order to complete.
530
	 * @param WC_Order $prepared_source   Payment source and customer data.
531
	 * @param boolean  $force_save_source Whether the payment source must be saved, like when dealing with a Subscription setup.
532
	 * @return array                      Redirection data for `process_payment`.
533
	 */
534
	public function complete_free_order( $order, $prepared_source, $force_save_source ) {
535
		if ( $force_save_source ) {
536
			$intent_secret = $this->setup_intent( $order, $prepared_source );
537
538
			if ( ! empty( $intent_secret ) ) {
539
				// `get_return_url()` must be called immediately before returning a value.
540
				return array(
541
					'result'              => 'success',
542
					'redirect'            => $this->get_return_url( $order ),
543
					'setup_intent_secret' => $intent_secret,
544
				);
545
			}
546
		}
547
548
		// Remove cart.
549
		WC()->cart->empty_cart();
550
551
		$order->payment_complete();
552
553
		// Return thank you page redirect.
554
		return array(
555
			'result'   => 'success',
556
			'redirect' => $this->get_return_url( $order ),
557
		);
558
	}
559
560
	/**
561
	 * Process the payment
562
	 *
563
	 * @since 1.0.0
564
	 * @since 4.1.0 Add 4th parameter to track previous error.
565
	 * @param int  $order_id Reference.
566
	 * @param bool $retry Should we retry on fail.
567
	 * @param bool $force_save_source Force save the payment source.
568
	 * @param mix  $previous_error Any error message from previous request.
569
	 *
570
	 * @throws Exception If payment will not be accepted.
571
	 * @return array|void
572
	 */
573
	public function process_payment( $order_id, $retry = true, $force_save_source = false, $previous_error = false ) {
574
		try {
575
			$order = wc_get_order( $order_id );
576
577
			// ToDo: `process_pre_order` saves the source to the order for a later payment.
578
			// This might not work well with PaymentIntents.
579
			if ( $this->maybe_process_pre_orders( $order_id ) ) {
580
				return $this->pre_orders->process_pre_order( $order_id );
581
			}
582
583
			// Check whether there is an existing intent.
584
			$intent = $this->get_intent_from_order( $order );
585
			if ( isset( $intent->object ) && 'setup_intent' === $intent->object ) {
586
				$intent = false; // This function can only deal with *payment* intents
587
			}
588
589
			$stripe_customer_id = null;
590
			if ( $intent && ! empty( $intent->customer ) ) {
591
				$stripe_customer_id = $intent->customer;
592
			}
593
594
			$prepared_source = $this->prepare_source( get_current_user_id(), $force_save_source, $stripe_customer_id );
595
596
			$this->maybe_disallow_prepaid_card( $prepared_source );
597
			$this->check_source( $prepared_source );
598
			$this->save_source_to_order( $order, $prepared_source );
599
600
			if ( 0 >= $order->get_total() ) {
601
				return $this->complete_free_order( $order, $prepared_source, $force_save_source );
602
			}
603
604
			// This will throw exception if not valid.
605
			$this->validate_minimum_order_amount( $order );
606
607
			WC_Stripe_Logger::log( "Info: Begin processing payment for order $order_id for the amount of {$order->get_total()}" );
608
609
			if ( $intent ) {
610
				$intent = $this->update_existing_intent( $intent, $order, $prepared_source );
611
			} else {
612
				$intent = $this->create_intent( $order, $prepared_source );
613
			}
614
615
			// Confirm the intent after locking the order to make sure webhooks will not interfere.
616
			if ( empty( $intent->error ) ) {
617
				$this->lock_order_payment( $order, $intent );
618
				$intent = $this->confirm_intent( $intent, $order, $prepared_source );
619
			}
620
621
			if ( ! empty( $intent->error ) ) {
622
				$this->maybe_remove_non_existent_customer( $intent->error, $order );
623
624
				// We want to retry.
625
				if ( $this->is_retryable_error( $intent->error ) ) {
626
					return $this->retry_after_error( $intent, $order, $retry, $force_save_source, $previous_error );
627
				}
628
629
				$this->unlock_order_payment( $order );
630
				$this->throw_localized_message( $intent, $order );
631
			}
632
633
			if ( ! empty( $intent ) ) {
634
				// Use the last charge within the intent to proceed.
635
				$response = end( $intent->charges->data );
636
637
				// If the intent requires a 3DS flow, redirect to it.
638
				if ( 'requires_action' === $intent->status ) {
639
					$this->unlock_order_payment( $order );
640
641
					if ( is_wc_endpoint_url( 'order-pay' ) ) {
642
						$redirect_url = add_query_arg( 'wc-stripe-confirmation', 1, $order->get_checkout_payment_url( false ) );
643
644
						return array(
645
							'result'   => 'success',
646
							'redirect' => $redirect_url,
647
						);
648
					} else {
649
						/**
650
						 * This URL contains only a hash, which will be sent to `checkout.js` where it will be set like this:
651
						 * `window.location = result.redirect`
652
						 * Once this redirect is sent to JS, the `onHashChange` function will execute `handleCardPayment`.
653
						 */
654
655
						return array(
656
							'result'                => 'success',
657
							'redirect'              => $this->get_return_url( $order ),
658
							'payment_intent_secret' => $intent->client_secret,
659
						);
660
					}
661
				}
662
			}
663
664
			// Process valid response.
665
			$this->process_response( $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...
666
667
			// Remove cart.
668
			if ( isset( WC()->cart ) ) {
669
				WC()->cart->empty_cart();
670
			}
671
672
			// Unlock the order.
673
			$this->unlock_order_payment( $order );
674
675
			// Return thank you page redirect.
676
			return array(
677
				'result'   => 'success',
678
				'redirect' => $this->get_return_url( $order ),
679
			);
680
681
		} catch ( WC_Stripe_Exception $e ) {
682
			wc_add_notice( $e->getLocalizedMessage(), 'error' );
683
			WC_Stripe_Logger::log( 'Error: ' . $e->getMessage() );
684
685
			do_action( 'wc_gateway_stripe_process_payment_error', $e, $order );
686
687
			/* translators: error message */
688
			$order->update_status( 'failed' );
689
690
			return array(
691
				'result'   => 'fail',
692
				'redirect' => '',
693
			);
694
		}
695
	}
696
697
	/**
698
	 * Displays the Stripe fee
699
	 *
700
	 * @since 4.1.0
701
	 *
702
	 * @param int $order_id The ID of the order.
703
	 */
704 View Code Duplication
	public function display_order_fee( $order_id ) {
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...
705
		if ( apply_filters( 'wc_stripe_hide_display_order_fee', false, $order_id ) ) {
706
			return;
707
		}
708
709
		$order = wc_get_order( $order_id );
710
711
		$fee      = WC_Stripe_Helper::get_stripe_fee( $order );
712
		$currency = WC_Stripe_Helper::get_stripe_currency( $order );
713
714
		if ( ! $fee || ! $currency ) {
715
			return;
716
		}
717
718
		?>
719
720
		<tr>
721
			<td class="label stripe-fee">
722
				<?php echo wc_help_tip( __( 'This represents the fee Stripe collects for the transaction.', 'woocommerce-gateway-stripe' ) ); // wpcs: xss ok. ?>
723
				<?php esc_html_e( 'Stripe Fee:', 'woocommerce-gateway-stripe' ); ?>
724
			</td>
725
			<td width="1%"></td>
726
			<td class="total">
727
				-&nbsp;<?php echo wc_price( $fee, array( 'currency' => $currency ) ); // wpcs: xss ok. ?>
728
			</td>
729
		</tr>
730
731
		<?php
732
	}
733
734
	/**
735
	 * Displays the net total of the transaction without the charges of Stripe.
736
	 *
737
	 * @since 4.1.0
738
	 *
739
	 * @param int $order_id The ID of the order.
740
	 */
741 View Code Duplication
	public function display_order_payout( $order_id ) {
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...
742
		if ( apply_filters( 'wc_stripe_hide_display_order_payout', false, $order_id ) ) {
743
			return;
744
		}
745
746
		$order = wc_get_order( $order_id );
747
748
		$net      = WC_Stripe_Helper::get_stripe_net( $order );
749
		$currency = WC_Stripe_Helper::get_stripe_currency( $order );
750
751
		if ( ! $net || ! $currency ) {
752
			return;
753
		}
754
755
		?>
756
757
		<tr>
758
			<td class="label stripe-payout">
759
				<?php echo wc_help_tip( __( 'This represents the net total that will be credited to your Stripe bank account. This may be in the currency that is set in your Stripe account.', 'woocommerce-gateway-stripe' ) ); // wpcs: xss ok. ?>
760
				<?php esc_html_e( 'Stripe Payout:', 'woocommerce-gateway-stripe' ); ?>
761
			</td>
762
			<td width="1%"></td>
763
			<td class="total">
764
				<?php echo wc_price( $net, array( 'currency' => $currency ) ); // wpcs: xss ok. ?>
765
			</td>
766
		</tr>
767
768
		<?php
769
	}
770
771
	/**
772
	 * Generates a localized message for an error, adds it as a note and throws it.
773
	 *
774
	 * @since 4.2.0
775
	 * @param  stdClass $response  The response from the Stripe API.
776
	 * @param  WC_Order $order     The order to add a note to.
777
	 * @throws WC_Stripe_Exception An exception with the right message.
778
	 */
779
	public function throw_localized_message( $response, $order ) {
780
		$localized_messages = WC_Stripe_Helper::get_localized_messages();
781
782
		if ( 'card_error' === $response->error->type ) {
783
			$localized_message = isset( $localized_messages[ $response->error->code ] ) ? $localized_messages[ $response->error->code ] : $response->error->message;
784
		} else {
785
			$localized_message = isset( $localized_messages[ $response->error->type ] ) ? $localized_messages[ $response->error->type ] : $response->error->message;
786
		}
787
788
		$order->add_order_note( $localized_message );
789
790
		throw new WC_Stripe_Exception( print_r( $response, true ), $localized_message );
791
	}
792
793
	/**
794
	 * Retries the payment process once an error occured.
795
	 *
796
	 * @since 4.2.0
797
	 * @param object   $response          The response from the Stripe API.
798
	 * @param WC_Order $order             An order that is being paid for.
799
	 * @param bool     $retry             A flag that indicates whether another retry should be attempted.
800
	 * @param bool     $force_save_source Force save the payment source.
801
	 * @param mixed    $previous_error Any error message from previous request.
802
	 * @throws WC_Stripe_Exception        If the payment is not accepted.
803
	 * @return array|void
804
	 */
805
	public function retry_after_error( $response, $order, $retry, $force_save_source, $previous_error ) {
806
		if ( ! $retry ) {
807
			$localized_message = __( 'Sorry, we are unable to process your payment at this time. Please retry later.', 'woocommerce-gateway-stripe' );
808
			$order->add_order_note( $localized_message );
809
			throw new WC_Stripe_Exception( print_r( $response, true ), $localized_message ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.
810
		}
811
812
		// Don't do anymore retries after this.
813
		if ( 5 <= $this->retry_interval ) {
814
			return $this->process_payment( $order->get_id(), false, $force_save_source, $response->error, $previous_error );
815
		}
816
817
		sleep( $this->retry_interval );
818
		$this->retry_interval++;
819
820
		return $this->process_payment( $order->get_id(), true, $force_save_source, $response->error, $previous_error );
821
	}
822
823
	/**
824
	 * Adds the necessary hooks to modify the "Pay for order" page in order to clean
825
	 * it up and prepare it for the Stripe PaymentIntents modal to confirm a payment.
826
	 *
827
	 * @since 4.2
828
	 * @param WC_Payment_Gateway[] $gateways A list of all available gateways.
829
	 * @return WC_Payment_Gateway[]          Either the same list or an empty one in the right conditions.
830
	 */
831
	public function prepare_order_pay_page( $gateways ) {
832
		if ( ! is_wc_endpoint_url( 'order-pay' ) || ! isset( $_GET['wc-stripe-confirmation'] ) ) { // wpcs: csrf ok.
833
			return $gateways;
834
		}
835
836
		try {
837
			$this->prepare_intent_for_order_pay_page();
838
		} catch ( WC_Stripe_Exception $e ) {
839
			// Just show the full order pay page if there was a problem preparing the Payment Intent
840
			return $gateways;
841
		}
842
843
		add_filter( 'woocommerce_checkout_show_terms', '__return_false' );
844
		add_filter( 'woocommerce_pay_order_button_html', '__return_false' );
845
		add_filter( 'woocommerce_available_payment_gateways', '__return_empty_array' );
846
		add_filter( 'woocommerce_no_available_payment_methods_message', array( $this, 'change_no_available_methods_message' ) );
847
		add_action( 'woocommerce_pay_order_after_submit', array( $this, 'render_payment_intent_inputs' ) );
848
849
		return array();
850
	}
851
852
	/**
853
	 * Changes the text of the "No available methods" message to one that indicates
854
	 * the need for a PaymentIntent to be confirmed.
855
	 *
856
	 * @since 4.2
857
	 * @return string the new message.
858
	 */
859
	public function change_no_available_methods_message() {
860
		return wpautop( __( "Almost there!\n\nYour order has already been created, the only thing that still needs to be done is for you to authorize the payment with your bank.", 'woocommerce-gateway-stripe' ) );
861
	}
862
863
	/**
864
	 * Prepares the Payment Intent for it to be completed in the "Pay for Order" page.
865
	 *
866
	 * @param WC_Order|null $order Order object, or null to get the order from the "order-pay" URL parameter
867
	 *
868
	 * @throws WC_Stripe_Exception
869
	 * @since 4.3
870
	 */
871
	public function prepare_intent_for_order_pay_page( $order = null ) {
872 View Code Duplication
		if ( ! isset( $order ) || empty( $order ) ) {
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...
873
			$order = wc_get_order( absint( get_query_var( 'order-pay' ) ) );
874
		}
875
		$intent = $this->get_intent_from_order( $order );
876
877
		if ( ! $intent ) {
878
			throw new WC_Stripe_Exception( 'Payment Intent not found', __( 'Payment Intent not found for order #' . $order->get_id(), 'woocommerce-gateway-stripe' ) );
879
		}
880
881
		if ( 'requires_payment_method' === $intent->status && isset( $intent->last_payment_error )
882
		     && 'authentication_required' === $intent->last_payment_error->code ) {
883
			$intent = WC_Stripe_API::request( array(
884
				'payment_method' => $intent->last_payment_error->source->id,
885
			), 'payment_intents/' . $intent->id . '/confirm' );
886
			if ( isset( $intent->error ) ) {
887
				throw new WC_Stripe_Exception( print_r( $intent, true ), $intent->error->message );
888
			}
889
		}
890
891
		$this->order_pay_intent = $intent;
892
	}
893
894
	/**
895
	 * Renders hidden inputs on the "Pay for Order" page in order to let Stripe handle PaymentIntents.
896
	 *
897
	 * @param WC_Order|null $order Order object, or null to get the order from the "order-pay" URL parameter
898
	 *
899
	 * @throws WC_Stripe_Exception
900
	 * @since 4.2
901
	 */
902
	public function render_payment_intent_inputs( $order = null ) {
903 View Code Duplication
		if ( ! isset( $order ) || empty( $order ) ) {
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...
904
			$order = wc_get_order( absint( get_query_var( 'order-pay' ) ) );
905
		}
906
		if ( ! isset( $this->order_pay_intent ) ) {
907
			$this->prepare_intent_for_order_pay_page( $order );
908
		}
909
910
		$verification_url = add_query_arg(
911
			array(
912
				'order'            => $order->get_id(),
913
				'nonce'            => wp_create_nonce( 'wc_stripe_confirm_pi' ),
914
				'redirect_to'      => rawurlencode( $this->get_return_url( $order ) ),
915
				'is_pay_for_order' => true,
916
			),
917
			WC_AJAX::get_endpoint( 'wc_stripe_verify_intent' )
918
		);
919
920
		echo '<input type="hidden" id="stripe-intent-id" value="' . esc_attr( $this->order_pay_intent->client_secret ) . '" />';
921
		echo '<input type="hidden" id="stripe-intent-return" value="' . esc_attr( $verification_url ) . '" />';
922
	}
923
924
	/**
925
	 * Adds an error message wrapper to each saved method.
926
	 *
927
	 * @since 4.2.0
928
	 * @param WC_Payment_Token $token Payment Token.
929
	 * @return string                 Generated payment method HTML
930
	 */
931
	public function get_saved_payment_method_option_html( $token ) {
932
		$html          = parent::get_saved_payment_method_option_html( $token );
933
		$error_wrapper = '<div class="stripe-source-errors" role="alert"></div>';
934
935
		return preg_replace( '~</(\w+)>\s*$~', "$error_wrapper</$1>", $html );
936
	}
937
938
	/**
939
	 * Attempt to manually complete the payment process for orders, which are still pending
940
	 * before displaying the View Order page. This is useful in case webhooks have not been set up.
941
	 *
942
	 * @since 4.2.0
943
	 * @param int $order_id The ID that will be used for the thank you page.
944
	 */
945
	public function check_intent_status_on_order_page( $order_id ) {
946
		if ( empty( $order_id ) || absint( $order_id ) <= 0 ) {
947
			return;
948
		}
949
950
		$order = wc_get_order( absint( $order_id ) );
951
952
		if ( ! $order ) {
953
			return;
954
		}
955
956
		$this->verify_intent_after_checkout( $order );
957
	}
958
959
	/**
960
	 * Attached to `woocommerce_payment_successful_result` with a late priority,
961
	 * this method will combine the "naturally" generated redirect URL from
962
	 * WooCommerce and a payment/setup intent secret into a hash, which contains both
963
	 * the secret, and a proper URL, which will confirm whether the intent succeeded.
964
	 *
965
	 * @since 4.2.0
966
	 * @param array $result   The result from `process_payment`.
967
	 * @param int   $order_id The ID of the order which is being paid for.
968
	 * @return array
969
	 */
970
	public function modify_successful_payment_result( $result, $order_id ) {
971
		if ( ! isset( $result['payment_intent_secret'] ) && ! isset( $result['setup_intent_secret'] ) ) {
972
			// Only redirects with intents need to be modified.
973
			return $result;
974
		}
975
976
		// Put the final thank you page redirect into the verification URL.
977
		$verification_url = add_query_arg(
978
			array(
979
				'order'       => $order_id,
980
				'nonce'       => wp_create_nonce( 'wc_stripe_confirm_pi' ),
981
				'redirect_to' => rawurlencode( $result['redirect'] ),
982
			),
983
			WC_AJAX::get_endpoint( 'wc_stripe_verify_intent' )
984
		);
985
986
		if ( isset( $result['payment_intent_secret'] ) ) {
987
			$redirect = sprintf( '#confirm-pi-%s:%s', $result['payment_intent_secret'], rawurlencode( $verification_url ) );
988
		} else if ( isset( $result['setup_intent_secret'] ) ) {
989
			$redirect = sprintf( '#confirm-si-%s:%s', $result['setup_intent_secret'], rawurlencode( $verification_url ) );
990
		}
991
992
		return array(
993
			'result'   => 'success',
994
			'redirect' => $redirect,
0 ignored issues
show
Bug introduced by
The variable $redirect 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...
995
		);
996
	}
997
998
	/**
999
	 * Proceed with current request using new login session (to ensure consistent nonce).
1000
	 */
1001
	public function set_cookie_on_current_request( $cookie ) {
1002
		$_COOKIE[ LOGGED_IN_COOKIE ] = $cookie;
1003
	}
1004
1005
	/**
1006
	 * Executed between the "Checkout" and "Thank you" pages, this
1007
	 * method updates orders based on the status of associated PaymentIntents.
1008
	 *
1009
	 * @since 4.2.0
1010
	 * @param WC_Order $order The order which is in a transitional state.
1011
	 */
1012
	public function verify_intent_after_checkout( $order ) {
1013
		$payment_method = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $order->payment_method : $order->get_payment_method();
1014
		if ( $payment_method !== $this->id ) {
1015
			// If this is not the payment method, an intent would not be available.
1016
			return;
1017
		}
1018
1019
		$intent = $this->get_intent_from_order( $order );
1020
		if ( ! $intent ) {
1021
			// No intent, redirect to the order received page for further actions.
1022
			return;
1023
		}
1024
1025
		// A webhook might have modified or locked the order while the intent was retreived. This ensures we are reading the right status.
1026
		clean_post_cache( $order->get_id() );
1027
		$order = wc_get_order( $order->get_id() );
1028
1029
		if ( ! $order->has_status( array( 'pending', 'failed' ) ) ) {
1030
			// If payment has already been completed, this function is redundant.
1031
			return;
1032
		}
1033
1034
		if ( $this->lock_order_payment( $order, $intent ) ) {
1035
			return;
1036
		}
1037
1038
		if ( 'setup_intent' === $intent->object && 'succeeded' === $intent->status ) {
1039
			WC()->cart->empty_cart();
1040
			if ( WC_Stripe_Helper::is_pre_orders_exists() && WC_Pre_Orders_Order::order_contains_pre_order( $order ) ) {
1041
				WC_Pre_Orders_Order::mark_order_as_pre_ordered( $order );
1042
			} else {
1043
				$order->payment_complete();
1044
			}
1045
		} else if ( 'succeeded' === $intent->status || 'requires_capture' === $intent->status ) {
1046
			// Proceed with the payment completion.
1047
			$this->process_response( end( $intent->charges->data ), $order );
1048
		} else if ( 'requires_payment_method' === $intent->status ) {
1049
			// `requires_payment_method` means that SCA got denied for the current payment method.
1050
			$this->failed_sca_auth( $order, $intent );
1051
		}
1052
1053
		$this->unlock_order_payment( $order );
1054
	}
1055
1056
	/**
1057
	 * Checks if the payment intent associated with an order failed and records the event.
1058
	 *
1059
	 * @since 4.2.0
1060
	 * @param WC_Order $order  The order which should be checked.
1061
	 * @param object   $intent The intent, associated with the order.
1062
	 */
1063
	public function failed_sca_auth( $order, $intent ) {
1064
		// If the order has already failed, do not repeat the same message.
1065
		if ( $order->has_status( 'failed' ) ) {
1066
			return;
1067
		}
1068
1069
		// Load the right message and update the status.
1070
		$status_message = isset( $intent->last_payment_error )
1071
			/* translators: 1) The error message that was received from Stripe. */
1072
			? sprintf( __( 'Stripe SCA authentication failed. Reason: %s', 'woocommerce-gateway-stripe' ), $intent->last_payment_error->message )
1073
			: __( 'Stripe SCA authentication failed.', 'woocommerce-gateway-stripe' );
1074
		$order->update_status( 'failed', $status_message );
1075
	}
1076
1077
	/**
1078
	 * Preserves the "wc-stripe-confirmation" URL parameter so the user can complete the SCA authentication after logging in.
1079
	 *
1080
	 * @param string $pay_url Current computed checkout URL for the given order.
1081
	 * @param WC_Order $order Order object.
1082
	 *
1083
	 * @return string Checkout URL for the given order.
1084
	 */
1085
	public function get_checkout_payment_url( $pay_url, $order ) {
1086
		global $wp;
1087
		if ( isset( $_GET['wc-stripe-confirmation'] ) && isset( $wp->query_vars['order-pay'] ) && $wp->query_vars['order-pay'] == $order->get_id() ) {
1088
			$pay_url = add_query_arg( 'wc-stripe-confirmation', 1, $pay_url );
1089
		}
1090
		return $pay_url;
1091
	}
1092
1093
	/**
1094
	 * Checks whether new keys are being entered when saving options.
1095
	 */
1096
	public function process_admin_options() {
1097
		// Load all old values before the new settings get saved.
1098
		$old_publishable_key      = $this->get_option( 'publishable_key' );
1099
		$old_secret_key           = $this->get_option( 'secret_key' );
1100
		$old_test_publishable_key = $this->get_option( 'test_publishable_key' );
1101
		$old_test_secret_key      = $this->get_option( 'test_secret_key' );
1102
1103
		parent::process_admin_options();
1104
1105
		// Load all old values after the new settings have been saved.
1106
		$new_publishable_key      = $this->get_option( 'publishable_key' );
1107
		$new_secret_key           = $this->get_option( 'secret_key' );
1108
		$new_test_publishable_key = $this->get_option( 'test_publishable_key' );
1109
		$new_test_secret_key      = $this->get_option( 'test_secret_key' );
1110
1111
		// Checks whether a value has transitioned from a non-empty value to a new one.
1112
		$has_changed = function( $old_value, $new_value ) {
1113
			return ! empty( $old_value ) && ( $old_value !== $new_value );
1114
		};
1115
1116
		// Look for updates.
1117
		if (
1118
			$has_changed( $old_publishable_key, $new_publishable_key )
1119
			|| $has_changed( $old_secret_key, $new_secret_key )
1120
			|| $has_changed( $old_test_publishable_key, $new_test_publishable_key )
1121
			|| $has_changed( $old_test_secret_key, $new_test_secret_key )
1122
		) {
1123
			update_option( 'wc_stripe_show_changed_keys_notice', 'yes' );
1124
		}
1125
	}
1126
}
1127