Completed
Push — master ( 91c29b...e1ae54 )
by Roy
33s
created

WC_Stripe_Payment_Gateway::is_available()   B

Complexity

Conditions 7
Paths 4

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 8
nc 4
nop 0
dl 0
loc 13
rs 8.2222
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
	 * Generate the request for the payment.
185
	 *
186
	 * @since 3.1.0
187
	 * @version 4.0.0
188
	 * @param  WC_Order $order
189
	 * @param  object $source
190
	 * @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...
191
	 */
192
	public function generate_payment_request( $order, $source ) {
193
		$settings                          = get_option( 'woocommerce_stripe_settings', array() );
194
		$statement_descriptor              = ! empty( $settings['statement_descriptor'] ) ? str_replace( "'", '', $settings['statement_descriptor'] ) : '';
195
		$capture                           = ! empty( $settings['capture'] ) && 'yes' === $settings['capture'] ? true : false;
196
		$post_data                         = array();
197
		$post_data['currency']             = strtolower( WC_Stripe_Helper::is_pre_30() ? $order->get_order_currency() : $order->get_currency() );
198
		$post_data['amount']               = WC_Stripe_Helper::get_stripe_amount( $order->get_total(), $post_data['currency'] );
199
		/* translators: 1) blog name 2) order number */
200
		$post_data['description']          = sprintf( __( '%1$s - Order %2$s', 'woocommerce-gateway-stripe' ), wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ), $order->get_order_number() );
201
		$billing_email      = WC_Stripe_Helper::is_pre_30() ? $order->billing_email : $order->get_billing_email();
202
		$billing_first_name = WC_Stripe_Helper::is_pre_30() ? $order->billing_first_name : $order->get_billing_first_name();
203
		$billing_last_name  = WC_Stripe_Helper::is_pre_30() ? $order->billing_last_name : $order->get_billing_last_name();
204
205
		if ( ! empty( $billing_email ) && apply_filters( 'wc_stripe_send_stripe_receipt', false ) ) {
206
			$post_data['receipt_email'] = $billing_email;
207
		}
208
209
		switch ( WC_Stripe_Helper::is_pre_30() ? $order->payment_method : $order->get_payment_method() ) {
210
			case 'stripe':
211
				if ( ! empty( $statement_descriptor ) ) {
212
					$post_data['statement_descriptor'] = WC_Stripe_Helper::clean_statement_descriptor( $statement_descriptor );
213
				}
214
215
				$post_data['capture'] = $capture ? 'true' : 'false';
216
				break;
217
			case 'stripe_sepa':
218
				if ( ! empty( $statement_descriptor ) ) {
219
					$post_data['statement_descriptor'] = WC_Stripe_Helper::clean_statement_descriptor( $statement_descriptor );
220
				}
221
				break;
222
		}
223
224
		$post_data['expand[]'] = 'balance_transaction';
225
226
		$metadata = array(
227
			__( 'customer_name', 'woocommerce-gateway-stripe' ) => sanitize_text_field( $billing_first_name ) . ' ' . sanitize_text_field( $billing_last_name ),
228
			__( 'customer_email', 'woocommerce-gateway-stripe' ) => sanitize_email( $billing_email ),
229
			'order_id' => WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id(),
230
		);
231
232
		$post_data['metadata'] = apply_filters( 'wc_stripe_payment_metadata', $metadata, $order, $source );
233
234
		if ( $source->customer ) {
235
			$post_data['customer'] = $source->customer;
236
		}
237
238
		if ( $source->source ) {
239
			$post_data['source'] = $source->source;
240
		}
241
242
		/**
243
		 * Filter the return value of the WC_Payment_Gateway_CC::generate_payment_request.
244
		 *
245
		 * @since 3.1.0
246
		 * @param array $post_data
247
		 * @param WC_Order $order
248
		 * @param object $source
249
		 */
250
		return apply_filters( 'wc_stripe_generate_payment_request', $post_data, $order, $source );
251
	}
252
253
	/**
254
	 * Store extra meta data for an order from a Stripe Response.
255
	 */
256
	public function process_response( $response, $order ) {
257
		WC_Stripe_Logger::log( 'Processing response: ' . print_r( $response, true ) );
258
259
		$order_id = WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id();
260
261
		$captured = ( isset( $response->captured ) && $response->captured ) ? 'yes' : 'no';
262
263
		// Store charge data
264
		WC_Stripe_Helper::is_pre_30() ? update_post_meta( $order_id, '_stripe_charge_captured', $captured ) : $order->update_meta_data( '_stripe_charge_captured', $captured );
265
266
		// Store other data such as fees
267 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...
268
			// Fees and Net needs to both come from Stripe to be accurate as the returned
269
			// values are in the local currency of the Stripe account, not from WC.
270
			$fee = ! empty( $response->balance_transaction->fee ) ? WC_Stripe_Helper::format_balance_fee( $response->balance_transaction, 'fee' ) : 0;
271
			$net = ! empty( $response->balance_transaction->net ) ? WC_Stripe_Helper::format_balance_fee( $response->balance_transaction, 'net' ) : 0;
272
			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 );
273
			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 );
274
		}
275
276
		if ( 'yes' === $captured ) {
277
			/**
278
			 * Charge can be captured but in a pending state. Payment methods
279
			 * that are asynchronous may take couple days to clear. Webhook will
280
			 * take care of the status changes.
281
			 */
282
			if ( 'pending' === $response->status ) {
283
				if ( ! wc_string_to_bool( get_post_meta( $order_id, '_order_stock_reduced', true ) ) ) {
284
					WC_Stripe_Helper::is_pre_30() ? $order->reduce_order_stock() : wc_reduce_stock_levels( $order_id );
285
				}
286
287
				WC_Stripe_Helper::is_pre_30() ? update_post_meta( $order_id, '_transaction_id', $response->id ) : $order->set_transaction_id( $response->id );
288
				/* translators: transaction id */
289
				$order->update_status( 'on-hold', sprintf( __( 'Stripe charge awaiting payment: %s.', 'woocommerce-gateway-stripe' ), $response->id ) );
290
			}
291
292
			if ( 'succeeded' === $response->status ) {
293
				$order->payment_complete( $response->id );
294
295
				/* translators: transaction id */
296
				$message = sprintf( __( 'Stripe charge complete (Charge ID: %s)', 'woocommerce-gateway-stripe' ), $response->id );
297
				$order->add_order_note( $message );
298
			}
299
300
			if ( 'failed' === $response->status ) {
301
				$localized_message = __( 'Payment processing failed. Please retry.', 'woocommerce-gateway-stripe' );
302
				$order->add_order_note( $localized_message );
303
				throw new WC_Stripe_Exception( print_r( $response, true ), $localized_message );
304
			}
305
		} else {
306
			WC_Stripe_Helper::is_pre_30() ? update_post_meta( $order_id, '_transaction_id', $response->id ) : $order->set_transaction_id( $response->id );
307
308
			if ( $order->has_status( array( 'pending', 'failed' ) ) ) {
309
				WC_Stripe_Helper::is_pre_30() ? $order->reduce_order_stock() : wc_reduce_stock_levels( $order_id );
310
			}
311
312
			/* translators: transaction id */
313
			$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 ) );
314
		}
315
316
		if ( is_callable( array( $order, 'save' ) ) ) {
317
			$order->save();
318
		}
319
320
		do_action( 'wc_gateway_stripe_process_response', $response, $order );
321
322
		return $response;
323
	}
324
325
	/**
326
	 * Sends the failed order email to admin.
327
	 *
328
	 * @since 3.1.0
329
	 * @version 4.0.0
330
	 * @param int $order_id
331
	 * @return null
332
	 */
333
	public function send_failed_order_email( $order_id ) {
334
		$emails = WC()->mailer()->get_emails();
335
		if ( ! empty( $emails ) && ! empty( $order_id ) ) {
336
			$emails['WC_Email_Failed_Order']->trigger( $order_id );
337
		}
338
	}
339
340
	/**
341
	 * Get owner details.
342
	 *
343
	 * @since 4.0.0
344
	 * @version 4.0.0
345
	 * @param object $order
346
	 * @return object $details
347
	 */
348
	public function get_owner_details( $order ) {
349
		$billing_first_name = WC_Stripe_Helper::is_pre_30() ? $order->billing_first_name : $order->get_billing_first_name();
350
		$billing_last_name  = WC_Stripe_Helper::is_pre_30() ? $order->billing_last_name : $order->get_billing_last_name();
351
352
		$details = array();
353
354
		$details['name']                   = $billing_first_name . ' ' . $billing_last_name;
355
		$details['email']                  = WC_Stripe_Helper::is_pre_30() ? $order->billing_email : $order->get_billing_email();
356
357
		$phone                             = WC_Stripe_Helper::is_pre_30() ? $order->billing_phone : $order->get_billing_phone();
358
359
		if ( ! empty( $phone ) ) {
360
			$details['phone']              = $phone;
361
		}
362
363
		$details['address']['line1']       = WC_Stripe_Helper::is_pre_30() ? $order->billing_address_1 : $order->get_billing_address_1();
364
		$details['address']['line2']       = WC_Stripe_Helper::is_pre_30() ? $order->billing_address_2 : $order->get_billing_address_2();
365
		$details['address']['state']       = WC_Stripe_Helper::is_pre_30() ? $order->billing_state : $order->get_billing_state();
366
		$details['address']['city']        = WC_Stripe_Helper::is_pre_30() ? $order->billing_city : $order->get_billing_city();
367
		$details['address']['postal_code'] = WC_Stripe_Helper::is_pre_30() ? $order->billing_postcode : $order->get_billing_postcode();
368
		$details['address']['country']     = WC_Stripe_Helper::is_pre_30() ? $order->billing_country : $order->get_billing_country();
369
370
		return (object) apply_filters( 'wc_stripe_owner_details', $details, $order );
371
	}
372
373
	/**
374
	 * Create source object by source id.
375
	 *
376
	 * @since 4.0.3
377
	 */
378
	public function get_source_object() {
379
		$source = ! empty( $_POST['stripe_source'] ) ? wc_clean( $_POST['stripe_source'] ) : '';
380
381
		if ( empty( $source ) ) {
382
			return '';
383
		}
384
385
		$source_object = WC_Stripe_API::retrieve( 'sources/' . $source );
386
387
		if ( ! empty( $source_object->error ) ) {
388
			throw new WC_Stripe_Exception( print_r( $source_object, true ), $source_object->error->message );
389
		}
390
391
		return $source_object;
392
	}
393
394
	/**
395
	 * Checks if 3DS is required.
396
	 *
397
	 * @since 4.0.4
398
	 * @param object $source_object
399
	 * @return bool
400
	 */
401
	public function is_3ds_required( $source_object ) {
402
		return (
403
			$source_object && ! empty( $source_object->card ) ) &&
404
			( 'card' === $source_object->type && 'required' === $source_object->card->three_d_secure ||
405
			( $this->three_d_secure && 'optional' === $source_object->card->three_d_secure )
406
		);
407
	}
408
409
	/**
410
	 * Checks if card is 3DS.
411
	 *
412
	 * @since 4.0.4
413
	 * @param object $source_object
414
	 * @return bool
415
	 */
416
	public function is_3ds_card( $source_object ) {
417
		return ( $source_object && 'three_d_secure' === $source_object->type );
418
	}
419
420
	/**
421
	 * Creates the 3DS source for charge.
422
	 *
423
	 * @since 4.0.0
424
	 * @since 4.0.4 Add $return_url
425
	 * @param object $order
426
	 * @param object $source_object
427
	 * @param string $return_url
428
	 * @return mixed
429
	 */
430
	public function create_3ds_source( $order, $source_object, $return_url = '' ) {
431
		$currency                    = WC_Stripe_Helper::is_pre_30() ? $order->get_order_currency() : $order->get_currency();
432
		$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...
433
		$return_url                  = empty( $return_url ) ? $this->get_stripe_return_url( $order ) : $return_url;
434
435
		$post_data                   = array();
436
		$post_data['amount']         = WC_Stripe_Helper::get_stripe_amount( $order->get_total(), $currency );
437
		$post_data['currency']       = strtolower( $currency );
438
		$post_data['type']           = 'three_d_secure';
439
		$post_data['owner']          = $this->get_owner_details( $order );
440
		$post_data['three_d_secure'] = array( 'card' => $source_object->id );
441
		$post_data['redirect']       = array( 'return_url' => $return_url );
442
443
		WC_Stripe_Logger::log( 'Info: Begin creating 3DS source...' );
444
445
		return WC_Stripe_API::request( apply_filters( 'wc_stripe_3ds_source', $post_data, $order ), 'sources' );
446
	}
447
448
	/**
449
	 * Get payment source. This can be a new token/source or existing WC token.
450
	 * If user is logged in and/or has WC account, create an account on Stripe.
451
	 * This way we can attribute the payment to the user to better fight fraud.
452
	 *
453
	 * @since 3.1.0
454
	 * @version 4.0.0
455
	 * @param object $source_object
456
	 * @param string $user_id
457
	 * @param bool $force_save_source Should we force save payment source.
458
	 *
459
	 * @throws Exception When card was not added or for and invalid card.
460
	 * @return object
461
	 */
462
	public function prepare_source( $source_object = '', $user_id, $force_save_source = false ) {
463
		$customer           = new WC_Stripe_Customer( $user_id );
464
		$set_customer       = true;
465
		$force_save_source  = apply_filters( 'wc_stripe_force_save_source', $force_save_source, $customer );
466
		$source_id          = '';
467
		$wc_token_id        = false;
468
		$payment_method     = isset( $_POST['payment_method'] ) ? wc_clean( $_POST['payment_method'] ) : 'stripe';
469
470
		// New CC info was entered and we have a new source to process.
471
		if ( ! empty( $source_object ) ) {
472
			$source_id = $source_object->id;
473
474
			// This checks to see if customer opted to save the payment method to file.
475
			$maybe_saved_card = isset( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] ) && ! empty( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] );
476
477
			/**
478
			 * This is true if the user wants to store the card to their account.
479
			 * Criteria to save to file is they are logged in, they opted to save or product requirements and the source is
480
			 * actually reusable. Either that or force_save_source is true.
481
			 */
482
			if ( ( $user_id && $this->saved_cards && $maybe_saved_card && 'reusable' === $source_object->usage ) || $force_save_source ) {
483
				$response = $customer->add_source( $source_object->id );
484
485
				if ( ! empty( $response->error ) ) {
486
					throw new WC_Stripe_Exception( print_r( $response, true ), $response->error->message );
487
				}
488
			}
489
		} elseif ( isset( $_POST[ 'wc-' . $payment_method . '-payment-token' ] ) && 'new' !== $_POST[ 'wc-' . $payment_method . '-payment-token' ] ) {
490
			// Use an existing token, and then process the payment
491
			$wc_token_id = wc_clean( $_POST[ 'wc-' . $payment_method . '-payment-token' ] );
492
			$wc_token    = WC_Payment_Tokens::get( $wc_token_id );
493
494
			if ( ! $wc_token || $wc_token->get_user_id() !== get_current_user_id() ) {
495
				WC()->session->set( 'refresh_totals', true );
496
				throw new WC_Stripe_Exception( 'Invalid payment method', __( 'Invalid payment method. Please input a new card number.', 'woocommerce-gateway-stripe' ) );
497
			}
498
499
			$source_id = $wc_token->get_token();
500
		} elseif ( isset( $_POST['stripe_token'] ) && 'new' !== $_POST['stripe_token'] ) {
501
			$stripe_token     = wc_clean( $_POST['stripe_token'] );
502
			$maybe_saved_card = isset( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] ) && ! empty( $_POST[ 'wc-' . $payment_method . '-new-payment-method' ] );
503
504
			// This is true if the user wants to store the card to their account.
505
			if ( ( $user_id && $this->saved_cards && $maybe_saved_card ) || $force_save_source ) {
506
				$response = $customer->add_source( $stripe_token );
507
508
				if ( ! empty( $response->error ) ) {
509
					throw new WC_Stripe_Exception( print_r( $response, true ), $response->error->message );
510
				}
511
			} else {
512
				$set_customer = false;
513
				$source_id    = $stripe_token;
514
			}
515
		}
516
517
		if ( ! $set_customer ) {
518
			$customer_id = false;
519
		} else {
520
			$customer_id = $customer->get_id() ? $customer->get_id() : false;
521
		}
522
523
		return (object) array(
524
			'token_id' => $wc_token_id,
525
			'customer' => $customer_id,
526
			'source'   => $source_id,
527
		);
528
	}
529
530
	/**
531
	 * Get payment source from an order. This could be used in the future for
532
	 * a subscription as an example, therefore using the current user ID would
533
	 * not work - the customer won't be logged in :)
534
	 *
535
	 * Not using 2.6 tokens for this part since we need a customer AND a card
536
	 * token, and not just one.
537
	 *
538
	 * @since 3.1.0
539
	 * @version 4.0.0
540
	 * @param object $order
541
	 * @return object
542
	 */
543
	public function prepare_order_source( $order = null ) {
544
		$stripe_customer = new WC_Stripe_Customer();
545
		$stripe_source   = false;
546
		$token_id        = false;
547
548
		if ( $order ) {
549
			$order_id = WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id();
550
551
			$stripe_customer_id = get_post_meta( $order_id, '_stripe_customer_id', true );
552
553
			if ( $stripe_customer_id ) {
554
				$stripe_customer->set_id( $stripe_customer_id );
555
			}
556
557
			$source_id = WC_Stripe_Helper::is_pre_30() ? get_post_meta( $order_id, '_stripe_source_id', true ) : $order->get_meta( '_stripe_source_id', true );
558
559
			// Since 4.0.0, we changed card to source so we need to account for that.
560
			if ( empty( $source_id ) ) {
561
				$source_id = WC_Stripe_Helper::is_pre_30() ? get_post_meta( $order_id, '_stripe_card_id', true ) : $order->get_meta( '_stripe_card_id', true );
562
563
				// Take this opportunity to update the key name.
564
				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 );
565
566
				if ( is_callable( array( $order, 'save' ) ) ) {
567
					$order->save();
568
				}
569
			}
570
571
			if ( $source_id ) {
572
				$stripe_source = $source_id;
573
			} elseif ( apply_filters( 'wc_stripe_use_default_customer_source', true ) ) {
574
				/*
575
				 * We can attempt to charge the customer's default source
576
				 * by sending empty source id.
577
				 */
578
				$stripe_source = '';
579
			}
580
		}
581
582
		return (object) array(
583
			'token_id' => $token_id,
584
			'customer' => $stripe_customer ? $stripe_customer->get_id() : false,
585
			'source'   => $stripe_source,
586
		);
587
	}
588
589
	/**
590
	 * Save source to order.
591
	 *
592
	 * @since 3.1.0
593
	 * @version 4.0.0
594
	 * @param WC_Order $order For to which the source applies.
595
	 * @param stdClass $source Source information.
596
	 */
597
	public function save_source_to_order( $order, $source ) {
598
		$order_id = WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id();
599
600
		// Store source in the order.
601
		if ( $source->customer ) {
602
			if ( WC_Stripe_Helper::is_pre_30() ) {
603
				update_post_meta( $order_id, '_stripe_customer_id', $source->customer );
604
			} else {
605
				$order->update_meta_data( '_stripe_customer_id', $source->customer );
606
			}
607
		}
608
609
		if ( $source->source ) {
610
			if ( WC_Stripe_Helper::is_pre_30() ) {
611
				update_post_meta( $order_id, '_stripe_source_id', $source->source );
612
			} else {
613
				$order->update_meta_data( '_stripe_source_id', $source->source );
614
			}
615
		}
616
617
		if ( is_callable( array( $order, 'save' ) ) ) {
618
			$order->save();
619
		}
620
	}
621
622
	/**
623
	 * Updates Stripe fees/net.
624
	 * e.g usage would be after a refund.
625
	 *
626
	 * @since 4.0.0
627
	 * @version 4.0.0
628
	 * @param object $order The order object
629
	 * @param int $balance_transaction_id
630
	 */
631
	public function update_fees( $order, $balance_transaction_id ) {
632
		$order_id = WC_Stripe_Helper::is_pre_30() ? $order->id : $order->get_id();
633
634
		$balance_transaction = WC_Stripe_API::retrieve( 'balance/history/' . $balance_transaction_id );
635
636
		if ( empty( $balance_transaction->error ) ) {
637
			if ( isset( $balance_transaction ) && isset( $balance_transaction->fee ) ) {
638
				// Fees and Net needs to both come from Stripe to be accurate as the returned
639
				// values are in the local currency of the Stripe account, not from WC.
640
				$fee = ! empty( $balance_transaction->fee ) ? WC_Stripe_Helper::format_balance_fee( $balance_transaction, 'fee' ) : 0;
641
				$net = ! empty( $balance_transaction->net ) ? WC_Stripe_Helper::format_balance_fee( $balance_transaction, 'net' ) : 0;
642
643
				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 );
644
				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 );
645
646
				if ( is_callable( array( $order, 'save' ) ) ) {
647
					$order->save();
648
				}
649
			}
650
		} else {
651
			WC_Stripe_Logger::log( "Unable to update fees/net meta for order: {$order_id}" );
652
		}
653
	}
654
655
	/**
656
	 * Refund a charge.
657
	 *
658
	 * @since 3.1.0
659
	 * @version 4.0.0
660
	 * @param  int $order_id
661
	 * @param  float $amount
662
	 * @return bool
663
	 */
664
	public function process_refund( $order_id, $amount = null, $reason = '' ) {
665
		$order = wc_get_order( $order_id );
666
667
		if ( ! $order || ! $order->get_transaction_id() ) {
668
			return false;
669
		}
670
671
		$request = array();
672
673
		if ( WC_Stripe_Helper::is_pre_30() ) {
674
			$order_currency = get_post_meta( $order_id, '_order_currency', true );
675
			$captured       = get_post_meta( $order_id, '_stripe_charge_captured', true );
676
		} else {
677
			$order_currency = $order->get_currency();
678
			$captured       = $order->get_meta( '_stripe_charge_captured', true );
679
		}
680
681
		if ( ! is_null( $amount ) ) {
682
			$request['amount'] = WC_Stripe_Helper::get_stripe_amount( $amount, $order_currency );
683
		}
684
685
		// If order is only authorized, don't pass amount.
686
		if ( 'yes' !== $captured ) {
687
			unset( $request['amount'] );
688
		}
689
690
		if ( $reason ) {
691
			$request['metadata'] = array(
692
				'reason' => $reason,
693
			);
694
		}
695
696
		$request['charge'] = $order->get_transaction_id();
697
698
		WC_Stripe_Logger::log( "Info: Beginning refund for order {$order->get_transaction_id()} for the amount of {$amount}" );
699
700
		$response = WC_Stripe_API::request( $request, 'refunds' );
701
702
		if ( ! empty( $response->error ) ) {
703
			WC_Stripe_Logger::log( 'Error: ' . $response->error->message );
704
705
			return $response;
706
707
		} elseif ( ! empty( $response->id ) ) {
708
			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 );
709
710
			$amount = wc_price( $response->amount / 100 );
711
712
			if ( in_array( strtolower( $order->get_currency() ), WC_Stripe_Helper::no_decimal_currencies() ) ) {
713
				$amount = wc_price( $response->amount );
714
			}
715
716
			if ( isset( $response->balance_transaction ) ) {
717
				$this->update_fees( $order, $response->balance_transaction );
718
			}
719
720
			/* translators: 1) dollar amount 2) transaction id 3) refund message */
721
			$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' );
722
723
			$order->add_order_note( $refund_message );
724
			WC_Stripe_Logger::log( 'Success: ' . html_entity_decode( strip_tags( $refund_message ) ) );
725
726
			return true;
727
		}
728
	}
729
730
	/**
731
	 * Add payment method via account screen.
732
	 * We don't store the token locally, but to the Stripe API.
733
	 *
734
	 * @since 3.0.0
735
	 * @version 4.0.0
736
	 */
737
	public function add_payment_method() {
738
		$error     = false;
739
		$error_msg = __( 'There was a problem adding the card.', 'woocommerce-gateway-stripe' );
740
		$source_id = '';
741
742
		if ( empty( $_POST['stripe_source'] ) && empty( $_POST['stripe_token'] ) || ! is_user_logged_in() ) {
743
			$error = true;
744
		}
745
746
		$stripe_customer = new WC_Stripe_Customer( get_current_user_id() );
747
748
		$source = ! empty( $_POST['stripe_source'] ) ? wc_clean( $_POST['stripe_source'] ) : '';
749
750
		$source_object = WC_Stripe_API::retrieve( 'sources/' . $source );
751
752
		if ( isset( $source_object ) ) {
753
			if ( ! empty( $source_object->error ) ) {
754
				$error = true;
755
			}
756
757
			$source_id = $source_object->id;
758
		} elseif ( isset( $_POST['stripe_token'] ) ) {
759
			$source_id = wc_clean( $_POST['stripe_token'] );
760
		}
761
762
		$response = $stripe_customer->add_source( $source_id );
763
764
		if ( ! $response || is_wp_error( $response ) || ! empty( $response->error ) ) {
765
			$error = true;
766
		}
767
768
		if ( $error ) {
769
			wc_add_notice( $error_msg, 'error' );
770
			WC_Stripe_Logger::log( 'Add payment method Error: ' . $error_msg );
771
			return;
772
		}
773
774
		return array(
775
			'result'   => 'success',
776
			'redirect' => wc_get_endpoint_url( 'payment-methods' ),
777
		);
778
	}
779
}
780