Completed
Pull Request — master (#237)
by Roy
04:06
created

includes/legacy/class-wc-gateway-stripe.php (5 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
if ( ! defined( 'ABSPATH' ) ) {
3
	exit;
4
}
5
6
/**
7
 * WC_Gateway_Stripe class.
8
 *
9
 * @extends WC_Payment_Gateway
10
 */
11
class WC_Gateway_Stripe extends WC_Payment_Gateway {
12
13
	/**
14
	 * Constructor
15
	 */
16
	public function __construct() {
17
		$this->id                   = 'stripe';
18
		$this->method_title         = __( 'Stripe', 'woocommerce-gateway-stripe' );
19
		$this->method_description   = __( 'Stripe works by adding credit card fields on the checkout and then sending the details to Stripe for verification.', 'woocommerce-gateway-stripe' );
20
		$this->has_fields           = true;
21
		$this->view_transaction_url = 'https://dashboard.stripe.com/payments/%s';
22
		$this->supports             = array(
23
			'subscriptions',
24
			'products',
25
			'refunds',
26
			'subscription_cancellation',
27
			'subscription_reactivation',
28
			'subscription_suspension',
29
			'subscription_amount_changes',
30
			'subscription_payment_method_change', // Subs 1.n compatibility
31
			'subscription_payment_method_change_customer',
32
			'subscription_payment_method_change_admin',
33
			'subscription_date_changes',
34
			'multiple_subscriptions',
35
			'pre-orders',
36
		);
37
38
		// Load the form fields
39
		$this->init_form_fields();
40
41
		// Load the settings.
42
		$this->init_settings();
43
44
		// Get setting values.
45
		$this->title                  = $this->get_option( 'title' );
46
		$this->description            = $this->get_option( 'description' );
47
		$this->enabled                = $this->get_option( 'enabled' );
48
		$this->testmode               = 'yes' === $this->get_option( 'testmode' );
49
		$this->capture                = 'yes' === $this->get_option( 'capture', 'yes' );
50
		$this->stripe_checkout        = 'yes' === $this->get_option( 'stripe_checkout' );
51
		$this->stripe_checkout_locale = $this->get_option( 'stripe_checkout_locale' );
52
		$this->stripe_checkout_image  = $this->get_option( 'stripe_checkout_image', '' );
53
		$this->saved_cards            = 'yes' === $this->get_option( 'saved_cards' );
54
		$this->secret_key             = $this->testmode ? $this->get_option( 'test_secret_key' ) : $this->get_option( 'secret_key' );
55
		$this->publishable_key        = $this->testmode ? $this->get_option( 'test_publishable_key' ) : $this->get_option( 'publishable_key' );
56
		$this->bitcoin                = 'USD' === strtoupper( get_woocommerce_currency() ) && 'yes' === $this->get_option( 'stripe_bitcoin' );
57
		$this->logging                = 'yes' === $this->get_option( 'logging' );
58
59
		if ( $this->stripe_checkout ) {
60
			$this->order_button_text = __( 'Continue to payment', 'woocommerce-gateway-stripe' );
61
		}
62
63
		if ( $this->testmode ) {
64
			$this->description .= ' ' . sprintf( __( 'TEST MODE ENABLED. In test mode, you can use the card number 4242424242424242 with any CVC and a valid expiration date or check the documentation "<a href="%s">Testing Stripe</a>" for more card numbers.', 'woocommerce-gateway-stripe' ), 'https://stripe.com/docs/testing' );
65
			$this->description  = trim( $this->description );
66
		}
67
68
		WC_Stripe_API::set_secret_key( $this->secret_key );
69
70
		// Hooks
71
		add_action( 'wp_enqueue_scripts', array( $this, 'payment_scripts' ) );
72
		add_action( 'admin_notices', array( $this, 'admin_notices' ) );
73
		add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );
74
	}
75
76
	/**
77
	 * get_icon function.
78
	 *
79
	 * @access public
80
	 * @return string
81
	 */
82 View Code Duplication
	public function get_icon() {
83
		$ext   = version_compare( WC()->version, '2.6', '>=' ) ? '.svg' : '.png';
84
		$style = version_compare( WC()->version, '2.6', '>=' ) ? 'style="margin-left: 0.3em"' : '';
85
86
		$icon  = '<img src="' . WC_HTTPS::force_https_url( WC()->plugin_url() . '/assets/images/icons/credit-cards/visa' . $ext ) . '" alt="Visa" width="32" ' . $style . ' />';
87
		$icon .= '<img src="' . WC_HTTPS::force_https_url( WC()->plugin_url() . '/assets/images/icons/credit-cards/mastercard' . $ext ) . '" alt="Mastercard" width="32" ' . $style . ' />';
88
		$icon .= '<img src="' . WC_HTTPS::force_https_url( WC()->plugin_url() . '/assets/images/icons/credit-cards/amex' . $ext ) . '" alt="Amex" width="32" ' . $style . ' />';
89
90
		$base_location = wc_get_base_location();
91
92
		if ( 'US' === $base_location['country'] ) {
93
			$icon .= '<img src="' . WC_HTTPS::force_https_url( WC()->plugin_url() . '/assets/images/icons/credit-cards/discover' . $ext ) . '" alt="Discover" width="32" ' . $style . ' />';
94
			$icon .= '<img src="' . WC_HTTPS::force_https_url( WC()->plugin_url() . '/assets/images/icons/credit-cards/jcb' . $ext ) . '" alt="JCB" width="32" ' . $style . ' />';
95
			$icon .= '<img src="' . WC_HTTPS::force_https_url( WC()->plugin_url() . '/assets/images/icons/credit-cards/diners' . $ext ) . '" alt="Diners" width="32" ' . $style . ' />';
96
		}
97
98
		if ( $this->bitcoin ) {
99
			$icon .= '<img src="' . WC_HTTPS::force_https_url( plugins_url( '/assets/images/bitcoin' . $ext, WC_STRIPE_MAIN_FILE ) ) . '" alt="Bitcoin" width="32" ' . $style . ' />';
100
		}
101
102
		return apply_filters( 'woocommerce_gateway_icon', $icon, $this->id );
103
	}
104
105
	/**
106
	 * Get Stripe amount to pay
107
	 * @return float
108
	 */
109 View Code Duplication
	public function get_stripe_amount( $total, $currency = '' ) {
110
		if ( ! $currency ) {
111
			$currency = get_woocommerce_currency();
112
		}
113
		switch ( strtoupper( $currency ) ) {
114
			// Zero decimal currencies
115
			case 'BIF' :
116
			case 'CLP' :
117
			case 'DJF' :
118
			case 'GNF' :
119
			case 'JPY' :
120
			case 'KMF' :
121
			case 'KRW' :
122
			case 'MGA' :
123
			case 'PYG' :
124
			case 'RWF' :
125
			case 'VND' :
126
			case 'VUV' :
127
			case 'XAF' :
128
			case 'XOF' :
129
			case 'XPF' :
130
				$total = absint( $total );
131
				break;
132
			default :
133
				$total = round( $total, 2 ) * 100; // In cents
134
				break;
135
		}
136
		return $total;
137
	}
138
139
	/**
140
	 * Check if SSL is enabled and notify the user
141
	 */
142
	public function admin_notices() {
143
		if ( $this->enabled == 'no' ) {
144
			return;
145
		}
146
147
		$addons = ( class_exists( 'WC_Subscriptions_Order' ) || class_exists( 'WC_Pre_Orders_Order' ) ) ? '_addons' : '';
148
149
		// Check required fields
150
		if ( ! $this->secret_key ) {
151
			echo '<div class="error"><p>' . sprintf( __( 'Stripe error: Please enter your secret key <a href="%s">here</a>', 'woocommerce-gateway-stripe' ), admin_url( 'admin.php?page=wc-settings&tab=checkout&section=wc_gateway_stripe' . $addons ) ) . '</p></div>';
152
			return;
153
154 View Code Duplication
		} elseif ( ! $this->publishable_key ) {
0 ignored issues
show
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...
155
			echo '<div class="error"><p>' . sprintf( __( 'Stripe error: Please enter your publishable key <a href="%s">here</a>', 'woocommerce-gateway-stripe' ), admin_url( 'admin.php?page=wc-settings&tab=checkout&section=wc_gateway_stripe' . $addons ) ) . '</p></div>';
156
			return;
157
		}
158
159
		// Simple check for duplicate keys
160 View Code Duplication
		if ( $this->secret_key == $this->publishable_key ) {
161
			echo '<div class="error"><p>' . sprintf( __( 'Stripe error: Your secret and publishable keys match. Please check and re-enter.', 'woocommerce-gateway-stripe' ), admin_url( 'admin.php?page=wc-settings&tab=checkout&section=wc_gateway_stripe' . $addons ) ) . '</p></div>';
162
			return;
163
		}
164
165
		// Show message if enabled and FORCE SSL is disabled and WordpressHTTPS plugin is not detected
166 View Code Duplication
		if ( ( function_exists( 'wc_site_is_https' ) && ! wc_site_is_https() ) && ( 'no' === get_option( 'woocommerce_force_ssl_checkout' ) && ! class_exists( 'WordPressHTTPS' ) ) ) {
0 ignored issues
show
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...
167
			echo '<div class="error"><p>' . sprintf( __( 'Stripe is enabled, but the <a href="%s">force SSL option</a> is disabled; your checkout may not be secure! Please enable SSL and ensure your server has a valid SSL certificate - Stripe will only work in test mode.', 'woocommerce-gateway-stripe' ), admin_url( 'admin.php?page=wc-settings&tab=checkout' ) ) . '</p></div>';
168
		}
169
	}
170
171
	/**
172
	 * Check if this gateway is enabled
173
	 */
174 View Code Duplication
	public function is_available() {
0 ignored issues
show
This method seems to be duplicated in your project.

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

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

Loading history...
175
		if ( 'yes' === $this->enabled ) {
176
			if ( ! $this->testmode && is_checkout() && ! is_ssl() ) {
177
				return false;
178
			}
179
			if ( ! $this->secret_key || ! $this->publishable_key ) {
180
				return false;
181
			}
182
			return true;
183
		}
184
		return false;
185
	}
186
187
	/**
188
	 * Initialise Gateway Settings Form Fields
189
	 */
190
	public function init_form_fields() {
191
		$this->form_fields = include( untrailingslashit( plugin_dir_path( WC_STRIPE_MAIN_FILE ) ) . '/includes/settings-stripe.php' );
192
193
		wc_enqueue_js( "
194
			jQuery( function( $ ) {
195
				$( '#woocommerce_stripe_stripe_checkout' ).change(function(){
196
					if ( $( this ).is( ':checked' ) ) {
197
						$( '#woocommerce_stripe_stripe_checkout_locale, #woocommerce_stripe_stripe_bitcoin, #woocommerce_stripe_stripe_checkout_image' ).closest( 'tr' ).show();
198
					} else {
199
						$( '#woocommerce_stripe_stripe_checkout_locale, #woocommerce_stripe_stripe_bitcoin, #woocommerce_stripe_stripe_checkout_image' ).closest( 'tr' ).hide();
200
					}
201
				}).change();
202
			});
203
		" );
204
	}
205
206
	/**
207
	 * Payment form on checkout page
208
	 */
209
	public function payment_fields() {
210
		?>
211
		<fieldset class="stripe-legacy-payment-fields">
212
			<?php
213
				if ( $this->description ) {
214
					echo apply_filters( 'wc_stripe_description', wpautop( wp_kses_post( $this->description ) ) );
215
				}
216
				if ( $this->saved_cards && is_user_logged_in() ) {
217
					$stripe_customer = new WC_Stripe_Customer( get_current_user_id() );
218
					?>
219
					<p class="form-row form-row-wide">
220
						<a class="<?php echo apply_filters( 'wc_stripe_manage_saved_cards_class', 'button' ); ?>" style="float:right;" href="<?php echo apply_filters( 'wc_stripe_manage_saved_cards_url', get_permalink( get_option( 'woocommerce_myaccount_page_id' ) ) ); ?>#saved-cards"><?php esc_html_e( 'Manage cards', 'woocommerce-gateway-stripe' ); ?></a>
221
						<?php
222
						if ( $cards = $stripe_customer->get_cards() ) {
223
							$default_card = $cards[0]->id;
224
							foreach ( (array) $cards as $card ) {
225
								if ( 'card' !== $card->object ) {
226
									continue;
227
								}
228
								?>
229
								<label for="stripe_card_<?php echo $card->id; ?>" class="brand-<?php echo esc_attr( strtolower( $card->brand ) ); ?>">
230
									<input type="radio" id="stripe_card_<?php echo $card->id; ?>" name="wc-stripe-payment-token" value="<?php echo $card->id; ?>" <?php checked( $default_card, $card->id ) ?> />
231
									<?php printf( __( '%s card ending in %s (Expires %s/%s)', 'woocommerce-gateway-stripe' ), $card->brand, $card->last4, $card->exp_month, $card->exp_year ); ?>
232
								</label>
233
								<?php
234
							}
235
						}
236
						?>
237
						<label for="new">
238
							<input type="radio" id="new" name="wc-stripe-payment-token" value="new" />
239
							<?php _e( 'Use a new credit card', 'woocommerce-gateway-stripe' ); ?>
240
						</label>
241
					</p>
242
					<?php
243
				}
244
245
				$user = wp_get_current_user();
246
247 View Code Duplication
				if ( $user ) {
248
					$user_email = get_user_meta( $user->ID, 'billing_email', true );
249
					$user_email = $user_email ? $user_email : $user->user_email;
250
				} else {
251
					$user_email = '';
252
				}
253
254
				$display = '';
255
256
				if ( $this->stripe_checkout || $this->saved_cards && ! empty( $cards ) ) {
257
					$display = 'style="display:none;"';
258
				}
259
260
				echo '<div ' . $display . ' id="stripe-payment-data"
261
					data-description=""
262
					data-email="' . esc_attr( $user_email ) . '"
263
					data-amount="' . esc_attr( $this->get_stripe_amount( WC()->cart->total ) ) . '"
264
					data-name="' . esc_attr( get_bloginfo( 'name', 'display' ) ) . '"
265
					data-currency="' . esc_attr( strtolower( get_woocommerce_currency() ) ) . '"
266
					data-image="' . esc_attr( $this->stripe_checkout_image ) . '"
267
					data-bitcoin="' . esc_attr( $this->bitcoin ? 'true' : 'false' ) . '"
268
					data-locale="' . esc_attr( $this->stripe_checkout_locale ? $this->stripe_checkout_locale : 'en' ) . '">';
269
270
				if ( ! $this->stripe_checkout ) {
271
					$this->credit_card_form( array( 'fields_have_names' => false ) );
272
				}
273
274
				echo '</div>';
275
			?>
276
		</fieldset>
277
		<?php
278
	}
279
280
	/**
281
	 * payment_scripts function.
282
	 *
283
	 * Outputs scripts used for stripe payment
284
	 *
285
	 * @access public
286
	 */
287
	public function payment_scripts() {
288
		if ( $this->stripe_checkout ) {
289
			wp_enqueue_script( 'stripe', 'https://checkout.stripe.com/v2/checkout.js', '', '2.0', true );
290
			wp_enqueue_script( 'woocommerce_stripe', plugins_url( 'assets/js/stripe_checkout.js', WC_STRIPE_MAIN_FILE ), array( 'stripe' ), WC_STRIPE_VERSION, true );
291 View Code Duplication
		} else {
0 ignored issues
show
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...
292
			wp_enqueue_script( 'stripe', 'https://js.stripe.com/v2/', '', '1.0', true );
293
			wp_enqueue_script( 'woocommerce_stripe', plugins_url( 'assets/js/stripe.js', WC_STRIPE_MAIN_FILE ), array( 'jquery-payment', 'stripe' ), WC_STRIPE_VERSION, true );
294
		}
295
296
		$stripe_params = array(
297
			'key'                  => $this->publishable_key,
298
			'i18n_terms'           => __( 'Please accept the terms and conditions first', 'woocommerce-gateway-stripe' ),
299
			'i18n_required_fields' => __( 'Please fill in required checkout fields first', 'woocommerce-gateway-stripe' ),
300
		);
301
302
		// If we're on the pay page we need to pass stripe.js the address of the order.
303
		if ( is_checkout_pay_page() && isset( $_GET['order'] ) && isset( $_GET['order_id'] ) ) {
304
			$order_key = urldecode( $_GET['order'] );
305
			$order_id  = absint( $_GET['order_id'] );
306
			$order     = wc_get_order( $order_id );
307
308
			if ( $order->id === $order_id && $order->order_key === $order_key ) {
309
				$stripe_params['billing_first_name'] = $order->billing_first_name;
310
				$stripe_params['billing_last_name']  = $order->billing_last_name;
311
				$stripe_params['billing_address_1']  = $order->billing_address_1;
312
				$stripe_params['billing_address_2']  = $order->billing_address_2;
313
				$stripe_params['billing_state']      = $order->billing_state;
314
				$stripe_params['billing_city']       = $order->billing_city;
315
				$stripe_params['billing_postcode']   = $order->billing_postcode;
316
				$stripe_params['billing_country']    = $order->billing_country;
317
			}
318
		}
319
320
		wp_localize_script( 'woocommerce_stripe', 'wc_stripe_params', apply_filters( 'wc_stripe_params', $stripe_params ) );
321
	}
322
323
	/**
324
	 * Generate the request for the payment.
325
	 * @param  WC_Order $order
326
	 * @param  object $source
327
	 * @return array()
328
	 */
329
	protected function generate_payment_request( $order, $source ) {
330
		$post_data                = array();
331
		$post_data['currency']    = strtolower( $order->get_order_currency() ? $order->get_order_currency() : get_woocommerce_currency() );
332
		$post_data['amount']      = $this->get_stripe_amount( $order->get_total(), $post_data['currency'] );
333
		$post_data['description'] = sprintf( __( '%s - Order %s', 'woocommerce-gateway-stripe' ), wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ), $order->get_order_number() );
334
		$post_data['capture']     = $this->capture ? 'true' : 'false';
335
336
		if ( ! empty( $order->billing_email ) && apply_filters( 'wc_stripe_send_stripe_receipt', false ) ) {
337
			$post_data['receipt_email'] = $order->billing_email;
338
		}
339
340
		$post_data['expand[]']    = 'balance_transaction';
341
342
		if ( $source->customer ) {
343
			$post_data['customer'] = $source->customer;
344
		}
345
346
		if ( $source->source ) {
347
			$post_data['source'] = $source->source;
348
		}
349
350
		return apply_filters( 'wc_stripe_generate_payment_request', $post_data, $order, $source );
351
	}
352
353
	/**
354
	 * Get payment source. This can be a new token or existing card.
355
	 * @param  bool $force_customer Should we force customer creation?
356
	 * @return object
357
	 */
358
	protected function get_source( $user_id, $force_customer = false ) {
359
		$stripe_customer = new WC_Stripe_Customer( $user_id );
360
		$stripe_source   = false;
361
		$token_id        = false;
362
363
		// New CC info was entered and we have a new token to process
364
		if ( isset( $_POST['stripe_token'] ) ) {
365
			$stripe_token     = wc_clean( $_POST['stripe_token'] );
366
			$maybe_saved_card = isset( $_POST['wc-stripe-new-payment-method'] ) && ! empty( $_POST['wc-stripe-new-payment-method'] );
367
368
			// This is true if the user wants to store the card to their account.
369 View Code Duplication
			if ( ( $user_id && $this->saved_cards && $maybe_saved_card ) || $force_customer ) {
370
				$stripe_source = $stripe_customer->add_card( $stripe_token );
371
372
				if ( is_wp_error( $stripe_source ) ) {
373
					throw new Exception( $stripe_source->get_error_message() );
374
				}
375
376
			} else {
377
				// Not saving token, so don't define customer either.
378
				$stripe_source   = $stripe_token;
379
				$stripe_customer = false;
380
			}
381
		}
382
383
		// Use an existing token, and then process the payment
384
		elseif ( isset( $_POST['wc-stripe-payment-token'] ) && 'new' !== $_POST['wc-stripe-payment-token'] ) {
385
			$stripe_source = wc_clean( $_POST['wc-stripe-payment-token'] );
386
		}
387
388
		return (object) array(
389
			'token_id' => $token_id,
390
			'customer' => $stripe_customer ? $stripe_customer->get_id() : false,
391
			'source'   => $stripe_source,
392
		);
393
	}
394
395
	/**
396
	 * Get payment source from an order. This could be used in the future for
397
	 * a subscription as an example, therefore using the current user ID would
398
	 * not work - the customer won't be logged in :)
399
	 *
400
	 * Not using 2.6 tokens for this part since we need a customer AND a card
401
	 * token, and not just one.
402
	 *
403
	 * @param object $order
404
	 * @return object
405
	 */
406
	protected function get_order_source( $order = null ) {
407
		$stripe_customer = new WC_Stripe_Customer();
408
		$stripe_source   = false;
409
		$token_id        = false;
410
411
		if ( $order ) {
412
			if ( $meta_value = get_post_meta( $order->id, '_stripe_customer_id', true ) ) {
413
				$stripe_customer->set_id( $meta_value );
414
			}
415
			if ( $meta_value = get_post_meta( $order->id, '_stripe_card_id', true ) ) {
416
				$stripe_source = $meta_value;
417
			}
418
		}
419
420
		return (object) array(
421
			'token_id' => $token_id,
422
			'customer' => $stripe_customer ? $stripe_customer->get_id() : false,
423
			'source'   => $stripe_source,
424
		);
425
	}
426
427
	/**
428
	 * Process the payment
429
	 */
430
	public function process_payment( $order_id, $retry = true, $force_customer = false ) {
431
		try {
432
			$order  = wc_get_order( $order_id );
433
			$source = $this->get_source( get_current_user_id(), $force_customer );
434
435 View Code Duplication
			if ( empty( $source->source ) && empty( $source->customer ) ) {
436
				$error_msg = __( 'Please enter your card details to make a payment.', 'woocommerce-gateway-stripe' );
437
				$error_msg .= ' ' . __( 'Developers: Please make sure that you are including jQuery and there are no JavaScript errors on the page.', 'woocommerce-gateway-stripe' );
438
				throw new Exception( $error_msg );
439
			}
440
441
			// Store source to order meta
442
			$this->save_source( $order, $source );
443
444
			// Handle payment
445
			if ( $order->get_total() > 0 ) {
446
447 View Code Duplication
				if ( $order->get_total() * 100 < WC_Stripe::get_minimum_amount() ) {
0 ignored issues
show
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...
448
					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::get_minimum_amount() / 100 ) ) );
449
				}
450
451
				WC_Stripe::log( "Info: Begin processing payment for order $order_id for the amount of {$order->get_total()}" );
452
453
				// Make the request
454
				$response = WC_Stripe_API::request( $this->generate_payment_request( $order, $source ) );
455
456
				if ( is_wp_error( $response ) ) {
457
					// Customer param wrong? The user may have been deleted on stripe's end. Remove customer_id. Can be retried without.
458
					if ( 'customer' === $response->get_error_code() && $retry ) {
459
						delete_user_meta( get_current_user_id(), '_stripe_customer_id' );
460
						return $this->process_payment( $order_id, false, $force_customer );
461
					}
462
					throw new Exception( $response->get_error_code() . ': ' . $response->get_error_message() );
463
				}
464
465
				// Process valid response
466
				$this->process_response( $response, $order );
467
			} else {
468
				$order->payment_complete();
469
			}
470
471
			// Remove cart
472
			WC()->cart->empty_cart();
473
474
			// Return thank you page redirect
475
			return array(
476
				'result'   => 'success',
477
				'redirect' => $this->get_return_url( $order )
478
			);
479
480
		} catch ( Exception $e ) {
481
			wc_add_notice( $e->getMessage(), 'error' );
482
			WC()->session->set( 'refresh_totals', true );
483
			WC_Stripe::log( sprintf( __( 'Error: %s', 'woocommerce-gateway-stripe' ), $e->getMessage() ) );
484
			return;
485
		}
486
	}
487
488
	/**
489
	 * Save source to order.
490
	 */
491
	protected function save_source( $order, $source ) {
492
		// Store source in the order
493
		if ( $source->customer ) {
494
			update_post_meta( $order->id, '_stripe_customer_id', $source->customer );
495
		}
496
		if ( $source->source ) {
497
			update_post_meta( $order->id, '_stripe_card_id', $source->source->id );
498
		}
499
	}
500
501
	/**
502
	 * Store extra meta data for an order from a Stripe Response.
503
	 */
504
	public function process_response( $response, $order ) {
505
		WC_Stripe::log( "Processing response: " . print_r( $response, true ) );
506
507
		// Store charge data
508
		update_post_meta( $order->id, '_stripe_charge_id', $response->id );
509
		update_post_meta( $order->id, '_stripe_charge_captured', $response->captured ? 'yes' : 'no' );
510
511
		// Store other data such as fees
512
		if ( isset( $response->balance_transaction ) && isset( $response->balance_transaction->fee ) ) {
513
			$fee = number_format( $response->balance_transaction->fee / 100, 2, '.', '' );
514
			update_post_meta( $order->id, 'Stripe Fee', $fee );
515
			update_post_meta( $order->id, 'Net Revenue From Stripe', $order->get_total() - $fee );
516
		}
517
518
		if ( $response->captured ) {
519
			$order->payment_complete( $response->id );
520
			WC_Stripe::log( "Successful charge: $response->id" );
521
		} else {
522
			add_post_meta( $order->id, '_transaction_id', $response->id, true );
523
524
			if ( $order->has_status( array( 'pending', 'failed' ) ) ) {
525
				$order->reduce_order_stock();
526
			}
527
528
			$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 ) );
529
			WC_Stripe::log( "Successful auth: $response->id" );
530
		}
531
532
		return $response;
533
	}
534
535
	/**
536
	 * Add payment method via account screen.
537
	 * We don't store the token locally, but to the Stripe API.
538
	 * @since 3.0.0
539
	 */
540
	public function add_payment_method() {
541
		if ( empty( $_POST['stripe_token'] ) || ! is_user_logged_in() ) {
542
			wc_add_notice( __( 'There was a problem adding the card.', 'woocommerce-gateway-stripe' ), 'error' );
543
			return;
544
		}
545
546
		$stripe_customer = new WC_Stripe_Customer( get_current_user_id() );
547
		$result          = $stripe_customer->add_card( wc_clean( $_POST['stripe_token'] ) );
548
549
		if ( is_wp_error( $result ) ) {
550
			throw new Exception( $result->get_error_message() );
551
		}
552
553
		return array(
554
			'result'   => 'success',
555
			'redirect' => wc_get_endpoint_url( 'payment-methods' ),
556
		);
557
	}
558
559
	/**
560
	 * Refund a charge
561
	 * @param  int $order_id
562
	 * @param  float $amount
563
	 * @return bool
564
	 */
565
	public function process_refund( $order_id, $amount = null, $reason = '' ) {
566
		$order = wc_get_order( $order_id );
567
568
		if ( ! $order || ! $order->get_transaction_id() ) {
569
			return false;
570
		}
571
572
		$body = array();
573
574
		if ( ! is_null( $amount ) ) {
575
			$body['amount']	= $this->get_stripe_amount( $amount );
576
		}
577
578
		if ( $reason ) {
579
			$body['metadata'] = array(
580
				'reason'	=> $reason,
581
			);
582
		}
583
584
		WC_Stripe::log( "Info: Beginning refund for order $order_id for the amount of {$amount}" );
585
586
		$response = WC_Stripe_API::request( $body, 'charges/' . $order->get_transaction_id() . '/refunds' );
587
588
		if ( is_wp_error( $response ) ) {
589
			WC_Stripe::log( "Error: " . $response->get_error_message() );
590
			return $response;
591
		} elseif ( ! empty( $response->id ) ) {
592
			$refund_message = sprintf( __( 'Refunded %s - Refund ID: %s - Reason: %s', 'woocommerce-gateway-stripe' ), wc_price( $response->amount / 100 ), $response->id, $reason );
593
			$order->add_order_note( $refund_message );
594
			WC_Stripe::log( "Success: " . html_entity_decode( strip_tags( $refund_message ) ) );
595
			return true;
596
		}
597
	}
598
}
599