Completed
Pull Request — master (#1400)
by
unknown
01:51
created

WC_Gateway_Stripe_Sepa   F

Complexity

Total Complexity 62

Size/Duplication

Total Lines 437
Duplicated Lines 13.27 %

Coupling/Cohesion

Components 2
Dependencies 7

Importance

Changes 0
Metric Value
dl 58
loc 437
rs 3.44
c 0
b 0
f 0
wmc 62
lcom 2
cbo 7

11 Methods

Rating   Name   Duplication   Size   Complexity  
F __construct() 5 58 12
A get_supported_currency() 0 8 1
A is_available() 0 11 4
A get_icon() 0 9 2
A payment_scripts() 0 8 5
A init_form_fields() 0 3 1
A mandate_display() 0 4 1
A form() 0 22 1
C payment_fields() 3 45 12
F process_payment() 50 117 17
A maybe_activate_subscriptions_early() 0 14 6

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_Sepa 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_Sepa, and based on these observations, apply Extract Interface, too.

1
<?php
2
if ( ! defined( 'ABSPATH' ) ) {
3
	exit;
4
}
5
6
/**
7
 * Class that handles SEPA payment method.
8
 *
9
 * @extends WC_Gateway_Stripe
10
 *
11
 * @since 4.0.0
12
 */
13
class WC_Gateway_Stripe_Sepa extends WC_Stripe_Payment_Gateway {
14
	/**
15
	 * The delay between retries.
16
	 *
17
	 * @var int
18
	 */
19
	public $retry_interval;
20
21
	/**
22
	 * Notices (array)
23
	 * @var array
24
	 */
25
	public $notices = array();
26
27
	/**
28
	 * Is test mode active?
29
	 *
30
	 * @var bool
31
	 */
32
	public $testmode;
33
34
	/**
35
	 * Alternate credit card statement name
36
	 *
37
	 * @var bool
38
	 */
39
	public $statement_descriptor;
40
41
	/**
42
	 * API access secret key
43
	 *
44
	 * @var string
45
	 */
46
	public $secret_key;
47
48
	/**
49
	 * Api access publishable key
50
	 *
51
	 * @var string
52
	 */
53
	public $publishable_key;
54
55
	/**
56
	 * Should we store the users credit cards?
57
	 *
58
	 * @var bool
59
	 */
60
	public $saved_cards;
61
62
	/**
63
	 * Pre Orders Object
64
	 *
65
	 * @var object
66
	 */
67
	public $pre_orders;
68
69
	/**
70
	 * Constructor
71
	 */
72
	public function __construct() {
73
		$this->retry_interval = 1;
74
		$this->id             = 'stripe_sepa';
75
		$this->method_title   = __( 'Stripe SEPA Direct Debit', 'woocommerce-gateway-stripe' );
76
		/* translators: link */
77
		$this->method_description = sprintf( __( 'All other general Stripe settings can be adjusted <a href="%s">here</a>.', 'woocommerce-gateway-stripe' ), admin_url( 'admin.php?page=wc-settings&tab=checkout&section=stripe' ) );
78
		$this->has_fields         = true;
79
		$this->supports           = array(
80
			'products',
81
			'refunds',
82
			'tokenization',
83
			'add_payment_method',
84
			'subscriptions',
85
			'subscription_cancellation',
86
			'subscription_suspension',
87
			'subscription_reactivation',
88
			'subscription_amount_changes',
89
			'subscription_date_changes',
90
			'subscription_payment_method_change',
91
			'subscription_payment_method_change_customer',
92
			'subscription_payment_method_change_admin',
93
			'multiple_subscriptions',
94
			'pre-orders',
95
		);
96
97
		// Load the form fields.
98
		$this->init_form_fields();
99
100
		// Load the settings.
101
		$this->init_settings();
102
103
		$main_settings                        = get_option( 'woocommerce_stripe_settings' );
104
		$this->title                          = $this->get_option( 'title' );
105
		$this->description                    = $this->get_option( 'description' );
106
		$this->enabled                        = $this->get_option( 'enabled' );
107
		$this->activate_subscriptions_early   = $this->get_option( 'activate_subscriptions_early' );
108
		$this->testmode                       = ( ! empty( $main_settings['testmode'] ) && 'yes' === $main_settings['testmode'] ) ? true : false;
109
		$this->saved_cards                    = ( ! empty( $main_settings['saved_cards'] ) && 'yes' === $main_settings['saved_cards'] ) ? true : false;
110
		$this->publishable_key                = ! empty( $main_settings['publishable_key'] ) ? $main_settings['publishable_key'] : '';
111
		$this->secret_key                     = ! empty( $main_settings['secret_key'] ) ? $main_settings['secret_key'] : '';
112
		$this->statement_descriptor           = ! empty( $main_settings['statement_descriptor'] ) ? $main_settings['statement_descriptor'] : '';
113
114
		if ( $this->testmode ) {
115
			$this->publishable_key = ! empty( $main_settings['test_publishable_key'] ) ? $main_settings['test_publishable_key'] : '';
116
			$this->secret_key      = ! empty( $main_settings['test_secret_key'] ) ? $main_settings['test_secret_key'] : '';
117
		}
118
119
		add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );
120
		add_action( 'wp_enqueue_scripts', array( $this, 'payment_scripts' ) );
121
        
122
		add_action( 'woocommerce_order_status_changed', array( $this, 'maybe_activate_subscriptions_early'), 10, 4 );
123
124 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...
125
			$this->pre_orders = new WC_Stripe_Pre_Orders_Compat();
126
127
			add_action( 'wc_pre_orders_process_pre_order_completion_payment_' . $this->id, array( $this->pre_orders, 'process_pre_order_release_payment' ) );
128
		}
129
	}
130
131
	/**
132
	 * Returns all supported currencies for this payment method.
133
	 *
134
	 * @since 4.0.0
135
	 * @version 4.0.0
136
	 * @return array
137
	 */
138
	public function get_supported_currency() {
139
		return apply_filters(
140
			'wc_stripe_sepa_supported_currencies',
141
			array(
142
				'EUR',
143
			)
144
		);
145
	}
146
147
	/**
148
	 * Checks to see if all criteria is met before showing payment method.
149
	 *
150
	 * @since 4.0.0
151
	 * @version 4.0.0
152
	 * @return bool
153
	 */
154
	public function is_available() {
155
		if ( ! in_array( get_woocommerce_currency(), $this->get_supported_currency() ) ) {
156
			return false;
157
		}
158
159
		if ( is_add_payment_method_page() && ! $this->saved_cards ) {
160
			return false;
161
		}
162
163
		return parent::is_available();
164
	}
165
166
	/**
167
	 * Get_icon function.
168
	 *
169
	 * @since 1.0.0
170
	 * @version 4.0.0
171
	 * @return string
172
	 */
173
	public function get_icon() {
174
		$icons = $this->payment_icons();
175
176
		$icons_str = '';
177
178
		$icons_str .= isset( $icons['sepa'] ) ? $icons['sepa'] : '';
179
180
		return apply_filters( 'woocommerce_gateway_icon', $icons_str, $this->id );
181
	}
182
183
	/**
184
	 * payment_scripts function.
185
	 *
186
	 * Outputs scripts used for stripe payment
187
	 *
188
	 * @access public
189
	 */
190
	public function payment_scripts() {
191
		if ( ! is_cart() && ! is_checkout() && ! isset( $_GET['pay_for_order'] ) && ! is_add_payment_method_page() ) {
192
			return;
193
		}
194
195
		wp_enqueue_style( 'stripe_styles' );
196
		wp_enqueue_script( 'woocommerce_stripe' );
197
	}
198
199
	/**
200
	 * Initialize Gateway Settings Form Fields.
201
	 */
202
	public function init_form_fields() {
203
		$this->form_fields = require( WC_STRIPE_PLUGIN_PATH . '/includes/admin/stripe-sepa-settings.php' );
204
	}
205
206
	/**
207
	 * Displays the mandate acceptance notice to customer.
208
	 *
209
	 * @since 4.0.0
210
	 * @version 4.0.0
211
	 * @return string
212
	 */
213
	public function mandate_display() {
214
		/* translators: statement descriptor */
215
		printf( __( 'By providing your IBAN and confirming this payment, you are authorizing %s and Stripe, our payment service provider, to send instructions to your bank to debit your account and your bank to debit your account in accordance with those instructions. You are entitled to a refund from your bank under the terms and conditions of your agreement with your bank. A refund must be claimed within 8 weeks starting from the date on which your account was debited.', 'woocommerce-gateway-stripe' ), WC_Stripe_Helper::clean_statement_descriptor( $this->statement_descriptor ) );
0 ignored issues
show
Documentation introduced by
$this->statement_descriptor is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
216
	}
217
218
	/**
219
	 * Renders the Stripe elements form.
220
	 *
221
	 * @since 4.0.0
222
	 * @version 4.0.0
223
	 */
224
	public function form() {
225
		?>
226
		<fieldset id="wc-<?php echo esc_attr( $this->id ); ?>-form" class="wc-payment-form">
227
			<?php do_action( 'woocommerce_credit_card_form_start', $this->id ); ?>
228
			<p class="wc-stripe-sepa-mandate" style="margin-bottom:40px;"><?php $this->mandate_display(); ?></p>
229
			<p class="form-row form-row-wide">
230
				<label for="stripe-iban-element">
231
					<?php esc_html_e( 'IBAN.', 'woocommerce-gateway-stripe' ); ?> <span class="required">*</span>
232
				</label>
233
				<div id="stripe-iban-element" class="wc-stripe-iban-element-field">
234
					<!-- A Stripe Element will be inserted here. -->
235
				</div>
236
			</p>
237
238
			<!-- Used to display form errors -->
239
			<div class="stripe-source-errors" role="alert"></div>
240
			<br />
241
			<?php do_action( 'woocommerce_credit_card_form_end', $this->id ); ?>
242
			<div class="clear"></div>
243
		</fieldset>
244
		<?php
245
	}
246
247
	/**
248
	 * Payment form on checkout page
249
	 */
250
	public function payment_fields() {
251
		global $wp;
252
		$total                = WC()->cart->total;
253
		$display_tokenization = $this->supports( 'tokenization' ) && is_checkout() && $this->saved_cards;
254
		$description          = $this->get_description();
255
		$description          = ! empty( $description ) ? $description : '';
256
257
		// If paying from order, we need to get total from order not cart.
258
		if ( isset( $_GET['pay_for_order'] ) && ! empty( $_GET['key'] ) ) {
259
			$order = wc_get_order( wc_clean( $wp->query_vars['order-pay'] ) );
260
			$total = $order->get_total();
261
		}
262
263
		if ( is_add_payment_method_page() ) {
264
			$total = '';
265
		}
266
267
		echo '<div
268
			id="stripe-sepa_debit-payment-data"
269
			data-amount="' . esc_attr( WC_Stripe_Helper::get_stripe_amount( $total ) ) . '"
270
			data-currency="' . esc_attr( strtolower( get_woocommerce_currency() ) ) . '">';
271
272
		if ( $this->testmode ) {
273
			$description .= ' ' . __( 'TEST MODE ENABLED. In test mode, you can use IBAN number DE89370400440532013000.', 'woocommerce-gateway-stripe' );
274
		}
275
276
		$description = trim( $description );
277
278
		echo apply_filters( 'wc_stripe_description', wpautop( wp_kses_post( $description ) ), $this->id );
279
280
		if ( $display_tokenization ) {
281
			$this->tokenization_script();
282
			$this->saved_payment_methods();
283
		}
284
285
		$this->form();
286
287 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'] ) ) {
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...
288
			$this->save_payment_method_checkbox();
289
		}
290
291
		do_action( 'wc_stripe_sepa_payment_fields', $this->id );
292
293
		echo '</div>';
294
	}
295
296
	/**
297
	 * Process the payment
298
	 *
299
	 * @param int  $order_id Reference.
300
	 * @param bool $retry Should we retry on fail.
301
	 * @param bool $force_save_source Force save the payment source.
302
	 *
303
	 * @throws Exception If payment will not be accepted.
304
	 *
305
	 * @return array|void
306
	 */
307
	public function process_payment( $order_id, $retry = true, $force_save_source = false ) {
308
		try {
309
			$order = wc_get_order( $order_id );
310
311
			if ( $this->maybe_process_pre_orders( $order_id ) ) {
312
				return $this->pre_orders->process_pre_order( $order_id );
313
			}
314
315
			// This comes from the create account checkbox in the checkout page.
316
			$create_account = ! empty( $_POST['createaccount'] ) ? true : false;
317
318
			if ( $create_account ) {
319
				$new_customer_id     = $order->get_customer_id();
320
				$new_stripe_customer = new WC_Stripe_Customer( $new_customer_id );
321
				$new_stripe_customer->create_customer();
322
			}
323
324
			$prepared_source = $this->prepare_source( get_current_user_id(), $force_save_source );
325
326
			$this->save_source_to_order( $order, $prepared_source );
327
328
			// Result from Stripe API request.
329
			$response = null;
330
331
			if ( $order->get_total() > 0 ) {
332
				// This will throw exception if not valid.
333
				$this->validate_minimum_order_amount( $order );
334
335
				WC_Stripe_Logger::log( "Info: Begin processing payment for order $order_id for the amount of {$order->get_total()}" );
336
337
				// Make the request.
338
				$response = WC_Stripe_API::request( $this->generate_payment_request( $order, $prepared_source ) );
339
340 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...
341
					// Customer param wrong? The user may have been deleted on stripe's end. Remove customer_id. Can be retried without.
342
					if ( $this->is_no_such_customer_error( $response->error ) ) {
343
						delete_user_option( $order->get_customer_id(), '_stripe_customer_id' );
344
						$order->delete_meta_data( '_stripe_customer_id' );
345
						$order->save();
346
					}
347
348
					if ( $this->is_no_such_token_error( $response->error ) && $prepared_source->token_id ) {
349
						// Source param wrong? The CARD may have been deleted on stripe's end. Remove token and show message.
350
						$wc_token = WC_Payment_Tokens::get( $prepared_source->token_id );
351
						$wc_token->delete();
352
						$localized_message = __( 'This card is no longer available and has been removed.', 'woocommerce-gateway-stripe' );
353
						$order->add_order_note( $localized_message );
354
						throw new WC_Stripe_Exception( print_r( $response, true ), $localized_message );
355
					}
356
357
					// We want to retry.
358
					if ( $this->is_retryable_error( $response->error ) ) {
359
						if ( $retry ) {
360
							// Don't do anymore retries after this.
361
							if ( 5 <= $this->retry_interval ) {
362
363
								return $this->process_payment( $order_id, false, $force_save_source );
364
							}
365
366
							sleep( $this->retry_interval );
367
368
							$this->retry_interval++;
369
370
							return $this->process_payment( $order_id, true, $force_save_source );
371
						} else {
372
							$localized_message = __( 'Sorry, we are unable to process your payment at this time. Please retry later.', 'woocommerce-gateway-stripe' );
373
							$order->add_order_note( $localized_message );
374
							throw new WC_Stripe_Exception( print_r( $response, true ), $localized_message );
375
						}
376
					}
377
378
					$localized_messages = WC_Stripe_Helper::get_localized_messages();
379
380
					if ( 'card_error' === $response->error->type ) {
381
						$localized_message = isset( $localized_messages[ $response->error->code ] ) ? $localized_messages[ $response->error->code ] : $response->error->message;
382
					} else {
383
						$localized_message = isset( $localized_messages[ $response->error->type ] ) ? $localized_messages[ $response->error->type ] : $response->error->message;
384
					}
385
386
					$order->add_order_note( $localized_message );
387
388
					throw new WC_Stripe_Exception( print_r( $response, true ), $localized_message );
389
				}
390
391
				do_action( 'wc_gateway_stripe_process_payment', $response, $order );
392
393
				// Process valid response.
394
				$this->process_response( $response, $order );
395
			} else {
396
				$order->payment_complete();
397
			}
398
399
			// Remove cart.
400
			WC()->cart->empty_cart();
401
402
			// Return thank you page redirect.
403
			return array(
404
				'result'   => 'success',
405
				'redirect' => $this->get_return_url( $order ),
406
			);
407
408
		} catch ( WC_Stripe_Exception $e ) {
409
			wc_add_notice( $e->getLocalizedMessage(), 'error' );
410
			WC_Stripe_Logger::log( 'Error: ' . $e->getMessage() );
411
412
			do_action( 'wc_gateway_stripe_process_payment_error', $e, $order );
413
414
			if ( $order->has_status( array( 'pending', 'failed' ) ) ) {
415
				$this->send_failed_order_email( $order_id );
416
			}
417
418
			return array(
419
				'result'   => 'fail',
420
				'redirect' => '',
421
			);
422
		}
423
	}
424
    
425
	/**
426
	 * Maybe activate subscriptions early during payment confirmation
427
	 * Process can take up to 7-10 days
428
	 * If order fails, subscription set to On Hold
429
	 *
430
	 * @param $order_id
431
	 * @param $status_from
432
	 * @param $status_to
433
	 * @param $order
434
	 */
435
	function maybe_activate_subscriptions_early ( $order_id, $status_from, $status_to, $order ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
436
		if ( 'yes' !== $this->activate_subscriptions_early || $status_to !== 'on-hold' ) {
437
			return;
438
		}
439
440
		if ( ! wcs_order_contains_subscription( $order, 'any' ) || ! function_exists( 'wcs_order_contains_subscription' ) ) {
441
			return;
442
		}
443
444
		$subscriptions = wcs_get_subscriptions_for_order( $order, array( 'order_type' => array( 'any' ) ) );
445
		foreach( $subscriptions as $subscription ){
446
			$subscription->update_status( 'active' );
447
		}
448
	}
449
}
450