Passed
Pull Request — master (#116)
by
unknown
03:57
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 ) ), $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( print_r($encoded_data_array, true), '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 ( 'wpi-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 );
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
        wpinv_set_payment_transaction_id( $parent_invoice_id, $ipn_data['txn_id'] );
513
    }
514
515
    $subscription = wpinv_get_paypal_subscription( $ipn_data );
516
    if ( false === $subscription ) {
517
        return;
518
    }
519
    
520
    wpinv_update_payment_status( $parent_invoice_id, 'publish' );
521
    sleep(1);
522
    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...
523
    update_post_meta($parent_invoice_id,'_wpinv_subscr_profile_id', $ipn_data['subscr_id']);
524
525
    $status = 'trialling' == $subscription->status ? 'trialling' : 'active';
526
    // Retrieve pending subscription from database and update it's status to active and set proper profile ID
527
    $subscription->update( array( 'profile_id' => $ipn_data['subscr_id'], 'status' => $status ) );
528
}
529
530
/**
531
 * Process the subscription payment received IPN.
532
 */
533
function wpinv_process_paypal_subscr_payment( $ipn_data ) {
534
535
    $parent_invoice_id = absint( $ipn_data['custom'] );
536
    $parent_invoice = wpinv_get_invoice( $parent_invoice_id );
537
    if ( empty( $parent_invoice ) ) {
538
        return;
539
    }
540
541
    $subscription = wpinv_get_paypal_subscription( $ipn_data );
542
    if ( false === $subscription ) {
543
        return;
544
    }
545
546
    $transaction_id = wpinv_get_payment_transaction_id( $parent_invoice_id );
547
    $times_billed   = $subscription->get_times_billed();
548
549
    // Look to see if payment is same day as signup and we have set the transaction ID on the parent payment yet.
550
    if ( empty( $times_billed ) && ( !$transaction_id || $transaction_id == $parent_invoice_id ) ) {
551
        wpinv_update_payment_status( $parent_invoice_id, 'publish' );
552
        sleep(1);
553
        
554
        // This is the very first payment
555
        wpinv_set_payment_transaction_id( $parent_invoice_id, $ipn_data['txn_id'] );
556
        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...
557
        return;
558
    }
559
    
560
    if ( wpinv_get_id_by_transaction_id( $ipn_data['txn_id'] ) ) {
561
        return; // Payment already recorded
562
    }
563
564
    $currency_code = strtolower( $ipn_data['mc_currency'] );
565
566
    // verify details
567
    if ( $currency_code != strtolower( wpinv_get_currency() ) ) {
568
        // the currency code is invalid
569
        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...
570
        return;
571
    }
572
573
    $args = array(
574
        'amount'         => $ipn_data['mc_gross'],
575
        'transaction_id' => $ipn_data['txn_id'],
576
        'gateway'        => 'paypal'
577
    );
578
579
    $invoice_id = $subscription->add_payment( $args );
580
581
    if ( $invoice_id > 0 ) {
582
        wpinv_update_payment_status( $invoice_id, 'publish' );
583
        sleep(1);
584
        wpinv_update_payment_status( $invoice_id, 'wpi-renewal' );
585
        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...
586
587
        $subscription->renew();
588
    }
589
590
}
591
592
/**
593
 * Process the subscription canceled IPN.
594
 */
595
function wpinv_process_paypal_subscr_cancel( $ipn_data ) {
596
    $subscription = wpinv_get_paypal_subscription( $ipn_data );
597
598
    if( false === $subscription ) {
599
        return;
600
    }
601
602
    $subscription->cancel();
603
}
604
605
/**
606
 * Process the subscription expired IPN.
607
 */
608
function wpinv_process_paypal_subscr_eot( $ipn_data ) {
609
    $subscription = wpinv_get_paypal_subscription( $ipn_data );
610
611
    if( false === $subscription ) {
612
        return;
613
    }
614
615
    $subscription->complete();
616
}
617
618
/**
619
 * Process the subscription payment failed IPN.
620
 */
621
function wpinv_process_paypal_subscr_failed( $ipn_data ) {
622
    $subscription = wpinv_get_paypal_subscription( $ipn_data );
623
624
    if( false === $subscription ) {
625
        return;
626
    }
627
628
    $subscription->failing();
629
630
    do_action( 'wpinv_recurring_payment_failed', $subscription );
631
}
632
633
/**
634
 * Retrieve the subscription this IPN notice is for.
635
 */
636
function wpinv_get_paypal_subscription( $ipn_data = array() ) {
637
    $parent_invoice_id = absint( $ipn_data['custom'] );
638
639
    if( empty( $parent_invoice_id ) ) {
640
        return false;
641
    }
642
643
    $invoice = wpinv_get_invoice( $parent_invoice_id );
644
    if ( empty( $invoice ) ) {
645
        return false;
646
    }
647
648
    $subscription = new WPInv_Subscription( $ipn_data['subscr_id'], true );
649
650 View Code Duplication
    if( ! $subscription || $subscription->id < 1 ) {
651
652
        $subs_db      = new WPInv_Subscriptions_DB;
653
        $subs         = $subs_db->get_subscriptions( array( 'parent_payment_id' => $parent_invoice_id, 'number' => 1 ) );
654
        $subscription = reset( $subs );
655
656
        if( $subscription && $subscription->id > 0 ) {
657
658
            // Update the profile ID so it is set for future renewals
659
            $subscription->update( array( 'profile_id' => sanitize_text_field( $ipn_data['subscr_id'] ) ) );
660
661
        } else {
662
663
            // No subscription found with a matching payment ID, bail
664
            return false;
665
666
        }
667
668
    }
669
670
    return $subscription;
671
672
}
673
674
function wpinv_process_paypal_refund( $data, $invoice_id = 0 ) {
675
	// Collect payment details
676
677
	if( empty( $invoice_id ) ) {
678
		return;
679
	}
680
681
	if ( get_post_status( $invoice_id ) == 'wpi-refunded' ) {
682
		return; // Only refund payments once
683
	}
684
685
	$payment_amount = wpinv_payment_total( $invoice_id );
686
	$refund_amount  = $data['mc_gross'] * -1;
687
688
	if ( number_format( (float) $refund_amount, 2 ) < number_format( (float) $payment_amount, 2 ) ) {
689
		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...
690
		return; // This is a partial refund
691
	}
692
693
	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...
694
	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...
695
	wpinv_update_payment_status( $invoice_id, 'wpi-refunded' );
696
}
697
698
function wpinv_get_paypal_redirect( $ssl_check = false ) {
699
    if ( is_ssl() || ! $ssl_check ) {
700
        $protocol = 'https://';
701
    } else {
702
        $protocol = 'http://';
703
    }
704
705
    // Check the current payment mode
706
    if ( wpinv_is_test_mode( 'paypal' ) ) {
707
        // Test mode
708
        $paypal_uri = $protocol . 'www.sandbox.paypal.com/cgi-bin/webscr';
709
    } else {
710
        // Live mode
711
        $paypal_uri = $protocol . 'www.paypal.com/cgi-bin/webscr';
712
    }
713
714
    return apply_filters( 'wpinv_paypal_uri', $paypal_uri );
715
}
716
717
function wpinv_paypal_success_page_content( $content ) {
718
    global $wpi_invoice;
719
    
720
    $session = wpinv_get_checkout_session();
721
722
    if ( empty( $_GET['invoice-id'] ) && empty( $session['invoice_key'] )  ) {
723
        return $content;
724
    }
725
726
    $invoice_id = !empty( $_GET['invoice-id'] ) ? absint( $_GET['invoice-id'] ) : wpinv_get_invoice_id_by_key( $session['invoice_key'] );
727
728
    if ( empty(  $invoice_id ) ) {
729
        return $content;
730
    }
731
732
    $wpi_invoice = wpinv_get_invoice( $invoice_id );
733
    
734
    if ( !empty( $wpi_invoice ) && 'wpi-pending' == $wpi_invoice->status ) {
735
        // Payment is still pending so show processing indicator to fix the Race Condition, issue #
736
        ob_start();
737
        wpinv_get_template_part( 'wpinv-payment-processing' );
738
        $content = ob_get_clean();
739
    }
740
741
    return $content;
742
}
743
add_filter( 'wpinv_payment_confirm_paypal', 'wpinv_paypal_success_page_content' );
744
745
function wpinv_paypal_get_transaction_id( $invoice_id ) {
746
    $transaction_id = '';
747
    $notes = wpinv_get_invoice_notes( $invoice_id );
748
749
    foreach ( $notes as $note ) {
750
        if ( preg_match( '/^PayPal Transaction ID: ([^\s]+)/', $note->comment_content, $match ) ) {
751
            $transaction_id = $match[1];
752
            continue;
753
        }
754
    }
755
756
    return apply_filters( 'wpinv_paypal_set_transaction_id', $transaction_id, $invoice_id );
757
}
758
add_filter( 'wpinv_payment_get_transaction_id-paypal', 'wpinv_paypal_get_transaction_id', 10, 1 );
759
760
function wpinv_paypal_link_transaction_id( $transaction_id, $invoice_id, $invoice ) {
761
    if ( $invoice->is_free_trial() || $transaction_id == $invoice_id ) { // Free trial does not have transaction at PayPal.
762
        $transaction_url = $invoice->get_view_url();
763
    } else {
764
        $sandbox = wpinv_is_test_mode( 'paypal' ) ? '.sandbox' : '';
765
        $transaction_url = 'https://www' . $sandbox . '.paypal.com/cgi-bin/webscr?cmd=_view-a-trans&id=' . $transaction_id;
766
    }
767
768
    $transaction_link = '<a href="' . esc_url( $transaction_url ) . '" target="_blank">' . $transaction_id . '</a>';
769
770
    return apply_filters( 'wpinv_paypal_link_payment_details_transaction_id', $transaction_link, $invoice );
771
}
772
add_filter( 'wpinv_payment_details_transaction_id-paypal', 'wpinv_paypal_link_transaction_id', 10, 3 );
773
774
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...
775
    return __( 'Proceed to PayPal', 'invoicing' );
776
}
777
add_filter( 'wpinv_gateway_paypal_button_label', 'wpinv_gateway_paypal_button_label', 10, 1 );