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

WC_Stripe_Sepa_Compat::process_payment()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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