Completed
Pull Request — master (#1412)
by Ravinder
17:25
created

paypal-standard.php ➔ give_process_paypal_payment()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 26
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 13
nc 2
nop 1
dl 0
loc 26
rs 8.8571
c 0
b 0
f 0
ccs 0
cts 17
cp 0
crap 6
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     https://opensource.org/licenses/gpl-license 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 Payment.
28
 *
29
 * @since 1.0
30
 *
31
 * @param array $payment_data Payment data.
32
 *
33
 * @return void
34
 */
35
function give_process_paypal_payment( $payment_data ) {
36
37
	// Validate nonce.
38
	give_validate_nonce( $payment_data['gateway_nonce'], 'give-gateway' );
39
	$payment_id = give_create_payment( $payment_data );
40
41
	// Check payment.
42
	if ( empty( $payment_id ) ) {
43
		// Record the error.
44
		give_record_gateway_error(
45
			esc_html__( 'Payment Error', 'give' ),
46
			sprintf(
47
				/* translators: %s: payment data */
48
				esc_html__( 'Payment creation failed before sending donor to PayPal. Payment data: %s', 'give' ),
49
				json_encode( $payment_data )
50
			),
51
			$payment_id
52
		);
53
		// Problems? Send back.
54
		give_send_back_to_checkout( '?payment-mode=' . $payment_data['post_data']['give-gateway'] );
55
	}
56
57
	// Redirect to PayPal.
58
	wp_redirect( give_build_paypal_url( $payment_id, $payment_data ) );
59
	exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The function give_process_paypal_payment() 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...
60
}
61
62
add_action( 'give_gateway_paypal', 'give_process_paypal_payment' );
63
64
/**
65
 * Listens for a PayPal IPN requests and then sends to the processing function
66
 *
67
 * @since 1.0
68
 * @return void
69
 */
70
function give_listen_for_paypal_ipn() {
71
	// Regular PayPal IPN
72
	if ( isset( $_GET['give-listener'] ) && $_GET['give-listener'] == 'IPN' ) {
73
		/**
74
		 * Fires while verifying PayPal IPN
75
		 *
76
		 * @since 1.0
77
		 */
78
		do_action( 'give_verify_paypal_ipn' );
79
	}
80
}
81
82
add_action( 'init', 'give_listen_for_paypal_ipn' );
83
84
/**
85
 * Process PayPal IPN
86
 *
87
 * @since 1.0
88
 * @return void
89
 */
90
function give_process_paypal_ipn() {
91
92
	// Check the request method is POST
93
	if ( isset( $_SERVER['REQUEST_METHOD'] ) && $_SERVER['REQUEST_METHOD'] != 'POST' ) {
94
		return;
95
	}
96
97
	// Set initial post data to empty string
98
	$post_data = '';
99
100
	// Fallback just in case post_max_size is lower than needed
101
	if ( ini_get( 'allow_url_fopen' ) ) {
102
		$post_data = file_get_contents( 'php://input' );
103
	} else {
104
		// If allow_url_fopen is not enabled, then make sure that post_max_size is large enough
105
		ini_set( 'post_max_size', '12M' );
106
	}
107
	// Start the encoded data collection with notification command
108
	$encoded_data = 'cmd=_notify-validate';
109
110
	// Get current arg separator
111
	$arg_separator = give_get_php_arg_separator_output();
112
113
	// Verify there is a post_data
114
	if ( $post_data || strlen( $post_data ) > 0 ) {
115
		// Append the data
116
		$encoded_data .= $arg_separator . $post_data;
117
	} else {
118
		// Check if POST is empty
119
		if ( empty( $_POST ) ) {
120
			// Nothing to do
121
			return;
122
		} else {
123
			// Loop through each POST
124
			foreach ( $_POST as $key => $value ) {
125
				// Encode the value and append the data.
126
				$encoded_data .= $arg_separator . "$key=" . urlencode( $value );
127
			}
128
		}
129
	}
130
131
	// Convert collected post data to an array.
132
	parse_str( $encoded_data, $encoded_data_array );
133
134
	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...
135
136
		if ( false !== strpos( $key, 'amp;' ) ) {
137
			$new_key = str_replace( '&amp;', '&', $key );
138
			$new_key = str_replace( 'amp;', '&', $new_key );
139
140
			unset( $encoded_data_array[ $key ] );
141
			$encoded_data_array[ $new_key ] = $value;
142
		}
143
	}
144
145
	// Validate IPN request w/ PayPal if user hasn't disabled this security measure
146
	if ( give_is_setting_enabled( give_get_option( 'paypal_verification' ) ) ) {
147
148
		$remote_post_vars = array(
149
			'method'      => 'POST',
150
			'timeout'     => 45,
151
			'redirection' => 5,
152
			'httpversion' => '1.1',
153
			'blocking'    => true,
154
			'headers'     => array(
155
				'host'         => 'www.paypal.com',
156
				'connection'   => 'close',
157
				'content-type' => 'application/x-www-form-urlencoded',
158
				'post'         => '/cgi-bin/webscr HTTP/1.1',
159
160
			),
161
			'sslverify'   => false,
162
			'body'        => $encoded_data_array,
163
		);
164
165
		// Validate the IPN.
166
		$api_response = wp_remote_post( give_get_paypal_redirect(), $remote_post_vars );
167
168
		if ( is_wp_error( $api_response ) ) {
169
			give_record_gateway_error(
170
				esc_html__( 'IPN Error', 'give' ),
171
				sprintf(
172
					/* translators: %s: Paypal IPN response */
173
					esc_html__( 'Invalid IPN verification response. IPN data: %s', 'give' ),
174
					json_encode( $api_response )
175
				)
176
			);
177
178
			return; // Something went wrong
179
		}
180
181
		if ( $api_response['body'] !== 'VERIFIED' ) {
182
			give_record_gateway_error(
183
				esc_html__( 'IPN Error', 'give' ),
184
				sprintf(
185
					/* translators: %s: Paypal IPN response */
186
					esc_html__( 'Invalid IPN verification response. IPN data: %s', 'give' ),
187
					json_encode( $api_response )
188
				)
189
			);
190
191
			return; // Response not okay
192
		}
193
	}
194
195
	// Check if $post_data_array has been populated
196
	if ( ! is_array( $encoded_data_array ) && ! empty( $encoded_data_array ) ) {
197
		return;
198
	}
199
200
	$defaults = array(
201
		'txn_type'       => '',
202
		'payment_status' => '',
203
	);
204
205
	$encoded_data_array = wp_parse_args( $encoded_data_array, $defaults );
206
207
	$payment_id = isset( $encoded_data_array['custom'] ) ? absint( $encoded_data_array['custom'] ) : 0;
208
	$txn_type   = $encoded_data_array['txn_type'];
209
210
	if ( has_action( 'give_paypal_' . $txn_type ) ) {
211
		/**
212
		 * Fires while processing PayPal IPN $txn_type.
213
		 *
214
		 * Allow PayPal IPN types to be processed separately.
215
		 *
216
		 * @since 1.0
217
		 *
218
		 * @param array $encoded_data_array Encoded data.
219
		 * @param int   $payment_id         Payment id.
220
		 */
221
		do_action( "give_paypal_{$txn_type}", $encoded_data_array, $payment_id );
222
	} else {
223
		/**
224
		 * Fires while process PayPal IPN.
225
		 *
226
		 * Fallback to web accept just in case the txn_type isn't present.
227
		 *
228
		 * @since 1.0
229
		 *
230
		 * @param array $encoded_data_array Encoded data.
231
		 * @param int   $payment_id         Payment id.
232
		 */
233
		do_action( 'give_paypal_web_accept', $encoded_data_array, $payment_id );
234
	}
235
	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...
236
}
237
238
add_action( 'give_verify_paypal_ipn', 'give_process_paypal_ipn' );
239
240
/**
241
 * Process web accept (one time) payment IPNs.
242
 *
243
 * @since 1.0
244
 *
245
 * @param array $data       IPN Data
246
 * @param int   $payment_id The payment ID from Give.
247
 *
248
 * @return void
249
 */
250
function give_process_paypal_web_accept_and_cart( $data, $payment_id ) {
251
252
	// Only allow through these transaction types.
253
	if ( $data['txn_type'] != 'web_accept' && $data['txn_type'] != 'cart' && strtolower( $data['payment_status'] ) != 'refunded' ) {
254
		return;
255
	}
256
257
	// Need $payment_id to continue.
258
	if ( empty( $payment_id ) ) {
259
		return;
260
	}
261
262
	// Collect donation payment details.
263
	$paypal_amount  = $data['mc_gross'];
264
	$payment_status = strtolower( $data['payment_status'] );
265
	$currency_code  = strtolower( $data['mc_currency'] );
266
	$business_email = isset( $data['business'] ) && is_email( $data['business'] ) ? trim( $data['business'] ) : trim( $data['receiver_email'] );
267
	$payment_meta   = give_get_payment_meta( $payment_id );
268
269
	// Must be a PayPal standard IPN.
270
	if ( give_get_payment_gateway( $payment_id ) != 'paypal' ) {
271
		return;
272
	}
273
274
	// Verify payment recipient
275
	if ( strcasecmp( $business_email, trim( give_get_option( 'paypal_email' ) ) ) != 0 ) {
276
277
		give_record_gateway_error(
278
			esc_html__( 'IPN Error', 'give' ),
279
			sprintf(
280
				/* translators: %s: Paypal IPN response */
281
				esc_html__( 'Invalid business email in IPN response. IPN data: %s', 'give' ),
282
				json_encode( $data )
283
			),
284
			$payment_id
285
		);
286
		give_update_payment_status( $payment_id, 'failed' );
287
		give_insert_payment_note( $payment_id, esc_html__( 'Payment failed due to invalid PayPal business email.', 'give' ) );
288
289
		return;
290
	}
291
292
	// Verify payment currency.
293
	if ( $currency_code != strtolower( $payment_meta['currency'] ) ) {
294
295
		give_record_gateway_error(
296
			esc_html__( 'IPN Error', 'give' ),
297
			sprintf(
298
				/* translators: %s: Paypal IPN response */
299
				esc_html__( 'Invalid currency in IPN response. IPN data: %s', 'give' ),
300
				json_encode( $data )
301
			),
302
			$payment_id
303
		);
304
		give_update_payment_status( $payment_id, 'failed' );
305
		give_insert_payment_note( $payment_id, esc_html__( 'Payment failed due to invalid currency in PayPal IPN.', 'give' ) );
306
307
		return;
308
	}
309
310
	// Process refunds & reversed.
311
	if ( $payment_status == 'refunded' || $payment_status == 'reversed' ) {
312
		give_process_paypal_refund( $data, $payment_id );
313
314
		return;
315
	}
316
317
	// Only complete payments once.
318
	if ( get_post_status( $payment_id ) == 'publish' ) {
319
		return;
320
	}
321
322
	// Retrieve the total donation amount (before PayPal).
323
	$payment_amount = give_get_payment_amount( $payment_id );
324
325
	// Check that the donation PP and local db amounts match.
326
	if ( number_format( (float) $paypal_amount, 2 ) < number_format( (float) $payment_amount, 2 ) ) {
327
		// The prices don't match
328
		give_record_gateway_error(
329
			esc_html__( 'IPN Error', 'give' ),
330
			sprintf(
331
				/* translators: %s: Paypal IPN response */
332
				esc_html__( 'Invalid payment amount in IPN response. IPN data: %s', 'give' ),
333
				json_encode( $data )
334
			),
335
			$payment_id
336
		);
337
		give_update_payment_status( $payment_id, 'failed' );
338
		give_insert_payment_note( $payment_id, esc_html__( 'Payment failed due to invalid amount in PayPal IPN.', 'give' ) );
339
340
		return;
341
	}
342
343
	// Process completed donations.
344
	if ( $payment_status == 'completed' || give_is_test_mode() ) {
345
346
		give_insert_payment_note(
347
			$payment_id,
348
			sprintf(
349
				/* translators: %s: Paypal transaction ID */
350
				esc_html__( 'PayPal Transaction ID: %s', 'give' ),
351
				$data['txn_id']
352
			)
353
		);
354
		give_set_payment_transaction_id( $payment_id, $data['txn_id'] );
355
		give_update_payment_status( $payment_id, 'publish' );
356
357
	} elseif ( 'pending' == $payment_status && isset( $data['pending_reason'] ) ) {
358
359
		// Look for possible pending reasons, such as an echeck.
360
		$note = give_paypal_get_pending_donation_note( strtolower( $data['pending_reason'] ) );
361
362
		if ( ! empty( $note ) ) {
363
364
			give_insert_payment_note( $payment_id, $note );
365
366
		}
367
	}
368
369
}
370
371
add_action( 'give_paypal_web_accept', 'give_process_paypal_web_accept_and_cart', 10, 2 );
372
373
/**
374
 * Process PayPal IPN Refunds
375
 *
376
 * @since 1.0
377
 *
378
 * @param array $data       IPN Data
379
 * @param int   $payment_id The payment ID.
380
 *
381
 * @return void
382
 */
383
function give_process_paypal_refund( $data, $payment_id = 0 ) {
384
385
	// Collect payment details
386
	if ( empty( $payment_id ) ) {
387
		return;
388
	}
389
390
	if ( get_post_status( $payment_id ) == 'refunded' ) {
391
		return; // Only refund payments once
392
	}
393
394
	$payment_amount = give_get_payment_amount( $payment_id );
395
	$refund_amount  = $data['payment_gross'] * - 1;
396
397
	if ( number_format( (float) $refund_amount, 2 ) < number_format( (float) $payment_amount, 2 ) ) {
398
399
		give_insert_payment_note(
400
			$payment_id,
401
			sprintf(
402
				/* translators: %s: Paypal parent transaction ID */
403
				esc_html__( 'Partial PayPal refund processed: %s', 'give' ),
404
				$data['parent_txn_id']
405
			)
406
		);
407
408
		return; // This is a partial refund
409
410
	}
411
412
	give_insert_payment_note(
413
		$payment_id,
414
		sprintf(
415
			/* translators: 1: Paypal parent transaction ID 2. Paypal reason code */
416
			esc_html__( 'PayPal Payment #%1$s Refunded for reason: %2$s', 'give' ),
417
			$data['parent_txn_id'],
418
			$data['reason_code']
419
		)
420
	);
421
	give_insert_payment_note(
422
		$payment_id,
423
		sprintf(
424
			/* translators: %s: Paypal transaction ID */
425
			esc_html__( 'PayPal Refund Transaction ID: %s', 'give' ),
426
			$data['txn_id']
427
		)
428
	);
429
	give_update_payment_status( $payment_id, 'refunded' );
430
}
431
432
/**
433
 * Get PayPal Redirect
434
 *
435
 * @since 1.0
436
 *
437
 * @param bool $ssl_check Is SSL?
438
 *
439
 * @return string
440
 */
441
function give_get_paypal_redirect( $ssl_check = false ) {
442
443
	if ( is_ssl() || ! $ssl_check ) {
444
		$protocol = 'https://';
445
	} else {
446
		$protocol = 'http://';
447
	}
448
449
	// Check the current payment mode
450
	if ( give_is_test_mode() ) {
451
		// Test mode
452
		$paypal_uri = $protocol . 'www.sandbox.paypal.com/cgi-bin/webscr';
453
	} else {
454
		// Live mode
455
		$paypal_uri = $protocol . 'www.paypal.com/cgi-bin/webscr';
456
	}
457
458
	return apply_filters( 'give_paypal_uri', $paypal_uri );
459
}
460
461
/**
462
 * Set the Page Style for offsite PayPal page.
463
 *
464
 * @since 1.0
465
 * @return string
466
 */
467
function give_get_paypal_page_style() {
468
	$page_style = trim( give_get_option( 'paypal_page_style', 'PayPal' ) );
469
470
	return apply_filters( 'give_paypal_page_style', $page_style );
471
}
472
473
/**
474
 * PayPal Success Page
475
 *
476
 * Shows "Donation Processing" message for PayPal payments that are still pending on site return
477
 *
478
 * @since      1.0
479
 *
480
 * @param $content
481
 *
482
 * @return string
483
 */
484
function give_paypal_success_page_content( $content ) {
485
486
	if ( ! isset( $_GET['payment-id'] ) && ! give_get_purchase_session() ) {
487
		return $content;
488
	}
489
490
	$payment_id = isset( $_GET['payment-id'] ) ? absint( $_GET['payment-id'] ) : false;
491
492
	if ( ! $payment_id ) {
493
		$session    = give_get_purchase_session();
494
		$payment_id = give_get_purchase_id_by_key( $session['purchase_key'] );
495
	}
496
497
	$payment = get_post( $payment_id );
498
	if ( $payment && 'pending' == $payment->post_status ) {
499
500
		// Payment is still pending so show processing indicator to fix the race condition.
501
		ob_start();
502
503
		give_get_template_part( 'payment', 'processing' );
504
505
		$content = ob_get_clean();
506
507
	}
508
509
	return $content;
510
511
}
512
513
add_filter( 'give_payment_confirm_paypal', 'give_paypal_success_page_content' );
514
515
/**
516
 * Given a Payment ID, extract the transaction ID
517
 *
518
 * @since  1.0
519
 *
520
 * @param  string $payment_id Payment ID
521
 *
522
 * @return string                   Transaction ID
523
 */
524
function give_paypal_get_payment_transaction_id( $payment_id ) {
525
526
	$transaction_id = '';
527
	$notes          = give_get_payment_notes( $payment_id );
528
529
	foreach ( $notes as $note ) {
530
		if ( preg_match( '/^PayPal Transaction ID: ([^\s]+)/', $note->comment_content, $match ) ) {
531
			$transaction_id = $match[1];
532
			continue;
533
		}
534
	}
535
536
	return apply_filters( 'give_paypal_set_payment_transaction_id', $transaction_id, $payment_id );
537
}
538
539
add_filter( 'give_get_payment_transaction_id-paypal', 'give_paypal_get_payment_transaction_id', 10, 1 );
540
541
/**
542
 * Given a transaction ID, generate a link to the PayPal transaction ID details
543
 *
544
 * @since  1.0
545
 *
546
 * @param  string $transaction_id The Transaction ID
547
 * @param  int    $payment_id     The payment ID for this transaction
548
 *
549
 * @return string                 A link to the PayPal transaction details
550
 */
551
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...
552
553
	$paypal_base_url = 'https://history.paypal.com/cgi-bin/webscr?cmd=_history-details-from-hub&id=';
554
	$transaction_url = '<a href="' . esc_url( $paypal_base_url . $transaction_id ) . '" target="_blank">' . $transaction_id . '</a>';
555
556
	return apply_filters( 'give_paypal_link_payment_details_transaction_id', $transaction_url );
557
558
}
559
560
add_filter( 'give_payment_details_transaction_id-paypal', 'give_paypal_link_transaction_id', 10, 2 );
561
562
563
/**
564
 * Get pending donation note.
565
 *
566
 * @since 1.6.3
567
 *
568
 * @param $pending_reason
569
 *
570
 * @return string
571
 */
572
function give_paypal_get_pending_donation_note( $pending_reason ) {
573
574
	$note = '';
575
576
	switch ( $pending_reason ) {
577
578
		case 'echeck' :
579
580
			$note = esc_html__( 'Payment made via eCheck and will clear automatically in 5-8 days.', 'give' );
581
582
			break;
583
584
		case 'address' :
585
586
			$note = esc_html__( 'Payment requires a confirmed donor address and must be accepted manually through PayPal.', 'give' );
587
588
			break;
589
590
		case 'intl' :
591
592
			$note = esc_html__( 'Payment must be accepted manually through PayPal due to international account regulations.', 'give' );
593
594
			break;
595
596
		case 'multi-currency' :
597
598
			$note = esc_html__( 'Payment received in non-shop currency and must be accepted manually through PayPal.', 'give' );
599
600
			break;
601
602
		case 'paymentreview' :
603
		case 'regulatory_review' :
604
605
			$note = esc_html__( 'Payment is being reviewed by PayPal staff as high-risk or in possible violation of government regulations.', 'give' );
606
607
			break;
608
609
		case 'unilateral' :
610
611
			$note = esc_html__( 'Payment was sent to non-confirmed or non-registered email address.', 'give' );
612
613
			break;
614
615
		case 'upgrade' :
616
617
			$note = esc_html__( 'PayPal account must be upgraded before this payment can be accepted.', 'give' );
618
619
			break;
620
621
		case 'verify' :
622
623
			$note = esc_html__( 'PayPal account is not verified. Verify account in order to accept this donation.', 'give' );
624
625
			break;
626
627
		case 'other' :
628
629
			$note = esc_html__( 'Payment is pending for unknown reasons. Contact PayPal support for assistance.', 'give' );
630
631
			break;
632
633
	}
634
635
	return $note;
636
637
}
638
639
/**
640
 * Build paypal url
641
 *
642
 * @param int   $payment_id   Payment ID
643
 * @param array $payment_data Array of payment data.
644
 *
645
 * @return mixed|string
646
 */
647
function give_build_paypal_url( $payment_id, $payment_data ) {
648
	// Only send to PayPal if the pending payment is created successfully.
649
	$listener_url = add_query_arg( 'give-listener', 'IPN', home_url( 'index.php' ) );
650
651
	// Get the success url.
652
	$return_url = add_query_arg( array(
653
		'payment-confirmation' => 'paypal',
654
		'payment-id'           => $payment_id,
655
656
	), get_permalink( give_get_option( 'success_page' ) ) );
657
658
	// Get the PayPal redirect uri.
659
	$paypal_redirect = trailingslashit( give_get_paypal_redirect() ) . '?';
660
661
	// Item name.
662
	$item_name = give_build_paypal_item_title( $payment_data );
663
664
	// Setup PayPal API params.
665
	$paypal_args = array(
666
		'business'      => give_get_option( 'paypal_email', false ),
667
		'first_name'    => $payment_data['user_info']['first_name'],
668
		'last_name'     => $payment_data['user_info']['last_name'],
669
		'email'         => $payment_data['user_email'],
670
		'invoice'       => $payment_data['purchase_key'],
671
		'amount'        => $payment_data['price'],
672
		'item_name'     => stripslashes( $item_name ),
673
		'no_shipping'   => '1',
674
		'shipping'      => '0',
675
		'no_note'       => '1',
676
		'currency_code' => give_get_currency(),
677
		'charset'       => get_bloginfo( 'charset' ),
678
		'custom'        => $payment_id,
679
		'rm'            => '2',
680
		'return'        => $return_url,
681
		'cancel_return' => give_get_failed_transaction_uri( '?payment-id=' . $payment_id ),
682
		'notify_url'    => $listener_url,
683
		'page_style'    => give_get_paypal_page_style(),
684
		'cbt'           => get_bloginfo( 'name' ),
685
		'bn'            => 'givewp_SP',
686
	);
687
688
	// Add user address if present.
689
	if ( ! empty( $payment_data['user_info']['address'] ) ) {
690
		$default_address = array(
691
			'line1'   => '',
692
			'line2'   => '',
693
			'city'    => '',
694
			'state'   => '',
695
			'country' => '',
696
		);
697
698
		$address = wp_parse_args( $payment_data['user_info']['address'], $default_address );
699
700
		$paypal_args['address1'] = $address['line1'];
701
		$paypal_args['address2'] = $address['line2'];
702
		$paypal_args['city']     = $address['city'];
703
		$paypal_args['state']    = $address['state'];
704
		$paypal_args['country']  = $address['country'];
705
	}
706
707
	// Donations or regular transactions?
708
	$paypal_args['cmd'] = give_get_paypal_button_type();
709
710
	/**
711
	 * Filter the paypal redirect args.
712
	 *
713
	 * @since 1.8
714
	 *
715
	 * @param array $paypal_args
716
	 * @param array $payment_data
717
	 */
718
	$paypal_args = apply_filters( 'give_paypal_redirect_args', $paypal_args, $payment_data );
719
720
	// Build query.
721
	$paypal_redirect .= http_build_query( $paypal_args );
722
723
	// Fix for some sites that encode the entities.
724
	$paypal_redirect = str_replace( '&amp;', '&', $paypal_redirect );
725
726
	return $paypal_redirect;
727
}
728
729
730
/**
731
 * Get paypal button type.
732
 *
733
 * @since 1.8
734
 * @return string
735
 */
736
function give_get_paypal_button_type() {
737
	// paypal_button_type can be donation or standard.
738
	$paypal_button_type = '_donations';
739
	if ( give_get_option( 'paypal_button_type' ) === 'standard' ) {
740
		$paypal_button_type = '_xclick';
741
	}
742
743
	return $paypal_button_type;
744
}
745
746
747
/**
748
 * Build item title for paypal.
749
 *
750
 * @since 1.8
751
 * @param $payment_data
752
 *
753
 * @return string
754
 */
755
function give_build_paypal_item_title( $payment_data ) {
756
	$form_id   = intval( $payment_data['post_data']['give-form-id'] );
757
	$item_name = $payment_data['post_data']['give-form-title'];
758
759
	// Verify has variable prices.
760
	if ( give_has_variable_prices( $form_id ) && isset( $payment_data['post_data']['give-price-id'] ) ) {
761
762
		$item_price_level_text = give_get_price_option_name( $form_id, $payment_data['post_data']['give-price-id'] );
763
		$price_level_amount    = give_get_price_option_amount( $form_id, $payment_data['post_data']['give-price-id'] );
764
765
		// Donation given doesn't match selected level (must be a custom amount).
766
		if ( $price_level_amount != give_sanitize_amount( $payment_data['price'] ) ) {
767
			$custom_amount_text = get_post_meta( $form_id, '_give_custom_amount_text', true );
768
			// user custom amount text if any, fallback to default if not.
769
			$item_name .= ' - ' . give_check_variable( $custom_amount_text, 'empty', esc_html__( 'Custom Amount', 'give' ) );
770
771
		} //Is there any donation level text?
772
		elseif ( ! empty( $item_price_level_text ) ) {
773
			$item_name .= ' - ' . $item_price_level_text;
774
		}
775
776
	} //Single donation: Custom Amount.
777
	elseif ( give_get_form_price( $form_id ) !== give_sanitize_amount( $payment_data['price'] ) ) {
778
		$custom_amount_text = get_post_meta( $form_id, '_give_custom_amount_text', true );
779
		// user custom amount text if any, fallback to default if not.
780
		$item_name .= ' - ' . give_check_variable( $custom_amount_text, 'empty', esc_html__( 'Custom Amount', 'give' ) );
781
	}
782
783
	return $item_name;
784
}