GetPaid_Authorize_Net_Gateway   F
last analyzed

Complexity

Total Complexity 82

Size/Duplication

Total Lines 887
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 348
dl 0
loc 887
rs 2
c 0
b 0
f 0
wmc 82

23 Methods

Rating   Name   Duplication   Size   Complexity  
A charge_customer_payment_profile() 0 42 2
A payment_fields() 0 7 1
A retrieve_payment_profile_from_cache() 0 21 4
A __construct() 0 9 1
B create_customer_payment_profile() 0 85 7
B create_customer_profile() 0 79 7
A get_customer_payment_profile() 0 12 1
A add_payment_profile_to_cache() 0 8 2
A get_customer_profile() 0 11 1
A get_payment_information() 0 7 1
B get_line_items() 0 34 9
A process_payment() 0 32 4
A process_subscription() 0 24 5
A process_initial_payment() 0 17 3
A validate_submission_data() 0 28 6
A get_customer_profile_meta_name() 0 2 2
A process_charge_response() 0 38 6
A maybe_renew_subscription() 0 4 4
A filter_addons_request() 0 16 3
A process_addons() 0 31 6
A renew_subscription() 0 52 5
A admin_settings() 0 41 1
A sandbox_notice() 0 6 1

How to fix   Complexity   

Complex Class

Complex classes like GetPaid_Authorize_Net_Gateway often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use GetPaid_Authorize_Net_Gateway, and based on these observations, apply Extract Interface, too.

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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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...
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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...
Bug introduced by
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
Unused Code introduced by
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
Bug introduced by
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
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...
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
Bug introduced by
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...
Bug introduced by
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
Bug introduced by
$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
Bug introduced by
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...
Bug introduced by
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
Bug introduced by
$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
Bug introduced by
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...
Bug introduced by
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