Passed
Push — master ( c5e685...72070d )
by Brian
15:09
created

GetPaid_Paypal_Gateway::get_line_items()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
c 1
b 0
f 0
dl 0
loc 2
rs 10
cc 1
nc 1
nop 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' );
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
83
        $this->title                = __( 'PayPal Standard', 'invoicing' );
84
        $this->method_title         = __( 'PayPal Standard', 'invoicing' );
85
        $this->checkout_button_text = __( 'Proceed to PayPal', 'invoicing' );
86
        $this->notify_url           = wpinv_get_ipn_url( $this->id );
87
88
        add_filter( 'getpaid_paypal_args', array( $this, 'process_subscription' ), 10, 2 );
89
        add_filter( 'wpinv_gateway_description', array( $this, 'sandbox_notice' ), 10, 2 );
90
91
        parent::__construct();
92
    }
93
94
    /**
95
	 * Process Payment.
96
	 *
97
	 *
98
	 * @param WPInv_Invoice $invoice Invoice.
99
	 * @param array $submission_data Posted checkout fields.
100
	 * @param GetPaid_Payment_Form_Submission $submission Checkout submission.
101
	 * @return array
102
	 */
103
	public function process_payment( $invoice, $submission_data, $submission ) {
104
105
        // Get redirect url.
106
        $paypal_redirect = $this->get_request_url( $invoice );
107
108
        // Add a note about the request url.
109
        $invoice->add_note(
110
            sprintf(
111
                __( 'Redirecting to PayPal: %s', 'invoicing' ),
112
                esc_url( $paypal_redirect )
113
            ),
114
            false,
115
            false,
116
            true
117
        );
118
119
        // Redirect to PayPal
120
        wp_redirect( $paypal_redirect );
121
        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...
122
123
    }
124
125
    /**
126
	 * Get the PayPal request URL for an invoice.
127
	 *
128
	 * @param  WPInv_Invoice $invoice Invoice object.
129
	 * @return string
130
	 */
131
	public function get_request_url( $invoice ) {
132
133
        // Endpoint for this request
134
		$this->endpoint    = $this->is_sandbox( $invoice ) ? 'https://www.sandbox.paypal.com/cgi-bin/webscr?test_ipn=1&' : 'https://www.paypal.com/cgi-bin/webscr?';
135
136
        // Retrieve paypal args.
137
        $paypal_args       = map_deep( $this->get_paypal_args( $invoice ), 'urlencode' );
138
139
        if ( $invoice->is_recurring() ) {
140
            $paypal_args['bn'] = 'GetPaid_Subscribe_WPS_US';
141
        } else {
142
            $paypal_args['bn'] = 'GetPaid_ShoppingCart_WPS_US';
143
        }
144
145
        return add_query_arg( $paypal_args, $this->endpoint );
146
147
	}
148
149
    /**
150
	 * Get PayPal Args for passing to PP.
151
	 *
152
	 * @param  WPInv_Invoice $invoice Invoice object.
153
	 * @return array
154
	 */
155
	protected function get_paypal_args( $invoice ) {
156
157
        // Whether or not to send the line items as one item.
158
		$force_one_line_item = apply_filters( 'getpaid_paypal_force_one_line_item', false, $invoice );
159
160
		if ( $invoice->is_recurring() || ( wpinv_use_taxes() && wpinv_prices_include_tax() ) ) {
161
			$force_one_line_item = true;
162
		}
163
164
		$paypal_args = apply_filters(
165
			'getpaid_paypal_args',
166
			array_merge(
167
				$this->get_transaction_args( $invoice ),
168
				$this->get_line_item_args( $invoice, $force_one_line_item )
169
			),
170
			$invoice
171
		);
172
173
		return $this->fix_request_length( $invoice, $paypal_args );
174
    }
175
176
    /**
177
	 * Get transaction args for paypal request.
178
	 *
179
	 * @param WPInv_Invoice $invoice Invoice object.
180
	 * @return array
181
	 */
182
	protected function get_transaction_args( $invoice ) {
183
184
		return array(
185
            'cmd'           => '_cart',
186
            'business'      => wpinv_get_option( 'paypal_email', false ),
187
            'no_shipping'   => '1',
188
            'shipping'      => '0',
189
            'no_note'       => '1',
190
            'charset'       => 'utf-8',
191
            'rm'            => is_ssl() ? 2 : 1,
192
            'upload'        => 1,
193
            'currency_code' => $invoice->get_currency(), // https://developer.paypal.com/docs/nvp-soap-api/currency-codes/#paypal
194
            'return'        => esc_url_raw( $this->get_return_url( $invoice ) ),
195
            'cancel_return' => esc_url_raw( $invoice->get_checkout_payment_url() ),
196
            'notify_url'    => getpaid_limit_length( $this->notify_url, 255 ),
197
            'invoice'       => getpaid_limit_length( $invoice->get_number(), 127 ),
198
            'custom'        => $invoice->get_id(),
199
            'first_name'    => getpaid_limit_length( $invoice->get_first_name(), 32 ),
200
            'last_name'     => getpaid_limit_length( $invoice->get_last_name(), 64 ),
201
            'country'       => getpaid_limit_length( $invoice->get_country(), 2 ),
202
            'email'         => getpaid_limit_length( $invoice->get_email(), 127 ),
203
            'cbt'           => get_bloginfo( 'name' )
204
        );
205
206
    }
207
208
    /**
209
	 * Get line item args for paypal request.
210
	 *
211
	 * @param  WPInv_Invoice $invoice Invoice object.
212
	 * @param  bool     $force_one_line_item Create only one item for this invoice.
213
	 * @return array
214
	 */
215
	protected function get_line_item_args( $invoice, $force_one_line_item = false ) {
216
217
        // Maybe send invoice as a single item.
218
		if ( $force_one_line_item ) {
219
            return $this->get_line_item_args_single_item( $invoice );
220
        }
221
222
        // Send each line item individually.
223
        $line_item_args = array();
224
225
        // Prepare line items.
226
        $this->prepare_line_items( $invoice );
227
228
        // Add taxes to the cart
229
        if ( wpinv_use_taxes() && $invoice->is_taxable() ) {
230
            $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

230
            $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...
231
        }
232
233
        // Add discount.
234
        if ( $invoice->get_total_discount() > 0 ) {
235
            $line_item_args['discount_amount_cart'] = wpinv_sanitize_amount( (float) $invoice->get_total_discount(), 2 );
236
        }
237
238
		return array_merge( $line_item_args, $this->get_line_items() );
239
240
    }
241
242
    /**
243
	 * Get line item args for paypal request as a single line item.
244
	 *
245
	 * @param  WPInv_Invoice $invoice Invoice object.
246
	 * @return array
247
	 */
248
	protected function get_line_item_args_single_item( $invoice ) {
249
		$this->delete_line_items();
250
251
        $item_name = sprintf( __( 'Invoice #%s', 'invoicing' ), $invoice->get_number() );
252
		$this->add_line_item( $item_name, 1, wpinv_sanitize_amount( (float) $invoice->get_total(), 2 ), $invoice->get_id() );
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

252
		$this->add_line_item( $item_name, 1, /** @scrutinizer ignore-call */ wpinv_sanitize_amount( (float) $invoice->get_total(), 2 ), $invoice->get_id() );

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...
253
254
		return $this->get_line_items();
255
    }
256
257
    /**
258
	 * Return all line items.
259
	 */
260
	protected function get_line_items() {
261
		return $this->line_items;
262
	}
263
264
    /**
265
	 * Remove all line items.
266
	 */
267
	protected function delete_line_items() {
268
		$this->line_items = array();
269
    }
270
271
    /**
272
	 * Prepare line items to send to paypal.
273
	 *
274
	 * @param  WPInv_Invoice $invoice Invoice object.
275
	 */
276
	protected function prepare_line_items( $invoice ) {
277
		$this->delete_line_items();
278
279
		// Items.
280
		foreach ( $invoice->get_items() as $item ) {
281
			$amount   = $invoice->get_template() == 'amount' ? $item->get_price() : $item->get_sub_total();
282
			$quantity = $invoice->get_template() == 'amount' ? 1 : $item->get_quantity();
283
			$this->add_line_item( $item->get_raw_name(), $quantity, $amount, $item->get_id() );
284
        }
285
286
        // Fees.
287
		foreach ( $invoice->get_fees() as $fee => $data ) {
288
            $this->add_line_item( $fee, 1, wpinv_sanitize_amount( $data['initial_fee'] ) );
289
        }
290
291
    }
292
293
    /**
294
	 * Add PayPal Line Item.
295
	 *
296
	 * @param  string $item_name Item name.
297
	 * @param  int    $quantity Item quantity.
298
	 * @param  float  $amount Amount.
299
	 * @param  string $item_number Item number.
300
	 */
301
	protected function add_line_item( $item_name, $quantity = 1, $amount = 0.0, $item_number = '' ) {
302
		$index = ( count( $this->line_items ) / 4 ) + 1;
303
304
		$item = apply_filters(
305
			'getpaid_paypal_line_item',
306
			array(
307
				'item_name'   => html_entity_decode( getpaid_limit_length( $item_name ? wp_strip_all_tags( $item_name ) : __( 'Item', 'invoicing' ), 127 ), ENT_NOQUOTES, 'UTF-8' ),
308
				'quantity'    => (int) $quantity,
309
				'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

309
				'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...
310
				'item_number' => $item_number,
311
			),
312
			$item_name,
313
			$quantity,
314
			$amount,
315
			$item_number
316
		);
317
318
		$this->line_items[ 'item_name_' . $index ]   = getpaid_limit_length( $item['item_name'], 127 );
319
        $this->line_items[ 'quantity_' . $index ]    = $item['quantity'];
320
        
321
        // The price or amount of the product, service, or contribution, not including shipping, handling, or tax.
322
		$this->line_items[ 'amount_' . $index ]      = $item['amount'];
323
		$this->line_items[ 'item_number_' . $index ] = getpaid_limit_length( $item['item_number'], 127 );
324
    }
325
326
    /**
327
	 * If the default request with line items is too long, generate a new one with only one line item.
328
	 *
329
	 * https://support.microsoft.com/en-us/help/208427/maximum-url-length-is-2-083-characters-in-internet-explorer.
330
	 *
331
	 * @param WPInv_Invoice $invoice Invoice to be sent to Paypal.
332
	 * @param array    $paypal_args Arguments sent to Paypal in the request.
333
	 * @return array
334
	 */
335
	protected function fix_request_length( $invoice, $paypal_args ) {
336
		$max_paypal_length = 2083;
337
		$query_candidate   = http_build_query( $paypal_args, '', '&' );
338
339
		if ( strlen( $this->endpoint . $query_candidate ) <= $max_paypal_length ) {
340
			return $paypal_args;
341
		}
342
343
		return apply_filters(
344
			'getpaid_paypal_args',
345
			array_merge(
346
				$this->get_transaction_args( $invoice ),
347
				$this->get_line_item_args( $invoice, true )
348
			),
349
			$invoice
350
		);
351
352
    }
353
    
354
    /**
355
	 * Processes recurring invoices.
356
	 *
357
	 * @param  array $paypal_args PayPal args.
358
	 * @param  WPInv_Invoice    $invoice Invoice object.
359
	 */
360
	public function process_subscription( $paypal_args, $invoice ) {
361
362
        // Make sure this is a subscription.
363
        if ( ! $invoice->is_recurring() || ! $subscription = wpinv_get_subscription( $invoice ) ) {
364
            return $paypal_args;
365
        }
366
367
        // It's a subscription
368
        $paypal_args['cmd'] = '_xclick-subscriptions';
369
370
        // Subscription name.
371
        $paypal_args['item_name'] = sprintf( __( 'Invoice #%s', 'invoicing' ), $invoice->get_number() );
372
373
        // Get subscription args.
374
        $period                 = strtoupper( substr( $subscription->get_period(), 0, 1) );
375
        $interval               = (int) $subscription->get_frequency();
376
        $bill_times             = (int) $subscription->get_bill_times();
377
        $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

377
        $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...
378
        $recurring_amount       = (float) wpinv_sanitize_amount( $invoice->get_recurring_total(), 2 );
379
        $subscription_item      = $invoice->get_recurring( true );
380
381
        if ( $subscription_item->has_free_trial() ) {
382
383
            $paypal_args['a1'] = 0 == $initial_amount ? 0 : $initial_amount;
384
385
			// Trial period length.
386
			$paypal_args['p1'] = $subscription_item->get_trial_interval();
387
388
			// Trial period.
389
			$paypal_args['t1'] = $subscription_item->get_trial_period();
390
391
        } else if ( $initial_amount != $recurring_amount ) {
392
393
            // No trial period, but initial amount includes a sign-up fee and/or other items, so charge it as a separate period.
394
395
            if ( 1 == $bill_times ) {
396
                $param_number = 3;
397
            } else {
398
                $param_number = 1;
399
            }
400
401
            $paypal_args[ 'a' . $param_number ] = $initial_amount ? 0 : $initial_amount;
402
403
            // Sign Up interval
404
            $paypal_args[ 'p' . $param_number ] = $interval;
405
406
            // Sign Up unit of duration
407
            $paypal_args[ 't' . $param_number ] = $period;
408
409
        }
410
411
        // We have a recurring payment
412
		if ( ! isset( $param_number ) || 1 == $param_number ) {
413
414
			// Subscription price
415
			$paypal_args['a3'] = $recurring_amount;
416
417
			// Subscription duration
418
			$paypal_args['p3'] = $interval;
419
420
			// Subscription period
421
			$paypal_args['t3'] = $period;
422
423
        }
424
        
425
        // Recurring payments
426
		if ( 1 == $bill_times || ( $initial_amount != $recurring_amount && ! $subscription_item->has_free_trial() && 2 == $bill_times ) ) {
427
428
			// Non-recurring payments
429
			$paypal_args['src'] = 0;
430
431
		} else {
432
433
			$paypal_args['src'] = 1;
434
435
			if ( $bill_times > 0 ) {
436
437
				// An initial period is being used to charge a sign-up fee
438
				if ( $initial_amount != $recurring_amount && ! $subscription_item->has_free_trial() ) {
439
					$bill_times--;
440
				}
441
442
                // Make sure it's not over the max of 52
443
                $paypal_args['srt'] = ( $bill_times <= 52 ? absint( $bill_times ) : 52 );
444
445
			}
446
        }
447
        
448
        // Force return URL so that order description & instructions display
449
        $paypal_args['rm'] = 2;
450
        
451
        // Get rid of redudant items.
452
        foreach ( array( 'item_name_1', 'quantity_1', 'amount_1', 'item_number_1' ) as $arg ) {
453
454
            if ( isset( $paypal_args[ $arg ] ) ) {
455
                unset( $paypal_args[ $arg ] );
456
            }
457
458
        }
459
460
        return apply_filters(
461
			'getpaid_paypal_subscription_args',
462
			$paypal_args,
463
			$invoice
464
        );
465
466
    }
467
468
    /**
469
	 * Processes ipns and marks payments as complete.
470
	 *
471
	 * @return void
472
	 */
473
	public function verify_ipn() {
474
        new GetPaid_Paypal_Gateway_IPN_Handler( $this );
475
    }
476
477
    /**
478
     * Displays a notice on the checkout page if sandbox is enabled.
479
     */
480
    public function sandbox_notice( $description, $gateway ) {
481
        if ( 'paypal' == $gateway && wpinv_is_test_mode( 'paypal' ) ) {
482
            $description .= '<br>&nbsp;<br>' . sprintf(
483
                __( 'SANDBOX ENABLED. You can use sandbox testing accounts only. See the %sPayPal Sandbox Testing Guide%s for more details.', 'invoicing' ),
484
                '<a href="https://developer.paypal.com/docs/classic/lifecycle/ug_sandbox/">',
485
                '</a>'
486
            );
487
        }
488
        return $description;
489
490
    }
491
492
}
493