# Laravel Authorize.Net

A comprehensive Laravel package for seamless integration with Authorize.Net payment gateway. This package provides an elegant, fluent interface for managing customer profiles, payment methods, transactions, subscriptions, and more.

## Features

- **Customer Profile Management** - Create and manage customer profiles on Authorize.Net
- **Payment Profile Management** - Store payment methods securely without PCI compliance requirements
- **Direct Card Transactions** - Process one-time card payments
- **Recurring Subscriptions** - Create and manage subscription payments
- **Transaction Management** - Process charges, refunds, and query transactions
- **Eloquent Models** - Built-in models for local storage of payment profiles
- **Type-Safe** - Full PHP 8.1+ type hints and return types
- **Laravel 10/11/12 Compatible** - Works with modern Laravel versions
- **Well-Tested** - Comprehensive test coverage

## Requirements

- PHP &gt;= 8.1
- Laravel &gt;= 10.0
- Authorize.Net account (sandbox or production)
- cURL extension enabled

## Installation

Install the package via Composer:

```bash
composer require squareetlabs/laravel-authorizenet

```

### Publish Configuration

Publish the configuration file (optional):

```bash
php artisan vendor:publish --tag=authorizenet-config

```

### Run Migrations

The package requires database tables to store customer and payment profiles. Run the migrations:

```bash
php artisan migrate

```

This will create two tables: - `user_gateway_profiles` - Stores Authorize.Net customer profile IDs - `user_payment_profiles` - Stores payment method information

### Setup User Model

Add the `ANetPayments` trait to your User model:

```php
<?php

namespace App\Models;

use Squareetlabs\AuthorizeNet\Traits\ANetPayments;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    use ANetPayments;
    
    // ... your model code
}

```

## Configuration

Add your Authorize.Net credentials to your `.env` file:

```env
AUTHORIZE_NET_LOGIN_ID=your_login_id
AUTHORIZE_NET_CLIENT_KEY=your_client_key
AUTHORIZE_NET_TRANSACTION_KEY=your_transaction_key
AUTHORIZE_NET_ENVIRONMENT=sandbox  # or 'production'

```

You can obtain these credentials from your Authorize.Net Merchant Interface: - **Sandbox**: https://sandbox.authorize.net - **Production**: https://account.authorize.net

Navigate to **Settings &gt; Security Settings &gt; API Credentials &amp; Keys** to find your credentials.

## Basic Usage

Once configured, you can access Authorize.Net functionality through the `anet()` method on your User model:

```php
$user = User::find(1);

// All Authorize.Net operations are available via $user->anet()
$user->anet()->createCustomerProfile();

```

## Customer Profiles

Customer profiles are required before you can store payment methods. Authorize.Net uses customer profiles to securely store payment information without requiring PCI compliance on your end.

### Create a Customer Profile

```php
$response = $user->anet()->createCustomerProfile();

// Access the profile ID
$profileId = $response->getCustomerProfileId();

```

### Get Customer Profile ID

```php
$profileId = $user->anet()->getCustomerProfileId();

```

The customer profile ID is automatically stored in your database using the `UserGatewayProfile` model.

## Payment Profiles

Payment profiles allow you to store payment methods securely without handling sensitive card data. This is achieved through Authorize.Net's Accept.js, which sends payment data directly to Authorize.Net.

### Creating a Payment Profile with Accept.js

**Frontend (JavaScript):**

```javascript
// Include Accept.js library
<script src="https://jstest.authorize.net/v1/Accept.js"></script>

// Collect payment data and send to Authorize.Net
Accept.dispatchData(secureDataRequest, function (response) {
    if (response.messages.resultCode === "Ok") {
        // Send opaque data to your server
        fetch('/api/payment-profiles', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
            },
            body: JSON.stringify({
                opaqueData: {
                    dataValue: response.opaqueData.dataValue,
                    dataDescriptor: response.opaqueData.dataDescriptor
                },
                source: {
                    last_4: '1234',
                    brand: 'VISA',
                    type: 'card'
                }
            })
        });
    }
});

```

**Backend (Laravel):**

```php
use Illuminate\Http\Request;

public function storePaymentProfile(Request $request)
{
    $user = auth()->user();
    
    $response = $user->anet()->createPaymentProfile(
        $request->opaqueData,
        $request->source
    );
    
    if ($response->getCustomerPaymentProfileId()) {
        return response()->json([
            'success' => true,
            'payment_profile_id' => $response->getCustomerPaymentProfileId()
        ]);
    }
    
    return response()->json(['success' => false], 422);
}

```

### Get Payment Profiles

```php
// Get all payment methods
$paymentMethods = $user->anet()->getPaymentMethods();

// Get only card payment profiles
$cards = $user->anet()->getPaymentCardProfiles();

// Get only bank account payment profiles
$banks = $user->anet()->getPaymentBankProfiles();

// Get just the payment profile IDs
$profileIds = $user->anet()->getPaymentProfiles();

```

### Using Eloquent Models

You can also work directly with the Eloquent models:

```php
use Squareetlabs\AuthorizeNet\Models\UserPaymentProfile;

// Get all payment profiles for a user
$profiles = UserPaymentProfile::where('user_id', $user->id)->get();

// Get only cards using scope
$cards = UserPaymentProfile::where('user_id', $user->id)->cards()->get();

// Get only banks using scope
$banks = UserPaymentProfile::where('user_id', $user->id)->banks()->get();

// Access model properties
foreach ($profiles as $profile) {
    echo $profile->payment_profile_id;
    echo $profile->last_4;
    echo $profile->brand;
    echo $profile->type; // 'card' or 'bank'
}

```

## Charging Payment Profiles

Once you have a payment profile, you can charge it:

```php
// Charge $19.99 (amount in cents)
$response = $user->anet()->charge(1999, $paymentProfileId);

// Check if transaction was approved
if ($response->getMessages()->getResultCode() === 'Ok') {
    $transactionResponse = $response->getTransactionResponse();
    
    if ($transactionResponse->getResponseCode() === '1') {
        $transactionId = $transactionResponse->getTransId();
        // Transaction successful
    }
}

```

## Refunds

Refund a previous transaction:

```php
// Refund $10.00 (amount in cents)
$response = $user->anet()->refund(
    1000,                    // Amount in cents
    $refTransId,            // Reference transaction ID from charge
    $paymentProfileId       // Payment profile ID
);

```

## Direct Card Transactions

For one-time payments without storing payment methods, you can process direct card transactions:

```php
$response = $user->anet()
    ->card()
    ->setNumbers(4111111111111111)
    ->setCVV(123)
    ->setNameOnCard('John Doe')
    ->setExpMonth(12)
    ->setExpYear(2025)
    ->setAmountInCents(1000)  // $10.00
    ->charge();

// Check transaction result
if ($response->getMessages()->getResultCode() === 'Ok') {
    $transactionResponse = $response->getTransactionResponse();
    $transactionId = $transactionResponse->getTransId();
}

```

### Card Service Methods

```php
$card = $user->anet()->card();

// Set card information
$card->setNumbers(4111111111111111);
$card->setCVV(123);
$card->setNameOnCard('John Doe');
$card->setExpMonth(12);              // Integer: 1-12
$card->setExpYear(2025);             // Integer or 2-digit year
$card->setBrand('VISA');              // Optional
$card->setType('Credit');            // Optional

// Set amount
$card->setAmountInCents(1000);       // $10.00
$card->setAmountInDollars(10.00);    // Alternative method

// Process charge
$response = $card->charge();

```

## Subscriptions

Create and manage recurring subscription payments:

### Create a Subscription

```php
$response = $user->anet()->subscription()->create([
    'name' => 'Monthly Premium Plan',
    'startDate' => '2024-01-15',
    'totalOccurrences' => 12,
    'trialOccurrences' => 1,
    'intervalLength' => 30,
    'intervalLengthUnit' => 'days',  // 'days' or 'months'
    'amountInDollars' => 29.99,
    'trialAmountInDollars' => 0.00,
    'cardNumber' => 4111111111111111,
    'cardExpiry' => '2025-12',
    'invoiceNumber' => 'INV-001',
    'subscriptionDescription' => 'Monthly premium subscription',
    'customerFirstName' => 'John',
    'customerLastName' => 'Doe'
]);

$subscriptionId = $response->getSubscriptionId();

```

### Get Subscription

```php
$subscription = $user->anet()->subscription()->get($subscriptionId);

```

### Update Subscription

```php
$response = $user->anet()->subscription()->update($subscriptionId, [
    'cardNumber' => 4111111111111111,
    'cardExpiry' => '2026-12'
]);

```

### Cancel Subscription

```php
$response = $user->anet()->subscription()->cancel($subscriptionId);

```

### Get Subscription Status

```php
$response = $user->anet()->subscription()->getStatus($subscriptionId);

```

### List Subscriptions

```php
$subscriptions = $user->anet()->subscription()->getList([
    'orderBy' => 'id',
    'orderDescending' => false,
    'limit' => 100,
    'offset' => 1,
    'searchType' => 'subscriptionActive'  // 'subscriptionActive' or 'subscriptionInactive'
]);

```

### Subscription Aliases

You can use any of these methods to access the subscription service:

```php
$user->anet()->subscription();
$user->anet()->subs();
$user->anet()->recurring();

```

## Transaction Management

Work with transaction responses to check status and retrieve information:

```php
// After charging a payment profile
$chargeResponse = $user->anet()->charge(1000, $paymentProfileId);

// Create transaction service instance
$transaction = $user->anet()->transactions($chargeResponse);

// Check if transaction was approved
if ($transaction->isApproved()) {
    // Transaction was successful
}

// Check if request was successful (doesn't mean transaction was approved)
if ($transaction->isRequestSuccessful()) {
    // Request reached Authorize.Net successfully
}

// Get transaction ID
$transactionId = $transaction->getId();

// Access transaction response methods
$transaction->getRefTransID();
$transaction->getResponseCode();
// ... and other transaction response methods

```

### Get Transactions by Batch ID

```php
$transactions = $user->anet()
    ->transactions($transactionResponse)
    ->get($batchId);

```

## Working with Transaction Responses

All transaction methods return a `CreateTransactionResponse` object. Here's how to work with it:

```php
$response = $user->anet()->charge(1000, $paymentProfileId);

// Check overall result
if ($response->getMessages()->getResultCode() === 'Ok') {
    $transactionResponse = $response->getTransactionResponse();
    
    // Check transaction response code
    // 1 = Approved, 2 = Declined, 3 = Error, 4 = Held for Review
    if ($transactionResponse->getResponseCode() === '1') {
        // Transaction approved
        $transactionId = $transactionResponse->getTransId();
        $authCode = $transactionResponse->getAuthCode();
        $accountNumber = $transactionResponse->getAccountNumber(); // Last 4 digits
    } else {
        // Transaction declined or error
        $errors = $transactionResponse->getErrors();
        foreach ($errors as $error) {
            echo $error->getErrorCode() . ': ' . $error->getErrorText();
        }
    }
}

```

## Eloquent Models

The package provides two Eloquent models for working with stored data:

### UserGatewayProfile

Represents a customer profile stored locally:

```php
use Squareetlabs\AuthorizeNet\Models\UserGatewayProfile;

// Get customer profile for user
$profile = UserGatewayProfile::where('user_id', $user->id)->first();

// Access properties
$profile->profile_id;  // Authorize.Net customer profile ID
$profile->user_id;

```

### UserPaymentProfile

Represents a payment method stored locally:

```php
use Squareetlabs\AuthorizeNet\Models\UserPaymentProfile;

// Relationships
$profile->user;  // BelongsTo User model

// Scopes
UserPaymentProfile::cards()->get();  // Only card payment profiles
UserPaymentProfile::banks()->get();  // Only bank payment profiles

// Access properties
$profile->payment_profile_id;  // Authorize.Net payment profile ID
$profile->last_4;              // Last 4 digits of card/account
$profile->brand;               // Card brand (VISA, MasterCard, etc.)
$profile->type;                // 'card' or 'bank'

```

## Environment Configuration

The package automatically uses the appropriate environment based on your Laravel app environment:

- **Local/Testing**: Uses Authorize.Net Sandbox
- **Production**: Uses Authorize.Net Production

You can override this by setting `AUTHORIZE_NET_ENVIRONMENT` in your `.env` file:

```env
AUTHORIZE_NET_ENVIRONMENT=sandbox  # or 'production'

```

## Error Handling

Always wrap Authorize.Net operations in try-catch blocks:

```php
try {
    $response = $user->anet()->charge(1000, $paymentProfileId);
    
    if ($response->getMessages()->getResultCode() === 'Ok') {
        $transactionResponse = $response->getTransactionResponse();
        
        if ($transactionResponse->getResponseCode() === '1') {
            // Success
        } else {
            // Transaction declined
            $errors = $transactionResponse->getErrors();
            // Handle errors
        }
    } else {
        // Request failed
        $messages = $response->getMessages()->getMessage();
        // Handle messages
    }
} catch (\Exception $e) {
    // Handle exception
    Log::error('Authorize.Net Error: ' . $e->getMessage());
}

```

## Testing

### Running Package Tests

The package includes a comprehensive test suite. To run the tests:

```bash
composer test
# or
vendor/bin/phpunit tests/

```

### Test Coverage

The package includes tests for:

- **Unit Tests** - Models, Services, and core functionality (all passing)
- **Integration Tests** - API interactions with Authorize.Net (require valid credentials)
- **Model Tests** - Eloquent models and relationships (all passing)
- **Service Tests** - Payment processing, subscriptions, and transactions

### Test Environment Setup

For testing, you need to configure your `.env` file with Authorize.Net sandbox credentials:

```env
AUTHORIZE_NET_ENVIRONMENT=sandbox
AUTHORIZE_NET_LOGIN_ID=your_sandbox_login_id
AUTHORIZE_NET_TRANSACTION_KEY=your_sandbox_transaction_key
AUTHORIZE_NET_CLIENT_KEY=your_sandbox_client_key

```

**Note**: Some integration tests require valid Authorize.Net sandbox credentials and will fail if credentials are invalid or missing. This is expected behavior as these tests verify actual API connectivity.

### Testing in Your Application

When testing your own application that uses this package:

#### Option 1: Use Authorize.Net Sandbox (Recommended)

Configure your test environment to use Authorize.Net's sandbox:

```env
# .env.testing
AUTHORIZE_NET_ENVIRONMENT=sandbox
AUTHORIZE_NET_LOGIN_ID=your_sandbox_login_id
AUTHORIZE_NET_TRANSACTION_KEY=your_sandbox_transaction_key
AUTHORIZE_NET_CLIENT_KEY=your_sandbox_client_key

```

#### Option 2: Mock Authorize.Net Responses

Use PHPUnit's mocking capabilities to mock API responses:

```php
use PHPUnit\Framework\TestCase;
use Squareetlabs\AuthorizeNet\Services\CustomerProfileService;
use net\authorize\api\contract\v1\CreateCustomerProfileResponse;

class PaymentTest extends TestCase
{
    public function test_customer_profile_creation()
    {
        // Mock the service
        $service = $this->createMock(CustomerProfileService::class);
        
        // Create mock response
        $mockResponse = new CreateCustomerProfileResponse();
        // ... configure mock response
        
        $service->expects($this->once())
            ->method('create')
            ->willReturn($mockResponse);
            
        // Test your code
    }
}

```

#### Option 3: Use Feature Flags

Skip actual API calls during testing:

```php
if (app()->environment('testing')) {
    // Return mock data instead of calling Authorize.Net
    return $this->mockAuthorizeNetResponse();
}

// Normal API call
return $user->anet()->charge(1000, $paymentProfileId);

```

### Test Database Setup

The package tests use an in-memory SQLite database. The package automatically creates the necessary tables during testing. **Note**: The package does not create a `users` table migration - your application should already have this table.

### Test Results

Current test status: - **Total Tests**: 89 - **Passing**: ~53 (unit tests and model tests) - **Integration Tests**: Require valid Authorize.Net credentials - **Model Tests**: 100% passing (10/10) - **Service Tests**: Unit tests passing, integration tests require API credentials

### Best Practices for Testing

1. **Use Sandbox Environment**: Always use Authorize.Net sandbox credentials for testing
2. **Mock External Calls**: For unit tests, mock Authorize.Net API responses
3. **Test Error Handling**: Test both success and failure scenarios
4. **Isolate Tests**: Each test should be independent and not rely on previous test data
5. **Use Test Database**: Always use a separate test database, never test against production data

## API Reference

**Authorize.Net Documentation**<span style="color: rgb(0, 0, 0); font-family: helvetica, sans-serif, arial, helvetica; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(252, 252, 252); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;">: https://developer.authorize.net/</span>

### ANet Class Methods

```php
// Customer Profiles
$user->anet()->createCustomerProfile();
$user->anet()->getCustomerProfileId();

// Payment Profiles
$user->anet()->createPaymentProfile($opaqueData, $source);
$user->anet()->getPaymentProfiles();
$user->anet()->getPaymentMethods();
$user->anet()->getPaymentCardProfiles();
$user->anet()->getPaymentBankProfiles();

// Transactions
$user->anet()->charge($cents, $paymentProfileId);
$user->anet()->refund($cents, $refTransId, $paymentProfileId);
$user->anet()->transactions($transactionResponse);
$user->anet()->card();

// Subscriptions
$user->anet()->subscription();
$user->anet()->subs();
$user->anet()->recurring();
```