Completed
Push — master ( 6d8164...bf7249 )
by Roy
03:26
created

WC_Stripe_Payment_Gateway::prepare_source()   C

Complexity

Conditions 24
Paths 68

Size

Total Lines 71
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 24
eloc 41
nc 68
nop 2
dl 0
loc 71
rs 5.4466
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
	const META_NAME_FEE = 'Stripe Fee';
15
	const META_NAME_NET = 'Net Revenue From Stripe';
16
17
	/**
18
	 * Check if this gateway is enabled
19
	 */
20
	public function is_available() {
21
		if ( 'yes' === $this->enabled ) {
22
			if ( ! $this->testmode && is_checkout() && ! is_ssl() ) {
23
				return false;
24
			}
25
			if ( ! $this->secret_key || ! $this->publishable_key ) {
26
				return false;
27
			}
28
			return true;
29
		}
30
31
		return parent::is_available();
32
	}
33
34
	/**
35
	 * Allow this class and other classes to add slug keyed notices (to avoid duplication).
36
	 *
37
	 * @since 4.0.0
38
	 * @version 4.0.0
39
	 */
40
	public function add_admin_notice( $slug, $class, $message ) {
41
		$this->notices[ $slug ] = array(
42
			'class'   => $class,
43
			'message' => $message,
44
		);
45
	}
46
47
	/**
48
	 * Remove admin notice.
49
	 *
50
	 * @since 4.0.0
51
	 * @version 4.0.0
52
	 */
53
	public function remove_admin_notice() {
54
		if ( did_action( 'woocommerce_update_options' ) ) {
55
			remove_action( 'admin_notices', array( $this, 'check_environment' ) );
56
		}
57
	}
58
59
	/**
60
	 * Validates that the order meets the minimum order amount
61
	 * set by Stripe.
62
	 *
63
	 * @since 4.0.0
64
	 * @version 4.0.0
65
	 * @param object $order
66
	 */
67
	public function validate_minimum_order_amount( $order ) {
68
		if ( $order->get_total() * 100 < WC_Stripe_Helper::get_minimum_amount() ) {
69
			/* translators: 1) dollar amount */
70
			throw new Exception( sprintf( __( 'Sorry, the minimum allowed order total is %1$s to use this payment method.', 'woocommerce-gateway-stripe' ), wc_price( WC_Stripe_Helper::get_minimum_amount() / 100 ) ) );
71
		}
72
	}
73
74
	/**
75
	 * Gets the transaction URL linked to Stripe dashboard.
76
	 *
77
	 * @since 4.0.0
78
	 * @version 4.0.0
79
	 */
80
	public function get_transaction_url( $order ) {
81
		if ( $this->testmode ) {
82
			$this->view_transaction_url = 'https://dashboard.stripe.com/test/payments/%s';
83
		} else {
84
			$this->view_transaction_url = 'https://dashboard.stripe.com/payments/%s';
85
		}
86
87
		return parent::get_transaction_url( $order );
88
	}
89
90
	/**
91
	 * Gets the saved customer id if exists.
92
	 *
93
	 * @since 4.0.0
94
	 * @version 4.0.0
95
	 */
96
	public function get_stripe_customer_id( $order ) {
97
		$customer = get_user_meta( WC_Stripe_Helper::is_pre_30() ? $order->customer_user : $order->get_customer_id(), '_stripe_customer_id', true );
98
99
		if ( empty( $customer ) ) {
100
			// Try to get it via the order.
101
			if ( WC_Stripe_Helper::is_pre_30() ) {
102
				return get_post_meta( $order->id, '_stripe_customer_id', true );
103
			} else {
104
				return $order->get_meta( '_stripe_customer_id', true );
105
			}
106
		} else {
107
			return $customer;
108
		}
109
110
		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...
111
	}
112
113
	/**
114
	 * Builds the return URL from redirects.
115
	 *
116
	 * @since 4.0.0
117
	 * @version 4.0.0
118
	 * @param object $order
119
	 * @param int $id Stripe session id.
120
	 */
121
	public function get_stripe_return_url( $order = null, $id = null ) {
122
		if ( is_object( $order ) ) {
123
			if ( empty( $id ) ) {
124
				$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...
125
			}
126
127
			$order_id = WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id();
128
129
			$args = array(
130
				'utm_nooverride' => '1',
131
				'order_id'       => $order_id,
132
			);
133
134
			return esc_url_raw( add_query_arg( $args, $this->get_return_url( $order ) ) );
135
		}
136
137
		return esc_url_raw( add_query_arg( array( 'utm_nooverride' => '1' ), $this->get_return_url() ) );
138
	}
139
140
	/**
141
	 * Generate the request for the payment.
142
	 *
143
	 * @since 3.1.0
144
	 * @version 4.0.0
145
	 * @param  WC_Order $order
146
	 * @param  object $source
147
	 * @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...
148
	 */
149
	public function generate_payment_request( $order, $source ) {
150
		$settings                          = get_option( 'woocommerce_stripe_settings', array() );
151
		$statement_descriptor              = ! empty( $settings['statement_descriptor'] ) ? str_replace( "'", '', $settings['statement_descriptor'] ) : '';
152
		$capture                           = ! empty( $settings['capture'] ) && 'yes' === $settings['capture'] ? true : false;
153
		$post_data                         = array();
154
		$post_data['currency']             = strtolower( WC_Stripe_Helper::is_pre_30() ? $order->get_order_currency() : $order->get_currency() );
155
		$post_data['amount']               = WC_Stripe_Helper::get_stripe_amount( $order->get_total(), $post_data['currency'] );
156
		/* translators: 1) blog name 2) order number */
157
		$post_data['description']          = sprintf( __( '%1$s - Order %2$s', 'woocommerce-gateway-stripe' ), wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ), $order->get_order_number() );
158
		$billing_email      = WC_Stripe_Helper::is_pre_30() ? $order->billing_email : $order->get_billing_email();
159
		$billing_first_name = WC_Stripe_Helper::is_pre_30() ? $order->billing_first_name : $order->get_billing_first_name();
160
		$billing_last_name  = WC_Stripe_Helper::is_pre_30() ? $order->billing_last_name : $order->get_billing_last_name();
161
162
		if ( ! empty( $billing_email ) && apply_filters( 'wc_stripe_send_stripe_receipt', false ) ) {
163
			$post_data['receipt_email'] = $billing_email;
164
		}
165
166
		switch ( WC_Stripe_Helper::is_pre_30() ? $order->payment_method : $order->get_payment_method() ) {
167
			case 'stripe':
168
				if ( ! empty( $statement_descriptor ) ) {
169
					$post_data['statement_descriptor'] = WC_Stripe_Helper::clean_statement_descriptor( $statement_descriptor );
170
				}
171
172
				$post_data['capture']              = $capture ? 'true' : 'false';
173
				break;
174
		}
175
176
		$post_data['expand[]'] = 'balance_transaction';
177
178
		$metadata = array(
179
			__( 'customer_name', 'woocommerce-gateway-stripe' ) => sanitize_text_field( $billing_first_name ) . ' ' . sanitize_text_field( $billing_last_name ),
180
			__( 'customer_email', 'woocommerce-gateway-stripe' ) => sanitize_email( $billing_email ),
181
			'order_id' => WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id(),
182
		);
183
184
		$post_data['metadata'] = apply_filters( 'wc_stripe_payment_metadata', $metadata, $order, $source );
185
186
		if ( $source->customer ) {
187
			$post_data['customer'] = $source->customer;
188
		}
189
190
		if ( $source->source ) {
191
			$post_data['source'] = $source->source;
192
		}
193
194
		/**
195
		 * Filter the return value of the WC_Payment_Gateway_CC::generate_payment_request.
196
		 *
197
		 * @since 3.1.0
198
		 * @param array $post_data
199
		 * @param WC_Order $order
200
		 * @param object $source
201
		 */
202
		return apply_filters( 'wc_stripe_generate_payment_request', $post_data, $order, $source );
203
	}
204
205
	/**
206
	 * Store extra meta data for an order from a Stripe Response.
207
	 */
208
	public function process_response( $response, $order ) {
209
		WC_Stripe_Logger::log( 'Processing response: ' . print_r( $response, true ) );
210
211
		$order_id = WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id();
212
213
		// Store charge data
214
		WC_Stripe_Helper::is_pre_30() ? update_post_meta( $order_id, '_stripe_charge_captured', $response->captured ? 'yes' : 'no' ) : $order->update_meta_data( '_stripe_charge_captured', $response->captured ? 'yes' : 'no' );
215
216
		// Store other data such as fees
217 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...
218
			// Fees and Net needs to both come from Stripe to be accurate as the returned
219
			// values are in the local currency of the Stripe account, not from WC.
220
			$fee = ! empty( $response->balance_transaction->fee ) ? WC_Stripe_Helper::format_balance_fee( $response->balance_transaction, 'fee' ) : 0;
221
			$net = ! empty( $response->balance_transaction->net ) ? WC_Stripe_Helper::format_balance_fee( $response->balance_transaction, 'net' ) : 0;
222
			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 );
223
			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 );
224
		}
225
226
		if ( $response->captured ) {
227
			/**
228
			 * Charge can be captured but in a pending state. Payment methods
229
			 * that are asynchronous may take couple days to clear. Webhook will
230
			 * take care of the status changes.
231
			 */
232
			if ( 'pending' === $response->status ) {
233
				if ( ! wc_string_to_bool( get_post_meta( $order_id, '_order_stock_reduced', true ) ) ) {
234
					WC_Stripe_Helper::is_pre_30() ? $order->reduce_order_stock() : wc_reduce_stock_levels( $order_id );
235
				}
236
237
				WC_Stripe_Helper::is_pre_30() ? update_post_meta( $order_id, '_transaction_id', $response->id, true ) : $order->set_transaction_id( $response->id );
238
				/* translators: transaction id */
239
				$order->update_status( 'on-hold', sprintf( __( 'Stripe charge awaiting payment: %s.', 'woocommerce-gateway-stripe' ), $response->id ) );
240
			}
241
242
			if ( 'succeeded' === $response->status ) {
243
				$order->payment_complete( $response->id );
244
245
				/* translators: transaction id */
246
				$message = sprintf( __( 'Stripe charge complete (Charge ID: %s)', 'woocommerce-gateway-stripe' ), $response->id );
247
				$order->add_order_note( $message );
248
			}
249
250
			if ( 'failed' === $response->status ) {
251
				$error_msg = __( 'Payment processing failed. Please retry.', 'woocommerce-gateway-stripe' );
252
				$order->add_order_note( $error_msg );
253
				throw new Exception( $error_msg );
254
			}
255
		} else {
256
			WC_Stripe_Helper::is_pre_30() ? update_post_meta( $order_id, '_transaction_id', $response->id, true ) : $order->set_transaction_id( $response->id );
257
258
			if ( $order->has_status( array( 'pending', 'failed' ) ) ) {
259
				WC_Stripe_Helper::is_pre_30() ? $order->reduce_order_stock() : wc_reduce_stock_levels( $order_id );
260
			}
261
262
			/* translators: transaction id */
263
			$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 ) );
264
		}
265
266
		if ( is_callable( array( $order, 'save' ) ) ) {
267
			$order->save();
268
		}
269
270
		do_action( 'wc_gateway_stripe_process_response', $response, $order );
271
272
		return $response;
273
	}
274
275
	/**
276
	 * Sends the failed order email to admin.
277
	 *
278
	 * @since 3.1.0
279
	 * @version 4.0.0
280
	 * @param int $order_id
281
	 * @return null
282
	 */
283
	public function send_failed_order_email( $order_id ) {
284
		$emails = WC()->mailer()->get_emails();
285
		if ( ! empty( $emails ) && ! empty( $order_id ) ) {
286
			$emails['WC_Email_Failed_Order']->trigger( $order_id );
287
		}
288
	}
289
290
	/**
291
	 * Get owner details.
292
	 *
293
	 * @since 4.0.0
294
	 * @version 4.0.0
295
	 * @param object $order
296
	 * @return object $details
297
	 */
298
	public function get_owner_details( $order ) {
299
		$billing_first_name = WC_Stripe_Helper::is_pre_30() ? $order->billing_first_name : $order->get_billing_first_name();
300
		$billing_last_name  = WC_Stripe_Helper::is_pre_30() ? $order->billing_last_name : $order->get_billing_last_name();
301
302
		$details = array();
303
304
		$details['name']                   = $billing_first_name . ' ' . $billing_last_name;
305
		$details['email']                  = WC_Stripe_Helper::is_pre_30() ? $order->billing_email : $order->get_billing_email();
306
307
		$phone                             = WC_Stripe_Helper::is_pre_30() ? $order->billing_phone : $order->get_billing_phone();
308
309
		if ( ! empty( $phone ) ) {
310
			$details['phone']              = $phone;
311
		}
312
313
		$details['address']['line1']       = WC_Stripe_Helper::is_pre_30() ? $order->billing_address_1 : $order->get_billing_address_1();
314
		$details['address']['line2']       = WC_Stripe_Helper::is_pre_30() ? $order->billing_address_2 : $order->get_billing_address_2();
315
		$details['address']['state']       = WC_Stripe_Helper::is_pre_30() ? $order->billing_state : $order->get_billing_state();
316
		$details['address']['city']        = WC_Stripe_Helper::is_pre_30() ? $order->billing_city : $order->get_billing_city();
317
		$details['address']['postal_code'] = WC_Stripe_Helper::is_pre_30() ? $order->billing_postcode : $order->get_billing_postcode();
318
		$details['address']['country']     = WC_Stripe_Helper::is_pre_30() ? $order->billing_country : $order->get_billing_country();
319
320
		return (object) apply_filters( 'wc_stripe_owner_details', $details, $order );
321
	}
322
323
	/**
324
	 * Get payment source. This can be a new token/source or existing WC token.
325
	 * If user is logged in and/or has WC account, create an account on Stripe.
326
	 * This way we can attribute the payment to the user to better fight fraud.
327
	 *
328
	 * @since 3.1.0
329
	 * @version 4.0.0
330
	 * @param string $user_id
331
	 * @param bool $force_save_source Should we force save payment source.
332
	 *
333
	 * @throws Exception When card was not added or for and invalid card.
334
	 * @return object
335
	 */
336
	public function prepare_source( $user_id, $force_save_source = false ) {
337
		$customer           = new WC_Stripe_Customer( $user_id );
338
		$set_customer       = true;
339
		$force_save_source  = apply_filters( 'wc_stripe_force_save_source', $force_save_source, $customer );
340
		$source             = '';
341
		$wc_token_id        = false;
342
		$payment_method     = isset( $_POST['payment_method'] ) ? wc_clean( $_POST['payment_method'] ) : 'stripe';
343
344
		// New CC info was entered and we have a new source to process.
345
		if ( ! empty( $_POST['stripe_source'] ) ) {
346
			// This gets the source object from Stripe.
347
			$source = json_decode( wc_clean( stripslashes( $_POST['stripe_source'] ) ) );
348
349
			// This checks to see if customer opted to save the payment method to file.
350
			$maybe_saved_card = isset( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] ) && ! empty( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] );
351
352
			/**
353
			 * This is true if the user wants to store the card to their account.
354
			 * Criteria to save to file is they are logged in, they opted to save or product requirements and the source is
355
			 * actually reusable. Either that or force_save_source is true.
356
			 */
357
			if ( ( $user_id && $this->saved_cards && $maybe_saved_card && 'reusable' === $source->usage ) || $force_save_source ) {
358
				$source = $customer->add_source( $source->id );
359
360
				if ( ! empty( $source->error ) ) {
361
					throw new Exception( $source->error->message );
362
				}
363
			} else {
364
				$source = $source->id;
365
			}
366
		} elseif ( isset( $_POST[ 'wc-' . $payment_method . '-payment-token' ] ) && 'new' !== $_POST[ 'wc-' . $payment_method . '-payment-token' ] ) {
367
			// Use an existing token, and then process the payment
368
369
			$wc_token_id = wc_clean( $_POST[ 'wc-' . $payment_method . '-payment-token' ] );
370
			$wc_token    = WC_Payment_Tokens::get( $wc_token_id );
371
372
			if ( ! $wc_token || $wc_token->get_user_id() !== get_current_user_id() ) {
373
				WC()->session->set( 'refresh_totals', true );
374
				throw new Exception( __( 'Invalid payment method. Please input a new card number.', 'woocommerce-gateway-stripe' ) );
375
			}
376
377
			$source = $wc_token->get_token();
378
		} elseif ( isset( $_POST['stripe_token'] ) && 'new' !== $_POST['stripe_token'] ) {
379
			$stripe_token     = wc_clean( $_POST['stripe_token'] );
380
			$maybe_saved_card = isset( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] ) && ! empty( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] );
381
382
			// This is true if the user wants to store the card to their account.
383
			if ( ( $user_id && $this->saved_cards && $maybe_saved_card ) || $force_save_source ) {
384
				$source = $customer->add_source( $stripe_token );
385
386
				if ( ! empty( $source->error ) ) {
387
					throw new Exception( $source->error->message );
388
				}
389
			} else {
390
				$set_customer = false;
391
				$source       = $stripe_token;
392
			}
393
		}
394
395
		if ( ! $set_customer ) {
396
			$customer_id = false;
397
		} else {
398
			$customer_id = $customer->get_id() ? $customer->get_id() : false;
399
		}
400
401
		return (object) array(
402
			'token_id' => $wc_token_id,
403
			'customer' => $customer_id,
404
			'source'   => $source,
405
		);
406
	}
407
408
	/**
409
	 * Save source to order.
410
	 *
411
	 * @since 3.1.0
412
	 * @version 4.0.0
413
	 * @param WC_Order $order For to which the source applies.
414
	 * @param stdClass $source Source information.
415
	 */
416
	public function save_source( $order, $source ) {
417
		$order_id = WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id();
418
419
		// Store source in the order.
420
		if ( $source->customer ) {
421
			if ( WC_Stripe_Helper::is_pre_30() ) {
422
				update_post_meta( $order_id, '_stripe_customer_id', $source->customer );
423
			} else {
424
				$order->update_meta_data( '_stripe_customer_id', $source->customer );
425
			}
426
		}
427
428
		if ( $source->source ) {
429
			if ( WC_Stripe_Helper::is_pre_30() ) {
430
				update_post_meta( $order_id, '_stripe_source_id', $source->source );
431
			} else {
432
				$order->update_meta_data( '_stripe_source_id', $source->source );
433
			}
434
		}
435
436
		if ( is_callable( array( $order, 'save' ) ) ) {
437
			$order->save();
438
		}
439
	}
440
441
	/**
442
	 * Get payment source from an order. This could be used in the future for
443
	 * a subscription as an example, therefore using the current user ID would
444
	 * not work - the customer won't be logged in :)
445
	 *
446
	 * Not using 2.6 tokens for this part since we need a customer AND a card
447
	 * token, and not just one.
448
	 *
449
	 * @since 3.1.0
450
	 * @version 4.0.0
451
	 * @param object $order
452
	 * @return object
453
	 */
454
	public function prepare_order_source( $order = null ) {
455
		$stripe_customer = new WC_Stripe_Customer();
456
		$stripe_source   = false;
457
		$token_id        = false;
458
459
		if ( $order ) {
460
			$order_id = WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id();
461
462
			$stripe_customer_id = get_post_meta( $order_id, '_stripe_customer_id', true );
463
464
			if ( $stripe_customer_id ) {
465
				$stripe_customer->set_id( $stripe_customer_id );
466
			}
467
468
			$source_id = WC_Stripe_Helper::is_pre_30() ? get_post_meta( $order_id, '_stripe_source_id', true ) : $order->get_meta( '_stripe_source_id', true );
469
470
			// Since 4.0.0, we changed card to source so we need to account for that.
471
			if ( empty( $source_id ) ) {
472
				$source_id = WC_Stripe_Helper::is_pre_30() ? get_post_meta( $order_id, '_stripe_card_id', true ) : $order->get_meta( '_stripe_card_id', true );
473
474
				// Take this opportunity to update the key name.
475
				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 );
476
477
				if ( is_callable( array( $order, 'save' ) ) ) {
478
					$order->save();
479
				}
480
			}
481
482
			if ( $source_id ) {
483
				$stripe_source = $source_id;
484
			}
485
		}
486
487
		return (object) array(
488
			'token_id' => $token_id,
489
			'customer' => $stripe_customer ? $stripe_customer->get_id() : false,
490
			'source'   => $stripe_source,
491
		);
492
	}
493
494
	/**
495
	 * Updates Stripe fees/net.
496
	 * e.g usage would be after a refund.
497
	 *
498
	 * @since 4.0.0
499
	 * @version 4.0.0
500
	 * @param object $order The order object
501
	 * @param int $balance_transaction_id
502
	 */
503
	public function update_fees( $order, $balance_transaction_id ) {
504
		$order_id = WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id();
505
506
		$balance_transaction = WC_Stripe_API::retrieve( 'balance/history/' . $balance_transaction_id );
507
508
		if ( empty( $balance_transaction->error ) ) {
509
			if ( isset( $balance_transaction ) && isset( $balance_transaction->fee ) ) {
510
				// Fees and Net needs to both come from Stripe to be accurate as the returned
511
				// values are in the local currency of the Stripe account, not from WC.
512
				$fee = ! empty( $balance_transaction->fee ) ? WC_Stripe_Helper::format_balance_fee( $balance_transaction, 'fee' ) : 0;
513
				$net = ! empty( $balance_transaction->net ) ? WC_Stripe_Helper::format_balance_fee( $balance_transaction, 'net' ) : 0;
514
515
				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 );
516
				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 );
517
518
				if ( is_callable( array( $order, 'save' ) ) ) {
519
					$order->save();
520
				}
521
			}
522
		} else {
523
			WC_Stripe_Logger::log( "Unable to update fees/net meta for order: {$order_id}" );
524
		}
525
	}
526
527
	/**
528
	 * Refund a charge.
529
	 *
530
	 * @since 3.1.0
531
	 * @version 4.0.0
532
	 * @param  int $order_id
533
	 * @param  float $amount
534
	 * @return bool
535
	 */
536
	public function process_refund( $order_id, $amount = null, $reason = '' ) {
537
		$order = wc_get_order( $order_id );
538
539
		if ( ! $order || ! $order->get_transaction_id() ) {
540
			return false;
541
		}
542
543
		$body = array();
544
545
		if ( WC_Stripe_Helper::is_pre_30() ) {
546
			$order_currency = get_post_meta( $order_id, '_order_currency', true );
547
		} else {
548
			$order_currency = $order->get_currency();
549
		}
550
551
		if ( ! is_null( $amount ) ) {
552
			$body['amount'] = WC_Stripe_Helper::get_stripe_amount( $amount, $order_currency );
553
		}
554
555
		if ( $reason ) {
556
			$body['metadata'] = array(
557
				'reason' => $reason,
558
			);
559
		}
560
561
		WC_Stripe_Logger::log( "Info: Beginning refund for order {$order->get_transaction_id()} for the amount of {$amount}" );
562
563
		$response = WC_Stripe_API::request( $body, 'charges/' . $order->get_transaction_id() . '/refunds' );
564
565
		if ( ! empty( $response->error ) ) {
566
			WC_Stripe_Logger::log( 'Error: ' . $response->error->message );
567
			return $response;
568
		} elseif ( ! empty( $response->id ) ) {
569
			$amount = wc_price( $response->amount / 100 );
570
571
			if ( in_array( strtolower( $order->get_currency() ), WC_Stripe_Helper::no_decimal_currencies() ) ) {
572
				$amount = wc_price( $response->amount );
573
			}
574
575
			if ( isset( $response->balance_transaction ) ) {
576
				$this->update_fees( $order, $response->balance_transaction );
577
			}
578
579
			/* translators: 1) dollar amount 2) transaction id 3) refund message */
580
			$refund_message = sprintf( __( 'Refunded %1$s - Refund ID: %2$s - Reason: %3$s', 'woocommerce-gateway-stripe' ), $amount, $response->id, $reason );
581
			$order->add_order_note( $refund_message );
582
			WC_Stripe_Logger::log( 'Success: ' . html_entity_decode( strip_tags( $refund_message ) ) );
583
584
			return true;
585
		}
586
	}
587
588
	/**
589
	 * Add payment method via account screen.
590
	 * We don't store the token locally, but to the Stripe API.
591
	 *
592
	 * @since 3.0.0
593
	 * @version 4.0.0
594
	 */
595
	public function add_payment_method() {
596
		$error     = false;
597
		$error_msg = __( 'There was a problem adding the card.', 'woocommerce-gateway-stripe' );
598
		$source_id = '';
599
600
		if ( empty( $_POST['stripe_source'] ) && empty( $_POST['stripe_token'] ) || ! is_user_logged_in() ) {
601
			$error = true;
602
		}
603
604
		$stripe_customer = new WC_Stripe_Customer( get_current_user_id() );
605
606
		if ( isset( $_POST['stripe_source'] ) ) {
607
			$source = json_decode( wc_clean( stripslashes( $_POST['stripe_source'] ) ) );
608
609
			if ( ! empty( $source->error ) ) {
610
				$error = true;
611
			}
612
613
			$source_id = $source->id;
614
		} elseif ( isset( $_POST['stripe_token'] ) ) {
615
			$source_id = wc_clean( $_POST['stripe_token'] );
616
		}
617
618
		$response = $stripe_customer->add_source( $source_id );
619
620
		if ( ! $response || is_wp_error( $response ) || ! empty( $response->error ) ) {
621
			$error = true;
622
		}
623
624
		if ( $error ) {
625
			wc_add_notice( $error_msg, 'error' );
626
			return;
627
		}
628
629
		return array(
630
			'result'   => 'success',
631
			'redirect' => wc_get_endpoint_url( 'payment-methods' ),
632
		);
633
	}
634
}
635