Completed
Push — master ( 27023b...4be843 )
by Roy
03:42 queued 01:29
created

change_subs_payment_method()   A

Complexity

Conditions 5
Paths 11

Size

Total Lines 30

Duplication

Lines 8
Ratio 26.67 %

Importance

Changes 0
Metric Value
cc 5
nc 11
nop 1
dl 8
loc 30
rs 9.1288
c 0
b 0
f 0
1
<?php
2
if ( ! defined( 'ABSPATH' ) ) {
3
	exit;
4
}
5
6
/**
7
 * Compatibility class for Subscriptions.
8
 *
9
 * @extends WC_Gateway_Stripe
10
 */
11
class WC_Stripe_Subs_Compat extends WC_Gateway_Stripe {
12
	/**
13
	 * Constructor
14
	 */
15 View Code Duplication
	public function __construct() {
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...
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
			add_action( 'wc_stripe_cards_payment_fields', array( $this, 'display_update_subs_payment_checkout' ) );
24
			add_action( 'wc_stripe_add_payment_method_' . $this->id . '_success', array( $this, 'handle_add_payment_method_success' ), 10, 2 );
25
26
			// display the credit card used for a subscription in the "My Subscriptions" table
27
			add_filter( 'woocommerce_my_subscriptions_payment_method', array( $this, 'maybe_render_subscription_payment_method' ), 10, 2 );
28
29
			// allow store managers to manually set Stripe as the payment method on a subscription
30
			add_filter( 'woocommerce_subscription_payment_meta', array( $this, 'add_subscription_payment_meta' ), 10, 2 );
31
			add_filter( 'woocommerce_subscription_validate_payment_meta', array( $this, 'validate_subscription_payment_meta' ), 10, 2 );
32
			add_filter( 'wc_stripe_display_save_payment_method_checkbox', array( $this, 'maybe_hide_save_checkbox' ) );
33
		}
34
	}
35
36
	/**
37
	 * Handles the return from processing the payment for Stripe Checkout.
38
	 *
39
	 * @since 4.1.0
40
	 */
41
	public function stripe_checkout_return_handler() {
42
		return parent::stripe_checkout_return_handler();
43
	}
44
45
	/**
46
	 * Checks to see if we need to hide the save checkbox field.
47
	 * Because when cart contains a subs product, it will save regardless.
48
	 *
49
	 * @since 4.0.0
50
	 * @version 4.0.0
51
	 */
52
	public function maybe_hide_save_checkbox( $display_tokenization ) {
53
		if ( WC_Subscriptions_Cart::cart_contains_subscription() ) {
54
			return false;
55
		}
56
57
		return $display_tokenization;
58
	}
59
60
	/**
61
	 * Is $order_id a subscription?
62
	 * @param  int  $order_id
63
	 * @return boolean
64
	 */
65
	public function has_subscription( $order_id ) {
66
		return ( function_exists( 'wcs_order_contains_subscription' ) && ( wcs_order_contains_subscription( $order_id ) || wcs_is_subscription( $order_id ) || wcs_order_contains_renewal( $order_id ) ) );
67
	}
68
69
	/**
70
	 * Checks if page is pay for order and change subs payment page.
71
	 *
72
	 * @since 4.0.4
73
	 * @return bool
74
	 */
75
	public function is_subs_change_payment() {
76
		return ( isset( $_GET['pay_for_order'] ) && isset( $_GET['change_payment_method'] ) );
77
	}
78
79
	/**
80
	 * Displays a checkbox to allow users to update all subs payments with new
81
	 * payment.
82
	 *
83
	 * @since 4.1.11
84
	 */
85 View Code Duplication
	public function display_update_subs_payment_checkout() {
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...
86
		if (
87
			apply_filters( 'wc_stripe_display_update_subs_payment_method_card_checkbox', true ) &&
88
			wcs_user_has_subscription( get_current_user_id(), '', 'active' ) &&
89
			is_add_payment_method_page()
90
		) {
91
			printf(
92
				'<p class="form-row">
93
					<input id="wc-%1$s-update-subs-payment-method-card" name="wc-%1$s-update-subs-payment-method-card" type="checkbox" value="true" style="width:auto;" />
94
					<label for="wc-%1$s-update-subs-payment-method-card" style="display:inline;">%2$s</label>
95
				</p>',
96
				esc_attr( $this->id ),
97
				esc_html( apply_filters( 'wc_stripe_save_to_subs_text', __( 'Update the Payment Method used for all of my active subscriptions (optional).', 'woocommerce-gateway-stripe' ) ) )
98
			);
99
		}
100
	}
101
102
	/**
103
	 * Updates all active subscriptions payment method.
104
	 *
105
	 * @since 4.1.11
106
	 * @param string $source_id
107
	 * @param object $source_object
108
	 */
109 View Code Duplication
	public function handle_add_payment_method_success( $source_id, $source_object ) {
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...
110
		if ( isset( $_POST['wc-' . $this->id . '-update-subs-payment-method-card'] ) ) {
111
			$all_subs = wcs_get_users_subscriptions();
112
113
			if ( ! empty( $all_subs ) ) {
114
				foreach ( $all_subs as $sub ) {
115
					if ( 'active' === $sub->get_status() ) {
116
						update_post_meta( $sub->get_id(), '_stripe_source_id', $source_id );
117
						update_post_meta( $sub->get_id(), '_payment_method', $this->id );
118
						update_post_meta( $sub->get_id(), '_payment_method_title', $this->method_title );
119
					}
120
				}
121
			}
122
		}
123
	}
124
125
	/**
126
	 * Process the payment method change for subscriptions.
127
	 *
128
	 * @since 4.0.4
129
	 * @since 4.1.11 Remove 3DS check as it is not needed.
130
	 * @param int $order_id
131
	 */
132
	public function change_subs_payment_method( $order_id ) {
133
		try {
134
			$subscription    = wc_get_order( $order_id );
135
			$prepared_source = $this->prepare_source( get_current_user_id(), true );
136
			$source_object   = $prepared_source->source_object;
137
138
			// Check if we don't allow prepaid credit cards.
139 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...
140
				$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' );
141
				throw new WC_Stripe_Exception( print_r( $source_object, true ), $localized_message );
142
			}
143
144 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...
145
				$localized_message = __( 'Payment processing failed. Please retry.', 'woocommerce-gateway-stripe' );
146
				throw new WC_Stripe_Exception( print_r( $prepared_source, true ), $localized_message );
147
			}
148
149
			$this->save_source_to_order( $subscription, $prepared_source );
150
 
151
			do_action( 'wc_stripe_change_subs_payment_method_success', $prepared_source->source, $prepared_source );
152
153
			return array(
154
				'result'   => 'success',
155
				'redirect' => $this->get_return_url( $subscription ),
156
			);
157
		} catch ( WC_Stripe_Exception $e ) {
158
			wc_add_notice( $e->getLocalizedMessage(), 'error' );
159
			WC_Stripe_Logger::log( 'Error: ' . $e->getMessage() );
160
		}
161
	}
162
163
	/**
164
	 * Process the payment based on type.
165
	 * @param  int $order_id
166
	 * @return array
167
	 */
168 View Code Duplication
	public function process_payment( $order_id, $retry = true, $force_save_source = false, $previous_error = 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...
169
		if ( $this->has_subscription( $order_id ) ) {
170
			if ( $this->is_subs_change_payment() ) {
171
				return $this->change_subs_payment_method( $order_id );
172
			}
173
174
			// Regular payment with force customer enabled
175
			return parent::process_payment( $order_id, $retry, true, $previous_error );
176
		} else {
177
			return parent::process_payment( $order_id, $retry, $force_save_source, $previous_error );
178
		}
179
	}
180
181
	/**
182
	 * Scheduled_subscription_payment function.
183
	 *
184
	 * @param $amount_to_charge float The amount to charge.
185
	 * @param $renewal_order WC_Order A WC_Order object created to record the renewal payment.
186
	 */
187
	public function scheduled_subscription_payment( $amount_to_charge, $renewal_order ) {
188
		$this->process_subscription_payment( $amount_to_charge, $renewal_order, true, false );
189
	}
190
191
	/**
192
	 * Process_subscription_payment function.
193
	 *
194
	 * @since 3.0
195
	 * @since 4.0.4 Add third parameter flag to retry.
196
	 * @since 4.1.0 Add fourth parameter to log previous errors.
197
	 * @param float $amount
198
	 * @param mixed $renewal_order
199
	 * @param bool $retry Should we retry the process?
200
	 * @param object $previous_error
201
	 */
202 View Code Duplication
	public function process_subscription_payment( $amount = 0.0, $renewal_order, $retry = true, $previous_error ) {
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...
203
		try {
204
			if ( $amount * 100 < WC_Stripe_Helper::get_minimum_amount() ) {
205
				/* translators: minimum amount */
206
				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 ) ) );
207
			}
208
209
			$order_id = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $renewal_order->id : $renewal_order->get_id();
210
211
			// Get source from order
212
			$prepared_source = $this->prepare_order_source( $renewal_order );
213
			$source_object   = $prepared_source->source_object;
214
215
			if ( ! $prepared_source->customer ) {
216
				return new WP_Error( 'stripe_error', __( 'Customer not found', 'woocommerce-gateway-stripe' ) );
217
			}
218
219
			WC_Stripe_Logger::log( "Info: Begin processing subscription payment for order {$order_id} for the amount of {$amount}" );
220
221
			/* If we're doing a retry and source is chargeable, we need to pass
222
			 * a different idempotency key and retry for success.
223
			 */
224
			if ( is_object( $source_object ) && empty( $source_object->error ) && $this->need_update_idempotency_key( $source_object, $previous_error ) ) {
225
				add_filter( 'wc_stripe_idempotency_key', array( $this, 'change_idempotency_key' ), 10, 2 );
226
			}
227
228
			if ( ( $this->is_no_such_source_error( $previous_error ) || $this->is_no_linked_source_error( $previous_error ) ) && apply_filters( 'wc_stripe_use_default_customer_source', true ) ) {
229
				// Passing empty source will charge customer default.
230
				$prepared_source->source = '';
231
			}
232
233
			$request            = $this->generate_payment_request( $renewal_order, $prepared_source );
234
			$request['capture'] = 'true';
235
			$request['amount']  = WC_Stripe_Helper::get_stripe_amount( $amount, $request['currency'] );
236
			$response           = WC_Stripe_API::request( $request );
237
238
			if ( ! empty( $response->error ) ) {
239
				// We want to retry.
240
				if ( $this->is_retryable_error( $response->error ) ) {
241
					if ( $retry ) {
242
						// Don't do anymore retries after this.
243
						if ( 5 <= $this->retry_interval ) {
244
							return $this->process_subscription_payment( $amount, $renewal_order, false, $response->error );
245
						}
246
247
						sleep( $this->retry_interval );
248
249
						$this->retry_interval++;
250
251
						return $this->process_subscription_payment( $amount, $renewal_order, true, $response->error );
252
					} else {
253
						$localized_message = __( 'Sorry, we are unable to process your payment at this time. Please retry later.', 'woocommerce-gateway-stripe' );
254
						$renewal_order->add_order_note( $localized_message );
255
						throw new WC_Stripe_Exception( print_r( $response, true ), $localized_message );
256
					}
257
				}
258
259
				$localized_messages = WC_Stripe_Helper::get_localized_messages();
260
261
				if ( 'card_error' === $response->error->type ) {
262
					$localized_message = isset( $localized_messages[ $response->error->code ] ) ? $localized_messages[ $response->error->code ] : $response->error->message;
263
				} else {
264
					$localized_message = isset( $localized_messages[ $response->error->type ] ) ? $localized_messages[ $response->error->type ] : $response->error->message;
265
				}
266
267
				$renewal_order->add_order_note( $localized_message );
268
269
				throw new WC_Stripe_Exception( print_r( $response, true ), $localized_message );
270
			}
271
272
			do_action( 'wc_gateway_stripe_process_payment', $response, $renewal_order );
273
274
			$this->process_response( $response, $renewal_order );
275
		} catch ( WC_Stripe_Exception $e ) {
276
			WC_Stripe_Logger::log( 'Error: ' . $e->getMessage() );
277
278
			do_action( 'wc_gateway_stripe_process_payment_error', $e, $renewal_order );
279
280
			/* translators: error message */
281
			$renewal_order->update_status( 'failed' );
282
		}
283
	}
284
285
	/**
286
	 * Updates other subscription sources.
287
	 *
288
	 * @since 3.1.0
289
	 * @version 4.0.0
290
	 */
291 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...
292
		parent::save_source_to_order( $order, $source );
293
294
		$order_id = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $order->id : $order->get_id();
295
296
		// Also store it on the subscriptions being purchased or paid for in the order
297
		if ( function_exists( 'wcs_order_contains_subscription' ) && wcs_order_contains_subscription( $order_id ) ) {
298
			$subscriptions = wcs_get_subscriptions_for_order( $order_id );
299
		} elseif ( function_exists( 'wcs_order_contains_renewal' ) && wcs_order_contains_renewal( $order_id ) ) {
300
			$subscriptions = wcs_get_subscriptions_for_renewal_order( $order_id );
301
		} else {
302
			$subscriptions = array();
303
		}
304
305
		foreach ( $subscriptions as $subscription ) {
306
			$subscription_id = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $subscription->id : $subscription->get_id();
307
			update_post_meta( $subscription_id, '_stripe_customer_id', $source->customer );
308
			update_post_meta( $subscription_id, '_stripe_source_id', $source->source );
309
		}
310
	}
311
312
	/**
313
	 * Don't transfer Stripe customer/token meta to resubscribe orders.
314
	 * @param int $resubscribe_order The order created for the customer to resubscribe to the old expired/cancelled subscription
315
	 */
316 View Code Duplication
	public function delete_resubscribe_meta( $resubscribe_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...
317
		delete_post_meta( ( WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $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...
318
		delete_post_meta( ( WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $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...
319
		// For BW compat will remove in future
320
		delete_post_meta( ( WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $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...
321
		$this->delete_renewal_meta( $resubscribe_order );
322
	}
323
324
	/**
325
	 * Don't transfer Stripe fee/ID meta to renewal orders.
326
	 * @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...
327
	 */
328
	public function delete_renewal_meta( $renewal_order ) {
329
		WC_Stripe_Helper::delete_stripe_fee( $renewal_order );
330
		WC_Stripe_Helper::delete_stripe_net( $renewal_order );
331
332
		return $renewal_order;
333
	}
334
335
	/**
336
	 * Update the customer_id for a subscription after using Stripe to complete a payment to make up for
337
	 * an automatic renewal payment which previously failed.
338
	 *
339
	 * @access public
340
	 * @param WC_Subscription $subscription The subscription for which the failing payment method relates.
341
	 * @param WC_Order $renewal_order The order which recorded the successful payment (to make up for the failed automatic payment).
342
	 * @return void
343
	 */
344 View Code Duplication
	public function update_failing_payment_method( $subscription, $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...
345
		if ( WC_Stripe_Helper::is_wc_lt( '3.0' ) ) {
346
			update_post_meta( $subscription->id, '_stripe_customer_id', $renewal_order->stripe_customer_id );
347
			update_post_meta( $subscription->id, '_stripe_source_id', $renewal_order->stripe_source_id );
348
349
		} else {
350
			update_post_meta( $subscription->get_id(), '_stripe_customer_id', $renewal_order->get_meta( '_stripe_customer_id', true ) );
351
			update_post_meta( $subscription->get_id(), '_stripe_source_id', $renewal_order->get_meta( '_stripe_source_id', true ) );
352
		}
353
	}
354
355
	/**
356
	 * Include the payment meta data required to process automatic recurring payments so that store managers can
357
	 * manually set up automatic recurring payments for a customer via the Edit Subscriptions screen in 2.0+.
358
	 *
359
	 * @since 2.5
360
	 * @param array $payment_meta associative array of meta data required for automatic payments
361
	 * @param WC_Subscription $subscription An instance of a subscription object
362
	 * @return array
363
	 */
364
	public function add_subscription_payment_meta( $payment_meta, $subscription ) {
365
		$subscription_id = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $subscription->id : $subscription->get_id();
366
		$source_id = get_post_meta( $subscription_id, '_stripe_source_id', true );
367
368
		// For BW compat will remove in future.
369
		if ( empty( $source_id ) ) {
370
			$source_id = get_post_meta( $subscription_id, '_stripe_card_id', true );
371
372
			// Take this opportunity to update the key name.
373
			update_post_meta( $subscription_id, '_stripe_source_id', $source_id );
374
			delete_post_meta( $subscription_id, '_stripe_card_id', $source_id );
375
		}
376
377
		$payment_meta[ $this->id ] = array(
378
			'post_meta' => array(
379
				'_stripe_customer_id' => array(
380
					'value' => get_post_meta( $subscription_id, '_stripe_customer_id', true ),
381
					'label' => 'Stripe Customer ID',
382
				),
383
				'_stripe_source_id' => array(
384
					'value' => $source_id,
385
					'label' => 'Stripe Source ID',
386
				),
387
			),
388
		);
389
390
		return $payment_meta;
391
	}
392
393
	/**
394
	 * Validate the payment meta data required to process automatic recurring payments so that store managers can
395
	 * manually set up automatic recurring payments for a customer via the Edit Subscriptions screen in 2.0+.
396
	 *
397
	 * @since 2.5
398
	 * @since 4.0.4 Stripe sourd id field no longer needs to be required.
399
	 * @param string $payment_method_id The ID of the payment method to validate
400
	 * @param array $payment_meta associative array of meta data required for automatic payments
401
	 * @return array
402
	 */
403 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...
404
		if ( $this->id === $payment_method_id ) {
405
406
			if ( ! isset( $payment_meta['post_meta']['_stripe_customer_id']['value'] ) || empty( $payment_meta['post_meta']['_stripe_customer_id']['value'] ) ) {
407
				throw new Exception( __( 'A "Stripe Customer ID" value is required.', 'woocommerce-gateway-stripe' ) );
408
			} elseif ( 0 !== strpos( $payment_meta['post_meta']['_stripe_customer_id']['value'], 'cus_' ) ) {
409
				throw new Exception( __( 'Invalid customer ID. A valid "Stripe Customer ID" must begin with "cus_".', 'woocommerce-gateway-stripe' ) );
410
			}
411
412
			if (
413
				( ! empty( $payment_meta['post_meta']['_stripe_source_id']['value'] )
414
				&& 0 !== strpos( $payment_meta['post_meta']['_stripe_source_id']['value'], 'card_' ) )
415
				&& ( ! empty( $payment_meta['post_meta']['_stripe_source_id']['value'] )
416
				&& 0 !== strpos( $payment_meta['post_meta']['_stripe_source_id']['value'], 'src_' ) ) ) {
417
418
				throw new Exception( __( 'Invalid source ID. A valid source "Stripe Source ID" must begin with "src_" or "card_".', 'woocommerce-gateway-stripe' ) );
419
			}
420
		}
421
	}
422
423
	/**
424
	 * Render the payment method used for a subscription in the "My Subscriptions" table
425
	 *
426
	 * @since 1.7.5
427
	 * @param string $payment_method_to_display the default payment method text to display
428
	 * @param WC_Subscription $subscription the subscription details
429
	 * @return string the subscription payment method
430
	 */
431
	public function maybe_render_subscription_payment_method( $payment_method_to_display, $subscription ) {
432
		$customer_user = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $subscription->customer_user : $subscription->get_customer_id();
433
434
		// bail for other payment methods
435 View Code Duplication
		if ( ( WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $subscription->payment_method : $subscription->get_payment_method() ) !== $this->id || ! $customer_user ) {
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...
436
			return $payment_method_to_display;
437
		}
438
439
		$stripe_source_id = get_post_meta( ( WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $subscription->id : $subscription->get_id() ), '_stripe_source_id', true );
440
441
		// For BW compat will remove in future.
442
		if ( empty( $stripe_source_id ) ) {
443
			$stripe_source_id = get_post_meta( ( WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $subscription->id : $subscription->get_id() ), '_stripe_card_id', true );
444
445
			// Take this opportunity to update the key name.
446
			WC_Stripe_Helper::is_wc_lt( '3.0' ) ? update_post_meta( $subscription->id, '_stripe_source_id', $stripe_source_id ) : update_post_meta( $subscription->get_id(), '_stripe_source_id', $stripe_source_id );
447
		}
448
449
		$stripe_customer    = new WC_Stripe_Customer();
450
		$stripe_customer_id = get_post_meta( ( WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $subscription->id : $subscription->get_id() ), '_stripe_customer_id', true );
451
452
		// If we couldn't find a Stripe customer linked to the subscription, fallback to the user meta data.
453 View Code Duplication
		if ( ! $stripe_customer_id || ! is_string( $stripe_customer_id ) ) {
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...
454
			$user_id            = $customer_user;
455
			$stripe_customer_id = get_user_meta( $user_id, '_stripe_customer_id', true );
456
			$stripe_source_id   = get_user_meta( $user_id, '_stripe_source_id', true );
457
458
			// For BW compat will remove in future.
459
			if ( empty( $stripe_source_id ) ) {
460
				$stripe_source_id = get_user_meta( $user_id, '_stripe_card_id', true );
461
462
				// Take this opportunity to update the key name.
463
				update_user_meta( $user_id, '_stripe_source_id', $stripe_source_id );
464
			}
465
		}
466
467
		// If we couldn't find a Stripe customer linked to the account, fallback to the order meta data.
468 View Code Duplication
		if ( ( ! $stripe_customer_id || ! is_string( $stripe_customer_id ) ) && false !== $subscription->order ) {
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...
469
			$stripe_customer_id = get_post_meta( ( WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $subscription->order->id : $subscription->get_parent_id() ), '_stripe_customer_id', true );
470
			$stripe_source_id   = get_post_meta( ( WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $subscription->order->id : $subscription->get_parent_id() ), '_stripe_source_id', true );
471
472
			// For BW compat will remove in future.
473
			if ( empty( $stripe_source_id ) ) {
474
				$stripe_source_id = get_post_meta( ( WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $subscription->order->id : $subscription->get_parent_id() ), '_stripe_card_id', true );
475
476
				// Take this opportunity to update the key name.
477
				WC_Stripe_Helper::is_wc_lt( '3.0' ) ? 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 );
478
			}
479
		}
480
481
		$stripe_customer->set_id( $stripe_customer_id );
482
		$sources = $stripe_customer->get_sources();
483
484
		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...
485
			$card         = false;
486
			$found_source = false;
487
488
			foreach ( $sources as $source ) {
489
				if ( isset( $source->type ) && 'card' === $source->type ) {
490
					$card = $source->card;
491
				} elseif ( isset( $source->object ) && 'card' === $source->object ) {
492
					$card = $source;
493
				}
494
495
				if ( $source->id === $stripe_source_id ) {
496
					$found_source = true;
497
498 View Code Duplication
					if ( $card ) {
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...
499
						/* translators: 1) card brand 2) last 4 digits */
500
						$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 );
501
					} else {
502
						$payment_method_to_display = __( 'N/A', 'woocommerce-gateway-stripe' );
503
					}
504
					break;
505
				}
506
			}
507
508
			if ( ! $found_source ) {
509
				if ( isset( $sources[0]->type ) && 'card' === $sources[0]->type ) {
510
					$card = $sources[0]->card;
511
				}
512
513 View Code Duplication
				if ( $card ) {
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...
514
					/* translators: 1) card brand 2) last 4 digits */
515
					$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 );
516
				} else {
517
					$payment_method_to_display = __( 'N/A', 'woocommerce-gateway-stripe' );
518
				}
519
			}
520
		}
521
522
		return $payment_method_to_display;
523
	}
524
}
525