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.

gateways/class-getpaid-authorize-net-gateway.php (25 issues)

1
<?php
2
/**
3
 * Authorize.net payment gateway
4
 *
5
 */
6
7
defined( 'ABSPATH' ) || exit;
8
9
/**
10
 * Authorize.net Payment Gateway class.
11
 *
12
 */
13
class GetPaid_Authorize_Net_Gateway extends GetPaid_Authorize_Net_Legacy_Gateway {
14
15
    /**
16
	 * Payment method id.
17
	 *
18
	 * @var string
19
	 */
20
    public $id = 'authorizenet';
21
22
    /**
23
	 * An array of features that this gateway supports.
24
	 *
25
	 * @var array
26
	 */
27
    protected $supports = array(
28
        'subscription',
29
        'sandbox',
30
        'tokens',
31
        'addons',
32
        'single_subscription_group',
33
        'multiple_subscription_groups',
34
        'subscription_date_change',
35
        'subscription_bill_times_change',
36
    );
37
38
    /**
39
	 * Payment method order.
40
	 *
41
	 * @var int
42
	 */
43
    public $order = 4;
44
45
    /**
46
	 * Endpoint for requests from Authorize.net.
47
	 *
48
	 * @var string
49
	 */
50
	protected $notify_url;
51
52
	/**
53
	 * Endpoint for requests to Authorize.net.
54
	 *
55
	 * @var string
56
	 */
57
    protected $endpoint;
58
59
    /**
60
	 * Currencies this gateway is allowed for.
61
	 *
62
	 * @var array
63
	 */
64
	public $currencies = array( 'USD', 'CAD', 'GBP', 'DKK', 'NOK', 'PLN', 'SEK', 'AUD', 'EUR', 'NZD' );
65
66
    /**
67
	 * URL to view a transaction.
68
	 *
69
	 * @var string
70
	 */
71
    public $view_transaction_url = 'https://{sandbox}authorize.net/ui/themes/sandbox/Transaction/TransactionReceipt.aspx?transid=%s';
72
73
    /**
74
	 * Class constructor.
75
	 */
76
	public function __construct() {
77
78
        $this->title                = __( 'Credit Card / Debit Card', 'invoicing' );
79
        $this->method_title         = __( 'Authorize.Net', 'invoicing' );
80
        $this->notify_url           = getpaid_get_non_query_string_ipn_url( $this->id );
81
82
        add_action( 'getpaid_should_renew_subscription', array( $this, 'maybe_renew_subscription' ), 11, 2 );
83
        add_filter( 'getpaid_authorizenet_sandbox_notice', array( $this, 'sandbox_notice' ) );
84
        parent::__construct();
85
    }
86
87
    /**
88
	 * Displays the payment method select field.
89
	 *
90
	 * @param int $invoice_id 0 or invoice id.
91
	 * @param GetPaid_Payment_Form $form Current payment form.
92
	 */
93
    public function payment_fields( $invoice_id, $form ) {
94
95
        // Let the user select a payment method.
96
        $this->saved_payment_methods();
97
98
        // Show the credit card entry form.
99
        $this->new_payment_method_entry( $this->get_cc_form( true ) );
100
    }
101
102
    /**
103
	 * Creates a customer profile.
104
	 *
105
	 *
106
	 * @param WPInv_Invoice $invoice Invoice.
107
     * @param array $submission_data Posted checkout fields.
108
     * @param bool $save Whether or not to save the payment as a token.
109
     * @link https://developer.authorize.net/api/reference/index.html#customer-profiles-create-customer-profile
110
	 * @return string|WP_Error Payment profile id.
111
	 */
112
	public function create_customer_profile( $invoice, $submission_data, $save = true ) {
113
114
        // Remove non-digits from the number
115
        $submission_data['authorizenet']['cc_number'] = preg_replace( '/\D/', '', $submission_data['authorizenet']['cc_number'] );
116
117
        // Generate args.
118
        $args = array(
119
            'createCustomerProfileRequest' => array(
120
                'merchantAuthentication' => $this->get_auth_params(),
121
                'profile'                => array(
122
                    'merchantCustomerId' => getpaid_limit_length( $invoice->get_user_id(), 20 ),
123
                    'description'        => getpaid_limit_length( $invoice->get_full_name(), 255 ),
124
                    'email'              => getpaid_limit_length( $invoice->get_email(), 255 ),
125
                    'paymentProfiles'    => array(
126
                        'customerType' => 'individual',
127
128
                        // Billing information.
129
                        'billTo'       => array(
130
                            'firstName' => getpaid_limit_length( $invoice->get_first_name(), 50 ),
131
                            'lastName'  => getpaid_limit_length( $invoice->get_last_name(), 50 ),
132
                            'address'   => getpaid_limit_length( $invoice->get_address(), 60 ),
133
                            'city'      => getpaid_limit_length( $invoice->get_city(), 40 ),
134
                            'state'     => getpaid_limit_length( $invoice->get_state(), 40 ),
135
                            'zip'       => getpaid_limit_length( $invoice->get_zip(), 20 ),
136
                            'country'   => getpaid_limit_length( $invoice->get_country(), 60 ),
137
                        ),
138
139
                        // Payment information.
140
                        'payment'      => $this->get_payment_information( $submission_data['authorizenet'] ),
141
                    ),
142
                ),
143
                'validationMode'         => $this->is_sandbox( $invoice ) ? 'testMode' : 'liveMode',
144
            ),
145
        );
146
147
        $response = $this->post( apply_filters( 'getpaid_authorizenet_customer_profile_args', $args, $invoice ), $invoice );
148
149
        if ( is_wp_error( $response ) ) {
150
151
            // In case the payment profile already exists remotely.
152
            if ( 'dup_payment_profile' === $response->get_error_code() ) {
0 ignored issues
show
The method get_error_code() does not exist on stdClass. ( Ignorable by Annotation )

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

152
            if ( 'dup_payment_profile' === $response->/** @scrutinizer ignore-call */ get_error_code() ) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
153
                $customer_profile_id = strtok( $response->get_error_message(), '.' );
0 ignored issues
show
The method get_error_message() does not exist on stdClass. ( Ignorable by Annotation )

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

153
                $customer_profile_id = strtok( $response->/** @scrutinizer ignore-call */ get_error_message(), '.' );

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
154
                update_user_meta( $invoice->get_user_id(), $this->get_customer_profile_meta_name( $invoice ), $customer_profile_id );
155
                return strtok( '.' );
156
            }
157
158
            // In case the customer profile already exists remotely.
159
            if ( 'E00039' === $response->get_error_code() ) {
160
                $customer_profile_id = str_replace( 'A duplicate record with ID ', '', $response->get_error_message() );
161
                $customer_profile_id = str_replace( ' already exists.', '', $customer_profile_id );
162
                return $this->create_customer_payment_profile( trim( $customer_profile_id ), $invoice, $submission_data, $save );
163
            }
164
165
            return $response;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $response also could return the type stdClass which is incompatible with the documented return type WP_Error|string.
Loading history...
166
        }
167
168
        update_user_meta( $invoice->get_user_id(), $this->get_customer_profile_meta_name( $invoice ), $response->customerProfileId );
0 ignored issues
show
The property customerProfileId does not seem to exist on WP_Error.
Loading history...
169
170
        // Save the payment token.
171
        if ( $save ) {
172
            $this->save_token(
173
                array(
174
                    'id'      => $response->customerPaymentProfileIdList[0],
0 ignored issues
show
The property customerPaymentProfileIdList does not seem to exist on WP_Error.
Loading history...
175
                    'name'    => getpaid_get_card_name( $submission_data['authorizenet']['cc_number'] ) . '&middot;&middot;&middot;&middot;' . substr( $submission_data['authorizenet']['cc_number'], -4 ),
176
                    'default' => true,
177
                    'type'    => $this->is_sandbox( $invoice ) ? 'sandbox' : 'live',
178
                )
179
            );
180
        }
181
182
        // Add a note about the validation response.
183
        $invoice->add_note(
184
            sprintf( __( 'Created Authorize.NET customer profile: %s', 'invoicing' ), $response->validationDirectResponseList[0] ),
0 ignored issues
show
The property validationDirectResponseList does not seem to exist on WP_Error.
Loading history...
185
            false,
186
            false,
187
            true
188
        );
189
190
        return $response->customerPaymentProfileIdList[0];
191
    }
192
193
    /**
194
	 * Retrieves a customer profile.
195
	 *
196
	 *
197
	 * @param string $profile_id profile id.
198
	 * @return string|WP_Error Profile id.
199
     * @link https://developer.authorize.net/api/reference/index.html#customer-profiles-get-customer-profile
200
	 */
201
	public function get_customer_profile( $profile_id ) {
202
203
        // Generate args.
204
        $args = array(
205
            'getCustomerProfileRequest' => array(
206
                'merchantAuthentication' => $this->get_auth_params(),
207
                'customerProfileId'      => $profile_id,
208
            ),
209
        );
210
211
        return $this->post( $args, false );
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->post($args, false) also could return the type stdClass which is incompatible with the documented return type WP_Error|string.
Loading history...
false of type false is incompatible with the type WPInv_Invoice expected by parameter $invoice of GetPaid_Authorize_Net_Legacy_Gateway::post(). ( Ignorable by Annotation )

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

211
        return $this->post( $args, /** @scrutinizer ignore-type */ false );
Loading history...
212
213
    }
214
215
    /**
216
	 * Creates a customer profile.
217
	 *
218
	 *
219
     * @param string $profile_id profile id.
220
	 * @param WPInv_Invoice $invoice Invoice.
221
     * @param array $submission_data Posted checkout fields.
222
     * @param bool $save Whether or not to save the payment as a token.
223
     * @link https://developer.authorize.net/api/reference/index.html#customer-profiles-create-customer-profile
224
	 * @return string|WP_Error Profile id.
225
	 */
226
	public function create_customer_payment_profile( $customer_profile, $invoice, $submission_data, $save ) {
227
228
        // Remove non-digits from the number
229
        $submission_data['authorizenet']['cc_number'] = preg_replace( '/\D/', '', $submission_data['authorizenet']['cc_number'] );
230
231
        // Prepare card details.
232
        $payment_information                          = $this->get_payment_information( $submission_data['authorizenet'] );
233
234
        // Authorize.NET does not support saving the same card twice.
235
        $cached_information                           = $this->retrieve_payment_profile_from_cache( $payment_information, $customer_profile, $invoice );
236
237
        if ( $cached_information ) {
238
            return $cached_information;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $cached_information returns the type array which is incompatible with the documented return type WP_Error|string.
Loading history...
239
        }
240
241
        // Generate args.
242
        $args = array(
243
            'createCustomerPaymentProfileRequest' => array(
244
                'merchantAuthentication' => $this->get_auth_params(),
245
                'customerProfileId'      => $customer_profile,
246
                'paymentProfile'         => array(
247
248
                    // Billing information.
249
                    'billTo'  => array(
250
                        'firstName' => getpaid_limit_length( $invoice->get_first_name(), 50 ),
251
                        'lastName'  => getpaid_limit_length( $invoice->get_last_name(), 50 ),
252
                        'address'   => getpaid_limit_length( $invoice->get_address(), 60 ),
253
                        'city'      => getpaid_limit_length( $invoice->get_city(), 40 ),
254
                        'state'     => getpaid_limit_length( $invoice->get_state(), 40 ),
255
                        'zip'       => getpaid_limit_length( $invoice->get_zip(), 20 ),
256
                        'country'   => getpaid_limit_length( $invoice->get_country(), 60 ),
257
                    ),
258
259
                    // Payment information.
260
                    'payment' => $payment_information,
261
                ),
262
                'validationMode'         => $this->is_sandbox( $invoice ) ? 'testMode' : 'liveMode',
263
            ),
264
        );
265
266
        $response = $this->post( apply_filters( 'getpaid_authorizenet_create_customer_payment_profile_args', $args, $invoice ), $invoice );
267
268
        if ( is_wp_error( $response ) ) {
269
270
            // In case the payment profile already exists remotely.
271
            if ( 'dup_payment_profile' == $response->get_error_code() ) {
272
                $customer_profile_id = strtok( $response->get_error_message(), '.' );
273
                $payment_profile_id  = strtok( '.' );
274
                update_user_meta( $invoice->get_user_id(), $this->get_customer_profile_meta_name( $invoice ), $customer_profile_id );
275
276
                // Cache payment profile id.
277
                $this->add_payment_profile_to_cache( $payment_information, $payment_profile_id );
278
279
                return $payment_profile_id;
280
            }
281
282
            return $response;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $response also could return the type stdClass which is incompatible with the documented return type WP_Error|string.
Loading history...
283
        }
284
285
        // Save the payment token.
286
        if ( $save ) {
287
            $this->save_token(
288
                array(
289
                    'id'      => $response->customerPaymentProfileId,
0 ignored issues
show
The property customerPaymentProfileId does not seem to exist on WP_Error.
Loading history...
290
                    'name'    => getpaid_get_card_name( $submission_data['authorizenet']['cc_number'] ) . ' &middot;&middot;&middot;&middot; ' . substr( $submission_data['authorizenet']['cc_number'], -4 ),
291
                    'default' => true,
292
                    'type'    => $this->is_sandbox( $invoice ) ? 'sandbox' : 'live',
293
                )
294
            );
295
        }
296
297
        // Cache payment profile id.
298
        $this->add_payment_profile_to_cache( $payment_information, $response->customerPaymentProfileId );
299
300
        // Add a note about the validation response.
301
        $invoice->add_note(
302
            sprintf( __( 'Saved Authorize.NET payment profile: %s', 'invoicing' ), $response->validationDirectResponse ),
0 ignored issues
show
The property validationDirectResponse does not seem to exist on WP_Error.
Loading history...
303
            false,
304
            false,
305
            true
306
        );
307
308
        update_user_meta( $invoice->get_user_id(), $this->get_customer_profile_meta_name( $invoice ), $customer_profile );
309
310
        return $response->customerPaymentProfileId;
311
    }
312
313
    /**
314
	 * Retrieves payment details from cache.
315
	 *
316
	 *
317
     * @param array $payment_details.
318
	 * @return array|false Profile id.
319
	 */
320
	public function retrieve_payment_profile_from_cache( $payment_details, $customer_profile, $invoice ) {
321
322
        $cached_information = get_option( 'getpaid_authorize_net_cached_profiles', array() );
323
        $payment_details    = hash_hmac( 'sha256', json_encode( $payment_details ), SECURE_AUTH_KEY );
324
325
        if ( ! is_array( $cached_information ) || ! array_key_exists( $payment_details, $cached_information ) ) {
326
            return false;
327
        }
328
329
        // Generate args.
330
        $args = array(
331
            'getCustomerPaymentProfileRequest' => array(
332
                'merchantAuthentication'   => $this->get_auth_params(),
333
                'customerProfileId'        => $customer_profile,
334
                'customerPaymentProfileId' => $cached_information[ $payment_details ],
335
            ),
336
        );
337
338
        $response = $this->post( $args, $invoice );
339
340
        return is_wp_error( $response ) ? false : $cached_information[ $payment_details ];
341
342
    }
343
344
    /**
345
	 * Securely adds payment details to cache.
346
	 *
347
	 *
348
     * @param array $payment_details.
349
     * @param string $payment_profile_id.
350
	 */
351
	public function add_payment_profile_to_cache( $payment_details, $payment_profile_id ) {
352
353
        $cached_information = get_option( 'getpaid_authorize_net_cached_profiles', array() );
354
        $cached_information = is_array( $cached_information ) ? $cached_information : array();
355
        $payment_details    = hash_hmac( 'sha256', json_encode( $payment_details ), SECURE_AUTH_KEY );
356
357
        $cached_information[ $payment_details ] = $payment_profile_id;
358
        update_option( 'getpaid_authorize_net_cached_profiles', $cached_information );
359
360
    }
361
362
    /**
363
	 * Retrieves a customer payment profile.
364
	 *
365
	 *
366
	 * @param string $customer_profile_id customer profile id.
367
     * @param string $payment_profile_id payment profile id.
368
	 * @return string|WP_Error Profile id.
369
     * @link https://developer.authorize.net/api/reference/index.html#customer-profiles-get-customer-payment-profile
370
	 */
371
	public function get_customer_payment_profile( $customer_profile_id, $payment_profile_id ) {
372
373
        // Generate args.
374
        $args = array(
375
            'getCustomerPaymentProfileRequest' => array(
376
                'merchantAuthentication'   => $this->get_auth_params(),
377
                'customerProfileId'        => $customer_profile_id,
378
                'customerPaymentProfileId' => $payment_profile_id,
379
            ),
380
        );
381
382
        return $this->post( $args, false );
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->post($args, false) also could return the type stdClass which is incompatible with the documented return type WP_Error|string.
Loading history...
false of type false is incompatible with the type WPInv_Invoice expected by parameter $invoice of GetPaid_Authorize_Net_Legacy_Gateway::post(). ( Ignorable by Annotation )

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

382
        return $this->post( $args, /** @scrutinizer ignore-type */ false );
Loading history...
383
384
    }
385
386
    /**
387
	 * Charges a customer payment profile.
388
	 *
389
     * @param string $customer_profile_id customer profile id.
390
     * @param string $payment_profile_id payment profile id.
391
	 * @param WPInv_Invoice $invoice Invoice.
392
     * @link https://developer.authorize.net/api/reference/index.html#payment-transactions-charge-a-customer-profile
393
	 * @return WP_Error|object
394
	 */
395
	public function charge_customer_payment_profile( $customer_profile_id, $payment_profile_id, $invoice ) {
396
397
        // Generate args.
398
        $args = array(
399
400
            'createTransactionRequest' => array(
401
402
                'merchantAuthentication' => $this->get_auth_params(),
403
                'refId'                  => $invoice->get_id(),
404
                'transactionRequest'     => array(
405
                    'transactionType' => 'authCaptureTransaction',
406
                    'amount'          => $invoice->get_total(),
407
                    'currencyCode'    => $invoice->get_currency(),
408
                    'profile'         => array(
409
                        'customerProfileId' => $customer_profile_id,
410
                        'paymentProfile'    => array(
411
                            'paymentProfileId' => $payment_profile_id,
412
                        ),
413
                    ),
414
                    'order'           => array(
415
                        'invoiceNumber' => getpaid_limit_length( $invoice->get_number(), 20 ),
416
                    ),
417
                    'lineItems'       => array( 'lineItem' => $this->get_line_items( $invoice ) ),
418
                    'tax'             => array(
419
                        'amount' => $invoice->get_total_tax(),
420
                        'name'   => __( 'TAX', 'invoicing' ),
421
                    ),
422
                    'poNumber'        => getpaid_limit_length( $invoice->get_number(), 25 ),
423
                    'customer'        => array(
424
                        'id'    => getpaid_limit_length( $invoice->get_user_id(), 25 ),
425
                        'email' => getpaid_limit_length( $invoice->get_email(), 25 ),
426
                    ),
427
                    'customerIP'      => $invoice->get_ip(),
428
                ),
429
            ),
430
        );
431
432
        if ( 0 == $invoice->get_total_tax() ) {
433
            unset( $args['createTransactionRequest']['transactionRequest']['tax'] );
434
        }
435
436
        return $this->post( apply_filters( 'getpaid_authorizenet_charge_customer_payment_profile_args', $args, $invoice ), $invoice );
0 ignored issues
show
The call to filter_addons_request() has too many arguments starting with $invoice. ( Ignorable by Annotation )

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

436
        return $this->post( /** @scrutinizer ignore-call */ apply_filters( 'getpaid_authorizenet_charge_customer_payment_profile_args', $args, $invoice ), $invoice );

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...
437
438
    }
439
440
    /**
441
	 * Processes a customer charge.
442
	 *
443
     * @param stdClass $result Api response.
444
	 * @param WPInv_Invoice $invoice Invoice.
445
	 */
446
	public function process_charge_response( $result, $invoice ) {
447
448
        wpinv_clear_errors();
449
		$response_code = (int) $result->transactionResponse->responseCode;
450
451
        $invoice->add_note( 'Transaction Response: ' . print_r( $result->transactionResponse, true ), false, false, true );
0 ignored issues
show
Are you sure print_r($result->transactionResponse, true) of type string|true can be used in concatenation? ( Ignorable by Annotation )

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

451
        $invoice->add_note( 'Transaction Response: ' . /** @scrutinizer ignore-type */ print_r( $result->transactionResponse, true ), false, false, true );
Loading history...
452
453
		// Succeeded.
454
		if ( 1 == $response_code || 4 == $response_code ) {
455
456
			// Maybe set a transaction id.
457
			if ( ! empty( $result->transactionResponse->transId ) ) {
458
				$invoice->set_transaction_id( $result->transactionResponse->transId );
459
			}
460
461
			$invoice->add_note( sprintf( __( 'Authentication code: %1$s (%2$s).', 'invoicing' ), $result->transactionResponse->authCode, $result->transactionResponse->accountNumber ), false, false, true );
462
463
			if ( 1 == $response_code ) {
464
				return $invoice->mark_paid();
465
			}
466
467
			$invoice->set_status( 'wpi-onhold' );
468
        	$invoice->add_note(
469
                sprintf(
470
                    __( 'Held for review: %s', 'invoicing' ),
471
                    $result->transactionResponse->messages->message[0]->description
472
                )
473
			);
474
475
			return $invoice->save();
476
477
		}
478
479
        wpinv_set_error( 'card_declined' );
480
481
        if ( ! empty( $result->transactionResponse->errors ) ) {
482
            $errors = (object) $result->transactionResponse->errors;
483
            wpinv_set_error( $errors->error[0]->errorCode, esc_html( $errors->error[0]->errorText ) );
484
        }
485
486
    }
487
488
    /**
489
	 * Returns payment information.
490
	 *
491
	 *
492
	 * @param array $card Card details.
493
	 * @return array
494
	 */
495
	public function get_payment_information( $card ) {
496
        return array(
497
498
            'creditCard' => array(
499
                'cardNumber'     => $card['cc_number'],
500
                'expirationDate' => $card['cc_expire_year'] . '-' . $card['cc_expire_month'],
501
                'cardCode'       => $card['cc_cvv2'],
502
            ),
503
504
        );
505
    }
506
507
    /**
508
	 * Returns the customer profile meta name.
509
	 *
510
	 *
511
	 * @param WPInv_Invoice $invoice Invoice.
512
	 * @return string
513
	 */
514
	public function get_customer_profile_meta_name( $invoice ) {
515
        return $this->is_sandbox( $invoice ) ? 'getpaid_authorizenet_sandbox_customer_profile_id' : 'getpaid_authorizenet_customer_profile_id';
516
    }
517
518
    /**
519
	 * Validates the submitted data.
520
	 *
521
	 *
522
	 * @param array $submission_data Posted checkout fields.
523
     * @param WPInv_Invoice $invoice
524
	 * @return WP_Error|string The payment profile id
525
	 */
526
	public function validate_submission_data( $submission_data, $invoice ) {
527
528
        // Validate authentication details.
529
        $auth = $this->get_auth_params();
530
531
        if ( empty( $auth['name'] ) || empty( $auth['transactionKey'] ) ) {
532
            return new WP_Error( 'invalid_settings', __( 'Please set-up your login id and transaction key before using this gateway.', 'invoicing' ) );
533
        }
534
535
        // Validate the payment method.
536
        if ( empty( $submission_data['getpaid-authorizenet-payment-method'] ) ) {
537
            return new WP_Error( 'invalid_payment_method', __( 'Please select a different payment method or add a new card.', 'invoicing' ) );
538
        }
539
540
        // Are we adding a new payment method?
541
        if ( 'new' != $submission_data['getpaid-authorizenet-payment-method'] ) {
542
            return $submission_data['getpaid-authorizenet-payment-method'];
543
        }
544
545
        // Retrieve the customer profile id.
546
        $profile_id = get_user_meta( $invoice->get_user_id(), $this->get_customer_profile_meta_name( $invoice ), true );
547
548
        // Create payment method.
549
        if ( empty( $profile_id ) ) {
550
            return $this->create_customer_profile( $invoice, $submission_data, ! empty( $submission_data['getpaid-authorizenet-new-payment-method'] ) );
551
        }
552
553
        return $this->create_customer_payment_profile( $profile_id, $invoice, $submission_data, ! empty( $submission_data['getpaid-authorizenet-new-payment-method'] ) );
554
555
    }
556
557
    /**
558
	 * Returns invoice line items.
559
	 *
560
	 *
561
	 * @param WPInv_Invoice $invoice Invoice.
562
	 * @return array
563
	 */
564
	public function get_line_items( $invoice ) {
565
        $items = array();
566
567
        foreach ( $invoice->get_items() as $item ) {
568
569
            $amount  = $invoice->is_renewal() ? $item->get_price() : $item->get_initial_price();
570
            $items[] = array(
571
                'itemId'      => getpaid_limit_length( $item->get_id(), 31 ),
572
                'name'        => getpaid_limit_length( $item->get_raw_name(), 31 ),
573
                'description' => getpaid_limit_length( $item->get_description(), 255 ),
574
                'quantity'    => (string) ( $invoice->get_template() == 'amount' ? 1 : $item->get_quantity() ),
575
                'unitPrice'   => (float) $amount,
576
                'taxable'     => wpinv_use_taxes() && $invoice->is_taxable() && 'tax-exempt' != $item->get_vat_rule(),
577
            );
578
579
        }
580
581
        foreach ( $invoice->get_fees() as $fee_name => $fee ) {
582
583
            $amount  = $invoice->is_renewal() ? $fee['recurring_fee'] : $fee['initial_fee'];
584
585
            if ( $amount > 0 ) {
586
                $items[] = array(
587
                    'itemId'      => getpaid_limit_length( $fee_name, 31 ),
588
                    'name'        => getpaid_limit_length( $fee_name, 31 ),
589
                    'description' => getpaid_limit_length( $fee_name, 255 ),
590
                    'quantity'    => '1',
591
                    'unitPrice'   => (float) $amount,
592
                    'taxable'     => false,
593
                );
594
            }
595
}
596
597
        return $items;
598
    }
599
600
    /**
601
	 * Process Payment.
602
	 *
603
	 *
604
	 * @param WPInv_Invoice $invoice Invoice.
605
	 * @param array $submission_data Posted checkout fields.
606
	 * @param GetPaid_Payment_Form_Submission $submission Checkout submission.
607
	 * @return array
608
	 */
609
	public function process_payment( $invoice, $submission_data, $submission ) {
610
611
        // Validate the submitted data.
612
        $payment_profile_id = $this->validate_submission_data( $submission_data, $invoice );
613
614
        // Do we have an error?
615
        if ( is_wp_error( $payment_profile_id ) ) {
616
            wpinv_set_error( $payment_profile_id->get_error_code(), $payment_profile_id->get_error_message() );
617
            wpinv_send_back_to_checkout( $invoice );
618
        }
619
620
        // Save the payment method to the order.
621
        update_post_meta( $invoice->get_id(), 'getpaid_authorizenet_profile_id', $payment_profile_id );
622
623
        // Check if this is a subscription or not.
624
        $subscriptions = getpaid_get_invoice_subscriptions( $invoice );
625
        if ( ! empty( $subscriptions ) ) {
626
            $this->process_subscription( $invoice, $subscriptions );
627
        }
628
629
        // If it is free, send to the success page.
630
        if ( ! $invoice->needs_payment() ) {
631
            $invoice->mark_paid();
632
            wpinv_send_to_success_page( array( 'invoice_key' => $invoice->get_key() ) );
633
        }
634
635
        // Charge the payment profile.
636
        $this->process_initial_payment( $invoice );
637
638
        wpinv_send_to_success_page( array( 'invoice_key' => $invoice->get_key() ) );
639
640
        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...
641
642
	}
643
644
	/**
645
	 * Processes the initial payment.
646
	 *
647
     * @param WPInv_Invoice $invoice Invoice.
648
	 */
649
	protected function process_initial_payment( $invoice ) {
650
651
		$payment_profile_id = get_post_meta( $invoice->get_id(), 'getpaid_authorizenet_profile_id', true );
652
        $customer_profile   = get_user_meta( $invoice->get_user_id(), $this->get_customer_profile_meta_name( $invoice ), true );
653
		$result             = $this->charge_customer_payment_profile( $customer_profile, $payment_profile_id, $invoice );
0 ignored issues
show
It seems like $payment_profile_id can also be of type false; however, parameter $payment_profile_id of GetPaid_Authorize_Net_Ga...tomer_payment_profile() does only seem to accept string, 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

653
		$result             = $this->charge_customer_payment_profile( $customer_profile, /** @scrutinizer ignore-type */ $payment_profile_id, $invoice );
Loading history...
It seems like $customer_profile can also be of type false; however, parameter $customer_profile_id of GetPaid_Authorize_Net_Ga...tomer_payment_profile() does only seem to accept string, 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

653
		$result             = $this->charge_customer_payment_profile( /** @scrutinizer ignore-type */ $customer_profile, $payment_profile_id, $invoice );
Loading history...
654
655
		// Do we have an error?
656
		if ( is_wp_error( $result ) ) {
657
			wpinv_set_error( $result->get_error_code(), $result->get_error_message() );
658
			wpinv_send_back_to_checkout( $invoice );
659
		}
660
661
		// Process the response.
662
		$this->process_charge_response( $result, $invoice );
0 ignored issues
show
$result of type WP_Error is incompatible with the type stdClass expected by parameter $result of GetPaid_Authorize_Net_Ga...ocess_charge_response(). ( Ignorable by Annotation )

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

662
		$this->process_charge_response( /** @scrutinizer ignore-type */ $result, $invoice );
Loading history...
663
664
		if ( wpinv_get_errors() ) {
665
			wpinv_send_back_to_checkout( $invoice );
666
		}
667
668
	}
669
670
    /**
671
	 * Processes recurring payments.
672
	 *
673
     * @param WPInv_Invoice $invoice Invoice.
674
     * @param WPInv_Subscription[]|WPInv_Subscription $subscriptions Subscriptions.
675
	 */
676
	public function process_subscription( $invoice, $subscriptions ) {
677
678
        // Check if there is an initial amount to charge.
679
        if ( (float) $invoice->get_total() > 0 ) {
680
			$this->process_initial_payment( $invoice );
681
        }
682
683
        // Activate the subscriptions.
684
        $subscriptions = is_array( $subscriptions ) ? $subscriptions : array( $subscriptions );
685
686
        foreach ( $subscriptions as $subscription ) {
687
            if ( $subscription->exists() ) {
688
                $duration = strtotime( $subscription->get_expiration() ) - strtotime( $subscription->get_date_created() );
689
                $expiry   = date( 'Y-m-d H:i:s', ( current_time( 'timestamp' ) + $duration ) );
690
691
                $subscription->set_next_renewal_date( $expiry );
692
                $subscription->set_date_created( current_time( 'mysql' ) );
693
                $subscription->set_profile_id( $invoice->generate_key( 'authnet_sub_' . $invoice->get_id() . '_' . $subscription->get_id() ) );
694
                $subscription->activate();
695
            }
696
        }
697
698
		// Redirect to the success page.
699
        wpinv_send_to_success_page( array( 'invoice_key' => $invoice->get_key() ) );
700
701
    }
702
703
	/**
704
	 * (Maybe) renews an authorize.net subscription profile.
705
	 *
706
	 *
707
	 * @param WPInv_Subscription $subscription
708
	 */
709
	public function maybe_renew_subscription( $subscription, $parent_invoice ) {
710
		// Ensure its our subscription && it's active.
711
		if ( ! empty( $parent_invoice ) && $this->id === $parent_invoice->get_gateway() && $subscription->has_status( 'active trialling' ) ) {
712
			$this->renew_subscription( $subscription );
713
		}
714
	}
715
716
    /**
717
	 * Renews a subscription.
718
	 *
719
     * @param WPInv_Subscription $subscription
720
	 */
721
	public function renew_subscription( $subscription ) {
722
723
		// Generate the renewal invoice.
724
		$new_invoice = $subscription->create_payment();
725
		$old_invoice = $subscription->get_parent_payment();
726
727
        if ( empty( $new_invoice ) ) {
728
            $old_invoice->add_note( __( 'Error generating a renewal invoice.', 'invoicing' ), false, false, false );
729
            $subscription->failing();
730
            return;
731
        }
732
733
        // Charge the payment method.
734
		$payment_profile_id = get_post_meta( $old_invoice->get_id(), 'getpaid_authorizenet_profile_id', true );
735
		$customer_profile   = get_user_meta( $old_invoice->get_user_id(), $this->get_customer_profile_meta_name( $old_invoice ), true );
736
		$result             = $this->charge_customer_payment_profile( $customer_profile, $payment_profile_id, $new_invoice );
0 ignored issues
show
It seems like $payment_profile_id can also be of type false; however, parameter $payment_profile_id of GetPaid_Authorize_Net_Ga...tomer_payment_profile() does only seem to accept string, 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

736
		$result             = $this->charge_customer_payment_profile( $customer_profile, /** @scrutinizer ignore-type */ $payment_profile_id, $new_invoice );
Loading history...
It seems like $customer_profile can also be of type false; however, parameter $customer_profile_id of GetPaid_Authorize_Net_Ga...tomer_payment_profile() does only seem to accept string, 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

736
		$result             = $this->charge_customer_payment_profile( /** @scrutinizer ignore-type */ $customer_profile, $payment_profile_id, $new_invoice );
Loading history...
737
738
		// Do we have an error?
739
		if ( is_wp_error( $result ) ) {
740
741
			$old_invoice->add_note(
742
				sprintf( __( 'Error renewing subscription : ( %s ).', 'invoicing' ), $result->get_error_message() ),
743
				true,
744
				false,
745
				true
746
			);
747
			$subscription->failing();
748
			return;
749
750
		}
751
752
		// Process the response.
753
		$this->process_charge_response( $result, $new_invoice );
0 ignored issues
show
$result of type WP_Error is incompatible with the type stdClass expected by parameter $result of GetPaid_Authorize_Net_Ga...ocess_charge_response(). ( Ignorable by Annotation )

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

753
		$this->process_charge_response( /** @scrutinizer ignore-type */ $result, $new_invoice );
Loading history...
754
755
		if ( wpinv_get_errors() ) {
756
757
			$old_invoice->add_note(
758
				sprintf( __( 'Error renewing subscription : ( %s ).', 'invoicing' ), getpaid_get_errors_html() ),
759
				true,
760
				false,
761
				true
762
			);
763
			$subscription->failing();
764
			return;
765
766
        }
767
768
        if ( ! $new_invoice->needs_payment() ) {
769
            $subscription->renew();
770
            $subscription->after_add_payment( $new_invoice );
771
        } else {
772
            $subscription->failing();
773
        }
774
    }
775
776
    /**
777
	 * Processes invoice addons.
778
	 *
779
	 * @param WPInv_Invoice $invoice
780
	 * @param GetPaid_Form_Item[] $items
781
	 * @return WPInv_Invoice
782
	 */
783
	public function process_addons( $invoice, $items ) {
784
785
        global $getpaid_authorize_addons;
786
787
        $getpaid_authorize_addons = array();
788
        foreach ( $items as $item ) {
789
790
            if ( is_null( $invoice->get_item( $item->get_id() ) ) && ! is_wp_error( $invoice->add_item( $item ) ) ) {
791
                $getpaid_authorize_addons[] = $item;
792
            }
793
}
794
795
        if ( empty( $getpaid_authorize_addons ) ) {
796
            return;
797
        }
798
799
        $invoice->recalculate_total();
800
801
        $payment_profile_id = get_post_meta( $invoice->get_id(), 'getpaid_authorizenet_profile_id', true );
802
		$customer_profile   = get_user_meta( $invoice->get_user_id(), $this->get_customer_profile_meta_name( $invoice ), true );
803
804
        add_filter( 'getpaid_authorizenet_charge_customer_payment_profile_args', array( $this, 'filter_addons_request' ), 10, 2 );
805
        $result = $this->charge_customer_payment_profile( $customer_profile, $payment_profile_id, $invoice );
0 ignored issues
show
It seems like $payment_profile_id can also be of type false; however, parameter $payment_profile_id of GetPaid_Authorize_Net_Ga...tomer_payment_profile() does only seem to accept string, 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

805
        $result = $this->charge_customer_payment_profile( $customer_profile, /** @scrutinizer ignore-type */ $payment_profile_id, $invoice );
Loading history...
It seems like $customer_profile can also be of type false; however, parameter $customer_profile_id of GetPaid_Authorize_Net_Ga...tomer_payment_profile() does only seem to accept string, 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

805
        $result = $this->charge_customer_payment_profile( /** @scrutinizer ignore-type */ $customer_profile, $payment_profile_id, $invoice );
Loading history...
806
        remove_filter( 'getpaid_authorizenet_charge_customer_payment_profile_args', array( $this, 'filter_addons_request' ) );
807
808
        if ( is_wp_error( $result ) ) {
809
            wpinv_set_error( $result->get_error_code(), $result->get_error_message() );
810
            return;
811
        }
812
813
        $invoice->save();
814
    }
815
816
    /**
817
	 * Processes invoice addons.
818
	 *
819
     * @param array $args
820
	 * @return array
821
	 */
822
    public function filter_addons_request( $args ) {
823
824
        global $getpaid_authorize_addons;
825
        $total = 0;
826
827
        foreach ( $getpaid_authorize_addons as $addon ) {
828
            $total += $addon->get_sub_total();
829
        }
830
831
        $args['createTransactionRequest']['transactionRequest']['amount'] = $total;
832
833
        if ( isset( $args['createTransactionRequest']['transactionRequest']['tax'] ) ) {
834
            unset( $args['createTransactionRequest']['transactionRequest']['tax'] );
835
        }
836
837
        return $args;
838
839
    }
840
841
    /**
842
     * Displays a notice on the checkout page if sandbox is enabled.
843
     */
844
    public function sandbox_notice() {
845
846
        return sprintf(
847
            __( 'SANDBOX ENABLED. You can use sandbox testing details only. See the %1$sAuthorize.NET Sandbox Testing Guide%2$s for more details.', 'invoicing' ),
848
            '<a href="https://developer.authorize.net/hello_world/testing_guide.html">',
849
            '</a>'
850
        );
851
852
    }
853
854
    /**
855
	 * Filters the gateway settings.
856
	 *
857
	 * @param array $admin_settings
858
	 */
859
	public function admin_settings( $admin_settings ) {
860
861
        $currencies = sprintf(
862
            __( 'Supported Currencies: %s', 'invoicing' ),
863
            implode( ', ', $this->currencies )
864
        );
865
866
        $admin_settings['authorizenet_active']['desc'] .= " ($currencies)";
867
        $admin_settings['authorizenet_desc']['std']     = __( 'Pay securely using your credit or debit card.', 'invoicing' );
868
869
        $admin_settings['authorizenet_login_id'] = array(
870
            'type' => 'text',
871
            'id'   => 'authorizenet_login_id',
872
            'name' => __( 'API Login ID', 'invoicing' ),
873
            'desc' => '<a href="https://support.authorize.net/knowledgebase/Knowledgearticle/?code=000001271"><em>' . __( 'How do I obtain my API Login ID and Transaction Key?', 'invoicing' ) . '</em></a>',
874
        );
875
876
        $admin_settings['authorizenet_transaction_key'] = array(
877
            'type' => 'text',
878
            'id'   => 'authorizenet_transaction_key',
879
            'name' => __( 'Transaction Key', 'invoicing' ),
880
        );
881
882
        $admin_settings['authorizenet_signature_key'] = array(
883
            'type' => 'text',
884
            'id'   => 'authorizenet_signature_key',
885
            'name' => __( 'Signature Key', 'invoicing' ),
886
            'desc' => '<a href="https://support.authorize.net/knowledgebase/Knowledgearticle/?code=000001271"><em>' . __( 'Learn more.', 'invoicing' ) . '</em></a>',
887
        );
888
889
        $admin_settings['authorizenet_ipn_url'] = array(
890
            'type'     => 'ipn_url',
891
            'id'       => 'authorizenet_ipn_url',
892
            'name'     => __( 'Webhook URL', 'invoicing' ),
893
            'std'      => $this->notify_url,
894
            'desc'     => __( 'Create a new webhook using this URL as the endpoint URL and set it to receive all payment events.', 'invoicing' ) . ' <a href="https://support.authorize.net/knowledgebase/Knowledgearticle/?code=000001542"><em>' . __( 'Learn more.', 'invoicing' ) . '</em></a>',
895
            'custom'   => 'authorizenet',
896
            'readonly' => true,
897
        );
898
899
		return $admin_settings;
900
	}
901
902
}
903