Completed
Push — master ( cb7011...d6e3eb )
by Roy
04:17
created

WC_Stripe_Payment_Gateway::remove_admin_notice()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 0
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
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
	const META_NAME_FEE = 'Stripe Fee';
15
	const META_NAME_NET = 'Net Revenue From Stripe';
16
17
	/**
18
	 * Checks to see if request is invalid and that
19
	 * they are worth retrying.
20
	 *
21
	 * @since 4.0.5
22
	 * @param array $error
23
	 */
24
	public function is_retryable_error( $error ) {
25
		return (
26
			'invalid_request_error' === $error->type ||
27
			'idempotency_error' === $error->type ||
28
			'rate_limit_error' === $error->type
29
		);
30
	}
31
32
	/**
33
	 * Check if this gateway is enabled
34
	 */
35
	public function is_available() {
36
		if ( 'yes' === $this->enabled ) {
37
			if ( ! $this->testmode && is_checkout() && ! is_ssl() ) {
38
				return false;
39
			}
40
			if ( ! $this->secret_key || ! $this->publishable_key ) {
41
				return false;
42
			}
43
			return true;
44
		}
45
46
		return parent::is_available();
47
	}
48
49
	/**
50
	 * Allow this class and other classes to add slug keyed notices (to avoid duplication).
51
	 *
52
	 * @since 4.0.0
53
	 * @version 4.0.0
54
	 */
55
	public function add_admin_notice( $slug, $class, $message ) {
56
		$this->notices[ $slug ] = array(
57
			'class'   => $class,
58
			'message' => $message,
59
		);
60
	}
61
62
	/**
63
	 * Remove admin notice.
64
	 *
65
	 * @since 4.0.0
66
	 * @version 4.0.0
67
	 */
68
	public function remove_admin_notice() {
69
		if ( did_action( 'woocommerce_update_options' ) ) {
70
			remove_action( 'admin_notices', array( $this, 'check_environment' ) );
71
		}
72
	}
73
74
	/**
75
	 * All payment icons that work with Stripe.
76
	 *
77
	 * @since 4.0.0
78
	 * @version 4.0.0
79
	 * @return array
80
	 */
81
	public function payment_icons() {
82
		return apply_filters( 'wc_stripe_payment_icons', array(
83
			'visa'       => '<i class="stripe-pf stripe-pf-visa stripe-pf-right" alt="Visa" aria-hidden="true"></i>',
84
			'amex'       => '<i class="stripe-pf stripe-pf-american-express stripe-pf-right" alt="Amex" aria-hidden="true"></i>',
85
			'mastercard' => '<i class="stripe-pf stripe-pf-mastercard stripe-pf-right" alt="Mastercard" aria-hidden="true"></i>',
86
			'discover'   => '<i class="stripe-pf stripe-pf-discover stripe-pf-right" alt="Discover" aria-hidden="true"></i>',
87
			'diners'     => '<i class="stripe-pf stripe-pf-diners stripe-pf-right" alt="Diners" aria-hidden="true"></i>',
88
			'jcb'        => '<i class="stripe-pf stripe-pf-jcb stripe-pf-right" alt="JCB" aria-hidden="true"></i>',
89
			'alipay'     => '<i class="stripe-pf stripe-pf-alipay stripe-pf-right" alt="Alipay" aria-hidden="true"></i>',
90
			'wechat'     => '<i class="stripe-pf stripe-pf-wechat-pay stripe-pf-right" alt="Wechat Pay" aria-hidden="true"></i>',
91
			'bitcoin'    => '<i class="stripe-pf stripe-pf-bitcoin stripe-pf-right" alt="Bitcoin" aria-hidden="true"></i>',
92
			'bancontact' => '<i class="stripe-pf stripe-pf-bancontact-mister-cash stripe-pf-right" alt="Bancontact" aria-hidden="true"></i>',
93
			'ideal'      => '<i class="stripe-pf stripe-pf-ideal stripe-pf-right" alt="iDeal" aria-hidden="true"></i>',
94
			'p24'        => '<i class="stripe-pf stripe-pf-p24 stripe-pf-right" alt="P24" aria-hidden="true"></i>',
95
			'giropay'    => '<i class="stripe-pf stripe-pf-giropay stripe-pf-right" alt="Giropay" aria-hidden="true"></i>',
96
			'eps'        => '<i class="stripe-pf stripe-pf-eps stripe-pf-right" alt="EPS" aria-hidden="true"></i>',
97
			'sofort'     => '<i class="stripe-pf stripe-pf-sofort stripe-pf-right" alt="SOFORT" aria-hidden="true"></i>',
98
			'sepa'       => '<i class="stripe-pf stripe-pf-sepa stripe-pf-right" alt="SEPA" aria-hidden="true"></i>',
99
		) );
100
	}
101
102
	/**
103
	 * Validates that the order meets the minimum order amount
104
	 * set by Stripe.
105
	 *
106
	 * @since 4.0.0
107
	 * @version 4.0.0
108
	 * @param object $order
109
	 */
110
	public function validate_minimum_order_amount( $order ) {
111
		if ( $order->get_total() * 100 < WC_Stripe_Helper::get_minimum_amount() ) {
112
			/* translators: 1) dollar amount */
113
			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 ) ) );
114
		}
115
	}
116
117
	/**
118
	 * Gets the transaction URL linked to Stripe dashboard.
119
	 *
120
	 * @since 4.0.0
121
	 * @version 4.0.0
122
	 */
123
	public function get_transaction_url( $order ) {
124
		if ( $this->testmode ) {
125
			$this->view_transaction_url = 'https://dashboard.stripe.com/test/payments/%s';
126
		} else {
127
			$this->view_transaction_url = 'https://dashboard.stripe.com/payments/%s';
128
		}
129
130
		return parent::get_transaction_url( $order );
131
	}
132
133
	/**
134
	 * Gets the saved customer id if exists.
135
	 *
136
	 * @since 4.0.0
137
	 * @version 4.0.0
138
	 */
139
	public function get_stripe_customer_id( $order ) {
140
		$customer = get_user_meta( WC_Stripe_Helper::is_pre_30() ? $order->customer_user : $order->get_customer_id(), '_stripe_customer_id', true );
141
142
		if ( empty( $customer ) ) {
143
			// Try to get it via the order.
144
			if ( WC_Stripe_Helper::is_pre_30() ) {
145
				return get_post_meta( $order->id, '_stripe_customer_id', true );
146
			} else {
147
				return $order->get_meta( '_stripe_customer_id', true );
148
			}
149
		} else {
150
			return $customer;
151
		}
152
153
		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...
154
	}
155
156
	/**
157
	 * Builds the return URL from redirects.
158
	 *
159
	 * @since 4.0.0
160
	 * @version 4.0.0
161
	 * @param object $order
162
	 * @param int $id Stripe session id.
163
	 */
164
	public function get_stripe_return_url( $order = null, $id = null ) {
165
		if ( is_object( $order ) ) {
166
			if ( empty( $id ) ) {
167
				$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...
168
			}
169
170
			$order_id = WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id();
171
172
			$args = array(
173
				'utm_nooverride' => '1',
174
				'order_id'       => $order_id,
175
			);
176
177
			return esc_url_raw( add_query_arg( $args, $this->get_return_url( $order ) ) );
178
		}
179
180
		return esc_url_raw( add_query_arg( array( 'utm_nooverride' => '1' ), $this->get_return_url() ) );
181
	}
182
183
	/**
184
	 * Is $order_id a subscription?
185
	 * @param  int  $order_id
186
	 * @return boolean
187
	 */
188
	public function has_subscription( $order_id ) {
189
		return ( function_exists( 'wcs_order_contains_subscription' ) && ( wcs_order_contains_subscription( $order_id ) || wcs_is_subscription( $order_id ) || wcs_order_contains_renewal( $order_id ) ) );
190
	}
191
192
	/**
193
	 * Generate the request for the payment.
194
	 *
195
	 * @since 3.1.0
196
	 * @version 4.0.0
197
	 * @param  WC_Order $order
198
	 * @param  object $prepared_source
199
	 * @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...
200
	 */
201
	public function generate_payment_request( $order, $prepared_source ) {
202
		$settings                          = get_option( 'woocommerce_stripe_settings', array() );
203
		$statement_descriptor              = ! empty( $settings['statement_descriptor'] ) ? str_replace( "'", '', $settings['statement_descriptor'] ) : '';
204
		$capture                           = ! empty( $settings['capture'] ) && 'yes' === $settings['capture'] ? true : false;
205
		$post_data                         = array();
206
		$post_data['currency']             = strtolower( WC_Stripe_Helper::is_pre_30() ? $order->get_order_currency() : $order->get_currency() );
207
		$post_data['amount']               = WC_Stripe_Helper::get_stripe_amount( $order->get_total(), $post_data['currency'] );
208
		/* translators: 1) blog name 2) order number */
209
		$post_data['description']          = sprintf( __( '%1$s - Order %2$s', 'woocommerce-gateway-stripe' ), wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ), $order->get_order_number() );
210
		$billing_email      = WC_Stripe_Helper::is_pre_30() ? $order->billing_email : $order->get_billing_email();
211
		$billing_first_name = WC_Stripe_Helper::is_pre_30() ? $order->billing_first_name : $order->get_billing_first_name();
212
		$billing_last_name  = WC_Stripe_Helper::is_pre_30() ? $order->billing_last_name : $order->get_billing_last_name();
213
214
		if ( ! empty( $billing_email ) && apply_filters( 'wc_stripe_send_stripe_receipt', false ) ) {
215
			$post_data['receipt_email'] = $billing_email;
216
		}
217
218
		switch ( WC_Stripe_Helper::is_pre_30() ? $order->payment_method : $order->get_payment_method() ) {
219
			case 'stripe':
220
				if ( ! empty( $statement_descriptor ) ) {
221
					$post_data['statement_descriptor'] = WC_Stripe_Helper::clean_statement_descriptor( $statement_descriptor );
222
				}
223
224
				$post_data['capture'] = $capture ? 'true' : 'false';
225
				break;
226
			case 'stripe_sepa':
227
				if ( ! empty( $statement_descriptor ) ) {
228
					$post_data['statement_descriptor'] = WC_Stripe_Helper::clean_statement_descriptor( $statement_descriptor );
229
				}
230
				break;
231
		}
232
233
		$post_data['expand[]'] = 'balance_transaction';
234
235
		$metadata = array(
236
			__( 'customer_name', 'woocommerce-gateway-stripe' ) => sanitize_text_field( $billing_first_name ) . ' ' . sanitize_text_field( $billing_last_name ),
237
			__( 'customer_email', 'woocommerce-gateway-stripe' ) => sanitize_email( $billing_email ),
238
			'order_id' => WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id(),
239
		);
240
241
		if ( $this->has_subscription( WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id() ) ) {
242
			$metadata += array(
243
				'payment_type' => 'recurring',
244
				'site_url'     => esc_url( get_site_url() ),
245
			);
246
		}
247
248
		$post_data['metadata'] = apply_filters( 'wc_stripe_payment_metadata', $metadata, $order, $prepared_source );
249
250
		if ( $prepared_source->customer ) {
251
			$post_data['customer'] = $prepared_source->customer;
252
		}
253
254
		if ( $prepared_source->source ) {
255
			$post_data['source'] = $prepared_source->source;
256
		}
257
258
		/**
259
		 * Filter the return value of the WC_Payment_Gateway_CC::generate_payment_request.
260
		 *
261
		 * @since 3.1.0
262
		 * @param array $post_data
263
		 * @param WC_Order $order
264
		 * @param object $source
265
		 */
266
		return apply_filters( 'wc_stripe_generate_payment_request', $post_data, $order, $prepared_source );
267
	}
268
269
	/**
270
	 * Store extra meta data for an order from a Stripe Response.
271
	 */
272
	public function process_response( $response, $order ) {
273
		WC_Stripe_Logger::log( 'Processing response: ' . print_r( $response, true ) );
274
275
		$order_id = WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id();
276
277
		$captured = ( isset( $response->captured ) && $response->captured ) ? 'yes' : 'no';
278
279
		// Store charge data.
280
		WC_Stripe_Helper::is_pre_30() ? update_post_meta( $order_id, '_stripe_charge_captured', $captured ) : $order->update_meta_data( '_stripe_charge_captured', $captured );
281
282
		// Store other data such as fees.
283 View Code Duplication
		if ( isset( $response->balance_transaction ) && isset( $response->balance_transaction->fee ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
284
			// Fees and Net needs to both come from Stripe to be accurate as the returned
285
			// values are in the local currency of the Stripe account, not from WC.
286
			$fee = ! empty( $response->balance_transaction->fee ) ? WC_Stripe_Helper::format_balance_fee( $response->balance_transaction, 'fee' ) : 0;
287
			$net = ! empty( $response->balance_transaction->net ) ? WC_Stripe_Helper::format_balance_fee( $response->balance_transaction, 'net' ) : 0;
288
			WC_Stripe_Helper::is_pre_30() ? update_post_meta( $order_id, self::META_NAME_FEE, $fee ) : $order->update_meta_data( self::META_NAME_FEE, $fee );
289
			WC_Stripe_Helper::is_pre_30() ? update_post_meta( $order_id, self::META_NAME_NET, $net ) : $order->update_meta_data( self::META_NAME_NET, $net );
290
		}
291
292
		if ( 'yes' === $captured ) {
293
			/**
294
			 * Charge can be captured but in a pending state. Payment methods
295
			 * that are asynchronous may take couple days to clear. Webhook will
296
			 * take care of the status changes.
297
			 */
298
			if ( 'pending' === $response->status ) {
299
				$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 );
300
301
				if ( ! $order_stock_reduced ) {
302
					WC_Stripe_Helper::is_pre_30() ? $order->reduce_order_stock() : wc_reduce_stock_levels( $order_id );
303
				}
304
305
				WC_Stripe_Helper::is_pre_30() ? update_post_meta( $order_id, '_transaction_id', $response->id ) : $order->set_transaction_id( $response->id );
306
				/* translators: transaction id */
307
				$order->update_status( 'on-hold', sprintf( __( 'Stripe charge awaiting payment: %s.', 'woocommerce-gateway-stripe' ), $response->id ) );
308
			}
309
310
			if ( 'succeeded' === $response->status ) {
311
				$order->payment_complete( $response->id );
312
313
				/* translators: transaction id */
314
				$message = sprintf( __( 'Stripe charge complete (Charge ID: %s)', 'woocommerce-gateway-stripe' ), $response->id );
315
				$order->add_order_note( $message );
316
			}
317
318
			if ( 'failed' === $response->status ) {
319
				$localized_message = __( 'Payment processing failed. Please retry.', 'woocommerce-gateway-stripe' );
320
				$order->add_order_note( $localized_message );
321
				throw new WC_Stripe_Exception( print_r( $response, true ), $localized_message );
322
			}
323
		} else {
324
			WC_Stripe_Helper::is_pre_30() ? update_post_meta( $order_id, '_transaction_id', $response->id ) : $order->set_transaction_id( $response->id );
325
326
			if ( $order->has_status( array( 'pending', 'failed' ) ) ) {
327
				WC_Stripe_Helper::is_pre_30() ? $order->reduce_order_stock() : wc_reduce_stock_levels( $order_id );
328
			}
329
330
			/* translators: transaction id */
331
			$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 ) );
332
		}
333
334
		if ( is_callable( array( $order, 'save' ) ) ) {
335
			$order->save();
336
		}
337
338
		do_action( 'wc_gateway_stripe_process_response', $response, $order );
339
340
		return $response;
341
	}
342
343
	/**
344
	 * Sends the failed order email to admin.
345
	 *
346
	 * @since 3.1.0
347
	 * @version 4.0.0
348
	 * @param int $order_id
349
	 * @return null
350
	 */
351
	public function send_failed_order_email( $order_id ) {
352
		$emails = WC()->mailer()->get_emails();
353
		if ( ! empty( $emails ) && ! empty( $order_id ) ) {
354
			$emails['WC_Email_Failed_Order']->trigger( $order_id );
355
		}
356
	}
357
358
	/**
359
	 * Get owner details.
360
	 *
361
	 * @since 4.0.0
362
	 * @version 4.0.0
363
	 * @param object $order
364
	 * @return object $details
365
	 */
366
	public function get_owner_details( $order ) {
367
		$billing_first_name = WC_Stripe_Helper::is_pre_30() ? $order->billing_first_name : $order->get_billing_first_name();
368
		$billing_last_name  = WC_Stripe_Helper::is_pre_30() ? $order->billing_last_name : $order->get_billing_last_name();
369
370
		$details = array();
371
372
		$details['name']                   = $billing_first_name . ' ' . $billing_last_name;
373
		$details['email']                  = WC_Stripe_Helper::is_pre_30() ? $order->billing_email : $order->get_billing_email();
374
375
		$phone                             = WC_Stripe_Helper::is_pre_30() ? $order->billing_phone : $order->get_billing_phone();
376
377
		if ( ! empty( $phone ) ) {
378
			$details['phone']              = $phone;
379
		}
380
381
		$details['address']['line1']       = WC_Stripe_Helper::is_pre_30() ? $order->billing_address_1 : $order->get_billing_address_1();
382
		$details['address']['line2']       = WC_Stripe_Helper::is_pre_30() ? $order->billing_address_2 : $order->get_billing_address_2();
383
		$details['address']['state']       = WC_Stripe_Helper::is_pre_30() ? $order->billing_state : $order->get_billing_state();
384
		$details['address']['city']        = WC_Stripe_Helper::is_pre_30() ? $order->billing_city : $order->get_billing_city();
385
		$details['address']['postal_code'] = WC_Stripe_Helper::is_pre_30() ? $order->billing_postcode : $order->get_billing_postcode();
386
		$details['address']['country']     = WC_Stripe_Helper::is_pre_30() ? $order->billing_country : $order->get_billing_country();
387
388
		return (object) apply_filters( 'wc_stripe_owner_details', $details, $order );
389
	}
390
391
	/**
392
	 * Get source object by source id.
393
	 *
394
	 * @since 4.0.3
395
	 * @param string $source_id The source ID to get source object for.
396
	 */
397
	public function get_source_object( $source_id = '' ) {
398
		if ( empty( $source_id ) ) {
399
			return '';
400
		}
401
402
		$source_object = WC_Stripe_API::retrieve( 'sources/' . $source_id );
403
404
		if ( ! empty( $source_object->error ) ) {
405
			throw new WC_Stripe_Exception( print_r( $source_object, true ), $source_object->error->message );
406
		}
407
408
		return $source_object;
409
	}
410
411
	/**
412
	 * Checks if 3DS is required.
413
	 *
414
	 * @since 4.0.4
415
	 * @param object $source_object
416
	 * @return bool
417
	 */
418
	public function is_3ds_required( $source_object ) {
419
		return (
420
			$source_object && ! empty( $source_object->card ) ) &&
421
			( 'card' === $source_object->type && 'required' === $source_object->card->three_d_secure ||
422
			( $this->three_d_secure && 'optional' === $source_object->card->three_d_secure )
423
		);
424
	}
425
426
	/**
427
	 * Checks if card is 3DS.
428
	 *
429
	 * @since 4.0.4
430
	 * @param object $source_object
431
	 * @return bool
432
	 */
433
	public function is_3ds_card( $source_object ) {
434
		return ( $source_object && 'three_d_secure' === $source_object->type );
435
	}
436
437
	/**
438
	 * Checks if card is a prepaid card.
439
	 *
440
	 * @since 4.0.6
441
	 * @param object $source_object
442
	 * @return bool
443
	 */
444
	public function is_prepaid_card( $source_object ) {
445
		return ( $source_object && 'token' === $source_object->object && 'prepaid' === $source_object->card->funding );
446
	}
447
448
	/**
449
	 * Creates the 3DS source for charge.
450
	 *
451
	 * @since 4.0.0
452
	 * @since 4.0.4 Add $return_url
453
	 * @param object $order
454
	 * @param object $source_object
455
	 * @param string $return_url
456
	 * @return mixed
457
	 */
458
	public function create_3ds_source( $order, $source_object, $return_url = '' ) {
459
		$currency                    = WC_Stripe_Helper::is_pre_30() ? $order->get_order_currency() : $order->get_currency();
460
		$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...
461
		$return_url                  = empty( $return_url ) ? $this->get_stripe_return_url( $order ) : $return_url;
462
463
		$post_data                   = array();
464
		$post_data['amount']         = WC_Stripe_Helper::get_stripe_amount( $order->get_total(), $currency );
465
		$post_data['currency']       = strtolower( $currency );
466
		$post_data['type']           = 'three_d_secure';
467
		$post_data['owner']          = $this->get_owner_details( $order );
468
		$post_data['three_d_secure'] = array( 'card' => $source_object->id );
469
		$post_data['redirect']       = array( 'return_url' => $return_url );
470
471
		WC_Stripe_Logger::log( 'Info: Begin creating 3DS source...' );
472
473
		return WC_Stripe_API::request( apply_filters( 'wc_stripe_3ds_source', $post_data, $order ), 'sources' );
474
	}
475
476
	/**
477
	 * Get payment source. This can be a new token/source or existing WC token.
478
	 * If user is logged in and/or has WC account, create an account on Stripe.
479
	 * This way we can attribute the payment to the user to better fight fraud.
480
	 *
481
	 * @since 3.1.0
482
	 * @version 4.0.0
483
	 * @param string $user_id
484
	 * @param bool $force_save_source Should we force save payment source.
485
	 *
486
	 * @throws Exception When card was not added or for and invalid card.
487
	 * @return object
488
	 */
489
	public function prepare_source( $user_id, $force_save_source = false ) {
490
		$customer           = new WC_Stripe_Customer( $user_id );
491
		$set_customer       = true;
492
		$force_save_source  = apply_filters( 'wc_stripe_force_save_source', $force_save_source, $customer );
493
		$source_object      = '';
494
		$source_id          = '';
495
		$wc_token_id        = false;
496
		$payment_method     = isset( $_POST['payment_method'] ) ? wc_clean( $_POST['payment_method'] ) : 'stripe';
497
498
		// New CC info was entered and we have a new source to process.
499
		if ( ! empty( $_POST['stripe_source'] ) ) {
500
			$source_object = self::get_source_object( wc_clean( $_POST['stripe_source'] ) );
501
			$source_id     = $source_object->id;
502
503
			// This checks to see if customer opted to save the payment method to file.
504
			$maybe_saved_card = isset( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] ) && ! empty( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] );
505
506
			/**
507
			 * This is true if the user wants to store the card to their account.
508
			 * Criteria to save to file is they are logged in, they opted to save or product requirements and the source is
509
			 * actually reusable. Either that or force_save_source is true.
510
			 */
511
			if ( ( $user_id && $this->saved_cards && $maybe_saved_card && 'reusable' === $source_object->usage ) || $force_save_source ) {
512
				$response = $customer->add_source( $source_object->id );
513
514
				if ( ! empty( $response->error ) ) {
515
					throw new WC_Stripe_Exception( print_r( $response, true ), $response->error->message );
516
				}
517
			}
518
		} elseif ( isset( $_POST[ 'wc-' . $payment_method . '-payment-token' ] ) && 'new' !== $_POST[ 'wc-' . $payment_method . '-payment-token' ] ) {
519
			// Use an existing token, and then process the payment.
520
			$wc_token_id = wc_clean( $_POST[ 'wc-' . $payment_method . '-payment-token' ] );
521
			$wc_token    = WC_Payment_Tokens::get( $wc_token_id );
522
523
			if ( ! $wc_token || $wc_token->get_user_id() !== get_current_user_id() ) {
524
				WC()->session->set( 'refresh_totals', true );
525
				throw new WC_Stripe_Exception( 'Invalid payment method', __( 'Invalid payment method. Please input a new card number.', 'woocommerce-gateway-stripe' ) );
526
			}
527
528
			$source_id = $wc_token->get_token();
529
		} elseif ( isset( $_POST['stripe_token'] ) && 'new' !== $_POST['stripe_token'] ) {
530
			$stripe_token     = wc_clean( $_POST['stripe_token'] );
531
			$maybe_saved_card = isset( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] ) && ! empty( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] );
532
533
			// This is true if the user wants to store the card to their account.
534
			if ( ( $user_id && $this->saved_cards && $maybe_saved_card ) || $force_save_source ) {
535
				$response = $customer->add_source( $stripe_token );
536
537
				if ( ! empty( $response->error ) ) {
538
					throw new WC_Stripe_Exception( print_r( $response, true ), $response->error->message );
539
				}
540
			} else {
541
				$set_customer = false;
542
				$source_id    = $stripe_token;
543
			}
544
		}
545
546
		if ( ! $set_customer ) {
547
			$customer_id = false;
548
		} else {
549
			$customer_id = $customer->get_id() ? $customer->get_id() : false;
550
		}
551
552
		if ( empty( $source_object ) ) {
553
			$source_object = self::get_source_object( $source_id );
554
		}
555
556
		return (object) array(
557
			'token_id'      => $wc_token_id,
558
			'customer'      => $customer_id,
559
			'source'        => $source_id,
560
			'source_object' => $source_object,
561
		);
562
	}
563
564
	/**
565
	 * Get payment source from an order. This could be used in the future for
566
	 * a subscription as an example, therefore using the current user ID would
567
	 * not work - the customer won't be logged in :)
568
	 *
569
	 * Not using 2.6 tokens for this part since we need a customer AND a card
570
	 * token, and not just one.
571
	 *
572
	 * @since 3.1.0
573
	 * @version 4.0.0
574
	 * @param object $order
575
	 * @return object
576
	 */
577
	public function prepare_order_source( $order = null ) {
578
		$stripe_customer = new WC_Stripe_Customer();
579
		$stripe_source   = false;
580
		$token_id        = false;
581
582
		if ( $order ) {
583
			$order_id = WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id();
584
585
			$stripe_customer_id = get_post_meta( $order_id, '_stripe_customer_id', true );
586
587
			if ( $stripe_customer_id ) {
588
				$stripe_customer->set_id( $stripe_customer_id );
589
			}
590
591
			$source_id = WC_Stripe_Helper::is_pre_30() ? get_post_meta( $order_id, '_stripe_source_id', true ) : $order->get_meta( '_stripe_source_id', true );
592
593
			// Since 4.0.0, we changed card to source so we need to account for that.
594
			if ( empty( $source_id ) ) {
595
				$source_id = WC_Stripe_Helper::is_pre_30() ? get_post_meta( $order_id, '_stripe_card_id', true ) : $order->get_meta( '_stripe_card_id', true );
596
597
				// Take this opportunity to update the key name.
598
				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 );
599
600
				if ( is_callable( array( $order, 'save' ) ) ) {
601
					$order->save();
602
				}
603
			}
604
605
			if ( $source_id ) {
606
				$stripe_source = $source_id;
607
			} elseif ( apply_filters( 'wc_stripe_use_default_customer_source', true ) ) {
608
				/*
609
				 * We can attempt to charge the customer's default source
610
				 * by sending empty source id.
611
				 */
612
				$stripe_source = '';
613
			}
614
		}
615
616
		return (object) array(
617
			'token_id' => $token_id,
618
			'customer' => $stripe_customer ? $stripe_customer->get_id() : false,
619
			'source'   => $stripe_source,
620
		);
621
	}
622
623
	/**
624
	 * Save source to order.
625
	 *
626
	 * @since 3.1.0
627
	 * @version 4.0.0
628
	 * @param WC_Order $order For to which the source applies.
629
	 * @param stdClass $source Source information.
630
	 */
631
	public function save_source_to_order( $order, $source ) {
632
		$order_id = WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id();
633
634
		// Store source in the order.
635
		if ( $source->customer ) {
636
			if ( WC_Stripe_Helper::is_pre_30() ) {
637
				update_post_meta( $order_id, '_stripe_customer_id', $source->customer );
638
			} else {
639
				$order->update_meta_data( '_stripe_customer_id', $source->customer );
640
			}
641
		}
642
643
		if ( $source->source ) {
644
			if ( WC_Stripe_Helper::is_pre_30() ) {
645
				update_post_meta( $order_id, '_stripe_source_id', $source->source );
646
			} else {
647
				$order->update_meta_data( '_stripe_source_id', $source->source );
648
			}
649
		}
650
651
		if ( is_callable( array( $order, 'save' ) ) ) {
652
			$order->save();
653
		}
654
	}
655
656
	/**
657
	 * Updates Stripe fees/net.
658
	 * e.g usage would be after a refund.
659
	 *
660
	 * @since 4.0.0
661
	 * @version 4.0.6
662
	 * @param object $order The order object
663
	 * @param int $balance_transaction_id
664
	 */
665
	public function update_fees( $order, $balance_transaction_id ) {
666
		$order_id = WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id();
667
668
		$balance_transaction = WC_Stripe_API::retrieve( 'balance/history/' . $balance_transaction_id );
669
670
		if ( empty( $balance_transaction->error ) ) {
671
			if ( isset( $balance_transaction ) && isset( $balance_transaction->fee ) ) {
672
				// Fees and Net needs to both come from Stripe to be accurate as the returned
673
				// values are in the local currency of the Stripe account, not from WC.
674
				$fee_refund = ! empty( $balance_transaction->fee ) ? WC_Stripe_Helper::format_balance_fee( $balance_transaction, 'fee' ) : 0;
675
				$net_refund = ! empty( $balance_transaction->net ) ? WC_Stripe_Helper::format_balance_fee( $balance_transaction, 'net' ) : 0;
676
677
				// Current data fee & net.
678
				$fee_current = WC_Stripe_Helper::is_pre_30() ? get_post_meta( $order_id, self::META_NAME_FEE, true ) : $order->get_meta( self::META_NAME_FEE, true );
679
				$net_current = WC_Stripe_Helper::is_pre_30() ? get_post_meta( $order_id, self::META_NAME_NET, true ) : $order->get_meta( self::META_NAME_NET, true );
680
681
				// Calculation.
682
				$fee = (float) $fee_current + (float) $fee_refund;
683
				$net = (float) $net_current + (float) $net_refund;
684
685
				WC_Stripe_Helper::is_pre_30() ? update_post_meta( $order_id, self::META_NAME_FEE, $fee ) : $order->update_meta_data( self::META_NAME_FEE, $fee );
686
				WC_Stripe_Helper::is_pre_30() ? update_post_meta( $order_id, self::META_NAME_NET, $net ) : $order->update_meta_data( self::META_NAME_NET, $net );
687
688
				if ( is_callable( array( $order, 'save' ) ) ) {
689
					$order->save();
690
				}
691
			}
692
		} else {
693
			WC_Stripe_Logger::log( "Unable to update fees/net meta for order: {$order_id}" );
694
		}
695
	}
696
697
	/**
698
	 * Refund a charge.
699
	 *
700
	 * @since 3.1.0
701
	 * @version 4.0.0
702
	 * @param  int $order_id
703
	 * @param  float $amount
704
	 * @return bool
705
	 */
706
	public function process_refund( $order_id, $amount = null, $reason = '' ) {
707
		$order = wc_get_order( $order_id );
708
709
		if ( ! $order || ! $order->get_transaction_id() ) {
710
			return false;
711
		}
712
713
		$request = array();
714
715
		if ( WC_Stripe_Helper::is_pre_30() ) {
716
			$order_currency = get_post_meta( $order_id, '_order_currency', true );
717
			$captured       = get_post_meta( $order_id, '_stripe_charge_captured', true );
718
		} else {
719
			$order_currency = $order->get_currency();
720
			$captured       = $order->get_meta( '_stripe_charge_captured', true );
721
		}
722
723
		if ( ! is_null( $amount ) ) {
724
			$request['amount'] = WC_Stripe_Helper::get_stripe_amount( $amount, $order_currency );
725
		}
726
727
		// If order is only authorized, don't pass amount.
728
		if ( 'yes' !== $captured ) {
729
			unset( $request['amount'] );
730
		}
731
732
		if ( $reason ) {
733
			$request['metadata'] = array(
734
				'reason' => $reason,
735
			);
736
		}
737
738
		$request['charge'] = $order->get_transaction_id();
739
740
		WC_Stripe_Logger::log( "Info: Beginning refund for order {$order->get_transaction_id()} for the amount of {$amount}" );
741
742
		$request = apply_filters( 'wc_stripe_refund_request_args', $request, $order );
743
744
		$response = WC_Stripe_API::request( $request, 'refunds' );
745
746
		if ( ! empty( $response->error ) ) {
747
			WC_Stripe_Logger::log( 'Error: ' . $response->error->message );
748
749
			return $response;
750
751
		} elseif ( ! empty( $response->id ) ) {
752
			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 );
753
754
			$amount = wc_price( $response->amount / 100 );
755
756
			if ( in_array( strtolower( $order->get_currency() ), WC_Stripe_Helper::no_decimal_currencies() ) ) {
757
				$amount = wc_price( $response->amount );
758
			}
759
760
			if ( isset( $response->balance_transaction ) ) {
761
				$this->update_fees( $order, $response->balance_transaction );
762
			}
763
764
			/* translators: 1) dollar amount 2) transaction id 3) refund message */
765
			$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' );
766
767
			$order->add_order_note( $refund_message );
768
			WC_Stripe_Logger::log( 'Success: ' . html_entity_decode( strip_tags( $refund_message ) ) );
769
770
			return true;
771
		}
772
	}
773
774
	/**
775
	 * Add payment method via account screen.
776
	 * We don't store the token locally, but to the Stripe API.
777
	 *
778
	 * @since 3.0.0
779
	 * @version 4.0.0
780
	 */
781
	public function add_payment_method() {
782
		$error     = false;
783
		$error_msg = __( 'There was a problem adding the card.', 'woocommerce-gateway-stripe' );
784
		$source_id = '';
785
786
		if ( empty( $_POST['stripe_source'] ) && empty( $_POST['stripe_token'] ) || ! is_user_logged_in() ) {
787
			$error = true;
788
		}
789
790
		$stripe_customer = new WC_Stripe_Customer( get_current_user_id() );
791
792
		$source = ! empty( $_POST['stripe_source'] ) ? wc_clean( $_POST['stripe_source'] ) : '';
793
794
		$source_object = WC_Stripe_API::retrieve( 'sources/' . $source );
795
796
		if ( isset( $source_object ) ) {
797
			if ( ! empty( $source_object->error ) ) {
798
				$error = true;
799
			}
800
801
			$source_id = $source_object->id;
802
		} elseif ( isset( $_POST['stripe_token'] ) ) {
803
			$source_id = wc_clean( $_POST['stripe_token'] );
804
		}
805
806
		$response = $stripe_customer->add_source( $source_id );
807
808
		if ( ! $response || is_wp_error( $response ) || ! empty( $response->error ) ) {
809
			$error = true;
810
		}
811
812
		if ( $error ) {
813
			wc_add_notice( $error_msg, 'error' );
814
			WC_Stripe_Logger::log( 'Add payment method Error: ' . $error_msg );
815
			return;
816
		}
817
818
		return array(
819
			'result'   => 'success',
820
			'redirect' => wc_get_endpoint_url( 'payment-methods' ),
821
		);
822
	}
823
824
	/**
825
	 * Gets the locale with normalization that only Stripe accepts.
826
	 *
827
	 * @since 4.0.6
828
	 * @return string $locale
829
	 */
830
	public function get_locale() {
831
		$locale = get_locale();
832
833
		/*
834
		 * Stripe expects Norwegian to only be passed NO.
835
		 * But WP has different dialects.
836
		 */
837
		if ( 'NO' === substr( $locale, 3, 2 ) ) {
838
			$locale = 'no';
839
		} else {
840
			$locale = substr( get_locale(), 0, 2 );
841
		}
842
843
		return $locale;
844
	}
845
846
	/**
847
	 * Change the idempotency key so charge can
848
	 * process order as a different transaction.
849
	 *
850
	 * @since 4.0.6
851
	 * @param string $idempotency_key
852
	 * @param array $request
853
	 */
854
	public function change_idempotency_key( $idempotency_key, $request ) {
855
		$customer = ! empty( $request['customer'] ) ? $request['customer'] : '';
856
		$source   = ! empty( $request['source'] ) ? $request['source'] : $customer;
857
		$count    = $this->retry_interval;
858
859
		return $request['metadata']['order_id'] . '-' . $count . '-' . $source;
860
	}
861
}
862