Completed
Pull Request — master (#739)
by Roy
01:24
created

WC_Stripe_Payment_Gateway   F

Complexity

Total Complexity 225

Size/Duplication

Total Lines 1053
Duplicated Lines 0.66 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
dl 7
loc 1053
rs 1.3879
c 0
b 0
f 0
wmc 225
lcom 1
cbo 5

39 Methods

Rating   Name   Duplication   Size   Complexity  
A display_admin_settings_webhook_description() 0 4 1
A save_payment_method_checkbox() 0 10 1
A is_retryable_error() 0 9 5
A is_same_idempotency_error() 0 7 3
A is_source_already_consumed_error() 0 7 3
A is_no_such_customer_error() 0 7 3
A is_no_such_token_error() 0 7 3
A is_no_such_source_error() 0 7 3
A is_no_linked_source_error() 0 7 3
A need_update_idempotency_key() 0 9 5
A is_available() 0 10 4
A maybe_process_pre_orders() 0 8 4
A add_admin_notice() 7 7 1
A payment_icons() 0 20 1
A validate_minimum_order_amount() 0 6 2
A get_transaction_url() 0 9 2
A get_stripe_customer_id() 0 16 4
A get_stripe_return_url() 0 18 4
A has_subscription() 0 3 4
F generate_payment_request() 0 67 20
F process_response() 0 74 22
A send_failed_order_email() 0 6 3
F get_owner_details() 0 31 14
A get_source_object() 0 13 3
A is_3ds_required() 0 7 6
A is_3ds_card() 0 3 2
A is_prepaid_card() 0 3 3
A is_type_legacy_card() 0 3 1
A is_using_saved_payment_method() 0 5 3
A create_3ds_source() 0 17 4
F prepare_source() 0 80 26
F prepare_order_source() 0 48 12
B save_source_to_order() 0 24 7
B update_fees() 0 34 9
F process_refund() 0 67 15
C add_payment_method() 0 42 12
A get_locale() 0 15 2
A change_idempotency_key() 0 7 3
A is_original_request() 0 7 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like WC_Stripe_Payment_Gateway often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use WC_Stripe_Payment_Gateway, and based on these observations, apply Extract Interface, too.

1
<?php
2
if ( ! defined( 'ABSPATH' ) ) {
3
	exit;
4
}
5
6
/**
7
 * Abstract class that will be inherited by all payment methods.
8
 *
9
 * @extends WC_Payment_Gateway_CC
10
 *
11
 * @since 4.0.0
12
 */
13
abstract class WC_Stripe_Payment_Gateway extends WC_Payment_Gateway_CC {
14
	/**
15
	 * Displays the admin settings webhook description.
16
	 *
17
	 * @since 4.1.0
18
	 * @return mixed
19
	 */
20
	public function display_admin_settings_webhook_description() {
21
		/* translators: 1) webhook url */
22
		return sprintf( __( 'You must add the following webhook endpoint <strong style="background-color:#ddd;">&nbsp;%s&nbsp;</strong> to your <a href="https://dashboard.stripe.com/account/webhooks" target="_blank">Stripe account settings</a>. This will enable you to receive notifications on the charge statuses.', 'woocommerce-gateway-stripe' ), WC_Stripe_Helper::get_webhook_url() );
23
	}
24
25
	/**
26
	 * Displays the save to account checkbox.
27
	 *
28
	 * @since 4.1.0
29
	 */
30
	public function save_payment_method_checkbox() {
31
		printf(
32
			'<p class="form-row woocommerce-SavedPaymentMethods-saveNew">
33
				<input id="wc-%1$s-new-payment-method" name="wc-%1$s-new-payment-method" type="checkbox" value="true" style="width:auto;" />
34
				<label for="wc-%1$s-new-payment-method" style="display:inline;">%2$s</label>
35
			</p>',
36
			esc_attr( $this->id ),
37
			esc_html( apply_filters( 'wc_stripe_save_to_account_text', __( 'Save payment information to my account for future purchases.', 'woocommerce-gateway-stripe' ) ) )
38
		);
39
	}
40
41
	/**
42
	 * Checks to see if request is invalid and that
43
	 * they are worth retrying.
44
	 *
45
	 * @since 4.0.5
46
	 * @param array $error
47
	 */
48
	public function is_retryable_error( $error ) {
49
		return (
50
			'invalid_request_error' === $error->type ||
51
			'idempotency_error' === $error->type ||
52
			'rate_limit_error' === $error->type ||
53
			'api_connection_error' === $error->type ||
54
			'api_error' === $error->type
55
		);
56
	}
57
58
	/**
59
	 * Checks to see if error is of same idempotency key
60
	 * error due to retries with different parameters.
61
	 *
62
	 * @since 4.1.0
63
	 * @param array $error
64
	 */
65
	public function is_same_idempotency_error( $error ) {
66
		return (
67
			$error &&
0 ignored issues
show
Bug Best Practice introduced by
The expression $error 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...
68
			'idempotency_error' === $error->type &&
69
			preg_match( '/Keys for idempotent requests can only be used with the same parameters they were first used with./i', $error->message )
70
		);
71
	}
72
73
	/**
74
	 * Checks to see if error is of invalid request
75
	 * error and source is already consumed.
76
	 *
77
	 * @since 4.1.0
78
	 * @param array $error
79
	 */
80
	public function is_source_already_consumed_error( $error ) {
81
		return (
82
			$error &&
0 ignored issues
show
Bug Best Practice introduced by
The expression $error 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...
83
			'invalid_request_error' === $error->type &&
84
			preg_match( '/The reusable source you provided is consumed because it was previously charged without being attached to a customer or was detached from a customer. To charge a reusable source multiple time you must attach it to a customer first./i', $error->message )
85
		);
86
	}
87
88
	/**
89
	 * Checks to see if error is of invalid request
90
	 * error and it is no such customer.
91
	 *
92
	 * @since 4.1.0
93
	 * @param array $error
94
	 */
95
	public function is_no_such_customer_error( $error ) {
96
		return (
97
			$error &&
0 ignored issues
show
Bug Best Practice introduced by
The expression $error 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...
98
			'invalid_request_error' === $error->type &&
99
			preg_match( '/No such customer/i', $error->message )
100
		);
101
	}
102
103
	/**
104
	 * Checks to see if error is of invalid request
105
	 * error and it is no such token.
106
	 *
107
	 * @since 4.1.0
108
	 * @param array $error
109
	 */
110
	public function is_no_such_token_error( $error ) {
111
		return (
112
			$error &&
0 ignored issues
show
Bug Best Practice introduced by
The expression $error 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...
113
			'invalid_request_error' === $error->type &&
114
			preg_match( '/No such token/i', $error->message )
115
		);
116
	}
117
118
	/**
119
	 * Checks to see if error is of invalid request
120
	 * error and it is no such source.
121
	 *
122
	 * @since 4.1.0
123
	 * @param array $error
124
	 */
125
	public function is_no_such_source_error( $error ) {
126
		return (
127
			$error &&
0 ignored issues
show
Bug Best Practice introduced by
The expression $error 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...
128
			'invalid_request_error' === $error->type &&
129
			preg_match( '/No such source/i', $error->message )
130
		);
131
	}
132
133
	/**
134
	 * Checks to see if error is of invalid request
135
	 * error and it is no such source linked to customer.
136
	 *
137
	 * @since 4.1.0
138
	 * @param array $error
139
	 */
140
	public function is_no_linked_source_error( $error ) {
141
		return (
142
			$error &&
0 ignored issues
show
Bug Best Practice introduced by
The expression $error 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...
143
			'invalid_request_error' === $error->type &&
144
			preg_match( '/does not have a linked source with ID/i', $error->message )
145
		);
146
	}
147
148
	/**
149
	 * Check to see if we need to update the idempotency
150
	 * key to be different from previous charge request.
151
	 *
152
	 * @since 4.1.0
153
	 * @param object $source_object
154
	 * @param object $error
155
	 * @return bool
156
	 */
157
	public function need_update_idempotency_key( $source_object, $error ) {
158
		return (
159
			$error &&
160
			1 < $this->retry_interval &&
161
			! empty( $source_object ) &&
162
			'chargeable' === $source_object->status &&
163
			self::is_same_idempotency_error( $error )
164
		);
165
	}
166
167
	/**
168
	 * Check if we need to make gateways available.
169
	 *
170
	 * @since 4.1.3
171
	 */
172
	public function is_available() {
173
		if ( 'yes' === $this->enabled ) {
174
			if ( ! $this->secret_key || ! $this->publishable_key ) {
175
				return false;
176
			}
177
			return true;
178
		}
179
180
		return parent::is_available();
181
	}
182
183
	/**
184
	 * Checks if we need to process pre orders when
185
	 * pre orders is in the cart.
186
	 *
187
	 * @since 4.1.0
188
	 * @param int $order_id
189
	 * @return bool
190
	 */
191
	public function maybe_process_pre_orders( $order_id ) {
192
		return (
193
			WC_Stripe_Helper::is_pre_orders_exists() &&
194
			$this->pre_orders->is_pre_order( $order_id ) &&
195
			WC_Pre_Orders_Order::order_requires_payment_tokenization( $order_id ) &&
196
			! is_wc_endpoint_url( 'order-pay' )
197
		);
198
	}
199
200
	/**
201
	 * Allow this class and other classes to add slug keyed notices (to avoid duplication).
202
	 *
203
	 * @since 1.0.0
204
	 * @version 4.0.0
205
	 */
206 View Code Duplication
	public function add_admin_notice( $slug, $class, $message, $dismissible = 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...
207
		$this->notices[ $slug ] = array(
208
			'class'       => $class,
209
			'message'     => $message,
210
			'dismissible' => $dismissible,
211
		);
212
	}
213
214
	/**
215
	 * All payment icons that work with Stripe. Some icons references
216
	 * WC core icons.
217
	 *
218
	 * @since 4.0.0
219
	 * @since 4.1.0 Changed to using img with svg (colored) instead of fonts.
220
	 * @return array
221
	 */
222
	public function payment_icons() {
223
		return apply_filters( 'wc_stripe_payment_icons', array(
224
			'visa'       => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/visa.svg" class="stripe-visa-icon stripe-icon" alt="Visa" />',
225
			'amex'       => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/amex.svg" class="stripe-amex-icon stripe-icon" alt="American Express" />',
226
			'mastercard' => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/mastercard.svg" class="stripe-mastercard-icon stripe-icon" alt="Mastercard" />',
227
			'discover'   => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/discover.svg" class="stripe-discover-icon stripe-icon" alt="Discover" />',
228
			'diners'     => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/diners.svg" class="stripe-diners-icon stripe-icon" alt="Diners" />',
229
			'jcb'        => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/jcb.svg" class="stripe-jcb-icon stripe-icon" alt="JCB" />',
230
			'alipay'     => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/alipay.svg" class="stripe-alipay-icon stripe-icon" alt="Alipay" />',
231
			'wechat'     => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/wechat.svg" class="stripe-wechat-icon stripe-icon" alt="Wechat Pay" />',
232
			'bancontact' => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/bancontact.svg" class="stripe-bancontact-icon stripe-icon" alt="Bancontact" />',
233
			'ideal'      => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/ideal.svg" class="stripe-ideal-icon stripe-icon" alt="iDeal" />',
234
			'p24'        => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/p24.svg" class="stripe-p24-icon stripe-icon" alt="P24" />',
235
			'giropay'    => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/giropay.svg" class="stripe-giropay-icon stripe-icon" alt="Giropay" />',
236
			'eps'        => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/eps.svg" class="stripe-eps-icon stripe-icon" alt="EPS" />',
237
			'multibanco' => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/multibanco.svg" class="stripe-multibanco-icon stripe-icon" alt="Multibanco" />',
238
			'sofort'     => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/sofort.svg" class="stripe-sofort-icon stripe-icon" alt="SOFORT" />',
239
			'sepa'       => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/sepa.svg" class="stripe-sepa-icon stripe-icon" alt="SEPA" />',
240
		) );
241
	}
242
243
	/**
244
	 * Validates that the order meets the minimum order amount
245
	 * set by Stripe.
246
	 *
247
	 * @since 4.0.0
248
	 * @version 4.0.0
249
	 * @param object $order
250
	 */
251
	public function validate_minimum_order_amount( $order ) {
252
		if ( $order->get_total() * 100 < WC_Stripe_Helper::get_minimum_amount() ) {
253
			/* translators: 1) dollar amount */
254
			throw new WC_Stripe_Exception( 'Did not meet minimum amount', 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 ) ) );
255
		}
256
	}
257
258
	/**
259
	 * Gets the transaction URL linked to Stripe dashboard.
260
	 *
261
	 * @since 4.0.0
262
	 * @version 4.0.0
263
	 */
264
	public function get_transaction_url( $order ) {
265
		if ( $this->testmode ) {
266
			$this->view_transaction_url = 'https://dashboard.stripe.com/test/payments/%s';
267
		} else {
268
			$this->view_transaction_url = 'https://dashboard.stripe.com/payments/%s';
269
		}
270
271
		return parent::get_transaction_url( $order );
272
	}
273
274
	/**
275
	 * Gets the saved customer id if exists.
276
	 *
277
	 * @since 4.0.0
278
	 * @version 4.0.0
279
	 */
280
	public function get_stripe_customer_id( $order ) {
281
		$customer = get_user_meta( WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $order->customer_user : $order->get_customer_id(), '_stripe_customer_id', true );
282
283
		if ( empty( $customer ) ) {
284
			// Try to get it via the order.
285
			if ( WC_Stripe_Helper::is_wc_lt( '3.0' ) ) {
286
				return get_post_meta( $order->id, '_stripe_customer_id', true );
287
			} else {
288
				return $order->get_meta( '_stripe_customer_id', true );
289
			}
290
		} else {
291
			return $customer;
292
		}
293
294
		return false;
0 ignored issues
show
Unused Code introduced by
return false; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
295
	}
296
297
	/**
298
	 * Builds the return URL from redirects.
299
	 *
300
	 * @since 4.0.0
301
	 * @version 4.0.0
302
	 * @param object $order
303
	 * @param int $id Stripe session id.
304
	 */
305
	public function get_stripe_return_url( $order = null, $id = null ) {
306
		if ( is_object( $order ) ) {
307
			if ( empty( $id ) ) {
308
				$id = uniqid();
0 ignored issues
show
Unused Code introduced by
$id is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
309
			}
310
311
			$order_id = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $order->id : $order->get_id();
312
313
			$args = array(
314
				'utm_nooverride' => '1',
315
				'order_id'       => $order_id,
316
			);
317
318
			return esc_url_raw( add_query_arg( $args, $this->get_return_url( $order ) ) );
319
		}
320
321
		return esc_url_raw( add_query_arg( array( 'utm_nooverride' => '1' ), $this->get_return_url() ) );
322
	}
323
324
	/**
325
	 * Is $order_id a subscription?
326
	 * @param  int  $order_id
327
	 * @return boolean
328
	 */
329
	public function has_subscription( $order_id ) {
330
		return ( function_exists( 'wcs_order_contains_subscription' ) && ( wcs_order_contains_subscription( $order_id ) || wcs_is_subscription( $order_id ) || wcs_order_contains_renewal( $order_id ) ) );
331
	}
332
333
	/**
334
	 * Generate the request for the payment.
335
	 *
336
	 * @since 3.1.0
337
	 * @version 4.0.0
338
	 * @param  WC_Order $order
339
	 * @param  object $prepared_source
340
	 * @return array()
0 ignored issues
show
Documentation introduced by
The doc-type array() could not be parsed: Expected "|" or "end of type", but got "(" at position 5. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
341
	 */
342
	public function generate_payment_request( $order, $prepared_source ) {
343
		$settings                          = get_option( 'woocommerce_stripe_settings', array() );
344
		$statement_descriptor              = ! empty( $settings['statement_descriptor'] ) ? str_replace( "'", '', $settings['statement_descriptor'] ) : '';
345
		$capture                           = ! empty( $settings['capture'] ) && 'yes' === $settings['capture'] ? true : false;
346
		$post_data                         = array();
347
		$post_data['currency']             = strtolower( WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $order->get_order_currency() : $order->get_currency() );
348
		$post_data['amount']               = WC_Stripe_Helper::get_stripe_amount( $order->get_total(), $post_data['currency'] );
349
		/* translators: 1) blog name 2) order number */
350
		$post_data['description']          = sprintf( __( '%1$s - Order %2$s', 'woocommerce-gateway-stripe' ), wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ), $order->get_order_number() );
351
		$billing_email      = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $order->billing_email : $order->get_billing_email();
352
		$billing_first_name = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $order->billing_first_name : $order->get_billing_first_name();
353
		$billing_last_name  = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $order->billing_last_name : $order->get_billing_last_name();
354
355
		if ( ! empty( $billing_email ) && apply_filters( 'wc_stripe_send_stripe_receipt', false ) ) {
356
			$post_data['receipt_email'] = $billing_email;
357
		}
358
359
		switch ( WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $order->payment_method : $order->get_payment_method() ) {
360
			case 'stripe':
361
				if ( ! empty( $statement_descriptor ) ) {
362
					$post_data['statement_descriptor'] = WC_Stripe_Helper::clean_statement_descriptor( $statement_descriptor );
363
				}
364
365
				$post_data['capture'] = $capture ? 'true' : 'false';
366
				break;
367
			case 'stripe_sepa':
368
				if ( ! empty( $statement_descriptor ) ) {
369
					$post_data['statement_descriptor'] = WC_Stripe_Helper::clean_statement_descriptor( $statement_descriptor );
370
				}
371
				break;
372
		}
373
374
		$post_data['expand[]'] = 'balance_transaction';
375
376
		$metadata = array(
377
			__( 'customer_name', 'woocommerce-gateway-stripe' ) => sanitize_text_field( $billing_first_name ) . ' ' . sanitize_text_field( $billing_last_name ),
378
			__( 'customer_email', 'woocommerce-gateway-stripe' ) => sanitize_email( $billing_email ),
379
			'order_id' => $order->get_order_number(),
380
		);
381
382
		if ( $this->has_subscription( WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $order->id : $order->get_id() ) ) {
383
			$metadata += array(
384
				'payment_type' => 'recurring',
385
				'site_url'     => esc_url( get_site_url() ),
386
			);
387
		}
388
389
		$post_data['metadata'] = apply_filters( 'wc_stripe_payment_metadata', $metadata, $order, $prepared_source );
390
391
		if ( $prepared_source->customer ) {
392
			$post_data['customer'] = $prepared_source->customer;
393
		}
394
395
		if ( $prepared_source->source ) {
396
			$post_data['source'] = $prepared_source->source;
397
		}
398
399
		/**
400
		 * Filter the return value of the WC_Payment_Gateway_CC::generate_payment_request.
401
		 *
402
		 * @since 3.1.0
403
		 * @param array $post_data
404
		 * @param WC_Order $order
405
		 * @param object $source
406
		 */
407
		return apply_filters( 'wc_stripe_generate_payment_request', $post_data, $order, $prepared_source );
408
	}
409
410
	/**
411
	 * Store extra meta data for an order from a Stripe Response.
412
	 */
413
	public function process_response( $response, $order ) {
414
		WC_Stripe_Logger::log( 'Processing response: ' . print_r( $response, true ) );
415
416
		$order_id = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $order->id : $order->get_id();
417
418
		$captured = ( isset( $response->captured ) && $response->captured ) ? 'yes' : 'no';
419
420
		// Store charge data.
421
		WC_Stripe_Helper::is_wc_lt( '3.0' ) ? update_post_meta( $order_id, '_stripe_charge_captured', $captured ) : $order->update_meta_data( '_stripe_charge_captured', $captured );
422
423
		// Store other data such as fees.
424
		if ( isset( $response->balance_transaction ) && isset( $response->balance_transaction->fee ) ) {
425
			// Fees and Net needs to both come from Stripe to be accurate as the returned
426
			// values are in the local currency of the Stripe account, not from WC.
427
			$fee = ! empty( $response->balance_transaction->fee ) ? WC_Stripe_Helper::format_balance_fee( $response->balance_transaction, 'fee' ) : 0;
428
			$net = ! empty( $response->balance_transaction->net ) ? WC_Stripe_Helper::format_balance_fee( $response->balance_transaction, 'net' ) : 0;
429
			WC_Stripe_Helper::update_stripe_fee( $order, $fee );
430
			WC_Stripe_Helper::update_stripe_net( $order, $net );
431
432
			// Store currency stripe.
433
			$currency = ! empty( $response->balance_transaction->currency ) ? strtoupper( $response->balance_transaction->currency ) : null;
434
			WC_Stripe_Helper::update_stripe_currency( $order, $currency );
435
		}
436
437
		if ( 'yes' === $captured ) {
438
			/**
439
			 * Charge can be captured but in a pending state. Payment methods
440
			 * that are asynchronous may take couple days to clear. Webhook will
441
			 * take care of the status changes.
442
			 */
443
			if ( 'pending' === $response->status ) {
444
				$order_stock_reduced = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? get_post_meta( $order_id, '_order_stock_reduced', true ) : $order->get_meta( '_order_stock_reduced', true );
445
446
				if ( ! $order_stock_reduced ) {
447
					WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $order->reduce_order_stock() : wc_reduce_stock_levels( $order_id );
448
				}
449
450
				WC_Stripe_Helper::is_wc_lt( '3.0' ) ? update_post_meta( $order_id, '_transaction_id', $response->id ) : $order->set_transaction_id( $response->id );
451
				/* translators: transaction id */
452
				$order->update_status( 'on-hold', sprintf( __( 'Stripe charge awaiting payment: %s.', 'woocommerce-gateway-stripe' ), $response->id ) );
453
			}
454
455
			if ( 'succeeded' === $response->status ) {
456
				$order->payment_complete( $response->id );
457
458
				/* translators: transaction id */
459
				$message = sprintf( __( 'Stripe charge complete (Charge ID: %s)', 'woocommerce-gateway-stripe' ), $response->id );
460
				$order->add_order_note( $message );
461
			}
462
463
			if ( 'failed' === $response->status ) {
464
				$localized_message = __( 'Payment processing failed. Please retry.', 'woocommerce-gateway-stripe' );
465
				$order->add_order_note( $localized_message );
466
				throw new WC_Stripe_Exception( print_r( $response, true ), $localized_message );
467
			}
468
		} else {
469
			WC_Stripe_Helper::is_wc_lt( '3.0' ) ? update_post_meta( $order_id, '_transaction_id', $response->id ) : $order->set_transaction_id( $response->id );
470
471
			if ( $order->has_status( array( 'pending', 'failed' ) ) ) {
472
				WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $order->reduce_order_stock() : wc_reduce_stock_levels( $order_id );
473
			}
474
475
			/* translators: transaction id */
476
			$order->update_status( 'on-hold', sprintf( __( 'Stripe charge authorized (Charge ID: %s). Process order to take payment, or cancel to remove the pre-authorization.', 'woocommerce-gateway-stripe' ), $response->id ) );
477
		}
478
479
		if ( is_callable( array( $order, 'save' ) ) ) {
480
			$order->save();
481
		}
482
483
		do_action( 'wc_gateway_stripe_process_response', $response, $order );
484
485
		return $response;
486
	}
487
488
	/**
489
	 * Sends the failed order email to admin.
490
	 *
491
	 * @since 3.1.0
492
	 * @version 4.0.0
493
	 * @param int $order_id
494
	 * @return null
495
	 */
496
	public function send_failed_order_email( $order_id ) {
497
		$emails = WC()->mailer()->get_emails();
498
		if ( ! empty( $emails ) && ! empty( $order_id ) ) {
499
			$emails['WC_Email_Failed_Order']->trigger( $order_id );
500
		}
501
	}
502
503
	/**
504
	 * Get owner details.
505
	 *
506
	 * @since 4.0.0
507
	 * @version 4.0.0
508
	 * @param object $order
509
	 * @return object $details
510
	 */
511
	public function get_owner_details( $order ) {
512
		$billing_first_name = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $order->billing_first_name : $order->get_billing_first_name();
513
		$billing_last_name  = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $order->billing_last_name : $order->get_billing_last_name();
514
515
		$details = array();
516
517
		$name  = $billing_first_name . ' ' . $billing_last_name;
518
		$email = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $order->billing_email : $order->get_billing_email();
519
		$phone = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $order->billing_phone : $order->get_billing_phone();
520
521
		if ( ! empty( $phone ) ) {
522
			$details['phone'] = $phone;
523
		}
524
525
		if ( ! empty( $name ) ) {
526
			$details['name'] = $name;
527
		}
528
529
		if ( ! empty( $email ) ) {
530
			$details['email'] = $email;
531
		}
532
533
		$details['address']['line1']       = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $order->billing_address_1 : $order->get_billing_address_1();
534
		$details['address']['line2']       = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $order->billing_address_2 : $order->get_billing_address_2();
535
		$details['address']['state']       = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $order->billing_state : $order->get_billing_state();
536
		$details['address']['city']        = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $order->billing_city : $order->get_billing_city();
537
		$details['address']['postal_code'] = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $order->billing_postcode : $order->get_billing_postcode();
538
		$details['address']['country']     = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $order->billing_country : $order->get_billing_country();
539
540
		return (object) apply_filters( 'wc_stripe_owner_details', $details, $order );
541
	}
542
543
	/**
544
	 * Get source object by source id.
545
	 *
546
	 * @since 4.0.3
547
	 * @param string $source_id The source ID to get source object for.
548
	 */
549
	public function get_source_object( $source_id = '' ) {
550
		if ( empty( $source_id ) ) {
551
			return '';
552
		}
553
554
		$source_object = WC_Stripe_API::retrieve( 'sources/' . $source_id );
555
556
		if ( ! empty( $source_object->error ) ) {
557
			throw new WC_Stripe_Exception( print_r( $source_object, true ), $source_object->error->message );
558
		}
559
560
		return $source_object;
561
	}
562
563
	/**
564
	 * Checks if 3DS is required.
565
	 *
566
	 * @since 4.0.4
567
	 * @since 4.1.0 Add filter and changed optional to recommended.
568
	 * @param object $source_object
569
	 * @return bool
570
	 */
571
	public function is_3ds_required( $source_object ) {
572
		return apply_filters( 'wc_stripe_require_3ds', (
573
			$source_object && ! empty( $source_object->card ) ) &&
574
			( 'card' === $source_object->type && 'required' === $source_object->card->three_d_secure ||
575
			( $this->three_d_secure && 'recommended' === $source_object->card->three_d_secure )
576
		), $source_object, $this->three_d_secure );
577
	}
578
579
	/**
580
	 * Checks if card is 3DS.
581
	 *
582
	 * @since 4.0.4
583
	 * @param object $source_object
584
	 * @return bool
585
	 */
586
	public function is_3ds_card( $source_object ) {
587
		return ( $source_object && 'three_d_secure' === $source_object->type );
588
	}
589
590
	/**
591
	 * Checks if card is a prepaid card.
592
	 *
593
	 * @since 4.0.6
594
	 * @param object $source_object
595
	 * @return bool
596
	 */
597
	public function is_prepaid_card( $source_object ) {
598
		return ( $source_object && 'token' === $source_object->object && 'prepaid' === $source_object->card->funding );
599
	}
600
601
	/**
602
	 * Checks if source is of legacy type card.
603
	 *
604
	 * @since 4.0.8
605
	 * @param string $source_id
606
	 * @return bool
607
	 */
608
	public function is_type_legacy_card( $source_id ) {
609
		return ( preg_match( '/^card_/', $source_id ) );
610
	}
611
612
	/**
613
	 * Checks if payment is via saved payment source.
614
	 *
615
	 * @since 4.1.0
616
	 * @return bool
617
	 */
618
	public function is_using_saved_payment_method() {
619
		$payment_method = isset( $_POST['payment_method'] ) ? wc_clean( $_POST['payment_method'] ) : 'stripe';
620
621
		return ( isset( $_POST[ 'wc-' . $payment_method . '-payment-token' ] ) && 'new' !== $_POST[ 'wc-' . $payment_method . '-payment-token' ] );
622
	}
623
624
	/**
625
	 * Creates the 3DS source for charge.
626
	 *
627
	 * @since 4.0.0
628
	 * @since 4.0.4 Add $return_url
629
	 * @param object $order
630
	 * @param object $source_object
631
	 * @param string $return_url
632
	 * @return mixed
633
	 */
634
	public function create_3ds_source( $order, $source_object, $return_url = '' ) {
635
		$currency                    = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $order->get_order_currency() : $order->get_currency();
636
		$order_id                    = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $order->id : $order->get_id();
0 ignored issues
show
Unused Code introduced by
$order_id is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
637
		$return_url                  = empty( $return_url ) ? $this->get_stripe_return_url( $order ) : $return_url;
638
639
		$post_data                   = array();
640
		$post_data['amount']         = WC_Stripe_Helper::get_stripe_amount( $order->get_total(), $currency );
641
		$post_data['currency']       = strtolower( $currency );
642
		$post_data['type']           = 'three_d_secure';
643
		$post_data['owner']          = $this->get_owner_details( $order );
644
		$post_data['three_d_secure'] = array( 'card' => $source_object->id );
645
		$post_data['redirect']       = array( 'return_url' => $return_url );
646
647
		WC_Stripe_Logger::log( 'Info: Begin creating 3DS source...' );
648
649
		return WC_Stripe_API::request( apply_filters( 'wc_stripe_3ds_source', $post_data, $order ), 'sources' );
650
	}
651
652
	/**
653
	 * Get payment source. This can be a new token/source or existing WC token.
654
	 * If user is logged in and/or has WC account, create an account on Stripe.
655
	 * This way we can attribute the payment to the user to better fight fraud.
656
	 *
657
	 * @since 3.1.0
658
	 * @version 4.0.0
659
	 * @param string $user_id
660
	 * @param bool $force_save_source Should we force save payment source.
661
	 *
662
	 * @throws Exception When card was not added or for and invalid card.
663
	 * @return object
664
	 */
665
	public function prepare_source( $user_id, $force_save_source = false ) {
666
		$customer           = new WC_Stripe_Customer( $user_id );
667
		$set_customer       = true;
668
		$force_save_source  = apply_filters( 'wc_stripe_force_save_source', $force_save_source, $customer );
669
		$source_object      = '';
670
		$source_id          = '';
671
		$wc_token_id        = false;
672
		$payment_method     = isset( $_POST['payment_method'] ) ? wc_clean( $_POST['payment_method'] ) : 'stripe';
673
		$is_token           = false;
674
675
		// New CC info was entered and we have a new source to process.
676
		if ( ! empty( $_POST['stripe_source'] ) ) {
677
			$source_object = self::get_source_object( wc_clean( $_POST['stripe_source'] ) );
678
			$source_id     = $source_object->id;
679
680
			// This checks to see if customer opted to save the payment method to file.
681
			$maybe_saved_card = isset( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] ) && ! empty( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] );
682
683
			/**
684
			 * This is true if the user wants to store the card to their account.
685
			 * Criteria to save to file is they are logged in, they opted to save or product requirements and the source is
686
			 * actually reusable. Either that or force_save_source is true.
687
			 */
688
			if ( ( $user_id && $this->saved_cards && $maybe_saved_card && 'reusable' === $source_object->usage ) || $force_save_source ) {
689
				$response = $customer->add_source( $source_object->id );
690
691
				if ( ! empty( $response->error ) ) {
692
					throw new WC_Stripe_Exception( print_r( $response, true ), $response->error->message );
693
				}
694
			}
695
		} elseif ( $this->is_using_saved_payment_method() ) {
696
			// Use an existing token, and then process the payment.
697
			$wc_token_id = wc_clean( $_POST[ 'wc-' . $payment_method . '-payment-token' ] );
698
			$wc_token    = WC_Payment_Tokens::get( $wc_token_id );
699
700
			if ( ! $wc_token || $wc_token->get_user_id() !== get_current_user_id() ) {
701
				WC()->session->set( 'refresh_totals', true );
702
				throw new WC_Stripe_Exception( 'Invalid payment method', __( 'Invalid payment method. Please input a new card number.', 'woocommerce-gateway-stripe' ) );
703
			}
704
705
			$source_id = $wc_token->get_token();
706
707
			if ( $this->is_type_legacy_card( $source_id ) ) {
708
				$is_token = true;
709
			}
710
		} elseif ( isset( $_POST['stripe_token'] ) && 'new' !== $_POST['stripe_token'] ) {
711
			$stripe_token     = wc_clean( $_POST['stripe_token'] );
712
			$maybe_saved_card = isset( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] ) && ! empty( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] );
713
714
			// This is true if the user wants to store the card to their account.
715
			if ( ( $user_id && $this->saved_cards && $maybe_saved_card ) || $force_save_source ) {
716
				$response = $customer->add_source( $stripe_token );
717
718
				if ( ! empty( $response->error ) ) {
719
					throw new WC_Stripe_Exception( print_r( $response, true ), $response->error->message );
720
				}
721
			} else {
722
				$set_customer = false;
723
				$source_id    = $stripe_token;
724
				$is_token     = true;
725
			}
726
		}
727
728
		if ( ! $set_customer ) {
729
			$customer_id = false;
730
		} else {
731
			$customer_id = $customer->get_id() ? $customer->get_id() : false;
732
		}
733
734
		if ( empty( $source_object ) && ! $is_token ) {
735
			$source_object = self::get_source_object( $source_id );
736
		}
737
738
		return (object) array(
739
			'token_id'      => $wc_token_id,
740
			'customer'      => $customer_id,
741
			'source'        => $source_id,
742
			'source_object' => $source_object,
743
		);
744
	}
745
746
	/**
747
	 * Get payment source from an order. This could be used in the future for
748
	 * a subscription as an example, therefore using the current user ID would
749
	 * not work - the customer won't be logged in :)
750
	 *
751
	 * Not using 2.6 tokens for this part since we need a customer AND a card
752
	 * token, and not just one.
753
	 *
754
	 * @since 3.1.0
755
	 * @version 4.0.0
756
	 * @param object $order
757
	 * @return object
758
	 */
759
	public function prepare_order_source( $order = null ) {
760
		$stripe_customer = new WC_Stripe_Customer();
761
		$stripe_source   = false;
762
		$token_id        = false;
763
		$source_object   = false;
764
765
		if ( $order ) {
766
			$order_id = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $order->id : $order->get_id();
767
768
			$stripe_customer_id = get_post_meta( $order_id, '_stripe_customer_id', true );
769
770
			if ( $stripe_customer_id ) {
771
				$stripe_customer->set_id( $stripe_customer_id );
772
			}
773
774
			$source_id = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? get_post_meta( $order_id, '_stripe_source_id', true ) : $order->get_meta( '_stripe_source_id', true );
775
776
			// Since 4.0.0, we changed card to source so we need to account for that.
777
			if ( empty( $source_id ) ) {
778
				$source_id = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? get_post_meta( $order_id, '_stripe_card_id', true ) : $order->get_meta( '_stripe_card_id', true );
779
780
				// Take this opportunity to update the key name.
781
				WC_Stripe_Helper::is_wc_lt( '3.0' ) ? update_post_meta( $order_id, '_stripe_source_id', $source_id ) : $order->update_meta_data( '_stripe_source_id', $source_id );
782
783
				if ( is_callable( array( $order, 'save' ) ) ) {
784
					$order->save();
785
				}
786
			}
787
788
			if ( $source_id ) {
789
				$stripe_source = $source_id;
790
				$source_object = WC_Stripe_API::retrieve( 'sources/' . $source_id );
791
			} elseif ( apply_filters( 'wc_stripe_use_default_customer_source', true ) ) {
792
				/*
793
				 * We can attempt to charge the customer's default source
794
				 * by sending empty source id.
795
				 */
796
				$stripe_source = '';
797
			}
798
		}
799
800
		return (object) array(
801
			'token_id'      => $token_id,
802
			'customer'      => $stripe_customer ? $stripe_customer->get_id() : false,
803
			'source'        => $stripe_source,
804
			'source_object' => $source_object,
805
		);
806
	}
807
808
	/**
809
	 * Save source to order.
810
	 *
811
	 * @since 3.1.0
812
	 * @version 4.0.0
813
	 * @param WC_Order $order For to which the source applies.
814
	 * @param stdClass $source Source information.
815
	 */
816
	public function save_source_to_order( $order, $source ) {
817
		$order_id = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $order->id : $order->get_id();
818
819
		// Store source in the order.
820
		if ( $source->customer ) {
821
			if ( WC_Stripe_Helper::is_wc_lt( '3.0' ) ) {
822
				update_post_meta( $order_id, '_stripe_customer_id', $source->customer );
823
			} else {
824
				$order->update_meta_data( '_stripe_customer_id', $source->customer );
825
			}
826
		}
827
828
		if ( $source->source ) {
829
			if ( WC_Stripe_Helper::is_wc_lt( '3.0' ) ) {
830
				update_post_meta( $order_id, '_stripe_source_id', $source->source );
831
			} else {
832
				$order->update_meta_data( '_stripe_source_id', $source->source );
833
			}
834
		}
835
836
		if ( is_callable( array( $order, 'save' ) ) ) {
837
			$order->save();
838
		}
839
	}
840
841
	/**
842
	 * Updates Stripe fees/net.
843
	 * e.g usage would be after a refund.
844
	 *
845
	 * @since 4.0.0
846
	 * @version 4.0.6
847
	 * @param object $order The order object
848
	 * @param int $balance_transaction_id
849
	 */
850
	public function update_fees( $order, $balance_transaction_id ) {
851
		$order_id = WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $order->id : $order->get_id();
852
853
		$balance_transaction = WC_Stripe_API::retrieve( 'balance/history/' . $balance_transaction_id );
854
855
		if ( empty( $balance_transaction->error ) ) {
856
			if ( isset( $balance_transaction ) && isset( $balance_transaction->fee ) ) {
857
				// Fees and Net needs to both come from Stripe to be accurate as the returned
858
				// values are in the local currency of the Stripe account, not from WC.
859
				$fee_refund = ! empty( $balance_transaction->fee ) ? WC_Stripe_Helper::format_balance_fee( $balance_transaction, 'fee' ) : 0;
860
				$net_refund = ! empty( $balance_transaction->net ) ? WC_Stripe_Helper::format_balance_fee( $balance_transaction, 'net' ) : 0;
861
862
				// Current data fee & net.
863
				$fee_current = WC_Stripe_Helper::get_stripe_fee( $order );
864
				$net_current = WC_Stripe_Helper::get_stripe_net( $order );
865
866
				// Calculation.
867
				$fee = (float) $fee_current + (float) $fee_refund;
868
				$net = (float) $net_current + (float) $net_refund;
869
870
				WC_Stripe_Helper::update_stripe_fee( $order, $fee );
871
				WC_Stripe_Helper::update_stripe_net( $order, $net );
872
873
				$currency = ! empty( $balance_transaction->currency ) ? strtoupper( $balance_transaction->currency ) : null;
874
				WC_Stripe_Helper::update_stripe_currency( $order, $currency );
875
876
				if ( is_callable( array( $order, 'save' ) ) ) {
877
					$order->save();
878
				}
879
			}
880
		} else {
881
			WC_Stripe_Logger::log( "Unable to update fees/net meta for order: {$order_id}" );
882
		}
883
	}
884
885
	/**
886
	 * Refund a charge.
887
	 *
888
	 * @since 3.1.0
889
	 * @version 4.0.0
890
	 * @param  int $order_id
891
	 * @param  float $amount
892
	 * @return bool
893
	 */
894
	public function process_refund( $order_id, $amount = null, $reason = '' ) {
895
		$order = wc_get_order( $order_id );
896
897
		if ( ! $order || ! $order->get_transaction_id() ) {
898
			return false;
899
		}
900
901
		$request = array();
902
903
		if ( WC_Stripe_Helper::is_wc_lt( '3.0' ) ) {
904
			$order_currency = get_post_meta( $order_id, '_order_currency', true );
905
			$captured       = get_post_meta( $order_id, '_stripe_charge_captured', true );
906
		} else {
907
			$order_currency = $order->get_currency();
908
			$captured       = $order->get_meta( '_stripe_charge_captured', true );
909
		}
910
911
		if ( ! is_null( $amount ) ) {
912
			$request['amount'] = WC_Stripe_Helper::get_stripe_amount( $amount, $order_currency );
913
		}
914
915
		// If order is only authorized, don't pass amount.
916
		if ( 'yes' !== $captured ) {
917
			unset( $request['amount'] );
918
		}
919
920
		if ( $reason ) {
921
			$request['metadata'] = array(
922
				'reason' => $reason,
923
			);
924
		}
925
926
		$request['charge'] = $order->get_transaction_id();
927
928
		WC_Stripe_Logger::log( "Info: Beginning refund for order {$order->get_transaction_id()} for the amount of {$amount}" );
929
930
		$request = apply_filters( 'wc_stripe_refund_request', $request, $order );
931
932
		$response = WC_Stripe_API::request( $request, 'refunds' );
933
934
		if ( ! empty( $response->error ) ) {
935
			WC_Stripe_Logger::log( 'Error: ' . $response->error->message );
936
937
			return $response;
938
939
		} elseif ( ! empty( $response->id ) ) {
940
			WC_Stripe_Helper::is_wc_lt( '3.0' ) ? update_post_meta( $order_id, '_stripe_refund_id', $response->id ) : $order->update_meta_data( '_stripe_refund_id', $response->id );
941
942
			$amount = wc_price( $response->amount / 100 );
943
944
			if ( in_array( strtolower( WC_Stripe_Helper::is_wc_lt( '3.0' ) ? $order->get_order_currency() : $order->get_currency() ), WC_Stripe_Helper::no_decimal_currencies() ) ) {
945
				$amount = wc_price( $response->amount );
946
			}
947
948
			if ( isset( $response->balance_transaction ) ) {
949
				$this->update_fees( $order, $response->balance_transaction );
950
			}
951
952
			/* translators: 1) dollar amount 2) transaction id 3) refund message */
953
			$refund_message = ( isset( $captured ) && 'yes' === $captured ) ? sprintf( __( 'Refunded %1$s - Refund ID: %2$s - Reason: %3$s', 'woocommerce-gateway-stripe' ), $amount, $response->id, $reason ) : __( 'Pre-Authorization Released', 'woocommerce-gateway-stripe' );
954
955
			$order->add_order_note( $refund_message );
956
			WC_Stripe_Logger::log( 'Success: ' . html_entity_decode( wp_strip_all_tags( $refund_message ) ) );
957
958
			return true;
959
		}
960
	}
961
962
	/**
963
	 * Add payment method via account screen.
964
	 * We don't store the token locally, but to the Stripe API.
965
	 *
966
	 * @since 3.0.0
967
	 * @version 4.0.0
968
	 */
969
	public function add_payment_method() {
970
		$error     = false;
971
		$error_msg = __( 'There was a problem adding the card.', 'woocommerce-gateway-stripe' );
972
		$source_id = '';
973
974
		if ( empty( $_POST['stripe_source'] ) && empty( $_POST['stripe_token'] ) || ! is_user_logged_in() ) {
975
			$error = true;
976
		}
977
978
		$stripe_customer = new WC_Stripe_Customer( get_current_user_id() );
979
980
		$source = ! empty( $_POST['stripe_source'] ) ? wc_clean( $_POST['stripe_source'] ) : '';
981
982
		$source_object = WC_Stripe_API::retrieve( 'sources/' . $source );
983
984
		if ( isset( $source_object ) ) {
985
			if ( ! empty( $source_object->error ) ) {
986
				$error = true;
987
			}
988
989
			$source_id = $source_object->id;
990
		} elseif ( isset( $_POST['stripe_token'] ) ) {
991
			$source_id = wc_clean( $_POST['stripe_token'] );
992
		}
993
994
		$response = $stripe_customer->add_source( $source_id );
995
996
		if ( ! $response || is_wp_error( $response ) || ! empty( $response->error ) ) {
997
			$error = true;
998
		}
999
1000
		if ( $error ) {
1001
			wc_add_notice( $error_msg, 'error' );
1002
			WC_Stripe_Logger::log( 'Add payment method Error: ' . $error_msg );
1003
			return;
1004
		}
1005
1006
		return array(
1007
			'result'   => 'success',
1008
			'redirect' => wc_get_endpoint_url( 'payment-methods' ),
1009
		);
1010
	}
1011
1012
	/**
1013
	 * Gets the locale with normalization that only Stripe accepts.
1014
	 *
1015
	 * @since 4.0.6
1016
	 * @return string $locale
1017
	 */
1018
	public function get_locale() {
1019
		$locale = get_locale();
1020
1021
		/*
1022
		 * Stripe expects Norwegian to only be passed NO.
1023
		 * But WP has different dialects.
1024
		 */
1025
		if ( 'NO' === substr( $locale, 3, 2 ) ) {
1026
			$locale = 'no';
1027
		} else {
1028
			$locale = substr( get_locale(), 0, 2 );
1029
		}
1030
1031
		return $locale;
1032
	}
1033
1034
	/**
1035
	 * Change the idempotency key so charge can
1036
	 * process order as a different transaction.
1037
	 *
1038
	 * @since 4.0.6
1039
	 * @param string $idempotency_key
1040
	 * @param array $request
1041
	 */
1042
	public function change_idempotency_key( $idempotency_key, $request ) {
1043
		$customer = ! empty( $request['customer'] ) ? $request['customer'] : '';
1044
		$source   = ! empty( $request['source'] ) ? $request['source'] : $customer;
1045
		$count    = $this->retry_interval;
1046
1047
		return $request['metadata']['order_id'] . '-' . $count . '-' . $source;
1048
	}
1049
1050
	/**
1051
	 * Checks if request is the original to prevent double processing
1052
	 * on WC side. The original-request header and request-id header
1053
	 * needs to be the same to mean its the original request.
1054
	 *
1055
	 * @since 4.0.6
1056
	 * @param array $headers
1057
	 */
1058
	public function is_original_request( $headers ) {
1059
		if ( $headers['original-request'] === $headers['request-id'] ) {
1060
			return true;
1061
		}
1062
1063
		return false;
1064
	}
1065
}
1066