Passed
Pull Request — master (#390)
by Brian
04:21
created

GetPaid_Authorize_Net_Gateway::sandbox_notice()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 6
c 1
b 0
f 0
dl 0
loc 10
rs 10
cc 3
nc 2
nop 2
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_Payment_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( 'subscription', 'sandbox', 'tokens' );
28
29
    /**
30
	 * Payment method order.
31
	 *
32
	 * @var int
33
	 */
34
    public $order = 4;
35
36
    /**
37
	 * Endpoint for requests from Authorize.net.
38
	 *
39
	 * @var string
40
	 */
41
	protected $notify_url;
42
43
	/**
44
	 * Endpoint for requests to Authorize.net.
45
	 *
46
	 * @var string
47
	 */
48
    protected $endpoint;
49
    
50
    /**
51
	 * Currencies this gateway is allowed for.
52
	 *
53
	 * @var array
54
	 */
55
	public $currencies = array( 'USD', 'CAD', 'GBP', 'DKK', 'NOK', 'PLN', 'SEK', 'AUD', 'EUR', 'NZD' );
56
57
    /**
58
	 * URL to view a transaction.
59
	 *
60
	 * @var string
61
	 */
62
    public $view_transaction_url = 'https://{sandbox}authorize.net/ui/themes/sandbox/Transaction/TransactionReceipt.aspx?transid=%s';
63
64
    /**
65
	 * Class constructor.
66
	 */
67
	public function __construct() {
68
69
        $this->title                = __( 'Credit Card / Debit Card', 'invoicing' );
70
        $this->method_title         = __( 'Authorize.Net', 'invoicing' );
71
        $this->notify_url           = wpinv_get_ipn_url( $this->id );
72
73
        add_filter( 'wpinv_renew_authorizenet_subscription_profile', array( $this, 'renew_subscription' ) );
74
        add_filter( 'wpinv_gateway_description', array( $this, 'sandbox_notice' ), 10, 2 );
75
        parent::__construct();
76
    }
77
78
    /**
79
	 * Displays the payment method select field.
80
	 * 
81
	 * @param int $invoice_id 0 or invoice id.
82
	 * @param GetPaid_Payment_Form $form Current payment form.
83
	 */
84
    public function payment_fields( $invoice_id, $form ) {
85
86
        // Let the user select a payment method.
87
        echo $this->saved_payment_methods();
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->saved_payment_methods() targeting GetPaid_Payment_Gateway::saved_payment_methods() seems to always return null.

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

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

}

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

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

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

Loading history...
88
89
        // Show the credit card entry form.
90
        echo $this->new_payment_method_entry( $this->get_cc_form( true ) );
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->new_payment_metho...his->get_cc_form(true)) targeting GetPaid_Payment_Gateway:..._payment_method_entry() seems to always return null.

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

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

}

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

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

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

Loading history...
91
    }
92
93
    /**
94
	 * Creates a customer profile.
95
	 *
96
	 *
97
	 * @param WPInv_Invoice $invoice Invoice.
98
     * @param array $submission_data Posted checkout fields.
99
     * @param bool $save Whether or not to save the payment as a token.
100
     * @link https://developer.authorize.net/api/reference/index.html#customer-profiles-create-customer-profile
101
	 * @return string|WP_Error Payment profile id.
102
	 */
103
	public function create_customer_profile( $invoice, $submission_data, $save = true ) {
104
105
        // Remove non-digits from the number
106
        $submission_data['authorizenet']['cc_number'] = preg_replace('/\D/', '', $submission_data['authorizenet']['cc_number'] );
107
108
        // Generate args.
109
        $args = array(
110
            'createCustomerProfileRequest' => array(
111
                'merchantAuthentication'   => $this->get_auth_params(),
112
                'profile'                  => array(
113
                    'merchantCustomerId'   => getpaid_limit_length( $invoice->get_user_id(), 20 ),
114
                    'description'          => getpaid_limit_length( $invoice->get_full_name(), 255 ),
115
                    'email'                => getpaid_limit_length( $invoice->get_email(), 255 ),
116
                    'paymentProfiles'      => array(
117
                        'customerType'     => 'individual',
118
119
                        // Billing information.
120
                        'billTo'           => array(
121
                            'firstName'    => getpaid_limit_length( $invoice->get_first_name(), 50 ),
122
                            'lastName'     => getpaid_limit_length( $invoice->get_last_name(), 50 ),
123
                            'address'      => getpaid_limit_length( $invoice->get_last_name(), 60 ),
124
                            'city'         => getpaid_limit_length( $invoice->get_city(), 40 ),
125
                            'state'        => getpaid_limit_length( $invoice->get_state(), 40 ),
126
                            'zip'          => getpaid_limit_length( $invoice->get_zip(), 20 ),
127
                            'country'      => getpaid_limit_length( $invoice->get_country(), 60 ),
128
                        ),
129
130
                        // Payment information.
131
                        'payment'          => $this->get_payment_information( $submission_data['authorizenet'] ),
132
                    )
133
                ),
134
                'validationMode'           => $this->is_sandbox( $invoice ) ? 'testMode' : 'liveMode', 
135
            )
136
        );
137
138
        $response = $this->post( apply_filters( 'getpaid_authorizenet_customer_profile_args', $args, $invoice ), $invoice );
139
140
        if ( is_wp_error( $response ) ) {
141
            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...
142
        }
143
144
        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...
145
146
        // Save the payment token.
147
        if ( $save ) {
148
            $this->save_token(
149
                array(
150
                    'id'      => $response->customerPaymentProfileIdList[0],
0 ignored issues
show
Bug introduced by
The property customerPaymentProfileIdList does not seem to exist on WP_Error.
Loading history...
151
                    'name'    => $this->get_card_name( $submission_data['authorizenet']['cc_number'] ) . '&middot;&middot;&middot;&middot;' . substr( $submission_data['authorizenet']['cc_number'], -4 ),
152
                    'default' => true,
153
                    'type'    => $this->is_sandbox( $invoice ) ? 'sandbox' : 'live',
154
                )
155
            );
156
        }
157
158
        // Add a note about the validation response.
159
        $invoice->add_note(
160
            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...
161
            false,
162
            false,
163
            true
164
        );
165
166
        return $response->customerPaymentProfileIdList[0];
167
    }
168
169
    /**
170
	 * Retrieves a customer profile.
171
	 *
172
	 *
173
	 * @param string $profile_id profile id.
174
	 * @return string|WP_Error Profile id.
175
     * @link https://developer.authorize.net/api/reference/index.html#customer-profiles-get-customer-profile
176
	 */
177
	public function get_customer_profile( $profile_id ) {
178
179
        // Generate args.
180
        $args = array(
181
            'getCustomerProfileRequest'  => array(
182
                'merchantAuthentication' => $this->get_auth_params(),
183
                'customerProfileId'      => $profile_id,
184
            )
185
        );
186
187
        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_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

187
        return $this->post( $args, /** @scrutinizer ignore-type */ false );
Loading history...
188
189
    }
190
191
    /**
192
	 * Creates a customer profile.
193
	 *
194
	 *
195
     * @param string $profile_id profile id.
196
	 * @param WPInv_Invoice $invoice Invoice.
197
     * @param array $submission_data Posted checkout fields.
198
     * @param bool $save Whether or not to save the payment as a token.
199
     * @link https://developer.authorize.net/api/reference/index.html#customer-profiles-create-customer-profile
200
	 * @return string|WP_Error Profile id.
201
	 */
202
	public function create_customer_payment_profile( $customer_profile, $invoice, $submission_data, $save ) {
203
204
        // Remove non-digits from the number
205
        $submission_data['authorizenet']['cc_number'] = preg_replace('/\D/', '', $submission_data['authorizenet']['cc_number'] );
206
207
        // Generate args.
208
        $args = array(
209
            'createCustomerPaymentProfileRequest' => array(
210
                'merchantAuthentication'   => $this->get_auth_params(),
211
                'customerProfileId'        => $customer_profile,
212
                'paymentProfile'           => array(
213
214
                    // Billing information.
215
                    'billTo'           => array(
216
                        'firstName'    => getpaid_limit_length( $invoice->get_first_name(), 50 ),
217
                        'lastName'     => getpaid_limit_length( $invoice->get_last_name(), 50 ),
218
                        'address'      => getpaid_limit_length( $invoice->get_last_name(), 60 ),
219
                        'city'         => getpaid_limit_length( $invoice->get_city(), 40 ),
220
                        'state'        => getpaid_limit_length( $invoice->get_state(), 40 ),
221
                        'zip'          => getpaid_limit_length( $invoice->get_zip(), 20 ),
222
                        'country'      => getpaid_limit_length( $invoice->get_country(), 60 ),
223
                    ),
224
225
                    // Payment information.
226
                    'payment'          => $this->get_payment_information( $submission_data['authorizenet'] )
227
                ),
228
                'validationMode'       => $this->is_sandbox( $invoice ) ? 'testMode' : 'liveMode', 
229
            )
230
        );
231
232
        $response = $this->post( apply_filters( 'getpaid_authorizenet_create_customer_payment_profile_args', $args, $invoice ), $invoice );
233
234
        if ( is_wp_error( $response ) ) {
235
            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...
236
        }
237
238
        // Save the payment token.
239
        if ( $save ) {
240
            $this->save_token(
241
                array(
242
                    'id'      => $response->customerPaymentProfileId,
0 ignored issues
show
Bug introduced by
The property customerPaymentProfileId does not seem to exist on WP_Error.
Loading history...
243
                    'name'    => $this->get_card_name( $submission_data['authorizenet']['cc_number'] ) . ' &middot;&middot;&middot;&middot; ' . substr( $submission_data['authorizenet']['cc_number'], -4 ),
244
                    'default' => true
245
                )
246
            );
247
        }
248
249
        // Add a note about the validation response.
250
        $invoice->add_note(
251
            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...
252
            false,
253
            false,
254
            true
255
        );
256
        
257
258
        return $response->customerPaymentProfileId;
259
    }
260
261
    /**
262
	 * Retrieves a customer payment profile.
263
	 *
264
	 *
265
	 * @param string $customer_profile_id customer profile id.
266
     * @param string $payment_profile_id payment profile id.
267
	 * @return string|WP_Error Profile id.
268
     * @link https://developer.authorize.net/api/reference/index.html#customer-profiles-get-customer-payment-profile
269
	 */
270
	public function get_customer_payment_profile( $customer_profile_id, $payment_profile_id ) {
271
272
        // Generate args.
273
        $args = array(
274
            'getCustomerPaymentProfileRequest' => array(
275
                'merchantAuthentication'       => $this->get_auth_params(),
276
                'customerProfileId'            => $customer_profile_id,
277
                'customerPaymentProfileId'     => $payment_profile_id,
278
            )
279
        );
280
281
        return $this->post( $args, false );
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type WPInv_Invoice expected by parameter $invoice of GetPaid_Authorize_Net_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

281
        return $this->post( $args, /** @scrutinizer ignore-type */ false );
Loading history...
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...
282
283
    }
284
285
    /**
286
	 * Charges a customer payment profile.
287
	 *
288
     * @param string $customer_profile_id customer profile id.
289
     * @param string $payment_profile_id payment profile id.
290
	 * @param WPInv_Invoice $invoice Invoice.
291
     * @link https://developer.authorize.net/api/reference/index.html#payment-transactions-charge-a-customer-profile
292
	 * @return WP_Error|object
293
	 */
294
	public function charge_customer_payment_profile( $customer_profile_id, $payment_profile_id, $invoice ) {
295
296
        // Generate args.
297
        $args = array(
298
299
            'createTransactionRequest'         => array(
300
301
                'merchantAuthentication'       => $this->get_auth_params(),
302
                'refId'                        => $invoice->get_id(),
303
                'transactionRequest'           => array(
304
                    'transactionType'          => 'authCaptureTransaction',
305
                    'amount'                   => $invoice->get_total(),
306
                    'currencyCode'             => $invoice->get_currency(),
307
                    'profile'                  => array(
308
                        'customerProfileId'    => $customer_profile_id,
309
                        'paymentProfile'       => array(
310
                            'paymentProfileId' => $payment_profile_id,
311
                        )
312
                    ),
313
                    'order'                    => array(
314
                        'invoiceNumber'        => getpaid_limit_length( $invoice->get_number(), 20 ),
315
                    ),
316
                    'lineItems'                => array( 'lineItem' => $this->get_line_items( $invoice ) ),
317
                    'tax'                      => array(
318
                        'amount'               => $invoice->get_total_tax(),
319
                        'name'                 => getpaid_tax()->get_vat_name(),
320
                    ),
321
                    'poNumber'                 => getpaid_limit_length( $invoice->get_number(), 25 ),
322
                    'customer'                 => array(
323
                        'id'                   => getpaid_limit_length( $invoice->get_user_id(), 25 ),
324
                        'email'                => getpaid_limit_length( $invoice->get_email(), 25 ),
325
                    ),
326
                    'customerIP'               => $invoice->get_ip(),
327
                )
328
            )
329
        );
330
log_noptin_message( $args );
0 ignored issues
show
Bug introduced by
The function log_noptin_message was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

330
/** @scrutinizer ignore-call */ 
331
log_noptin_message( $args );
Loading history...
331
        return $this->post( apply_filters( 'getpaid_authorizenet_charge_customer_payment_profile_args', $args, $invoice ), $invoice );
332
333
    }
334
335
    /**
336
	 * Processes a customer charge.
337
	 *
338
     * @param stdClass $result Api response.
339
	 * @param WPInv_Invoice $invoice Invoice.
340
	 */
341
	public function process_charge_response( $result, $invoice ) {
342
343
        wpinv_clear_errors();
344
345
        switch ( (int) $result->transactionResponse->responseCode ) {
346
347
            case 1:
348
            case 4:
349
350
                if ( ! empty( $result->transactionResponse->transId ) ) {
351
                    $invoice->set_transaction_id( $result->transactionResponse->transId );
352
                }
353
354
                if ( 1 == (int) $result->transactionResponse->responseCode ) {
355
                    $invoice->mark_paid();
356
                } else {
357
                    $invoice->set_status( 'wpi-onhold' );
358
                    $invoice->add_note( 
359
                        sprintf(
360
                            __( 'Held for review: %s', 'invoicing' ),
361
                            $result->transactionResponse->messages->message[0]->description
362
                        )
363
                    );
364
                }
365
366
                $invoice->add_note( sprintf( __( 'Authentication code: %s (%s).', 'invoicing' ), $result->transactionResponse->authCode, $result->transactionResponse->accountNumber ), false, false, true );
367
                $invoice->save();
368
369
                return;
370
371
            case 2:
372
            case 3:
373
                wpinv_set_error( 'card_declined', __( 'Credit card declined.', 'invoicing' ) );
374
375
                if ( ! empty( $result->transactionResponse->errors ) ) {
376
                    $errors = (object) $result->transactionResponse->errors;
377
                    wpinv_set_error( $errors->error[0]->errorCode, $errors->error[0]->errorText );
378
                }
379
380
                return;
381
382
        }
383
384
    }
385
386
    /**
387
	 * Returns payment information.
388
	 *
389
	 *
390
	 * @param array $card Card details.
391
	 * @return array
392
	 */
393
	public function get_payment_information( $card ) {
394
        return array(
395
396
            'creditCard'         => array (
397
                'cardNumber'     => $card['cc_number'],
398
                'expirationDate' => $card['cc_expire_year'] . '-' . $card['cc_expire_month'],
399
                'cardCode'       => $card['cc_cvv2'],
400
            )
401
402
        );
403
    }
404
405
    /**
406
	 * Returns the card name.
407
	 *
408
	 *
409
	 * @param string $card_number Card number.
410
	 * @return string
411
	 */
412
	public function get_card_name( $card_number ) {
413
414
        switch( $card_number ) {
415
416
            case( preg_match ( '/^4/', $card_number ) >= 1 ):
417
                return __( 'Visa', 'invoicing' );
418
419
            case( preg_match ( '/^5[1-5]/', $card_number ) >= 1 ):
420
                return __( 'Mastercard', 'invoicing' );
421
422
            case( preg_match ( '/^3[47]/', $card_number ) >= 1 ):
423
                return __( 'Amex', 'invoicing' );
424
425
            case( preg_match ( '/^3(?:0[0-5]|[68])/', $card_number ) >= 1 ):
426
                return __( 'Diners Club', 'invoicing' );
427
428
            case( preg_match ( '/^6(?:011|5)/', $card_number ) >= 1 ):
429
                return __( 'Discover', 'invoicing' );
430
431
            case( preg_match ( '/^(?:2131|1800|35\d{3})/', $card_number ) >= 1 ):
432
                return __( 'JCB', 'invoicing' );
433
434
            default:
435
            return __( 'Card', 'invoicing' );
436
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
437
        }
438
439
    }
440
441
    /**
442
	 * Returns the customer profile meta name.
443
	 *
444
	 *
445
	 * @param WPInv_Invoice $invoice Invoice.
446
	 * @return string
447
	 */
448
	public function get_customer_profile_meta_name( $invoice ) {
449
        return $this->is_sandbox( $invoice ) ? 'getpaid_authorizenet_sandbox_customer_profile_id' : 'getpaid_authorizenet_customer_profile_id';
450
    }
451
452
    /**
453
	 * Returns the API URL.
454
	 *
455
	 *
456
	 * @param WPInv_Invoice $invoice Invoice.
457
	 * @return string
458
	 */
459
	public function get_api_url( $invoice ) {
460
        return $this->is_sandbox( $invoice ) ? 'https://apitest.authorize.net/xml/v1/request.api' : 'https://api.authorize.net/xml/v1/request.api';
461
    }
462
463
    /**
464
	 * Returns the API authentication params.
465
	 *
466
	 *
467
	 * @return array
468
	 */
469
	public function get_auth_params() {
470
471
        return array(
472
            'name'           => $this->get_option( 'login_id' ),
473
            'transactionKey' => $this->get_option( 'transaction_key' ),
474
        );
475
476
    }
477
478
    /**
479
	 * Validates the submitted data.
480
	 *
481
	 *
482
	 * @param array $submission_data Posted checkout fields.
483
     * @param WPInv_Invoice $invoice
484
	 * @return WP_Error|string The payment profile id
485
	 */
486
	public function validate_submission_data( $submission_data, $invoice ) {
487
488
        // Validate authentication details.
489
        $auth = $this->get_auth_params();
490
491
        if ( empty( $auth['name'] ) || empty( $auth['transactionKey'] ) ) {
492
            return new WP_Error( 'invalid_settings', __( 'This gateway has not been set up.', 'invoicing') );
493
        }
494
495
        // Validate the payment method.
496
        if ( empty( $submission_data['getpaid-authorizenet-payment-method'] ) ) {
497
            return new WP_Error( 'invalid_payment_method', __( 'Please select a different payment method or add a new card.', 'invoicing') );
498
        }
499
500
        // Are we adding a new payment method?
501
        if ( 'new' != $submission_data['getpaid-authorizenet-payment-method'] ) {
502
            return $submission_data['getpaid-authorizenet-payment-method'];
503
        }
504
505
        // Retrieve the customer profile id.
506
        $profile_id = get_user_meta( $invoice->get_user_id(), $this->get_customer_profile_meta_name( $invoice ), true );
507
508
        // Create payment method.
509
        if ( empty( $profile_id ) ) {
510
            return $this->create_customer_profile( $invoice, $submission_data, ! empty( $submission_data['getpaid-authorizenet-new-payment-method'] ) );
511
        }
512
513
        return $this->create_customer_payment_profile( $profile_id, $invoice, $submission_data, ! empty( $submission_data['getpaid-authorizenet-new-payment-method'] ) );
514
515
    }
516
517
    /**
518
	 * Returns invoice line items.
519
	 *
520
	 *
521
	 * @param WPInv_Invoice $invoice Invoice.
522
	 * @return array
523
	 */
524
	public function get_line_items( $invoice ) {
525
        $items = array();
526
527
        foreach ( $invoice->get_items() as $item ) {
528
529
            $items[] = array(
530
                'itemId'      => getpaid_limit_length( $item->get_id(), 31 ),
531
                'name'        => getpaid_limit_length( $item->get_raw_name(), 31 ),
532
                'description' => getpaid_limit_length( $item->get_description(), 255 ),
533
                'quantity'    => (int) $invoice->get_template() == 'amount' ? 1 : $item->get_quantity(),
534
                'unitPrice'   => (float) $item->get_price(),
535
                'taxable'     => wpinv_use_taxes() && $invoice->is_taxable() && 'tax-exempt' != $item->get_vat_rule(),
536
            );
537
538
        }
539
540
        return $items;
541
    }
542
543
    /**
544
	 * Communicates with authorize.net
545
	 *
546
	 *
547
	 * @param array $post Data to post.
548
     * @param WPInv_Invoice $invoice Invoice.
549
	 * @return stdClass|WP_Error
550
	 */
551
    public function post( $post, $invoice ){
552
553
        $url      = $this->get_api_url( $invoice );
554
        $response = wp_remote_post(
555
            $url,
556
            array(
557
                'headers'          => array(
558
                    'Content-Type' => 'application/json; charset=utf-8'
559
                ),
560
                'body'             => json_encode( $post ),
561
                'method'           => 'POST'
562
            )
563
        );
564
565
        if ( is_wp_error( $response ) ) {
566
            return $response;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $response also could return the type array which is incompatible with the documented return type WP_Error|stdClass.
Loading history...
567
        }
568
569
        $response = wp_unslash( wp_remote_retrieve_body( $response ) );
570
        $response = preg_replace('/\xEF\xBB\xBF/', '', $response); // https://community.developer.authorize.net/t5/Integration-and-Testing/JSON-issues/td-p/48851
571
        $response = json_decode( $response );
572
573
        if ( empty( $response ) ) {
574
            return new WP_Error( 'invalid_reponse', __( 'Invalid response', 'invoicing' ) );
575
        }
576
577
        if ( $response->messages->resultCode == 'Error' ) {
578
579
            if ( ! empty( $response->transactionResponse ) && ! empty( $response->transactionResponse->errors ) ) {
580
                $error = $response->transactionResponse->errors[0];
581
                return new WP_Error( $error->errorCode, $error->errorText );
582
            }
583
584
            return new WP_Error( $response->messages->message[0]->code, $response->messages->message[0]->text );
585
        }
586
587
        return $response;
588
589
    }
590
591
    /**
592
	 * Process Payment.
593
	 *
594
	 *
595
	 * @param WPInv_Invoice $invoice Invoice.
596
	 * @param array $submission_data Posted checkout fields.
597
	 * @param GetPaid_Payment_Form_Submission $submission Checkout submission.
598
	 * @return array
599
	 */
600
	public function process_payment( $invoice, $submission_data, $submission ) {
601
602
        // Validate the submitted data.
603
        $payment_profile_id = $this->validate_submission_data( $submission_data, $invoice );
604
605
        // Do we have an error?
606
        if ( is_wp_error( $payment_profile_id ) ) {
607
            wpinv_set_error( $payment_profile_id->get_error_code(), $payment_profile_id->get_error_message() );
608
            wpinv_send_back_to_checkout();
609
        }
610
611
        // Save the payment method to the order.
612
        update_post_meta( $invoice->get_id(), 'getpaid_authorizenet_profile_id', $payment_profile_id );
613
614
        // Check if this is a subscription or not.
615
        if ( $invoice->is_recurring() && $subscription = wpinv_get_subscription( $invoice ) ) {
616
            $this->process_subscription( $invoice, $subscription );
0 ignored issues
show
Bug introduced by
It seems like $subscription can also be of type true; however, parameter $subscription of GetPaid_Authorize_Net_Ga...:process_subscription() does only seem to accept WPInv_Subscription, 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

616
            $this->process_subscription( $invoice, /** @scrutinizer ignore-type */ $subscription );
Loading history...
617
        }
618
619
        // If it is free, send to the success page.
620
        if ( ! $invoice->needs_payment() ) {
621
            $invoice->mark_paid();
622
            wpinv_send_to_success_page( array( 'invoice_key' => $invoice->get_key() ) );
623
        }
624
625
        // Charge the payment profile.
626
        $customer_profile = get_user_meta( $invoice->get_user_id(), $this->get_customer_profile_meta_name( $invoice ), true );
627
        $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 WP_Error; 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

627
        $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

627
        $result           = $this->charge_customer_payment_profile( /** @scrutinizer ignore-type */ $customer_profile, $payment_profile_id, $invoice );
Loading history...
628
629
        // Do we have an error?
630
        if ( is_wp_error( $result ) ) {
631
            wpinv_set_error( $result->get_error_code(), $result->get_error_message() );
632
            wpinv_send_back_to_checkout();
633
        }
634
635
        // Process the response.
636
        $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

636
        $this->process_charge_response( /** @scrutinizer ignore-type */ $result, $invoice );
Loading history...
637
638
        if ( wpinv_get_errors() ) {
639
            wpinv_send_back_to_checkout();
640
        }
641
642
        wpinv_send_to_success_page( array( 'invoice_key' => $invoice->get_key() ) );
643
644
        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...
645
646
    }
647
648
    /**
649
	 * Processes recurring payments.
650
	 *
651
     * @param WPInv_Invoice $invoice Invoice.
652
     * @param WPInv_Subscription $subscription Subscription.
653
	 */
654
	public function process_subscription( $invoice, $subscription ) {
655
656
        // Check if there is an initial amount to charge.
657
        if ( (float) $invoice->get_total() > 0 ) {
658
659
            // Retrieve the payment method.
660
            $payment_profile_id = get_post_meta( $invoice->get_id(), 'getpaid_authorizenet_profile_id', true );
661
            $customer_profile   = get_user_meta( $invoice->get_user_id(), $this->get_customer_profile_meta_name( $invoice ), true );
662
            $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

662
            $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

662
            $result             = $this->charge_customer_payment_profile( /** @scrutinizer ignore-type */ $customer_profile, $payment_profile_id, $invoice );
Loading history...
663
664
            // Do we have an error?
665
            if ( is_wp_error( $result ) ) {
666
                wpinv_set_error( $result->get_error_code(), $result->get_error_message() );
667
                wpinv_send_back_to_checkout();
668
            }
669
670
            // Process the response.
671
            $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

671
            $this->process_charge_response( /** @scrutinizer ignore-type */ $result, $invoice );
Loading history...
672
673
            if ( wpinv_get_errors() ) {
674
                wpinv_send_back_to_checkout();
675
            }
676
677
        }
678
679
        // Recalculate the new subscription expiry.
680
        $duration = strtotime( $subscription->expiration ) - strtotime( $subscription->created );
681
        $expiry   = date( 'Y-m-d H:i:s', ( current_time( 'timestamp' ) + $duration ) );
682
683
        // Schedule an action to run when the subscription expires.
684
        $action_id = as_schedule_single_action(
685
            strtotime( $expiry ),
686
            'wpinv_renew_authorizenet_subscription_profile',
687
            array( $invoice->get_id() ),
688
            'invoicing'
689
        );
690
691
        // Update the subscription.
692
        $subscription->update( 
693
            array(
694
                'profile_id' => $action_id,
695
                'status'     => 'trialling' == $subscription->status ? 'trialling' : 'active',
696
                'created'    => current_time( 'mysql' ),
697
                'expiration' => $expiry,
698
            )
699
        );
700
701
        wpinv_send_to_success_page( array( 'invoice_key' => $invoice->get_key() ) );
702
703
    }
704
705
    /**
706
	 * Renews a subscription.
707
	 *
708
     * @param int $invoice Invoice id.
709
	 */
710
	public function renew_subscription( $invoice ) {
711
712
        // Retrieve the subscription.
713
        $subscription = wpinv_get_subscription( $invoice );
714
        if ( empty( $subscription ) ) {
715
            return;
716
        }
717
718
        // Abort if it is canceled or complete.
719
        if ( $subscription->status == 'completed' || $subscription->status == 'cancelled' ) {
720
            return;
721
        }
722
723
        // Retrieve the invoice.
724
        $invoice = new WPInv_Invoice( $invoice );
725
726
        // If we have not maxed out on bill times...
727
        $times_billed = $subscription->get_times_billed();
728
        $max_bills    = $subscription->bill_times;
729
730
        if ( empty( $max_bills ) || $max_bills > $times_billed ) {
731
732
            $new_invoice = $subscription->create_payment();
733
734
            if ( empty( $new_invoice ) ) {
735
                $invoice->add_note( __( 'Error generating a renewal invoice.', 'invoicing' ), false, false, false );
736
                $subscription->failing();
737
                return;
738
            }
739
740
            // retrieve the payment method.
741
            $payment_profile_id = get_post_meta( $invoice->get_id(), 'getpaid_authorizenet_profile_id', true );
742
            $customer_profile   = get_user_meta( $invoice->get_user_id(), $this->get_customer_profile_meta_name( $invoice ), true );
743
            $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

743
            $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

743
            $result             = $this->charge_customer_payment_profile( /** @scrutinizer ignore-type */ $customer_profile, $payment_profile_id, $new_invoice );
Loading history...
744
745
            // Do we have an error?
746
            if ( is_wp_error( $result ) ) {
747
                $invoice->add_note(
748
                    sprintf( __( 'Error renewing subscription : ( %s ).', 'invoicing' ), $result->get_error_message() ),
749
                    true,
750
                    false,
751
                    true
752
                );
753
                $subscription->failing();
754
                return;
755
            }
756
757
            // Process the response.
758
            $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

758
            $this->process_charge_response( /** @scrutinizer ignore-type */ $result, $new_invoice );
Loading history...
759
760
            if ( wpinv_get_errors() ) {
761
762
                $invoice->add_note(
763
                    sprintf( __( 'Error renewing subscription : ( %s ).', 'invoicing' ), getpaid_get_errors_html() ),
764
                    true,
765
                    false,
766
                    true
767
                );
768
                $subscription->failing();
769
                return;
770
771
            }
772
773
            // Renew the subscription.
774
            $subscription->add_payment(
775
                array(
776
                    'transaction_id' => $new_invoice->get_transaction_id(),
777
                    'gateway'        => $this->id
778
                ),
779
                $new_invoice
780
            );
781
782
            // Renew/Complete the subscription.
783
            $subscription->renew();
784
785
            if ( 'completed' != $subscription->status ) {
786
787
                // Schedule an action to run when the subscription expires.
788
                $action_id = as_schedule_single_action(
789
                    strtotime( $subscription->expiration ),
790
                    'wpinv_renew_authorizenet_subscription_profile',
791
                    array( $invoice->get_id() ),
792
                    'invoicing'
793
                );
794
    
795
                $subscription->update( array( 'profile_id' => $action_id, ) );
796
    
797
            }
798
799
        }
800
    }
801
802
    /**
803
	 * Cancels a subscription remotely
804
	 *
805
	 *
806
	 * @param WPInv_Subscription $subscription Subscription.
807
     * @param WPInv_Invoice $invoice Invoice.
808
	 */
809
	public function cancel_subscription( $subscription, $invoice ) {
810
811
        if ( as_unschedule_action( 'wpinv_renew_authorizenet_subscription_profile', array( $invoice->get_id() ), 'invoicing' ) ) {
812
            return;
813
        }
814
815
        // Backwards compatibility.
816
        $this->post(
817
            array(
818
                'ARBCancelSubscriptionRequest' => array(
819
                    'merchantAuthentication'   => $this->get_auth_params(),
820
                    'subscriptionId'           => $subscription->profile_id,
821
                )
822
            ),
823
            $invoice
824
        );
825
826
    }
827
828
    /**
829
	 * Processes ipns.
830
	 *
831
	 * @return void
832
	 */
833
	public function verify_ipn() {
834
835
        $this->maybe_process_old_ipn();
836
837
        // Validate the IPN.
838
        if ( empty( $_POST ) || ! $this->validate_ipn() ) {
839
		    wp_die( 'Authorize.NET IPN Request Failure', 'Authorize.NET IPN', array( 'response' => 500 ) );
840
        }
841
842
        // Event type.
843
        $posted = json_decode( file_get_contents('php://input') );
844
        if ( empty( $posted ) ) {
845
            wp_die( 'Invalid JSON', 'Authorize.NET IPN', array( 'response' => 500 ) );
846
        }
847
848
        // Process the IPN.
849
        $posted = (object) wp_unslash( $posted );
850
851
        // Process refunds.
852
        if ( 'net.authorize.payment.refund.created' == $posted->eventType ) {
853
            $invoice = new WPInv_Invoice( $posted->payload->merchantReferenceId );
854
855
            if ( $invoice->get_id() && $posted->payload->id == $invoice->get_transaction_id() ) {
856
                $invoice->refund();
857
            }
858
859
        }
860
861
        // Held funds approved.
862
        if ( 'net.authorize.payment.fraud.approved' == $posted->eventType ) {
863
            $invoice = new WPInv_Invoice( $posted->payload->id );
864
865
            if ( $invoice->get_id() && $posted->payload->id == $invoice->get_transaction_id() ) {
866
                $invoice->mark_paid( false, __( 'Payment released', 'invoicing' ));
867
            }
868
869
        }
870
871
        // Held funds declined.
872
        if ( 'net.authorize.payment.fraud.declined' == $posted->eventType ) {
873
            $invoice = new WPInv_Invoice( $posted->payload->id );
874
875
            if ( $invoice->get_id() && $posted->payload->id == $invoice->get_transaction_id() ) {
876
                $invoice->set_status( 'wpi-failed', __( 'Payment desclined', 'invoicing' ) );
877
                $invoice->save();
878
            }
879
880
        }
881
882
        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...
883
884
    }
885
886
    /**
887
	 * Backwards compatibility.
888
	 *
889
	 * @return void
890
	 */
891
	public function maybe_process_old_ipn() {
892
893
        // Ensure that we are using the old subscriptions.
894
        if ( empty( $_POST['x_subscription_id'] ) ) {
895
            return;
896
        }
897
898
        // Check validity.
899
        $signature = $this->get_option( 'signature_key' );
900
        if ( ! empty( $signature ) ) {
901
            $login_id  = $this->get_option( 'login_id' );
902
            $trans_id  = $_POST['x_trans_id'];
903
            $amount    = $_POST['x_amount'];
904
            $hash      = hash_hmac ( 'sha512', "^$login_id^$trans_id^$amount^", hex2bin( $signature ) );
905
906
            if ( ! hash_equals( $hash, $_POST['x_SHA2_Hash'] ) ) {
907
                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...
908
            }
909
910
        }
911
912
        // Fetch the associated subscription.
913
        $subscription = new WPInv_Subscription( $_POST['x_subscription_id'], true );
914
915
        // Abort if it is missing or completed.
916
        if ( empty( $subscription->id ) || $subscription->status == 'completed' ) {
917
            return;
918
        }
919
920
        // Payment status.
921
        if ( 1 == $_POST['x_response_code'] ) {
922
923
            $args = array(
924
                'transaction_id' => wpinv_clean( $_POST['x_trans_id'] ),
925
                'gateway'        => $this->id
926
            );
927
928
            $subscription->add_payment( $args );
929
            $subscription->renew();
930
931
        } else {
932
            $subscription->failing();
933
        }
934
935
        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...
936
937
    }
938
939
    /**
940
	 * Check Authorize.NET IPN validity.
941
	 */
942
	public function validate_ipn() {
943
944
        wpinv_error_log( 'Validating Authorize.NET IPN response' );
945
946
        if ( empty( $_SERVER['HTTP_X_ANET_SIGNATURE'] ) ) {
947
            return false;
948
        }
949
950
        $signature = $this->get_option( 'signature_key' );
951
952
        if ( empty( $signature ) ) {
953
            wpinv_error_log( 'Error: You have not set a signature key' );
954
            return false;
955
        }
956
957
        $hash  = hash_hmac ( 'sha512', file_get_contents('php://input'), hex2bin( $signature ) );
958
959
        if ( hash_equals( $hash, $_SERVER['HTTP_X_ANET_SIGNATURE'] ) ) {
960
            wpinv_error_log( 'Successfully validated the IPN' );
961
            return true;
962
        }
963
964
        wpinv_error_log( 'IPN hash is not valid' );
965
        wpinv_error_log(  $_SERVER['HTTP_X_ANET_SIGNATURE']  );
966
        return false;
967
968
    }
969
970
    /**
971
     * Displays a notice on the checkout page if sandbox is enabled.
972
     */
973
    public function sandbox_notice( $description, $gateway ) {
974
975
        if ( $this->id == $gateway && wpinv_is_test_mode( $this->id ) ) {
976
            $description .= '<br>' . sprintf(
977
                __( 'SANDBOX ENABLED. You can use sandbox testing details only. See the %sAuthorize.NET Sandbox Testing Guide%s for more details.', 'invoicing' ),
978
                '<a href="https://developer.authorize.net/hello_world/testing_guide.html">',
979
                '</a>'
980
            );
981
        }
982
        return $description;
983
984
    }
985
986
    /**
987
	 * Filters the gateway settings.
988
	 * 
989
	 * @param array $admin_settings
990
	 */
991
	public function admin_settings( $admin_settings ) {
992
993
        $currencies = sprintf(
994
            __( 'Supported Currencies: %s', 'invoicing' ),
995
            implode( ', ', $this->currencies )
996
        );
997
998
        $admin_settings['authorizenet_active']['desc'] .= $admin_settings['authorizenet_active']['desc'] . " ($currencies)";
999
        $admin_settings['authorizenet_desc']['std']     = __( 'Pay securely using your credit or debit card.', 'invoicing' );
1000
1001
        $admin_settings['authorizenet_login_id'] = array(
1002
            'type' => 'text',
1003
            'id'   => 'authorizenet_login_id',
1004
            'name' => __( 'API Login ID', 'invoicing' ),
1005
            'desc' => '<a href="https://support.authorize.net/s/article/How-do-I-obtain-my-API-Login-ID-and-Transaction-Key"><em>' . __( 'How do I obtain my API Login ID and Transaction Key?', 'invoicing' ) . '</em></a>',
1006
        );
1007
1008
        $admin_settings['authorizenet_transaction_key'] = array(
1009
            'type' => 'text',
1010
            'id'   => 'authorizenet_transaction_key',
1011
            'name' => __( 'Transaction Key', 'invoicing' ),
1012
        );
1013
1014
        $admin_settings['authorizenet_signature_key'] = array(
1015
            'type' => 'text',
1016
            'id'   => 'authorizenet_signature_key',
1017
            'name' => __( 'Signature Key', 'invoicing' ),
1018
            'desc' => '<a href="https://support.authorize.net/s/article/What-is-a-Signature-Key"><em>' . __( 'Learn more.', 'invoicing' ) . '</em></a>',
1019
        );
1020
1021
        $admin_settings['authorizenet_ipn_url'] = array(
1022
            'type'     => 'ipn_url',
1023
            'id'       => 'authorizenet_ipn_url',
1024
            'name'     => __( 'Webhook URL', 'invoicing' ),
1025
            'std'      => $this->notify_url,
1026
            '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/s/article/How-do-I-add-edit-Webhook-notification-end-points"><em>' . __( 'Learn more.', 'invoicing' ) . '</em></a>',
1027
            'custom'   => 'authorizenet',
1028
            'readonly' => true,
1029
        );
1030
1031
		return $admin_settings;
1032
	}
1033
1034
}
1035