Passed
Push — master ( 7aed77...3e5483 )
by Brian
218:38 queued 121:18
created

wpinv_process_paypal_payment()   C

Complexity

Conditions 11
Paths 18

Size

Total Lines 105
Code Lines 66

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 66
nc 18
nop 1
dl 0
loc 105
rs 6.5951
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
2
// Exit if accessed directly
3
if ( ! defined( 'ABSPATH' ) ) exit;
4
5
add_action( 'wpinv_paypal_cc_form', '__return_false' );
6
add_filter( 'wpinv_paypal_support_subscription', '__return_true' );
7
8
function wpinv_process_paypal_payment( $purchase_data ) {
9
    if( ! wp_verify_nonce( $purchase_data['gateway_nonce'], 'wpi-gateway' ) ) {
10
        wp_die( __( 'Nonce verification has failed', 'invoicing' ), __( 'Error', 'invoicing' ), array( 'response' => 403 ) );
11
    }
12
13
    // Collect payment data
14
    $payment_data = array(
15
        'price'         => $purchase_data['price'],
16
        'date'          => $purchase_data['date'],
17
        'user_email'    => $purchase_data['user_email'],
18
        'invoice_key'   => $purchase_data['invoice_key'],
19
        'currency'      => wpinv_get_currency(),
20
        'items'         => $purchase_data['items'],
21
        'user_info'     => $purchase_data['user_info'],
22
        'cart_details'  => $purchase_data['cart_details'],
23
        'gateway'       => 'paypal',
24
        'status'        => 'wpi-pending'
25
    );
26
27
    // Record the pending payment
28
    $invoice = wpinv_get_invoice( $purchase_data['invoice_id'] );
29
30
    // Check payment
31
    if ( ! $invoice ) {
32
        // Record the error
33
        wpinv_record_gateway_error( __( 'Payment Error', 'invoicing' ), sprintf( __( 'Payment creation failed before sending buyer to PayPal. Payment data: %s', 'invoicing' ), json_encode( $payment_data ) ), $invoice );
34
        // Problems? send back
35
        wpinv_send_back_to_checkout( '?payment-mode=' . $purchase_data['post_data']['wpi-gateway'] );
36
    } else {
37
        // Only send to PayPal if the pending payment is created successfully
38
        $listener_url = wpinv_get_ipn_url( 'paypal' );
39
40
        // Get the success url
41
        $return_url = add_query_arg( array(
42
                'payment-confirm' => 'paypal',
43
                'invoice-id'      => $invoice->ID,
44
                'utm_nooverride'  => 1
45
            ), get_permalink( wpinv_get_option( 'success_page', false ) ) );
0 ignored issues
show
Bug introduced by
It seems like wpinv_get_option('success_page', false) can also be of type false; however, parameter $post of get_permalink() does only seem to accept WP_Post|integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

45
            ), get_permalink( /** @scrutinizer ignore-type */ wpinv_get_option( 'success_page', false ) ) );
Loading history...
46
47
        // Get the PayPal redirect uri
48
        $paypal_redirect = wpinv_get_paypal_redirect();
49
50
        // Setup PayPal arguments
51
        $paypal_args = array(
52
            'cmd'           => '_cart',
53
            'upload'        => '1',
54
            'business'      => wpinv_get_option( 'paypal_email', false ),
55
            'email'         => $invoice->get_email(),
56
            'first_name'    => $invoice->get_first_name(),
57
            'last_name'     => $invoice->get_last_name(),
58
            'invoice'       => $invoice->get_key(),
59
            'no_shipping'   => '1',
60
            'shipping'      => '0',
61
            'no_note'       => '1',
62
            'currency_code' => $invoice->get_currency(),
63
            'charset'       => get_bloginfo( 'charset' ),
64
            'custom'        => $invoice->ID,
65
            'rm'            => is_ssl() ? 2 : 1,
66
            'return'        => esc_url_raw( $return_url ),
67
            'cancel_return' => $invoice->get_checkout_payment_url(),
68
            'notify_url'    => $listener_url,
69
            'cbt'           => get_bloginfo( 'name' ),
70
            'bn'            => 'WPInvoicing_SP',
71
        );
72
73
        // Add cart items
74
        $i = 1;
75
        if( is_array( $purchase_data['cart_details'] ) && ! empty( $purchase_data['cart_details'] ) ) {
76
            foreach ( $purchase_data['cart_details'] as $item ) {
77
                $item['quantity'] = $item['quantity'] > 0 ? $item['quantity'] : 1;
78
                $item_amount = wpinv_sanitize_amount( $item['subtotal'] / $item['quantity'], 2 );
79
80
                if ( $item_amount <= 0 ) {
81
                    $item_amount = 0;
82
                }
83
84
                $paypal_args['item_number_' . $i ]      = $item['id'];
85
                $paypal_args['item_name_' . $i ]        = stripslashes_deep( html_entity_decode( wpinv_get_cart_item_name( $item ), ENT_COMPAT, 'UTF-8' ) );
86
                $paypal_args['quantity_' . $i ]         = $item['quantity'];
87
                $paypal_args['amount_' . $i ]           = $item_amount;
88
                $paypal_args['discount_amount_' . $i ]  = wpinv_sanitize_amount( $item['discount'], 2 );
89
90
                $i++;
91
            }
92
        }
93
94
        // Add taxes to the cart
95
        if ( wpinv_use_taxes() && $invoice->is_taxable() ) {
96
            $paypal_args['tax_cart'] = wpinv_sanitize_amount( (float) $invoice->get_tax(), 2 );
97
        }
98
99
        $paypal_args = apply_filters( 'wpinv_paypal_args', $paypal_args, $purchase_data, $invoice );
100
101
        // Build query
102
        $paypal_redirect .= http_build_query( $paypal_args );
103
104
        // Fix for some sites that encode the entities
105
        $paypal_redirect = str_replace( '&amp;', '&', $paypal_redirect );
106
107
        // Get rid of cart contents
108
        wpinv_empty_cart();
109
        
110
        // Redirect to PayPal
111
        wp_redirect( $paypal_redirect );
112
        exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
113
    }
114
}
115
add_action( 'wpinv_gateway_paypal', 'wpinv_process_paypal_payment' );
116
117
function wpinv_get_paypal_recurring_args( $paypal_args, $purchase_data, $invoice ) {
118
    if ( $invoice->is_recurring() && $subscription = wpinv_get_subscription( $invoice->ID ) ) {
119
120
        $period             = strtoupper( substr( $subscription->period, 0, 1) );
121
        $interval           = $subscription->frequency;
0 ignored issues
show
Bug Best Practice introduced by
The property frequency does not exist on WPInv_Subscription. Since you implemented __get, consider adding a @property annotation.
Loading history...
122
        $bill_times         = (int) $subscription->bill_times;
123
        
124
        $initial_amount     = wpinv_sanitize_amount( $invoice->get_total(), 2 );
125
        $recurring_amount   = wpinv_sanitize_amount( $invoice->get_recurring_details( 'total' ), 2 );
126
        
127
        $paypal_args['cmd'] = '_xclick-subscriptions';
128
        $paypal_args['sra'] = '1';
129
        $paypal_args['src'] = '1';
130
        
131
        // Set item description
132
        $item_name                  = wpinv_get_cart_item_name( array( 'id' => $subscription->product_id ) );
133
        $paypal_args['item_name']   = stripslashes_deep( html_entity_decode( $item_name, ENT_COMPAT, 'UTF-8' ) );
134
        $paypal_args['item_number'] = $subscription->product_id;
135
        $item   = new WPInv_Item( $subscription->period );
136
137
        if ( $invoice->is_free_trial() && $item->has_free_trial() ) {
138
            $paypal_args['a1']  = $initial_amount;
139
            $paypal_args['p1']  = $item->get_trial_interval();
140
            $paypal_args['t1']  = $item->get_trial_period();
141
            
142
            // Set the recurring amount
143
            $paypal_args['a3']  = $recurring_amount;
144
        } else if ( $initial_amount != $recurring_amount && $bill_times != 1 ) {
145
            $paypal_args['a1']  = $initial_amount;
146
            $paypal_args['p1']  = $interval;
147
            $paypal_args['t1']  = $period;
148
149
            // Set the recurring amount
150
            $paypal_args['a3']  = $recurring_amount;
151
            
152
            if ( $bill_times > 1 ) {
153
                $bill_times--;
154
            }
155
        } else {
156
            $paypal_args['a3']  = $initial_amount;
157
        }
158
        
159
        $paypal_args['p3']  = $interval;
160
        $paypal_args['t3']  = $period;
161
        
162
        if ( $bill_times > 1 ) {
163
            // Make sure it's not over the max of 52
164
            $paypal_args['srt'] = ( $bill_times <= 52 ? absint( $bill_times ) : 52 );
165
        }
166
                
167
        // Remove cart items
168
        $i = 1;
169
        if( is_array( $purchase_data['cart_details'] ) && ! empty( $purchase_data['cart_details'] ) ) {
170
            foreach ( $purchase_data['cart_details'] as $item ) {                
171
                if ( isset( $paypal_args['item_number_' . $i] ) ) {
172
                    unset( $paypal_args['item_number_' . $i] );
173
                }
174
                if ( isset( $paypal_args['item_name_' . $i] ) ) {
175
                    unset( $paypal_args['item_name_' . $i] );
176
                }
177
                if ( isset( $paypal_args['quantity_' . $i] ) ) {
178
                    unset( $paypal_args['quantity_' . $i] );
179
                }
180
                if ( isset( $paypal_args['amount_' . $i] ) ) {
181
                    unset( $paypal_args['amount_' . $i] );
182
                }
183
                if ( isset( $paypal_args['discount_amount_' . $i] ) ) {
184
                    unset( $paypal_args['discount_amount_' . $i] );
185
                }
186
187
                $i++;
188
            }
189
        }
190
        
191
        if ( isset( $paypal_args['tax_cart'] ) ) {
192
            unset( $paypal_args['tax_cart'] );
193
        }
194
                
195
        if ( isset( $paypal_args['upload'] ) ) {
196
            unset( $paypal_args['upload'] );
197
        }
198
        
199
        $paypal_args = apply_filters( 'wpinv_paypal_recurring_args', $paypal_args, $purchase_data, $invoice );
200
    }
201
    
202
    return $paypal_args;
203
}
204
add_filter( 'wpinv_paypal_args', 'wpinv_get_paypal_recurring_args', 10, 3 );
205
206
function wpinv_process_paypal_ipn() {
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 = wpinv_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
	wp_parse_str( $encoded_data, $encoded_data_array );
0 ignored issues
show
Security Variable Injection introduced by
$encoded_data can contain request data and is used in variable name context(s) leading to a potential security vulnerability.

1 path for user data to reach this point

  1. Read from $_POST, and $_POST is assigned to $value
    in includes/gateways/paypal.php on line 239
  2. Data is passed through urlencode(), and $arg_separator . $key.'=' . urlencode($value) is assigned to $encoded_data
    in includes/gateways/paypal.php on line 241

Used in variable context

  1. wp_parse_str() is called
    in includes/gateways/paypal.php on line 262
  2. Enters via parameter $string
    in wordpress/wp-includes/formatting.php on line 4865
  3. parse_str() is called
    in wordpress/wp-includes/formatting.php on line 4866

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
248
249
	foreach ( $encoded_data_array as $key => $value ) {
250
		if ( false !== strpos( $key, 'amp;' ) ) {
251
			$new_key = str_replace( '&amp;', '&', $key );
252
			$new_key = str_replace( 'amp;', '&' , $new_key );
253
254
			unset( $encoded_data_array[ $key ] );
255
			$encoded_data_array[ $new_key ] = $value;
256
		}
257
	}
258
259
	// Get the PayPal redirect uri
260
	$paypal_redirect = wpinv_get_paypal_redirect( true );
0 ignored issues
show
Unused Code introduced by
The assignment to $paypal_redirect is dead and can be removed.
Loading history...
261
262
	if ( !wpinv_get_option( 'disable_paypal_verification', false ) ) {
263
		// Validate the IPN
264
265
		$remote_post_vars      = array(
266
			'method'           => 'POST',
267
			'timeout'          => 45,
268
			'redirection'      => 5,
269
			'httpversion'      => '1.1',
270
			'blocking'         => true,
271
			'headers'          => array(
272
				'host'         => 'www.paypal.com',
273
				'connection'   => 'close',
274
				'content-type' => 'application/x-www-form-urlencoded',
275
				'post'         => '/cgi-bin/webscr HTTP/1.1',
276
277
			),
278
			'sslverify'        => false,
279
			'body'             => $encoded_data_array
280
		);
281
282
		// Get response
283
		$api_response = wp_remote_post( wpinv_get_paypal_redirect(), $remote_post_vars );
284
285
		if ( is_wp_error( $api_response ) ) {
286
			wpinv_record_gateway_error( __( 'IPN Error', 'invoicing' ), sprintf( __( 'Invalid IPN verification response. IPN data: %s', 'invoicing' ), json_encode( $api_response ) ) );
287
			return; // Something went wrong
288
		}
289
290
		if ( $api_response['body'] !== 'VERIFIED' && wpinv_get_option( 'disable_paypal_verification', false ) ) {
291
			wpinv_record_gateway_error( __( 'IPN Error', 'invoicing' ), sprintf( __( 'Invalid IPN verification response. IPN data: %s', 'invoicing' ), json_encode( $api_response ) ) );
292
			return; // Response not okay
293
		}
294
	}
295
296
	// Check if $post_data_array has been populated
297
	if ( !is_array( $encoded_data_array ) && !empty( $encoded_data_array ) )
298
		return;
299
300
	$defaults = array(
301
		'txn_type'       => '',
302
		'payment_status' => ''
303
	);
304
305
	$encoded_data_array = wp_parse_args( $encoded_data_array, $defaults );
306
307
	$invoice_id = isset( $encoded_data_array['custom'] ) ? absint( $encoded_data_array['custom'] ) : 0;
308
    
309
	wpinv_error_log( $encoded_data_array['txn_type'], 'PayPal txn_type', __FILE__, __LINE__ );
310
	wpinv_error_log( $encoded_data_array, 'PayPal IPN response', __FILE__, __LINE__ );
311
312
	if ( has_action( 'wpinv_paypal_' . $encoded_data_array['txn_type'] ) ) {
313
		// Allow PayPal IPN types to be processed separately
314
		do_action( 'wpinv_paypal_' . $encoded_data_array['txn_type'], $encoded_data_array, $invoice_id );
315
	} else {
316
		// Fallback to web accept just in case the txn_type isn't present
317
		do_action( 'wpinv_paypal_web_accept', $encoded_data_array, $invoice_id );
318
	}
319
	exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
320
}
321
add_action( 'wpinv_verify_paypal_ipn', 'wpinv_process_paypal_ipn' );
322
323
function wpinv_process_paypal_web_accept_and_cart( $data, $invoice_id ) {
324
	if ( $data['txn_type'] != 'web_accept' && $data['txn_type'] != 'cart' && $data['payment_status'] != 'Refunded' ) {
325
		return;
326
	}
327
328
	if( empty( $invoice_id ) ) {
329
		return;
330
	}
331
332
	// Collect payment details
333
	$purchase_key   = isset( $data['invoice'] ) ? $data['invoice'] : $data['item_number'];
334
	$paypal_amount  = $data['mc_gross'];
335
	$payment_status = strtolower( $data['payment_status'] );
336
	$currency_code  = strtolower( $data['mc_currency'] );
337
	$business_email = isset( $data['business'] ) && is_email( $data['business'] ) ? trim( $data['business'] ) : trim( $data['receiver_email'] );
338
	$payment_meta   = wpinv_get_invoice_meta( $invoice_id );
339
340
	if ( wpinv_get_payment_gateway( $invoice_id ) != 'paypal' ) {
341
		return; // this isn't a PayPal standard IPN
342
	}
343
344
	// Verify payment recipient
345
	if ( strcasecmp( $business_email, trim( wpinv_get_option( 'paypal_email', false ) ) ) != 0 ) {
0 ignored issues
show
Bug introduced by
It seems like wpinv_get_option('paypal_email', false) can also be of type false; however, parameter $str of trim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

345
	if ( strcasecmp( $business_email, trim( /** @scrutinizer ignore-type */ wpinv_get_option( 'paypal_email', false ) ) ) != 0 ) {
Loading history...
346
		wpinv_record_gateway_error( __( 'IPN Error', 'invoicing' ), sprintf( __( 'Invalid business email in IPN response. IPN data: %s', 'invoicing' ), json_encode( $data ) ), $invoice_id );
347
		wpinv_update_payment_status( $invoice_id, 'wpi-failed' );
348
		wpinv_insert_payment_note( $invoice_id, __( 'Payment failed due to invalid PayPal business email.', 'invoicing' ), '', '', true );
349
		return;
350
	}
351
352
	// Verify payment currency
353
	if ( $currency_code != strtolower( $payment_meta['currency'] ) ) {
354
		wpinv_record_gateway_error( __( 'IPN Error', 'invoicing' ), sprintf( __( 'Invalid currency in IPN response. IPN data: %s', 'invoicing' ), json_encode( $data ) ), $invoice_id );
355
		wpinv_update_payment_status( $invoice_id, 'wpi-failed' );
356
		wpinv_insert_payment_note( $invoice_id, __( 'Payment failed due to invalid currency in PayPal IPN.', 'invoicing' ), '', '', true );
357
		return;
358
	}
359
360
	if ( !wpinv_get_payment_user_email( $invoice_id ) ) {
361
		// This runs when a Buy Now purchase was made. It bypasses checkout so no personal info is collected until PayPal
362
		// No email associated with purchase, so store from PayPal
363
		wpinv_update_invoice_meta( $invoice_id, '_wpinv_email', $data['payer_email'] );
364
365
		// Setup and store the customer's details
366
		$user_info = array(
367
			'user_id'    => '-1',
368
			'email'      => sanitize_text_field( $data['payer_email'] ),
369
			'first_name' => sanitize_text_field( $data['first_name'] ),
370
			'last_name'  => sanitize_text_field( $data['last_name'] ),
371
			'discount'   => '',
372
		);
373
		$user_info['address'] = ! empty( $data['address_street']       ) ? sanitize_text_field( $data['address_street'] )       : false;
374
		$user_info['city']    = ! empty( $data['address_city']         ) ? sanitize_text_field( $data['address_city'] )         : false;
375
		$user_info['state']   = ! empty( $data['address_state']        ) ? sanitize_text_field( $data['address_state'] )        : false;
376
		$user_info['country'] = ! empty( $data['address_country_code'] ) ? sanitize_text_field( $data['address_country_code'] ) : false;
377
		$user_info['zip']     = ! empty( $data['address_zip']          ) ? sanitize_text_field( $data['address_zip'] )          : false;
378
379
		$payment_meta['user_info'] = $user_info;
380
		wpinv_update_invoice_meta( $invoice_id, '_wpinv_payment_meta', $payment_meta );
381
	}
382
383
	if ( $payment_status == 'refunded' || $payment_status == 'reversed' ) {
384
		// Process a refund
385
		wpinv_process_paypal_refund( $data, $invoice_id );
386
	} else {
387
		if ( get_post_status( $invoice_id ) == 'publish' ) {
388
			return; // Only paid payments once
389
		}
390
391
		// Retrieve the total purchase amount (before PayPal)
392
		$payment_amount = wpinv_payment_total( $invoice_id );
393
394
		if ( number_format( (float) $paypal_amount, 2 ) < number_format( (float) $payment_amount, 2 ) ) {
395
			// The prices don't match
396
			wpinv_record_gateway_error( __( 'IPN Error', 'invoicing' ), sprintf( __( 'Invalid payment amount in IPN response. IPN data: %s', 'invoicing' ), json_encode( $data ) ), $invoice_id );
397
			wpinv_update_payment_status( $invoice_id, 'wpi-failed' );
398
			wpinv_insert_payment_note( $invoice_id, __( 'Payment failed due to invalid amount in PayPal IPN.', 'invoicing' ), '', '', true );
399
			return;
400
		}
401
		if ( $purchase_key != wpinv_get_payment_key( $invoice_id ) ) {
402
			// Purchase keys don't match
403
			wpinv_record_gateway_error( __( 'IPN Error', 'invoicing' ), sprintf( __( 'Invalid purchase key in IPN response. IPN data: %s', 'invoicing' ), json_encode( $data ) ), $invoice_id );
404
			wpinv_update_payment_status( $invoice_id, 'wpi-failed' );
405
			wpinv_insert_payment_note( $invoice_id, __( 'Payment failed due to invalid purchase key in PayPal IPN.', 'invoicing' ), '', '', true );
406
			return;
407
		}
408
409
		if ( 'complete' == $payment_status || 'completed' == $payment_status || 'processed' == $payment_status || wpinv_is_test_mode( 'paypal' ) ) {
410
			wpinv_insert_payment_note( $invoice_id, sprintf( __( 'PayPal Transaction ID: %s', 'invoicing' ) , $data['txn_id'] ), '', '', true );
411
			wpinv_set_payment_transaction_id( $invoice_id, $data['txn_id'] );
412
			wpinv_update_payment_status( $invoice_id, 'publish' );
413
		} else if ( 'pending' == $payment_status && isset( $data['pending_reason'] ) ) {
414
			// Look for possible pending reasons, such as an echeck
415
			$note = '';
416
417
			switch( strtolower( $data['pending_reason'] ) ) {
418
				case 'echeck' :
419
					$note = __( 'Payment made via eCheck and will clear automatically in 5-8 days', 'invoicing' );
420
					break;
421
				
422
                case 'address' :
423
					$note = __( 'Payment requires a confirmed customer address and must be accepted manually through PayPal', 'invoicing' );
424
					break;
425
				
426
                case 'intl' :
427
					$note = __( 'Payment must be accepted manually through PayPal due to international account regulations', 'invoicing' );
428
					break;
429
				
430
                case 'multi-currency' :
431
					$note = __( 'Payment received in non-shop currency and must be accepted manually through PayPal', 'invoicing' );
432
					break;
433
				
434
                case 'paymentreview' :
435
                case 'regulatory_review' :
436
					$note = __( 'Payment is being reviewed by PayPal staff as high-risk or in possible violation of government regulations', 'invoicing' );
437
					break;
438
				
439
                case 'unilateral' :
440
					$note = __( 'Payment was sent to non-confirmed or non-registered email address.', 'invoicing' );
441
					break;
442
				
443
                case 'upgrade' :
444
					$note = __( 'PayPal account must be upgraded before this payment can be accepted', 'invoicing' );
445
					break;
446
				
447
                case 'verify' :
448
					$note = __( 'PayPal account is not verified. Verify account in order to accept this payment', 'invoicing' );
449
					break;
450
451
				case 'other' :
452
					$note = __( 'Payment is pending for unknown reasons. Contact PayPal support for assistance', 'invoicing' );
453
					break;
454
			}
455
456
			if ( ! empty( $note ) ) {
457
				wpinv_insert_payment_note( $invoice_id, $note, '', '', true );
458
			}
459
		} else {
460
			wpinv_insert_payment_note( $invoice_id, wp_sprintf( __( 'PayPal IPN has been received with invalid payment status: %s', 'invoicing' ), $payment_status ), '', '', true );
461
		}
462
	}
463
}
464
add_action( 'wpinv_paypal_web_accept', 'wpinv_process_paypal_web_accept_and_cart', 10, 2 );
465
466
// Process PayPal subscription sign ups
467
add_action( 'wpinv_paypal_subscr_signup', 'wpinv_process_paypal_subscr_signup' );
468
469
// Process PayPal subscription payments
470
add_action( 'wpinv_paypal_subscr_payment', 'wpinv_process_paypal_subscr_payment' );
471
472
// Process PayPal subscription cancellations
473
add_action( 'wpinv_paypal_subscr_cancel', 'wpinv_process_paypal_subscr_cancel' );
474
475
// Process PayPal subscription end of term notices
476
add_action( 'wpinv_paypal_subscr_eot', 'wpinv_process_paypal_subscr_eot' );
477
478
// Process PayPal payment failed
479
add_action( 'wpinv_paypal_subscr_failed', 'wpinv_process_paypal_subscr_failed' );
480
481
482
/**
483
 * Process the subscription started IPN.
484
 */
485
function wpinv_process_paypal_subscr_signup( $ipn_data ) {
486
    $parent_invoice_id = absint( $ipn_data['custom'] );
487
    if( empty( $parent_invoice_id ) ) {
488
        return;
489
    }
490
491
    $invoice = wpinv_get_invoice( $parent_invoice_id );
492
    if ( empty( $invoice ) ) {
493
        return;
494
    }
495
496
    if ( $invoice->is_free_trial() && !empty( $ipn_data['invoice'] ) ) {
497
        wpinv_insert_payment_note( $parent_invoice_id, sprintf( __( 'PayPal Invoice ID: %s', 'invoicing' ) , $ipn_data['invoice'] ), '', '', true);
498
        if ( !empty( $ipn_data['txn_id'] ) ) {
499
            wpinv_set_payment_transaction_id( $parent_invoice_id, $ipn_data['txn_id'] );
500
        }
501
    }
502
503
    $subscription = wpinv_get_paypal_subscription( $ipn_data );
504
    if ( false === $subscription ) {
505
        return;
506
    }
507
    
508
    wpinv_update_payment_status( $parent_invoice_id, 'publish' );
509
    sleep(1);
510
    wpinv_insert_payment_note( $parent_invoice_id, sprintf( __( 'PayPal Subscription ID: %s', 'invoicing' ) , $ipn_data['subscr_id'] ), '', '', true );
511
    update_post_meta($parent_invoice_id,'_wpinv_subscr_profile_id', $ipn_data['subscr_id']);
512
513
    $status     = 'trialling' == $subscription->status ? 'trialling' : 'active';
514
    $diff_days  = absint( ( ( strtotime( $subscription->expiration ) - strtotime( $subscription->created ) ) / DAY_IN_SECONDS ) );
515
    $created    = date_i18n( 'Y-m-d H:i:s' );
516
    $expiration = date_i18n( 'Y-m-d 23:59:59', ( strtotime( $created ) + ( $diff_days * DAY_IN_SECONDS ) ) );
517
518
    // Retrieve pending subscription from database and update it's status to active and set proper profile ID
519
    $subscription->update( array( 'profile_id' => $ipn_data['subscr_id'], 'status' => $status, 'created' => $created, 'expiration' => $expiration ) );
520
}
521
522
/**
523
 * Process the subscription payment received IPN.
524
 */
525
function wpinv_process_paypal_subscr_payment( $ipn_data ) {
526
    $parent_invoice_id = absint( $ipn_data['custom'] );
527
528
    $parent_invoice = wpinv_get_invoice( $parent_invoice_id );
529
    if ( empty( $parent_invoice ) ) {
530
        return;
531
    }
532
533
    $subscription = wpinv_get_paypal_subscription( $ipn_data );
534
    if ( false === $subscription ) {
535
        return;
536
    }
537
538
    $transaction_id = wpinv_get_payment_transaction_id( $parent_invoice_id );
539
    $times_billed   = $subscription->get_times_billed();
540
    $signup_date    = strtotime( $subscription->created );
541
    $today          = date( 'Ynd', $signup_date ) == date( 'Ynd', strtotime( $ipn_data['payment_date'] ) );
542
543
    // Look to see if payment is same day as signup and we have set the transaction ID on the parent payment yet.
544
    if ( (empty($times_billed) || $today) && ( !$transaction_id || $transaction_id == $parent_invoice_id ) ) {
545
        wpinv_update_payment_status( $parent_invoice_id, 'publish' );
546
        sleep(1);
547
        
548
        // This is the very first payment
549
        wpinv_set_payment_transaction_id( $parent_invoice_id, $ipn_data['txn_id'] );
550
        wpinv_insert_payment_note( $parent_invoice_id, sprintf( __( 'PayPal Transaction ID: %s', 'invoicing' ) , $ipn_data['txn_id'] ), '', '', true );
551
        return;
552
    }
553
554
    if ( wpinv_get_id_by_transaction_id( $ipn_data['txn_id'] ) ) {
555
        return; // Payment already recorded
556
    }
557
558
    $currency_code = strtolower( $ipn_data['mc_currency'] );
559
560
    // verify details
561
    if ( $currency_code != strtolower( wpinv_get_currency() ) ) {
562
        // the currency code is invalid
563
        wpinv_record_gateway_error( __( 'IPN Error', 'invoicing' ), sprintf( __( 'Invalid currency in IPN response. IPN data: ', 'invoicing' ), json_encode( $ipn_data ) ) );
564
        return;
565
    }
566
567
    $args = array(
568
        'amount'         => $ipn_data['mc_gross'],
569
        'transaction_id' => $ipn_data['txn_id'],
570
        'gateway'        => 'paypal'
571
    );
572
    
573
    $invoice_id = $subscription->add_payment( $args );
574
575
    if ( $invoice_id > 0 ) {
576
        wpinv_insert_payment_note( $invoice_id, wp_sprintf( __( 'PayPal Transaction ID: %s', 'invoicing' ) , $ipn_data['txn_id'] ), '', '', true );
577
        wpinv_insert_payment_note( $invoice_id, wp_sprintf( __( 'PayPal Subscription ID: %s', 'invoicing' ) , $ipn_data['subscr_id'] ), '', '', true );
578
579
        $subscription->renew();
580
    }
581
}
582
583
/**
584
 * Process the subscription canceled IPN.
585
 */
586
function wpinv_process_paypal_subscr_cancel( $ipn_data ) {
587
    $subscription = wpinv_get_paypal_subscription( $ipn_data );
588
589
    if( false === $subscription ) {
590
        return;
591
    }
592
593
    $subscription->cancel();
594
}
595
596
/**
597
 * Process the subscription expired IPN.
598
 */
599
function wpinv_process_paypal_subscr_eot( $ipn_data ) {
600
    $subscription = wpinv_get_paypal_subscription( $ipn_data );
601
602
    if( false === $subscription ) {
603
        return;
604
    }
605
606
    $subscription->complete();
607
}
608
609
/**
610
 * Process the subscription payment failed IPN.
611
 */
612
function wpinv_process_paypal_subscr_failed( $ipn_data ) {
613
    $subscription = wpinv_get_paypal_subscription( $ipn_data );
614
615
    if( false === $subscription ) {
616
        return;
617
    }
618
619
    $subscription->failing();
620
621
    do_action( 'wpinv_recurring_payment_failed', $subscription );
622
}
623
624
/**
625
 * Retrieve the subscription this IPN notice is for.
626
 */
627
function wpinv_get_paypal_subscription( $ipn_data = array() ) {
628
    $parent_invoice_id = absint( $ipn_data['custom'] );
629
630
    if( empty( $parent_invoice_id ) ) {
631
        return false;
632
    }
633
634
    $invoice = wpinv_get_invoice( $parent_invoice_id );
635
    if ( empty( $invoice ) ) {
636
        return false;
637
    }
638
639
    $subscription = new WPInv_Subscription( $ipn_data['subscr_id'], true );
640
641
    if ( ! ( ! empty( $subscription ) && $subscription->id > 0 ) ) {
642
        $subscription = wpinv_get_subscription( $parent_invoice_id );
643
644
        if ( ! empty( $subscription ) && $subscription->id > 0 ) {
645
            $subscription->update( array( 'profile_id' => sanitize_text_field( $ipn_data['subscr_id'] ) ) );
646
        } else {
647
            return false;
648
        }
649
    }
650
651
    return $subscription;
652
653
}
654
655
function wpinv_process_paypal_refund( $data, $invoice_id = 0 ) {
656
	// Collect payment details
657
658
	if( empty( $invoice_id ) ) {
659
		return;
660
	}
661
662
	if ( get_post_status( $invoice_id ) == 'wpi-refunded' ) {
663
		return; // Only refund payments once
664
	}
665
666
	$payment_amount = wpinv_payment_total( $invoice_id );
667
	$refund_amount  = $data['mc_gross'] * -1;
668
669
	do_action( 'wpinv_paypal_refund_request', $data, $invoice_id );
670
671
	if ( number_format( (float) $refund_amount, 2 ) < number_format( (float) $payment_amount, 2 ) ) {
672
		wpinv_insert_payment_note( $invoice_id, wp_sprintf( __( 'PayPal partial refund of %s processed for transaction #%s for reason: %s', 'invoicing' ), (float)$refund_amount . ' '. $data['mc_currency'], $data['parent_txn_id'], $data['reason_code'] ), '', '', true );
673
674
		do_action( 'wpinv_paypal_invoice_partially_refunded', $data, $invoice_id, $refund_amount );
675
676
		return; // This is a partial refund
677
	}
678
679
	wpinv_insert_payment_note( $invoice_id, sprintf( __( 'PayPal Payment #%s Refunded for reason: %s', 'invoicing' ), $data['parent_txn_id'], $data['reason_code'] ), '', '', true );
680
	wpinv_insert_payment_note( $invoice_id, sprintf( __( 'PayPal Refund Transaction ID: %s', 'invoicing' ), $data['txn_id'] ), '', '', true );
681
	wpinv_update_payment_status( $invoice_id, 'wpi-refunded' );
682
683
	do_action( 'wpinv_paypal_invoice_fully_refunded', $data, $invoice_id );
684
}
685
686
function wpinv_get_paypal_redirect( $ssl_check = false ) {
0 ignored issues
show
Unused Code introduced by
The parameter $ssl_check is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

686
function wpinv_get_paypal_redirect( /** @scrutinizer ignore-unused */ $ssl_check = false ) {

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

Loading history...
687
    return apply_filters( 'wpinv_paypal_uri', wpinv_is_test_mode( 'paypal' ) ? 'https://www.sandbox.paypal.com/cgi-bin/webscr?test_ipn=1&' : 'https://www.paypal.com/cgi-bin/webscr?' );
688
}
689
690
function wpinv_paypal_success_page_content( $content ) {
691
    global $wpi_invoice;
692
    
693
    $session = wpinv_get_checkout_session();
694
695
    if ( empty( $_GET['invoice-id'] ) && empty( $session['invoice_key'] )  ) {
696
        return $content;
697
    }
698
699
    $invoice_id = ! empty( $_GET['invoice-id'] ) ? absint( $_GET['invoice-id'] ) : wpinv_get_invoice_id_by_key( $session['invoice_key'] );
700
701
    if ( empty(  $invoice_id ) ) {
702
        return $content;
703
    }
704
705
    $wpi_invoice = wpinv_get_invoice( $invoice_id );
706
    
707
    if ( ! empty( $wpi_invoice ) && 'wpi-pending' == $wpi_invoice->status ) {
708
        // Payment is still pending so show processing indicator to fix the Race Condition, issue #
709
        ob_start();
710
        wpinv_get_template_part( 'wpinv-payment-processing' );
711
        $content = ob_get_clean();
712
    }
713
714
    return $content;
715
}
716
add_filter( 'wpinv_payment_confirm_paypal', 'wpinv_paypal_success_page_content' );
717
718
function wpinv_paypal_get_transaction_id( $invoice_id ) {
719
    $transaction_id = '';
720
    $notes = wpinv_get_invoice_notes( $invoice_id );
721
722
    foreach ( $notes as $note ) {
723
        if ( preg_match( '/^PayPal Transaction ID: ([^\s]+)/', $note->comment_content, $match ) ) {
724
            $transaction_id = $match[1];
725
            continue;
726
        }
727
    }
728
729
    return apply_filters( 'wpinv_paypal_set_transaction_id', $transaction_id, $invoice_id );
730
}
731
add_filter( 'wpinv_payment_get_transaction_id-paypal', 'wpinv_paypal_get_transaction_id', 10, 1 );
732
733
function wpinv_paypal_link_transaction_id( $transaction_id, $invoice_id, $invoice ) {
734
    if ( $transaction_id == $invoice_id ) {
735
        $transaction_link = $transaction_id;
736
    } else {
737
        if ( ! empty( $invoice ) && ! empty( $invoice->mode ) ) {
738
            $mode = $invoice->mode;
739
        } else {
740
            $mode = wpinv_is_test_mode( 'paypal' ) ? 'test' : 'live';
741
        }
742
743
        $sandbox = $mode == 'test' ? '.sandbox' : '';
744
        $transaction_url = 'https://www' . $sandbox . '.paypal.com/webscr?cmd=_history-details-from-hub&id=' . $transaction_id;
745
746
        $transaction_link = '<a href="' . esc_url( $transaction_url ) . '" target="_blank">' . $transaction_id . '</a>';
747
    }
748
749
    return apply_filters( 'wpinv_paypal_link_payment_details_transaction_id', $transaction_link, $transaction_id, $invoice );
750
}
751
add_filter( 'wpinv_payment_details_transaction_id-paypal', 'wpinv_paypal_link_transaction_id', 10, 3 );
752
753
function wpinv_paypal_profile_id_link( $profile_id, $subscription ) {
754
    $link = $profile_id;
755
756
    if ( ! empty( $profile_id ) && ! empty( $subscription ) && ( $invoice_id = $subscription->get_original_payment_id() ) ) {
757
        $invoice = wpinv_get_invoice( $invoice_id );
758
759
        if ( ! empty( $invoice ) && ! empty( $invoice->mode ) ) {
760
            $mode = $invoice->mode;
761
        } else {
762
            $mode = wpinv_is_test_mode( 'paypal' ) ? 'test' : 'live';
763
        }
764
765
        $sandbox = $mode == 'test' ? '.sandbox' : '';
766
        $url = 'https://www' . $sandbox . '.paypal.com/cgi-bin/webscr?cmd=_profile-recurring-payments&encrypted_profile_id=' . $profile_id;
767
768
        $link = '<a href="' . esc_url( $url ) . '" target="_blank">' . $profile_id . '</a>';
769
    }
770
    
771
    return apply_filters( 'wpinv_paypal_profile_id_link', $link, $profile_id, $subscription );
772
}
773
add_filter( 'wpinv_subscription_profile_link_paypal', 'wpinv_paypal_profile_id_link', 10, 2 );
774
775
function wpinv_paypal_transaction_id_link( $transaction_id, $subscription ) {
776
    if ( ! empty( $transaction_id ) && ! empty( $subscription ) && ( $invoice_id = $subscription->get_original_payment_id() ) ) {
777
        $invoice = wpinv_get_invoice( $invoice_id );
778
779
        if ( ! empty( $invoice ) ) {
780
            return wpinv_paypal_link_transaction_id( $transaction_id, $invoice_id, $invoice );
781
        }        
782
    }
783
    
784
    return $transaction_id;
785
}
786
add_filter( 'wpinv_subscription_transaction_link_paypal', 'wpinv_paypal_transaction_id_link', 10, 2 );
787
788
function wpinv_is_paypal_valid_for_use() {
789
    return in_array( wpinv_get_currency(), apply_filters( 'wpinv_paypal_supported_currencies', array( 'AUD', 'BRL', 'CAD', 'MXN', 'NZD', 'HKD', 'SGD', 'USD', 'EUR', 'JPY', 'TRY', 'NOK', 'CZK', 'DKK', 'HUF', 'ILS', 'MYR', 'PHP', 'PLN', 'SEK', 'CHF', 'TWD', 'THB', 'GBP', 'RMB', 'RUB', 'INR' ) ) );
790
}
791
792
function wpinv_check_paypal_currency_support( $gateway_list ) {
793
    if ( isset( $gateway_list['paypal'] ) && ! wpinv_is_paypal_valid_for_use() ) {
794
        unset( $gateway_list['paypal'] );
795
    }
796
    return $gateway_list;
797
}
798
add_filter( 'wpinv_enabled_payment_gateways', 'wpinv_check_paypal_currency_support', 10, 1 );
799
800
function wpinv_gateway_paypal_button_label( $label ) {
0 ignored issues
show
Unused Code introduced by
The parameter $label is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

800
function wpinv_gateway_paypal_button_label( /** @scrutinizer ignore-unused */ $label ) {

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

Loading history...
801
    return __( 'Proceed to PayPal', 'invoicing' );
802
}
803
add_filter( 'wpinv_gateway_paypal_button_label', 'wpinv_gateway_paypal_button_label', 10, 1 );