Completed
Push — master ( e3a5fe...3adadd )
by Roy
05:10
created

WC_Stripe_Payment_Gateway::process_refund()   F

Complexity

Conditions 14
Paths 545

Size

Total Lines 67
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 14
eloc 36
nc 545
nop 3
dl 0
loc 67
rs 3.3955
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 this gateway is enabled
169
	 */
170
	public function is_available() {
171
		if ( 'yes' === $this->enabled ) {
172
			if ( ! $this->secret_key || ! $this->publishable_key ) {
173
				return false;
174
			}
175
			return true;
176
		}
177
178
		return parent::is_available();
179
	}
180
181
	/**
182
	 * Checks if we need to process pre orders when
183
	 * pre orders is in the cart.
184
	 *
185
	 * @since 4.1.0
186
	 * @param int $order_id
187
	 * @return bool
188
	 */
189
	public function maybe_process_pre_orders( $order_id ) {
190
		return (
191
			WC_Stripe_Helper::is_pre_orders_exists() &&
192
			$this->pre_orders->is_pre_order( $order_id ) &&
193
			WC_Pre_Orders_Order::order_requires_payment_tokenization( $order_id ) &&
194
			! is_wc_endpoint_url( 'order-pay' )
195
		);
196
	}
197
198
	/**
199
	 * Allow this class and other classes to add slug keyed notices (to avoid duplication).
200
	 *
201
	 * @since 1.0.0
202
	 * @version 4.0.0
203
	 */
204 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...
205
		$this->notices[ $slug ] = array(
206
			'class'       => $class,
207
			'message'     => $message,
208
			'dismissible' => $dismissible,
209
		);
210
	}
211
212
	/**
213
	 * All payment icons that work with Stripe. Some icons references
214
	 * WC core icons.
215
	 *
216
	 * @since 4.0.0
217
	 * @since 4.1.0 Changed to using img with svg (colored) instead of fonts.
218
	 * @return array
219
	 */
220
	public function payment_icons() {
221
		return apply_filters( 'wc_stripe_payment_icons', array(
222
			'visa'       => '<img src="' . WC()->plugin_url() . '/assets/images/icons/credit-cards/visa.svg" class="stripe-visa-icon stripe-icon" alt="Visa" />',
223
			'amex'       => '<img src="' . WC()->plugin_url() . '/assets/images/icons/credit-cards/amex.svg" class="stripe-amex-icon stripe-icon" alt="American Express" />',
224
			'mastercard' => '<img src="' . WC()->plugin_url() . '/assets/images/icons/credit-cards/mastercard.svg" class="stripe-mastercard-icon stripe-icon" alt="Mastercard" />',
225
			'discover'   => '<img src="' . WC()->plugin_url() . '/assets/images/icons/credit-cards/discover.svg" class="stripe-discover-icon stripe-icon" alt="Discover" />',
226
			'diners'     => '<img src="' . WC()->plugin_url() . '/assets/images/icons/credit-cards/diners.svg" class="stripe-diners-icon stripe-icon" alt="Diners" />',
227
			'jcb'        => '<img src="' . WC()->plugin_url() . '/assets/images/icons/credit-cards/jcb.svg" class="stripe-jcb-icon stripe-icon" alt="JCB" />',
228
			'alipay'     => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/alipay.svg" class="stripe-alipay-icon stripe-icon" alt="Alipay" />',
229
			'wechat'     => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/wechat.svg" class="stripe-wechat-icon stripe-icon" alt="Wechat Pay" />',
230
			'bitcoin'    => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/bitcoin.svg" class="stripe-bitcoin-icon stripe-icon" alt="Bitcoin" />',
231
			'bancontact' => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/bancontact.svg" class="stripe-bancontact-icon stripe-icon" alt="Bancontact" />',
232
			'ideal'      => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/ideal.svg" class="stripe-ideal-icon stripe-icon" alt="iDeal" />',
233
			'p24'        => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/p24.svg" class="stripe-p24-icon stripe-icon" alt="P24" />',
234
			'giropay'    => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/giropay.svg" class="stripe-giropay-icon stripe-icon" alt="Giropay" />',
235
			'eps'        => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/eps.svg" class="stripe-eps-icon stripe-icon" alt="EPS" />',
236
			'multibanco' => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/multibanco.svg" class="stripe-multibanco-icon stripe-icon" alt="Multibanco" />',
237
			'sofort'     => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/sofort.svg" class="stripe-sofort-icon stripe-icon" alt="SOFORT" />',
238
			'sepa'       => '<img src="' . WC_STRIPE_PLUGIN_URL . '/assets/images/sepa.svg" class="stripe-sepa-icon stripe-icon" alt="SEPA" />',
239
		) );
240
	}
241
242
	/**
243
	 * Validates that the order meets the minimum order amount
244
	 * set by Stripe.
245
	 *
246
	 * @since 4.0.0
247
	 * @version 4.0.0
248
	 * @param object $order
249
	 */
250
	public function validate_minimum_order_amount( $order ) {
251
		if ( $order->get_total() * 100 < WC_Stripe_Helper::get_minimum_amount() ) {
252
			/* translators: 1) dollar amount */
253
			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 ) ) );
254
		}
255
	}
256
257
	/**
258
	 * Gets the transaction URL linked to Stripe dashboard.
259
	 *
260
	 * @since 4.0.0
261
	 * @version 4.0.0
262
	 */
263
	public function get_transaction_url( $order ) {
264
		if ( $this->testmode ) {
265
			$this->view_transaction_url = 'https://dashboard.stripe.com/test/payments/%s';
266
		} else {
267
			$this->view_transaction_url = 'https://dashboard.stripe.com/payments/%s';
268
		}
269
270
		return parent::get_transaction_url( $order );
271
	}
272
273
	/**
274
	 * Gets the saved customer id if exists.
275
	 *
276
	 * @since 4.0.0
277
	 * @version 4.0.0
278
	 */
279
	public function get_stripe_customer_id( $order ) {
280
		$customer = get_user_meta( WC_Stripe_Helper::is_pre_30() ? $order->customer_user : $order->get_customer_id(), '_stripe_customer_id', true );
281
282
		if ( empty( $customer ) ) {
283
			// Try to get it via the order.
284
			if ( WC_Stripe_Helper::is_pre_30() ) {
285
				return get_post_meta( $order->id, '_stripe_customer_id', true );
286
			} else {
287
				return $order->get_meta( '_stripe_customer_id', true );
288
			}
289
		} else {
290
			return $customer;
291
		}
292
293
		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...
294
	}
295
296
	/**
297
	 * Builds the return URL from redirects.
298
	 *
299
	 * @since 4.0.0
300
	 * @version 4.0.0
301
	 * @param object $order
302
	 * @param int $id Stripe session id.
303
	 */
304
	public function get_stripe_return_url( $order = null, $id = null ) {
305
		if ( is_object( $order ) ) {
306
			if ( empty( $id ) ) {
307
				$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...
308
			}
309
310
			$order_id = WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id();
311
312
			$args = array(
313
				'utm_nooverride' => '1',
314
				'order_id'       => $order_id,
315
			);
316
317
			return esc_url_raw( add_query_arg( $args, $this->get_return_url( $order ) ) );
318
		}
319
320
		return esc_url_raw( add_query_arg( array( 'utm_nooverride' => '1' ), $this->get_return_url() ) );
321
	}
322
323
	/**
324
	 * Is $order_id a subscription?
325
	 * @param  int  $order_id
326
	 * @return boolean
327
	 */
328
	public function has_subscription( $order_id ) {
329
		return ( function_exists( 'wcs_order_contains_subscription' ) && ( wcs_order_contains_subscription( $order_id ) || wcs_is_subscription( $order_id ) || wcs_order_contains_renewal( $order_id ) ) );
330
	}
331
332
	/**
333
	 * Generate the request for the payment.
334
	 *
335
	 * @since 3.1.0
336
	 * @version 4.0.0
337
	 * @param  WC_Order $order
338
	 * @param  object $prepared_source
339
	 * @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...
340
	 */
341
	public function generate_payment_request( $order, $prepared_source ) {
342
		$settings                          = get_option( 'woocommerce_stripe_settings', array() );
343
		$statement_descriptor              = ! empty( $settings['statement_descriptor'] ) ? str_replace( "'", '', $settings['statement_descriptor'] ) : '';
344
		$capture                           = ! empty( $settings['capture'] ) && 'yes' === $settings['capture'] ? true : false;
345
		$post_data                         = array();
346
		$post_data['currency']             = strtolower( WC_Stripe_Helper::is_pre_30() ? $order->get_order_currency() : $order->get_currency() );
347
		$post_data['amount']               = WC_Stripe_Helper::get_stripe_amount( $order->get_total(), $post_data['currency'] );
348
		/* translators: 1) blog name 2) order number */
349
		$post_data['description']          = sprintf( __( '%1$s - Order %2$s', 'woocommerce-gateway-stripe' ), wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ), $order->get_order_number() );
350
		$billing_email      = WC_Stripe_Helper::is_pre_30() ? $order->billing_email : $order->get_billing_email();
351
		$billing_first_name = WC_Stripe_Helper::is_pre_30() ? $order->billing_first_name : $order->get_billing_first_name();
352
		$billing_last_name  = WC_Stripe_Helper::is_pre_30() ? $order->billing_last_name : $order->get_billing_last_name();
353
354
		if ( ! empty( $billing_email ) && apply_filters( 'wc_stripe_send_stripe_receipt', false ) ) {
355
			$post_data['receipt_email'] = $billing_email;
356
		}
357
358
		switch ( WC_Stripe_Helper::is_pre_30() ? $order->payment_method : $order->get_payment_method() ) {
359
			case 'stripe':
360
				if ( ! empty( $statement_descriptor ) ) {
361
					$post_data['statement_descriptor'] = WC_Stripe_Helper::clean_statement_descriptor( $statement_descriptor );
362
				}
363
364
				$post_data['capture'] = $capture ? 'true' : 'false';
365
				break;
366
			case 'stripe_sepa':
367
				if ( ! empty( $statement_descriptor ) ) {
368
					$post_data['statement_descriptor'] = WC_Stripe_Helper::clean_statement_descriptor( $statement_descriptor );
369
				}
370
				break;
371
		}
372
373
		$post_data['expand[]'] = 'balance_transaction';
374
375
		$metadata = array(
376
			__( 'customer_name', 'woocommerce-gateway-stripe' ) => sanitize_text_field( $billing_first_name ) . ' ' . sanitize_text_field( $billing_last_name ),
377
			__( 'customer_email', 'woocommerce-gateway-stripe' ) => sanitize_email( $billing_email ),
378
			'order_id' => $order->get_order_number(),
379
		);
380
381
		if ( $this->has_subscription( WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id() ) ) {
382
			$metadata += array(
383
				'payment_type' => 'recurring',
384
				'site_url'     => esc_url( get_site_url() ),
385
			);
386
		}
387
388
		$post_data['metadata'] = apply_filters( 'wc_stripe_payment_metadata', $metadata, $order, $prepared_source );
389
390
		if ( $prepared_source->customer ) {
391
			$post_data['customer'] = $prepared_source->customer;
392
		}
393
394
		if ( $prepared_source->source ) {
395
			$post_data['source'] = $prepared_source->source;
396
		}
397
398
		/**
399
		 * Filter the return value of the WC_Payment_Gateway_CC::generate_payment_request.
400
		 *
401
		 * @since 3.1.0
402
		 * @param array $post_data
403
		 * @param WC_Order $order
404
		 * @param object $source
405
		 */
406
		return apply_filters( 'wc_stripe_generate_payment_request', $post_data, $order, $prepared_source );
407
	}
408
409
	/**
410
	 * Store extra meta data for an order from a Stripe Response.
411
	 */
412
	public function process_response( $response, $order ) {
413
		WC_Stripe_Logger::log( 'Processing response: ' . print_r( $response, true ) );
414
415
		$order_id = WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id();
416
417
		$captured = ( isset( $response->captured ) && $response->captured ) ? 'yes' : 'no';
418
419
		// Store charge data.
420
		WC_Stripe_Helper::is_pre_30() ? update_post_meta( $order_id, '_stripe_charge_captured', $captured ) : $order->update_meta_data( '_stripe_charge_captured', $captured );
421
422
		// Store other data such as fees.
423
		if ( isset( $response->balance_transaction ) && isset( $response->balance_transaction->fee ) ) {
424
			// Fees and Net needs to both come from Stripe to be accurate as the returned
425
			// values are in the local currency of the Stripe account, not from WC.
426
			$fee = ! empty( $response->balance_transaction->fee ) ? WC_Stripe_Helper::format_balance_fee( $response->balance_transaction, 'fee' ) : 0;
427
			$net = ! empty( $response->balance_transaction->net ) ? WC_Stripe_Helper::format_balance_fee( $response->balance_transaction, 'net' ) : 0;
428
			WC_Stripe_Helper::update_stripe_fee( $order, $fee );
429
			WC_Stripe_Helper::update_stripe_net( $order, $net );
430
431
			// Store currency stripe.
432
			$currency = ! empty( $response->balance_transaction->currency ) ? strtoupper( $response->balance_transaction->currency ) : null;
433
			WC_Stripe_Helper::update_stripe_currency( $order, $currency );
434
		}
435
436
		if ( 'yes' === $captured ) {
437
			/**
438
			 * Charge can be captured but in a pending state. Payment methods
439
			 * that are asynchronous may take couple days to clear. Webhook will
440
			 * take care of the status changes.
441
			 */
442
			if ( 'pending' === $response->status ) {
443
				$order_stock_reduced = WC_Stripe_Helper::is_pre_30() ? get_post_meta( $order_id, '_order_stock_reduced', true ) : $order->get_meta( '_order_stock_reduced', true );
444
445
				if ( ! $order_stock_reduced ) {
446
					WC_Stripe_Helper::is_pre_30() ? $order->reduce_order_stock() : wc_reduce_stock_levels( $order_id );
447
				}
448
449
				WC_Stripe_Helper::is_pre_30() ? update_post_meta( $order_id, '_transaction_id', $response->id ) : $order->set_transaction_id( $response->id );
450
				/* translators: transaction id */
451
				$order->update_status( 'on-hold', sprintf( __( 'Stripe charge awaiting payment: %s.', 'woocommerce-gateway-stripe' ), $response->id ) );
452
			}
453
454
			if ( 'succeeded' === $response->status ) {
455
				$order->payment_complete( $response->id );
456
457
				/* translators: transaction id */
458
				$message = sprintf( __( 'Stripe charge complete (Charge ID: %s)', 'woocommerce-gateway-stripe' ), $response->id );
459
				$order->add_order_note( $message );
460
			}
461
462
			if ( 'failed' === $response->status ) {
463
				$localized_message = __( 'Payment processing failed. Please retry.', 'woocommerce-gateway-stripe' );
464
				$order->add_order_note( $localized_message );
465
				throw new WC_Stripe_Exception( print_r( $response, true ), $localized_message );
466
			}
467
		} else {
468
			WC_Stripe_Helper::is_pre_30() ? update_post_meta( $order_id, '_transaction_id', $response->id ) : $order->set_transaction_id( $response->id );
469
470
			if ( $order->has_status( array( 'pending', 'failed' ) ) ) {
471
				WC_Stripe_Helper::is_pre_30() ? $order->reduce_order_stock() : wc_reduce_stock_levels( $order_id );
472
			}
473
474
			/* translators: transaction id */
475
			$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 ) );
476
		}
477
478
		if ( is_callable( array( $order, 'save' ) ) ) {
479
			$order->save();
480
		}
481
482
		do_action( 'wc_gateway_stripe_process_response', $response, $order );
483
484
		return $response;
485
	}
486
487
	/**
488
	 * Sends the failed order email to admin.
489
	 *
490
	 * @since 3.1.0
491
	 * @version 4.0.0
492
	 * @param int $order_id
493
	 * @return null
494
	 */
495
	public function send_failed_order_email( $order_id ) {
496
		$emails = WC()->mailer()->get_emails();
497
		if ( ! empty( $emails ) && ! empty( $order_id ) ) {
498
			$emails['WC_Email_Failed_Order']->trigger( $order_id );
499
		}
500
	}
501
502
	/**
503
	 * Get owner details.
504
	 *
505
	 * @since 4.0.0
506
	 * @version 4.0.0
507
	 * @param object $order
508
	 * @return object $details
509
	 */
510
	public function get_owner_details( $order ) {
511
		$billing_first_name = WC_Stripe_Helper::is_pre_30() ? $order->billing_first_name : $order->get_billing_first_name();
512
		$billing_last_name  = WC_Stripe_Helper::is_pre_30() ? $order->billing_last_name : $order->get_billing_last_name();
513
514
		$details = array();
515
516
		$name  = $billing_first_name . ' ' . $billing_last_name;
517
		$email = WC_Stripe_Helper::is_pre_30() ? $order->billing_email : $order->get_billing_email();
518
		$phone = WC_Stripe_Helper::is_pre_30() ? $order->billing_phone : $order->get_billing_phone();
519
520
		if ( ! empty( $phone ) ) {
521
			$details['phone'] = $phone;
522
		}
523
524
		if ( ! empty( $name ) ) {
525
			$details['name'] = $name;
526
		}
527
528
		if ( ! empty( $email ) ) {
529
			$details['email'] = $email;
530
		}
531
532
		$details['address']['line1']       = WC_Stripe_Helper::is_pre_30() ? $order->billing_address_1 : $order->get_billing_address_1();
533
		$details['address']['line2']       = WC_Stripe_Helper::is_pre_30() ? $order->billing_address_2 : $order->get_billing_address_2();
534
		$details['address']['state']       = WC_Stripe_Helper::is_pre_30() ? $order->billing_state : $order->get_billing_state();
535
		$details['address']['city']        = WC_Stripe_Helper::is_pre_30() ? $order->billing_city : $order->get_billing_city();
536
		$details['address']['postal_code'] = WC_Stripe_Helper::is_pre_30() ? $order->billing_postcode : $order->get_billing_postcode();
537
		$details['address']['country']     = WC_Stripe_Helper::is_pre_30() ? $order->billing_country : $order->get_billing_country();
538
539
		return (object) apply_filters( 'wc_stripe_owner_details', $details, $order );
540
	}
541
542
	/**
543
	 * Get source object by source id.
544
	 *
545
	 * @since 4.0.3
546
	 * @param string $source_id The source ID to get source object for.
547
	 */
548
	public function get_source_object( $source_id = '' ) {
549
		if ( empty( $source_id ) ) {
550
			return '';
551
		}
552
553
		$source_object = WC_Stripe_API::retrieve( 'sources/' . $source_id );
554
555
		if ( ! empty( $source_object->error ) ) {
556
			throw new WC_Stripe_Exception( print_r( $source_object, true ), $source_object->error->message );
557
		}
558
559
		return $source_object;
560
	}
561
562
	/**
563
	 * Checks if 3DS is required.
564
	 *
565
	 * @since 4.0.4
566
	 * @since 4.1.0 Add filter and changed optional to recommended.
567
	 * @param object $source_object
568
	 * @return bool
569
	 */
570
	public function is_3ds_required( $source_object ) {
571
		return apply_filters( 'wc_stripe_require_3ds', (
572
			$source_object && ! empty( $source_object->card ) ) &&
573
			( 'card' === $source_object->type && 'required' === $source_object->card->three_d_secure ||
574
			( $this->three_d_secure && 'recommended' === $source_object->card->three_d_secure )
575
		), $source_object, $this->three_d_secure );
576
	}
577
578
	/**
579
	 * Checks if card is 3DS.
580
	 *
581
	 * @since 4.0.4
582
	 * @param object $source_object
583
	 * @return bool
584
	 */
585
	public function is_3ds_card( $source_object ) {
586
		return ( $source_object && 'three_d_secure' === $source_object->type );
587
	}
588
589
	/**
590
	 * Checks if card is a prepaid card.
591
	 *
592
	 * @since 4.0.6
593
	 * @param object $source_object
594
	 * @return bool
595
	 */
596
	public function is_prepaid_card( $source_object ) {
597
		return ( $source_object && 'token' === $source_object->object && 'prepaid' === $source_object->card->funding );
598
	}
599
600
	/**
601
	 * Checks if source is of legacy type card.
602
	 *
603
	 * @since 4.0.8
604
	 * @param string $source_id
605
	 * @return bool
606
	 */
607
	public function is_type_legacy_card( $source_id ) {
608
		return ( preg_match( '/^card_/', $source_id ) );
609
	}
610
611
	/**
612
	 * Checks if payment is via saved payment source.
613
	 *
614
	 * @since 4.1.0
615
	 * @return bool
616
	 */
617
	public function is_using_saved_payment_method() {
618
		$payment_method = isset( $_POST['payment_method'] ) ? wc_clean( $_POST['payment_method'] ) : 'stripe';
619
620
		return ( isset( $_POST[ 'wc-' . $payment_method . '-payment-token' ] ) && 'new' !== $_POST[ 'wc-' . $payment_method . '-payment-token' ] );
621
	}
622
623
	/**
624
	 * Creates the 3DS source for charge.
625
	 *
626
	 * @since 4.0.0
627
	 * @since 4.0.4 Add $return_url
628
	 * @param object $order
629
	 * @param object $source_object
630
	 * @param string $return_url
631
	 * @return mixed
632
	 */
633
	public function create_3ds_source( $order, $source_object, $return_url = '' ) {
634
		$currency                    = WC_Stripe_Helper::is_pre_30() ? $order->get_order_currency() : $order->get_currency();
635
		$order_id                    = WC_Stripe_Helper::is_pre_30() ? $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...
636
		$return_url                  = empty( $return_url ) ? $this->get_stripe_return_url( $order ) : $return_url;
637
638
		$post_data                   = array();
639
		$post_data['amount']         = WC_Stripe_Helper::get_stripe_amount( $order->get_total(), $currency );
640
		$post_data['currency']       = strtolower( $currency );
641
		$post_data['type']           = 'three_d_secure';
642
		$post_data['owner']          = $this->get_owner_details( $order );
643
		$post_data['three_d_secure'] = array( 'card' => $source_object->id );
644
		$post_data['redirect']       = array( 'return_url' => $return_url );
645
646
		WC_Stripe_Logger::log( 'Info: Begin creating 3DS source...' );
647
648
		return WC_Stripe_API::request( apply_filters( 'wc_stripe_3ds_source', $post_data, $order ), 'sources' );
649
	}
650
651
	/**
652
	 * Get payment source. This can be a new token/source or existing WC token.
653
	 * If user is logged in and/or has WC account, create an account on Stripe.
654
	 * This way we can attribute the payment to the user to better fight fraud.
655
	 *
656
	 * @since 3.1.0
657
	 * @version 4.0.0
658
	 * @param string $user_id
659
	 * @param bool $force_save_source Should we force save payment source.
660
	 *
661
	 * @throws Exception When card was not added or for and invalid card.
662
	 * @return object
663
	 */
664
	public function prepare_source( $user_id, $force_save_source = false ) {
665
		$customer           = new WC_Stripe_Customer( $user_id );
666
		$set_customer       = true;
667
		$force_save_source  = apply_filters( 'wc_stripe_force_save_source', $force_save_source, $customer );
668
		$source_object      = '';
669
		$source_id          = '';
670
		$wc_token_id        = false;
671
		$payment_method     = isset( $_POST['payment_method'] ) ? wc_clean( $_POST['payment_method'] ) : 'stripe';
672
		$is_token           = false;
673
674
		// New CC info was entered and we have a new source to process.
675
		if ( ! empty( $_POST['stripe_source'] ) ) {
676
			$source_object = self::get_source_object( wc_clean( $_POST['stripe_source'] ) );
677
			$source_id     = $source_object->id;
678
679
			// This checks to see if customer opted to save the payment method to file.
680
			$maybe_saved_card = isset( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] ) && ! empty( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] );
681
682
			/**
683
			 * This is true if the user wants to store the card to their account.
684
			 * Criteria to save to file is they are logged in, they opted to save or product requirements and the source is
685
			 * actually reusable. Either that or force_save_source is true.
686
			 */
687
			if ( ( $user_id && $this->saved_cards && $maybe_saved_card && 'reusable' === $source_object->usage ) || $force_save_source ) {
688
				$response = $customer->add_source( $source_object->id );
689
690
				if ( ! empty( $response->error ) ) {
691
					throw new WC_Stripe_Exception( print_r( $response, true ), $response->error->message );
692
				}
693
			}
694
		} elseif ( $this->is_using_saved_payment_method() ) {
695
			// Use an existing token, and then process the payment.
696
			$wc_token_id = wc_clean( $_POST[ 'wc-' . $payment_method . '-payment-token' ] );
697
			$wc_token    = WC_Payment_Tokens::get( $wc_token_id );
698
699
			if ( ! $wc_token || $wc_token->get_user_id() !== get_current_user_id() ) {
700
				WC()->session->set( 'refresh_totals', true );
701
				throw new WC_Stripe_Exception( 'Invalid payment method', __( 'Invalid payment method. Please input a new card number.', 'woocommerce-gateway-stripe' ) );
702
			}
703
704
			$source_id = $wc_token->get_token();
705
706
			if ( $this->is_type_legacy_card( $source_id ) ) {
707
				$is_token = true;
708
			}
709
		} elseif ( isset( $_POST['stripe_token'] ) && 'new' !== $_POST['stripe_token'] ) {
710
			$stripe_token     = wc_clean( $_POST['stripe_token'] );
711
			$maybe_saved_card = isset( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] ) && ! empty( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] );
712
713
			// This is true if the user wants to store the card to their account.
714
			if ( ( $user_id && $this->saved_cards && $maybe_saved_card ) || $force_save_source ) {
715
				$response = $customer->add_source( $stripe_token );
716
717
				if ( ! empty( $response->error ) ) {
718
					throw new WC_Stripe_Exception( print_r( $response, true ), $response->error->message );
719
				}
720
			} else {
721
				$set_customer = false;
722
				$source_id    = $stripe_token;
723
				$is_token     = true;
724
			}
725
		}
726
727
		if ( ! $set_customer ) {
728
			$customer_id = false;
729
		} else {
730
			$customer_id = $customer->get_id() ? $customer->get_id() : false;
731
		}
732
733
		if ( empty( $source_object ) && ! $is_token ) {
734
			$source_object = self::get_source_object( $source_id );
735
		}
736
737
		return (object) array(
738
			'token_id'      => $wc_token_id,
739
			'customer'      => $customer_id,
740
			'source'        => $source_id,
741
			'source_object' => $source_object,
742
		);
743
	}
744
745
	/**
746
	 * Get payment source from an order. This could be used in the future for
747
	 * a subscription as an example, therefore using the current user ID would
748
	 * not work - the customer won't be logged in :)
749
	 *
750
	 * Not using 2.6 tokens for this part since we need a customer AND a card
751
	 * token, and not just one.
752
	 *
753
	 * @since 3.1.0
754
	 * @version 4.0.0
755
	 * @param object $order
756
	 * @return object
757
	 */
758
	public function prepare_order_source( $order = null ) {
759
		$stripe_customer = new WC_Stripe_Customer();
760
		$stripe_source   = false;
761
		$token_id        = false;
762
		$source_object   = false;
763
764
		if ( $order ) {
765
			$order_id = WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id();
766
767
			$stripe_customer_id = get_post_meta( $order_id, '_stripe_customer_id', true );
768
769
			if ( $stripe_customer_id ) {
770
				$stripe_customer->set_id( $stripe_customer_id );
771
			}
772
773
			$source_id = WC_Stripe_Helper::is_pre_30() ? get_post_meta( $order_id, '_stripe_source_id', true ) : $order->get_meta( '_stripe_source_id', true );
774
775
			// Since 4.0.0, we changed card to source so we need to account for that.
776
			if ( empty( $source_id ) ) {
777
				$source_id = WC_Stripe_Helper::is_pre_30() ? get_post_meta( $order_id, '_stripe_card_id', true ) : $order->get_meta( '_stripe_card_id', true );
778
779
				// Take this opportunity to update the key name.
780
				WC_Stripe_Helper::is_pre_30() ? update_post_meta( $order_id, '_stripe_source_id', $source_id ) : $order->update_meta_data( '_stripe_source_id', $source_id );
781
782
				if ( is_callable( array( $order, 'save' ) ) ) {
783
					$order->save();
784
				}
785
			}
786
787
			if ( $source_id ) {
788
				$stripe_source = $source_id;
789
				$source_object = WC_Stripe_API::retrieve( 'sources/' . $source_id );
790
			} elseif ( apply_filters( 'wc_stripe_use_default_customer_source', true ) ) {
791
				/*
792
				 * We can attempt to charge the customer's default source
793
				 * by sending empty source id.
794
				 */
795
				$stripe_source = '';
796
			}
797
		}
798
799
		return (object) array(
800
			'token_id'      => $token_id,
801
			'customer'      => $stripe_customer ? $stripe_customer->get_id() : false,
802
			'source'        => $stripe_source,
803
			'source_object' => $source_object,
804
		);
805
	}
806
807
	/**
808
	 * Save source to order.
809
	 *
810
	 * @since 3.1.0
811
	 * @version 4.0.0
812
	 * @param WC_Order $order For to which the source applies.
813
	 * @param stdClass $source Source information.
814
	 */
815
	public function save_source_to_order( $order, $source ) {
816
		$order_id = WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id();
817
818
		// Store source in the order.
819
		if ( $source->customer ) {
820
			if ( WC_Stripe_Helper::is_pre_30() ) {
821
				update_post_meta( $order_id, '_stripe_customer_id', $source->customer );
822
			} else {
823
				$order->update_meta_data( '_stripe_customer_id', $source->customer );
824
			}
825
		}
826
827
		if ( $source->source ) {
828
			if ( WC_Stripe_Helper::is_pre_30() ) {
829
				update_post_meta( $order_id, '_stripe_source_id', $source->source );
830
			} else {
831
				$order->update_meta_data( '_stripe_source_id', $source->source );
832
			}
833
		}
834
835
		if ( is_callable( array( $order, 'save' ) ) ) {
836
			$order->save();
837
		}
838
	}
839
840
	/**
841
	 * Updates Stripe fees/net.
842
	 * e.g usage would be after a refund.
843
	 *
844
	 * @since 4.0.0
845
	 * @version 4.0.6
846
	 * @param object $order The order object
847
	 * @param int $balance_transaction_id
848
	 */
849
	public function update_fees( $order, $balance_transaction_id ) {
850
		$order_id = WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id();
851
852
		$balance_transaction = WC_Stripe_API::retrieve( 'balance/history/' . $balance_transaction_id );
853
854
		if ( empty( $balance_transaction->error ) ) {
855
			if ( isset( $balance_transaction ) && isset( $balance_transaction->fee ) ) {
856
				// Fees and Net needs to both come from Stripe to be accurate as the returned
857
				// values are in the local currency of the Stripe account, not from WC.
858
				$fee_refund = ! empty( $balance_transaction->fee ) ? WC_Stripe_Helper::format_balance_fee( $balance_transaction, 'fee' ) : 0;
859
				$net_refund = ! empty( $balance_transaction->net ) ? WC_Stripe_Helper::format_balance_fee( $balance_transaction, 'net' ) : 0;
860
861
				// Current data fee & net.
862
				$fee_current = WC_Stripe_Helper::get_stripe_fee( $order );
863
				$net_current = WC_Stripe_Helper::get_stripe_net( $order );
864
865
				// Calculation.
866
				$fee = (float) $fee_current + (float) $fee_refund;
867
				$net = (float) $net_current + (float) $net_refund;
868
869
				WC_Stripe_Helper::update_stripe_fee( $order, $fee );
870
				WC_Stripe_Helper::update_stripe_net( $order, $net );
871
872
				if ( is_callable( array( $order, 'save' ) ) ) {
873
					$order->save();
874
				}
875
			}
876
		} else {
877
			WC_Stripe_Logger::log( "Unable to update fees/net meta for order: {$order_id}" );
878
		}
879
	}
880
881
	/**
882
	 * Refund a charge.
883
	 *
884
	 * @since 3.1.0
885
	 * @version 4.0.0
886
	 * @param  int $order_id
887
	 * @param  float $amount
888
	 * @return bool
889
	 */
890
	public function process_refund( $order_id, $amount = null, $reason = '' ) {
891
		$order = wc_get_order( $order_id );
892
893
		if ( ! $order || ! $order->get_transaction_id() ) {
894
			return false;
895
		}
896
897
		$request = array();
898
899
		if ( WC_Stripe_Helper::is_pre_30() ) {
900
			$order_currency = get_post_meta( $order_id, '_order_currency', true );
901
			$captured       = get_post_meta( $order_id, '_stripe_charge_captured', true );
902
		} else {
903
			$order_currency = $order->get_currency();
904
			$captured       = $order->get_meta( '_stripe_charge_captured', true );
905
		}
906
907
		if ( ! is_null( $amount ) ) {
908
			$request['amount'] = WC_Stripe_Helper::get_stripe_amount( $amount, $order_currency );
909
		}
910
911
		// If order is only authorized, don't pass amount.
912
		if ( 'yes' !== $captured ) {
913
			unset( $request['amount'] );
914
		}
915
916
		if ( $reason ) {
917
			$request['metadata'] = array(
918
				'reason' => $reason,
919
			);
920
		}
921
922
		$request['charge'] = $order->get_transaction_id();
923
924
		WC_Stripe_Logger::log( "Info: Beginning refund for order {$order->get_transaction_id()} for the amount of {$amount}" );
925
926
		$request = apply_filters( 'wc_stripe_refund_request', $request, $order );
927
928
		$response = WC_Stripe_API::request( $request, 'refunds' );
929
930
		if ( ! empty( $response->error ) ) {
931
			WC_Stripe_Logger::log( 'Error: ' . $response->error->message );
932
933
			return $response;
934
935
		} elseif ( ! empty( $response->id ) ) {
936
			WC_Stripe_Helper::is_pre_30() ? update_post_meta( $order_id, '_stripe_refund_id', $response->id ) : $order->update_meta_data( '_stripe_refund_id', $response->id );
937
938
			$amount = wc_price( $response->amount / 100 );
939
940
			if ( in_array( strtolower( $order->get_currency() ), WC_Stripe_Helper::no_decimal_currencies() ) ) {
941
				$amount = wc_price( $response->amount );
942
			}
943
944
			if ( isset( $response->balance_transaction ) ) {
945
				$this->update_fees( $order, $response->balance_transaction );
946
			}
947
948
			/* translators: 1) dollar amount 2) transaction id 3) refund message */
949
			$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' );
950
951
			$order->add_order_note( $refund_message );
952
			WC_Stripe_Logger::log( 'Success: ' . html_entity_decode( strip_tags( $refund_message ) ) );
953
954
			return true;
955
		}
956
	}
957
958
	/**
959
	 * Add payment method via account screen.
960
	 * We don't store the token locally, but to the Stripe API.
961
	 *
962
	 * @since 3.0.0
963
	 * @version 4.0.0
964
	 */
965
	public function add_payment_method() {
966
		$error     = false;
967
		$error_msg = __( 'There was a problem adding the card.', 'woocommerce-gateway-stripe' );
968
		$source_id = '';
969
970
		if ( empty( $_POST['stripe_source'] ) && empty( $_POST['stripe_token'] ) || ! is_user_logged_in() ) {
971
			$error = true;
972
		}
973
974
		$stripe_customer = new WC_Stripe_Customer( get_current_user_id() );
975
976
		$source = ! empty( $_POST['stripe_source'] ) ? wc_clean( $_POST['stripe_source'] ) : '';
977
978
		$source_object = WC_Stripe_API::retrieve( 'sources/' . $source );
979
980
		if ( isset( $source_object ) ) {
981
			if ( ! empty( $source_object->error ) ) {
982
				$error = true;
983
			}
984
985
			$source_id = $source_object->id;
986
		} elseif ( isset( $_POST['stripe_token'] ) ) {
987
			$source_id = wc_clean( $_POST['stripe_token'] );
988
		}
989
990
		$response = $stripe_customer->add_source( $source_id );
991
992
		if ( ! $response || is_wp_error( $response ) || ! empty( $response->error ) ) {
993
			$error = true;
994
		}
995
996
		if ( $error ) {
997
			wc_add_notice( $error_msg, 'error' );
998
			WC_Stripe_Logger::log( 'Add payment method Error: ' . $error_msg );
999
			return;
1000
		}
1001
1002
		return array(
1003
			'result'   => 'success',
1004
			'redirect' => wc_get_endpoint_url( 'payment-methods' ),
1005
		);
1006
	}
1007
1008
	/**
1009
	 * Gets the locale with normalization that only Stripe accepts.
1010
	 *
1011
	 * @since 4.0.6
1012
	 * @return string $locale
1013
	 */
1014
	public function get_locale() {
1015
		$locale = get_locale();
1016
1017
		/*
1018
		 * Stripe expects Norwegian to only be passed NO.
1019
		 * But WP has different dialects.
1020
		 */
1021
		if ( 'NO' === substr( $locale, 3, 2 ) ) {
1022
			$locale = 'no';
1023
		} else {
1024
			$locale = substr( get_locale(), 0, 2 );
1025
		}
1026
1027
		return $locale;
1028
	}
1029
1030
	/**
1031
	 * Change the idempotency key so charge can
1032
	 * process order as a different transaction.
1033
	 *
1034
	 * @since 4.0.6
1035
	 * @param string $idempotency_key
1036
	 * @param array $request
1037
	 */
1038
	public function change_idempotency_key( $idempotency_key, $request ) {
1039
		$customer = ! empty( $request['customer'] ) ? $request['customer'] : '';
1040
		$source   = ! empty( $request['source'] ) ? $request['source'] : $customer;
1041
		$count    = $this->retry_interval;
1042
1043
		return $request['metadata']['order_id'] . '-' . $count . '-' . $source;
1044
	}
1045
1046
	/**
1047
	 * Checks if request is the original to prevent double processing
1048
	 * on WC side. The original-request header and request-id header
1049
	 * needs to be the same to mean its the original request.
1050
	 *
1051
	 * @since 4.0.6
1052
	 * @param array $headers
1053
	 */
1054
	public function is_original_request( $headers ) {
1055
		if ( $headers['original-request'] === $headers['request-id'] ) {
1056
			return true;
1057
		}
1058
1059
		return false;
1060
	}
1061
}
1062