Completed
Push — master ( cb7011...d6e3eb )
by Roy
04:17
created

WC_Stripe_Compat::scheduled_subscription_payment()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 18
Code Lines 9

Duplication

Lines 18
Ratio 100 %

Importance

Changes 0
Metric Value
cc 6
eloc 9
nc 6
nop 2
dl 18
loc 18
rs 8.8571
c 0
b 0
f 0
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
		}
32
33
		if ( class_exists( 'WC_Pre_Orders_Order' ) ) {
34
			add_action( 'wc_pre_orders_process_pre_order_completion_payment_' . $this->id, array( $this, 'process_pre_order_release_payment' ) );
35
		}
36
	}
37
38
	/**
39
	 * Checks to see if we need to hide the save checkbox field.
40
	 * Because when cart contains a subs product, it will save regardless.
41
	 *
42
	 * @since 4.0.0
43
	 * @version 4.0.0
44
	 */
45
	public function maybe_hide_save_checkbox( $display_tokenization ) {
46
		if ( WC_Subscriptions_Cart::cart_contains_subscription() ) {
47
			return false;
48
		}
49
50
		return $display_tokenization;
51
	}
52
53
	/**
54
	 * Is $order_id a subscription?
55
	 * @param  int  $order_id
56
	 * @return boolean
57
	 */
58
	public function has_subscription( $order_id ) {
59
		return ( function_exists( 'wcs_order_contains_subscription' ) && ( wcs_order_contains_subscription( $order_id ) || wcs_is_subscription( $order_id ) || wcs_order_contains_renewal( $order_id ) ) );
60
	}
61
62
	/**
63
	 * Checks if page is pay for order and change subs payment page.
64
	 *
65
	 * @since 4.0.4
66
	 * @return bool
67
	 */
68
	public function is_subs_change_payment() {
69
		return ( isset( $_GET['pay_for_order'] ) && isset( $_GET['change_payment_method'] ) );
70
	}
71
72
	/**
73
	 * Is $order_id a pre-order?
74
	 * @param  int  $order_id
75
	 * @return boolean
76
	 */
77
	public function is_pre_order( $order_id ) {
78
		return ( class_exists( 'WC_Pre_Orders_Order' ) && WC_Pre_Orders_Order::order_contains_pre_order( $order_id ) );
79
	}
80
81
	/**
82
	 * Process the payment method change for subscriptions.
83
	 *
84
	 * @since 4.0.4
85
	 * @param int $order_id
86
	 */
87
	public function change_subs_payment_method( $order_id ) {
88
		try {
89
			$subscription    = wc_get_order( $order_id );
90
			$prepared_source = $this->prepare_source( get_current_user_id(), true );
91
			$source_object   = $prepared_source->source_object;
92
93
			// Check if we don't allow prepaid credit cards.
94 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...
95
				$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' );
96
				throw new WC_Stripe_Exception( print_r( $source_object, true ), $localized_message );
97
			}
98
99 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...
100
				$localized_message = __( 'Payment processing failed. Please retry.', 'woocommerce-gateway-stripe' );
101
				throw new WC_Stripe_Exception( print_r( $prepared_source, true ), $localized_message );
102
			}
103
104
			$this->save_source_to_order( $subscription, $prepared_source );
105
106
			/*
107
			 * Check if card 3DS is required or optional with 3DS setting.
108
			 * Will need to first create 3DS source and require redirection
109
			 * for customer to login to their credit card company.
110
			 * Note that if we need to save source, the original source must be first
111
			 * attached to a customer in Stripe before it can be charged.
112
			 */
113
			if ( $this->is_3ds_required( $source_object ) ) {
114
				$order    = $subscription->get_parent();
115
				$response = $this->create_3ds_source( $order, $source_object, $subscription->get_view_order_url() );
116
117
				if ( ! empty( $response->error ) ) {
118
					$localized_message = $response->error->message;
119
120
					$order->add_order_note( $localized_message );
121
122
					throw new WC_Stripe_Exception( print_r( $response, true ), $localized_message );
123
				}
124
125
				// Update order meta with 3DS source.
126
				if ( WC_Stripe_Helper::is_pre_30() ) {
127
					update_post_meta( $order_id, '_stripe_source_id', $response->id );
128
				} else {
129
					$subscription->update_meta_data( '_stripe_source_id', $response->id );
130
					$subscription->save();
131
				}
132
133
				WC_Stripe_Logger::log( 'Info: Redirecting to 3DS...' );
134
135
				return array(
136
					'result'   => 'success',
137
					'redirect' => esc_url_raw( $response->redirect->url ),
138
				);
139
			}
140
141
			return array(
142
				'result'   => 'success',
143
				'redirect' => $this->get_return_url( $subscription ),
144
			);
145
		} catch ( WC_Stripe_Exception $e ) {
146
			wc_add_notice( $e->getLocalizedMessage(), 'error' );
147
			WC_Stripe_Logger::log( 'Error: ' . $e->getMessage() );
148
		}
149
	}
150
151
	/**
152
	 * Process the payment based on type.
153
	 * @param  int $order_id
154
	 * @return array
155
	 */
156
	public function process_payment( $order_id, $retry = true, $force_save_source = false ) {
157
		if ( $this->has_subscription( $order_id ) ) {
158
			if ( $this->is_subs_change_payment() ) {
159
				return $this->change_subs_payment_method( $order_id );
160
			}
161
162
			// Regular payment with force customer enabled
163
			return parent::process_payment( $order_id, true, true );
164
		} elseif ( $this->is_pre_order( $order_id ) ) {
165
			return $this->process_pre_order( $order_id, $retry, $force_save_source );
166
		} else {
167
			return parent::process_payment( $order_id, $retry, $force_save_source );
168
		}
169
	}
170
171
	/**
172
	 * Updates other subscription sources.
173
	 *
174
	 * @since 3.1.0
175
	 * @version 4.0.0
176
	 */
177 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...
178
		parent::save_source_to_order( $order, $source );
179
180
		$order_id = WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id();
181
182
		// Also store it on the subscriptions being purchased or paid for in the order
183
		if ( function_exists( 'wcs_order_contains_subscription' ) && wcs_order_contains_subscription( $order_id ) ) {
184
			$subscriptions = wcs_get_subscriptions_for_order( $order_id );
185
		} elseif ( function_exists( 'wcs_order_contains_renewal' ) && wcs_order_contains_renewal( $order_id ) ) {
186
			$subscriptions = wcs_get_subscriptions_for_renewal_order( $order_id );
187
		} else {
188
			$subscriptions = array();
189
		}
190
191
		foreach ( $subscriptions as $subscription ) {
192
			$subscription_id = WC_Stripe_Helper::is_pre_30() ? $subscription->id : $subscription->get_id();
193
			update_post_meta( $subscription_id, '_stripe_customer_id', $source->customer );
194
			update_post_meta( $subscription_id, '_stripe_source_id', $source->source );
195
		}
196
	}
197
198
	/**
199
	 * Process_subscription_payment function.
200
	 *
201
	 * @since 3.0
202
	 * @since 4.0.4 Add third parameter flag to retry.
203
	 * @param float $amount
204
	 * @param mixed $renewal_order
205
	 * @param bool $is_retry Is this a retry process.
206
	 */
207 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...
208
		if ( $amount * 100 < WC_Stripe_Helper::get_minimum_amount() ) {
209
			/* translators: minimum amount */
210
			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 ) ) );
211
		}
212
213
		$order_id = WC_Stripe_Helper::is_pre_30() ? $renewal_order->id : $renewal_order->get_id();
214
215
		// Get source from order
216
		$prepared_source = $this->prepare_order_source( $renewal_order );
217
218
		if ( ! $prepared_source->customer ) {
219
			return new WP_Error( 'stripe_error', __( 'Customer not found', 'woocommerce-gateway-stripe' ) );
220
		}
221
222
		WC_Stripe_Logger::log( "Info: Begin processing subscription payment for order {$order_id} for the amount of {$amount}" );
223
224
		if ( $is_retry ) {
225
			// Passing empty source with charge customer default.
226
			$prepared_source->source = '';
227
		}
228
229
		$request            = $this->generate_payment_request( $renewal_order, $prepared_source );
230
		$request['capture'] = 'true';
231
		$request['amount']  = WC_Stripe_Helper::get_stripe_amount( $amount, $request['currency'] );
232
		$response           = WC_Stripe_API::request( $request );
233
234
		if ( ! empty( $response->error ) || is_wp_error( $response ) ) {
235
			if ( $is_retry ) {
236
				/* translators: error message */
237
				$renewal_order->update_status( 'failed', sprintf( __( 'Stripe Transaction Failed (%s)', 'woocommerce-gateway-stripe' ), $response->error->message ) );
238
			}
239
240
			return $response; // Default catch all errors.
241
		}
242
243
		$this->process_response( $response, $renewal_order );
244
245
		if ( ! $is_retry ) {
246
			return $response;
247
		}
248
	}
249
250
	/**
251
	 * Don't transfer Stripe customer/token meta to resubscribe orders.
252
	 * @param int $resubscribe_order The order created for the customer to resubscribe to the old expired/cancelled subscription
253
	 */
254
	public function delete_resubscribe_meta( $resubscribe_order ) {
255
		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...
256
		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...
257
		// For BW compat will remove in future
258
		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...
259
		$this->delete_renewal_meta( $resubscribe_order );
260
	}
261
262
	/**
263
	 * Don't transfer Stripe fee/ID meta to renewal orders.
264
	 * @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...
265
	 */
266
	public function delete_renewal_meta( $renewal_order ) {
267
		delete_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $renewal_order->id : $renewal_order->get_id() ), 'Stripe Fee' );
268
		delete_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $renewal_order->id : $renewal_order->get_id() ), 'Net Revenue From Stripe' );
269
		return $renewal_order;
270
	}
271
272
	/**
273
	 * Scheduled_subscription_payment function.
274
	 *
275
	 * @param $amount_to_charge float The amount to charge.
276
	 * @param $renewal_order WC_Order A WC_Order object created to record the renewal payment.
277
	 */
278 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...
279
		$response = $this->process_subscription_payment( $amount_to_charge, $renewal_order );
280
281
		if ( is_wp_error( $response ) ) {
282
			/* translators: error message */
283
			$renewal_order->update_status( 'failed', sprintf( __( 'Stripe Transaction Failed (%s)', 'woocommerce-gateway-stripe' ), $response->get_error_message() ) );
284
		}
285
286
		if ( ! empty( $response->error ) ) {
287
			// This is a very generic error to listen for but worth a retry before total fail.
288
			if ( isset( $response->error->type ) && 'invalid_request_error' === $response->error->type && apply_filters( 'wc_stripe_use_default_customer_source', true ) ) {
289
				$this->process_subscription_payment( $amount_to_charge, $renewal_order, true );
290
			} else {
291
				/* translators: error message */
292
				$renewal_order->update_status( 'failed', sprintf( __( 'Stripe Transaction Failed (%s)', 'woocommerce-gateway-stripe' ), $response->error->message ) );
293
			}
294
		}
295
	}
296
297
	/**
298
	 * Remove order meta
299
	 * @param object $order
300
	 */
301
	public function remove_order_source_before_retry( $order ) {
302
		$order_id = WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id();
303
		delete_post_meta( $order_id, '_stripe_source_id' );
304
		// For BW compat will remove in the future.
305
		delete_post_meta( $order_id, '_stripe_card_id' );
306
	}
307
308
	/**
309
	 * Remove order meta
310
	 * @param  object $order
311
	 */
312
	public function remove_order_customer_before_retry( $order ) {
313
		$order_id = WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id();
314
		delete_post_meta( $order_id, '_stripe_customer_id' );
315
	}
316
317
	/**
318
	 * Update the customer_id for a subscription after using Stripe to complete a payment to make up for
319
	 * an automatic renewal payment which previously failed.
320
	 *
321
	 * @access public
322
	 * @param WC_Subscription $subscription The subscription for which the failing payment method relates.
323
	 * @param WC_Order $renewal_order The order which recorded the successful payment (to make up for the failed automatic payment).
324
	 * @return void
325
	 */
326
	public function update_failing_payment_method( $subscription, $renewal_order ) {
327
		if ( WC_Stripe_Helper::is_pre_30() ) {
328
			update_post_meta( $subscription->id, '_stripe_customer_id', $renewal_order->stripe_customer_id );
329
			update_post_meta( $subscription->id, '_stripe_source_id', $renewal_order->stripe_source_id );
330
331
		} else {
332
			update_post_meta( $subscription->get_id(), '_stripe_customer_id', $renewal_order->get_meta( '_stripe_customer_id', true ) );
333
			update_post_meta( $subscription->get_id(), '_stripe_source_id', $renewal_order->get_meta( '_stripe_source_id', true ) );
334
		}
335
	}
336
337
	/**
338
	 * Include the payment meta data required to process automatic recurring payments so that store managers can
339
	 * manually set up automatic recurring payments for a customer via the Edit Subscriptions screen in 2.0+.
340
	 *
341
	 * @since 2.5
342
	 * @param array $payment_meta associative array of meta data required for automatic payments
343
	 * @param WC_Subscription $subscription An instance of a subscription object
344
	 * @return array
345
	 */
346
	public function add_subscription_payment_meta( $payment_meta, $subscription ) {
347
		$source_id = get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->id : $subscription->get_id() ), '_stripe_source_id', true );
348
349
		// For BW compat will remove in future.
350
		if ( empty( $source_id ) ) {
351
			$source_id = get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->id : $subscription->get_id() ), '_stripe_card_id', true );
352
353
			// Take this opportunity to update the key name.
354
			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 );
355
		}
356
357
		$payment_meta[ $this->id ] = array(
358
			'post_meta' => array(
359
				'_stripe_customer_id' => array(
360
					'value' => get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->id : $subscription->get_id() ), '_stripe_customer_id', true ),
361
					'label' => 'Stripe Customer ID',
362
				),
363
				'_stripe_source_id' => array(
364
					'value' => $source_id,
365
					'label' => 'Stripe Source ID',
366
				),
367
			),
368
		);
369
370
		return $payment_meta;
371
	}
372
373
	/**
374
	 * Validate the payment meta data required to process automatic recurring payments so that store managers can
375
	 * manually set up automatic recurring payments for a customer via the Edit Subscriptions screen in 2.0+.
376
	 *
377
	 * @since 2.5
378
	 * @since 4.0.4 Stripe sourd id field no longer needs to be required.
379
	 * @param string $payment_method_id The ID of the payment method to validate
380
	 * @param array $payment_meta associative array of meta data required for automatic payments
381
	 * @return array
382
	 */
383 View Code Duplication
	public function validate_subscription_payment_meta( $payment_method_id, $payment_meta ) {
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...
384
		if ( $this->id === $payment_method_id ) {
385
386
			if ( ! isset( $payment_meta['post_meta']['_stripe_customer_id']['value'] ) || empty( $payment_meta['post_meta']['_stripe_customer_id']['value'] ) ) {
387
				throw new Exception( __( 'A "Stripe Customer ID" value is required.', 'woocommerce-gateway-stripe' ) );
388
			} elseif ( 0 !== strpos( $payment_meta['post_meta']['_stripe_customer_id']['value'], 'cus_' ) ) {
389
				throw new Exception( __( 'Invalid customer ID. A valid "Stripe Customer ID" must begin with "cus_".', 'woocommerce-gateway-stripe' ) );
390
			}
391
392
			if (
393
				( ! empty( $payment_meta['post_meta']['_stripe_source_id']['value'] )
394
				&& 0 !== strpos( $payment_meta['post_meta']['_stripe_source_id']['value'], 'card_' ) )
395
				&& ( ! empty( $payment_meta['post_meta']['_stripe_source_id']['value'] )
396
				&& 0 !== strpos( $payment_meta['post_meta']['_stripe_source_id']['value'], 'src_' ) ) ) {
397
398
				throw new Exception( __( 'Invalid source ID. A valid source "Stripe Source ID" must begin with "src_" or "card_".', 'woocommerce-gateway-stripe' ) );
399
			}
400
		}
401
	}
402
403
	/**
404
	 * Render the payment method used for a subscription in the "My Subscriptions" table
405
	 *
406
	 * @since 1.7.5
407
	 * @param string $payment_method_to_display the default payment method text to display
408
	 * @param WC_Subscription $subscription the subscription details
409
	 * @return string the subscription payment method
410
	 */
411
	public function maybe_render_subscription_payment_method( $payment_method_to_display, $subscription ) {
412
		$customer_user = WC_Stripe_Helper::is_pre_30() ? $subscription->customer_user : $subscription->get_customer_id();
413
414
		// bail for other payment methods
415
		if ( ( WC_Stripe_Helper::is_pre_30() ? $subscription->payment_method : $subscription->get_payment_method() ) !== $this->id || ! $customer_user ) {
416
			return $payment_method_to_display;
417
		}
418
419
		$stripe_source_id = get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->id : $subscription->get_id() ), '_stripe_source_id', true );
420
421
		// For BW compat will remove in future.
422
		if ( empty( $stripe_source_id ) ) {
423
			$stripe_source_id = get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->id : $subscription->get_id() ), '_stripe_card_id', true );
424
425
			// Take this opportunity to update the key name.
426
			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 );
427
		}
428
429
		$stripe_customer    = new WC_Stripe_Customer();
430
		$stripe_customer_id = get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->id : $subscription->get_id() ), '_stripe_customer_id', true );
431
432
		// If we couldn't find a Stripe customer linked to the subscription, fallback to the user meta data.
433
		if ( ! $stripe_customer_id || ! is_string( $stripe_customer_id ) ) {
434
			$user_id            = $customer_user;
435
			$stripe_customer_id = get_user_meta( $user_id, '_stripe_customer_id', true );
436
			$stripe_source_id   = get_user_meta( $user_id, '_stripe_source_id', true );
437
438
			// For BW compat will remove in future.
439
			if ( empty( $stripe_source_id ) ) {
440
				$stripe_source_id = get_user_meta( $user_id, '_stripe_card_id', true );
441
442
				// Take this opportunity to update the key name.
443
				update_user_meta( $user_id, '_stripe_source_id', $stripe_source_id );
444
			}
445
		}
446
447
		// If we couldn't find a Stripe customer linked to the account, fallback to the order meta data.
448
		if ( ( ! $stripe_customer_id || ! is_string( $stripe_customer_id ) ) && false !== $subscription->order ) {
449
			$stripe_customer_id = get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->order->id : $subscription->get_parent_id() ), '_stripe_customer_id', true );
450
			$stripe_source_id   = get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->order->id : $subscription->get_parent_id() ), '_stripe_source_id', true );
451
452
			// For BW compat will remove in future.
453
			if ( empty( $stripe_source_id ) ) {
454
				$stripe_source_id = get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->order->id : $subscription->get_parent_id() ), '_stripe_card_id', true );
455
456
				// Take this opportunity to update the key name.
457
				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 );
458
			}
459
		}
460
461
		$stripe_customer->set_id( $stripe_customer_id );
462
		$sources = $stripe_customer->get_sources();
463
464
		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...
465
			$found_source = false;
466
			foreach ( $sources as $source ) {
467
				if ( isset( $source->type ) && 'card' === $source->type ) {
468
					$card = $source->card;
469
				} elseif ( isset( $source->object ) && 'card' === $source->object ) {
470
					$card = $source;
471
				}
472
473
				if ( $source->id === $stripe_source_id ) {
474
					$found_source = true;
475
476
					if ( $card ) {
477
						/* translators: 1) card brand 2) last 4 digits */
478
						$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...
479
					} else {
480
						$payment_method_to_display = __( 'N/A', 'woocommerce-gateway-stripe' );
481
					}
482
					break;
483
				}
484
			}
485
486
			if ( ! $found_source ) {
487
				if ( 'card' === $sources[0]->type ) {
488
					$card = $sources[0]->card;
489
				}
490
491
				if ( $card ) {
492
					/* translators: 1) card brand 2) last 4 digits */
493
					$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 );
494
				} else {
495
					$payment_method_to_display = __( 'N/A', 'woocommerce-gateway-stripe' );
496
				}
497
			}
498
		}
499
500
		return $payment_method_to_display;
501
	}
502
503
	/**
504
	 * Process the pre-order
505
	 * @param int $order_id
506
	 * @return array
507
	 */
508 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...
509
		if ( WC_Pre_Orders_Order::order_requires_payment_tokenization( $order_id ) ) {
510
			try {
511
				$order = wc_get_order( $order_id );
512
513
				if ( $order->get_total() * 100 < WC_Stripe_Helper::get_minimum_amount() ) {
514
					/* translators: minimum amount */
515
					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 ) ) );
516
				}
517
518
				$prepared_source = $this->prepare_source( get_current_user_id(), true );
519
520
				// We need a source on file to continue.
521
				if ( empty( $prepared_source->customer ) || empty( $prepared_source->source ) ) {
522
					throw new Exception( __( 'Unable to store payment details. Please try again.', 'woocommerce-gateway-stripe' ) );
523
				}
524
525
				$this->save_source_to_order( $order, $prepared_source );
526
527
				// Remove cart
528
				WC()->cart->empty_cart();
529
530
				// Is pre ordered!
531
				WC_Pre_Orders_Order::mark_order_as_pre_ordered( $order );
532
533
				// Return thank you page redirect
534
				return array(
535
					'result'   => 'success',
536
					'redirect' => $this->get_return_url( $order ),
537
				);
538
			} catch ( Exception $e ) {
539
				wc_add_notice( $e->getMessage(), 'error' );
540
				return;
541
			}
542
		} else {
543
			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...
544
		}
545
	}
546
547
	/**
548
	 * Process a pre-order payment when the pre-order is released
549
	 * @param WC_Order $order
550
	 * @return void
551
	 */
552
	public function process_pre_order_release_payment( $order ) {
553
		try {
554
			// Define some callbacks if the first attempt fails.
555
			$retry_callbacks = array(
556
				'remove_order_source_before_retry',
557
				'remove_order_customer_before_retry',
558
			);
559
560
			while ( 1 ) {
561
				$source   = $this->prepare_order_source( $order );
562
				$response = WC_Stripe_API::request( $this->generate_payment_request( $order, $source ) );
563
564
				if ( ! empty( $response->error ) ) {
565
					if ( 0 === sizeof( $retry_callbacks ) ) {
566
						throw new Exception( $response->error->message );
567
					} else {
568
						$retry_callback = array_shift( $retry_callbacks );
569
						call_user_func( array( $this, $retry_callback ), $order );
570
					}
571
				} else {
572
					// Successful
573
					$this->process_response( $response, $order );
574
					break;
575
				}
576
			}
577
		} catch ( Exception $e ) {
578
			/* translators: error message */
579
			$order_note = sprintf( __( 'Stripe Transaction Failed (%s)', 'woocommerce-gateway-stripe' ), $e->getMessage() );
580
581
			// Mark order as failed if not already set,
582
			// otherwise, make sure we add the order note so we can detect when someone fails to check out multiple times
583
			if ( ! $order->has_status( 'failed' ) ) {
584
				$order->update_status( 'failed', $order_note );
585
			} else {
586
				$order->add_order_note( $order_note );
587
			}
588
		}
589
	}
590
}
591