Completed
Push — master ( 85d0ea...ee5959 )
by Roy
08:36 queued 02:39
created

WC_Stripe_Compat::is_pre_order()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 2
nc 2
nop 1
dl 0
loc 3
rs 10
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
			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
			// Store source to order meta.
108
			$this->save_source( $subscription, $prepared_source );
109
110
			/*
111
			 * Check if card 3DS is required or optional with 3DS setting.
112
			 * Will need to first create 3DS source and require redirection
113
			 * for customer to login to their credit card company.
114
			 * Note that if we need to save source, the original source must be first
115
			 * attached to a customer in Stripe before it can be charged.
116
			 */
117
			if ( $this->is_3ds_required( $source_object ) ) {
118
				$order    = $subscription->get_parent();
119
				$response = $this->create_3ds_source( $order, $source_object, $subscription->get_view_order_url() );
120
121
				if ( ! empty( $response->error ) ) {
122
					$localized_message = $response->error->message;
123
124
					$order->add_order_note( $localized_message );
125
126
					throw new WC_Stripe_Exception( print_r( $response, true ), $localized_message );
127
				}
128
129
				// Update order meta with 3DS source.
130
				if ( WC_Stripe_Helper::is_pre_30() ) {
131
					update_post_meta( $order_id, '_stripe_source_id', $response->id );
132
				} else {
133
					$subscription->update_meta_data( '_stripe_source_id', $response->id );
134
					$subscription->save();
135
				}
136
137
				WC_Stripe_Logger::log( 'Info: Redirecting to 3DS...' );
138
139
				return array(
140
					'result'   => 'success',
141
					'redirect' => esc_url_raw( $response->redirect->url ),
142
				);
143
			}
144
145
			return array(
146
				'result'   => 'success',
147
				'redirect' => $this->get_return_url( $subscription ),
148
			);
149
		} catch ( WC_Stripe_Exception $e ) {
150
			wc_add_notice( $e->getLocalizedMessage(), 'error' );
151
			WC_Stripe_Logger::log( 'Error: ' . $e->getMessage() );
152
		}
153
	}
154
155
	/**
156
	 * Process the payment based on type.
157
	 * @param  int $order_id
158
	 * @return array
159
	 */
160
	public function process_payment( $order_id, $retry = true, $force_save_source = false ) {
161
		if ( $this->has_subscription( $order_id ) ) {
162
			if ( $this->is_subs_change_payment() ) {
163
				return $this->change_subs_payment_method( $order_id );
164
			}
165
166
			// Regular payment with force customer enabled
167
			return parent::process_payment( $order_id, true, true );
168
		} elseif ( $this->is_pre_order( $order_id ) ) {
169
			return $this->process_pre_order( $order_id, $retry, $force_save_source );
170
		} else {
171
			return parent::process_payment( $order_id, $retry, $force_save_source );
172
		}
173
	}
174
175
	/**
176
	 * Adds subscription related meta data on charge request.
177
	 *
178
	 * @since 4.0.0
179
	 * @param array $metadata
180
	 * @param object $order
181
	 */
182
	public function add_subscription_meta_data( $metadata, $order ) {
183
		if ( ! $this->has_subscription( $order->get_id() ) ) {
184
			return $metadata;
185
		}
186
187
		return $metadata += array(
188
			'payment_type'   => 'recurring',
189
			'site_url'       => esc_url( get_site_url() ),
190
		);
191
	}
192
193
	/**
194
	 * Updates other subscription sources.
195
	 *
196
	 * @since 3.1.0
197
	 * @version 4.0.0
198
	 */
199
	public function save_source( $order, $source ) {
200
		parent::save_source( $order, $source );
201
202
		$order_id = WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id();
203
204
		// Also store it on the subscriptions being purchased or paid for in the order
205
		if ( function_exists( 'wcs_order_contains_subscription' ) && wcs_order_contains_subscription( $order_id ) ) {
206
			$subscriptions = wcs_get_subscriptions_for_order( $order_id );
207
		} elseif ( function_exists( 'wcs_order_contains_renewal' ) && wcs_order_contains_renewal( $order_id ) ) {
208
			$subscriptions = wcs_get_subscriptions_for_renewal_order( $order_id );
209
		} else {
210
			$subscriptions = array();
211
		}
212
213
		foreach ( $subscriptions as $subscription ) {
214
			$subscription_id = WC_Stripe_Helper::is_pre_30() ? $subscription->id : $subscription->get_id();
215
			update_post_meta( $subscription_id, '_stripe_customer_id', $source->customer );
216
			update_post_meta( $subscription_id, '_stripe_source_id', $source->source );
217
		}
218
	}
219
220
	/**
221
	 * process_subscription_payment function.
222
	 * @param float $amount
223
	 * @param mixed $renewal_order
224
	 * @param  bool initial_payment
225
	 */
226 View Code Duplication
	public function process_subscription_payment( $amount = 0.0, $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...
227
		if ( $amount * 100 < WC_Stripe_Helper::get_minimum_amount() ) {
228
			/* translators: minimum amount */
229
			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 ) ) );
230
		}
231
232
		$order_id = WC_Stripe_Helper::is_pre_30() ? $renewal_order->id : $renewal_order->get_id();
233
234
		// Get source from order
235
		$prepared_source = $this->prepare_order_source( $renewal_order );
236
237
		// Or fail :(
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
238
		if ( ! $prepared_source->customer ) {
239
			return new WP_Error( 'stripe_error', __( 'Customer not found', 'woocommerce-gateway-stripe' ) );
240
		}
241
242
		WC_Stripe_Logger::log( "Info: Begin processing subscription payment for order {$order_id} for the amount of {$amount}" );
243
244
		// Make the request
245
		$request            = $this->generate_payment_request( $renewal_order, $prepared_source );
246
		$request['capture'] = 'true';
247
		$request['amount']  = WC_Stripe_Helper::get_stripe_amount( $amount, $request['currency'] );
248
		$response           = WC_Stripe_API::request( $request );
249
250
		// Process valid response
251
		if ( ! empty( $response->error ) ) {
252
			return $response; // Default catch all errors.
253
		}
254
255
		$this->process_response( $response, $renewal_order );
256
257
		return $response;
258
	}
259
260
	/**
261
	 * Process a retry for subscriptions with default source.
262
	 * This is used when renewal failed.
263
	 *
264
	 * @todo refactor to avoid DRY.
265
	 * @param float $amount
266
	 * @param mixed $renewal_order
267
	 * @param  bool initial_payment
268
	 */
269 View Code Duplication
	public function retry_subscription_payment( $amount = 0.0, $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...
270
		if ( $amount * 100 < WC_Stripe_Helper::get_minimum_amount() ) {
271
			/* translators: minimum amount */
272
			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 ) ) );
273
		}
274
275
		$order_id = WC_Stripe_Helper::is_pre_30() ? $renewal_order->id : $renewal_order->get_id();
276
277
		// Get source from order
278
		$prepared_source = $this->prepare_order_source( $renewal_order );
279
280
		if ( ! $prepared_source->customer ) {
281
			return new WP_Error( 'stripe_error', __( 'Customer not found', 'woocommerce-gateway-stripe' ) );
282
		}
283
284
		// Passing empty source with charge customer default.
285
		$prepared_source->source = '';
286
287
		WC_Stripe_Logger::log( "Info: Begin processing subscription payment for order {$order_id} for the amount of {$amount}" );
288
289
		// Make the request
290
		$request             = $this->generate_payment_request( $renewal_order, $prepared_source );
291
		$request['capture']  = 'true';
292
		$request['amount']   = WC_Stripe_Helper::get_stripe_amount( $amount, $request['currency'] );
293
		$response            = WC_Stripe_API::request( $request );
294
295
		if ( ! empty( $response->error ) || is_wp_error( $response ) ) {
296
			/* translators: error message */
297
			$renewal_order->update_status( 'failed', sprintf( __( 'Stripe Transaction Failed (%s)', 'woocommerce-gateway-stripe' ), $response->error->message ) );
298
		}
299
300
		$this->process_response( $response, $renewal_order );
301
	}
302
303
	/**
304
	 * Don't transfer Stripe customer/token meta to resubscribe orders.
305
	 * @param int $resubscribe_order The order created for the customer to resubscribe to the old expired/cancelled subscription
306
	 */
307
	public function delete_resubscribe_meta( $resubscribe_order ) {
308
		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...
309
		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...
310
		// For BW compat will remove in future
311
		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...
312
		$this->delete_renewal_meta( $resubscribe_order );
313
	}
314
315
	/**
316
	 * Don't transfer Stripe fee/ID meta to renewal orders.
317
	 * @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...
318
	 */
319
	public function delete_renewal_meta( $renewal_order ) {
320
		delete_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $renewal_order->id : $renewal_order->get_id() ), 'Stripe Fee' );
321
		delete_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $renewal_order->id : $renewal_order->get_id() ), 'Net Revenue From Stripe' );
322
		return $renewal_order;
323
	}
324
325
	/**
326
	 * scheduled_subscription_payment function.
327
	 *
328
	 * @param $amount_to_charge float The amount to charge.
329
	 * @param $renewal_order WC_Order A WC_Order object created to record the renewal payment.
330
	 */
331 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...
332
		$response = $this->process_subscription_payment( $amount_to_charge, $renewal_order );
333
334
		if ( is_wp_error( $response ) ) {
335
			/* translators: error message */
336
			$renewal_order->update_status( 'failed', sprintf( __( 'Stripe Transaction Failed (%s)', 'woocommerce-gateway-stripe' ), $response->get_error_message() ) );
337
		}
338
339
		if ( ! empty( $response->error ) ) {
340
			// This is a very generic error to listen for but worth a retry before total fail.
341
			if ( isset( $response->error->type ) && 'invalid_request_error' === $response->error->type && apply_filters( 'wc_stripe_use_default_customer_source', true ) ) {
342
				$this->retry_subscription_payment( $amount_to_charge, $renewal_order );
343
			} else {
344
				/* translators: error message */
345
				$renewal_order->update_status( 'failed', sprintf( __( 'Stripe Transaction Failed (%s)', 'woocommerce-gateway-stripe' ), $response->error->message ) );
346
			}
347
		}
348
	}
349
350
	/**
351
	 * Remove order meta
352
	 * @param object $order
353
	 */
354
	public function remove_order_source_before_retry( $order ) {
355
		$order_id = WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id();
356
		delete_post_meta( $order_id, '_stripe_source_id' );
357
		// For BW compat will remove in the future.
358
		delete_post_meta( $order_id, '_stripe_card_id' );
359
	}
360
361
	/**
362
	 * Remove order meta
363
	 * @param  object $order
364
	 */
365
	public function remove_order_customer_before_retry( $order ) {
366
		$order_id = WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id();
367
		delete_post_meta( $order_id, '_stripe_customer_id' );
368
	}
369
370
	/**
371
	 * Update the customer_id for a subscription after using Stripe to complete a payment to make up for
372
	 * an automatic renewal payment which previously failed.
373
	 *
374
	 * @access public
375
	 * @param WC_Subscription $subscription The subscription for which the failing payment method relates.
376
	 * @param WC_Order $renewal_order The order which recorded the successful payment (to make up for the failed automatic payment).
377
	 * @return void
378
	 */
379
	public function update_failing_payment_method( $subscription, $renewal_order ) {
380
		if ( WC_Stripe_Helper::is_pre_30() ) {
381
			update_post_meta( $subscription->id, '_stripe_customer_id', $renewal_order->stripe_customer_id );
382
			update_post_meta( $subscription->id, '_stripe_source_id', $renewal_order->stripe_source_id );
383
384
		} else {
385
			update_post_meta( $subscription->get_id(), '_stripe_customer_id', $renewal_order->get_meta( '_stripe_customer_id', true ) );
386
			update_post_meta( $subscription->get_id(), '_stripe_source_id', $renewal_order->get_meta( '_stripe_source_id', true ) );
387
		}
388
	}
389
390
	/**
391
	 * Include the payment meta data required to process automatic recurring payments so that store managers can
392
	 * manually set up automatic recurring payments for a customer via the Edit Subscriptions screen in 2.0+.
393
	 *
394
	 * @since 2.5
395
	 * @param array $payment_meta associative array of meta data required for automatic payments
396
	 * @param WC_Subscription $subscription An instance of a subscription object
397
	 * @return array
398
	 */
399
	public function add_subscription_payment_meta( $payment_meta, $subscription ) {
400
		$source_id = get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->id : $subscription->get_id() ), '_stripe_source_id', true );
401
402
		// For BW compat will remove in future.
403
		if ( empty( $source_id ) ) {
404
			$source_id = get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->id : $subscription->get_id() ), '_stripe_card_id', true );
405
406
			// Take this opportunity to update the key name.
407
			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 );
408
		}
409
410
		$payment_meta[ $this->id ] = array(
411
			'post_meta' => array(
412
				'_stripe_customer_id' => array(
413
					'value' => get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->id : $subscription->get_id() ), '_stripe_customer_id', true ),
414
					'label' => 'Stripe Customer ID',
415
				),
416
				'_stripe_source_id' => array(
417
					'value' => $source_id,
418
					'label' => 'Stripe Source ID',
419
				),
420
			),
421
		);
422
423
		return $payment_meta;
424
	}
425
426
	/**
427
	 * Validate the payment meta data required to process automatic recurring payments so that store managers can
428
	 * manually set up automatic recurring payments for a customer via the Edit Subscriptions screen in 2.0+.
429
	 *
430
	 * @since 2.5
431
	 * @since 4.0.4 Stripe sourd id field no longer needs to be required.
432
	 * @param string $payment_method_id The ID of the payment method to validate
433
	 * @param array $payment_meta associative array of meta data required for automatic payments
434
	 * @return array
435
	 */
436
	public function validate_subscription_payment_meta( $payment_method_id, $payment_meta ) {
437
		if ( $this->id === $payment_method_id ) {
438
439
			if ( ! isset( $payment_meta['post_meta']['_stripe_customer_id']['value'] ) || empty( $payment_meta['post_meta']['_stripe_customer_id']['value'] ) ) {
440
				throw new Exception( __( 'A "Stripe Customer ID" value is required.', 'woocommerce-gateway-stripe' ) );
441 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...
442
				throw new Exception( __( 'Invalid customer ID. A valid "Stripe Customer ID" must begin with "cus_".', 'woocommerce-gateway-stripe' ) );
443
			}
444
445
			if (
446
				( ! empty( $payment_meta['post_meta']['_stripe_source_id']['value'] )
447
				&& 0 !== strpos( $payment_meta['post_meta']['_stripe_source_id']['value'], 'card_' ) )
448
				&& ( ! empty( $payment_meta['post_meta']['_stripe_source_id']['value'] )
449
				&& 0 !== strpos( $payment_meta['post_meta']['_stripe_source_id']['value'], 'src_' ) ) ) {
450
451
				throw new Exception( __( 'Invalid source ID. A valid source "Stripe Source ID" must begin with "src_" or "card_".', 'woocommerce-gateway-stripe' ) );
452
			}
453
		}
454
	}
455
456
	/**
457
	 * Render the payment method used for a subscription in the "My Subscriptions" table
458
	 *
459
	 * @since 1.7.5
460
	 * @param string $payment_method_to_display the default payment method text to display
461
	 * @param WC_Subscription $subscription the subscription details
462
	 * @return string the subscription payment method
463
	 */
464
	public function maybe_render_subscription_payment_method( $payment_method_to_display, $subscription ) {
465
		$customer_user = WC_Stripe_Helper::is_pre_30() ? $subscription->customer_user : $subscription->get_customer_id();
466
467
		// bail for other payment methods
468
		if ( ( WC_Stripe_Helper::is_pre_30() ? $subscription->payment_method : $subscription->get_payment_method() ) !== $this->id || ! $customer_user ) {
469
			return $payment_method_to_display;
470
		}
471
472
		$stripe_source_id = get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->id : $subscription->get_id() ), '_stripe_source_id', true );
473
474
		// For BW compat will remove in future.
475
		if ( empty( $stripe_source_id ) ) {
476
			$stripe_source_id = get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->id : $subscription->get_id() ), '_stripe_card_id', true );
477
478
			// Take this opportunity to update the key name.
479
			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 );
480
		}
481
482
		$stripe_customer    = new WC_Stripe_Customer();
483
		$stripe_customer_id = get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->id : $subscription->get_id() ), '_stripe_customer_id', true );
484
485
		// If we couldn't find a Stripe customer linked to the subscription, fallback to the user meta data.
486
		if ( ! $stripe_customer_id || ! is_string( $stripe_customer_id ) ) {
487
			$user_id            = $customer_user;
488
			$stripe_customer_id = get_user_meta( $user_id, '_stripe_customer_id', true );
489
			$stripe_source_id   = get_user_meta( $user_id, '_stripe_source_id', true );
490
491
			// For BW compat will remove in future.
492
			if ( empty( $stripe_source_id ) ) {
493
				$stripe_source_id = get_user_meta( $user_id, '_stripe_card_id', true );
494
495
				// Take this opportunity to update the key name.
496
				update_user_meta( $user_id, '_stripe_source_id', $stripe_source_id );
497
			}
498
		}
499
500
		// If we couldn't find a Stripe customer linked to the account, fallback to the order meta data.
501
		if ( ( ! $stripe_customer_id || ! is_string( $stripe_customer_id ) ) && false !== $subscription->order ) {
502
			$stripe_customer_id = get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->order->id : $subscription->get_parent_id() ), '_stripe_customer_id', true );
503
			$stripe_source_id   = get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->order->id : $subscription->get_parent_id() ), '_stripe_source_id', true );
504
505
			// For BW compat will remove in future.
506
			if ( empty( $stripe_source_id ) ) {
507
				$stripe_source_id = get_post_meta( ( WC_Stripe_Helper::is_pre_30() ? $subscription->order->id : $subscription->get_parent_id() ), '_stripe_card_id', true );
508
509
				// Take this opportunity to update the key name.
510
				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 );
511
			}
512
		}
513
514
		$stripe_customer->set_id( $stripe_customer_id );
515
		$sources = $stripe_customer->get_sources();
516
517
		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...
518
			$found_source = false;
519
			foreach ( $sources as $source ) {
520
				if ( isset( $source->type ) && 'card' === $source->type ) {
521
					$card = $source->card;
522
				} elseif ( isset( $source->object ) && 'card' === $source->object ) {
523
					$card = $source;
524
				}
525
526
				if ( $source->id === $stripe_source_id ) {
527
					$found_source = true;
528
529
					if ( $card ) {
530
						/* translators: 1) card brand 2) last 4 digits */
531
						$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...
532
					} else {
533
						$payment_method_to_display = __( 'N/A', 'woocommerce-gateway-stripe' );
534
					}
535
					break;
536
				}
537
			}
538
539
			if ( ! $found_source ) {
540
				if ( 'card' === $sources[0]->type ) {
541
					$card = $sources[0]->card;
542
				}
543
544
				if ( $card ) {
545
					/* translators: 1) card brand 2) last 4 digits */
546
					$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 );
547
				} else {
548
					$payment_method_to_display = __( 'N/A', 'woocommerce-gateway-stripe' );
549
				}
550
			}
551
		}
552
553
		return $payment_method_to_display;
554
	}
555
556
	/**
557
	 * Process the pre-order
558
	 * @param int $order_id
559
	 * @return array
560
	 */
561 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...
562
		if ( WC_Pre_Orders_Order::order_requires_payment_tokenization( $order_id ) ) {
563
			try {
564
				$order = wc_get_order( $order_id );
565
566
				if ( $order->get_total() * 100 < WC_Stripe_Helper::get_minimum_amount() ) {
567
					/* translators: minimum amount */
568
					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 ) ) );
569
				}
570
571
				$source = $this->prepare_source( $this->get_source_object(), get_current_user_id(), true );
572
573
				// We need a source on file to continue.
574
				if ( empty( $source->customer ) || empty( $source->source ) ) {
575
					throw new Exception( __( 'Unable to store payment details. Please try again.', 'woocommerce-gateway-stripe' ) );
576
				}
577
578
				// Store source to order meta
579
				$this->save_source( $order, $source );
580
581
				// Remove cart
582
				WC()->cart->empty_cart();
583
584
				// Is pre ordered!
585
				WC_Pre_Orders_Order::mark_order_as_pre_ordered( $order );
586
587
				// Return thank you page redirect
588
				return array(
589
					'result'   => 'success',
590
					'redirect' => $this->get_return_url( $order ),
591
				);
592
			} catch ( Exception $e ) {
593
				wc_add_notice( $e->getMessage(), 'error' );
594
				return;
595
			}
596
		} else {
597
			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...
598
		}
599
	}
600
601
	/**
602
	 * Process a pre-order payment when the pre-order is released
603
	 * @param WC_Order $order
604
	 * @return void
605
	 */
606
	public function process_pre_order_release_payment( $order ) {
607
		try {
608
			// Define some callbacks if the first attempt fails.
609
			$retry_callbacks = array(
610
				'remove_order_source_before_retry',
611
				'remove_order_customer_before_retry',
612
			);
613
614
			while ( 1 ) {
615
				$source   = $this->prepare_order_source( $order );
616
				$response = WC_Stripe_API::request( $this->generate_payment_request( $order, $source ) );
617
618
				if ( ! empty( $response->error ) ) {
619
					if ( 0 === sizeof( $retry_callbacks ) ) {
620
						throw new Exception( $response->error->message );
621
					} else {
622
						$retry_callback = array_shift( $retry_callbacks );
623
						call_user_func( array( $this, $retry_callback ), $order );
624
					}
625
				} else {
626
					// Successful
627
					$this->process_response( $response, $order );
628
					break;
629
				}
630
			}
631
		} catch ( Exception $e ) {
632
			/* translators: error message */
633
			$order_note = sprintf( __( 'Stripe Transaction Failed (%s)', 'woocommerce-gateway-stripe' ), $e->getMessage() );
634
635
			// Mark order as failed if not already set,
636
			// otherwise, make sure we add the order note so we can detect when someone fails to check out multiple times
637
			if ( ! $order->has_status( 'failed' ) ) {
638
				$order->update_status( 'failed', $order_note );
639
			} else {
640
				$order->add_order_note( $order_note );
641
			}
642
		}
643
	}
644
}
645