Completed
Push — master ( c3ad01...0907fe )
by Devin
19:10
created

paypal-standard.php ➔ give_process_paypal_purchase()   F

Complexity

Conditions 18
Paths 1852

Size

Total Lines 146
Code Lines 84

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 342

Importance

Changes 0
Metric Value
cc 18
eloc 84
nc 1852
nop 1
dl 0
loc 146
ccs 0
cts 85
cp 0
crap 342
rs 2
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 35 and the first side effect is on line 13.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
/**
3
 * PayPal Standard Gateway
4
 *
5
 * @package     Give
6
 * @subpackage  Gateways
7
 * @copyright   Copyright (c) 2016, WordImpress
8
 * @license     http://opensource.org/licenses/gpl-2.0.php GNU Public License
9
 * @since       1.0
10
 */
11
12
if ( ! defined( 'ABSPATH' ) ) {
13
	exit;
14
}
15
16
/**
17
 * PayPal Remove CC Form.
18
 *
19
 * PayPal Standard does not need a CC form, so remove it.
20
 *
21
 * @access private
22
 * @since  1.0
23
 */
24
add_action( 'give_paypal_cc_form', '__return_false' );
25
26
/**
27
 * Process PayPal Purchase.
28
 *
29
 * @since 1.0
30
 *
31
 * @param array $purchase_data Purchase Data
32
 *
33
 * @return void
34
 */
35
function give_process_paypal_purchase( $purchase_data ) {
36
37
	if ( ! wp_verify_nonce( $purchase_data['gateway_nonce'], 'give-gateway' ) ) {
38
		wp_die( esc_html__( 'Nonce verification has failed.', 'give' ), esc_html__( 'Error', 'give' ), array( 'response' => 403 ) );
39
	}
40
41
	$form_id  = intval( $purchase_data['post_data']['give-form-id'] );
42
	$price_id = isset( $purchase_data['post_data']['give-price-id'] ) ? $purchase_data['post_data']['give-price-id'] : '';
43
44
	// Collect payment data.
45
	$payment_data = array(
46
		'price'           => $purchase_data['price'],
47
		'give_form_title' => $purchase_data['post_data']['give-form-title'],
48
		'give_form_id'    => $form_id,
49
		'give_price_id'   => $price_id,
50
		'date'            => $purchase_data['date'],
51
		'user_email'      => $purchase_data['user_email'],
52
		'purchase_key'    => $purchase_data['purchase_key'],
53
		'currency'        => give_get_currency(),
54
		'user_info'       => $purchase_data['user_info'],
55
		'status'          => 'pending',
56
		'gateway'         => 'paypal'
57
	);
58
59
	// Record the pending payment.
60
	$payment = give_insert_payment( $payment_data );
61
62
	// Check payment.
63
	if ( ! $payment ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $payment of type false|integer is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
64
		// Record the error.
65
		give_record_gateway_error(
66
			esc_html__( 'Payment Error', 'give' ),
67
			sprintf(
68
			/* translators: %s: payment data */
69
				esc_html__( 'Payment creation failed before sending buyer to PayPal. Payment data: %s', 'give' ),
70
				json_encode( $payment_data )
71
			),
72
			$payment
73
		);
74
		// Problems? Send back.
75
		give_send_back_to_checkout( '?payment-mode=' . $purchase_data['post_data']['give-gateway'] );
76
77
	} else {
78
79
		// Only send to PayPal if the pending payment is created successfully.
80
		$listener_url = add_query_arg( 'give-listener', 'IPN', home_url( 'index.php' ) );
81
82
		// Get the success url
83
		$return_url = add_query_arg( array(
84
			'payment-confirmation' => 'paypal',
85
			'payment-id'           => $payment
86
87
		), get_permalink( give_get_option( 'success_page' ) ) );
88
89
		// Get the PayPal redirect uri
90
		$paypal_redirect = trailingslashit( give_get_paypal_redirect() ) . '?';
91
92
		//Item name - pass level name if variable priced
93
		$item_name = $purchase_data['post_data']['give-form-title'];
94
95
		//Verify has variable prices
96
		if ( give_has_variable_prices( $form_id ) && isset( $purchase_data['post_data']['give-price-id'] ) ) {
97
98
			$item_price_level_text = give_get_price_option_name( $form_id, $purchase_data['post_data']['give-price-id'] );
99
100
			$price_level_amount = give_get_price_option_amount( $form_id, $purchase_data['post_data']['give-price-id'] );
101
102
			//Donation given doesn't match selected level (must be a custom amount)
103
			if ( $price_level_amount != give_sanitize_amount( $purchase_data['price'] ) ) {
104
				$custom_amount_text = get_post_meta( $form_id, '_give_custom_amount_text', true );
105
				//user custom amount text if any, fallback to default if not
106
				$item_name .= ' - ' . ( ! empty( $custom_amount_text ) ? $custom_amount_text : esc_html__( 'Custom Amount', 'give' ) );
107
108
			} //Is there any donation level text?
109
			elseif ( ! empty( $item_price_level_text ) ) {
110
				$item_name .= ' - ' . $item_price_level_text;
111
			}
112
113
		} //Single donation: Custom Amount
114
		elseif ( give_get_form_price( $form_id ) !== give_sanitize_amount( $purchase_data['price'] ) ) {
115
			$custom_amount_text = get_post_meta( $form_id, '_give_custom_amount_text', true );
116
			//user custom amount text if any, fallback to default if not
117
			$item_name .= ' - ' . ( ! empty( $custom_amount_text ) ? $custom_amount_text : esc_html__( 'Custom Amount', 'give' ) );
118
		}
119
120
		// Setup PayPal arguments
121
		$paypal_args = array(
122
			'business'      => give_get_option( 'paypal_email', false ),
123
			'first_name'    => $purchase_data['user_info']['first_name'],
124
			'last_name'     => $purchase_data['user_info']['last_name'],
125
			'email'         => $purchase_data['user_email'],
126
			'invoice'       => $purchase_data['purchase_key'],
127
			'amount'        => $purchase_data['price'],
128
			// The all important donation amount
129
			'item_name'     => $item_name,
130
			// "Purpose" field pre-populated with Form Title
131
			'no_shipping'   => '1',
132
			'shipping'      => '0',
133
			'no_note'       => '1',
134
			'currency_code' => give_get_currency(),
135
			'charset'       => get_bloginfo( 'charset' ),
136
			'custom'        => $payment,
137
			'rm'            => '2',
138
			'return'        => $return_url,
139
			'cancel_return' => give_get_failed_transaction_uri( '?payment-id=' . $payment ),
140
			'notify_url'    => $listener_url,
141
			'page_style'    => give_get_paypal_page_style(),
142
			'cbt'           => get_bloginfo( 'name' ),
143
			'bn'            => 'givewp_SP'
144
		);
145
146
		//Add user address if present.
147
		if ( ! empty( $purchase_data['user_info']['address'] ) ) {
148
			$paypal_args['address1'] = isset( $purchase_data['user_info']['address']['line1'] ) ? $purchase_data['user_info']['address']['line1'] : '';
149
			$paypal_args['address2'] = isset( $purchase_data['user_info']['address']['line2'] ) ? $purchase_data['user_info']['address']['line2'] : '';
150
			$paypal_args['city']     = isset( $purchase_data['user_info']['address']['city'] ) ? $purchase_data['user_info']['address']['city'] : '';
151
			$paypal_args['state']    = isset( $purchase_data['user_info']['address']['state'] ) ? $purchase_data['user_info']['address']['state'] : '';
152
			$paypal_args['country']  = isset( $purchase_data['user_info']['address']['country'] ) ? $purchase_data['user_info']['address']['country'] : '';
153
		}
154
155
		//Donations or regular transactions?
156
		if ( give_get_option( 'paypal_button_type' ) === 'standard' ) {
157
			$paypal_extra_args = array(
158
				'cmd' => '_xclick',
159
			);
160
		} else {
161
			$paypal_extra_args = array(
162
				'cmd' => '_donations',
163
			);
164
		}
165
166
		$paypal_args = array_merge( $paypal_extra_args, $paypal_args );
167
		$paypal_args = apply_filters( 'give_paypal_redirect_args', $paypal_args, $purchase_data );
168
169
		// Build query
170
		$paypal_redirect .= http_build_query( $paypal_args );
171
172
		// Fix for some sites that encode the entities
173
		$paypal_redirect = str_replace( '&amp;', '&', $paypal_redirect );
174
175
		// Redirect to PayPal
176
		wp_redirect( $paypal_redirect );
177
		exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The function give_process_paypal_purchase() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
178
	}
179
180
}
181
182
add_action( 'give_gateway_paypal', 'give_process_paypal_purchase' );
183
184
/**
185
 * Listens for a PayPal IPN requests and then sends to the processing function
186
 *
187
 * @since 1.0
188
 * @return void
189
 */
190
function give_listen_for_paypal_ipn() {
191
	// Regular PayPal IPN
192
	if ( isset( $_GET['give-listener'] ) && $_GET['give-listener'] == 'IPN' ) {
193
		do_action( 'give_verify_paypal_ipn' );
194
	}
195
}
196
197
add_action( 'init', 'give_listen_for_paypal_ipn' );
198
199
/**
200
 * Process PayPal IPN
201
 *
202
 * @since 1.0
203
 * @return void
204
 */
205
function give_process_paypal_ipn() {
206
207
	// Check the request method is POST
208
	if ( isset( $_SERVER['REQUEST_METHOD'] ) && $_SERVER['REQUEST_METHOD'] != 'POST' ) {
209
		return;
210
	}
211
212
	// Set initial post data to empty string
213
	$post_data = '';
214
215
	// Fallback just in case post_max_size is lower than needed
216
	if ( ini_get( 'allow_url_fopen' ) ) {
217
		$post_data = file_get_contents( 'php://input' );
218
	} else {
219
		// If allow_url_fopen is not enabled, then make sure that post_max_size is large enough
220
		ini_set( 'post_max_size', '12M' );
221
	}
222
	// Start the encoded data collection with notification command
223
	$encoded_data = 'cmd=_notify-validate';
224
225
	// Get current arg separator
226
	$arg_separator = give_get_php_arg_separator_output();
227
228
	// Verify there is a post_data
229
	if ( $post_data || strlen( $post_data ) > 0 ) {
230
		// Append the data
231
		$encoded_data .= $arg_separator . $post_data;
232
	} else {
233
		// Check if POST is empty
234
		if ( empty( $_POST ) ) {
235
			// Nothing to do
236
			return;
237
		} else {
238
			// Loop through each POST
239
			foreach ( $_POST as $key => $value ) {
240
				// Encode the value and append the data
241
				$encoded_data .= $arg_separator . "$key=" . urlencode( $value );
242
			}
243
		}
244
	}
245
246
	// Convert collected post data to an array
247
	parse_str( $encoded_data, $encoded_data_array );
248
249
	foreach ( $encoded_data_array as $key => $value ) {
0 ignored issues
show
Bug introduced by
The expression $encoded_data_array of type null|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
250
251
		if ( false !== strpos( $key, 'amp;' ) ) {
252
			$new_key = str_replace( '&amp;', '&', $key );
253
			$new_key = str_replace( 'amp;', '&', $new_key );
254
255
			unset( $encoded_data_array[ $key ] );
256
			$encoded_data_array[ $new_key ] = $value;
257
		}
258
259
	}
260
261
	//Validate IPN request w/ PayPal if user hasn't disabled this security measure
262
	if ( ! give_get_option( 'disable_paypal_verification' ) ) {
263
264
		$remote_post_vars = array(
265
			'method'      => 'POST',
266
			'timeout'     => 45,
267
			'redirection' => 5,
268
			'httpversion' => '1.1',
269
			'blocking'    => true,
270
			'headers'     => array(
271
				'host'         => 'www.paypal.com',
272
				'connection'   => 'close',
273
				'content-type' => 'application/x-www-form-urlencoded',
274
				'post'         => '/cgi-bin/webscr HTTP/1.1',
275
276
			),
277
			'sslverify'   => false,
278
			'body'        => $encoded_data_array
279
		);
280
281
		// Validate the IPN
282
		$api_response = wp_remote_post( give_get_paypal_redirect(), $remote_post_vars );
283
284
		if ( is_wp_error( $api_response ) ) {
285
			give_record_gateway_error(
286
				esc_html__( 'IPN Error', 'give' ),
287
				sprintf(
288
				/* translators: %s: Paypal IPN response */
289
					esc_html__( 'Invalid IPN verification response. IPN data: %s', 'give' ),
290
					json_encode( $api_response )
291
				)
292
			);
293
294
			return; // Something went wrong
295
		}
296
297
		if ( $api_response['body'] !== 'VERIFIED' && give_get_option( 'disable_paypal_verification', false ) ) {
298
			give_record_gateway_error(
299
				esc_html__( 'IPN Error', 'give' ),
300
				sprintf(
301
				/* translators: %s: Paypal IPN response */
302
					esc_html__( 'Invalid IPN verification response. IPN data: %s', 'give' ),
303
					json_encode( $api_response )
304
				)
305
			);
306
307
			return; // Response not okay
308
		}
309
310
	}
311
312
	// Check if $post_data_array has been populated
313
	if ( ! is_array( $encoded_data_array ) && ! empty( $encoded_data_array ) ) {
314
		return;
315
	}
316
317
	$defaults = array(
318
		'txn_type'       => '',
319
		'payment_status' => ''
320
	);
321
322
	$encoded_data_array = wp_parse_args( $encoded_data_array, $defaults );
323
324
	$payment_id = isset( $encoded_data_array['custom'] ) ? absint( $encoded_data_array['custom'] ) : 0;
325
326
	if ( has_action( 'give_paypal_' . $encoded_data_array['txn_type'] ) ) {
327
		// Allow PayPal IPN types to be processed separately
328
		do_action( 'give_paypal_' . $encoded_data_array['txn_type'], $encoded_data_array, $payment_id );
329
	} else {
330
		// Fallback to web accept just in case the txn_type isn't present
331
		do_action( 'give_paypal_web_accept', $encoded_data_array, $payment_id );
332
	}
333
	exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The function give_process_paypal_ipn() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
334
}
335
336
add_action( 'give_verify_paypal_ipn', 'give_process_paypal_ipn' );
337
338
/**
339
 * Process web accept (one time) payment IPNs.
340
 *
341
 * @since 1.0
342
 *
343
 * @param array $data IPN Data
344
 *
345
 * @return void
346
 */
347
function give_process_paypal_web_accept_and_cart( $data, $payment_id ) {
348
349
	if ( $data['txn_type'] != 'web_accept' && $data['txn_type'] != 'cart' && $data['payment_status'] != 'Refunded' ) {
350
		return;
351
	}
352
353
	if ( empty( $payment_id ) ) {
354
		return;
355
	}
356
357
	// Collect payment details
358
	$purchase_key   = isset( $data['invoice'] ) ? $data['invoice'] : $data['item_number'];
359
	$paypal_amount  = $data['mc_gross'];
360
	$payment_status = strtolower( $data['payment_status'] );
361
	$currency_code  = strtolower( $data['mc_currency'] );
362
	$business_email = isset( $data['business'] ) && is_email( $data['business'] ) ? trim( $data['business'] ) : trim( $data['receiver_email'] );
363
	$payment_meta   = give_get_payment_meta( $payment_id );
364
365
366
	if ( give_get_payment_gateway( $payment_id ) != 'paypal' ) {
367
		return; // this isn't a PayPal standard IPN
368
	}
369
370
	// Verify payment recipient
371
	if ( strcasecmp( $business_email, trim( give_get_option( 'paypal_email' ) ) ) != 0 ) {
372
373
		give_record_gateway_error(
374
			esc_html__( 'IPN Error', 'give' ),
375
			sprintf(
376
			/* translators: %s: Paypal IPN response */
377
				esc_html__( 'Invalid business email in IPN response. IPN data: %s', 'give' ),
378
				json_encode( $data )
379
			),
380
			$payment_id
381
		);
382
		give_update_payment_status( $payment_id, 'failed' );
383
		give_insert_payment_note( $payment_id, esc_html__( 'Payment failed due to invalid PayPal business email.', 'give' ) );
384
385
		return;
386
	}
387
388
	// Verify payment currency.
389
	if ( $currency_code != strtolower( $payment_meta['currency'] ) ) {
390
391
		give_record_gateway_error(
392
			esc_html__( 'IPN Error', 'give' ),
393
			sprintf(
394
			/* translators: %s: Paypal IPN response */
395
				esc_html__( 'Invalid currency in IPN response. IPN data: %s', 'give' ),
396
				json_encode( $data )
397
			),
398
			$payment_id
399
		);
400
		give_update_payment_status( $payment_id, 'failed' );
401
		give_insert_payment_note( $payment_id, esc_html__( 'Payment failed due to invalid currency in PayPal IPN.', 'give' ) );
402
403
		return;
404
	}
405
406
	if ( ! give_get_payment_user_email( $payment_id ) ) {
407
408
		// No email associated with donation, so store email from PayPal.
409
		give_update_payment_meta( $payment_id, '_give_payment_user_email', $data['payer_email'] );
410
411
		// Setup and store the donors's details.
412
		$address            = array();
413
		$address['line1']   = ! empty( $data['address_street'] ) ? sanitize_text_field( $data['address_street'] ) : false;
414
		$address['city']    = ! empty( $data['address_city'] ) ? sanitize_text_field( $data['address_city'] ) : false;
415
		$address['state']   = ! empty( $data['address_state'] ) ? sanitize_text_field( $data['address_state'] ) : false;
416
		$address['country'] = ! empty( $data['address_country_code'] ) ? sanitize_text_field( $data['address_country_code'] ) : false;
417
		$address['zip']     = ! empty( $data['address_zip'] ) ? sanitize_text_field( $data['address_zip'] ) : false;
418
419
		$user_info = array(
420
			'id'         => '-1',
421
			'email'      => sanitize_text_field( $data['payer_email'] ),
422
			'first_name' => sanitize_text_field( $data['first_name'] ),
423
			'last_name'  => sanitize_text_field( $data['last_name'] ),
424
			'discount'   => '',
425
			'address'    => $address
426
		);
427
428
		$payment_meta['user_info'] = $user_info;
429
		give_update_payment_meta( $payment_id, '_give_payment_meta', $payment_meta );
430
	}
431
432
	if ( $payment_status == 'refunded' || $payment_status == 'reversed' ) {
433
434
		// Process a refund
435
		give_process_paypal_refund( $data, $payment_id );
436
437
	} else {
438
439
		if ( get_post_status( $payment_id ) == 'publish' ) {
440
			return; // Only complete payments once
441
		}
442
443
		// Retrieve the total purchase amount (before PayPal)
444
		$payment_amount = give_get_payment_amount( $payment_id );
445
446
		if ( number_format( (float) $paypal_amount, 2 ) < number_format( (float) $payment_amount, 2 ) ) {
447
			// The prices don't match
448
			give_record_gateway_error(
449
				esc_html__( 'IPN Error', 'give' ),
450
				sprintf(
451
				/* translators: %s: Paypal IPN response */
452
					esc_html__( 'Invalid payment amount in IPN response. IPN data: %s', 'give' ),
453
					json_encode( $data )
454
				),
455
				$payment_id
456
			);
457
			give_update_payment_status( $payment_id, 'failed' );
458
			give_insert_payment_note( $payment_id, esc_html__( 'Payment failed due to invalid amount in PayPal IPN.', 'give' ) );
459
460
			return;
461
		}
462
		if ( $purchase_key != give_get_payment_key( $payment_id ) ) {
463
			// Purchase keys don't match
464
			give_record_gateway_error(
465
				esc_html__( 'IPN Error', 'give' ),
466
				sprintf(
467
				/* translators: %s: Paypal IPN response */
468
					esc_html__( 'Invalid purchase key in IPN response. IPN data: %s', 'give' ),
469
					json_encode( $data )
470
				),
471
				$payment_id
472
			);
473
			give_update_payment_status( $payment_id, 'failed' );
474
			give_insert_payment_note( $payment_id, esc_html__( 'Payment failed due to invalid purchase key in PayPal IPN.', 'give' ) );
475
476
			return;
477
		}
478
479
		if ( $payment_status == 'completed' || give_is_test_mode() ) {
480
			give_insert_payment_note(
481
				$payment_id,
482
				sprintf(
483
				/* translators: %s: Paypal transaction ID */
484
					esc_html__( 'PayPal Transaction ID: %s', 'give' ),
485
					$data['txn_id']
486
				)
487
			);
488
			give_set_payment_transaction_id( $payment_id, $data['txn_id'] );
489
			give_update_payment_status( $payment_id, 'publish' );
490
		} else if ( 'pending' == $payment_status && isset( $data['pending_reason'] ) ) {
491
492
			// Look for possible pending reasons, such as an echeck
493
494
			$note = '';
495
496
			switch ( strtolower( $data['pending_reason'] ) ) {
497
498
				case 'echeck' :
499
500
					$note = esc_html__( 'Payment made via eCheck and will clear automatically in 5-8 days.', 'give' );
501
502
					break;
503
504
				case 'address' :
505
506
					$note = esc_html__( 'Payment requires a confirmed donor address and must be accepted manually through PayPal.', 'give' );
507
508
					break;
509
510
				case 'intl' :
511
512
					$note = esc_html__( 'Payment must be accepted manually through PayPal due to international account regulations.', 'give' );
513
514
					break;
515
516
				case 'multi-currency' :
517
518
					$note = esc_html__( 'Payment received in non-shop currency and must be accepted manually through PayPal.', 'give' );
519
520
					break;
521
522
				case 'paymentreview' :
523
				case 'regulatory_review' :
524
525
					$note = esc_html__( 'Payment is being reviewed by PayPal staff as high-risk or in possible violation of government regulations.', 'give' );
526
527
					break;
528
529
				case 'unilateral' :
530
531
					$note = esc_html__( 'Payment was sent to non-confirmed or non-registered email address.', 'give' );
532
533
					break;
534
535
				case 'upgrade' :
536
537
					$note = esc_html__( 'PayPal account must be upgraded before this payment can be accepted.', 'give' );
538
539
					break;
540
541
				case 'verify' :
542
543
					$note = esc_html__( 'PayPal account is not verified. Verify account in order to accept this payment.', 'give' );
544
545
					break;
546
547
				case 'other' :
548
549
					$note = esc_html__( 'Payment is pending for unknown reasons. Contact PayPal support for assistance.', 'give' );
550
551
					break;
552
553
			}
554
555
			if ( ! empty( $note ) ) {
556
557
				give_insert_payment_note( $payment_id, $note );
558
559
			}
560
		}
561
	}
562
}
563
564
add_action( 'give_paypal_web_accept', 'give_process_paypal_web_accept_and_cart', 10, 2 );
565
566
/**
567
 * Process PayPal IPN Refunds
568
 *
569
 * @since 1.0
570
 *
571
 * @param array $data IPN Data
572
 *
573
 * @return void
574
 */
575
function give_process_paypal_refund( $data, $payment_id = 0 ) {
576
577
	// Collect payment details
578
579
	if ( empty( $payment_id ) ) {
580
		return;
581
	}
582
583
	if ( get_post_status( $payment_id ) == 'refunded' ) {
584
		return; // Only refund payments once
585
	}
586
587
	$payment_amount = give_get_payment_amount( $payment_id );
588
	$refund_amount  = $data['payment_gross'] * - 1;
589
590
	if ( number_format( (float) $refund_amount, 2 ) < number_format( (float) $payment_amount, 2 ) ) {
591
592
		give_insert_payment_note(
593
			$payment_id,
594
			sprintf(
595
			/* translators: %s: Paypal parent transaction ID */
596
				esc_html__( 'Partial PayPal refund processed: %s', 'give' ),
597
				$data['parent_txn_id']
598
			)
599
		);
600
601
		return; // This is a partial refund
602
603
	}
604
605
	give_insert_payment_note(
606
		$payment_id,
607
		sprintf(
608
		/* translators: %s: Paypal parent transaction ID */
609
			esc_html__( 'PayPal Payment #%s Refunded for reason: %s', 'give' ),
610
			$data['parent_txn_id'], $data['reason_code']
611
		)
612
	);
613
	give_insert_payment_note(
614
		$payment_id,
615
		sprintf(
616
		/* translators: %s: Paypal transaction ID */
617
			esc_html__( 'PayPal Refund Transaction ID: %s', 'give' ),
618
			$data['txn_id']
619
		)
620
	);
621
	give_update_payment_status( $payment_id, 'refunded' );
622
}
623
624
/**
625
 * Get PayPal Redirect
626
 *
627
 * @since 1.0
628
 *
629
 * @param bool $ssl_check Is SSL?
630
 *
631
 * @return string
632
 */
633
function give_get_paypal_redirect( $ssl_check = false ) {
634
635
	if ( is_ssl() || ! $ssl_check ) {
636
		$protocal = 'https://';
637
	} else {
638
		$protocal = 'http://';
639
	}
640
641
	// Check the current payment mode
642
	if ( give_is_test_mode() ) {
643
		// Test mode
644
		$paypal_uri = $protocal . 'www.sandbox.paypal.com/cgi-bin/webscr';
645
	} else {
646
		// Live mode
647
		$paypal_uri = $protocal . 'www.paypal.com/cgi-bin/webscr';
648
	}
649
650
	return apply_filters( 'give_paypal_uri', $paypal_uri );
651
}
652
653
/**
654
 * Set the Page Style for PayPal Purchase page
655
 *
656
 * @since 1.0
657
 * @return string
658
 */
659
function give_get_paypal_page_style() {
660
	$page_style = trim( give_get_option( 'paypal_page_style', 'PayPal' ) );
661
662
	return apply_filters( 'give_paypal_page_style', $page_style );
663
}
664
665
/**
666
 * PayPal Success Page
667
 *
668
 * Shows "Donation Processing" message for PayPal payments that are still pending on site return
669
 *
670
 * @since      1.0
671
 *
672
 * @param $content
673
 *
674
 * @return string
675
 *
676
 */
677
function give_paypal_success_page_content( $content ) {
678
679
	if ( ! isset( $_GET['payment-id'] ) && ! give_get_purchase_session() ) {
680
		return $content;
681
	}
682
683
	$payment_id = isset( $_GET['payment-id'] ) ? absint( $_GET['payment-id'] ) : false;
684
685
	if ( ! $payment_id ) {
686
		$session    = give_get_purchase_session();
687
		$payment_id = give_get_purchase_id_by_key( $session['purchase_key'] );
688
	}
689
690
	$payment = get_post( $payment_id );
691
692
	if ( $payment && 'pending' == $payment->post_status ) {
693
694
		// Payment is still pending so show processing indicator to fix the Race Condition
695
		ob_start();
696
697
		give_get_template_part( 'payment', 'processing' );
698
699
		$content = ob_get_clean();
700
701
	}
702
703
	return $content;
704
705
}
706
707
add_filter( 'give_payment_confirm_paypal', 'give_paypal_success_page_content' );
708
709
/**
710
 * Given a Payment ID, extract the transaction ID
711
 *
712
 * @since  1.0
713
 *
714
 * @param  string $payment_id Payment ID
715
 *
716
 * @return string                   Transaction ID
717
 */
718
function give_paypal_get_payment_transaction_id( $payment_id ) {
719
720
	$transaction_id = '';
721
	$notes          = give_get_payment_notes( $payment_id );
722
723
	foreach ( $notes as $note ) {
724
		if ( preg_match( '/^PayPal Transaction ID: ([^\s]+)/', $note->comment_content, $match ) ) {
725
			$transaction_id = $match[1];
726
			continue;
727
		}
728
	}
729
730
	return apply_filters( 'give_paypal_set_payment_transaction_id', $transaction_id, $payment_id );
731
}
732
733
add_filter( 'give_get_payment_transaction_id-paypal', 'give_paypal_get_payment_transaction_id', 10, 1 );
734
735
/**
736
 * Given a transaction ID, generate a link to the PayPal transaction ID details
737
 *
738
 * @since  1.0
739
 *
740
 * @param  string $transaction_id The Transaction ID
741
 * @param  int    $payment_id The payment ID for this transaction
742
 *
743
 * @return string                 A link to the PayPal transaction details
744
 */
745
function give_paypal_link_transaction_id( $transaction_id, $payment_id ) {
0 ignored issues
show
Unused Code introduced by
The parameter $payment_id is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
746
747
	$paypal_base_url = 'https://history.paypal.com/cgi-bin/webscr?cmd=_history-details-from-hub&id=';
748
	$transaction_url = '<a href="' . esc_url( $paypal_base_url . $transaction_id ) . '" target="_blank">' . $transaction_id . '</a>';
749
750
	return apply_filters( 'give_paypal_link_payment_details_transaction_id', $transaction_url );
751
752
}
753
754
add_filter( 'give_payment_details_transaction_id-paypal', 'give_paypal_link_transaction_id', 10, 2 );
755