Completed
Push — master ( 4b5d49...174751 )
by Devin
18:20
created

paypal-standard.php ➔ give_paypal_standard_billing_fields()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

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