Completed
Push — master ( 2515df...302e86 )
by Roy
02:08
created

WC_Stripe_Payment_Gateway::payment_icons()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 20
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

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