Passed
Pull Request — master (#126)
by Kiran
04:13
created

paypal.php ➔ wpinv_get_paypal_subscription()   C

Complexity

Conditions 7
Paths 5

Size

Total Lines 27
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 15
nc 5
nop 1
dl 0
loc 27
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 ) ), $invoice );
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
	wpinv_error_log( $encoded_data_array, 'PayPal IPN response', __FILE__, __LINE__ );
325
326
	if ( has_action( 'wpinv_paypal_' . $encoded_data_array['txn_type'] ) ) {
327
		// Allow PayPal IPN types to be processed separately
328
		do_action( 'wpinv_paypal_' . $encoded_data_array['txn_type'], $encoded_data_array, $invoice_id );
329
	} else {
330
		// Fallback to web accept just in case the txn_type isn't present
331
		do_action( 'wpinv_paypal_web_accept', $encoded_data_array, $invoice_id );
332
	}
333
	exit;
334
}
335
add_action( 'wpinv_verify_paypal_ipn', 'wpinv_process_paypal_ipn' );
336
337
function wpinv_process_paypal_web_accept_and_cart( $data, $invoice_id ) {
338
	if ( $data['txn_type'] != 'web_accept' && $data['txn_type'] != 'cart' && $data['payment_status'] != 'Refunded' ) {
339
		return;
340
	}
341
342
	if( empty( $invoice_id ) ) {
343
		return;
344
	}
345
346
	// Collect payment details
347
	$purchase_key   = isset( $data['invoice'] ) ? $data['invoice'] : $data['item_number'];
348
	$paypal_amount  = $data['mc_gross'];
349
	$payment_status = strtolower( $data['payment_status'] );
350
	$currency_code  = strtolower( $data['mc_currency'] );
351
	$business_email = isset( $data['business'] ) && is_email( $data['business'] ) ? trim( $data['business'] ) : trim( $data['receiver_email'] );
352
	$payment_meta   = wpinv_get_invoice_meta( $invoice_id );
353
354
	if ( wpinv_get_payment_gateway( $invoice_id ) != 'paypal' ) {
355
		return; // this isn't a PayPal standard IPN
356
	}
357
358
	// Verify payment recipient
359
	if ( strcasecmp( $business_email, trim( wpinv_get_option( 'paypal_email', false ) ) ) != 0 ) {
360
		wpinv_record_gateway_error( __( 'IPN Error', 'invoicing' ), sprintf( __( 'Invalid business email in IPN response. IPN data: %s', 'invoicing' ), json_encode( $data ) ), $invoice_id );
361
		wpinv_update_payment_status( $invoice_id, 'wpi-failed' );
362
		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...
363
		return;
364
	}
365
366
	// Verify payment currency
367 View Code Duplication
	if ( $currency_code != strtolower( $payment_meta['currency'] ) ) {
368
		wpinv_record_gateway_error( __( 'IPN Error', 'invoicing' ), sprintf( __( 'Invalid currency in IPN response. IPN data: %s', 'invoicing' ), json_encode( $data ) ), $invoice_id );
369
		wpinv_update_payment_status( $invoice_id, 'wpi-failed' );
370
		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...
371
		return;
372
	}
373
374
	if ( !wpinv_get_payment_user_email( $invoice_id ) ) {
375
		// This runs when a Buy Now purchase was made. It bypasses checkout so no personal info is collected until PayPal
376
		// No email associated with purchase, so store from PayPal
377
		wpinv_update_invoice_meta( $invoice_id, '_wpinv_email', $data['payer_email'] );
378
379
		// Setup and store the customer's details
380
		$user_info = array(
381
			'user_id'    => '-1',
382
			'email'      => sanitize_text_field( $data['payer_email'] ),
383
			'first_name' => sanitize_text_field( $data['first_name'] ),
384
			'last_name'  => sanitize_text_field( $data['last_name'] ),
385
			'discount'   => '',
386
		);
387
		$user_info['address'] = ! empty( $data['address_street']       ) ? sanitize_text_field( $data['address_street'] )       : false;
388
		$user_info['city']    = ! empty( $data['address_city']         ) ? sanitize_text_field( $data['address_city'] )         : false;
389
		$user_info['state']   = ! empty( $data['address_state']        ) ? sanitize_text_field( $data['address_state'] )        : false;
390
		$user_info['country'] = ! empty( $data['address_country_code'] ) ? sanitize_text_field( $data['address_country_code'] ) : false;
391
		$user_info['zip']     = ! empty( $data['address_zip']          ) ? sanitize_text_field( $data['address_zip'] )          : false;
392
393
		$payment_meta['user_info'] = $user_info;
394
		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...
395
	}
396
397
	if ( $payment_status == 'refunded' || $payment_status == 'reversed' ) {
398
		// Process a refund
399
		wpinv_process_paypal_refund( $data, $invoice_id );
400
	} else {
401
		if ( get_post_status( $invoice_id ) == 'publish' ) {
402
			return; // Only paid payments once
403
		}
404
405
		// Retrieve the total purchase amount (before PayPal)
406
		$payment_amount = wpinv_payment_total( $invoice_id );
407
408
		if ( number_format( (float) $paypal_amount, 2 ) < number_format( (float) $payment_amount, 2 ) ) {
409
			// The prices don't match
410
			wpinv_record_gateway_error( __( 'IPN Error', 'invoicing' ), sprintf( __( 'Invalid payment amount in IPN response. IPN data: %s', 'invoicing' ), json_encode( $data ) ), $invoice_id );
411
			wpinv_update_payment_status( $invoice_id, 'wpi-failed' );
412
			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...
413
			return;
414
		}
415 View Code Duplication
		if ( $purchase_key != wpinv_get_payment_key( $invoice_id ) ) {
416
			// Purchase keys don't match
417
			wpinv_record_gateway_error( __( 'IPN Error', 'invoicing' ), sprintf( __( 'Invalid purchase key in IPN response. IPN data: %s', 'invoicing' ), json_encode( $data ) ), $invoice_id );
418
			wpinv_update_payment_status( $invoice_id, 'wpi-failed' );
419
			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...
420
			return;
421
		}
422
423
		if ( 'complete' == $payment_status || 'completed' == $payment_status || 'processed' == $payment_status || wpinv_is_test_mode( 'paypal' ) ) {
424
			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...
425
			wpinv_set_payment_transaction_id( $invoice_id, $data['txn_id'] );
426
			wpinv_update_payment_status( $invoice_id, 'publish' );
427
		} else if ( 'pending' == $payment_status && isset( $data['pending_reason'] ) ) {
428
			// Look for possible pending reasons, such as an echeck
429
			$note = '';
430
431
			switch( strtolower( $data['pending_reason'] ) ) {
432
				case 'echeck' :
433
					$note = __( 'Payment made via eCheck and will clear automatically in 5-8 days', 'invoicing' );
434
					break;
435
				
436
                case 'address' :
437
					$note = __( 'Payment requires a confirmed customer address and must be accepted manually through PayPal', 'invoicing' );
438
					break;
439
				
440
                case 'intl' :
441
					$note = __( 'Payment must be accepted manually through PayPal due to international account regulations', 'invoicing' );
442
					break;
443
				
444
                case 'multi-currency' :
445
					$note = __( 'Payment received in non-shop currency and must be accepted manually through PayPal', 'invoicing' );
446
					break;
447
				
448
                case 'paymentreview' :
449
                case 'regulatory_review' :
450
					$note = __( 'Payment is being reviewed by PayPal staff as high-risk or in possible violation of government regulations', 'invoicing' );
451
					break;
452
				
453
                case 'unilateral' :
454
					$note = __( 'Payment was sent to non-confirmed or non-registered email address.', 'invoicing' );
455
					break;
456
				
457
                case 'upgrade' :
458
					$note = __( 'PayPal account must be upgraded before this payment can be accepted', 'invoicing' );
459
					break;
460
				
461
                case 'verify' :
462
					$note = __( 'PayPal account is not verified. Verify account in order to accept this payment', 'invoicing' );
463
					break;
464
465
				case 'other' :
466
					$note = __( 'Payment is pending for unknown reasons. Contact PayPal support for assistance', 'invoicing' );
467
					break;
468
			}
469
470
			if ( ! empty( $note ) ) {
471
				wpinv_insert_payment_note( $invoice_id, $note, '', '', 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...
472
			}
473
		} else {
474
			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...
475
		}
476
	}
477
}
478
add_action( 'wpinv_paypal_web_accept', 'wpinv_process_paypal_web_accept_and_cart', 10, 2 );
479
480
// Process PayPal subscription sign ups
481
add_action( 'wpinv_paypal_subscr_signup', 'wpinv_process_paypal_subscr_signup' );
482
483
// Process PayPal subscription payments
484
add_action( 'wpinv_paypal_subscr_payment', 'wpinv_process_paypal_subscr_payment' );
485
486
// Process PayPal subscription cancellations
487
add_action( 'wpinv_paypal_subscr_cancel', 'wpinv_process_paypal_subscr_cancel' );
488
489
// Process PayPal subscription end of term notices
490
add_action( 'wpinv_paypal_subscr_eot', 'wpinv_process_paypal_subscr_eot' );
491
492
// Process PayPal payment failed
493
add_action( 'wpinv_paypal_subscr_failed', 'wpinv_process_paypal_subscr_failed' );
494
495
496
/**
497
 * Process the subscription started IPN.
498
 */
499
function wpinv_process_paypal_subscr_signup( $ipn_data ) {
500
    $parent_invoice_id = absint( $ipn_data['custom'] );
501
    if( empty( $parent_invoice_id ) ) {
502
        return;
503
    }
504
505
    $invoice = wpinv_get_invoice( $parent_invoice_id );
506
    if ( empty( $invoice ) ) {
507
        return;
508
    }
509
510
    if ( $invoice->is_free_trial() && !empty( $ipn_data['invoice'] ) ) {
511
        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...
512
        if ( !empty( $ipn_data['txn_id'] ) ) {
513
            wpinv_set_payment_transaction_id( $parent_invoice_id, $ipn_data['txn_id'] );
514
        }
515
    }
516
517
    $subscription = wpinv_get_paypal_subscription( $ipn_data );
518
    if ( false === $subscription ) {
519
        return;
520
    }
521
    
522
    wpinv_update_payment_status( $parent_invoice_id, 'publish' );
523
    sleep(1);
524
    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...
525
    update_post_meta($parent_invoice_id,'_wpinv_subscr_profile_id', $ipn_data['subscr_id']);
526
527
    $status = 'trialling' == $subscription->status ? 'trialling' : 'active';
528
    // Retrieve pending subscription from database and update it's status to active and set proper profile ID
529
    $subscription->update( array( 'profile_id' => $ipn_data['subscr_id'], 'status' => $status ) );
530
}
531
532
/**
533
 * Process the subscription payment received IPN.
534
 */
535
function wpinv_process_paypal_subscr_payment( $ipn_data ) {
536
    $parent_invoice_id = absint( $ipn_data['custom'] );
537
538
    $parent_invoice = wpinv_get_invoice( $parent_invoice_id );
539
    if ( empty( $parent_invoice ) ) {
540
        return;
541
    }
542
543
    $subscription = wpinv_get_paypal_subscription( $ipn_data );
544
    if ( false === $subscription ) {
545
        return;
546
    }
547
548
    $transaction_id = wpinv_get_payment_transaction_id( $parent_invoice_id );
549
    $times_billed   = $subscription->get_times_billed();
550
551
    // Look to see if payment is same day as signup and we have set the transaction ID on the parent payment yet.
552
    if ( empty( $times_billed ) && ( !$transaction_id || $transaction_id == $parent_invoice_id ) ) {
553
        wpinv_update_payment_status( $parent_invoice_id, 'publish' );
554
        sleep(1);
555
        
556
        // This is the very first payment
557
        wpinv_set_payment_transaction_id( $parent_invoice_id, $ipn_data['txn_id'] );
558
        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...
559
        return;
560
    }
561
    
562
    if ( wpinv_get_id_by_transaction_id( $ipn_data['txn_id'] ) ) {
563
        return; // Payment already recorded
564
    }
565
566
    $currency_code = strtolower( $ipn_data['mc_currency'] );
567
568
    // verify details
569
    if ( $currency_code != strtolower( wpinv_get_currency() ) ) {
570
        // the currency code is invalid
571
        wpinv_record_gateway_error( __( 'IPN Error', 'invoicing' ), sprintf( __( 'Invalid currency in IPN response. IPN data: ', 'invoicing' ), json_encode( $ipn_data ) ) );
572
        return;
573
    }
574
575
    $args = array(
576
        'amount'         => $ipn_data['mc_gross'],
577
        'transaction_id' => $ipn_data['txn_id'],
578
        'gateway'        => 'paypal'
579
    );
580
    
581
    $invoice_id = $subscription->add_payment( $args );
582
583
    if ( $invoice_id > 0 ) {
584
        wpinv_insert_payment_note( $invoice_id, wp_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
        wpinv_insert_payment_note( $invoice_id, wp_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...
586
587
        $subscription->renew();
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
    if ( ! ( ! empty( $subscription ) && $subscription->id > 0 ) ) {
650
        $subscription = wpinv_get_subscription( $parent_invoice_id );
651
652
        if ( ! empty( $subscription ) && $subscription->id > 0 ) {
653
            $subscription->update( array( 'profile_id' => sanitize_text_field( $ipn_data['subscr_id'] ) ) );
654
        } else {
655
            return false;
656
        }
657
    }
658
659
    return $subscription;
660
661
}
662
663
function wpinv_process_paypal_refund( $data, $invoice_id = 0 ) {
664
	// Collect payment details
665
666
	if( empty( $invoice_id ) ) {
667
		return;
668
	}
669
670
	if ( get_post_status( $invoice_id ) == 'wpi-refunded' ) {
671
		return; // Only refund payments once
672
	}
673
674
	$payment_amount = wpinv_payment_total( $invoice_id );
675
	$refund_amount  = $data['mc_gross'] * -1;
676
677
	do_action( 'wpinv_paypal_refund_request', $data, $invoice_id );
678
679
	if ( number_format( (float) $refund_amount, 2 ) < number_format( (float) $payment_amount, 2 ) ) {
680
		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 );
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...
681
682
		do_action( 'wpinv_paypal_invoice_partially_refunded', $data, $invoice_id, $refund_amount );
683
684
		return; // This is a partial refund
685
	}
686
687
	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...
688
	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...
689
	wpinv_update_payment_status( $invoice_id, 'wpi-refunded' );
690
691
	do_action( 'wpinv_paypal_invoice_fully_refunded', $data, $invoice_id );
692
}
693
694
function wpinv_get_paypal_redirect( $ssl_check = false ) {
695
    if ( is_ssl() || ! $ssl_check ) {
696
        $protocol = 'https://';
697
    } else {
698
        $protocol = 'http://';
699
    }
700
701
    // Check the current payment mode
702
    if ( wpinv_is_test_mode( 'paypal' ) ) {
703
        // Test mode
704
        $paypal_uri = $protocol . 'www.sandbox.paypal.com/cgi-bin/webscr';
705
    } else {
706
        // Live mode
707
        $paypal_uri = $protocol . 'www.paypal.com/cgi-bin/webscr';
708
    }
709
710
    return apply_filters( 'wpinv_paypal_uri', $paypal_uri );
711
}
712
713
function wpinv_paypal_success_page_content( $content ) {
714
    global $wpi_invoice;
715
    
716
    $session = wpinv_get_checkout_session();
717
718
    if ( empty( $_GET['invoice-id'] ) && empty( $session['invoice_key'] )  ) {
719
        return $content;
720
    }
721
722
    $invoice_id = !empty( $_GET['invoice-id'] ) ? absint( $_GET['invoice-id'] ) : wpinv_get_invoice_id_by_key( $session['invoice_key'] );
723
724
    if ( empty(  $invoice_id ) ) {
725
        return $content;
726
    }
727
728
    $wpi_invoice = wpinv_get_invoice( $invoice_id );
729
    
730
    if ( !empty( $wpi_invoice ) && 'wpi-pending' == $wpi_invoice->status ) {
731
        // Payment is still pending so show processing indicator to fix the Race Condition, issue #
732
        ob_start();
733
        wpinv_get_template_part( 'wpinv-payment-processing' );
734
        $content = ob_get_clean();
735
    }
736
737
    return $content;
738
}
739
add_filter( 'wpinv_payment_confirm_paypal', 'wpinv_paypal_success_page_content' );
740
741
function wpinv_paypal_get_transaction_id( $invoice_id ) {
742
    $transaction_id = '';
743
    $notes = wpinv_get_invoice_notes( $invoice_id );
744
745
    foreach ( $notes as $note ) {
746
        if ( preg_match( '/^PayPal Transaction ID: ([^\s]+)/', $note->comment_content, $match ) ) {
747
            $transaction_id = $match[1];
748
            continue;
749
        }
750
    }
751
752
    return apply_filters( 'wpinv_paypal_set_transaction_id', $transaction_id, $invoice_id );
753
}
754
add_filter( 'wpinv_payment_get_transaction_id-paypal', 'wpinv_paypal_get_transaction_id', 10, 1 );
755
756
function wpinv_paypal_link_transaction_id( $transaction_id, $invoice_id, $invoice ) {
757
    if ( $invoice->is_free_trial() || $transaction_id == $invoice_id ) { // Free trial does not have transaction at PayPal.
758
        $transaction_url = $invoice->get_view_url();
759
    } else {
760
        $sandbox = wpinv_is_test_mode( 'paypal' ) ? '.sandbox' : '';
761
        $transaction_url = 'https://www' . $sandbox . '.paypal.com/cgi-bin/webscr?cmd=_view-a-trans&id=' . $transaction_id;
762
    }
763
764
    $transaction_link = '<a href="' . esc_url( $transaction_url ) . '" target="_blank">' . $transaction_id . '</a>';
765
766
    return apply_filters( 'wpinv_paypal_link_payment_details_transaction_id', $transaction_link, $invoice );
767
}
768
add_filter( 'wpinv_payment_details_transaction_id-paypal', 'wpinv_paypal_link_transaction_id', 10, 3 );
769
770
function wpinv_is_paypal_valid_for_use() {
771
    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' ) ) );
772
}
773
774
function wpinv_check_paypal_currency_support( $gateway_list ) {
775
    if ( isset( $gateway_list['paypal'] ) && ! wpinv_is_paypal_valid_for_use() ) {
776
        unset( $gateway_list['paypal'] );
777
    }
778
    return $gateway_list;
779
}
780
add_filter( 'wpinv_enabled_payment_gateways', 'wpinv_check_paypal_currency_support', 10, 1 );
781
782
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...
783
    return __( 'Proceed to PayPal', 'invoicing' );
784
}
785
add_filter( 'wpinv_gateway_paypal_button_label', 'wpinv_gateway_paypal_button_label', 10, 1 );