Completed
Push — master ( c62a31...30cf99 )
by Roy
02:13
created

WC_Stripe_Compat   D

Complexity

Total Complexity 126

Size/Duplication

Total Lines 601
Duplicated Lines 21.8 %

Coupling/Cohesion

Components 2
Dependencies 6

Importance

Changes 0
Metric Value
dl 131
loc 601
rs 4.8678
c 0
b 0
f 0
wmc 126
lcom 2
cbo 6

21 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 23 3
A maybe_hide_save_checkbox() 0 7 2
A has_subscription() 0 3 4
A is_subs_change_payment() 0 3 2
A is_pre_order() 0 3 2
C change_subs_payment_method() 10 65 10
A process_payment() 0 14 4
A add_subscription_meta_data() 0 10 2
B save_source_to_order() 20 20 8
D process_subscription_payment() 42 42 9
A delete_resubscribe_meta() 0 7 4
A delete_renewal_meta() 0 5 3
B scheduled_subscription_payment() 18 18 6
A remove_order_source_before_retry() 0 6 2
A remove_order_customer_before_retry() 0 4 2
A update_failing_payment_method() 0 10 2
B add_subscription_payment_meta() 0 26 6
B validate_subscription_payment_meta() 3 19 9
F maybe_render_subscription_payment_method() 0 91 34
B process_pre_order() 38 38 6
B process_pre_order_release_payment() 0 38 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_Stripe_Compat 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_Stripe_Compat, and based on these observations, apply Extract Interface, too.

1
<?php
2
if ( ! defined( 'ABSPATH' ) ) {
3
	exit;
4
}
5
6
/**
7
 * Compatibility class for Subscriptions and Pre-Orders.
8
 *
9
 * @extends WC_Gateway_Stripe
10
 */
11
class WC_Stripe_Compat extends WC_Gateway_Stripe {
12
	/**
13
	 * Constructor
14
	 */
15
	public function __construct() {
16
		parent::__construct();
17
18
		if ( class_exists( 'WC_Subscriptions_Order' ) ) {
19
			add_action( 'woocommerce_scheduled_subscription_payment_' . $this->id, array( $this, 'scheduled_subscription_payment' ), 10, 2 );
20
			add_action( 'wcs_resubscribe_order_created', array( $this, 'delete_resubscribe_meta' ), 10 );
21
			add_action( 'wcs_renewal_order_created', array( $this, 'delete_renewal_meta' ), 10 );
22
			add_action( 'woocommerce_subscription_failing_payment_method_updated_stripe', array( $this, 'update_failing_payment_method' ), 10, 2 );
23
24
			// display the credit card used for a subscription in the "My Subscriptions" table
25
			add_filter( 'woocommerce_my_subscriptions_payment_method', array( $this, 'maybe_render_subscription_payment_method' ), 10, 2 );
26
27
			// allow store managers to manually set Stripe as the payment method on a subscription
28
			add_filter( 'woocommerce_subscription_payment_meta', array( $this, 'add_subscription_payment_meta' ), 10, 2 );
29
			add_filter( 'woocommerce_subscription_validate_payment_meta', array( $this, 'validate_subscription_payment_meta' ), 10, 2 );
30
			add_filter( 'wc_stripe_display_save_payment_method_checkbox', array( $this, 'maybe_hide_save_checkbox' ) );
31
			add_filter( 'wc_stripe_payment_metadata', array( $this, 'add_subscription_meta_data' ), 10, 2 );
32
		}
33
34
		if ( class_exists( 'WC_Pre_Orders_Order' ) ) {
35
			add_action( 'wc_pre_orders_process_pre_order_completion_payment_' . $this->id, array( $this, 'process_pre_order_release_payment' ) );
36
		}
37
	}
38
39
	/**
40
	 * Checks to see if we need to hide the save checkbox field.
41
	 * Because when cart contains a subs product, it will save regardless.
42
	 *
43
	 * @since 4.0.0
44
	 * @version 4.0.0
45
	 */
46
	public function maybe_hide_save_checkbox( $display_tokenization ) {
47
		if ( WC_Subscriptions_Cart::cart_contains_subscription() ) {
48
			return false;
49
		}
50
51
		return $display_tokenization;
52
	}
53
54
	/**
55
	 * Is $order_id a subscription?
56
	 * @param  int  $order_id
57
	 * @return boolean
58
	 */
59
	public function has_subscription( $order_id ) {
60
		return ( function_exists( 'wcs_order_contains_subscription' ) && ( wcs_order_contains_subscription( $order_id ) || wcs_is_subscription( $order_id ) || wcs_order_contains_renewal( $order_id ) ) );
61
	}
62
63
	/**
64
	 * Checks if page is pay for order and change subs payment page.
65
	 *
66
	 * @since 4.0.4
67
	 * @return bool
68
	 */
69
	public function is_subs_change_payment() {
70
		return ( isset( $_GET['pay_for_order'] ) && isset( $_GET['change_payment_method'] ) );
71
	}
72
73
	/**
74
	 * Is $order_id a pre-order?
75
	 * @param  int  $order_id
76
	 * @return boolean
77
	 */
78
	public function is_pre_order( $order_id ) {
79
		return ( class_exists( 'WC_Pre_Orders_Order' ) && WC_Pre_Orders_Order::order_contains_pre_order( $order_id ) );
80
	}
81
82
	/**
83
	 * Process the payment method change for subscriptions.
84
	 *
85
	 * @since 4.0.4
86
	 * @param int $order_id
87
	 */
88
	public function change_subs_payment_method( $order_id ) {
89
		try {
90
			$subscription    = wc_get_order( $order_id );
91
			$source_object   = $this->get_source_object();
92
			$prepared_source = $this->prepare_source( $source_object, get_current_user_id(), true );
93
94
			// Check if we don't allow prepaid credit cards.
95 View Code Duplication
			if ( ! apply_filters( 'wc_stripe_allow_prepaid_card', true ) ) {
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...
96
				if ( $source_object && 'token' === $source_object->object && 'prepaid' === $source_object->card->funding ) {
97
					$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' );
98
					throw new WC_Stripe_Exception( print_r( $source_object, true ), $localized_message );
99
				}
100
			}
101
102 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...
103
				$localized_message = __( 'Payment processing failed. Please retry.', 'woocommerce-gateway-stripe' );
104
				throw new WC_Stripe_Exception( print_r( $prepared_source, true ), $localized_message );
105
			}
106
107
			$this->save_source_to_order( $subscription, $prepared_source );
108
109
			/*
110
			 * Check if card 3DS is required or optional with 3DS setting.
111
			 * Will need to first create 3DS source and require redirection
112
			 * for customer to login to their credit card company.
113
			 * Note that if we need to save source, the original source must be first
114
			 * attached to a customer in Stripe before it can be charged.
115
			 */
116
			if ( $this->is_3ds_required( $source_object ) ) {
117
				$order    = $subscription->get_parent();
118
				$response = $this->create_3ds_source( $order, $source_object, $subscription->get_view_order_url() );
119
120
				if ( ! empty( $response->error ) ) {
121
					$localized_message = $response->error->message;
122
123
					$order->add_order_note( $localized_message );
124
125
					throw new WC_Stripe_Exception( print_r( $response, true ), $localized_message );
126
				}
127
128
				// Update order meta with 3DS source.
129
				if ( WC_Stripe_Helper::is_pre_30() ) {
130
					update_post_meta( $order_id, '_stripe_source_id', $response->id );
131
				} else {
132
					$subscription->update_meta_data( '_stripe_source_id', $response->id );
133
					$subscription->save();
134
				}
135
136
				WC_Stripe_Logger::log( 'Info: Redirecting to 3DS...' );
137
138
				return array(
139
					'result'   => 'success',
140
					'redirect' => esc_url_raw( $response->redirect->url ),
141
				);
142
			}
143
144
			return array(
145
				'result'   => 'success',
146
				'redirect' => $this->get_return_url( $subscription ),
147
			);
148
		} catch ( WC_Stripe_Exception $e ) {
149
			wc_add_notice( $e->getLocalizedMessage(), 'error' );
150
			WC_Stripe_Logger::log( 'Error: ' . $e->getMessage() );
151
		}
152
	}
153
154
	/**
155
	 * Process the payment based on type.
156
	 * @param  int $order_id
157
	 * @return array
158
	 */
159
	public function process_payment( $order_id, $retry = true, $force_save_source = false ) {
160
		if ( $this->has_subscription( $order_id ) ) {
161
			if ( $this->is_subs_change_payment() ) {
162
				return $this->change_subs_payment_method( $order_id );
163
			}
164
165
			// Regular payment with force customer enabled
166
			return parent::process_payment( $order_id, true, true );
167
		} elseif ( $this->is_pre_order( $order_id ) ) {
168
			return $this->process_pre_order( $order_id, $retry, $force_save_source );
169
		} else {
170
			return parent::process_payment( $order_id, $retry, $force_save_source );
171
		}
172
	}
173
174
	/**
175
	 * Adds subscription related meta data on charge request.
176
	 *
177
	 * @since 4.0.0
178
	 * @param array $metadata
179
	 * @param object $order
180
	 */
181
	public function add_subscription_meta_data( $metadata, $order ) {
182
		if ( ! $this->has_subscription( $order->get_id() ) ) {
183
			return $metadata;
184
		}
185
186
		return $metadata += array(
187
			'payment_type'   => 'recurring',
188
			'site_url'       => esc_url( get_site_url() ),
189
		);
190
	}
191
192
	/**
193
	 * Updates other subscription sources.
194
	 *
195
	 * @since 3.1.0
196
	 * @version 4.0.0
197
	 */
198 View Code Duplication
	public function save_source_to_order( $order, $source ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
199
		parent::save_source_to_order( $order, $source );
200
201
		$order_id = WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id();
202
203
		// Also store it on the subscriptions being purchased or paid for in the order
204
		if ( function_exists( 'wcs_order_contains_subscription' ) && wcs_order_contains_subscription( $order_id ) ) {
205
			$subscriptions = wcs_get_subscriptions_for_order( $order_id );
206
		} elseif ( function_exists( 'wcs_order_contains_renewal' ) && wcs_order_contains_renewal( $order_id ) ) {
207
			$subscriptions = wcs_get_subscriptions_for_renewal_order( $order_id );
208
		} else {
209
			$subscriptions = array();
210
		}
211
212
		foreach ( $subscriptions as $subscription ) {
213
			$subscription_id = WC_Stripe_Helper::is_pre_30() ? $subscription->id : $subscription->get_id();
214
			update_post_meta( $subscription_id, '_stripe_customer_id', $source->customer );
215
			update_post_meta( $subscription_id, '_stripe_source_id', $source->source );
216
		}
217
	}
218
219
	/**
220
	 * Process_subscription_payment function.
221
	 *
222
	 * @since 3.0
223
	 * @since 4.0.4 Add third parameter flag to retry.
224
	 * @param float $amount
225
	 * @param mixed $renewal_order
226
	 * @param bool $is_retry Is this a retry process.
227
	 */
228 View Code Duplication
	public function process_subscription_payment( $amount = 0.0, $renewal_order, $is_retry = false ) {
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...
229
		if ( $amount * 100 < WC_Stripe_Helper::get_minimum_amount() ) {
230
			/* translators: minimum amount */
231
			return new WP_Error( 'stripe_error', sprintf( __( 'Sorry, the minimum allowed order total is %1$s to use this payment method.', 'woocommerce-gateway-stripe' ), wc_price( WC_Stripe_Helper::get_minimum_amount() / 100 ) ) );
232
		}
233
234
		$order_id = WC_Stripe_Helper::is_pre_30() ? $renewal_order->id : $renewal_order->get_id();
235
236
		// Get source from order
237
		$prepared_source = $this->prepare_order_source( $renewal_order );
238
239
		if ( ! $prepared_source->customer ) {
240
			return new WP_Error( 'stripe_error', __( 'Customer not found', 'woocommerce-gateway-stripe' ) );
241
		}
242
243
		WC_Stripe_Logger::log( "Info: Begin processing subscription payment for order {$order_id} for the amount of {$amount}" );
244
245
		if ( $is_retry ) {
246
			// Passing empty source with charge customer default.
247
			$prepared_source->source = '';
248
		}
249
250
		$request            = $this->generate_payment_request( $renewal_order, $prepared_source );
251
		$request['capture'] = 'true';
252
		$request['amount']  = WC_Stripe_Helper::get_stripe_amount( $amount, $request['currency'] );
253
		$response           = WC_Stripe_API::request( $request );
254
255
		if ( ! empty( $response->error ) || is_wp_error( $response ) ) {
256
			if ( $is_retry ) {
257
				/* translators: error message */
258
				$renewal_order->update_status( 'failed', sprintf( __( 'Stripe Transaction Failed (%s)', 'woocommerce-gateway-stripe' ), $response->error->message ) );
259
			}
260
261
			return $response; // Default catch all errors.
262
		}
263
264
		$this->process_response( $response, $renewal_order );
265
266
		if ( ! $is_retry ) {
267
			return $response;
268
		}
269
	}
270
271
	/**
272
	 * Don't transfer Stripe customer/token meta to resubscribe orders.
273
	 * @param int $resubscribe_order The order created for the customer to resubscribe to the old expired/cancelled subscription
274
	 */
275
	public function delete_resubscribe_meta( $resubscribe_order ) {
276
		delete_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $resubscribe_order->id : $resubscribe_order->get_id() ), '_stripe_customer_id' );
0 ignored issues
show
Bug introduced by
The method get_id cannot be called on $resubscribe_order (of type integer).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
277
		delete_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $resubscribe_order->id : $resubscribe_order->get_id() ), '_stripe_source_id' );
0 ignored issues
show
Bug introduced by
The method get_id cannot be called on $resubscribe_order (of type integer).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
278
		// For BW compat will remove in future
279
		delete_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $resubscribe_order->id : $resubscribe_order->get_id() ), '_stripe_card_id' );
0 ignored issues
show
Bug introduced by
The method get_id cannot be called on $resubscribe_order (of type integer).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
280
		$this->delete_renewal_meta( $resubscribe_order );
281
	}
282
283
	/**
284
	 * Don't transfer Stripe fee/ID meta to renewal orders.
285
	 * @param int $resubscribe_order The order created for the customer to resubscribe to the old expired/cancelled subscription
0 ignored issues
show
Bug introduced by
There is no parameter named $resubscribe_order. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
286
	 */
287
	public function delete_renewal_meta( $renewal_order ) {
288
		delete_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $renewal_order->id : $renewal_order->get_id() ), 'Stripe Fee' );
289
		delete_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $renewal_order->id : $renewal_order->get_id() ), 'Net Revenue From Stripe' );
290
		return $renewal_order;
291
	}
292
293
	/**
294
	 * Scheduled_subscription_payment function.
295
	 *
296
	 * @param $amount_to_charge float The amount to charge.
297
	 * @param $renewal_order WC_Order A WC_Order object created to record the renewal payment.
298
	 */
299 View Code Duplication
	public function scheduled_subscription_payment( $amount_to_charge, $renewal_order ) {
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...
300
		$response = $this->process_subscription_payment( $amount_to_charge, $renewal_order );
301
302
		if ( is_wp_error( $response ) ) {
303
			/* translators: error message */
304
			$renewal_order->update_status( 'failed', sprintf( __( 'Stripe Transaction Failed (%s)', 'woocommerce-gateway-stripe' ), $response->get_error_message() ) );
305
		}
306
307
		if ( ! empty( $response->error ) ) {
308
			// This is a very generic error to listen for but worth a retry before total fail.
309
			if ( isset( $response->error->type ) && 'invalid_request_error' === $response->error->type && apply_filters( 'wc_stripe_use_default_customer_source', true ) ) {
310
				$this->process_subscription_payment( $amount_to_charge, $renewal_order, true );
311
			} else {
312
				/* translators: error message */
313
				$renewal_order->update_status( 'failed', sprintf( __( 'Stripe Transaction Failed (%s)', 'woocommerce-gateway-stripe' ), $response->error->message ) );
314
			}
315
		}
316
	}
317
318
	/**
319
	 * Remove order meta
320
	 * @param object $order
321
	 */
322
	public function remove_order_source_before_retry( $order ) {
323
		$order_id = WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id();
324
		delete_post_meta( $order_id, '_stripe_source_id' );
325
		// For BW compat will remove in the future.
326
		delete_post_meta( $order_id, '_stripe_card_id' );
327
	}
328
329
	/**
330
	 * Remove order meta
331
	 * @param  object $order
332
	 */
333
	public function remove_order_customer_before_retry( $order ) {
334
		$order_id = WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id();
335
		delete_post_meta( $order_id, '_stripe_customer_id' );
336
	}
337
338
	/**
339
	 * Update the customer_id for a subscription after using Stripe to complete a payment to make up for
340
	 * an automatic renewal payment which previously failed.
341
	 *
342
	 * @access public
343
	 * @param WC_Subscription $subscription The subscription for which the failing payment method relates.
344
	 * @param WC_Order $renewal_order The order which recorded the successful payment (to make up for the failed automatic payment).
345
	 * @return void
346
	 */
347
	public function update_failing_payment_method( $subscription, $renewal_order ) {
348
		if ( WC_Stripe_Helper::is_pre_30() ) {
349
			update_post_meta( $subscription->id, '_stripe_customer_id', $renewal_order->stripe_customer_id );
350
			update_post_meta( $subscription->id, '_stripe_source_id', $renewal_order->stripe_source_id );
351
352
		} else {
353
			update_post_meta( $subscription->get_id(), '_stripe_customer_id', $renewal_order->get_meta( '_stripe_customer_id', true ) );
354
			update_post_meta( $subscription->get_id(), '_stripe_source_id', $renewal_order->get_meta( '_stripe_source_id', true ) );
355
		}
356
	}
357
358
	/**
359
	 * Include the payment meta data required to process automatic recurring payments so that store managers can
360
	 * manually set up automatic recurring payments for a customer via the Edit Subscriptions screen in 2.0+.
361
	 *
362
	 * @since 2.5
363
	 * @param array $payment_meta associative array of meta data required for automatic payments
364
	 * @param WC_Subscription $subscription An instance of a subscription object
365
	 * @return array
366
	 */
367
	public function add_subscription_payment_meta( $payment_meta, $subscription ) {
368
		$source_id = get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->id : $subscription->get_id() ), '_stripe_source_id', true );
369
370
		// For BW compat will remove in future.
371
		if ( empty( $source_id ) ) {
372
			$source_id = get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->id : $subscription->get_id() ), '_stripe_card_id', true );
373
374
			// Take this opportunity to update the key name.
375
			WC_Stripe_Helper::is_pre_30() ? update_post_meta( $subscription->id, '_stripe_source_id', $source_id ) : update_post_meta( $subscription->get_id(), '_stripe_source_id', $source_id );
376
		}
377
378
		$payment_meta[ $this->id ] = array(
379
			'post_meta' => array(
380
				'_stripe_customer_id' => array(
381
					'value' => get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->id : $subscription->get_id() ), '_stripe_customer_id', true ),
382
					'label' => 'Stripe Customer ID',
383
				),
384
				'_stripe_source_id' => array(
385
					'value' => $source_id,
386
					'label' => 'Stripe Source ID',
387
				),
388
			),
389
		);
390
391
		return $payment_meta;
392
	}
393
394
	/**
395
	 * Validate the payment meta data required to process automatic recurring payments so that store managers can
396
	 * manually set up automatic recurring payments for a customer via the Edit Subscriptions screen in 2.0+.
397
	 *
398
	 * @since 2.5
399
	 * @since 4.0.4 Stripe sourd id field no longer needs to be required.
400
	 * @param string $payment_method_id The ID of the payment method to validate
401
	 * @param array $payment_meta associative array of meta data required for automatic payments
402
	 * @return array
403
	 */
404
	public function validate_subscription_payment_meta( $payment_method_id, $payment_meta ) {
405
		if ( $this->id === $payment_method_id ) {
406
407
			if ( ! isset( $payment_meta['post_meta']['_stripe_customer_id']['value'] ) || empty( $payment_meta['post_meta']['_stripe_customer_id']['value'] ) ) {
408
				throw new Exception( __( 'A "Stripe Customer ID" value is required.', 'woocommerce-gateway-stripe' ) );
409 View Code Duplication
			} elseif ( 0 !== strpos( $payment_meta['post_meta']['_stripe_customer_id']['value'], 'cus_' ) ) {
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...
410
				throw new Exception( __( 'Invalid customer ID. A valid "Stripe Customer ID" must begin with "cus_".', 'woocommerce-gateway-stripe' ) );
411
			}
412
413
			if (
414
				( ! empty( $payment_meta['post_meta']['_stripe_source_id']['value'] )
415
				&& 0 !== strpos( $payment_meta['post_meta']['_stripe_source_id']['value'], 'card_' ) )
416
				&& ( ! empty( $payment_meta['post_meta']['_stripe_source_id']['value'] )
417
				&& 0 !== strpos( $payment_meta['post_meta']['_stripe_source_id']['value'], 'src_' ) ) ) {
418
419
				throw new Exception( __( 'Invalid source ID. A valid source "Stripe Source ID" must begin with "src_" or "card_".', 'woocommerce-gateway-stripe' ) );
420
			}
421
		}
422
	}
423
424
	/**
425
	 * Render the payment method used for a subscription in the "My Subscriptions" table
426
	 *
427
	 * @since 1.7.5
428
	 * @param string $payment_method_to_display the default payment method text to display
429
	 * @param WC_Subscription $subscription the subscription details
430
	 * @return string the subscription payment method
431
	 */
432
	public function maybe_render_subscription_payment_method( $payment_method_to_display, $subscription ) {
433
		$customer_user = WC_Stripe_Helper::is_pre_30() ? $subscription->customer_user : $subscription->get_customer_id();
434
435
		// bail for other payment methods
436
		if ( ( WC_Stripe_Helper::is_pre_30() ? $subscription->payment_method : $subscription->get_payment_method() ) !== $this->id || ! $customer_user ) {
437
			return $payment_method_to_display;
438
		}
439
440
		$stripe_source_id = get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->id : $subscription->get_id() ), '_stripe_source_id', true );
441
442
		// For BW compat will remove in future.
443
		if ( empty( $stripe_source_id ) ) {
444
			$stripe_source_id = get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->id : $subscription->get_id() ), '_stripe_card_id', true );
445
446
			// Take this opportunity to update the key name.
447
			WC_Stripe_Helper::is_pre_30() ? update_post_meta( $subscription->id, '_stripe_source_id', $stripe_source_id ) : update_post_meta( $subscription->get_id(), '_stripe_source_id', $stripe_source_id );
448
		}
449
450
		$stripe_customer    = new WC_Stripe_Customer();
451
		$stripe_customer_id = get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->id : $subscription->get_id() ), '_stripe_customer_id', true );
452
453
		// If we couldn't find a Stripe customer linked to the subscription, fallback to the user meta data.
454
		if ( ! $stripe_customer_id || ! is_string( $stripe_customer_id ) ) {
455
			$user_id            = $customer_user;
456
			$stripe_customer_id = get_user_meta( $user_id, '_stripe_customer_id', true );
457
			$stripe_source_id   = get_user_meta( $user_id, '_stripe_source_id', true );
458
459
			// For BW compat will remove in future.
460
			if ( empty( $stripe_source_id ) ) {
461
				$stripe_source_id = get_user_meta( $user_id, '_stripe_card_id', true );
462
463
				// Take this opportunity to update the key name.
464
				update_user_meta( $user_id, '_stripe_source_id', $stripe_source_id );
465
			}
466
		}
467
468
		// If we couldn't find a Stripe customer linked to the account, fallback to the order meta data.
469
		if ( ( ! $stripe_customer_id || ! is_string( $stripe_customer_id ) ) && false !== $subscription->order ) {
470
			$stripe_customer_id = get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->order->id : $subscription->get_parent_id() ), '_stripe_customer_id', true );
471
			$stripe_source_id   = get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->order->id : $subscription->get_parent_id() ), '_stripe_source_id', true );
472
473
			// For BW compat will remove in future.
474
			if ( empty( $stripe_source_id ) ) {
475
				$stripe_source_id = get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->order->id : $subscription->get_parent_id() ), '_stripe_card_id', true );
476
477
				// Take this opportunity to update the key name.
478
				WC_Stripe_Helper::is_pre_30() ? update_post_meta( $subscription->order->id, '_stripe_source_id', $stripe_source_id ) : update_post_meta( $subscription->get_parent_id(), '_stripe_source_id', $stripe_source_id );
479
			}
480
		}
481
482
		$stripe_customer->set_id( $stripe_customer_id );
483
		$sources = $stripe_customer->get_sources();
484
485
		if ( $sources ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $sources of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
486
			$found_source = false;
487
			foreach ( $sources as $source ) {
488
				if ( isset( $source->type ) && 'card' === $source->type ) {
489
					$card = $source->card;
490
				} elseif ( isset( $source->object ) && 'card' === $source->object ) {
491
					$card = $source;
492
				}
493
494
				if ( $source->id === $stripe_source_id ) {
495
					$found_source = true;
496
497
					if ( $card ) {
498
						/* translators: 1) card brand 2) last 4 digits */
499
						$payment_method_to_display = sprintf( __( 'Via %1$s card ending in %2$s', 'woocommerce-gateway-stripe' ), ( isset( $card->brand ) ? $card->brand : __( 'N/A', 'woocommerce-gateway-stripe' ) ), $card->last4 );
0 ignored issues
show
Bug introduced by
The variable $card 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...
500
					} else {
501
						$payment_method_to_display = __( 'N/A', 'woocommerce-gateway-stripe' );
502
					}
503
					break;
504
				}
505
			}
506
507
			if ( ! $found_source ) {
508
				if ( 'card' === $sources[0]->type ) {
509
					$card = $sources[0]->card;
510
				}
511
512
				if ( $card ) {
513
					/* translators: 1) card brand 2) last 4 digits */
514
					$payment_method_to_display = sprintf( __( 'Via %1$s card ending in %2$s', 'woocommerce-gateway-stripe' ), ( isset( $card->brand ) ? $card->brand : __( 'N/A', 'woocommerce-gateway-stripe' ) ), $card->last4 );
515
				} else {
516
					$payment_method_to_display = __( 'N/A', 'woocommerce-gateway-stripe' );
517
				}
518
			}
519
		}
520
521
		return $payment_method_to_display;
522
	}
523
524
	/**
525
	 * Process the pre-order
526
	 * @param int $order_id
527
	 * @return array
528
	 */
529 View Code Duplication
	public function process_pre_order( $order_id, $retry, $force_save_source ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
530
		if ( WC_Pre_Orders_Order::order_requires_payment_tokenization( $order_id ) ) {
531
			try {
532
				$order = wc_get_order( $order_id );
533
534
				if ( $order->get_total() * 100 < WC_Stripe_Helper::get_minimum_amount() ) {
535
					/* translators: minimum amount */
536
					throw new Exception( sprintf( __( 'Sorry, the minimum allowed order total is %1$s to use this payment method.', 'woocommerce-gateway-stripe' ), wc_price( WC_Stripe_Helper::get_minimum_amount() / 100 ) ) );
537
				}
538
539
				$source = $this->prepare_source( $this->get_source_object(), get_current_user_id(), true );
540
541
				// We need a source on file to continue.
542
				if ( empty( $source->customer ) || empty( $source->source ) ) {
543
					throw new Exception( __( 'Unable to store payment details. Please try again.', 'woocommerce-gateway-stripe' ) );
544
				}
545
546
				$this->save_source_to_order( $order, $source );
547
548
				// Remove cart
549
				WC()->cart->empty_cart();
550
551
				// Is pre ordered!
552
				WC_Pre_Orders_Order::mark_order_as_pre_ordered( $order );
553
554
				// Return thank you page redirect
555
				return array(
556
					'result'   => 'success',
557
					'redirect' => $this->get_return_url( $order ),
558
				);
559
			} catch ( Exception $e ) {
560
				wc_add_notice( $e->getMessage(), 'error' );
561
				return;
562
			}
563
		} else {
564
			return parent::process_payment( $order_id, $retry, $force_save_source );
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (process_payment() instead of process_pre_order()). Are you sure this is correct? If so, you might want to change this to $this->process_payment().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
565
		}
566
	}
567
568
	/**
569
	 * Process a pre-order payment when the pre-order is released
570
	 * @param WC_Order $order
571
	 * @return void
572
	 */
573
	public function process_pre_order_release_payment( $order ) {
574
		try {
575
			// Define some callbacks if the first attempt fails.
576
			$retry_callbacks = array(
577
				'remove_order_source_before_retry',
578
				'remove_order_customer_before_retry',
579
			);
580
581
			while ( 1 ) {
582
				$source   = $this->prepare_order_source( $order );
583
				$response = WC_Stripe_API::request( $this->generate_payment_request( $order, $source ) );
584
585
				if ( ! empty( $response->error ) ) {
586
					if ( 0 === sizeof( $retry_callbacks ) ) {
587
						throw new Exception( $response->error->message );
588
					} else {
589
						$retry_callback = array_shift( $retry_callbacks );
590
						call_user_func( array( $this, $retry_callback ), $order );
591
					}
592
				} else {
593
					// Successful
594
					$this->process_response( $response, $order );
595
					break;
596
				}
597
			}
598
		} catch ( Exception $e ) {
599
			/* translators: error message */
600
			$order_note = sprintf( __( 'Stripe Transaction Failed (%s)', 'woocommerce-gateway-stripe' ), $e->getMessage() );
601
602
			// Mark order as failed if not already set,
603
			// otherwise, make sure we add the order note so we can detect when someone fails to check out multiple times
604
			if ( ! $order->has_status( 'failed' ) ) {
605
				$order->update_status( 'failed', $order_note );
606
			} else {
607
				$order->add_order_note( $order_note );
608
			}
609
		}
610
	}
611
}
612