Passed
Pull Request — master (#116)
by
unknown
04:15
created

paypal.php ➔ wpinv_get_paypal_subscription()   C

Complexity

Conditions 7
Paths 5

Size

Total Lines 37
Code Lines 17

Duplication

Lines 19
Ratio 51.35 %

Importance

Changes 0
Metric Value
cc 7
eloc 17
nc 5
nop 1
dl 19
loc 37
rs 6.7272
c 0
b 0
f 0
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 ) ), $payment );
0 ignored issues
show
Bug introduced by
The variable $payment does not exist. Did you mean $payment_data?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
34
        // Problems? send back
35
        wpinv_send_back_to_checkout( '?payment-mode=' . $purchase_data['post_data']['wpi-gateway'] );
0 ignored issues
show
Documentation introduced by
'?payment-mode=' . $purc...t_data']['wpi-gateway'] is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
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
            ), get_permalink( wpinv_get_option( 'success_page', false ) ) );
45
46
        // Get the PayPal redirect uri
47
        $paypal_redirect = trailingslashit( wpinv_get_paypal_redirect() ) . '?';
48
49
        // Setup PayPal arguments
50
        $paypal_args = array(
51
            'business'      => wpinv_get_option( 'paypal_email', false ),
52
            'email'         => $invoice->get_email(),
53
            'first_name'    => $invoice->get_first_name(),
54
            'last_name'     => $invoice->get_last_name(),
55
            'invoice'       => $invoice->get_key(),
56
            'no_shipping'   => '1',
57
            'shipping'      => '0',
58
            'no_note'       => '1',
59
            'currency_code' => wpinv_get_currency(),
60
            'charset'       => get_bloginfo( 'charset' ),
61
            'custom'        => $invoice->ID,
62
            'rm'            => '2',
63
            'return'        => $return_url,
64
            'cancel_return' => wpinv_get_failed_transaction_uri( '?invoice-id=' . $invoice->ID ),
0 ignored issues
show
Documentation introduced by
'?invoice-id=' . $invoice->ID is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
65
            'notify_url'    => $listener_url,
66
            'cbt'           => get_bloginfo( 'name' ),
67
            'bn'            => 'WPInvoicing_SP',
68
            'lc'            => 'US', // this will force paypal site to english
69
            'landing_page'  => apply_filters( 'wpinv_paypal_standard_landing_page', 'billing', $invoice ), // 'login' or 'billing'. login - PayPal account login, billing - Non-PayPal account.
70
        );
71
72
        $paypal_args['address1'] = $invoice->get_address();
73
        $paypal_args['city']     = $invoice->user_info['city'];
74
        $paypal_args['state']    = $invoice->user_info['state'];
75
        $paypal_args['country']  = $invoice->user_info['country'];
76
        $paypal_args['zip']      = $invoice->user_info['zip'];
77
78
        $paypal_extra_args = array(
79
            'cmd'    => '_cart',
80
            'upload' => '1'
81
        );
82
83
        $paypal_args = array_merge( $paypal_extra_args, $paypal_args );
84
85
        // Add cart items
86
        $i = 1;
87
        if( is_array( $purchase_data['cart_details'] ) && ! empty( $purchase_data['cart_details'] ) ) {
88
            foreach ( $purchase_data['cart_details'] as $item ) {
89
                $item['quantity'] = $item['quantity'] > 0 ? $item['quantity'] : 1;
90
                $item_amount = wpinv_sanitize_amount( $item['subtotal'] / $item['quantity'], 2 );
91
92
                if ( $item_amount <= 0 ) {
93
                    $item_amount = 0;
94
                }
95
96
                $paypal_args['item_number_' . $i ]      = $item['id'];
97
                $paypal_args['item_name_' . $i ]        = stripslashes_deep( html_entity_decode( wpinv_get_cart_item_name( $item ), ENT_COMPAT, 'UTF-8' ) );
98
                $paypal_args['quantity_' . $i ]         = $item['quantity'];
99
                $paypal_args['amount_' . $i ]           = $item_amount;
100
                $paypal_args['discount_amount_' . $i ]  = wpinv_sanitize_amount( $item['discount'], 2 );
101
102
                $i++;
103
            }
104
        }
105
106
        // Add taxes to the cart
107
        if ( wpinv_use_taxes() ) {
108
            $paypal_args['tax_cart'] = wpinv_sanitize_amount( (float)$invoice->get_tax(), 2 );
109
        }
110
111
        $paypal_args = apply_filters( 'wpinv_paypal_args', $paypal_args, $purchase_data, $invoice );
112
113
        // Build query
114
        $paypal_redirect .= http_build_query( $paypal_args );
115
116
        // Fix for some sites that encode the entities
117
        $paypal_redirect = str_replace( '&amp;', '&', $paypal_redirect );
118
119
        // Get rid of cart contents
120
        wpinv_empty_cart();
121
        
122
        // Redirect to PayPal
123
        wp_redirect( $paypal_redirect );
124
        exit;
125
    }
126
}
127
add_action( 'wpinv_gateway_paypal', 'wpinv_process_paypal_payment' );
128
129
function wpinv_get_paypal_recurring_args( $paypal_args, $purchase_data, $invoice ) {
130
    if ( $invoice->is_recurring() && $item_id = $invoice->get_recurring() ) {
131
        $item   = new WPInv_Item( $item_id );
132
        
133
        if ( empty( $item ) ) {
134
            return $paypal_args;
135
        }
136
137
        $period             = $item->get_recurring_period();
138
        $interval           = $item->get_recurring_interval();
139
        $bill_times         = (int)$item->get_recurring_limit();
140
        
141
        $initial_amount     = wpinv_sanitize_amount( $invoice->get_total(), 2 );
142
        $recurring_amount   = wpinv_sanitize_amount( $invoice->get_recurring_details( 'total' ), 2 );
143
        
144
        $paypal_args['cmd'] = '_xclick-subscriptions';
145
        $paypal_args['sra'] = '1';
146
        $paypal_args['src'] = '1';
147
        
148
        // Set item description
149
        $paypal_args['item_name']   = stripslashes_deep( html_entity_decode( wpinv_get_cart_item_name( array( 'id' => $item->ID ) ), ENT_COMPAT, 'UTF-8' ) );
150
        
151
        if ( $invoice->is_free_trial() && $item->has_free_trial() ) {
152
            $paypal_args['a1']  = $initial_amount;
153
            $paypal_args['p1']  = $item->get_trial_interval();
154
            $paypal_args['t1']  = $item->get_trial_period();
155
            
156
            // Set the recurring amount
157
            $paypal_args['a3']  = $recurring_amount;
158
        } else if ( $initial_amount != $recurring_amount && $bill_times != 1 ) {
159
            $paypal_args['a1']  = $initial_amount;
160
            $paypal_args['p1']  = $interval;
161
            $paypal_args['t1']  = $period;
162
            
163
            // Set the recurring amount
164
            $paypal_args['a3']  = $recurring_amount;
165
            
166
            if ( $bill_times > 1 ) {
167
                $bill_times--;
168
            }
169
        } else {
170
            $paypal_args['a3']  = $initial_amount;
171
        }
172
        
173
        $paypal_args['p3']  = $interval;
174
        $paypal_args['t3']  = $period;
175
        
176
        if ( $bill_times > 1 ) {
177
            // Make sure it's not over the max of 52
178
            $paypal_args['srt'] = ( $bill_times <= 52 ? absint( $bill_times ) : 52 );
179
        }
180
                
181
        // Remove cart items
182
        $i = 1;
183
        if( is_array( $purchase_data['cart_details'] ) && ! empty( $purchase_data['cart_details'] ) ) {
184
            foreach ( $purchase_data['cart_details'] as $item ) {                
185
                if ( isset( $paypal_args['item_number_' . $i] ) ) {
186
                    unset( $paypal_args['item_number_' . $i] );
187
                }
188
                if ( isset( $paypal_args['item_name_' . $i] ) ) {
189
                    unset( $paypal_args['item_name_' . $i] );
190
                }
191
                if ( isset( $paypal_args['quantity_' . $i] ) ) {
192
                    unset( $paypal_args['quantity_' . $i] );
193
                }
194
                if ( isset( $paypal_args['amount_' . $i] ) ) {
195
                    unset( $paypal_args['amount_' . $i] );
196
                }
197
                if ( isset( $paypal_args['discount_amount_' . $i] ) ) {
198
                    unset( $paypal_args['discount_amount_' . $i] );
199
                }
200
201
                $i++;
202
            }
203
        }
204
        
205
        if ( isset( $paypal_args['tax_cart'] ) ) {
206
            unset( $paypal_args['tax_cart'] );
207
        }
208
                
209
        if ( isset( $paypal_args['upload'] ) ) {
210
            unset( $paypal_args['upload'] );
211
        }
212
        
213
        $paypal_args = apply_filters( 'wpinv_paypal_recurring_args', $paypal_args, $purchase_data, $invoice );
214
    }
215
    
216
    return $paypal_args;
217
}
218
add_filter( 'wpinv_paypal_args', 'wpinv_get_paypal_recurring_args', 10, 3 );
219
220
function wpinv_process_paypal_ipn() {
221
	// Check the request method is POST
222
	if ( isset( $_SERVER['REQUEST_METHOD'] ) && $_SERVER['REQUEST_METHOD'] != 'POST' ) {
223
		return;
224
	}
225
226
	// Set initial post data to empty string
227
	$post_data = '';
228
229
	// Fallback just in case post_max_size is lower than needed
230
	if ( ini_get( 'allow_url_fopen' ) ) {
231
		$post_data = file_get_contents( 'php://input' );
232
	} else {
233
		// If allow_url_fopen is not enabled, then make sure that post_max_size is large enough
234
		ini_set( 'post_max_size', '12M' );
235
	}
236
	// Start the encoded data collection with notification command
237
	$encoded_data = 'cmd=_notify-validate';
238
239
	// Get current arg separator
240
	$arg_separator = wpinv_get_php_arg_separator_output();
241
242
	// Verify there is a post_data
243 View Code Duplication
	if ( $post_data || strlen( $post_data ) > 0 ) {
244
		// Append the data
245
		$encoded_data .= $arg_separator.$post_data;
246
	} else {
247
		// Check if POST is empty
248
		if ( empty( $_POST ) ) {
249
			// Nothing to do
250
			return;
251
		} else {
252
			// Loop through each POST
253
			foreach ( $_POST as $key => $value ) {
254
				// Encode the value and append the data
255
				$encoded_data .= $arg_separator."$key=" . urlencode( $value );
256
			}
257
		}
258
	}
259
260
	// Convert collected post data to an array
261
	parse_str( $encoded_data, $encoded_data_array );
262
263 View Code Duplication
	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...
264
		if ( false !== strpos( $key, 'amp;' ) ) {
265
			$new_key = str_replace( '&amp;', '&', $key );
266
			$new_key = str_replace( 'amp;', '&' , $new_key );
267
268
			unset( $encoded_data_array[ $key ] );
269
			$encoded_data_array[ $new_key ] = $value;
270
		}
271
	}
272
273
	// Get the PayPal redirect uri
274
	$paypal_redirect = wpinv_get_paypal_redirect( true );
0 ignored issues
show
Unused Code introduced by
$paypal_redirect is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
275
276
	if ( !wpinv_get_option( 'disable_paypal_verification', false ) ) {
277
		// Validate the IPN
278
279
		$remote_post_vars      = array(
280
			'method'           => 'POST',
281
			'timeout'          => 45,
282
			'redirection'      => 5,
283
			'httpversion'      => '1.1',
284
			'blocking'         => true,
285
			'headers'          => array(
286
				'host'         => 'www.paypal.com',
287
				'connection'   => 'close',
288
				'content-type' => 'application/x-www-form-urlencoded',
289
				'post'         => '/cgi-bin/webscr HTTP/1.1',
290
291
			),
292
			'sslverify'        => false,
293
			'body'             => $encoded_data_array
294
		);
295
296
		// Get response
297
		$api_response = wp_remote_post( wpinv_get_paypal_redirect(), $remote_post_vars );
298
299
		if ( is_wp_error( $api_response ) ) {
300
			wpinv_record_gateway_error( __( 'IPN Error', 'invoicing' ), sprintf( __( 'Invalid IPN verification response. IPN data: %s', 'invoicing' ), json_encode( $api_response ) ) );
301
			return; // Something went wrong
302
		}
303
304
		if ( $api_response['body'] !== 'VERIFIED' && wpinv_get_option( 'disable_paypal_verification', false ) ) {
305
			wpinv_record_gateway_error( __( 'IPN Error', 'invoicing' ), sprintf( __( 'Invalid IPN verification response. IPN data: %s', 'invoicing' ), json_encode( $api_response ) ) );
306
			return; // Response not okay
307
		}
308
	}
309
310
	// Check if $post_data_array has been populated
311
	if ( !is_array( $encoded_data_array ) && !empty( $encoded_data_array ) )
312
		return;
313
314
	$defaults = array(
315
		'txn_type'       => '',
316
		'payment_status' => ''
317
	);
318
319
	$encoded_data_array = wp_parse_args( $encoded_data_array, $defaults );
320
321
	$invoice_id = isset( $encoded_data_array['custom'] ) ? absint( $encoded_data_array['custom'] ) : 0;
322
    
323
	wpinv_error_log( $encoded_data_array['txn_type'], 'PayPal txn_type', __FILE__, __LINE__ );
324
325
	if ( has_action( 'wpinv_paypal_' . $encoded_data_array['txn_type'] ) ) {
326
		// Allow PayPal IPN types to be processed separately
327
		do_action( 'wpinv_paypal_' . $encoded_data_array['txn_type'], $encoded_data_array, $invoice_id );
328
	} else {
329
		// Fallback to web accept just in case the txn_type isn't present
330
		do_action( 'wpinv_paypal_web_accept', $encoded_data_array, $invoice_id );
331
	}
332
	exit;
333
}
334
add_action( 'wpinv_verify_paypal_ipn', 'wpinv_process_paypal_ipn' );
335
336
function wpinv_process_paypal_web_accept_and_cart( $data, $invoice_id ) {
337
	if ( $data['txn_type'] != 'web_accept' && $data['txn_type'] != 'cart' && $data['payment_status'] != 'Refunded' ) {
338
		return;
339
	}
340
341
	if( empty( $invoice_id ) ) {
342
		return;
343
	}
344
345
	// Collect payment details
346
	$purchase_key   = isset( $data['invoice'] ) ? $data['invoice'] : $data['item_number'];
347
	$paypal_amount  = $data['mc_gross'];
348
	$payment_status = strtolower( $data['payment_status'] );
349
	$currency_code  = strtolower( $data['mc_currency'] );
350
	$business_email = isset( $data['business'] ) && is_email( $data['business'] ) ? trim( $data['business'] ) : trim( $data['receiver_email'] );
351
	$payment_meta   = wpinv_get_invoice_meta( $invoice_id );
352
353
	if ( wpinv_get_payment_gateway( $invoice_id ) != 'paypal' ) {
354
		return; // this isn't a PayPal standard IPN
355
	}
356
357
	// Verify payment recipient
358
	if ( strcasecmp( $business_email, trim( wpinv_get_option( 'paypal_email', false ) ) ) != 0 ) {
359
		wpinv_record_gateway_error( __( 'IPN Error', 'invoicing' ), sprintf( __( 'Invalid business email in IPN response. IPN data: %s', 'invoicing' ), json_encode( $data ) ), $invoice_id );
360
		wpinv_update_payment_status( $invoice_id, 'wpi-failed' );
361
		wpinv_insert_payment_note( $invoice_id, __( 'Payment failed due to invalid PayPal business email.', 'invoicing' ), '', '', true );
0 ignored issues
show
Documentation introduced by
'' is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
362
		return;
363
	}
364
365
	// Verify payment currency
366 View Code Duplication
	if ( $currency_code != strtolower( $payment_meta['currency'] ) ) {
367
		wpinv_record_gateway_error( __( 'IPN Error', 'invoicing' ), sprintf( __( 'Invalid currency in IPN response. IPN data: %s', 'invoicing' ), json_encode( $data ) ), $invoice_id );
368
		wpinv_update_payment_status( $invoice_id, 'wpi-failed' );
369
		wpinv_insert_payment_note( $invoice_id, __( 'Payment failed due to invalid currency in PayPal IPN.', 'invoicing' ), '', '', true );
0 ignored issues
show
Documentation introduced by
'' is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
370
		return;
371
	}
372
373
	if ( !wpinv_get_payment_user_email( $invoice_id ) ) {
374
		// This runs when a Buy Now purchase was made. It bypasses checkout so no personal info is collected until PayPal
375
		// No email associated with purchase, so store from PayPal
376
		wpinv_update_invoice_meta( $invoice_id, '_wpinv_email', $data['payer_email'] );
377
378
		// Setup and store the customer's details
379
		$user_info = array(
380
			'user_id'    => '-1',
381
			'email'      => sanitize_text_field( $data['payer_email'] ),
382
			'first_name' => sanitize_text_field( $data['first_name'] ),
383
			'last_name'  => sanitize_text_field( $data['last_name'] ),
384
			'discount'   => '',
385
		);
386
		$user_info['address'] = ! empty( $data['address_street']       ) ? sanitize_text_field( $data['address_street'] )       : false;
387
		$user_info['city']    = ! empty( $data['address_city']         ) ? sanitize_text_field( $data['address_city'] )         : false;
388
		$user_info['state']   = ! empty( $data['address_state']        ) ? sanitize_text_field( $data['address_state'] )        : false;
389
		$user_info['country'] = ! empty( $data['address_country_code'] ) ? sanitize_text_field( $data['address_country_code'] ) : false;
390
		$user_info['zip']     = ! empty( $data['address_zip']          ) ? sanitize_text_field( $data['address_zip'] )          : false;
391
392
		$payment_meta['user_info'] = $user_info;
393
		wpinv_update_invoice_meta( $invoice_id, '_wpinv_payment_meta', $payment_meta );
0 ignored issues
show
Documentation introduced by
$payment_meta is of type array<string,array<strin...g,?,{\"zip\":\"?\"}>"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
394
	}
395
396
	if ( $payment_status == 'refunded' || $payment_status == 'reversed' ) {
397
		// Process a refund
398
		wpinv_process_paypal_refund( $data, $invoice_id );
399
	} else {
400
		if ( get_post_status( $invoice_id ) == 'publish' ) {
401
			return; // Only paid payments once
402
		}
403
404
		// Retrieve the total purchase amount (before PayPal)
405
		$payment_amount = wpinv_payment_total( $invoice_id );
406
407
		if ( number_format( (float) $paypal_amount, 2 ) < number_format( (float) $payment_amount, 2 ) ) {
408
			// The prices don't match
409
			wpinv_record_gateway_error( __( 'IPN Error', 'invoicing' ), sprintf( __( 'Invalid payment amount in IPN response. IPN data: %s', 'invoicing' ), json_encode( $data ) ), $invoice_id );
410
			wpinv_update_payment_status( $invoice_id, 'wpi-failed' );
411
			wpinv_insert_payment_note( $invoice_id, __( 'Payment failed due to invalid amount in PayPal IPN.', 'invoicing' ), '', '', true );
0 ignored issues
show
Documentation introduced by
'' is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
412
			return;
413
		}
414 View Code Duplication
		if ( $purchase_key != wpinv_get_payment_key( $invoice_id ) ) {
415
			// Purchase keys don't match
416
			wpinv_record_gateway_error( __( 'IPN Error', 'invoicing' ), sprintf( __( 'Invalid purchase key in IPN response. IPN data: %s', 'invoicing' ), json_encode( $data ) ), $invoice_id );
417
			wpinv_update_payment_status( $invoice_id, 'wpi-failed' );
418
			wpinv_insert_payment_note( $invoice_id, __( 'Payment failed due to invalid purchase key in PayPal IPN.', 'invoicing' ), '', '', true );
0 ignored issues
show
Documentation introduced by
'' is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
419
			return;
420
		}
421
422
		if ( 'complete' == $payment_status || 'completed' == $payment_status || 'processed' == $payment_status || wpinv_is_test_mode( 'paypal' ) ) {
423
			wpinv_insert_payment_note( $invoice_id, sprintf( __( 'PayPal Transaction ID: %s', 'invoicing' ) , $data['txn_id'] ), '', '', true );
0 ignored issues
show
Documentation introduced by
'' is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
424
			wpinv_set_payment_transaction_id( $invoice_id, $data['txn_id'] );
425
			wpinv_update_payment_status( $invoice_id, 'publish' );
426
		} else if ( 'wpi-pending' == $payment_status && isset( $data['pending_reason'] ) ) {
427
			// Look for possible pending reasons, such as an echeck
428
			$note = '';
429
430
			switch( strtolower( $data['pending_reason'] ) ) {
431
				case 'echeck' :
432
					$note = __( 'Payment made via eCheck and will clear automatically in 5-8 days', 'invoicing' );
433
					break;
434
				
435
                case 'address' :
436
					$note = __( 'Payment requires a confirmed customer address and must be accepted manually through PayPal', 'invoicing' );
437
					break;
438
				
439
                case 'intl' :
440
					$note = __( 'Payment must be accepted manually through PayPal due to international account regulations', 'invoicing' );
441
					break;
442
				
443
                case 'multi-currency' :
444
					$note = __( 'Payment received in non-shop currency and must be accepted manually through PayPal', 'invoicing' );
445
					break;
446
				
447
                case 'paymentreview' :
448
                case 'regulatory_review' :
449
					$note = __( 'Payment is being reviewed by PayPal staff as high-risk or in possible violation of government regulations', 'invoicing' );
450
					break;
451
				
452
                case 'unilateral' :
453
					$note = __( 'Payment was sent to non-confirmed or non-registered email address.', 'invoicing' );
454
					break;
455
				
456
                case 'upgrade' :
457
					$note = __( 'PayPal account must be upgraded before this payment can be accepted', 'invoicing' );
458
					break;
459
				
460
                case 'verify' :
461
					$note = __( 'PayPal account is not verified. Verify account in order to accept this payment', 'invoicing' );
462
					break;
463
464
				case 'other' :
465
					$note = __( 'Payment is pending for unknown reasons. Contact PayPal support for assistance', 'invoicing' );
466
					break;
467
			}
468
469
			if ( ! empty( $note ) ) {
470
				wpinv_insert_payment_note( $invoice_id, $note );
471
			}
472
		} else {
473
			wpinv_insert_payment_note( $invoice_id, wp_sprintf( __( 'PayPal IPN has been received with invalid payment status: %s', 'invoicing' ), $payment_status ), '', '', true );
0 ignored issues
show
Documentation introduced by
'' is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
474
		}
475
	}
476
}
477
add_action( 'wpinv_paypal_web_accept', 'wpinv_process_paypal_web_accept_and_cart', 10, 2 );
478
479
// Process PayPal subscription sign ups
480
add_action( 'wpinv_paypal_subscr_signup', 'wpinv_process_paypal_subscr_signup' );
481
482
// Process PayPal subscription payments
483
add_action( 'wpinv_paypal_subscr_payment', 'wpinv_process_paypal_subscr_payment' );
484
485
// Process PayPal subscription cancellations
486
add_action( 'wpinv_paypal_subscr_cancel', 'wpinv_process_paypal_subscr_cancel' );
487
488
// Process PayPal subscription end of term notices
489
add_action( 'wpinv_paypal_subscr_eot', 'wpinv_process_paypal_subscr_eot' );
490
491
// Process PayPal payment failed
492
add_action( 'wpinv_paypal_subscr_failed', 'wpinv_process_paypal_subscr_failed' );
493
494
495
/**
496
 * Process the subscription started IPN.
497
 */
498
function wpinv_process_paypal_subscr_signup( $ipn_data ) {
499
    $parent_invoice_id = absint( $ipn_data['custom'] );
500
    if( empty( $parent_invoice_id ) ) {
501
        return;
502
    }
503
504
    $invoice = wpinv_get_invoice( $parent_invoice_id );
505
    if ( empty( $invoice ) ) {
506
        return;
507
    }
508
509
    if ( $invoice->is_free_trial() && !empty( $ipn_data['invoice'] ) ) {
510
        wpinv_insert_payment_note( $parent_invoice_id, sprintf( __( 'PayPal Invoice ID: %s', 'invoicing' ) , $ipn_data['invoice'] ), '', '', true);
0 ignored issues
show
Documentation introduced by
'' is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
511
        wpinv_set_payment_transaction_id( $parent_invoice_id, $ipn_data['txn_id'] );
512
    }
513
514
    $subscription = wpinv_get_paypal_subscription( $ipn_data );
515
    if ( false === $subscription ) {
516
        return;
517
    }
518
    
519
    wpinv_update_payment_status( $parent_invoice_id, 'publish' );
520
    sleep(1);
521
    wpinv_insert_payment_note( $parent_invoice_id, sprintf( __( 'PayPal Subscription ID: %s', 'invoicing' ) , $ipn_data['subscr_id'] ), '', '', true );
0 ignored issues
show
Documentation introduced by
'' is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
522
    update_post_meta($parent_invoice_id,'_wpinv_subscr_profile_id', $ipn_data['subscr_id']);
523
524
    $status = 'trialling' == $subscription->status ? 'trialling' : 'active';
525
    // Retrieve pending subscription from database and update it's status to active and set proper profile ID
526
    $subscription->update( array( 'profile_id' => $ipn_data['subscr_id'], 'status' => $status ) );
527
}
528
529
/**
530
 * Process the subscription payment received IPN.
531
 */
532
function wpinv_process_paypal_subscr_payment( $ipn_data ) {
533
534
    $parent_invoice_id = absint( $ipn_data['custom'] );
535
    $parent_invoice = wpinv_get_invoice( $parent_invoice_id );
536
    if ( empty( $parent_invoice ) ) {
537
        return;
538
    }
539
540
    $subscription = wpinv_get_paypal_subscription( $ipn_data );
541
    if ( false === $subscription ) {
542
        return;
543
    }
544
545
    $transaction_id = wpinv_get_payment_transaction_id( $parent_invoice_id );
546
    $times_billed   = $subscription->get_times_billed();
547
548
    // Look to see if payment is same day as signup and we have set the transaction ID on the parent payment yet.
549
    if ( empty( $times_billed ) && ( !$transaction_id || $transaction_id == $parent_invoice_id ) ) {
550
        wpinv_update_payment_status( $parent_invoice_id, 'publish' );
551
        sleep(1);
552
        
553
        // This is the very first payment
554
        wpinv_set_payment_transaction_id( $parent_invoice_id, $ipn_data['txn_id'] );
555
        wpinv_insert_payment_note( $parent_invoice_id, sprintf( __( 'PayPal Transaction ID: %s', 'invoicing' ) , $ipn_data['txn_id'] ), '', '', true );
0 ignored issues
show
Documentation introduced by
'' is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
556
        return;
557
    }
558
    
559
    if ( wpinv_get_id_by_transaction_id( $ipn_data['txn_id'] ) ) {
560
        return; // Payment already recorded
561
    }
562
563
    $currency_code = strtolower( $ipn_data['mc_currency'] );
564
565
    // verify details
566
    if ( $currency_code != strtolower( wpinv_get_currency() ) ) {
567
        // the currency code is invalid
568
        wpinv_record_gateway_error( __( 'IPN Error', 'invoicing' ), sprintf( __( 'Invalid currency in IPN response. IPN data: ', 'invoicing' ), json_encode( $ipn_data ) ), '', '', true );
0 ignored issues
show
Unused Code introduced by
The call to wpinv_record_gateway_error() has too many arguments starting with ''.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
569
        return;
570
    }
571
572
    $args = array(
573
        'amount'         => $ipn_data['mc_gross'],
574
        'transaction_id' => $ipn_data['txn_id'],
575
        'gateway'        => 'paypal'
576
    );
577
578
    $invoice_id = $subscription->add_payment( $args );
579
580
    if ( $invoice_id > 0 ) {
581
        wpinv_update_payment_status( $invoice_id, 'publish' );
582
        sleep(1);
583
        wpinv_update_payment_status( $invoice_id, 'wpi-renewal' );
584
        wpinv_insert_payment_note( $invoice_id, sprintf( __( 'PayPal Transaction ID: %s', 'invoicing' ) , $ipn_data['txn_id'] ), '', '', true );
0 ignored issues
show
Documentation introduced by
'' is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
585
586
        $subscription->renew();
587
    }
588
589
}
590
591
/**
592
 * Process the subscription canceled IPN.
593
 */
594
function wpinv_process_paypal_subscr_cancel( $ipn_data ) {
595
    $subscription = wpinv_get_paypal_subscription( $ipn_data );
596
597
    if( false === $subscription ) {
598
        return;
599
    }
600
601
    $subscription->cancel();
602
}
603
604
/**
605
 * Process the subscription expired IPN.
606
 */
607
function wpinv_process_paypal_subscr_eot( $ipn_data ) {
608
    $subscription = wpinv_get_paypal_subscription( $ipn_data );
609
610
    if( false === $subscription ) {
611
        return;
612
    }
613
614
    $subscription->complete();
615
}
616
617
/**
618
 * Process the subscription payment failed IPN.
619
 */
620
function wpinv_process_paypal_subscr_failed( $ipn_data ) {
621
    $subscription = wpinv_get_paypal_subscription( $ipn_data );
622
623
    if( false === $subscription ) {
624
        return;
625
    }
626
627
    $subscription->failing();
628
629
    do_action( 'wpinv_recurring_payment_failed', $subscription );
630
}
631
632
/**
633
 * Retrieve the subscription this IPN notice is for.
634
 */
635
function wpinv_get_paypal_subscription( $ipn_data = array() ) {
636
    $parent_invoice_id = absint( $ipn_data['custom'] );
637
638
    if( empty( $parent_invoice_id ) ) {
639
        return false;
640
    }
641
642
    $invoice = wpinv_get_invoice( $parent_invoice_id );
643
    if ( empty( $invoice ) ) {
644
        return false;
645
    }
646
647
    $subscription = new WPInv_Subscription( $ipn_data['subscr_id'], true );
648
649 View Code Duplication
    if( ! $subscription || $subscription->id < 1 ) {
650
651
        $subs_db      = new WPInv_Subscriptions_DB;
652
        $subs         = $subs_db->get_subscriptions( array( 'parent_payment_id' => $parent_invoice_id, 'number' => 1 ) );
653
        $subscription = reset( $subs );
654
655
        if( $subscription && $subscription->id > 0 ) {
656
657
            // Update the profile ID so it is set for future renewals
658
            $subscription->update( array( 'profile_id' => sanitize_text_field( $ipn_data['subscr_id'] ) ) );
659
660
        } else {
661
662
            // No subscription found with a matching payment ID, bail
663
            return false;
664
665
        }
666
667
    }
668
669
    return $subscription;
670
671
}
672
673
function wpinv_process_paypal_refund( $data, $invoice_id = 0 ) {
674
	// Collect payment details
675
676
	if( empty( $invoice_id ) ) {
677
		return;
678
	}
679
680
	if ( get_post_status( $invoice_id ) == 'wpi-refunded' ) {
681
		return; // Only refund payments once
682
	}
683
684
	$payment_amount = wpinv_payment_total( $invoice_id );
685
	$refund_amount  = $data['mc_gross'] * -1;
686
687
	if ( number_format( (float) $refund_amount, 2 ) < number_format( (float) $payment_amount, 2 ) ) {
688
		wpinv_insert_payment_note( $invoice_id, sprintf( __( 'Partial PayPal refund processed: %s', 'invoicing' ), $data['parent_txn_id'] ), '', '', true );
0 ignored issues
show
Documentation introduced by
'' is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
689
		return; // This is a partial refund
690
	}
691
692
	wpinv_insert_payment_note( $invoice_id, sprintf( __( 'PayPal Payment #%s Refunded for reason: %s', 'invoicing' ), $data['parent_txn_id'], $data['reason_code'] ), '', '', true );
0 ignored issues
show
Documentation introduced by
'' is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
693
	wpinv_insert_payment_note( $invoice_id, sprintf( __( 'PayPal Refund Transaction ID: %s', 'invoicing' ), $data['txn_id'] ), '', '', true );
0 ignored issues
show
Documentation introduced by
'' is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
694
	wpinv_update_payment_status( $invoice_id, 'wpi-refunded' );
695
}
696
697
function wpinv_get_paypal_redirect( $ssl_check = false ) {
698
    if ( is_ssl() || ! $ssl_check ) {
699
        $protocol = 'https://';
700
    } else {
701
        $protocol = 'http://';
702
    }
703
704
    // Check the current payment mode
705
    if ( wpinv_is_test_mode( 'paypal' ) ) {
706
        // Test mode
707
        $paypal_uri = $protocol . 'www.sandbox.paypal.com/cgi-bin/webscr';
708
    } else {
709
        // Live mode
710
        $paypal_uri = $protocol . 'www.paypal.com/cgi-bin/webscr';
711
    }
712
713
    return apply_filters( 'wpinv_paypal_uri', $paypal_uri );
714
}
715
716
function wpinv_paypal_success_page_content( $content ) {
717
    global $wpi_invoice;
718
    
719
    $session = wpinv_get_checkout_session();
720
721
    if ( empty( $_GET['invoice-id'] ) && empty( $session['invoice_key'] )  ) {
722
        return $content;
723
    }
724
725
    $invoice_id = !empty( $_GET['invoice-id'] ) ? absint( $_GET['invoice-id'] ) : wpinv_get_invoice_id_by_key( $session['invoice_key'] );
726
727
    if ( empty(  $invoice_id ) ) {
728
        return $content;
729
    }
730
731
    $wpi_invoice = wpinv_get_invoice( $invoice_id );
732
    
733
    if ( !empty( $wpi_invoice ) && 'wpi-pending' == $wpi_invoice->status ) {
734
        // Payment is still pending so show processing indicator to fix the Race Condition, issue #
735
        ob_start();
736
        wpinv_get_template_part( 'wpinv-payment-processing' );
737
        $content = ob_get_clean();
738
    }
739
740
    return $content;
741
}
742
add_filter( 'wpinv_payment_confirm_paypal', 'wpinv_paypal_success_page_content' );
743
744
function wpinv_paypal_get_transaction_id( $invoice_id ) {
745
    $transaction_id = '';
746
    $notes = wpinv_get_invoice_notes( $invoice_id );
747
748
    foreach ( $notes as $note ) {
749
        if ( preg_match( '/^PayPal Transaction ID: ([^\s]+)/', $note->comment_content, $match ) ) {
750
            $transaction_id = $match[1];
751
            continue;
752
        }
753
    }
754
755
    return apply_filters( 'wpinv_paypal_set_transaction_id', $transaction_id, $invoice_id );
756
}
757
add_filter( 'wpinv_payment_get_transaction_id-paypal', 'wpinv_paypal_get_transaction_id', 10, 1 );
758
759
function wpinv_paypal_link_transaction_id( $transaction_id, $invoice_id, $invoice ) {
760
    if ( $invoice->is_free_trial() || $transaction_id == $invoice_id ) { // Free trial does not have transaction at PayPal.
761
        $transaction_url = $invoice->get_view_url();
762
    } else {
763
        $sandbox = wpinv_is_test_mode( 'paypal' ) ? '.sandbox' : '';
764
        $transaction_url = 'https://www' . $sandbox . '.paypal.com/cgi-bin/webscr?cmd=_view-a-trans&id=' . $transaction_id;
765
    }
766
767
    $transaction_link = '<a href="' . esc_url( $transaction_url ) . '" target="_blank">' . $transaction_id . '</a>';
768
769
    return apply_filters( 'wpinv_paypal_link_payment_details_transaction_id', $transaction_link, $invoice );
770
}
771
add_filter( 'wpinv_payment_details_transaction_id-paypal', 'wpinv_paypal_link_transaction_id', 10, 3 );
772
773
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.

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...
774
    return __( 'Proceed to PayPal', 'invoicing' );
775
}
776
add_filter( 'wpinv_gateway_paypal_button_label', 'wpinv_gateway_paypal_button_label', 10, 1 );