Issues (850)

Security Analysis    4 potential vulnerabilities

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Code Injection (1)
Code Injection enables an attacker to execute arbitrary code on the server.
  Variable Injection (2)
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Cross-Site Scripting (1)
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  Header Injection
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

includes/gateways/class-getpaid-paypal-gateway.php (9 issues)

1
<?php
2
/**
3
 * Paypal payment gateway
4
 *
5
 */
6
7
defined( 'ABSPATH' ) || exit;
8
9
/**
10
 * Paypal Payment Gateway class.
11
 *
12
 */
13
class GetPaid_Paypal_Gateway extends GetPaid_Payment_Gateway {
14
15
    /**
16
	 * Payment method id.
17
	 *
18
	 * @var string
19
	 */
20
    public $id = 'paypal';
21
22
    /**
23
	 * An array of features that this gateway supports.
24
	 *
25
	 * @var array
26
	 */
27
    protected $supports = array( 'subscription', 'sandbox', 'single_subscription_group', 'refunds' );
28
29
    /**
30
	 * Payment method order.
31
	 *
32
	 * @var int
33
	 */
34
    public $order = 1;
35
36
    /**
37
	 * Stores line items to send to PayPal.
38
	 *
39
	 * @var array
40
	 */
41
    protected $line_items = array();
42
43
    /**
44
	 * Endpoint for requests from PayPal.
45
	 *
46
	 * @var string
47
	 */
48
	protected $notify_url;
49
50
	/**
51
	 * Endpoint for requests to PayPal.
52
	 *
53
	 * @var string
54
	 */
55
    protected $endpoint;
56
57
    /**
58
	 * Currencies this gateway is allowed for.
59
	 *
60
	 * @var array
61
	 */
62
	public $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' );
63
64
    /**
65
	 * URL to view a transaction.
66
	 *
67
	 * @var string
68
	 */
69
    public $view_transaction_url = 'https://www.{sandbox}paypal.com/activity/payment/%s';
70
71
    /**
72
	 * URL to view a subscription.
73
	 *
74
	 * @var string
75
	 */
76
	public $view_subscription_url = 'https://www.{sandbox}paypal.com/cgi-bin/webscr?cmd=_profile-recurring-payments&encrypted_profile_id=%s';
77
78
    /**
79
	 * Class constructor.
80
	 */
81
	public function __construct() {
82
		parent::__construct();
83
84
        $this->title                = __( 'PayPal Standard', 'invoicing' );
85
        $this->method_title         = __( 'PayPal Standard', 'invoicing' );
86
        $this->checkout_button_text = __( 'Proceed to PayPal', 'invoicing' );
87
        $this->notify_url           = wpinv_get_ipn_url( $this->id );
88
89
		add_filter( 'wpinv_subscription_cancel_url', array( $this, 'filter_cancel_subscription_url' ), 10, 2 );
90
		add_filter( 'getpaid_paypal_args', array( $this, 'process_subscription' ), 10, 2 );
91
		add_filter( 'getpaid_get_paypal_connect_url', array( $this, 'maybe_get_connect_url' ), 10, 2 );
92
		add_action( 'getpaid_authenticated_admin_action_connect_paypal', array( $this, 'connect_paypal' ) );
93
		add_action( 'wpinv_paypal_connect', array( $this, 'display_connect_buttons' ) );
94
95
		if ( $this->enabled ) {
96
			add_filter( 'getpaid_paypal_sandbox_notice', array( $this, 'sandbox_notice' ) );
97
			add_action( 'getpaid_paypal_subscription_cancelled', array( $this, 'subscription_cancelled' ) );
98
			add_action( 'getpaid_delete_subscription', array( $this, 'subscription_cancelled' ) );
99
			add_action( 'getpaid_refund_invoice_remotely', array( $this, 'refund_invoice' ) );
100
		}
101
    }
102
103
    /**
104
	 * Process Payment.
105
	 *
106
	 *
107
	 * @param WPInv_Invoice $invoice Invoice.
108
	 * @param array $submission_data Posted checkout fields.
109
	 * @param GetPaid_Payment_Form_Submission $submission Checkout submission.
110
	 * @return array
111
	 */
112
	public function process_payment( $invoice, $submission_data, $submission ) {
113
114
        // Get redirect url.
115
        $paypal_redirect = $this->get_request_url( $invoice );
116
117
        // Add a note about the request url.
118
        $invoice->add_note(
119
            sprintf(
120
                __( 'Redirecting to PayPal: %s', 'invoicing' ),
121
                esc_url( $paypal_redirect )
122
            ),
123
            false,
124
            false,
125
            true
126
        );
127
128
        // Redirect to PayPal
129
        wp_redirect( $paypal_redirect );
130
        exit;
0 ignored issues
show
Using exit here is not recommended.

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

Loading history...
131
132
    }
133
134
    /**
135
	 * Get the PayPal request URL for an invoice.
136
	 *
137
	 * @param  WPInv_Invoice $invoice Invoice object.
138
	 * @return string
139
	 */
140
	public function get_request_url( $invoice ) {
141
142
        // Endpoint for this request
143
		$this->endpoint    = $this->is_sandbox( $invoice ) ? 'https://www.sandbox.paypal.com/cgi-bin/webscr?test_ipn=1&' : 'https://www.paypal.com/cgi-bin/webscr?';
144
145
        // Retrieve paypal args.
146
        $paypal_args       = map_deep( $this->get_paypal_args( $invoice ), 'urlencode' );
147
148
        if ( $invoice->is_recurring() ) {
149
            $paypal_args['bn'] = 'GetPaid_Subscribe_WPS_US';
150
        } else {
151
            $paypal_args['bn'] = 'GetPaid_ShoppingCart_WPS_US';
152
        }
153
154
        return add_query_arg( $paypal_args, $this->endpoint );
155
156
	}
157
158
    /**
159
	 * Get PayPal Args for passing to PP.
160
	 *
161
	 * @param  WPInv_Invoice $invoice Invoice object.
162
	 * @return array
163
	 */
164
	protected function get_paypal_args( $invoice ) {
165
166
        // Whether or not to send the line items as one item.
167
		$force_one_line_item = apply_filters( 'getpaid_paypal_force_one_line_item', true, $invoice );
168
169
		if ( $invoice->is_recurring() || ( wpinv_use_taxes() && wpinv_prices_include_tax() ) ) {
170
			$force_one_line_item = true;
171
		}
172
173
		$paypal_args = apply_filters(
174
			'getpaid_paypal_args',
175
			array_merge(
176
				$this->get_transaction_args( $invoice ),
177
				$this->get_line_item_args( $invoice, $force_one_line_item )
178
			),
179
			$invoice
180
		);
181
182
		return $this->fix_request_length( $invoice, $paypal_args );
183
    }
184
185
    /**
186
	 * Get transaction args for paypal request.
187
	 *
188
	 * @param WPInv_Invoice $invoice Invoice object.
189
	 * @return array
190
	 */
191
	protected function get_transaction_args( $invoice ) {
192
193
		$email = $this->is_sandbox( $invoice ) ? wpinv_get_option( 'paypal_sandbox_email', wpinv_get_option( 'paypal_email', '' ) ) : wpinv_get_option( 'paypal_email', '' );
194
		return array(
195
            'cmd'           => '_cart',
196
            'business'      => $email,
197
            'no_shipping'   => '1',
198
            'shipping'      => '0',
199
            'no_note'       => '1',
200
            'charset'       => 'utf-8',
201
            'rm'            => is_ssl() ? 2 : 1,
202
            'upload'        => 1,
203
            'currency_code' => $invoice->get_currency(), // https://developer.paypal.com/docs/nvp-soap-api/currency-codes/#paypal
204
            'return'        => esc_url_raw( $this->get_return_url( $invoice ) ),
205
            'cancel_return' => esc_url_raw( $invoice->get_checkout_payment_url() ),
206
            'notify_url'    => getpaid_limit_length( $this->notify_url, 255 ),
207
            'invoice'       => getpaid_limit_length( $invoice->get_number(), 127 ),
208
            'custom'        => $invoice->get_id(),
209
            'first_name'    => getpaid_limit_length( $invoice->get_first_name(), 32 ),
210
            'last_name'     => getpaid_limit_length( $invoice->get_last_name(), 64 ),
211
            'country'       => getpaid_limit_length( $invoice->get_country(), 2 ),
212
            'email'         => getpaid_limit_length( $invoice->get_email(), 127 ),
213
            'cbt'           => get_bloginfo( 'name' ),
214
        );
215
216
    }
217
218
    /**
219
	 * Get line item args for paypal request.
220
	 *
221
	 * @param  WPInv_Invoice $invoice Invoice object.
222
	 * @param  bool     $force_one_line_item Create only one item for this invoice.
223
	 * @return array
224
	 */
225
	protected function get_line_item_args( $invoice, $force_one_line_item = false ) {
226
227
        // Maybe send invoice as a single item.
228
		if ( $force_one_line_item ) {
229
            return $this->get_line_item_args_single_item( $invoice );
230
        }
231
232
        // Send each line item individually.
233
        $line_item_args = array();
234
235
        // Prepare line items.
236
        $this->prepare_line_items( $invoice );
237
238
        // Add taxes to the cart
239
        if ( wpinv_use_taxes() && $invoice->is_taxable() ) {
240
            $line_item_args['tax_cart'] = wpinv_sanitize_amount( (float) $invoice->get_total_tax(), 2 );
0 ignored issues
show
The call to wpinv_sanitize_amount() has too many arguments starting with 2. ( Ignorable by Annotation )

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

240
            $line_item_args['tax_cart'] = /** @scrutinizer ignore-call */ wpinv_sanitize_amount( (float) $invoice->get_total_tax(), 2 );

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. Please note the @ignore annotation hint above.

Loading history...
241
        }
242
243
        // Add discount.
244
        if ( $invoice->get_total_discount() > 0 ) {
245
            $line_item_args['discount_amount_cart'] = wpinv_sanitize_amount( (float) $invoice->get_total_discount(), 2 );
246
        }
247
248
		return array_merge( $line_item_args, $this->get_line_items() );
249
250
    }
251
252
    /**
253
	 * Get line item args for paypal request as a single line item.
254
	 *
255
	 * @param  WPInv_Invoice $invoice Invoice object.
256
	 * @return array
257
	 */
258
	protected function get_line_item_args_single_item( $invoice ) {
259
		$this->delete_line_items();
260
261
        $item_name = wp_sprintf( __( 'Invoice %s', 'invoicing' ), $invoice->get_number() );
262
		$this->add_line_item( $item_name, 1, wpinv_round_amount( (float) $invoice->get_total(), 2, true ), $invoice->get_id() );
0 ignored issues
show
It seems like wpinv_round_amount((doub...->get_total(), 2, true) can also be of type string; however, parameter $amount of GetPaid_Paypal_Gateway::add_line_item() does only seem to accept double, maybe add an additional type check? ( Ignorable by Annotation )

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

262
		$this->add_line_item( $item_name, 1, /** @scrutinizer ignore-type */ wpinv_round_amount( (float) $invoice->get_total(), 2, true ), $invoice->get_id() );
Loading history...
263
264
		return $this->get_line_items();
265
    }
266
267
    /**
268
	 * Return all line items.
269
	 */
270
	protected function get_line_items() {
271
		return $this->line_items;
272
	}
273
274
    /**
275
	 * Remove all line items.
276
	 */
277
	protected function delete_line_items() {
278
		$this->line_items = array();
279
    }
280
281
    /**
282
	 * Prepare line items to send to paypal.
283
	 *
284
	 * @param  WPInv_Invoice $invoice Invoice object.
285
	 */
286
	protected function prepare_line_items( $invoice ) {
287
		$this->delete_line_items();
288
289
		// Items.
290
		foreach ( $invoice->get_items() as $item ) {
291
			$amount   = $item->get_price();
292
			$quantity = $invoice->get_template() == 'amount' ? 1 : $item->get_quantity();
293
			$this->add_line_item( $item->get_raw_name(), $quantity, $amount, $item->get_id() );
294
        }
295
296
        // Fees.
297
		foreach ( $invoice->get_fees() as $fee => $data ) {
298
            $this->add_line_item( $fee, 1, wpinv_sanitize_amount( $data['initial_fee'] ) );
299
        }
300
301
    }
302
303
    /**
304
	 * Add PayPal Line Item.
305
	 *
306
	 * @param  string $item_name Item name.
307
	 * @param  float    $quantity Item quantity.
308
	 * @param  float  $amount Amount.
309
	 * @param  string $item_number Item number.
310
	 */
311
	protected function add_line_item( $item_name, $quantity = 1, $amount = 0.0, $item_number = '' ) {
312
		$index = ( count( $this->line_items ) / 4 ) + 1;
313
314
		/**
315
		 * Prevent error "Things don't appear to be working at the moment. (https://www.sandbox.paypal.com/webapps/hermes/error)"
316
		 */
317
		$item_name = str_replace( "#", "", $item_name );
318
319
		$item = apply_filters(
320
			'getpaid_paypal_line_item',
321
			array(
322
				'item_name'   => html_entity_decode( getpaid_limit_length( $item_name ? wp_strip_all_tags( $item_name ) : __( 'Item', 'invoicing' ), 127 ), ENT_NOQUOTES, 'UTF-8' ),
323
				'quantity'    => (float) $quantity,
324
				'amount'      => wpinv_sanitize_amount( (float) $amount, 2 ),
0 ignored issues
show
The call to wpinv_sanitize_amount() has too many arguments starting with 2. ( Ignorable by Annotation )

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

324
				'amount'      => /** @scrutinizer ignore-call */ wpinv_sanitize_amount( (float) $amount, 2 ),

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. Please note the @ignore annotation hint above.

Loading history...
325
				'item_number' => $item_number,
326
			),
327
			$item_name,
328
			$quantity,
329
			$amount,
330
			$item_number
331
		);
332
333
		$this->line_items[ 'item_name_' . $index ]   = getpaid_limit_length( $item['item_name'], 127 );
334
        $this->line_items[ 'quantity_' . $index ]    = $item['quantity'];
335
336
        // The price or amount of the product, service, or contribution, not including shipping, handling, or tax.
337
		$this->line_items[ 'amount_' . $index ]      = $item['amount'] * $item['quantity'];
338
		$this->line_items[ 'item_number_' . $index ] = getpaid_limit_length( $item['item_number'], 127 );
339
    }
340
341
    /**
342
	 * If the default request with line items is too long, generate a new one with only one line item.
343
	 *
344
	 * https://support.microsoft.com/en-us/help/208427/maximum-url-length-is-2-083-characters-in-internet-explorer.
345
	 *
346
	 * @param WPInv_Invoice $invoice Invoice to be sent to Paypal.
347
	 * @param array    $paypal_args Arguments sent to Paypal in the request.
348
	 * @return array
349
	 */
350
	protected function fix_request_length( $invoice, $paypal_args ) {
351
		$max_paypal_length = 2083;
352
		$query_candidate   = http_build_query( $paypal_args, '', '&' );
353
354
		if ( strlen( $this->endpoint . $query_candidate ) <= $max_paypal_length ) {
355
			return $paypal_args;
356
		}
357
358
		return apply_filters(
359
			'getpaid_paypal_args',
360
			array_merge(
361
				$this->get_transaction_args( $invoice ),
362
				$this->get_line_item_args( $invoice, true )
363
			),
364
			$invoice
365
		);
366
367
    }
368
369
    /**
370
	 * Processes recurring invoices.
371
	 *
372
	 * @param  array $paypal_args PayPal args.
373
	 * @param  WPInv_Invoice    $invoice Invoice object.
374
	 */
375
	public function process_subscription( $paypal_args, $invoice ) {
376
377
        // Make sure this is a subscription.
378
        if ( ! $invoice->is_recurring() || ! $subscription = getpaid_get_invoice_subscription( $invoice ) ) {
379
            return $paypal_args;
380
        }
381
382
        // It's a subscription
383
        $paypal_args['cmd'] = '_xclick-subscriptions';
384
385
        // Subscription name.
386
        $paypal_args['item_name'] = wp_sprintf( __( 'Invoice %s', 'invoicing' ), $invoice->get_number() );
387
388
        // Get subscription args.
389
        $period                 = strtoupper( substr( $subscription->get_period(), 0, 1 ) );
390
        $interval               = (int) $subscription->get_frequency();
391
        $bill_times             = (int) $subscription->get_bill_times();
392
        $initial_amount         = (float) wpinv_sanitize_amount( $invoice->get_initial_total(), 2 );
0 ignored issues
show
The call to wpinv_sanitize_amount() has too many arguments starting with 2. ( Ignorable by Annotation )

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

392
        $initial_amount         = (float) /** @scrutinizer ignore-call */ wpinv_sanitize_amount( $invoice->get_initial_total(), 2 );

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. Please note the @ignore annotation hint above.

Loading history...
393
        $recurring_amount       = (float) wpinv_sanitize_amount( $invoice->get_recurring_total(), 2 );
394
        $subscription_item      = $invoice->get_recurring( true );
395
396
		// Convert 365 days to 1 year.
397
		if ( 'D' == $period && 365 == $interval ) {
398
			$period = 'Y';
399
			$interval = 1;
400
		}
401
402
        if ( $subscription_item->has_free_trial() ) {
403
404
            $paypal_args['a1'] = 0 == $initial_amount ? 0 : $initial_amount;
405
406
			// Trial period length.
407
			$paypal_args['p1'] = $subscription_item->get_trial_interval();
408
409
			// Trial period.
410
			$paypal_args['t1'] = $subscription_item->get_trial_period();
411
412
        } elseif ( $initial_amount != $recurring_amount ) {
413
414
            // No trial period, but initial amount includes a sign-up fee and/or other items, so charge it as a separate period.
415
416
            if ( 1 == $bill_times ) {
417
                $param_number = 3;
418
            } else {
419
                $param_number = 1;
420
            }
421
422
            $paypal_args[ 'a' . $param_number ] = $initial_amount ? $initial_amount : 0;
423
424
            // Sign Up interval
425
            $paypal_args[ 'p' . $param_number ] = $interval;
426
427
            // Sign Up unit of duration
428
            $paypal_args[ 't' . $param_number ] = $period;
429
430
        }
431
432
        // We have a recurring payment
433
		if ( ! isset( $param_number ) || 1 == $param_number ) {
434
435
			// Subscription price
436
			$paypal_args['a3'] = $recurring_amount;
437
438
			// Subscription duration
439
			$paypal_args['p3'] = $interval;
440
441
			// Subscription period
442
			$paypal_args['t3'] = $period;
443
444
        }
445
446
        // Recurring payments
447
		if ( 1 == $bill_times || ( $initial_amount != $recurring_amount && ! $subscription_item->has_free_trial() && 2 == $bill_times ) ) {
448
449
			// Non-recurring payments
450
			$paypal_args['src'] = 0;
451
452
		} else {
453
454
			$paypal_args['src'] = 1;
455
456
			if ( $bill_times > 0 ) {
457
458
				// An initial period is being used to charge a sign-up fee
459
				if ( $initial_amount != $recurring_amount && ! $subscription_item->has_free_trial() ) {
460
					$bill_times--;
461
				}
462
463
                // Make sure it's not over the max of 52
464
                $paypal_args['srt'] = ( $bill_times <= 52 ? absint( $bill_times ) : 52 );
465
466
			}
467
        }
468
469
        // Force return URL so that order description & instructions display
470
        $paypal_args['rm'] = 2;
471
472
        // Get rid of redudant items.
473
        foreach ( array( 'item_name_1', 'quantity_1', 'amount_1', 'item_number_1' ) as $arg ) {
474
475
            if ( isset( $paypal_args[ $arg ] ) ) {
476
                unset( $paypal_args[ $arg ] );
477
            }
478
		}
479
480
        return apply_filters(
481
			'getpaid_paypal_subscription_args',
482
			$paypal_args,
483
			$invoice
484
        );
485
486
    }
487
488
	/**
489
	 * Refunds an invoice remotely.
490
	 * 
491
	 * @since 2.8.24
492
	 * @param WPInv_Invoice $invoice Invoice object.
493
	 */
494
	public function refund_invoice( $invoice ) {
495
496
		if ( $invoice->get_gateway() !== $this->id ) {
497
			return;
498
		}
499
500
		$mode	= $this->is_sandbox( $invoice ) ? 'sandbox' : 'live';
501
		$result = GetPaid_PayPal_API::refund_capture( $invoice->get_transaction_id(), array(), $mode );
502
503
		if ( is_wp_error( $result ) ) {
504
			$invoice->add_system_note(
505
				sprintf(
506
					// translators: %s is the error message.
507
					__( 'An error occured while trying to refund invoice #%1$s in PayPal: %2$s', 'invoicing' ),
508
					$invoice->get_id(),
509
					$result->get_error_message()
510
				)
511
			);
512
		} else {
513
			$invoice->add_system_note(
514
				sprintf(
515
					// translators: %s is the refund ID.
516
					__( 'Successfully refunded invoice #%1$s in PayPal. Refund ID: %2$s', 'invoicing' ),
517
					$invoice->get_id(),
518
					$result->id
0 ignored issues
show
The property id does not seem to exist on WP_Error.
Loading history...
519
				)
520
			);
521
		}
522
	}
523
524
	/**
525
	 * Cancels a subscription remotely.
526
	 * 
527
	 * @since 2.8.24
528
	 * @param WPInv_Subscription $subscription Subscription object.
529
	 */
530
	public function subscription_cancelled( $subscription ) {
531
532
		if ( $subscription->get_gateway() != $this->id ) {
533
			return;
534
		}
535
536
		$invoice = $subscription->get_parent_invoice();
537
538
		// Abort if the parent invoice does not exist.
539
		if ( ! $invoice->exists() ) {
540
			return;
541
		}
542
543
		$mode	= $this->is_sandbox( $invoice ) ? 'sandbox' : 'live';
544
		$result = GetPaid_PayPal_API::cancel_subscription( 
545
			$invoice->get_remote_subscription_id(), 
546
			array(
547
				'reason' => __(' Customer requested cancellation', 'invoicing' ),
548
			), 
549
			$mode 
550
		);
551
552
		if ( is_wp_error( $result ) ) {
553
554
			$error = sprintf(
555
				// translators: %s is the subscription ID.
556
				__( 'An error occured while trying to cancel subscription #%s in PayPal.', 'invoicing' ),
557
				$subscription->get_id()
558
			);
559
560
			getpaid_admin()->show_error( $error . ' ' . $result->get_error_message() );
561
562
			if ( ! is_admin() ) {
563
				wpinv_set_error( $result->get_error_code(), $error );
564
			}
565
566
			return;
567
		}
568
569
		if ( is_admin() ) {
570
			getpaid_admin()->show_success(
571
				sprintf(
572
					// translators: %s is the subscription ID.
573
					__( 'Successfully cancelled subscription #%s in PayPal.', 'invoicing' ),
574
					$subscription->get_id()
575
				)
576
			);
577
		}
578
579
	}
580
581
    /**
582
	 * Processes ipns and marks payments as complete.
583
	 *
584
	 * @return void
585
	 */
586
	public function verify_ipn() {
587
        new GetPaid_Paypal_Gateway_IPN_Handler( $this );
588
    }
589
590
    /**
591
     * Returns a sandbox notice.
592
     */
593
    public function sandbox_notice() {
594
595
        return sprintf(
596
			__( 'SANDBOX ENABLED. You can use sandbox testing accounts only. See the %1$sPayPal Sandbox Testing Guide%2$s for more details.', 'invoicing' ),
597
			'<a href="https://developer.paypal.com/docs/classic/lifecycle/ug_sandbox/">',
598
			'</a>'
599
		);
600
601
    }
602
603
	/**
604
	 * Filters the gateway settings.
605
	 *
606
	 * @param array $admin_settings
607
	 */
608
	public function admin_settings( $admin_settings ) {
609
610
        $currencies = sprintf(
611
            __( 'Supported Currencies: %s', 'invoicing' ),
612
            implode( ', ', $this->currencies )
613
        );
614
615
        $admin_settings['paypal_active']['desc'] .= " ($currencies)";
616
        $admin_settings['paypal_desc']['std']     = __( 'Pay via PayPal: you can pay with your credit card if you don\'t have a PayPal account.', 'invoicing' );
617
618
		// Access tokens.
619
		$live_email      = wpinv_get_option( 'paypal_email' );
0 ignored issues
show
The assignment to $live_email is dead and can be removed.
Loading history...
620
		$sandbox_email   = wpinv_get_option( 'paypal_sandbox_email' );
0 ignored issues
show
The assignment to $sandbox_email is dead and can be removed.
Loading history...
621
622
		$admin_settings['paypal_connect'] = array(
623
			'type' => 'hook',
624
			'id'   => 'paypal_connect',
625
			'name' => __( 'Connect to PayPal', 'invoicing' ),
626
		);
627
628
        $admin_settings['paypal_email'] = array(
629
            'type'  => 'text',
630
			'class' => 'live-auth-data',
631
            'id'    => 'paypal_email',
632
            'name'  => __( 'Live Email Address', 'invoicing' ),
633
            'desc'  => __( 'The email address of your PayPal account.', 'invoicing' ),
634
        );
635
636
		$admin_settings['paypal_sandbox_email'] = array(
637
            'type'  => 'text',
638
			'class' => 'sandbox-auth-data',
639
            'id'    => 'paypal_sandbox_email',
640
            'name'  => __( 'Sandbox Email Address', 'invoicing' ),
641
            'desc'  => __( 'The email address of your sandbox PayPal account.', 'invoicing' ),
642
			'std'   => wpinv_get_option( 'paypal_email', '' ),
643
        );
644
645
		// Client ID and secret.
646
		$admin_settings['paypal_client_id'] = array(
647
			'type'  => 'text',
648
			'class' => 'live-auth-data',
649
			'id'    => 'paypal_client_id',
650
			'name'  => __( 'Live Client ID', 'invoicing' ),
651
			'desc'  => __( 'The client ID of your PayPal account. You can retrieve this from your PayPal developer account.', 'invoicing' ),
652
		);
653
654
		$admin_settings['paypal_sandbox_client_id'] = array(
655
			'type'  => 'text',
656
			'class' => 'sandbox-auth-data',
657
			'id'    => 'paypal_sandbox_client_id',
658
			'name'  => __( 'Sandbox Client ID', 'invoicing' ),
659
			'desc'  => __( 'The client ID of your sandbox PayPal account. You can retrieve this from your PayPal developer account.', 'invoicing' ),
660
			'std'   => wpinv_get_option( 'paypal_client_id', '' ),
661
		);
662
663
		$admin_settings['paypal_secret'] = array(
664
			'type'  => 'text',
665
			'class' => 'live-auth-data',
666
			'id'    => 'paypal_secret',
667
			'name'  => __( 'Live Secret', 'invoicing' ),
668
			'desc'  => __( 'The secret of your PayPal account. You can retrieve this from your PayPal developer account.', 'invoicing' ),
669
		);
670
671
		$admin_settings['paypal_sandbox_secret'] = array(
672
			'type'  => 'text',
673
			'class' => 'sandbox-auth-data',
674
			'id'    => 'paypal_sandbox_secret',
675
			'name'  => __( 'Sandbox Secret', 'invoicing' ),
676
			'desc'  => __( 'The secret of your sandbox PayPal account. You can retrieve this from your PayPal developer account.', 'invoicing' ),
677
		);
678
679
        $admin_settings['paypal_ipn_url'] = array(
680
            'type'     => 'ipn_url',
681
            'id'       => 'paypal_ipn_url',
682
            'name'     => __( 'IPN Url', 'invoicing' ),
683
            'std'      => $this->notify_url,
684
            'desc'     => __( "If you've not enabled IPNs in your paypal account, use the above URL to enable them.", 'invoicing' ) . ' <a href="https://developer.paypal.com/docs/api-basics/notifications/ipn/"><em>' . __( 'Learn more.', 'invoicing' ) . '</em></a>',
685
            'readonly' => true,
686
        );
687
688
		return $admin_settings;
689
	}
690
691
	/**
692
	 * Retrieves the URL to cancel a subscription.
693
	 *
694
	 * @param string $url
695
	 * @param WPInv_Subscription $subscription
696
	 */
697
	public function filter_cancel_subscription_url( $url, $subscription ) {
698
699
		if ( $this->id !== $subscription->get_gateway() ) {
700
			return $url;
701
		}
702
703
		// Get the PayPal profile ID.
704
		$profile_id = $subscription->get_profile_id();
705
706
		// Bail if no profile ID.
707
		if ( empty( $profile_id ) ) {
708
			return $url;
709
		}
710
711
		$cancel_url = 'https://www.paypal.com/myaccount/autopay/connect/%s/cancel';
712
		if ( $this->is_sandbox( $subscription->get_parent_payment() ) ) {
713
			$cancel_url = 'https://www.sandbox.paypal.com/myaccount/autopay/connect/%s/cancel';
714
		}
715
716
		return sprintf( $cancel_url, $profile_id );
717
	}
718
719
	/**
720
	 * Retrieves the PayPal connect URL when using the setup wizzard.
721
	 *
722
	 *
723
     * @param array $data
724
     * @return string
725
	 */
726
	public static function maybe_get_connect_url( $url = '', $data = array() ) {
727
		return self::get_connect_url( false, urldecode( $data['redirect'] ) );
728
	}
729
730
	/**
731
	 * Retrieves the PayPal connect URL.
732
	 *
733
	 *
734
     * @param bool $is_sandbox
735
	 * @param string $redirect
736
     * @return string
737
	 */
738
	public static function get_connect_url( $is_sandbox, $redirect = '' ) {
739
740
        $redirect_url = add_query_arg(
741
            array(
742
                'getpaid-admin-action' => 'connect_paypal',
743
                'page'                 => 'wpinv-settings',
744
                'live_mode'            => (int) empty( $is_sandbox ),
745
                'tab'                  => 'gateways',
746
                'section'              => 'paypal',
747
                'getpaid-nonce'        => wp_create_nonce( 'getpaid-nonce' ),
748
				'redirect'             => urlencode( $redirect ),
749
            ),
750
            admin_url( 'admin.php' )
751
        );
752
753
        return add_query_arg(
754
            array(
755
                'live_mode'    => (int) empty( $is_sandbox ),
756
                'redirect_url' => urlencode( str_replace( '&amp;', '&', $redirect_url ) ),
757
            ),
758
            'https://ayecode.io/oauth/paypal'
759
        );
760
761
    }
762
763
	/**
764
	 * Generates settings page js.
765
	 *
766
     * @return void
767
	 */
768
	public static function display_connect_buttons() {
769
770
        ?>
771
			<div class="wpinv-paypal-connect-live">
772
				<a class="button button-primary" href="<?php echo esc_url( self::get_connect_url( false ) ); ?>"><?php esc_html_e( 'Connect to PayPal', 'invoicing' ); ?></a>
773
			</div>
774
			<div class="wpinv-paypal-connect-sandbox">
775
				<a class="button button-primary" href="<?php echo esc_url( self::get_connect_url( true ) ); ?>"><?php esc_html_e( 'Connect to PayPal Sandbox', 'invoicing' ); ?></a>
776
			</div>
777
778
            <script>
779
                jQuery(document).ready(function() {
780
781
                    jQuery( '#wpinv-settings-paypal_sandbox' ).on ( 'change', function( e ) {
782
783
						jQuery( '.wpinv-paypal-connect-live, .live-auth-data' ).toggle( ! this.checked )
784
						jQuery( '.wpinv-paypal-connect-sandbox, .sandbox-auth-data' ).toggle( this.checked )
785
786
						if ( this.checked ) {
787
788
							if ( jQuery('#wpinv-settings-paypal_sandbox_email').val().length > 0 ) {
789
								jQuery('.wpinv-paypal-connect-sandbox').closest('tr').hide()
790
							} else {
791
								jQuery('.wpinv-paypal-connect-sandbox').closest('tr').show()
792
							}
793
						} else {
794
							if ( jQuery('#wpinv-settings-paypal_email').val().length > 0 ) {
795
								jQuery('.wpinv-paypal-connect-live').closest('tr').hide()
796
							} else {
797
								jQuery('.wpinv-paypal-connect-live').closest('tr').show()
798
							}
799
						}
800
                    })
801
802
                    // Set initial state.
803
                    jQuery( '#wpinv-settings-paypal_sandbox' ).trigger( 'change' )
804
805
                });
806
            </script>
807
        <?php
808
    }
809
810
	/**
811
	 * Connects to PayPal.
812
	 *
813
	 * @param array $data Connection data.
814
	 * @return void
815
	 */
816
	public function connect_paypal( $data ) {
817
818
		$sandbox      = $this->is_sandbox();
819
		$data         = wp_unslash( $data );
820
		$access_token = empty( $data['access_token'] ) ? '' : sanitize_text_field( $data['access_token'] );
821
822
		if ( isset( $data['live_mode'] ) ) {
823
			$sandbox = empty( $data['live_mode'] );
824
		}
825
826
		wpinv_update_option( 'paypal_sandbox', (int) $sandbox );
827
		wpinv_update_option( 'paypal_active', 1 );
828
829
		if ( ! empty( $data['error_description'] ) ) {
830
			getpaid_admin()->show_error( wp_kses_post( urldecode( $data['error_description'] ) ) );
831
		} else {
832
833
			// Retrieve the user info.
834
			$user_info = wp_remote_get(
835
				! $sandbox ? 'https://api-m.paypal.com/v1/identity/oauth2/userinfo?schema=paypalv1.1' : 'https://api-m.sandbox.paypal.com/v1/identity/oauth2/userinfo?schema=paypalv1.1',
836
				array(
837
838
					'headers' => array(
839
						'Authorization' => 'Bearer ' . $access_token,
840
						'Content-type'  => 'application/json',
841
					),
842
843
				)
844
			);
845
846
			if ( is_wp_error( $user_info ) ) {
847
				getpaid_admin()->show_error( wp_kses_post( $user_info->get_error_message() ) );
848
			} else {
849
850
				// Create application.
851
				$user_info = json_decode( wp_remote_retrieve_body( $user_info ) );
852
853
				if ( $sandbox ) {
854
					wpinv_update_option( 'paypal_sandbox_email', sanitize_email( $user_info->emails[0]->value ) );
855
					wpinv_update_option( 'paypal_sandbox_refresh_token', sanitize_text_field( urldecode( $data['refresh_token'] ) ) );
856
					set_transient( 'getpaid_paypal_sandbox_access_token', sanitize_text_field( urldecode( $data['access_token'] ) ), (int) $data['expires_in'] );
857
					getpaid_admin()->show_success( __( 'Successfully connected your PayPal sandbox account', 'invoicing' ) );
858
				} else {
859
					wpinv_update_option( 'paypal_email', sanitize_email( $user_info->emails[0]->value ) );
860
					wpinv_update_option( 'paypal_refresh_token', sanitize_text_field( urldecode( $data['refresh_token'] ) ) );
861
					set_transient( 'getpaid_paypal_access_token', sanitize_text_field( urldecode( $data['access_token'] ) ), (int) $data['expires_in'] );
862
					getpaid_admin()->show_success( __( 'Successfully connected your PayPal account', 'invoicing' ) );
863
				}
864
			}
865
		}
866
867
		$redirect = empty( $data['redirect'] ) ? admin_url( 'admin.php?page=wpinv-settings&tab=gateways&section=paypal' ) : urldecode( $data['redirect'] );
868
869
		if ( isset( $data['step'] ) ) {
870
			$redirect = add_query_arg( 'step', $data['step'], $redirect );
871
		}
872
		wp_redirect( $redirect );
873
		exit;
0 ignored issues
show
Using exit here is not recommended.

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

Loading history...
874
	}
875
876
}
877