Completed
Push — master ( e0634c...a8a1a1 )
by Roy
02:22
created

WC_Gateway_Stripe::init_form_fields()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 0
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_CC {
12
13
	/**
14
	 * Should we capture Credit cards
15
	 *
16
	 * @var bool
17
	 */
18
	public $capture;
19
20
	/**
21
	 * Alternate credit card statement name
22
	 *
23
	 * @var bool
24
	 */
25
	public $statement_descriptor;
26
27
	/**
28
	 * Checkout enabled
29
	 *
30
	 * @var bool
31
	 */
32
	public $stripe_checkout;
33
34
	/**
35
	 * Checkout Locale
36
	 *
37
	 * @var string
38
	 */
39
	public $stripe_checkout_locale;
40
41
	/**
42
	 * Credit card image
43
	 *
44
	 * @var string
45
	 */
46
	public $stripe_checkout_image;
47
48
	/**
49
	 * Should we store the users credit cards?
50
	 *
51
	 * @var bool
52
	 */
53
	public $saved_cards;
54
55
	/**
56
	 * API access secret key
57
	 *
58
	 * @var string
59
	 */
60
	public $secret_key;
61
62
	/**
63
	 * Api access publishable key
64
	 *
65
	 * @var string
66
	 */
67
	public $publishable_key;
68
69
	/**
70
	 * Do we accept bitcoin?
71
	 *
72
	 * @var bool
73
	 */
74
	public $bitcoin;
75
76
	/**
77
	 * Alow Remember me setting for Stripe Checkout
78
	 *
79
	 * @var bool
80
	 */
81
	public $allow_remember_me;
82
83
	/**
84
	 * Do we accept Apple Pay?
85
	 *
86
	 * @var bool
87
	 */
88
	public $apple_pay;
89
90
	/**
91
	 * Apple Pay Domain Set.
92
	 *
93
	 * @var bool
94
	 */
95
	public $apple_pay_domain_set;
96
97
	/**
98
	 * Apple Pay button style.
99
	 *
100
	 * @var bool
101
	 */
102
	public $apple_pay_button;
103
104
	/**
105
	 * Is test mode active?
106
	 *
107
	 * @var bool
108
	 */
109
	public $testmode;
110
111
	/**
112
	 * Logging enabled?
113
	 *
114
	 * @var bool
115
	 */
116
	public $logging;
117
118
	/**
119
	 * Constructor
120
	 */
121
	public function __construct() {
122
		$this->id                   = 'stripe';
123
		$this->method_title         = __( 'Stripe', 'woocommerce-gateway-stripe' );
124
		$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' );
125
		$this->has_fields           = true;
126
		$this->view_transaction_url = 'https://dashboard.stripe.com/payments/%s';
127
		$this->supports             = array(
128
			'subscriptions',
129
			'products',
130
			'refunds',
131
			'subscription_cancellation',
132
			'subscription_reactivation',
133
			'subscription_suspension',
134
			'subscription_amount_changes',
135
			'subscription_payment_method_change', // Subs 1.n compatibility.
136
			'subscription_payment_method_change_customer',
137
			'subscription_payment_method_change_admin',
138
			'subscription_date_changes',
139
			'multiple_subscriptions',
140
			'pre-orders',
141
			'tokenization',
142
			'add_payment_method',
143
		);
144
145
		// Load the form fields.
146
		$this->init_form_fields();
147
148
		// Load the settings.
149
		$this->init_settings();
150
151
		// Get setting values.
152
		$this->title                  = $this->get_option( 'title' );
153
		$this->description            = $this->get_option( 'description' );
154
		$this->enabled                = $this->get_option( 'enabled' );
155
		$this->testmode               = 'yes' === $this->get_option( 'testmode' );
156
		$this->capture                = 'yes' === $this->get_option( 'capture', 'yes' );
157
		$this->statement_descriptor   = $this->get_option( 'statement_descriptor', wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ) );
158
		$this->stripe_checkout        = 'yes' === $this->get_option( 'stripe_checkout' );
159
		$this->stripe_checkout_locale = $this->get_option( 'stripe_checkout_locale' );
160
		$this->stripe_checkout_image  = $this->get_option( 'stripe_checkout_image', '' );
161
		$this->saved_cards            = 'yes' === $this->get_option( 'saved_cards' );
162
		$this->secret_key             = $this->testmode ? $this->get_option( 'test_secret_key' ) : $this->get_option( 'secret_key' );
163
		$this->publishable_key        = $this->testmode ? $this->get_option( 'test_publishable_key' ) : $this->get_option( 'publishable_key' );
164
		$this->bitcoin                = 'USD' === strtoupper( get_woocommerce_currency() ) && 'yes' === $this->get_option( 'stripe_bitcoin' );
165
		$this->apple_pay              = 'yes' === $this->get_option( 'apple_pay' );
166
		$this->apple_pay_domain_set   = 'yes' === $this->get_option( 'apple_pay_domain_set', 'no' );
167
		$this->apple_pay_button       = $this->get_option( 'apple_pay_button', 'black' );
168
		$this->logging                = 'yes' === $this->get_option( 'logging' );
169
		$this->allow_remember_me      = 'yes' === $this->get_option( 'allow_remember_me', 'no' );
170
171
		if ( $this->stripe_checkout ) {
172
			$this->order_button_text = __( 'Continue to payment', 'woocommerce-gateway-stripe' );
173
		}
174
175
		if ( $this->testmode ) {
176
			$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' );
177
			$this->description  = trim( $this->description );
178
		}
179
180
		WC_Stripe_API::set_secret_key( $this->secret_key );
181
182
		// Hooks.
183
		add_action( 'wp_enqueue_scripts', array( $this, 'payment_scripts' ) );
184
		add_action( 'admin_enqueue_scripts', array( $this, 'admin_scripts' ) );
185
		add_action( 'admin_notices', array( $this, 'admin_notices' ) );
186
		add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );
187
	}
188
189
	/**
190
	 * Renders the apple pay domain settings field (custom).
191
	 *
192
	 * @since 3.1.0
193
	 * @version 3.1.0
194
	 */
195
	public function generate_apple_pay_domain_html() {
196
		ob_start();
197
		?>
198
		<tr valign="top">
199
			<th scope="row" class="titledesc">
200
				<label for="wc-to-square"><?php esc_html_e( 'Apple Pay Domain Verification', 'woocommerce-gateway-stripe' ); ?></label>
201
				<?php echo wc_help_tip( __( 'Click button to verify your domain with Apple Pay. This is required.', 'woocommerce-gateway-stripe' ) ); ?>
202
			</th>
203
			<td class="forminp">
204
				<?php if ( ! empty( $this->secret_key ) && ! $this->apple_pay_domain_set ) { ?>
205
					<a href="#" id="wc-gateway-stripe-apple-pay-domain" class="button button-secondary"><?php esc_html_e( 'Verify Domain', 'woocommerce-gateway-stripe' ); ?></a>
206
				<?php } else { ?>
207
					<a href="#" id="wc-gateway-stripe-apple-pay-domain" class="button button-secondary"><?php esc_html_e( 'Re-verify Domain', 'woocommerce-gateway-stripe' ); ?></a>
208
					<p class="wc-stripe-apple-pay-domain-message" style="color:green;"><?php esc_html_e( 'Your domain has been verified with Apple Pay!', 'woocommerce-gateway-stripe' ); ?></p>
209
				<?php } ?>
210
				<input type="hidden" name="woocommerce_stripe_apple_pay_domain_set" class="wc-gateway-stripe-apple-pay-domain-set" />
211
			</td>
212
		</tr>
213
		<?php
214
		return ob_get_clean();
215
	}
216
217
	/**
218
	 * Validate apple pay domain set setting.
219
	 *
220
	 * @since 3.1.0
221
	 * @version 3.1.0
222
	 * @param string $key
223
	 */
224
	public function validate_apple_pay_domain_field( $key ) {
225
		$value = isset( $_POST['woocommerce_stripe_apple_pay_domain_set'] ) ? 'yes' : 'no';
226
227
		return $value;
228
	}
229
230
	/**
231
	 * Get_icon function.
232
	 *
233
	 * @access public
234
	 * @return string
235
	 */
236 View Code Duplication
	public function get_icon() {
0 ignored issues
show
Duplication introduced by
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...
237
		$ext   = version_compare( WC()->version, '2.6', '>=' ) ? '.svg' : '.png';
238
		$style = version_compare( WC()->version, '2.6', '>=' ) ? 'style="margin-left: 0.3em"' : '';
239
240
		$icon  = '<img src="' . WC_HTTPS::force_https_url( WC()->plugin_url() . '/assets/images/icons/credit-cards/visa' . $ext ) . '" alt="Visa" width="32" ' . $style . ' />';
241
		$icon .= '<img src="' . WC_HTTPS::force_https_url( WC()->plugin_url() . '/assets/images/icons/credit-cards/mastercard' . $ext ) . '" alt="Mastercard" width="32" ' . $style . ' />';
242
		$icon .= '<img src="' . WC_HTTPS::force_https_url( WC()->plugin_url() . '/assets/images/icons/credit-cards/amex' . $ext ) . '" alt="Amex" width="32" ' . $style . ' />';
243
244
		if ( 'USD' === get_woocommerce_currency() ) {
245
			$icon .= '<img src="' . WC_HTTPS::force_https_url( WC()->plugin_url() . '/assets/images/icons/credit-cards/discover' . $ext ) . '" alt="Discover" width="32" ' . $style . ' />';
246
			$icon .= '<img src="' . WC_HTTPS::force_https_url( WC()->plugin_url() . '/assets/images/icons/credit-cards/jcb' . $ext ) . '" alt="JCB" width="32" ' . $style . ' />';
247
			$icon .= '<img src="' . WC_HTTPS::force_https_url( WC()->plugin_url() . '/assets/images/icons/credit-cards/diners' . $ext ) . '" alt="Diners" width="32" ' . $style . ' />';
248
		}
249
250
		if ( $this->bitcoin && $this->stripe_checkout ) {
251
			$icon .= '<img src="' . WC_HTTPS::force_https_url( plugins_url( '/assets/images/bitcoin' . $ext, WC_STRIPE_MAIN_FILE ) ) . '" alt="Bitcoin" width="24" ' . $style . ' />';
252
		}
253
254
		return apply_filters( 'woocommerce_gateway_icon', $icon, $this->id );
255
	}
256
257
	/**
258
	 * Get Stripe amount to pay
259
	 *
260
	 * @param float  $total Amount due.
261
	 * @param string $currency Accepted currency.
262
	 *
263
	 * @return float|int
264
	 */
265 View Code Duplication
	public function get_stripe_amount( $total, $currency = '' ) {
0 ignored issues
show
Duplication introduced by
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...
266
		if ( ! $currency ) {
267
			$currency = get_woocommerce_currency();
268
		}
269
		switch ( strtoupper( $currency ) ) {
270
			// Zero decimal currencies.
271
			case 'BIF' :
272
			case 'CLP' :
273
			case 'DJF' :
274
			case 'GNF' :
275
			case 'JPY' :
276
			case 'KMF' :
277
			case 'KRW' :
278
			case 'MGA' :
279
			case 'PYG' :
280
			case 'RWF' :
281
			case 'VND' :
282
			case 'VUV' :
283
			case 'XAF' :
284
			case 'XOF' :
285
			case 'XPF' :
286
				$total = absint( $total );
287
				break;
288
			default :
289
				$total = round( $total, 2 ) * 100; // In cents.
290
				break;
291
		}
292
		return $total;
293
	}
294
295
	/**
296
	 * Check if SSL is enabled and notify the user
297
	 */
298
	public function admin_notices() {
299
		if ( 'no' === $this->enabled ) {
300
			return;
301
		}
302
303
		// Show message if enabled and FORCE SSL is disabled and WordpressHTTPS plugin is not detected.
304 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
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...
305
			echo '<div class="error stripe-ssl-message"><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>';
306
		}
307
	}
308
309
	/**
310
	 * Check if this gateway is enabled
311
	 */
312 View Code Duplication
	public function is_available() {
0 ignored issues
show
Duplication introduced by
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...
313
		if ( 'yes' === $this->enabled ) {
314
			if ( ! $this->testmode && is_checkout() && ! is_ssl() ) {
315
				return false;
316
			}
317
			if ( ! $this->secret_key || ! $this->publishable_key ) {
318
				return false;
319
			}
320
			return true;
321
		}
322
		return false;
323
	}
324
325
	/**
326
	 * Initialise Gateway Settings Form Fields
327
	 */
328
	public function init_form_fields() {
329
		$this->form_fields = include( 'settings-stripe.php' );
330
	}
331
332
	/**
333
	 * Payment form on checkout page
334
	 */
335
	public function payment_fields() {
336
		$user                 = wp_get_current_user();
337
		$display_tokenization = $this->supports( 'tokenization' ) && is_checkout() && $this->saved_cards;
338
		$total                = WC()->cart->total;
339
340
		// If paying from order, we need to get total from order not cart.
341
		if ( isset( $_GET['pay_for_order'] ) && isset( $_GET['key'] ) ) {
342
			$order = wc_get_order( wc_get_order_id_by_order_key( wc_clean( $_GET['key'] ) ) );
343
			$total = $order->get_total();
344
		}
345
346 View Code Duplication
		if ( $user->ID ) {
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...
347
			$user_email = get_user_meta( $user->ID, 'billing_email', true );
348
			$user_email = $user_email ? $user_email : $user->user_email;
349
		} else {
350
			$user_email = '';
351
		}
352
353
		if ( is_add_payment_method_page() ) {
354
			$pay_button_text = __( 'Add Card', 'woocommerce-gateway-stripe' );
355
		} else {
356
			$pay_button_text = '';
357
		}
358
359
		echo '<div
360
			id="stripe-payment-data"
361
			data-panel-label="' . esc_attr( $pay_button_text ) . '"
362
			data-description=""
363
			data-email="' . esc_attr( $user_email ) . '"
364
			data-amount="' . esc_attr( $this->get_stripe_amount( $total ) ) . '"
365
			data-name="' . esc_attr( $this->statement_descriptor ) . '"
366
			data-currency="' . esc_attr( strtolower( get_woocommerce_currency() ) ) . '"
367
			data-image="' . esc_attr( $this->stripe_checkout_image ) . '"
368
			data-bitcoin="' . esc_attr( $this->bitcoin ? 'true' : 'false' ) . '"
369
			data-locale="' . esc_attr( $this->stripe_checkout_locale ? $this->stripe_checkout_locale : 'en' ) . '"
370
			data-allow-remember-me="' . esc_attr( $this->allow_remember_me ? 'true' : 'false' ) . '">';
371
372
		if ( $this->description ) {
373
			echo apply_filters( 'wc_stripe_description', wpautop( wp_kses_post( $this->description ) ) );
374
		}
375
376
		if ( $display_tokenization ) {
377
			$this->tokenization_script();
378
			$this->saved_payment_methods();
379
		}
380
381
		if ( ! $this->stripe_checkout ) {
382
			$this->form();
383
384
			if ( $display_tokenization ) {
385
				$this->save_payment_method_checkbox();
386
			}
387
		}
388
389
		echo '</div>';
390
	}
391
392
	/**
393
	 * Localize Stripe messages based on code
394
	 *
395
	 * @since 3.0.6
396
	 * @version 3.0.6
397
	 * @return array
398
	 */
399
	public function get_localized_messages() {
400
		return apply_filters( 'wc_stripe_localized_messages', array(
401
			'invalid_number'        => __( 'The card number is not a valid credit card number.', 'woocommerce-gateway-stripe' ),
402
			'invalid_expiry_month'  => __( 'The card\'s expiration month is invalid.', 'woocommerce-gateway-stripe' ),
403
			'invalid_expiry_year'   => __( 'The card\'s expiration year is invalid.', 'woocommerce-gateway-stripe' ),
404
			'invalid_cvc'           => __( 'The card\'s security code is invalid.', 'woocommerce-gateway-stripe' ),
405
			'incorrect_number'      => __( 'The card number is incorrect.', 'woocommerce-gateway-stripe' ),
406
			'expired_card'          => __( 'The card has expired.', 'woocommerce-gateway-stripe' ),
407
			'incorrect_cvc'         => __( 'The card\'s security code is incorrect.', 'woocommerce-gateway-stripe' ),
408
			'incorrect_zip'         => __( 'The card\'s zip code failed validation.', 'woocommerce-gateway-stripe' ),
409
			'card_declined'         => __( 'The card was declined.', 'woocommerce-gateway-stripe' ),
410
			'missing'               => __( 'There is no card on a customer that is being charged.', 'woocommerce-gateway-stripe' ),
411
			'processing_error'      => __( 'An error occurred while processing the card.', 'woocommerce-gateway-stripe' ),
412
			'invalid_request_error' => __( 'Could not find payment information.', 'woocommerce-gateway-stripe' ),
413
		) );
414
	}
415
416
	/**
417
	 * Load admin scripts.
418
	 *
419
	 * @since 3.1.0
420
	 * @version 3.1.0
421
	 */
422
	public function admin_scripts() {
423
		if ( 'woocommerce_page_wc-settings' !== get_current_screen()->id ) {
424
			return;
425
		}
426
427
		$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
428
429
		wp_enqueue_script( 'woocommerce_stripe_admin', plugins_url( 'assets/js/stripe-admin' . $suffix . '.js', WC_STRIPE_MAIN_FILE ), array(), WC_STRIPE_VERSION, true );
430
431
		$stripe_admin_params = array(
432
			'localized_messages' => array(
433
				'not_valid_live_key_msg' => __( 'This is not a valid live key. Live keys start with "sk_live_" and "pk_live_".', 'woocommerce-gateway-stripe' ),
434
				'not_valid_test_key_msg' => __( 'This is not a valid test key. Test keys start with "sk_test_" and "pk_test_".', 'woocommerce-gateway-stripe' ),
435
				're_verify_button_text'  => __( 'Re-verify Domain', 'woocommerce-gateway-stripe' ),
436
			),
437
			'ajaxurl'            => admin_url( 'admin-ajax.php' ),
438
			'nonce'              => array( 
439
				'apple_pay_domain_nonce' => wp_create_nonce( '_wc_stripe_apple_pay_domain_nonce' ),
440
			),
441
		);
442
443
		wp_localize_script( 'woocommerce_stripe_admin', 'wc_stripe_admin_params', apply_filters( 'wc_stripe_admin_params', $stripe_admin_params ) );
444
	}
445
446
	/**
447
	 * payment_scripts function.
448
	 *
449
	 * Outputs scripts used for stripe payment
450
	 *
451
	 * @access public
452
	 */
453
	public function payment_scripts() {
454
		if ( ! is_cart() && ! is_checkout() && ! isset( $_GET['pay_for_order'] ) ) {
455
			return;
456
		}
457
458
		$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
459
460
		if ( $this->stripe_checkout ) {
461
			wp_enqueue_script( 'stripe_checkout', 'https://checkout.stripe.com/v2/checkout.js', '', '2.0', true );
462
			wp_enqueue_script( 'woocommerce_stripe', plugins_url( 'assets/js/stripe-checkout' . $suffix . '.js', WC_STRIPE_MAIN_FILE ), array( 'stripe' ), WC_STRIPE_VERSION, true );
463 View Code Duplication
		} else {
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...
464
			wp_enqueue_script( 'stripe', 'https://js.stripe.com/v2/', '', '1.0', true );
465
			wp_enqueue_script( 'woocommerce_stripe', plugins_url( 'assets/js/stripe' . $suffix . '.js', WC_STRIPE_MAIN_FILE ), array( 'jquery-payment', 'stripe' ), WC_STRIPE_VERSION, true );
466
		}
467
468
		$stripe_params = array(
469
			'key'                  => $this->publishable_key,
470
			'i18n_terms'           => __( 'Please accept the terms and conditions first', 'woocommerce-gateway-stripe' ),
471
			'i18n_required_fields' => __( 'Please fill in required checkout fields first', 'woocommerce-gateway-stripe' ),
472
		);
473
474
		// If we're on the pay page we need to pass stripe.js the address of the order.
475
		if ( isset( $_GET['pay_for_order'] ) && 'true' === $_GET['pay_for_order'] ) {
476
			$order_id = wc_get_order_id_by_order_key( urldecode( $_GET['key'] ) );
477
			$order    = wc_get_order( $order_id );
478
479
			$stripe_params['billing_first_name'] = $order->billing_first_name;
480
			$stripe_params['billing_last_name']  = $order->billing_last_name;
481
			$stripe_params['billing_address_1']  = $order->billing_address_1;
482
			$stripe_params['billing_address_2']  = $order->billing_address_2;
483
			$stripe_params['billing_state']      = $order->billing_state;
484
			$stripe_params['billing_city']       = $order->billing_city;
485
			$stripe_params['billing_postcode']   = $order->billing_postcode;
486
			$stripe_params['billing_country']    = $order->billing_country;
487
		}
488
489
		$stripe_params['no_prepaid_card_msg']                     = __( 'Sorry, we\'re not accepting prepaid cards at this time.', 'woocommerce-gateway-stripe' );
490
		$stripe_params['allow_prepaid_card']                      = apply_filters( 'wc_stripe_allow_prepaid_card', true ) ? 'yes' : 'no';
491
		$stripe_params['stripe_checkout_require_billing_address'] = apply_filters( 'wc_stripe_checkout_require_billing_address', false ) ? 'yes' : 'no';
492
493
		// merge localized messages to be use in JS
494
		$stripe_params = array_merge( $stripe_params, $this->get_localized_messages() );
495
496
		wp_localize_script( 'woocommerce_stripe', 'wc_stripe_params', apply_filters( 'wc_stripe_params', $stripe_params ) );
497
	}
498
499
	/**
500
	 * Generate the request for the payment.
501
	 * @param  WC_Order $order
502
	 * @param  object $source
503
	 * @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...
504
	 */
505
	protected function generate_payment_request( $order, $source ) {
506
		$post_data                = array();
507
		$post_data['currency']    = strtolower( $order->get_order_currency() ? $order->get_order_currency() : get_woocommerce_currency() );
508
		$post_data['amount']      = $this->get_stripe_amount( $order->get_total(), $post_data['currency'] );
509
		$post_data['description'] = sprintf( __( '%1$s - Order %2$s', 'woocommerce-gateway-stripe' ), $this->statement_descriptor, $order->get_order_number() );
510
		$post_data['capture']     = $this->capture ? 'true' : 'false';
511
512
		if ( ! empty( $order->billing_email ) && apply_filters( 'wc_stripe_send_stripe_receipt', false ) ) {
513
			$post_data['receipt_email'] = $order->billing_email;
514
		}
515
516
		$post_data['expand[]']    = 'balance_transaction';
517
518
		$metadata = array(
519
			__( 'Customer Name', 'woocommerce-gateway-stripe' ) => sanitize_text_field( $order->billing_first_name ) . ' ' . sanitize_text_field( $order->billing_last_name ),
520
			__( 'Customer Email', 'woocommerce-gateway-stripe' ) => sanitize_email( $order->billing_email ),
521
		);
522
523
		$post_data['metadata'] = apply_filters( 'wc_stripe_payment_metadata', $metadata, $order, $source );
524
525
		if ( $source->customer ) {
526
			$post_data['customer'] = $source->customer;
527
		}
528
529
		if ( $source->source ) {
530
			$post_data['source'] = $source->source;
531
		}
532
533
		/**
534
		 * Filter the return value of the WC_Payment_Gateway_CC::generate_payment_request.
535
		 *
536
		 * @since 3.1.0
537
		 * @param array $post_data
538
		 * @param WC_Order $order
539
		 * @param object $source
540
		 */
541
		return apply_filters( 'wc_stripe_generate_payment_request', $post_data, $order, $source );
542
	}
543
544
	/**
545
	 * Get payment source. This can be a new token or existing card.
546
	 *
547
	 * @param string $user_id
548
	 * @param bool  $force_customer Should we force customer creation.
549
	 *
550
	 * @throws Exception When card was not added or for and invalid card.
551
	 * @return object
552
	 */
553
	protected function get_source( $user_id, $force_customer = false ) {
554
		$stripe_customer = new WC_Stripe_Customer( $user_id );
555
		$stripe_source   = false;
556
		$token_id        = false;
557
558
		// New CC info was entered and we have a new token to process
559
		if ( isset( $_POST['stripe_token'] ) ) {
560
			$stripe_token     = wc_clean( $_POST['stripe_token'] );
561
			$maybe_saved_card = isset( $_POST['wc-stripe-new-payment-method'] ) && ! empty( $_POST['wc-stripe-new-payment-method'] );
562
563
			// This is true if the user wants to store the card to their account.
564 View Code Duplication
			if ( ( $user_id && $this->saved_cards && $maybe_saved_card ) || $force_customer ) {
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...
565
				$stripe_source = $stripe_customer->add_card( $stripe_token );
566
567
				if ( is_wp_error( $stripe_source ) ) {
568
					throw new Exception( $stripe_source->get_error_message() );
569
				}
570
			} else {
571
				// Not saving token, so don't define customer either.
572
				$stripe_source   = $stripe_token;
573
				$stripe_customer = false;
574
			}
575
		} elseif ( isset( $_POST['wc-stripe-payment-token'] ) && 'new' !== $_POST['wc-stripe-payment-token'] ) {
576
			// Use an existing token, and then process the payment
577
578
			$token_id = wc_clean( $_POST['wc-stripe-payment-token'] );
579
			$token    = WC_Payment_Tokens::get( $token_id );
580
581
			if ( ! $token || $token->get_user_id() !== get_current_user_id() ) {
582
				WC()->session->set( 'refresh_totals', true );
583
				throw new Exception( __( 'Invalid payment method. Please input a new card number.', 'woocommerce-gateway-stripe' ) );
584
			}
585
586
			$stripe_source = $token->get_token();
587
		}
588
589
		return (object) array(
590
			'token_id' => $token_id,
591
			'customer' => $stripe_customer ? $stripe_customer->get_id() : false,
592
			'source'   => $stripe_source,
593
		);
594
	}
595
596
	/**
597
	 * Get payment source from an order. This could be used in the future for
598
	 * a subscription as an example, therefore using the current user ID would
599
	 * not work - the customer won't be logged in :)
600
	 *
601
	 * Not using 2.6 tokens for this part since we need a customer AND a card
602
	 * token, and not just one.
603
	 *
604
	 * @param object $order
605
	 * @return object
606
	 */
607 View Code Duplication
	protected function get_order_source( $order = null ) {
0 ignored issues
show
Duplication introduced by
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...
608
		$stripe_customer = new WC_Stripe_Customer();
609
		$stripe_source   = false;
610
		$token_id        = false;
611
612
		if ( $order ) {
613
			if ( $meta_value = get_post_meta( $order->id, '_stripe_customer_id', true ) ) {
614
				$stripe_customer->set_id( $meta_value );
615
			}
616
			if ( $meta_value = get_post_meta( $order->id, '_stripe_card_id', true ) ) {
617
				$stripe_source = $meta_value;
618
			}
619
		}
620
621
		return (object) array(
622
			'token_id' => $token_id,
623
			'customer' => $stripe_customer ? $stripe_customer->get_id() : false,
624
			'source'   => $stripe_source,
625
		);
626
	}
627
628
	/**
629
	 * Process the payment
630
	 *
631
	 * @param int  $order_id Reference.
632
	 * @param bool $retry Should we retry on fail.
633
	 * @param bool $force_customer Force user creation.
634
	 *
635
	 * @throws Exception If payment will not be accepted.
636
	 *
637
	 * @return array|void
638
	 */
639
	public function process_payment( $order_id, $retry = true, $force_customer = false ) {
640
		try {
641
			$order  = wc_get_order( $order_id );
642
			$source = $this->get_source( get_current_user_id(), $force_customer );
643
644 View Code Duplication
			if ( empty( $source->source ) && empty( $source->customer ) ) {
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...
645
				$error_msg = __( 'Please enter your card details to make a payment.', 'woocommerce-gateway-stripe' );
646
				$error_msg .= ' ' . __( 'Developers: Please make sure that you are including jQuery and there are no JavaScript errors on the page.', 'woocommerce-gateway-stripe' );
647
				throw new Exception( $error_msg );
648
			}
649
650
			// Store source to order meta.
651
			$this->save_source( $order, $source );
652
653
			// Handle payment.
654
			if ( $order->get_total() > 0 ) {
655
656 View Code Duplication
				if ( $order->get_total() * 100 < WC_Stripe::get_minimum_amount() ) {
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...
657
					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 ) ) );
658
				}
659
660
				$this->log( "Info: Begin processing payment for order $order_id for the amount of {$order->get_total()}" );
661
662
				// Make the request.
663
				$response = WC_Stripe_API::request( $this->generate_payment_request( $order, $source ) );
664
665
				if ( is_wp_error( $response ) ) {
666
					// Customer param wrong? The user may have been deleted on stripe's end. Remove customer_id. Can be retried without.
667
					if ( 'customer' === $response->get_error_code() && $retry ) {
668
						delete_user_meta( get_current_user_id(), '_stripe_customer_id' );
669
						return $this->process_payment( $order_id, false, $force_customer );
670
						// Source param wrong? The CARD may have been deleted on stripe's end. Remove token and show message.
671
					} elseif ( 'source' === $response->get_error_code() && $source->token_id ) {
672
						$token = WC_Payment_Tokens::get( $source->token_id );
673
						$token->delete();
674
						$message = __( 'This card is no longer available and has been removed.', 'woocommerce-gateway-stripe' );
675
						$order->add_order_note( $message );
676
						throw new Exception( $message );
677
					}
678
679
					$localized_messages = $this->get_localized_messages();
680
681
					$message = isset( $localized_messages[ $response->get_error_code() ] ) ? $localized_messages[ $response->get_error_code() ] : $response->get_error_message();
682
683
					$order->add_order_note( $message );
684
685
					throw new Exception( $message );
686
				}
687
688
				// Process valid response.
689
				$this->process_response( $response, $order );
690
			} else {
691
				$order->payment_complete();
692
			}
693
694
			// Remove cart.
695
			WC()->cart->empty_cart();
696
697
			do_action( 'wc_gateway_stripe_process_payment', $response, $order );
0 ignored issues
show
Bug introduced by
The variable $response does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
698
699
			// Return thank you page redirect.
700
			return array(
701
				'result'   => 'success',
702
				'redirect' => $this->get_return_url( $order ),
703
			);
704
705
		} catch ( Exception $e ) {
706
			wc_add_notice( $e->getMessage(), 'error' );
707
			$this->log( sprintf( __( 'Error: %s', 'woocommerce-gateway-stripe' ), $e->getMessage() ) );
708
709
			if ( $order->has_status( array( 'pending', 'failed' ) ) ) {
710
				$this->send_failed_order_email( $order_id );
711
			}
712
713
			do_action( 'wc_gateway_stripe_process_payment_error', $e, $order );
714
715
			return array(
716
				'result'   => 'fail',
717
				'redirect' => '',
718
			);
719
		}
720
	}
721
722
	/**
723
	 * Save source to order.
724
	 *
725
	 * @param WC_Order $order For to which the source applies.
726
	 * @param stdClass $source Source information.
727
	 */
728 View Code Duplication
	protected function save_source( $order, $source ) {
0 ignored issues
show
Duplication introduced by
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...
729
		// Store source in the order.
730
		if ( $source->customer ) {
731
			update_post_meta( $order->id, '_stripe_customer_id', $source->customer );
732
		}
733
		if ( $source->source ) {
734
			update_post_meta( $order->id, '_stripe_card_id', $source->source );
735
		}
736
	}
737
738
	/**
739
	 * Store extra meta data for an order from a Stripe Response.
740
	 */
741
	public function process_response( $response, $order ) {
742
		$this->log( 'Processing response: ' . print_r( $response, true ) );
743
744
		// Store charge data
745
		update_post_meta( $order->id, '_stripe_charge_id', $response->id );
746
		update_post_meta( $order->id, '_stripe_charge_captured', $response->captured ? 'yes' : 'no' );
747
748
		// Store other data such as fees
749 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...
750
			// Fees and Net needs to both come from Stripe to be accurate as the returned
751
			// values are in the local currency of the Stripe account, not from WC.
752
			$fee = ! empty( $response->balance_transaction->fee ) ? number_format( $response->balance_transaction->fee / 100, 2, '.', '' ) : 0;
753
			$net = ! empty( $response->balance_transaction->net ) ? number_format( $response->balance_transaction->net / 100, 2, '.', '' ) : 0;
754
			update_post_meta( $order->id, 'Stripe Fee', $fee );
755
			update_post_meta( $order->id, 'Net Revenue From Stripe', $net );
756
		}
757
758
		if ( $response->captured ) {
759
			$order->payment_complete( $response->id );
760
761
			$message = sprintf( __( 'Stripe charge complete (Charge ID: %s)', 'woocommerce-gateway-stripe' ), $response->id );
762
			$order->add_order_note( $message );
763
			$this->log( 'Success: ' . $message );
764
765
		} else {
766
			add_post_meta( $order->id, '_transaction_id', $response->id, true );
767
768
			if ( $order->has_status( array( 'pending', 'failed' ) ) ) {
769
				$order->reduce_order_stock();
770
			}
771
772
			$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 ) );
773
			$this->log( "Successful auth: $response->id" );
774
		}
775
776
		return $response;
777
	}
778
779
	/**
780
	 * Add payment method via account screen.
781
	 * We don't store the token locally, but to the Stripe API.
782
	 * @since 3.0.0
783
	 */
784
	public function add_payment_method() {
785
		if ( empty( $_POST['stripe_token'] ) || ! is_user_logged_in() ) {
786
			wc_add_notice( __( 'There was a problem adding the card.', 'woocommerce-gateway-stripe' ), 'error' );
787
			return;
788
		}
789
790
		$stripe_customer = new WC_Stripe_Customer( get_current_user_id() );
791
		$card            = $stripe_customer->add_card( wc_clean( $_POST['stripe_token'] ) );
792
793
		if ( is_wp_error( $card ) ) {
794
			$localized_messages = $this->get_localized_messages();
795
			$error_msg = __( 'There was a problem adding the card.', 'woocommerce-gateway-stripe' );
796
797
			// loop through the errors to find matching localized message
798
			foreach ( $card->errors as $error => $msg ) {
799
				if ( isset( $localized_messages[ $error ] ) ) {
800
					$error_msg = $localized_messages[ $error ];
801
				}
802
			}
803
804
			wc_add_notice( $error_msg, 'error' );
805
			return;
806
		}
807
808
		return array(
809
			'result'   => 'success',
810
			'redirect' => wc_get_endpoint_url( 'payment-methods' ),
811
		);
812
	}
813
814
	/**
815
	 * Refund a charge
816
	 * @param  int $order_id
817
	 * @param  float $amount
818
	 * @return bool
819
	 */
820
	public function process_refund( $order_id, $amount = null, $reason = '' ) {
821
		$order = wc_get_order( $order_id );
822
823
		if ( ! $order || ! $order->get_transaction_id() ) {
824
			return false;
825
		}
826
827
		$body = array();
828
829
		if ( ! is_null( $amount ) ) {
830
			$body['amount']	= $this->get_stripe_amount( $amount );
831
		}
832
833
		if ( $reason ) {
834
			$body['metadata'] = array(
835
				'reason'	=> $reason,
836
			);
837
		}
838
839
		$this->log( "Info: Beginning refund for order $order_id for the amount of {$amount}" );
840
841
		$response = WC_Stripe_API::request( $body, 'charges/' . $order->get_transaction_id() . '/refunds' );
842
843
		if ( is_wp_error( $response ) ) {
844
			$this->log( 'Error: ' . $response->get_error_message() );
845
			return $response;
846
		} elseif ( ! empty( $response->id ) ) {
847
			$refund_message = sprintf( __( 'Refunded %1$s - Refund ID: %2$s - Reason: %3$s', 'woocommerce-gateway-stripe' ), wc_price( $response->amount / 100 ), $response->id, $reason );
848
			$order->add_order_note( $refund_message );
849
			$this->log( 'Success: ' . html_entity_decode( strip_tags( $refund_message ) ) );
850
			return true;
851
		}
852
	}
853
854
	/**
855
	 * Sends the failed order email to admin
856
	 *
857
	 * @version 3.1.0
858
	 * @since 3.1.0
859
	 * @param int $order_id
860
	 * @return null
861
	 */
862
	public function send_failed_order_email( $order_id ) {
863
		$emails = WC()->mailer()->get_emails();
864
		if ( ! empty( $emails ) && ! empty( $order_id ) ) {
865
			$emails['WC_Email_Failed_Order']->trigger( $order_id );
866
		}
867
	}
868
869
	/**
870
	 * Logs
871
	 *
872
	 * @since 3.1.0
873
	 * @version 3.1.0
874
	 *
875
	 * @param string $message
876
	 */
877
	public function log( $message ) {
878
		if ( $this->logging ) {
879
			WC_Stripe::log( $message );
880
		}
881
	}
882
}
883