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/payments/class-getpaid-checkout.php (6 issues)

1
<?php
2
/**
3
 * Contains the Main Checkout Class.
4
 *
5
 */
6
7
defined( 'ABSPATH' ) || exit;
8
9
/**
10
 * Main Checkout Class.
11
 *
12
 */
13
class GetPaid_Checkout {
14
15
	/**
16
	 * @var GetPaid_Payment_Form_Submission
17
	 */
18
	protected $payment_form_submission;
19
20
	/**
21
	 * Class constructor.
22
	 *
23
	 * @param GetPaid_Payment_Form_Submission $submission
24
	 */
25
	public function __construct( $submission ) {
26
		$this->payment_form_submission = $submission;
27
	}
28
29
	/**
30
	 * Processes the checkout.
31
	 *
32
	 */
33
	public function process_checkout() {
34
35
		// Validate the submission.
36
		$this->validate_submission();
37
38
		// Prepare the invoice.
39
		$items    = $this->get_submission_items();
40
		$invoice  = $this->get_submission_invoice();
41
		$invoice  = $this->process_submission_invoice( $invoice, $items );
42
		$prepared = $this->prepare_submission_data_for_saving();
43
44
		$this->prepare_billing_info( $invoice );
45
46
		$shipping   = $this->prepare_shipping_info( $invoice );
47
48
		// Save the invoice.
49
		$invoice->set_is_viewed( true );
50
		$invoice->recalculate_total();
51
        $invoice->save();
52
53
		do_action( 'getpaid_checkout_invoice_updated', $invoice );
54
55
		// Send to the gateway.
56
		$this->post_process_submission( $invoice, $prepared, $shipping );
57
	}
58
59
	/**
60
	 * Validates the submission.
61
	 *
62
	 */
63
	protected function validate_submission() {
64
65
		$submission = $this->payment_form_submission;
66
		$data       = $submission->get_data();
67
68
		// Do we have an error?
69
        if ( ! empty( $submission->last_error ) ) {
70
			wp_send_json_error( $submission->last_error );
71
        }
72
73
		// We need a billing email.
74
        if ( ! $submission->has_billing_email() ) {
75
            wp_send_json_error( __( 'Provide a valid billing email.', 'invoicing' ) );
76
		}
77
78
		// Non-recurring gateways should not be allowed to process recurring invoices.
79
		if ( $submission->should_collect_payment_details() && $submission->has_recurring && ! wpinv_gateway_support_subscription( $data['wpi-gateway'] ) ) {
80
			wp_send_json_error( __( 'The selected payment gateway does not support subscription payments.', 'invoicing' ) );
81
		}
82
83
		// Ensure the gateway is active.
84
		if ( $submission->should_collect_payment_details() && ! wpinv_is_gateway_active( $data['wpi-gateway'] ) ) {
85
			wp_send_json_error( __( 'The selected payment gateway is not active', 'invoicing' ) );
86
		}
87
88
		// Clear any existing errors.
89
		wpinv_clear_errors();
90
91
		// Allow themes and plugins to hook to errors
92
		do_action( 'getpaid_checkout_error_checks', $submission );
93
94
		// Do we have any errors?
95
        if ( wpinv_get_errors() ) {
96
            wp_send_json_error( getpaid_get_errors_html() );
97
		}
98
99
	}
100
101
	/**
102
	 * Retrieves submission items.
103
	 *
104
	 * @return GetPaid_Form_Item[]
105
	 */
106
	protected function get_submission_items() {
107
108
		$items = $this->payment_form_submission->get_items();
109
110
        // Ensure that we have items.
111
        if ( empty( $items ) && ! $this->payment_form_submission->has_fees() ) {
112
            wp_send_json_error( __( 'Please provide at least one item or amount.', 'invoicing' ) );
113
		}
114
115
		return $items;
116
	}
117
118
	/**
119
	 * Retrieves submission invoice.
120
	 *
121
	 * @return WPInv_Invoice
122
	 */
123
	protected function get_submission_invoice() {
124
		$submission = $this->payment_form_submission;
125
126
		if ( ! $submission->has_invoice() ) {
127
			$invoice = new WPInv_Invoice();
128
			$invoice->set_created_via( 'payment_form' );
129
			return $invoice;
130
        }
131
132
		$invoice = $submission->get_invoice();
133
134
		// Make sure that it is neither paid or refunded.
135
		if ( $invoice->is_paid() || $invoice->is_refunded() ) {
136
			wp_send_json_error( __( 'This invoice has already been paid for.', 'invoicing' ) );
137
		}
138
139
		return $invoice;
140
	}
141
142
	/**
143
	 * Processes the submission invoice.
144
	 *
145
	 * @param WPInv_Invoice $invoice
146
	 * @param GetPaid_Form_Item[] $items
147
	 * @return WPInv_Invoice
148
	 */
149
	protected function process_submission_invoice( $invoice, $items ) {
150
151
		$submission = $this->payment_form_submission;
152
153
		// Set-up the invoice details.
154
		$invoice->set_email( sanitize_email( $submission->get_billing_email() ) );
155
		$invoice->set_user_id( $this->get_submission_customer() );
156
		$invoice->set_submission_id( $submission->id );
157
		$invoice->set_payment_form( absint( $submission->get_payment_form()->get_id() ) );
158
        $invoice->set_items( $items );
159
        $invoice->set_fees( $submission->get_fees() );
160
        $invoice->set_taxes( $submission->get_taxes() );
161
		$invoice->set_discounts( $submission->get_discounts() );
162
		$invoice->set_gateway( $submission->get_field( 'wpi-gateway' ) );
0 ignored issues
show
Are you sure the usage of $submission->get_field('wpi-gateway') targeting GetPaid_Payment_Form_Submission::get_field() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
163
		$invoice->set_currency( $submission->get_currency() );
164
165
		if ( $submission->has_shipping() ) {
166
			$invoice->set_shipping( $submission->get_shipping() );
167
		}
168
169
		$address_confirmed = $submission->get_field( 'confirm-address' );
0 ignored issues
show
Are you sure the assignment to $address_confirmed is correct as $submission->get_field('confirm-address') targeting GetPaid_Payment_Form_Submission::get_field() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
170
		$invoice->set_address_confirmed( ! empty( $address_confirmed ) );
171
172
		if ( $submission->has_discount_code() ) {
173
            $invoice->set_discount_code( $submission->get_discount_code() );
174
		}
175
176
		getpaid_maybe_add_default_address( $invoice );
177
		return $invoice;
178
	}
179
180
	/**
181
	 * Retrieves the submission's customer.
182
	 *
183
	 * @return int The customer id.
184
	 */
185
	protected function get_submission_customer() {
186
		$submission = $this->payment_form_submission;
187
188
		// If this is an existing invoice...
189
		if ( $submission->has_invoice() ) {
190
			return $submission->get_invoice()->get_user_id();
191
		}
192
193
		// (Maybe) create the user.
194
        $user = get_current_user_id();
195
196
        if ( empty( $user ) ) {
197
            $user = get_user_by( 'email', $submission->get_billing_email() );
198
        }
199
200
        if ( empty( $user ) ) {
201
			$name = array( $submission->get_field( 'wpinv_first_name', 'billing' ), $submission->get_field( 'wpinv_last_name', 'billing' ) );
0 ignored issues
show
Are you sure the usage of $submission->get_field('..._last_name', 'billing') targeting GetPaid_Payment_Form_Submission::get_field() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Are you sure the usage of $submission->get_field('...first_name', 'billing') targeting GetPaid_Payment_Form_Submission::get_field() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
202
			$name = implode( '', array_filter( $name ) );
203
            $user = wpinv_create_user( $submission->get_billing_email(), $name );
204
205
			// (Maybe) send new user notification.
206
			$should_send_notification = wpinv_get_option( 'disable_new_user_emails' );
207
			if ( ! empty( $user ) && is_numeric( $user ) && apply_filters( 'getpaid_send_new_user_notification', empty( $should_send_notification ), $user ) ) {
0 ignored issues
show
The condition is_numeric($user) is always false.
Loading history...
208
				wp_send_new_user_notifications( $user, 'user' );
209
			}
210
		}
211
212
        if ( is_wp_error( $user ) ) {
213
            wp_send_json_error( $user->get_error_message() );
214
        }
215
216
        if ( is_numeric( $user ) ) {
217
            return $user;
218
		}
219
220
		return $user->ID;
221
222
	}
223
224
	/**
225
     * Prepares submission data for saving to the database.
226
     *
227
	 * @return array
228
     */
229
    public function prepare_submission_data_for_saving() {
230
231
		$submission = $this->payment_form_submission;
232
233
		// Prepared submission details.
234
        $prepared = array(
235
			'all'  => array(),
236
			'meta' => array(),
237
		);
238
239
        // Raw submission details.
240
		$data     = $submission->get_data();
241
242
		// Loop through the submitted details.
243
        foreach ( $submission->get_payment_form()->get_elements() as $field ) {
244
245
			// Skip premade fields.
246
            if ( ! empty( $field['premade'] ) ) {
247
                continue;
248
            }
249
250
			// Ensure address is provided.
251
			if ( 'address' === $field['type'] ) {
252
                $address_type = isset( $field['address_type'] ) && 'shipping' === $field['address_type'] ? 'shipping' : 'billing';
253
254
				foreach ( $field['fields'] as $address_field ) {
255
256
					if ( ! empty( $address_field['visible'] ) && ! empty( $address_field['required'] ) && '' === trim( $_POST[ $address_type ][ $address_field['name'] ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
257
						wp_send_json_error( __( 'Please fill all required fields.', 'invoicing' ) );
258
					}
259
			}
260
		}
261
262
            // If it is required and not set, abort.
263
            if ( ! $submission->is_required_field_set( $field ) ) {
264
                wp_send_json_error( __( 'Please fill all required fields.', 'invoicing' ) );
265
            }
266
267
            // Handle misc fields.
268
            if ( isset( $data[ $field['id'] ] ) ) {
269
270
				// Uploads.
271
				if ( 'file_upload' === $field['type'] ) {
272
					$max_file_num = empty( $field['max_file_num'] ) ? 1 : absint( $field['max_file_num'] );
273
274
					if ( count( $data[ $field['id'] ] ) > $max_file_num ) {
275
						wp_send_json_error( __( 'Maximum number of allowed files exceeded.', 'invoicing' ) );
276
					}
277
278
					$value = array();
279
280
					foreach ( $data[ $field['id'] ] as $url => $name ) {
281
						$value[] = sprintf(
282
							'<a href="%s" target="_blank">%s</a>',
283
							esc_url_raw( $url ),
284
							esc_html( $name )
285
						);
286
					}
287
288
					$value = implode( ' | ', $value );
289
290
				} elseif ( 'checkbox' === $field['type'] ) {
291
					$value = ! empty( $data[ $field['id'] ] ) ? __( 'Yes', 'invoicing' ) : __( 'No', 'invoicing' );
292
				} else {
293
					$value = wp_kses_post( $data[ $field['id'] ] );
294
				}
295
296
                $label = $field['id'];
297
298
                if ( isset( $field['label'] ) ) {
299
                    $label = $field['label'];
300
                }
301
302
				if ( ! empty( $field['add_meta'] ) ) {
303
					$prepared['meta'][ wpinv_clean( $label ) ] = wp_kses_post_deep( $value );
304
				}
305
				$prepared['all'][ wpinv_clean( $label ) ] = wp_kses_post_deep( $value );
306
307
            }
308
		}
309
310
		return $prepared;
311
312
	}
313
314
	/**
315
     * Retrieves address details.
316
     *
317
	 * @return array
318
	 * @param WPInv_Invoice $invoice
319
	 * @param string $type
320
     */
321
    public function prepare_address_details( $invoice, $type = 'billing' ) {
322
323
		$data     = $this->payment_form_submission->get_data();
324
		$type     = sanitize_key( $type );
325
		$address  = array();
326
		$prepared = array();
327
328
		if ( ! empty( $data[ $type ] ) ) {
329
			$address = $data[ $type ];
330
		}
331
332
		// Clean address details.
333
		foreach ( $address as $key => $value ) {
334
			$key             = sanitize_key( $key );
335
			$key             = str_replace( 'wpinv_', '', $key );
336
			$value           = wpinv_clean( $value );
337
			$prepared[ $key ] = apply_filters( "getpaid_checkout_{$type}_address_$key", $value, $this->payment_form_submission, $invoice );
338
		}
339
340
		// Filter address details.
341
		$prepared = apply_filters( "getpaid_checkout_{$type}_address", $prepared, $this->payment_form_submission, $invoice );
342
343
		// Remove non-whitelisted values.
344
		return array_filter( $prepared, 'getpaid_is_address_field_whitelisted', ARRAY_FILTER_USE_KEY );
345
346
	}
347
348
	/**
349
     * Prepares the billing details.
350
     *
351
	 * @return array
352
	 * @param WPInv_Invoice $invoice
353
     */
354
    protected function prepare_billing_info( &$invoice ) {
355
356
		$billing_address = $this->prepare_address_details( $invoice, 'billing' );
357
358
		// Update the invoice with the billing details.
359
		$invoice->set_props( $billing_address );
360
361
	}
362
363
	/**
364
     * Prepares the shipping details.
365
     *
366
	 * @return array
367
	 * @param WPInv_Invoice $invoice
368
     */
369
    protected function prepare_shipping_info( $invoice ) {
370
371
		$data = $this->payment_form_submission->get_data();
372
373
		if ( empty( $data['same-shipping-address'] ) ) {
374
			return $this->prepare_address_details( $invoice, 'shipping' );
375
		}
376
377
		return $this->prepare_address_details( $invoice, 'billing' );
378
379
	}
380
381
	/**
382
	 * Confirms the submission is valid and send users to the gateway.
383
	 *
384
	 * @param WPInv_Invoice $invoice
385
	 * @param array $prepared_payment_form_data
386
	 * @param array $shipping
387
	 */
388
	protected function post_process_submission( $invoice, $prepared_payment_form_data, $shipping ) {
389
390
		// Ensure the invoice exists.
391
        if ( ! $invoice->exists() ) {
392
            wp_send_json_error( __( 'An error occured while saving your invoice. Please try again.', 'invoicing' ) );
393
        }
394
395
		// Save payment form data.
396
		$prepared_payment_form_data = apply_filters( 'getpaid_prepared_payment_form_data', $prepared_payment_form_data, $invoice );
397
        delete_post_meta( $invoice->get_id(), 'payment_form_data' );
398
		delete_post_meta( $invoice->get_id(), 'additional_meta_data' );
399
		if ( ! empty( $prepared_payment_form_data ) ) {
400
401
			if ( ! empty( $prepared_payment_form_data['all'] ) ) {
402
				update_post_meta( $invoice->get_id(), 'payment_form_data', $prepared_payment_form_data['all'] );
403
			}
404
405
			if ( ! empty( $prepared_payment_form_data['meta'] ) ) {
406
				update_post_meta( $invoice->get_id(), 'additional_meta_data', $prepared_payment_form_data['meta'] );
407
			}
408
		}
409
410
		// Save payment form data.
411
		$shipping = apply_filters( 'getpaid_checkout_shipping_details', $shipping, $this->payment_form_submission );
412
        if ( ! empty( $shipping ) ) {
413
            update_post_meta( $invoice->get_id(), 'shipping_address', $shipping );
414
		}
415
416
		// Backwards compatibility.
417
        add_filter( 'wp_redirect', array( $this, 'send_redirect_response' ) );
418
419
		try {
420
			$this->process_payment( $invoice );
421
		} catch ( Exception $e ) {
422
			wpinv_set_error( 'payment_error', $e->getMessage() );
423
		}
424
425
        // If we are here, there was an error.
426
		wpinv_send_back_to_checkout( $invoice );
427
428
	}
429
430
	/**
431
	 * Processes the actual payment.
432
	 *
433
	 * @param WPInv_Invoice $invoice
434
	 */
435
	protected function process_payment( $invoice ) {
436
437
		// Clear any checkout errors.
438
		wpinv_clear_errors();
439
440
		// No need to send free invoices to the gateway.
441
		if ( $invoice->is_free() ) {
442
			$this->process_free_payment( $invoice );
443
		}
444
445
		$submission = $this->payment_form_submission;
446
447
		// Fires before sending to the gateway.
448
		do_action( 'getpaid_checkout_before_gateway', $invoice, $submission );
449
450
		// Allow the sumission data to be modified before it is sent to the gateway.
451
		$submission_data    = $submission->get_data();
452
		$submission_gateway = apply_filters( 'getpaid_gateway_submission_gateway', $invoice->get_gateway(), $submission, $invoice );
453
		$submission_data    = apply_filters( 'getpaid_gateway_submission_data', $submission_data, $submission, $invoice );
454
455
		// Validate the currency.
456
		if ( ! apply_filters( "getpaid_gateway_{$submission_gateway}_is_valid_for_currency", true, $invoice->get_currency() ) ) {
457
			wpinv_set_error( 'invalid_currency' );
458
		}
459
460
		// Check to see if we have any errors.
461
		if ( wpinv_get_errors() ) {
462
			wpinv_send_back_to_checkout( $invoice );
463
		}
464
465
		// Send info to the gateway for payment processing
466
		do_action( "getpaid_gateway_$submission_gateway", $invoice, $submission_data, $submission );
467
468
		// Backwards compatibility.
469
		wpinv_send_to_gateway( $submission_gateway, $invoice );
0 ignored issues
show
Deprecated Code introduced by
The function wpinv_send_to_gateway() has been deprecated. ( Ignorable by Annotation )

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

469
		/** @scrutinizer ignore-deprecated */ wpinv_send_to_gateway( $submission_gateway, $invoice );
Loading history...
470
471
	}
472
473
	/**
474
	 * Marks the invoice as paid in case the checkout is free.
475
	 *
476
	 * @param WPInv_Invoice $invoice
477
	 */
478
	protected function process_free_payment( $invoice ) {
479
480
		$invoice->set_gateway( 'none' );
481
		$invoice->add_note( __( "This is a free invoice and won't be sent to the payment gateway", 'invoicing' ), false, false, true );
482
		$invoice->mark_paid();
483
		wpinv_send_to_success_page( array( 'invoice_key' => $invoice->get_key() ) );
484
485
	}
486
487
	/**
488
     * Sends a redrect response to payment details.
489
     *
490
     */
491
    public function send_redirect_response( $url ) {
492
        $url = rawurlencode( $url );
493
        wp_send_json_success( $url );
494
    }
495
496
}
497