Passed
Pull Request — master (#111)
by Kiran
03:52
created

paypal.php ➔ wpinv_process_paypal_subscr_payment()   B

Complexity

Conditions 9
Paths 7

Size

Total Lines 56
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 32
nc 7
nop 1
dl 0
loc 56
rs 7.1584
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
// Exit if accessed directly
3
if ( ! defined( 'ABSPATH' ) ) exit;
4
5
add_action( 'wpinv_paypal_cc_form', '__return_false' );
6
add_filter( 'wpinv_paypal_support_subscription', '__return_true' );
7
8
function wpinv_process_paypal_payment( $purchase_data ) {
9
    if( ! wp_verify_nonce( $purchase_data['gateway_nonce'], 'wpi-gateway' ) ) {
10
        wp_die( __( 'Nonce verification has failed', 'invoicing' ), __( 'Error', 'invoicing' ), array( 'response' => 403 ) );
11
    }
12
13
    // Collect payment data
14
    $payment_data = array(
15
        'price'         => $purchase_data['price'],
16
        'date'          => $purchase_data['date'],
17
        'user_email'    => $purchase_data['user_email'],
18
        'invoice_key'   => $purchase_data['invoice_key'],
19
        'currency'      => wpinv_get_currency(),
20
        'items'         => $purchase_data['items'],
21
        'user_info'     => $purchase_data['user_info'],
22
        'cart_details'  => $purchase_data['cart_details'],
23
        'gateway'       => 'paypal',
24
        'status'        => 'wpi-pending'
25
    );
26
27
    // Record the pending payment
28
    $invoice = wpinv_get_invoice( $purchase_data['invoice_id'] );
29
30
    // Check payment
31
    if ( ! $invoice ) {
32
        // Record the error
33
        wpinv_record_gateway_error( __( 'Payment Error', 'invoicing' ), sprintf( __( 'Payment creation failed before sending buyer to PayPal. Payment data: %s', 'invoicing' ), json_encode( $payment_data ) ), $payment );
0 ignored issues
show
Bug introduced by
The variable $payment does not exist. Did you mean $payment_data?

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

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

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

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
36
    } else {
37
        // Only send to PayPal if the pending payment is created successfully
38
        $listener_url = wpinv_get_ipn_url( 'paypal' );
39
40
        // Get the success url
41
        $return_url = add_query_arg( array(
42
                'payment-confirm' => 'paypal',
43
                'invoice-id' => $invoice->ID
44
            ), get_permalink( wpinv_get_option( 'success_page', false ) ) );
45
46
        // Get the PayPal redirect uri
47
        $paypal_redirect = trailingslashit( wpinv_get_paypal_redirect() ) . '?';
48
49
        // Setup PayPal arguments
50
        $paypal_args = array(
51
            'business'      => wpinv_get_option( 'paypal_email', false ),
52
            'email'         => $invoice->get_email(),
53
            'first_name'    => $invoice->get_first_name(),
54
            'last_name'     => $invoice->get_last_name(),
55
            'invoice'       => $invoice->get_key(),
56
            'no_shipping'   => '1',
57
            'shipping'      => '0',
58
            'no_note'       => '1',
59
            'currency_code' => wpinv_get_currency(),
60
            'charset'       => get_bloginfo( 'charset' ),
61
            'custom'        => $invoice->ID,
62
            'rm'            => '2',
63
            'return'        => $return_url,
64
            'cancel_return' => wpinv_get_failed_transaction_uri( '?invoice-id=' . $invoice->ID ),
0 ignored issues
show
Documentation introduced by
'?invoice-id=' . $invoice->ID is of type string, but the function expects a boolean.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

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

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
264
		if ( false !== strpos( $key, 'amp;' ) ) {
265
			$new_key = str_replace( '&amp;', '&', $key );
266
			$new_key = str_replace( 'amp;', '&' , $new_key );
267
268
			unset( $encoded_data_array[ $key ] );
269
			$encoded_data_array[ $new_key ] = $value;
270
		}
271
	}
272
273
	// Get the PayPal redirect uri
274
	$paypal_redirect = wpinv_get_paypal_redirect( true );
0 ignored issues
show
Unused Code introduced by
$paypal_redirect is not used, you could remove the assignment.

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

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

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

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

Loading history...
275
276
	if ( !wpinv_get_option( 'disable_paypal_verification', false ) ) {
277
		// Validate the IPN
278
279
		$remote_post_vars      = array(
280
			'method'           => 'POST',
281
			'timeout'          => 45,
282
			'redirection'      => 5,
283
			'httpversion'      => '1.1',
284
			'blocking'         => true,
285
			'headers'          => array(
286
				'host'         => 'www.paypal.com',
287
				'connection'   => 'close',
288
				'content-type' => 'application/x-www-form-urlencoded',
289
				'post'         => '/cgi-bin/webscr HTTP/1.1',
290
291
			),
292
			'sslverify'        => false,
293
			'body'             => $encoded_data_array
294
		);
295
296
		// Get response
297
		$api_response = wp_remote_post( wpinv_get_paypal_redirect(), $remote_post_vars );
298
299
		if ( is_wp_error( $api_response ) ) {
300
			wpinv_record_gateway_error( __( 'IPN Error', 'invoicing' ), sprintf( __( 'Invalid IPN verification response. IPN data: %s', 'invoicing' ), json_encode( $api_response ) ) );
301
			return; // Something went wrong
302
		}
303
304
		if ( $api_response['body'] !== 'VERIFIED' && wpinv_get_option( 'disable_paypal_verification', false ) ) {
305
			wpinv_record_gateway_error( __( 'IPN Error', 'invoicing' ), sprintf( __( 'Invalid IPN verification response. IPN data: %s', 'invoicing' ), json_encode( $api_response ) ) );
306
			return; // Response not okay
307
		}
308
	}
309
310
	// Check if $post_data_array has been populated
311
	if ( !is_array( $encoded_data_array ) && !empty( $encoded_data_array ) )
312
		return;
313
314
	$defaults = array(
315
		'txn_type'       => '',
316
		'payment_status' => ''
317
	);
318
319
	$encoded_data_array = wp_parse_args( $encoded_data_array, $defaults );
320
321
	$invoice_id = isset( $encoded_data_array['custom'] ) ? absint( $encoded_data_array['custom'] ) : 0;
322
    
323
	wpinv_error_log( $encoded_data_array['txn_type'], 'PayPal txn_type', __FILE__, __LINE__ );
324
325
	if ( has_action( 'wpinv_paypal_' . $encoded_data_array['txn_type'] ) ) {
326
		// Allow PayPal IPN types to be processed separately
327
		do_action( 'wpinv_paypal_' . $encoded_data_array['txn_type'], $encoded_data_array, $invoice_id );
328
	} else {
329
		// Fallback to web accept just in case the txn_type isn't present
330
		do_action( 'wpinv_paypal_web_accept', $encoded_data_array, $invoice_id );
331
	}
332
	exit;
333
}
334
add_action( 'wpinv_verify_paypal_ipn', 'wpinv_process_paypal_ipn' );
335
336
function wpinv_process_paypal_web_accept_and_cart( $data, $invoice_id ) {
337
	if ( $data['txn_type'] != 'web_accept' && $data['txn_type'] != 'cart' && $data['payment_status'] != 'Refunded' ) {
338
		return;
339
	}
340
341
	if( empty( $invoice_id ) ) {
342
		return;
343
	}
344
345
	// Collect payment details
346
	$purchase_key   = isset( $data['invoice'] ) ? $data['invoice'] : $data['item_number'];
347
	$paypal_amount  = $data['mc_gross'];
348
	$payment_status = strtolower( $data['payment_status'] );
349
	$currency_code  = strtolower( $data['mc_currency'] );
350
	$business_email = isset( $data['business'] ) && is_email( $data['business'] ) ? trim( $data['business'] ) : trim( $data['receiver_email'] );
351
	$payment_meta   = wpinv_get_invoice_meta( $invoice_id );
352
353
	if ( wpinv_get_payment_gateway( $invoice_id ) != 'paypal' ) {
354
		return; // this isn't a PayPal standard IPN
355
	}
356
357
	// Verify payment recipient
358
	if ( strcasecmp( $business_email, trim( wpinv_get_option( 'paypal_email', false ) ) ) != 0 ) {
359
		wpinv_record_gateway_error( __( 'IPN Error', 'invoicing' ), sprintf( __( 'Invalid business email in IPN response. IPN data: %s', 'invoicing' ), json_encode( $data ) ), $invoice_id );
360
		wpinv_update_payment_status( $invoice_id, 'wpi-failed' );
361
		wpinv_insert_payment_note( $invoice_id, __( 'Payment failed due to invalid PayPal business email.', 'invoicing' ) );
362
		return;
363
	}
364
365
	// Verify payment currency
366 View Code Duplication
	if ( $currency_code != strtolower( $payment_meta['currency'] ) ) {
367
		wpinv_record_gateway_error( __( 'IPN Error', 'invoicing' ), sprintf( __( 'Invalid currency in IPN response. IPN data: %s', 'invoicing' ), json_encode( $data ) ), $invoice_id );
368
		wpinv_update_payment_status( $invoice_id, 'wpi-failed' );
369
		wpinv_insert_payment_note( $invoice_id, __( 'Payment failed due to invalid currency in PayPal IPN.', 'invoicing' ) );
370
		return;
371
	}
372
373
	if ( !wpinv_get_payment_user_email( $invoice_id ) ) {
374
		// This runs when a Buy Now purchase was made. It bypasses checkout so no personal info is collected until PayPal
375
		// No email associated with purchase, so store from PayPal
376
		wpinv_update_invoice_meta( $invoice_id, '_wpinv_email', $data['payer_email'] );
377
378
		// Setup and store the customer's details
379
		$user_info = array(
380
			'user_id'    => '-1',
381
			'email'      => sanitize_text_field( $data['payer_email'] ),
382
			'first_name' => sanitize_text_field( $data['first_name'] ),
383
			'last_name'  => sanitize_text_field( $data['last_name'] ),
384
			'discount'   => '',
385
		);
386
		$user_info['address'] = ! empty( $data['address_street']       ) ? sanitize_text_field( $data['address_street'] )       : false;
387
		$user_info['city']    = ! empty( $data['address_city']         ) ? sanitize_text_field( $data['address_city'] )         : false;
388
		$user_info['state']   = ! empty( $data['address_state']        ) ? sanitize_text_field( $data['address_state'] )        : false;
389
		$user_info['country'] = ! empty( $data['address_country_code'] ) ? sanitize_text_field( $data['address_country_code'] ) : false;
390
		$user_info['zip']     = ! empty( $data['address_zip']          ) ? sanitize_text_field( $data['address_zip'] )          : false;
391
392
		$payment_meta['user_info'] = $user_info;
393
		wpinv_update_invoice_meta( $invoice_id, '_wpinv_payment_meta', $payment_meta );
0 ignored issues
show
Documentation introduced by
$payment_meta is of type array<string,array<strin...g,?,{\"zip\":\"?\"}>"}>, but the function expects a string.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
394
	}
395
396
	if ( $payment_status == 'refunded' || $payment_status == 'reversed' ) {
397
		// Process a refund
398
		wpinv_process_paypal_refund( $data, $invoice_id );
399
	} else {
400
		if ( get_post_status( $invoice_id ) == 'publish' ) {
401
			return; // Only paid payments once
402
		}
403
404
		// Retrieve the total purchase amount (before PayPal)
405
		$payment_amount = wpinv_payment_total( $invoice_id );
406
407
		if ( number_format( (float) $paypal_amount, 2 ) < number_format( (float) $payment_amount, 2 ) ) {
408
			// The prices don't match
409
			wpinv_record_gateway_error( __( 'IPN Error', 'invoicing' ), sprintf( __( 'Invalid payment amount in IPN response. IPN data: %s', 'invoicing' ), json_encode( $data ) ), $invoice_id );
410
			wpinv_update_payment_status( $invoice_id, 'wpi-failed' );
411
			wpinv_insert_payment_note( $invoice_id, __( 'Payment failed due to invalid amount in PayPal IPN.', 'invoicing' ) );
412
			return;
413
		}
414 View Code Duplication
		if ( $purchase_key != wpinv_get_payment_key( $invoice_id ) ) {
415
			// Purchase keys don't match
416
			wpinv_record_gateway_error( __( 'IPN Error', 'invoicing' ), sprintf( __( 'Invalid purchase key in IPN response. IPN data: %s', 'invoicing' ), json_encode( $data ) ), $invoice_id );
417
			wpinv_update_payment_status( $invoice_id, 'wpi-failed' );
418
			wpinv_insert_payment_note( $invoice_id, __( 'Payment failed due to invalid purchase key in PayPal IPN.', 'invoicing' ) );
419
			return;
420
		}
421
422
		if ( 'complete' == $payment_status || 'completed' == $payment_status || 'processed' == $payment_status || wpinv_is_test_mode( 'paypal' ) ) {
423
			wpinv_insert_payment_note( $invoice_id, sprintf( __( 'PayPal Transaction ID: %s', 'invoicing' ) , $data['txn_id'] ) );
424
			wpinv_set_payment_transaction_id( $invoice_id, $data['txn_id'] );
425
			wpinv_update_payment_status( $invoice_id, 'publish' );
426
		} else if ( 'wpi-pending' == $payment_status && isset( $data['pending_reason'] ) ) {
427
			// Look for possible pending reasons, such as an echeck
428
			$note = '';
429
430
			switch( strtolower( $data['pending_reason'] ) ) {
431
				case 'echeck' :
432
					$note = __( 'Payment made via eCheck and will clear automatically in 5-8 days', 'invoicing' );
433
					break;
434
				
435
                case 'address' :
436
					$note = __( 'Payment requires a confirmed customer address and must be accepted manually through PayPal', 'invoicing' );
437
					break;
438
				
439
                case 'intl' :
440
					$note = __( 'Payment must be accepted manually through PayPal due to international account regulations', 'invoicing' );
441
					break;
442
				
443
                case 'multi-currency' :
444
					$note = __( 'Payment received in non-shop currency and must be accepted manually through PayPal', 'invoicing' );
445
					break;
446
				
447
                case 'paymentreview' :
448
                case 'regulatory_review' :
449
					$note = __( 'Payment is being reviewed by PayPal staff as high-risk or in possible violation of government regulations', 'invoicing' );
450
					break;
451
				
452
                case 'unilateral' :
453
					$note = __( 'Payment was sent to non-confirmed or non-registered email address.', 'invoicing' );
454
					break;
455
				
456
                case 'upgrade' :
457
					$note = __( 'PayPal account must be upgraded before this payment can be accepted', 'invoicing' );
458
					break;
459
				
460
                case 'verify' :
461
					$note = __( 'PayPal account is not verified. Verify account in order to accept this payment', 'invoicing' );
462
					break;
463
464
				case 'other' :
465
					$note = __( 'Payment is pending for unknown reasons. Contact PayPal support for assistance', 'invoicing' );
466
					break;
467
			}
468
469
			if ( ! empty( $note ) ) {
470
				wpinv_insert_payment_note( $invoice_id, $note );
471
			}
472
		} else {
473
			wpinv_insert_payment_note( $invoice_id, wp_sprintf( __( 'PayPal IPN has been received with invalid payment status: %s', 'invoicing' ), $payment_status ) );
474
		}
475
	}
476
}
477
add_action( 'wpinv_paypal_web_accept', 'wpinv_process_paypal_web_accept_and_cart', 10, 2 );
478
479
// Process PayPal subscription sign ups
480
add_action( 'wpinv_paypal_subscr_signup', 'wpinv_process_paypal_subscr_signup' );
481
482
// Process PayPal subscription payments
483
add_action( 'wpinv_paypal_subscr_payment', 'wpinv_process_paypal_subscr_payment' );
484
485
// Process PayPal subscription cancellations
486
add_action( 'wpinv_paypal_subscr_cancel', 'wpinv_process_paypal_subscr_cancel' );
487
488
// Process PayPal subscription end of term notices
489
add_action( 'wpinv_paypal_subscr_eot', 'wpinv_process_paypal_subscr_eot' );
490
491
// Process PayPal payment failed
492
add_action( 'wpinv_paypal_subscr_failed', 'wpinv_process_paypal_subscr_failed' );
493
494
495
/**
496
 * Process the subscription started IPN.
497
 */
498
function wpinv_process_paypal_subscr_signup( $ipn_data ) {
499
    $parent_invoice_id = absint( $ipn_data['custom'] );
500
    if( empty( $parent_invoice_id ) ) {
501
        return;
502
    }
503
504
    $invoice = wpinv_get_invoice( $parent_invoice_id );
505
    if ( empty( $invoice ) ) {
506
        return;
507
    }
508
509
    if ( $invoice->is_free_trial() && !empty( $ipn_data['invoice'] ) ) {
510
        wpinv_insert_payment_note( $parent_invoice_id, sprintf( __( 'PayPal Invoice ID: %s', 'invoicing' ) , $ipn_data['invoice'] ) );
511
        wpinv_set_payment_transaction_id( $parent_invoice_id, $ipn_data['invoice'] );
512
    }
513
    
514
    wpinv_update_payment_status( $parent_invoice_id, 'publish' );
515
    sleep(1);
516
    wpinv_insert_payment_note( $parent_invoice_id, sprintf( __( 'PayPal Subscription ID: %s', 'invoicing' ) , $ipn_data['subscr_id'] ) );
517
    
518
    $subscription = wpinv_get_paypal_subscription( $ipn_data );
519
    if ( false === $subscription ) {
520
        return;
521
    }
522
523
    $cart_details   = $invoice->cart_details;
524
525
    if ( !empty( $cart_details ) ) {
526
        foreach ( $cart_details as $cart_item ) {
527
            $item = new WPInv_Item( $cart_item['id'] );
528
            
529
            $status = $invoice->is_free_trial() && $item->has_free_trial() ? 'trialing' : 'active';
530
            
531
            $args = array(
532
                'item_id'           => $cart_item['id'],
533
                'status'            => $status,
534
                'period'            => $item->get_recurring_period(),
535
                'initial_amount'    => $invoice->get_total(),
536
                'recurring_amount'  => $invoice->get_recurring_details( 'total' ),
537
                'interval'          => $item->get_recurring_interval(),
538
                'bill_times'        => $item->get_recurring_limit(),
539
                'expiration'        => $invoice->get_new_expiration( $cart_item['id'] ),
540
                'profile_id'        => $ipn_data['subscr_id'],
541
                'created'           => date_i18n( 'Y-m-d H:i:s', strtotime( $ipn_data['subscr_date'] ) )
542
            );
543
            
544 View Code Duplication
            if ( $item->has_free_trial() ) {
545
                $args['trial_period']      = $item->get_trial_period();
546
                $args['trial_interval']    = $item->get_trial_interval();
547
            } else {
548
                $args['trial_period']      = '';
549
                $args['trial_interval']    = 0;
550
            }
551
            
552
553
            $subscription->update_subscription( $args );
554
        }
555
    }
556
}
557
558
/**
559
 * Process the subscription payment received IPN.
560
 */
561
function wpinv_process_paypal_subscr_payment( $ipn_data ) {
562
    $parent_invoice_id = absint( $ipn_data['custom'] );
563
    
564
    $subscription = wpinv_get_paypal_subscription( $ipn_data );
565
    if ( false === $subscription ) {
566
        return;
567
    }
568
569
    $parent_invoice = wpinv_get_invoice( $parent_invoice_id );
570
    if ( empty( $parent_invoice ) ) {
571
        return;
572
    }
573
574
    $transaction_id = wpinv_get_payment_transaction_id( $parent_invoice_id );
575
    $signup_date    = $subscription->get_subscription_created();
0 ignored issues
show
Unused Code introduced by
$signup_date 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...
576
    $payment_date   = $ipn_data['payment_date'];
0 ignored issues
show
Unused Code introduced by
$payment_date 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...
577
    $times_billed   = $parent_invoice->get_total_payments( false );
578
579
    // Look to see if payment is same day as signup and we have set the transaction ID on the parent payment yet.
580
    if ( empty( $times_billed ) && ( !$transaction_id || $transaction_id == $parent_invoice_id ) ) {
581
        wpinv_update_payment_status( $parent_invoice_id, 'publish' );
582
        sleep(1);
583
        
584
        // This is the very first payment
585
        wpinv_set_payment_transaction_id( $parent_invoice_id, $ipn_data['txn_id'] );
586
        wpinv_insert_payment_note( $parent_invoice_id, sprintf( __( 'PayPal Transaction ID: %s', 'invoicing' ) , $ipn_data['txn_id'] ) );
587
        return;
588
    }
589
    
590
    if ( wpinv_get_id_by_transaction_id( $ipn_data['txn_id'] ) ) {
591
        return; // Payment already recorded
592
    }
593
594
    $currency_code = strtolower( $ipn_data['mc_currency'] );
595
596
    // verify details
597
    if ( $currency_code != strtolower( wpinv_get_currency() ) ) {
598
        // the currency code is invalid
599
        wpinv_record_gateway_error( __( 'IPN Error', 'invoicing' ), sprintf( __( 'Invalid currency in IPN response. IPN data: ', 'invoicing' ), json_encode( $ipn_data ) ) );
600
        return;
601
    }
602
603
    $args = array(
604
        'amount'         => $ipn_data['mc_gross'],
605
        'transaction_id' => $ipn_data['txn_id']
606
    );
607
    
608
    $invoice = wpinv_recurring_add_subscription_payment( $parent_invoice_id, $args );
609
    
610
    if ( !empty( $invoice ) ) {
611
        sleep(1);
612
        wpinv_insert_payment_note( $invoice->ID, sprintf( __( 'PayPal Transaction ID: %s', 'invoicing' ) , $ipn_data['txn_id'] ) );
613
614
        $invoice->renew_subscription();
615
    }
616
}
617
618
/**
619
 * Process the subscription canceled IPN.
620
 */
621
function wpinv_process_paypal_subscr_cancel( $ipn_data ) {
622
    $subscription = wpinv_get_paypal_subscription( $ipn_data );
623
624
    if( false === $subscription ) {
625
        return;
626
    }
627
628
    $subscription->cancel_subscription();
629
}
630
631
/**
632
 * Process the subscription expired IPN.
633
 */
634
function wpinv_process_paypal_subscr_eot( $ipn_data ) {
635
    $subscription = wpinv_get_paypal_subscription( $ipn_data );
636
637
    if( false === $subscription ) {
638
        return;
639
    }
640
641
    $subscription->complete_subscription();
642
}
643
644
/**
645
 * Process the subscription payment failed IPN.
646
 */
647
function wpinv_process_paypal_subscr_failed( $ipn_data ) {
648
    $subscription = wpinv_get_paypal_subscription( $ipn_data );
649
650
    if( false === $subscription ) {
651
        return;
652
    }
653
654
    $subscription->failing_subscription();
655
656
    do_action( 'wpinv_recurring_payment_failed', $subscription );
657
}
658
659
/**
660
 * Retrieve the subscription this IPN notice is for.
661
 */
662
function wpinv_get_paypal_subscription( $ipn_data = array() ) {
663
    $parent_invoice_id = absint( $ipn_data['custom'] );
664
665
    if( empty( $parent_invoice_id ) ) {
666
        return false;
667
    }
668
669
    $invoice = wpinv_get_invoice( $parent_invoice_id );
670
    if ( empty( $invoice ) ) {
671
        return false;
672
    }
673
674
    $subscription = wpinv_get_subscription( $ipn_data['subscr_id'], true );
675
676
    if ( empty( $subscription ) ) {
677
        $subs         = wpinv_get_subscriptions( array( 'parent_invoice_id' => $parent_invoice_id, 'numberposts' => 1 ) );
678
        $subscription = reset( $subs );
679
680
        if ( $subscription && $subscription->ID > 0 ) {
681
            // Update the profile ID so it is set for future renewals
682
            $subscription->update_subscription( array( 'profile_id' => sanitize_text_field( $ipn_data['subscr_id'] ) ) );
683
        } else {
684
            $subscription = $invoice;
685
            $subscription->update_subscription( array( 'profile_id' => sanitize_text_field( $ipn_data['subscr_id'] ) ) );
686
            // No subscription found with a matching payment ID, bail
687
            //return false;
688
        }
689
    }
690
691
    return $subscription;
692
693
}
694
695
function wpinv_process_paypal_refund( $data, $invoice_id = 0 ) {
696
	// Collect payment details
697
698
	if( empty( $invoice_id ) ) {
699
		return;
700
	}
701
702
	if ( get_post_status( $invoice_id ) == 'wpi-refunded' ) {
703
		return; // Only refund payments once
704
	}
705
706
	$payment_amount = wpinv_payment_total( $invoice_id );
707
	$refund_amount  = $data['mc_gross'] * -1;
708
709
	if ( number_format( (float) $refund_amount, 2 ) < number_format( (float) $payment_amount, 2 ) ) {
710
		wpinv_insert_payment_note( $invoice_id, sprintf( __( 'Partial PayPal refund processed: %s', 'invoicing' ), $data['parent_txn_id'] ) );
711
		return; // This is a partial refund
712
	}
713
714
	wpinv_insert_payment_note( $invoice_id, sprintf( __( 'PayPal Payment #%s Refunded for reason: %s', 'invoicing' ), $data['parent_txn_id'], $data['reason_code'] ) );
715
	wpinv_insert_payment_note( $invoice_id, sprintf( __( 'PayPal Refund Transaction ID: %s', 'invoicing' ), $data['txn_id'] ) );
716
	wpinv_update_payment_status( $invoice_id, 'wpi-refunded' );
717
}
718
719
function wpinv_get_paypal_redirect( $ssl_check = false ) {
720
    if ( is_ssl() || ! $ssl_check ) {
721
        $protocol = 'https://';
722
    } else {
723
        $protocol = 'http://';
724
    }
725
726
    // Check the current payment mode
727
    if ( wpinv_is_test_mode( 'paypal' ) ) {
728
        // Test mode
729
        $paypal_uri = $protocol . 'www.sandbox.paypal.com/cgi-bin/webscr';
730
    } else {
731
        // Live mode
732
        $paypal_uri = $protocol . 'www.paypal.com/cgi-bin/webscr';
733
    }
734
735
    return apply_filters( 'wpinv_paypal_uri', $paypal_uri );
736
}
737
738
function wpinv_paypal_success_page_content( $content ) {
739
    global $wpi_invoice;
740
    
741
    $session = wpinv_get_checkout_session();
742
743
    if ( empty( $_GET['invoice-id'] ) && empty( $session['invoice_key'] )  ) {
744
        return $content;
745
    }
746
747
    $invoice_id = !empty( $_GET['invoice-id'] ) ? absint( $_GET['invoice-id'] ) : wpinv_get_invoice_id_by_key( $session['invoice_key'] );
748
749
    if ( empty(  $invoice_id ) ) {
750
        return $content;
751
    }
752
753
    $wpi_invoice = wpinv_get_invoice( $invoice_id );
754
    
755
    if ( !empty( $wpi_invoice ) && 'wpi-pending' == $wpi_invoice->status ) {
756
        // Payment is still pending so show processing indicator to fix the Race Condition, issue #
757
        ob_start();
758
        wpinv_get_template_part( 'wpinv-payment-processing' );
759
        $content = ob_get_clean();
760
    }
761
762
    return $content;
763
}
764
add_filter( 'wpinv_payment_confirm_paypal', 'wpinv_paypal_success_page_content' );
765
766
function wpinv_paypal_get_transaction_id( $invoice_id ) {
767
    $transaction_id = '';
768
    $notes = wpinv_get_invoice_notes( $invoice_id );
769
770
    foreach ( $notes as $note ) {
771
        if ( preg_match( '/^PayPal Transaction ID: ([^\s]+)/', $note->comment_content, $match ) ) {
772
            $transaction_id = $match[1];
773
            continue;
774
        }
775
    }
776
777
    return apply_filters( 'wpinv_paypal_set_transaction_id', $transaction_id, $invoice_id );
778
}
779
add_filter( 'wpinv_payment_get_transaction_id-paypal', 'wpinv_paypal_get_transaction_id', 10, 1 );
780
781
function wpinv_paypal_link_transaction_id( $transaction_id, $invoice_id, $invoice ) {
782
    if ( $invoice->is_free_trial() || $transaction_id == $invoice_id ) { // Free trial does not have transaction at PayPal.
783
        $transaction_url = $invoice->get_view_url();
784
    } else {
785
        $sandbox = wpinv_is_test_mode( 'paypal' ) ? '.sandbox' : '';
786
        $transaction_url = 'https://www' . $sandbox . '.paypal.com/cgi-bin/webscr?cmd=_view-a-trans&id=' . $transaction_id;
787
    }
788
789
    $transaction_link = '<a href="' . esc_url( $transaction_url ) . '" target="_blank">' . $transaction_id . '</a>';
790
791
    return apply_filters( 'wpinv_paypal_link_payment_details_transaction_id', $transaction_link, $invoice );
792
}
793
add_filter( 'wpinv_payment_details_transaction_id-paypal', 'wpinv_paypal_link_transaction_id', 10, 3 );
794
795
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...
796
    return __( 'Proceed to PayPal', 'invoicing' );
797
}
798
add_filter( 'wpinv_gateway_paypal_button_label', 'wpinv_gateway_paypal_button_label', 10, 1 );