GetPaid_Paypal_Gateway::verify_ipn()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 2
rs 10
c 0
b 0
f 0
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
Best Practice introduced by
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
Unused Code introduced by
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
Bug introduced by
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
Unused Code introduced by
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
Unused Code introduced by
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
Bug introduced by
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
Unused Code introduced by
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
Unused Code introduced by
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
Best Practice introduced by
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