Completed
Pull Request — master (#605)
by Roy
05:45
created

WC_Gateway_Stripe   D

Complexity

Total Complexity 155

Size/Duplication

Total Lines 908
Duplicated Lines 16.96 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 0
Metric Value
dl 154
loc 908
rs 4.4444
c 0
b 0
f 0
wmc 155
lcom 1
cbo 8

16 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 5 71 6
A are_keys_set() 0 7 3
A is_available() 0 7 3
A show_update_card_notice() 0 8 4
A get_icon() 0 21 4
A init_form_fields() 0 3 1
F payment_fields() 19 79 29
A elements_form() 0 49 2
A admin_scripts() 0 9 4
F payment_scripts() 0 83 32
C stripe_checkout_receipt_page() 10 70 21
C stripe_checkout_return_handler() 0 29 7
A maybe_redirect_stripe_checkout() 0 8 4
F process_payment() 62 186 27
B display_order_fee() 29 29 4
B display_order_payout() 29 29 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like WC_Gateway_Stripe often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use WC_Gateway_Stripe, and based on these observations, apply Extract Interface, too.

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
	 * Checkout enabled
35
	 *
36
	 * @var bool
37
	 */
38
	public $stripe_checkout;
39
40
	/**
41
	 * Stripe Checkout description.
42
	 *
43
	 * @var string
44
	 */
45
	public $stripe_checkout_description;
46
47
	/**
48
	 * Require 3D Secure enabled
49
	 *
50
	 * @var bool
51
	 */
52
	public $three_d_secure;
53
54
	/**
55
	 * Credit card image
56
	 *
57
	 * @var string
58
	 */
59
	public $stripe_checkout_image;
60
61
	/**
62
	 * Should we store the users credit cards?
63
	 *
64
	 * @var bool
65
	 */
66
	public $saved_cards;
67
68
	/**
69
	 * API access secret key
70
	 *
71
	 * @var string
72
	 */
73
	public $secret_key;
74
75
	/**
76
	 * Api access publishable key
77
	 *
78
	 * @var string
79
	 */
80
	public $publishable_key;
81
82
	/**
83
	 * Do we accept bitcoin?
84
	 *
85
	 * @var bool
86
	 */
87
	public $bitcoin;
88
89
	/**
90
	 * Do we accept Payment Request?
91
	 *
92
	 * @var bool
93
	 */
94
	public $payment_request;
95
96
	/**
97
	 * Is test mode active?
98
	 *
99
	 * @var bool
100
	 */
101
	public $testmode;
102
103
	/**
104
	 * Inline CC form styling
105
	 *
106
	 * @var string
107
	 */
108
	public $inline_cc_form;
109
110
	/**
111
	 * Pre Orders Object
112
	 *
113
	 * @var object
114
	 */
115
	public $pre_orders;
116
117
	/**
118
	 * Constructor
119
	 */
120
	public function __construct() {
121
		$this->retry_interval       = 1;
122
		$this->id                   = 'stripe';
123
		$this->method_title         = __( 'Stripe', 'woocommerce-gateway-stripe' );
124
		/* translators: 1) link to Stripe register page 2) link to Stripe api keys page */
125
		$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' );
126
		$this->has_fields           = true;
127
		$this->supports             = array(
128
			'products',
129
			'refunds',
130
			'tokenization',
131
			'add_payment_method',
132
			'subscriptions',
133
			'subscription_cancellation',
134
			'subscription_suspension',
135
			'subscription_reactivation',
136
			'subscription_amount_changes',
137
			'subscription_date_changes',
138
			'subscription_payment_method_change',
139
			'subscription_payment_method_change_customer',
140
			'subscription_payment_method_change_admin',
141
			'multiple_subscriptions',
142
			'pre-orders',
143
		);
144
145
		// Load the form fields.
146
		$this->init_form_fields();
147
148
		// Load the settings.
149
		$this->init_settings();
150
151
		// Get setting values.
152
		$this->title                       = $this->get_option( 'title' );
153
		$this->description                 = $this->get_option( 'description' );
154
		$this->enabled                     = $this->get_option( 'enabled' );
155
		$this->testmode                    = 'yes' === $this->get_option( 'testmode' );
156
		$this->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...
157
		$this->capture                     = 'yes' === $this->get_option( 'capture', 'yes' );
158
		$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...
159
		$this->three_d_secure              = 'yes' === $this->get_option( 'three_d_secure' );
160
		$this->stripe_checkout             = 'yes' === $this->get_option( 'stripe_checkout' );
161
		$this->stripe_checkout_image       = $this->get_option( 'stripe_checkout_image', '' );
162
		$this->stripe_checkout_description = $this->get_option( 'stripe_checkout_description' );
163
		$this->saved_cards                 = 'yes' === $this->get_option( 'saved_cards' );
164
		$this->secret_key                  = $this->testmode ? $this->get_option( 'test_secret_key' ) : $this->get_option( 'secret_key' );
165
		$this->publishable_key             = $this->testmode ? $this->get_option( 'test_publishable_key' ) : $this->get_option( 'publishable_key' );
166
		$this->bitcoin                     = 'USD' === strtoupper( get_woocommerce_currency() ) && 'yes' === $this->get_option( 'stripe_bitcoin' );
167
		$this->payment_request             = 'yes' === $this->get_option( 'payment_request', 'yes' );
168
169
		if ( $this->stripe_checkout ) {
170
			$this->order_button_text = __( 'Continue to payment', 'woocommerce-gateway-stripe' );
171
		}
172
173
		WC_Stripe_API::set_secret_key( $this->secret_key );
174
175
		// Hooks.
176
		add_action( 'wp_enqueue_scripts', array( $this, 'payment_scripts' ) );
177
		add_action( 'admin_enqueue_scripts', array( $this, 'admin_scripts' ) );
178
		add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );
179
		add_action( 'woocommerce_admin_order_totals_after_total', array( $this, 'display_order_fee' ), 10, 1 );
180
		add_action( 'woocommerce_admin_order_totals_after_total', array( $this, 'display_order_payout' ), 20, 1 );
181
		add_action( 'woocommerce_customer_save_address', array( $this, 'show_update_card_notice' ), 10, 2 );
182
		add_action( 'woocommerce_receipt_stripe', array( $this, 'stripe_checkout_receipt_page' ) );
183
		add_action( 'woocommerce_api_' . strtolower( get_class( $this ) ), array( $this, 'stripe_checkout_return_handler' ) );
184
185 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...
186
			$this->pre_orders = new WC_Stripe_Pre_Orders_Compat();
187
188
			add_action( 'wc_pre_orders_process_pre_order_completion_payment_' . $this->id, array( $this->pre_orders, 'process_pre_order_release_payment' ) );
189
		}
190
	}
191
192
	/**
193
	 * Checks if keys are set.
194
	 *
195
	 * @since 4.0.6
196
	 * @return bool
197
	 */
198
	public function are_keys_set() {
199
		if ( empty( $this->secret_key ) || empty( $this->publishable_key ) ) {
200
			return false;
201
		}
202
203
		return true;
204
	}
205
206
	/**
207
	 * Checks if gateway should be available to use.
208
	 *
209
	 * @since 4.0.2
210
	 */
211
	public function is_available() {
212
		if ( is_add_payment_method_page() && ! $this->saved_cards ) {
213
			return false;
214
		}
215
216
		return parent::is_available();
217
	}
218
219
	/**
220
	 * Adds a notice for customer when they update their billing address.
221
	 *
222
	 * @since 4.1.0
223
	 * @param int $user_id
224
	 * @param array $load_address
225
	 */
226
	public function show_update_card_notice( $user_id, $load_address ) {
227
		if ( ! $this->saved_cards || ! WC_Stripe_Payment_Tokens::customer_has_saved_methods( $user_id ) || 'billing' !== $load_address ) {
228
			return;
229
		}
230
231
		/* translators: 1) Opening anchor tag 2) closing anchor tag */
232
		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' );
233
	}
234
235
	/**
236
	 * Get_icon function.
237
	 *
238
	 * @since 1.0.0
239
	 * @version 4.0.0
240
	 * @return string
241
	 */
242
	public function get_icon() {
243
		$icons = $this->payment_icons();
244
245
		$icons_str = '';
246
247
		$icons_str .= $icons['visa'];
248
		$icons_str .= $icons['amex'];
249
		$icons_str .= $icons['mastercard'];
250
251
		if ( 'USD' === get_woocommerce_currency() ) {
252
			$icons_str .= $icons['discover'];
253
			$icons_str .= $icons['jcb'];
254
			$icons_str .= $icons['diners'];
255
		}
256
257
		if ( $this->bitcoin && $this->stripe_checkout ) {
258
			$icons_str .= $icons['bitcoin'];
259
		}
260
261
		return apply_filters( 'woocommerce_gateway_icon', $icons_str, $this->id );
262
	}
263
264
	/**
265
	 * Initialise Gateway Settings Form Fields
266
	 */
267
	public function init_form_fields() {
268
		$this->form_fields = require( dirname( __FILE__ ) . '/admin/stripe-settings.php' );
269
	}
270
271
	/**
272
	 * Payment form on checkout page
273
	 */
274
	public function payment_fields() {
275
		$user                 = wp_get_current_user();
276
		$display_tokenization = $this->supports( 'tokenization' ) && is_checkout() && $this->saved_cards;
277
		$total                = WC()->cart->total;
278
		$user_email           = '';
279
280
		// If paying from order, we need to get total from order not cart.
281 View Code Duplication
		if ( isset( $_GET['pay_for_order'] ) && ! empty( $_GET['key'] ) ) {
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
			$order      = wc_get_order( wc_get_order_id_by_order_key( wc_clean( $_GET['key'] ) ) );
283
			$total      = $order->get_total();
284
			$user_email = WC_Stripe_Helper::is_pre_30() ? $order->billing_email : $order->get_billing_email();
285
		} else {
286
			if ( $user->ID ) {
287
				$user_email = get_user_meta( $user->ID, 'billing_email', true );
288
				$user_email = $user_email ? $user_email : $user->user_email;
289
			}
290
		}
291
292
		if ( is_add_payment_method_page() ) {
293
			$pay_button_text = __( 'Add Card', 'woocommerce-gateway-stripe' );
294
			$total        = '';
295
		} elseif ( function_exists( 'wcs_order_contains_subscription' ) && isset( $_GET['change_payment_method'] ) ) {
296
			$pay_button_text = __( 'Change Payment Method', 'woocommerce-gateway-stripe' );
297
			$total        = '';
298
		} else {
299
			$pay_button_text = '';
300
		}
301
302
		ob_start();
303
304
		echo '<div
305
			id="stripe-payment-data"
306
			data-panel-label="' . esc_attr( $pay_button_text ) . '"
307
			data-description="' . esc_attr( strip_tags( $this->stripe_checkout_description ) ) . '"
308
			data-email="' . esc_attr( $user_email ) . '"
309
			data-verify-zip="' . esc_attr( apply_filters( 'wc_stripe_checkout_verify_zip', false ) ? 'true' : 'false' ) . '"
310
			data-billing-address="' . esc_attr( apply_filters( 'wc_stripe_checkout_require_billing_address', false ) ? 'true' : 'false' ) . '"
311
			data-shipping-address="' . esc_attr( apply_filters( 'wc_stripe_checkout_require_shipping_address', false ) ? 'true' : 'false' ) . '" 
312
			data-amount="' . esc_attr( WC_Stripe_Helper::get_stripe_amount( $total ) ) . '"
313
			data-name="' . esc_attr( $this->statement_descriptor ) . '"
314
			data-currency="' . esc_attr( strtolower( get_woocommerce_currency() ) ) . '"
315
			data-image="' . esc_attr( $this->stripe_checkout_image ) . '"
316
			data-bitcoin="' . esc_attr( ( $this->bitcoin && $this->capture ) ? 'true' : 'false' ) . '"
317
			data-locale="' . esc_attr( apply_filters( 'wc_stripe_checkout_locale', $this->get_locale() ) ) . '"
318
			data-three-d-secure="' . esc_attr( $this->three_d_secure ? 'true' : 'false' ) . '"
319
			data-allow-remember-me="' . esc_attr( apply_filters( 'wc_stripe_allow_remember_me', true ) ? 'true' : 'false' ) . '">';
320
321 View Code Duplication
		if ( $this->description ) {
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...
322
			if ( $this->testmode ) {
323
				/* translators: link to Stripe testing page */
324
				$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 <a href="%s" target="_blank">Testing Stripe documentation</a> for more card numbers.', 'woocommerce-gateway-stripe' ), 'https://stripe.com/docs/testing' );
325
				$this->description  = trim( $this->description );
326
			}
327
328
			echo apply_filters( 'wc_stripe_description', wpautop( wp_kses_post( $this->description ) ), $this->id );
329
		}
330
331
		if ( $display_tokenization ) {
332
			$this->tokenization_script();
333
			$this->saved_payment_methods();
334
		}
335
336
		if ( ! $this->stripe_checkout ) {
337
			$this->elements_form();
338
		}
339
340
		if ( apply_filters( 'wc_stripe_display_save_payment_method_checkbox', $display_tokenization ) && ! is_add_payment_method_page() && ! isset( $_GET['change_payment_method'] ) ) {
341
342
			if ( ! $this->stripe_checkout ) {
343
				$this->save_payment_method_checkbox();
344
			} elseif ( $this->stripe_checkout && isset( $_GET['pay_for_order'] ) && ! empty( $_GET['key'] ) ) {
345
				$this->save_payment_method_checkbox();
346
			}
347
		}
348
349
		echo '</div>';
350
351
		ob_end_flush();
352
	}
353
354
	/**
355
	 * Renders the Stripe elements form.
356
	 *
357
	 * @since 4.0.0
358
	 * @version 4.0.0
359
	 */
360
	public function elements_form() {
361
		?>
362
		<fieldset id="wc-<?php echo esc_attr( $this->id ); ?>-cc-form" class="wc-credit-card-form wc-payment-form" style="background:transparent;">
363
			<?php do_action( 'woocommerce_credit_card_form_start', $this->id ); ?>
364
365
			<?php if ( $this->inline_cc_form ) { ?>
366
				<label for="card-element">
367
					<?php esc_html_e( 'Credit or debit card', 'woocommerce-gateway-stripe' ); ?>
368
				</label>
369
370
				<div id="stripe-card-element" style="background:#fff;padding:0 1em;border:1px solid #ddd;margin:5px 0;padding:10px 5px;">
371
				<!-- a Stripe Element will be inserted here. -->
372
				</div>
373
			<?php } else { ?>
374
				<div class="form-row form-row-wide">
375
					<label><?php esc_html_e( 'Card Number', 'woocommerce-gateway-stripe' ); ?> <span class="required">*</span></label>
376
					<div class="stripe-card-group">
377
						<div id="stripe-card-element" style="background:#fff;padding:0 1em;border:1px solid #ddd;margin:5px 0;padding:10px 5px;">
378
						<!-- a Stripe Element will be inserted here. -->
379
						</div>
380
381
						<i class="stripe-credit-card-brand stripe-card-brand" alt="Credit Card"></i>
382
					</div>
383
				</div>
384
385
				<div class="form-row form-row-first">
386
					<label><?php esc_html_e( 'Expiry Date', 'woocommerce-gateway-stripe' ); ?> <span class="required">*</span></label>
387
388
					<div id="stripe-exp-element" style="background:#fff;padding:0 1em;border:1px solid #ddd;margin:5px 0;padding:10px 5px;">
389
					<!-- a Stripe Element will be inserted here. -->
390
					</div>
391
				</div>
392
393
				<div class="form-row form-row-last">
394
					<label><?php esc_html_e( 'Card Code (CVC)', 'woocommerce-gateway-stripe' ); ?> <span class="required">*</span></label>
395
				<div id="stripe-cvc-element" style="background:#fff;padding:0 1em;border:1px solid #ddd;margin:5px 0;padding:10px 5px;">
396
				<!-- a Stripe Element will be inserted here. -->
397
				</div>
398
				</div>
399
				<div class="clear"></div>
400
			<?php } ?>
401
402
			<!-- Used to display form errors -->
403
			<div class="stripe-source-errors" role="alert"></div>
404
			<?php do_action( 'woocommerce_credit_card_form_end', $this->id ); ?>
405
			<div class="clear"></div>
406
		</fieldset>
407
		<?php
408
	}
409
410
	/**
411
	 * Load admin scripts.
412
	 *
413
	 * @since 3.1.0
414
	 * @version 3.1.0
415
	 */
416
	public function admin_scripts() {
417
		if ( 'woocommerce_page_wc-settings' !== get_current_screen()->id ) {
418
			return;
419
		}
420
421
		$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
422
423
		wp_enqueue_script( 'woocommerce_stripe_admin', plugins_url( 'assets/js/stripe-admin' . $suffix . '.js', WC_STRIPE_MAIN_FILE ), array(), WC_STRIPE_VERSION, true );
424
	}
425
426
	/**
427
	 * Payment_scripts function.
428
	 *
429
	 * Outputs scripts used for stripe payment
430
	 *
431
	 * @since 3.1.0
432
	 * @version 4.0.0
433
	 */
434
	public function payment_scripts() {
435
		if ( ! is_cart() && ! is_checkout() && ! isset( $_GET['pay_for_order'] ) && ! is_add_payment_method_page() && ! isset( $_GET['change_payment_method'] ) ) {
436
			return;
437
		}
438
439
		// If Stripe is not enabled bail.
440
		if ( 'no' === $this->enabled ) {
441
			return;
442
		}
443
444
		// If keys are not set bail.
445
		if ( ! $this->are_keys_set() ) {
446
			WC_Stripe_Logger::log( 'Keys are not set correctly.' );
447
			return;
448
		}
449
450
		// If no SSL bail.
451
		if ( ! $this->testmode && ! is_ssl() ) {
452
			WC_Stripe_Logger::log( 'Stripe live mode requires SSL.' );
453
		}
454
455
		$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
456
457
		wp_register_style( 'stripe_styles', plugins_url( 'assets/css/stripe-styles.css', WC_STRIPE_MAIN_FILE ), array(), WC_STRIPE_VERSION );
458
		wp_enqueue_style( 'stripe_styles' );
459
		wp_register_script( 'stripe_checkout', 'https://checkout.stripe.com/checkout.js', '', WC_STRIPE_VERSION, true );
460
		wp_register_script( 'stripe', 'https://js.stripe.com/v3/', '', '3.0', true );
461
		wp_register_script( 'woocommerce_stripe', plugins_url( 'assets/js/stripe' . $suffix . '.js', WC_STRIPE_MAIN_FILE ), array( 'jquery-payment', 'stripe' ), WC_STRIPE_VERSION, true );
462
463
		$stripe_params = array(
464
			'key'                  => $this->publishable_key,
465
			'i18n_terms'           => __( 'Please accept the terms and conditions first', 'woocommerce-gateway-stripe' ),
466
			'i18n_required_fields' => __( 'Please fill in required checkout fields first', 'woocommerce-gateway-stripe' ),
467
		);
468
469
		// If we're on the pay page we need to pass stripe.js the address of the order.
470
		if ( isset( $_GET['pay_for_order'] ) && 'true' === $_GET['pay_for_order'] ) {
471
			$order_id = wc_get_order_id_by_order_key( urldecode( $_GET['key'] ) );
472
			$order    = wc_get_order( $order_id );
473
474
			$stripe_params['billing_first_name'] = WC_Stripe_Helper::is_pre_30() ? $order->billing_first_name : $order->get_billing_first_name();
475
			$stripe_params['billing_last_name']  = WC_Stripe_Helper::is_pre_30() ? $order->billing_last_name : $order->get_billing_last_name();
476
			$stripe_params['billing_address_1']  = WC_Stripe_Helper::is_pre_30() ? $order->billing_address_1 : $order->get_billing_address_1();
477
			$stripe_params['billing_address_2']  = WC_Stripe_Helper::is_pre_30() ? $order->billing_address_2 : $order->get_billing_address_2();
478
			$stripe_params['billing_state']      = WC_Stripe_Helper::is_pre_30() ? $order->billing_state : $order->get_billing_state();
479
			$stripe_params['billing_city']       = WC_Stripe_Helper::is_pre_30() ? $order->billing_city : $order->get_billing_city();
480
			$stripe_params['billing_postcode']   = WC_Stripe_Helper::is_pre_30() ? $order->billing_postcode : $order->get_billing_postcode();
481
			$stripe_params['billing_country']    = WC_Stripe_Helper::is_pre_30() ? $order->billing_country : $order->get_billing_country();
482
		}
483
484
		$stripe_params['no_prepaid_card_msg']                     = __( 'Sorry, we\'re not accepting prepaid cards at this time. Your credit card has not been charge. Please try with alternative payment method.', 'woocommerce-gateway-stripe' );
485
		$stripe_params['no_sepa_owner_msg']                       = __( 'Please enter your IBAN account name.', 'woocommerce-gateway-stripe' );
486
		$stripe_params['no_sepa_iban_msg']                        = __( 'Please enter your IBAN account number.', 'woocommerce-gateway-stripe' );
487
		$stripe_params['sepa_mandate_notification']               = apply_filters( 'wc_stripe_sepa_mandate_notification', 'email' );
488
		$stripe_params['allow_prepaid_card']                      = apply_filters( 'wc_stripe_allow_prepaid_card', true ) ? 'yes' : 'no';
489
		$stripe_params['inline_cc_form']                          = $this->inline_cc_form ? 'yes' : 'no';
490
		$stripe_params['stripe_checkout_require_billing_address'] = apply_filters( 'wc_stripe_checkout_require_billing_address', false ) ? 'yes' : 'no';
491
		$stripe_params['is_checkout']                             = ( is_checkout() && empty( $_GET['pay_for_order'] ) ) ? 'yes' : 'no';
492
		$stripe_params['return_url']                              = $this->get_stripe_return_url();
493
		$stripe_params['ajaxurl']                                 = WC_AJAX::get_endpoint( '%%endpoint%%' );
494
		$stripe_params['stripe_nonce']                            = wp_create_nonce( '_wc_stripe_nonce' );
495
		$stripe_params['statement_descriptor']                    = $this->statement_descriptor;
496
		$stripe_params['elements_options']                        = apply_filters( 'wc_stripe_elements_options', array() );
497
		$stripe_params['is_stripe_checkout']                      = $this->stripe_checkout ? 'yes' : 'no';
498
		$stripe_params['is_change_payment_page']                  = isset( $_GET['change_payment_method'] ) ? 'yes' : 'no';
499
		$stripe_params['is_add_payment_page']                     = is_wc_endpoint_url( 'add-payment-method' ) ? 'yes' : 'no';
500
		$stripe_params['is_pay_for_order_page']                   = is_wc_endpoint_url( 'order-pay' ) ? 'yes' : 'no';
501
		$stripe_params['elements_styling']                        = apply_filters( 'wc_stripe_elements_styling', false );
502
		$stripe_params['elements_classes']                        = apply_filters( 'wc_stripe_elements_classes', false );
503
504
		// merge localized messages to be use in JS
505
		$stripe_params = array_merge( $stripe_params, WC_Stripe_Helper::get_localized_messages() );
506
507
		wp_localize_script( 'woocommerce_stripe', 'wc_stripe_params', apply_filters( 'wc_stripe_params', $stripe_params ) );
508
		wp_localize_script( 'woocommerce_stripe_checkout', 'wc_stripe_params', apply_filters( 'wc_stripe_params', $stripe_params ) );
509
510
		if ( $this->stripe_checkout ) {
511
			wp_enqueue_script( 'stripe_checkout' );
512
		}
513
514
		$this->tokenization_script();
515
		wp_enqueue_script( 'woocommerce_stripe' );
516
	}
517
518
	/**
519
	 * Add Stripe Checkout items to receipt page.
520
	 *
521
	 * @since 4.1.0
522
	 */
523
	public function stripe_checkout_receipt_page( $order_id ) {
524
		if ( ! $this->stripe_checkout ) {
525
			return;
526
		}
527
528
		$user                 = wp_get_current_user();
529
		$total                = WC()->cart->total;
530
		$user_email           = '';
531
		$display_tokenization = $this->supports( 'tokenization' ) && $this->saved_cards;
532
533
		// If paying from order, we need to get total from order not cart.
534 View Code Duplication
		if ( ! empty( $_GET['key'] ) ) {
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...
535
			$order      = wc_get_order( wc_get_order_id_by_order_key( wc_clean( $_GET['key'] ) ) );
536
			$total      = $order->get_total();
537
			$user_email = WC_Stripe_Helper::is_pre_30() ? $order->billing_email : $order->get_billing_email();
538
		} else {
539
			if ( $user->ID ) {
540
				$user_email = get_user_meta( $user->ID, 'billing_email', true );
541
				$user_email = $user_email ? $user_email : $user->user_email;
542
			}
543
		}
544
545
		ob_start();
546
547
		do_action( 'wc_stripe_checkout_receipt_page_before_form' );
548
549
		echo '<form method="post" class="woocommerce-checkout" action="' . WC()->api_request_url( get_class( $this ) ) . '">';
550
		echo '<div
551
			id="stripe-payment-data"
552
			data-panel-label="' . esc_attr( apply_filters( 'wc_stripe_checkout_label', '' ) ) . '"
553
			data-description="' . esc_attr( strip_tags( $this->stripe_checkout_description ) ) . '"
554
			data-email="' . esc_attr( $user_email ) . '"
555
			data-verify-zip="' . esc_attr( apply_filters( 'wc_stripe_checkout_verify_zip', false ) ? 'true' : 'false' ) . '"
556
			data-billing-address="' . esc_attr( apply_filters( 'wc_stripe_checkout_require_billing_address', false ) ? 'true' : 'false' ) . '"
557
			data-shipping-address="' . esc_attr( apply_filters( 'wc_stripe_checkout_require_shipping_address', false ) ? 'true' : 'false' ) . '" 
558
			data-amount="' . esc_attr( WC_Stripe_Helper::get_stripe_amount( $total ) ) . '"
559
			data-name="' . esc_attr( $this->statement_descriptor ) . '"
560
			data-currency="' . esc_attr( strtolower( get_woocommerce_currency() ) ) . '"
561
			data-image="' . esc_attr( $this->stripe_checkout_image ) . '"
562
			data-bitcoin="' . esc_attr( ( $this->bitcoin && $this->capture ) ? 'true' : 'false' ) . '"
563
			data-locale="' . esc_attr( apply_filters( 'wc_stripe_checkout_locale', $this->get_locale() ) ) . '"
564
			data-three-d-secure="' . esc_attr( $this->three_d_secure ? 'true' : 'false' ) . '"
565
			data-allow-remember-me="' . esc_attr( apply_filters( 'wc_stripe_allow_remember_me', true ) ? 'true' : 'false' ) . '">';
566
		echo '<input type="hidden" name="order_id" value="' . esc_attr( $order_id ) . '" />';
567
		echo '<input type="hidden" name="stripe_checkout_order" value="yes" />';
568
569
		if (
570
			apply_filters( 'wc_stripe_display_save_payment_method_checkbox', $display_tokenization ) &&
571
			( ! function_exists( 'wcs_order_contains_subscription' ) || ( function_exists( 'wcs_order_contains_subscription' ) && ! WC_Subscriptions_Cart::cart_contains_subscription() ) ) &&
572
			( ! WC_Stripe_Helper::is_pre_orders_exists() || ( WC_Stripe_Helper::is_pre_orders_exists() && ! $this->pre_orders->is_pre_order( $order_id ) ) )
573
		) {
574
			$this->save_payment_method_checkbox();
575
		}
576
577
		wp_nonce_field( 'stripe-checkout-process', 'stripe_checkout_process_nonce' );
578
579
		do_action( 'wc_stripe_checkout_receipt_page_before_form_submit' );
580
581
		echo '<button type="submit" class="wc-stripe-checkout-button">' . __( 'Place Order', 'woocommerce-gateway-stripe' ) . '</button>';
582
583
		do_action( 'wc_stripe_checkout_receipt_page_after_form_submit' );
584
585
		echo '</form>';
586
587
		do_action( 'wc_stripe_checkout_receipt_page_after_form' );
588
589
		echo '</div>';
590
591
		ob_end_flush();
592
	}
593
594
	/**
595
	 * Handles the return from processing the payment.
596
	 *
597
	 * @since 4.1.0
598
	 */
599
	public function stripe_checkout_return_handler() {
600
		if ( ! $this->stripe_checkout ) {
601
			return;
602
		}
603
604
		if ( ! wp_verify_nonce( $_POST['stripe_checkout_process_nonce'], 'stripe-checkout-process' ) ) {
605
			return;
606
		}
607
608
		$order_id = wc_clean( $_POST['order_id'] );
609
		$order    = wc_get_order( $order_id );
610
611
		do_action( 'wc_stripe_checkout_return_handler', $order );
612
613
		if ( WC_Stripe_Helper::is_pre_orders_exists() && $this->pre_orders->is_pre_order( $order_id ) && WC_Pre_Orders_Order::order_requires_payment_tokenization( $order_id ) ) {
614
			$result = $this->pre_orders->process_pre_order( $order_id );
615
		} else {
616
			$result = $this->process_payment( $order_id );
617
		}
618
619
		if ( 'success' === $result['result'] ) {
620
			wp_redirect( $result['redirect'] );
621
			exit;
622
		}
623
624
		// Redirects back to pay order page.
625
		wp_safe_redirect( $order->get_checkout_payment_url( true ) );
626
		exit;
627
	}
628
629
	/**
630
	 * Checks if we need to redirect for Stripe Checkout.
631
	 *
632
	 * @since 4.1.0
633
	 * @return bool
634
	 */
635
	public function maybe_redirect_stripe_checkout() {
636
		return (
637
			$this->stripe_checkout &&
638
			! isset( $_POST['stripe_checkout_order'] ) &&
639
			! $this->is_using_saved_payment_method() &&
640
			! is_wc_endpoint_url( 'order-pay' )
641
		);
642
	}
643
644
	/**
645
	 * Process the payment
646
	 *
647
	 * @since 1.0.0
648
	 * @since 4.1.0 Add 4th parameter to track previous error.
649
	 * @param int  $order_id Reference.
650
	 * @param bool $retry Should we retry on fail.
651
	 * @param bool $force_save_source Force save the payment source.
652
	 * @param mix $previous_error Any error message from previous request.
653
	 *
654
	 * @throws Exception If payment will not be accepted.
655
	 *
656
	 * @return array|void
657
	 */
658
	public function process_payment( $order_id, $retry = true, $force_save_source = false, $previous_error = false ) {
659
		try {
660
			$order = wc_get_order( $order_id );
661
662
			if ( $this->maybe_redirect_stripe_checkout() ) {
663
				WC_Stripe_Logger::log( sprintf( 'Redirecting to Stripe Checkout page for order %s', $order_id ) );
664
665
				return array(
666
					'result'   => 'success',
667
					'redirect' => $order->get_checkout_payment_url( true ),
668
				);
669
			}
670
671
			if ( $this->maybe_process_pre_orders( $order_id ) ) {
672
				return $this->pre_orders->process_pre_order( $order_id );
673
			}
674
675
			// This comes from the create account checkbox in the checkout page.
676
			$create_account = ! empty( $_POST['createaccount'] ) ? true : false;
677
678
			if ( $create_account ) {
679
				$new_customer_id     = WC_Stripe_Helper::is_pre_30() ? $order->customer_user : $order->get_customer_id();
680
				$new_stripe_customer = new WC_Stripe_Customer( $new_customer_id );
681
				$new_stripe_customer->create_customer();
682
			}
683
684
			$prepared_source = $this->prepare_source( get_current_user_id(), $force_save_source );
685
			$source_object   = $prepared_source->source_object;
686
687
			// Check if we don't allow prepaid credit cards.
688 View Code Duplication
			if ( ! apply_filters( 'wc_stripe_allow_prepaid_card', true ) && $this->is_prepaid_card( $source_object ) ) {
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...
689
				$localized_message = __( 'Sorry, we\'re not accepting prepaid cards at this time. Your credit card has not been charge. Please try with alternative payment method.', 'woocommerce-gateway-stripe' );
690
				throw new WC_Stripe_Exception( print_r( $source_object, true ), $localized_message );
691
			}
692
693 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...
694
				$localized_message = __( 'Payment processing failed. Please retry.', 'woocommerce-gateway-stripe' );
695
				throw new WC_Stripe_Exception( print_r( $prepared_source, true ), $localized_message );
696
			}
697
698
			$this->save_source_to_order( $order, $prepared_source );
699
700
			// Result from Stripe API request.
701
			$response = null;
702
703
			if ( $order->get_total() > 0 ) {
704
				// This will throw exception if not valid.
705
				$this->validate_minimum_order_amount( $order );
706
707
				/*
708
				 * Check if card 3DS is required or optional with 3DS setting.
709
				 * Will need to first create 3DS source and require redirection
710
				 * for customer to login to their credit card company.
711
				 * Note that if we need to save source, the original source must be first
712
				 * attached to a customer in Stripe before it can be charged.
713
				 */
714
				if ( $this->is_3ds_required( $source_object ) ) {
715
					$response = $this->create_3ds_source( $order, $source_object );
716
717
					if ( ! empty( $response->error ) ) {
718
						$localized_message = $response->error->message;
719
720
						$order->add_order_note( $localized_message );
721
722
						throw new WC_Stripe_Exception( print_r( $response, true ), $localized_message );
723
					}
724
725
					// Update order meta with 3DS source.
726
					if ( WC_Stripe_Helper::is_pre_30() ) {
727
						update_post_meta( $order_id, '_stripe_source_id', $response->id );
728
					} else {
729
						$order->update_meta_data( '_stripe_source_id', $response->id );
730
						$order->save();
731
					}
732
733
					WC_Stripe_Logger::log( 'Info: Redirecting to 3DS...' );
734
735
					return array(
736
						'result'   => 'success',
737
						'redirect' => esc_url_raw( $response->redirect->url ),
738
					);
739
				}
740
741
				WC_Stripe_Logger::log( "Info: Begin processing payment for order $order_id for the amount of {$order->get_total()}" );
742
743
				/* If we're doing a retry and source is chargeable, we need to pass
744
				 * a different idempotency key and retry for success.
745
				 */
746
				if ( $this->need_update_idempotency_key( $source_object, $previous_error ) ) {
747
					add_filter( 'wc_stripe_idempotency_key', array( $this, 'change_idempotency_key' ), 10, 2 );
748
				}
749
750
				// Make the request.
751
				$response = WC_Stripe_API::request( $this->generate_payment_request( $order, $prepared_source ) );
752
753 View Code Duplication
				if ( ! empty( $response->error ) ) {
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...
754
					// Customer param wrong? The user may have been deleted on stripe's end. Remove customer_id. Can be retried without.
755
					if ( $this->is_no_such_customer_error( $response->error ) ) {
756
						if ( WC_Stripe_Helper::is_pre_30() ) {
757
							delete_user_meta( $order->customer_user, '_stripe_customer_id' );
758
							delete_post_meta( $order_id, '_stripe_customer_id' );
759
						} else {
760
							delete_user_meta( $order->get_customer_id(), '_stripe_customer_id' );
761
							$order->delete_meta_data( '_stripe_customer_id' );
762
							$order->save();
763
						}
764
					}
765
766
					if ( $this->is_no_such_token_error( $response->error ) && $prepared_source->token_id ) {
767
						// Source param wrong? The CARD may have been deleted on stripe's end. Remove token and show message.
768
						$wc_token = WC_Payment_Tokens::get( $prepared_source->token_id );
769
						$wc_token->delete();
770
						$localized_message = __( 'This card is no longer available and has been removed.', 'woocommerce-gateway-stripe' );
771
						$order->add_order_note( $localized_message );
772
						throw new WC_Stripe_Exception( print_r( $response, true ), $localized_message );
773
					}
774
775
					// We want to retry.
776
					if ( $this->is_retryable_error( $response->error ) ) {
777
						if ( $retry ) {
778
							// Don't do anymore retries after this.
779
							if ( 5 <= $this->retry_interval ) {
780
								return $this->process_payment( $order_id, false, $force_save_source, $response->error );
781
							}
782
783
							sleep( $this->retry_interval );
784
785
							$this->retry_interval++;
786
787
							return $this->process_payment( $order_id, true, $force_save_source, $response->error );
788
						} else {
789
							$localized_message = __( 'Sorry, we are unable to process your payment at this time. Please retry later.', 'woocommerce-gateway-stripe' );
790
							$order->add_order_note( $localized_message );
791
							throw new WC_Stripe_Exception( print_r( $response, true ), $localized_message );
792
						}
793
					}
794
795
					$localized_messages = WC_Stripe_Helper::get_localized_messages();
796
797
					if ( 'card_error' === $response->error->type ) {
798
						$localized_message = isset( $localized_messages[ $response->error->code ] ) ? $localized_messages[ $response->error->code ] : $response->error->message;
799
					} else {
800
						$localized_message = isset( $localized_messages[ $response->error->type ] ) ? $localized_messages[ $response->error->type ] : $response->error->message;
801
					}
802
803
					$order->add_order_note( $localized_message );
804
805
					throw new WC_Stripe_Exception( print_r( $response, true ), $localized_message );
806
				}
807
808
				do_action( 'wc_gateway_stripe_process_payment', $response, $order );
809
810
				// Process valid response.
811
				$this->process_response( $response, $order );
812
			} else {
813
				$order->payment_complete();
814
			}
815
816
			// Remove cart.
817
			WC()->cart->empty_cart();
818
819
			// Return thank you page redirect.
820
			return array(
821
				'result'   => 'success',
822
				'redirect' => $this->get_return_url( $order ),
823
			);
824
825
		} catch ( WC_Stripe_Exception $e ) {
826
			wc_add_notice( $e->getLocalizedMessage(), 'error' );
827
			WC_Stripe_Logger::log( 'Error: ' . $e->getMessage() );
828
829
			do_action( 'wc_gateway_stripe_process_payment_error', $e, $order );
830
831
			/* translators: error message */
832
			$order->update_status( 'failed' );
833
834
			if ( $order->has_status( array( 'pending', 'failed' ) ) ) {
835
				$this->send_failed_order_email( $order_id );
836
			}
837
838
			return array(
839
				'result'   => 'fail',
840
				'redirect' => '',
841
			);
842
		}
843
	}
844
845
	/**
846
	 * Displays the Stripe fee
847
	 *
848
	 * @since 4.1.0
849
	 *
850
	 * @param int $order_id
851
	 */
852 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...
853
		if ( apply_filters( 'wc_stripe_hide_display_order_fee', false, $order_id ) ) {
854
			return;
855
		}
856
857
		$order = wc_get_order( $order_id );
858
859
		$fee      = WC_Stripe_Helper::get_stripe_fee( $order );
860
		$currency = WC_Stripe_Helper::get_stripe_currency( $order );
861
862
		if ( ! $fee || ! $currency ) {
863
			return;
864
		}
865
866
		?>
867
868
		<tr>
869
			<td class="label stripe-fee">
870
				<?php echo wc_help_tip( __( 'This represents the fee Stripe collects for the transaction.', 'woocommerce-gateway-stripe' ) ); ?>
871
				<?php esc_html_e( 'Stripe Fee:', 'woocommerce-gateway-stripe' ); ?>
872
			</td>
873
			<td width="1%"></td>
874
			<td class="total">
875
				-&nbsp;<?php echo wc_price( $fee, array( 'currency' => $currency ) ); ?>
876
			</td>
877
		</tr>
878
879
		<?php
880
	}
881
882
	/**
883
	 * Displays the net total of the transaction without the charges of Stripe.
884
	 *
885
	 * @since 4.1.0
886
	 *
887
	 * @param int $order_id
888
	 */
889 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...
890
		if ( apply_filters( 'wc_stripe_hide_display_order_payout', false, $order_id ) ) {
891
			return;
892
		}
893
894
		$order = wc_get_order( $order_id );
895
896
		$net      = WC_Stripe_Helper::get_stripe_net( $order );
897
		$currency = WC_Stripe_Helper::get_stripe_currency( $order );
898
899
		if ( ! $net || ! $currency ) {
900
			return;
901
		}
902
903
		?>
904
905
		<tr>
906
			<td class="label stripe-payout">
907
				<?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' ) ); ?>
908
				<?php esc_html_e( 'Stripe Payout:', 'woocommerce-gateway-stripe' ); ?>
909
			</td>
910
			<td width="1%"></td>
911
			<td class="total">
912
				<?php echo wc_price( $net, array( 'currency' => $currency ) ); ?>
913
			</td>
914
		</tr>
915
916
		<?php
917
	}
918
}
919