itul/slick-payment-gateway

A Laravel package for integrating multiple payment gateways with PCI compliant tokenization

v0.2.1 2025-08-25 12:53 UTC

This package is auto-updated.

Last update: 2025-08-25 12:54:22 UTC


README

Latest Version on Packagist GitHub Tests Action Status Total Downloads License

A PCI DSS compliant Laravel package for seamless payment processing with multiple gateway support, built for modern SaaS applications with multi-tenant architecture.

โœจ Key Features

  • ๐Ÿ”’ PCI DSS Compliant - Client-side tokenization only, zero raw card data processing
  • ๐Ÿข Multi-Tenant SaaS Ready - Built-in tenant isolation and configuration management
  • ๐Ÿ’ณ Multiple Payment Gateways - Stripe, PayPal, Authorize.Net with unified interface
  • ๐Ÿ” Enterprise Security - Laravel encryption, audit logging, role-based access
  • ๐ŸŽฏ Developer Friendly - Clean APIs, comprehensive docs, extensive testing
  • โšก Production Ready - Rate limiting, webhook handling, async processing

๐Ÿš€ Quick Start

1. Installation

composer require itul/slick-payment-gateway

2. Publish & Migrate

# Publish configuration
php artisan vendor:publish --tag=slick-payment-gateway-config

# Run migrations
php artisan migrate

3. Configure Environment

# Choose your default gateway
SLICK_PAYMENT_GATEWAY_DRIVER=stripe

# Stripe Configuration
STRIPE_SECRET_KEY=sk_test_...
STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...

# Optional: Enable multi-tenant mode
SLICK_SASS_MODE_ENABLED=true

4. Your First Payment โšก

Stripe Example:

use Itul\SlickPaymentGateway\Facades\SlickPaymentGateway;
use Itul\SlickPaymentGateway\DTOs\PaymentRequest;

// Process a payment (using pre-tokenized payment method)
$payment = SlickPaymentGateway::processPayment(
    PaymentRequest::make([
        'payment_method_id' => '1', // Your stored payment method ID
        'amount' => 29.99,
        'currency' => 'USD',
        'description' => 'Pro Plan Subscription'
    ])
);

if ($payment->success) {
    echo "Transaction ID: " . $payment->transactionId;
} else {
    echo "Error: " . $payment->message;
}

Authorize.Net Example:

// Configure for Authorize.Net
config(['slick-payment-gateway.default_driver' => 'authorize_net']);

$payment = SlickPaymentGateway::processPayment(
    PaymentRequest::make([
        'payment_method_id' => '1',
        'amount' => 99.50,
        'currency' => 'USD',
        'description' => 'Enterprise Plan - Annual',
        'metadata' => [
            'invoice_number' => 'INV-2024-001',
            'customer_po' => 'PO-12345'
        ]
    ])
);

if ($payment->success) {
    echo "Auth.Net Transaction: " . $payment->transactionId;
    echo "Auth Code: " . $payment->gatewayResponse['authCode'];
} else {
    echo "Decline Reason: " . $payment->message;
}

๐Ÿ’ณ Payment Method Management

Creating Payment Methods (Client-Side Tokenization)

Frontend (Authorize.Net Accept.js):

// Create payment nonce with Accept.js (PCI compliant)
const authData = {
    clientKey: 'your_client_key',
    apiLoginID: 'your_api_login_id'
};

const cardData = {
    cardNumber: cardElement.value,
    month: expMonth.value,
    year: expYear.value,
    cardCode: cvv.value
};

Accept.dispatchData(authData, cardData, function(response) {
    if (response.messages.resultCode === 'Error') {
        // Handle client-side error
        console.error('Accept.js Error:', response.messages.message[0].text);
        return;
    }
    
    // Send payment nonce to backend (PCI compliant)
    fetch('/api/payment-methods', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({
            payment_method_token: response.opaqueData.dataValue,
            payment_descriptor: response.opaqueData.dataDescriptor,
            owner_type: 'App\\Models\\User',
            owner_id: user.id
        })
    });
});

Frontend (Stripe Elements):

// Create payment method on client-side (PCI compliant)
const {paymentMethod} = await stripe.createPaymentMethod({
    type: 'card',
    card: cardElement,
    billing_details: {
        name: 'John Doe',
        email: 'john@example.com'
    }
});

// Send token to backend
fetch('/api/payment-methods', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({
        payment_method_token: paymentMethod.id,
        owner_type: 'App\\Models\\User',
        owner_id: user.id
    })
});

Backend Processing:

use Itul\SlickPaymentGateway\Facades\SlickPaymentGateway;

public function storePaymentMethod(Request $request)
{
    // Handle Authorize.Net payment nonce
    if ($request->has('payment_descriptor')) {
        $request->validate([
            'payment_method_token' => 'required|string',
            'payment_descriptor' => 'required|string',
            'owner_type' => 'required|string',
            'owner_id' => 'required|integer'
        ]);
        
        $paymentMethodId = SlickPaymentGateway::attachPaymentMethod(
            $request->payment_method_token,
            [
                'owner_type' => $request->owner_type,
                'owner_id' => $request->owner_id,
                'payment_descriptor' => $request->payment_descriptor
            ]
        );
    } 
    // Handle other gateway tokens (Stripe, PayPal)
    else {
        $request->validate([
            'payment_method_token' => 'required|string',
            'owner_type' => 'required|string',
            'owner_id' => 'required|integer'
        ]);

        $paymentMethodId = SlickPaymentGateway::attachPaymentMethod(
            $request->payment_method_token,
            [
                'owner_type' => $request->owner_type,
                'owner_id' => $request->owner_id
            ]
        );
    }

    return response()->json([
        'payment_method_id' => $paymentMethodId,
        'message' => 'Payment method saved successfully'
    ]);
}

Using Stored Payment Methods

use Itul\SlickPaymentGateway\Models\SlickPaymentMethod;

// Get user's payment methods
$paymentMethods = SlickPaymentMethod::where('owner_type', 'App\\Models\\User')
                                   ->where('owner_id', auth()->id())
                                   ->get();

// Display in your UI
foreach ($paymentMethods as $method) {
    echo $method->getDisplayName(); // "Visa ending in 4242"
    echo $method->isExpired() ? 'Expired' : 'Active';
}

// Make a payment method default
$paymentMethod = SlickPaymentMethod::find(1);
$paymentMethod->makeDefault();

๐Ÿ’ฐ Payment Processing

One-Time Payments

Authorize.Net Example:

use Itul\SlickPaymentGateway\DTOs\PaymentRequest;
use Itul\SlickPaymentGateway\Facades\SlickPaymentGateway;

// Configure for Authorize.Net
config(['slick-payment-gateway.default_driver' => 'authorize_net']);

// Process enterprise payment with detailed metadata
$result = SlickPaymentGateway::processPayment(
    PaymentRequest::make([
        'payment_method_id' => '1',
        'amount' => 2499.99,
        'currency' => 'USD',
        'description' => 'Enterprise Software License',
        'metadata' => [
            'invoice_number' => 'INV-2024-0123',
            'customer_po' => 'PO-ACME-456',
            'department' => 'IT Services',
            'billing_period' => 'Q1 2024',
            'tax_exempt' => true
        ]
    ])
);

// Handle Authorize.Net specific response data
if ($result->success) {
    $transactionId = $result->transactionId; // Auth.Net transaction ID
    $authCode = $result->gatewayResponse['authCode']; // Authorization code
    $avsResponse = $result->gatewayResponse['avsResultCode']; // Address verification
    $cvvResponse = $result->gatewayResponse['cvvResultCode']; // CVV verification
    
    // Log enterprise transaction details
    Log::info('Enterprise payment processed', [
        'transaction_id' => $transactionId,
        'auth_code' => $authCode,
        'avs_result' => $avsResponse,
        'cvv_result' => $cvvResponse,
        'invoice_number' => 'INV-2024-0123'
    ]);
    
    return redirect()->route('payment.success', $transactionId);
} else {
    // Handle Authorize.Net specific error codes
    $errorCode = $result->gatewayResponse['errorCode'] ?? null;
    $errorText = $result->gatewayResponse['errorText'] ?? $result->message;
    
    Log::warning('Authorize.Net payment failed', [
        'error_code' => $errorCode,
        'error_text' => $errorText,
        'invoice_number' => 'INV-2024-0123'
    ]);
    
    return back()->withErrors(['payment' => "Payment failed: {$errorText}"]);
}

Stripe Example:

// Process payment with stored payment method
$result = SlickPaymentGateway::processPayment(
    PaymentRequest::make([
        'payment_method_id' => '1',
        'amount' => 99.99,
        'currency' => 'USD',
        'description' => 'Annual Pro Subscription',
        'metadata' => [
            'customer_id' => auth()->id(),
            'plan' => 'pro-annual'
        ]
    ])
);

// Handle response
if ($result->success) {
    // Payment completed
    $transactionId = $result->transactionId;
    $status = $result->status; // 'completed', 'pending', etc.
    
    // Redirect user to success page
    return redirect()->route('payment.success', $transactionId);
} else {
    // Payment failed
    return back()->withErrors(['payment' => $result->message]);
}

Refund Processing

Authorize.Net Refund Example:

use Itul\SlickPaymentGateway\DTOs\RefundRequest;

// Authorize.Net requires original transaction details for refunds
$refund = SlickPaymentGateway::refundPayment(
    RefundRequest::make([
        'transaction_id' => 'authnet_txn_123456',
        'amount' => 1500.00, // Partial refund of $2499.99 original
        'reason' => 'Partial service cancellation',
        'metadata' => [
            'original_invoice' => 'INV-2024-0123',
            'refund_invoice' => 'REF-2024-0045',
            'last_four' => '1234' // Required by Authorize.Net for refunds
        ]
    ])
);

if ($refund->success) {
    $refundId = $refund->transactionId;
    $refundAmount = $refund->gatewayResponse['settleAmount'];
    
    Log::info('Authorize.Net refund processed', [
        'refund_id' => $refundId,
        'original_transaction' => 'authnet_txn_123456',
        'refund_amount' => $refundAmount
    ]);
} else {
    // Handle refund failure - common with Auth.Net batch processing
    Log::error('Authorize.Net refund failed', [
        'error' => $refund->message,
        'transaction_id' => 'authnet_txn_123456'
    ]);
}

Standard Refund Examples:

// Full refund
$refund = SlickPaymentGateway::refundPayment(
    RefundRequest::make([
        'transaction_id' => 'txn_123456',
        'reason' => 'Customer requested cancellation'
    ])
);

// Partial refund
$partialRefund = SlickPaymentGateway::refundPayment(
    RefundRequest::make([
        'transaction_id' => 'txn_123456',
        'amount' => 25.00, // Refund $25 of original $100
        'reason' => 'Partial service credit'
    ])
);

Subscription Management

Authorize.Net Recurring Billing (ARB):

use Itul\SlickPaymentGateway\DTOs\SubscriptionRequest;

// Create Authorize.Net ARB subscription
$subscription = SlickPaymentGateway::createSubscription(
    SubscriptionRequest::make([
        'payment_method_id' => '1',
        'amount' => 149.99,
        'currency' => 'USD',
        'interval' => 'monthly',
        'plan_name' => 'Enterprise Plan',
        'metadata' => [
            'trial_days' => 14,
            'setup_fee' => 50.00,
            'billing_cycles' => 12, // 1 year contract
            'customer_type' => 'business',
            'tax_rate' => 8.25
        ]
    ])
);

if ($subscription->success) {
    $arbSubscriptionId = $subscription->transactionId;
    
    Log::info('Authorize.Net ARB subscription created', [
        'arb_subscription_id' => $arbSubscriptionId,
        'customer_id' => auth()->id(),
        'plan' => 'Enterprise Plan'
    ]);
}

// Cancel ARB subscription
$cancelled = SlickPaymentGateway::cancelSubscription('arb_sub_123456');

Standard Subscription Example:

// Create subscription
$subscription = SlickPaymentGateway::createSubscription(
    SubscriptionRequest::make([
        'payment_method_id' => '1',
        'amount' => 29.99,
        'currency' => 'USD',
        'interval' => 'monthly',
        'plan_name' => 'Pro Plan'
    ])
);

// Cancel subscription
$cancelled = SlickPaymentGateway::cancelSubscription('sub_123456');

๐Ÿข Multi-Tenant SaaS Usage

Enable SaaS Mode

// config/slick-payment-gateway.php
'sass_mode' => [
    'enabled' => true,
    'tenant_model' => App\Models\Company::class,
    'tenant_key' => 'company_id', // Key in User model
],

Tenant-Specific Gateway Configuration

use Itul\SlickPaymentGateway\Models\SlickTenantConnection;

// Set up gateway for a tenant
$tenant = Company::find(1);
$connection = SlickTenantConnection::create([
    'tenant_type' => Company::class,
    'tenant_id' => $tenant->id,
    'name' => 'Acme Corp Payment Gateway',
    'is_active' => true
]);

// Configure Stripe for this tenant
$connection->setGatewayCredentials('stripe', [
    'secret_key' => 'sk_test_tenant_specific_key',
    'publishable_key' => 'pk_test_tenant_specific_key',
    'webhook_secret' => 'whsec_tenant_specific_secret'
]);

// Configure PayPal for this tenant  
$connection->setGatewayCredentials('paypal', [
    'client_id' => 'tenant_paypal_client_id',
    'client_secret' => 'tenant_paypal_secret',
    'mode' => 'sandbox'
]);

// Configure Authorize.Net for this tenant
$connection->setGatewayCredentials('authorize_net', [
    'api_login_id' => 'tenant_authnet_login_id',
    'transaction_key' => 'tenant_authnet_transaction_key',
    'sandbox' => true
]);

Tenant Context in Requests

// In your middleware or service provider
app(SlickTenantConnection::class)->setCurrentTenant($company);

// Or use the SassAware trait in your models
class User extends Model 
{
    use \Itul\SlickPaymentGateway\Traits\HasSlickPaymentMethods;
    
    public function company()
    {
        return $this->belongsTo(Company::class);
    }
}

// Payments are automatically scoped to the tenant
$userPaymentMethods = $user->slickPaymentMethods; // Only this tenant's methods

๐Ÿ”Œ Gateway-Specific Features

Authorize.Net Integration

// Authorize.Net enterprise features and metadata
$payment = SlickPaymentGateway::processPayment(
    PaymentRequest::make([
        'payment_method_id' => '1',
        'amount' => 5000.00,
        'currency' => 'USD',
        'description' => 'Enterprise Software License - Annual',
        'metadata' => [
            'invoice_number' => 'INV-2024-ENT-001',
            'customer_po' => 'PO-ENTERPRISE-789',
            'department' => 'IT Infrastructure',
            'cost_center' => 'CC-9876',
            'billing_contact' => 'John Smith, CFO',
            'license_type' => 'enterprise_unlimited',
            'contract_term' => '12_months',
            'tax_exempt_id' => 'TAX-EXEMPT-12345',
            'approval_code' => 'APPROVED-BY-FINANCE',
            'recurring_billing' => false,
            'duplicate_window' => 300, // 5 minute duplicate transaction window
            'customer_ip' => request()->ip(),
            'merchant_defined_fields' => [
                'field_1' => 'Customer Tier: Enterprise',
                'field_2' => 'Sales Rep: Jane Doe',
                'field_3' => 'Referral Source: Direct Sales',
                'field_4' => 'Contract ID: ENT-2024-001'
            ]
        ]
    ])
);

// Access Authorize.Net specific response data
if ($payment->success) {
    $response = $payment->gatewayResponse;
    
    echo "Transaction ID: " . $response['transactionResponse']['transId'];
    echo "Auth Code: " . $response['transactionResponse']['authCode'];
    echo "Response Code: " . $response['transactionResponse']['responseCode'];
    echo "AVS Result: " . $response['transactionResponse']['avsResultCode'];
    echo "CVV Result: " . $response['transactionResponse']['cvvResultCode'];
    echo "CAVV Result: " . $response['transactionResponse']['cavvResultCode'];
    echo "Account Number: " . $response['transactionResponse']['accountNumber']; // Masked
    echo "Account Type: " . $response['transactionResponse']['accountType'];
    echo "Test Request: " . $response['transactionResponse']['testRequest'];
    
    // Enterprise reporting fields
    if (isset($response['transactionResponse']['userFields'])) {
        foreach ($response['transactionResponse']['userFields'] as $field) {
            echo "Custom Field {$field['name']}: {$field['value']}";
        }
    }
}

Stripe Integration

// Stripe-specific metadata
$payment = SlickPaymentGateway::processPayment(
    PaymentRequest::make([
        'payment_method_id' => '1',
        'amount' => 50.00,
        'metadata' => [
            'stripe_account' => 'acct_connected_account_id', // For Stripe Connect
            'application_fee' => 2.50, // Platform fee
        ]
    ])
);

PayPal Integration

// PayPal-specific configuration
$payment = SlickPaymentGateway::processPayment(
    PaymentRequest::make([
        'payment_method_id' => '1',
        'amount' => 100.00,
        'return_url' => route('payment.success'),
        'cancel_url' => route('payment.cancel')
    ])
);

// Handle PayPal redirect
if ($payment->redirectUrl) {
    return redirect($payment->redirectUrl);
}

๐Ÿ“ก Webhook Handling

Setup Webhook Endpoints

// routes/web.php - Webhook routes are auto-registered

// Configure webhook URLs in your payment gateway dashboards:
// Stripe: https://yourapp.com/slick/webhooks/stripe
// PayPal: https://yourapp.com/slick/webhooks/paypal  
// Authorize.Net: https://yourapp.com/slick/webhooks/authorize-net

Custom Webhook Handlers

use Itul\SlickPaymentGateway\Events\PaymentCompleted;
use Itul\SlickPaymentGateway\Events\PaymentFailed;
use Itul\SlickPaymentGateway\Events\RefundProcessed;

// Listen to payment events
class PaymentEventListener
{
    public function handlePaymentCompleted(PaymentCompleted $event)
    {
        $payment = $event->payment;
        
        // Update your application state
        Order::where('transaction_id', $payment->gateway_transaction_id)
             ->update(['status' => 'paid']);
             
        // Send confirmation email
        Mail::to($payment->owner)->send(new PaymentConfirmationMail($payment));
    }
    
    public function handlePaymentFailed(PaymentFailed $event)
    {
        // Handle failed payment
        Log::warning('Payment failed', ['payment' => $event->payment]);
        
        // Notify customer
        // Update subscription status, etc.
    }
}

Manual Webhook Processing

use Itul\SlickPaymentGateway\Support\WebhookDispatcher;

public function handleWebhook(Request $request)
{
    $dispatcher = app(WebhookDispatcher::class);
    
    // Handle Authorize.Net Silent Post
    if ($request->has('x_trans_id')) {
        $result = $dispatcher->dispatch(
            gateway: 'authorize_net',
            payload: $request->all(),
            signature: $request->header('X-ANET-Signature') // Authorize.Net webhook signature
        );
    }
    // Handle Stripe webhook  
    elseif ($request->header('Stripe-Signature')) {
        $result = $dispatcher->dispatch(
            gateway: 'stripe',
            payload: $request->all(),
            signature: $request->header('Stripe-Signature')
        );
    }
    // Handle PayPal webhook
    else {
        $result = $dispatcher->dispatch(
            gateway: 'paypal',
            payload: $request->all(),
            signature: $request->header('PAYPAL-TRANSMISSION-SIG')
        );
    }
    
    if ($result) {
        return response()->json(['status' => 'success']);
    }
    
    return response()->json(['error' => 'Invalid webhook'], 400);
}

๐Ÿ—„๏ธ Database Models & Relationships

Core Models

use Itul\SlickPaymentGateway\Models\{
    SlickPayment,
    SlickPaymentMethod, 
    SlickBillingContact,
    SlickOrder,
    SlickInvoice,
    SlickSubscription
};

// Payment model with relationships
$payment = SlickPayment::with([
    'paymentMethod',
    'billingContact', 
    'order',
    'owner' // Polymorphic - User, Company, etc.
])->find(1);

echo $payment->amount; // 99.99
echo $payment->status; // 'completed'
echo $payment->gateway; // 'stripe'
echo $payment->owner->name; // Related model name

Model Relationships

// In your User model
class User extends Model
{
    use \Itul\SlickPaymentGateway\Traits\HasSlickPaymentMethods;
    
    // Automatically available relationships:
    public function slickPaymentMethods() // HasMany
    public function slickPayments() // HasMany  
    public function slickOrders() // HasMany
    public function slickInvoices() // HasMany
    public function slickSubscriptions() // HasMany
}

// Usage
$user = User::find(1);
$defaultPaymentMethod = $user->slickPaymentMethods()->where('is_default', true)->first();
$recentPayments = $user->slickPayments()->where('created_at', '>', now()->subDays(30))->get();

Billing Contacts

use Itul\SlickPaymentGateway\Models\SlickBillingContact;

// Create billing contact
$billingContact = SlickBillingContact::create([
    'owner_type' => 'App\\Models\\User',
    'owner_id' => auth()->id(),
    'type' => SlickBillingContact::TYPE_INDIVIDUAL,
    'name' => 'John Doe',
    'email' => 'john@example.com',
    'address_line_1' => '123 Main St',
    'city' => 'Sacramento',
    'state' => 'CA',
    'postal_code' => '95814',
    'country' => 'US'
]);

echo $billingContact->getDisplayName(); // "John Doe"
echo $billingContact->getFormattedAddress(); // "123 Main St, Sacramento, CA, 95814, US"

๐ŸŽจ Admin Interface

Gateway Management UI

The package includes a built-in admin interface for gateway configuration:

// Access via routes (protected by auth middleware)
Route::get('/slick/gateways'); // Gateway management dashboard

Features:

  • Connect/disconnect payment gateways
  • Test gateway connections
  • View gateway status
  • Configure webhook endpoints
  • Monitor payment activity

Customizing Admin Views

# Publish views for customization
php artisan vendor:publish --tag=slick-payment-gateway-views

Edit published views in resources/views/vendor/slick-payment-gateway/

๐Ÿ”’ Security & PCI Compliance

PCI DSS Compliance Features

โœ… Requirement 3.1-3.4: No cardholder data storage
โœ… Requirement 4.1: Encrypted data transmission
โœ… Requirement 7.1: Role-based access controls
โœ… Requirement 10.1: Comprehensive audit logging

Security Best Practices

// All sensitive data is encrypted at rest
SlickPaymentMethod::create([
    'gateway_token' => 'pm_1234...', // Automatically encrypted
    'gateway_customer_id' => 'cus_5678...', // Automatically encrypted
    'metadata' => ['key' => 'value'] // Automatically encrypted
]);

// Audit logging is automatic
Log::channel('pci_audit')->info('Payment processed', [
    'payment_id' => $payment->id,
    'user_id' => auth()->id(),
    'ip_address' => request()->ip(),
    'user_agent' => request()->userAgent()
]);

Access Control

// In your User model or policy
public function canAccessPaymentData()
{
    return $this->hasRole(['admin', 'finance', 'payment_processor']);
}

// Middleware automatically restricts access
Route::middleware(['slick.auth'])->group(function () {
    Route::get('/payments', [PaymentController::class, 'index']);
});

โš™๏ธ Advanced Configuration

Complete Configuration File

// config/slick-payment-gateway.php
return [
    'default_driver' => env('SLICK_PAYMENT_GATEWAY_DRIVER', 'stripe'),
    
    'drivers' => [
        'stripe' => [
            'secret_key' => env('STRIPE_SECRET_KEY'),
            'publishable_key' => env('STRIPE_PUBLISHABLE_KEY'), 
            'webhook_secret' => env('STRIPE_WEBHOOK_SECRET'),
            'currency' => env('STRIPE_CURRENCY', 'usd'),
        ],
        'paypal' => [
            'client_id' => env('PAYPAL_CLIENT_ID'),
            'client_secret' => env('PAYPAL_CLIENT_SECRET'),
            'mode' => env('PAYPAL_MODE', 'sandbox'),
            'currency' => env('PAYPAL_CURRENCY', 'USD'),
        ],
        'authorize_net' => [
            'api_login_id' => env('AUTHORIZE_NET_API_LOGIN_ID'),
            'transaction_key' => env('AUTHORIZE_NET_TRANSACTION_KEY'),
            'sandbox' => env('AUTHORIZE_NET_SANDBOX', true),
            'currency' => env('AUTHORIZE_NET_CURRENCY', 'USD'),
        ],
    ],
    
    'sass_mode' => [
        'enabled' => env('SLICK_SASS_MODE_ENABLED', false),
        'tenant_model' => env('SLICK_TENANT_MODEL', 'App\\Models\\Company'),
        'tenant_key' => env('SLICK_TENANT_KEY', 'company_id'),
    ],
    
    'security' => [
        'require_https' => env('SLICK_REQUIRE_HTTPS', true),
        'audit_logging' => env('SLICK_AUDIT_LOGGING', true),
        'rate_limiting' => [
            'payments' => env('SLICK_RATE_LIMIT_PAYMENTS', 60), // per minute
            'webhooks' => env('SLICK_RATE_LIMIT_WEBHOOKS', 1000), // per minute
        ],
    ],
    
    'features' => [
        'subscriptions' => env('SLICK_ENABLE_SUBSCRIPTIONS', true),
        'refunds' => env('SLICK_ENABLE_REFUNDS', true),
        'partial_refunds' => env('SLICK_ENABLE_PARTIAL_REFUNDS', true),
        'saved_payment_methods' => env('SLICK_ENABLE_SAVED_METHODS', true),
    ],
    
    'ui' => [
        'admin_enabled' => env('SLICK_ADMIN_UI_ENABLED', true),
        'theme' => env('SLICK_UI_THEME', 'default'),
    ],
    
    'metrics' => [
        'driver' => env('SLICK_METRICS_DRIVER', 'log'), // 'log', 'noop'
        'channels' => [
            'default' => env('LOG_CHANNEL', 'stack'),
            'pci_audit' => env('SLICK_PCI_AUDIT_CHANNEL', 'single'),
        ],
    ],
];

Environment Variables

# Core Configuration
SLICK_PAYMENT_GATEWAY_DRIVER=stripe
SLICK_SASS_MODE_ENABLED=false

# Stripe
STRIPE_SECRET_KEY=sk_test_...
STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
STRIPE_CURRENCY=usd

# PayPal
PAYPAL_CLIENT_ID=your_paypal_client_id
PAYPAL_CLIENT_SECRET=your_paypal_client_secret
PAYPAL_MODE=sandbox
PAYPAL_CURRENCY=USD

# Authorize.Net
AUTHORIZE_NET_API_LOGIN_ID=your_api_login_id
AUTHORIZE_NET_TRANSACTION_KEY=your_transaction_key
AUTHORIZE_NET_SANDBOX=true
AUTHORIZE_NET_CURRENCY=USD

# Multi-Tenant Configuration
SLICK_TENANT_MODEL=App\\Models\\Company
SLICK_TENANT_KEY=company_id

# Security
SLICK_REQUIRE_HTTPS=true
SLICK_AUDIT_LOGGING=true
SLICK_RATE_LIMIT_PAYMENTS=60
SLICK_RATE_LIMIT_WEBHOOKS=1000

# Features
SLICK_ENABLE_SUBSCRIPTIONS=true
SLICK_ENABLE_REFUNDS=true
SLICK_ENABLE_PARTIAL_REFUNDS=true
SLICK_ENABLE_SAVED_METHODS=true

# UI
SLICK_ADMIN_UI_ENABLED=true
SLICK_UI_THEME=default

# Metrics & Logging
SLICK_METRICS_DRIVER=log
SLICK_PCI_AUDIT_CHANNEL=single

๐Ÿงช Testing

Running Tests

# Run all tests
php artisan test

# Run specific test suite
php artisan test --testsuite=Feature
php artisan test --testsuite=Unit

# Run with coverage
php artisan test --coverage

Test Your Integration

use Itul\SlickPaymentGateway\Tests\TestCase;
use Itul\SlickPaymentGateway\Facades\SlickPaymentGateway;

class PaymentIntegrationTest extends TestCase
{
    public function test_can_process_payment()
    {
        // Create test payment method
        $paymentMethod = $this->createTestPaymentMethod();
        
        // Process payment
        $result = SlickPaymentGateway::processPayment(
            PaymentRequest::make([
                'payment_method_id' => $paymentMethod->id,
                'amount' => 25.00,
                'currency' => 'USD'
            ])
        );
        
        $this->assertTrue($result->success);
        $this->assertNotNull($result->transactionId);
    }
}

Mock Responses for Testing

use Itul\SlickPaymentGateway\Testing\MockGatewayResponses;

// In your tests
MockGatewayResponses::fake([
    'stripe' => [
        'payment_intent.succeeded' => ['id' => 'pi_test_success'],
        'payment_method.attached' => ['id' => 'pm_test_method']
    ]
]);

๐Ÿšจ Error Handling

Exception Types

use Itul\SlickPaymentGateway\Exceptions\{
    PaymentGatewayException,
    InvalidPaymentMethodException,
    InsufficientFundsException,
    GatewayConfigurationException
};

try {
    $payment = SlickPaymentGateway::processPayment($request);
} catch (InvalidPaymentMethodException $e) {
    // Payment method is expired or invalid
    return response()->json(['error' => 'Please update your payment method'], 400);
} catch (InsufficientFundsException $e) {
    // Card declined due to insufficient funds
    return response()->json(['error' => 'Payment declined - insufficient funds'], 400);
} catch (PaymentGatewayException $e) {
    // Generic gateway error
    Log::error('Payment gateway error', ['error' => $e->getMessage()]);
    return response()->json(['error' => 'Payment processing error'], 500);
}

Graceful Error Handling

// Always check payment response
$result = SlickPaymentGateway::processPayment($request);

if (!$result->success) {
    // Log the failure
    Log::warning('Payment failed', [
        'message' => $result->message,
        'gateway_response' => $result->gatewayResponse
    ]);
    
    // Handle different failure types
    if (str_contains($result->message, 'card_declined')) {
        return back()->withErrors(['payment' => 'Your card was declined. Please try a different payment method.']);
    }
    
    if (str_contains($result->message, 'expired')) {
        return back()->withErrors(['payment' => 'Your payment method has expired. Please update your payment information.']);
    }
    
    // Generic error
    return back()->withErrors(['payment' => 'We encountered an issue processing your payment. Please try again.']);
}

๐ŸŽฏ Common Use Cases

E-commerce Integration

class CheckoutController extends Controller
{
    public function processCheckout(Request $request)
    {
        $cart = Cart::forUser(auth()->user());
        
        // Create order
        $order = SlickOrder::create([
            'customer_type' => 'App\\Models\\User',
            'customer_id' => auth()->id(),
            'total_amount' => $cart->total(),
            'currency' => 'USD',
            'status' => SlickOrder::STATUS_PENDING
        ]);
        
        // Add items to order
        foreach ($cart->items as $item) {
            $order->items()->create([
                'name' => $item->product->name,
                'quantity' => $item->quantity,
                'unit_price' => $item->unit_price,
                'total_price' => $item->total_price
            ]);
        }
        
        // Process payment
        $payment = SlickPaymentGateway::processPayment(
            PaymentRequest::make([
                'payment_method_id' => $request->payment_method_id,
                'amount' => $order->total_amount,
                'currency' => $order->currency,
                'description' => "Order #{$order->id}",
                'metadata' => ['order_id' => $order->id]
            ])
        );
        
        if ($payment->success) {
            $order->update(['status' => SlickOrder::STATUS_CONFIRMED]);
            $cart->clear();
            
            return redirect()->route('order.success', $order);
        }
        
        return back()->withErrors(['payment' => $payment->message]);
    }
}

Subscription Service

class SubscriptionController extends Controller
{
    public function subscribe(Request $request)
    {
        $plan = Plan::find($request->plan_id);
        
        // Create subscription
        $subscription = SlickPaymentGateway::createSubscription(
            SubscriptionRequest::make([
                'payment_method_id' => $request->payment_method_id,
                'amount' => $plan->price,
                'currency' => 'USD',
                'interval' => $plan->billing_interval, // 'monthly', 'yearly'
                'plan_name' => $plan->name,
                'metadata' => [
                    'user_id' => auth()->id(),
                    'plan_id' => $plan->id
                ]
            ])
        );
        
        if ($subscription->success) {
            // Update user subscription status
            auth()->user()->update([
                'subscription_id' => $subscription->transactionId,
                'plan_id' => $plan->id,
                'subscribed_at' => now()
            ]);
            
            return redirect()->route('dashboard')->with('success', 'Subscription activated!');
        }
        
        return back()->withErrors(['subscription' => $subscription->message]);
    }
}

Invoice Payment System

class InvoiceController extends Controller  
{
    public function payInvoice(Invoice $invoice, Request $request)
    {
        if ($invoice->isPaid()) {
            return redirect()->back()->with('info', 'Invoice already paid');
        }
        
        $payment = SlickPaymentGateway::processPayment(
            PaymentRequest::make([
                'payment_method_id' => $request->payment_method_id,
                'amount' => $invoice->total_amount,
                'currency' => $invoice->currency,
                'description' => "Invoice #{$invoice->number}",
                'invoice_id' => $invoice->id,
                'metadata' => [
                    'invoice_number' => $invoice->number,
                    'customer_id' => $invoice->customer_id
                ]
            ])
        );
        
        if ($payment->success) {
            $invoice->update([
                'status' => 'paid',
                'paid_at' => now(),
                'payment_transaction_id' => $payment->transactionId
            ]);
            
            // Send payment confirmation
            Mail::to($invoice->customer)->send(new PaymentReceiptMail($invoice, $payment));
            
            return redirect()->route('invoice.show', $invoice)
                           ->with('success', 'Payment processed successfully');
        }
        
        return back()->withErrors(['payment' => $payment->message]);
    }
}

๐Ÿ“Š Monitoring & Analytics

Payment Metrics

use Itul\SlickPaymentGateway\Models\SlickPayment;
use Illuminate\Support\Facades\DB;

class PaymentAnalytics
{
    public function getDashboardMetrics()
    {
        $today = now()->startOfDay();
        $lastMonth = now()->subMonth();
        
        return [
            'today_revenue' => SlickPayment::where('status', 'completed')
                                         ->whereDate('created_at', $today)
                                         ->sum('amount'),
            
            'monthly_revenue' => SlickPayment::where('status', 'completed')
                                           ->where('created_at', '>=', $lastMonth)
                                           ->sum('amount'),
            
            'success_rate' => $this->getSuccessRate(),
            
            'top_gateways' => SlickPayment::select('gateway', DB::raw('count(*) as count'))
                                       ->where('created_at', '>=', $lastMonth)
                                       ->groupBy('gateway')
                                       ->orderBy('count', 'desc')
                                       ->get(),
            
            'failed_payments' => SlickPayment::where('status', 'failed')
                                           ->whereDate('created_at', $today)
                                           ->count()
        ];
    }
    
    private function getSuccessRate(): float
    {
        $total = SlickPayment::whereDate('created_at', now())->count();
        $successful = SlickPayment::where('status', 'completed')
                                 ->whereDate('created_at', now())
                                 ->count();
        
        return $total > 0 ? ($successful / $total) * 100 : 0;
    }
}

Custom Analytics Dashboard

// Create analytics endpoints
Route::middleware(['auth', 'slick.auth'])->prefix('analytics')->group(function () {
    Route::get('/payments', [AnalyticsController::class, 'payments']);
    Route::get('/revenue', [AnalyticsController::class, 'revenue']);
    Route::get('/gateways', [AnalyticsController::class, 'gatewayPerformance']);
});

class AnalyticsController extends Controller
{
    public function payments(Request $request)
    {
        $query = SlickPayment::query();
        
        // Date filtering
        if ($request->has(['start_date', 'end_date'])) {
            $query->whereBetween('created_at', [
                $request->start_date,
                $request->end_date
            ]);
        }
        
        // Gateway filtering
        if ($request->has('gateway')) {
            $query->where('gateway', $request->gateway);
        }
        
        return response()->json([
            'payments' => $query->with(['paymentMethod', 'owner'])->paginate(50),
            'summary' => [
                'total_amount' => $query->sum('amount'),
                'average_amount' => $query->avg('amount'),
                'payment_count' => $query->count()
            ]
        ]);
    }
}

๐Ÿ”ง Artisan Commands

Available Commands

# Clean up expired payment methods
php artisan slick:clean-expired-payment-methods

# Generate PCI compliance report
php artisan slick:pci-audit-report

# Test gateway connections
php artisan slick:test-gateways

# Sync webhook configurations
php artisan slick:sync-webhooks

# Generate sample test data
php artisan slick:seed-test-data

Custom Command Examples

// Create custom commands for your business logic
class ProcessFailedPaymentsCommand extends Command
{
    protected $signature = 'payments:retry-failed {--days=3 : Days to look back}';
    protected $description = 'Retry failed payments from recent days';
    
    public function handle()
    {
        $days = $this->option('days');
        $cutoff = now()->subDays($days);
        
        $failedPayments = SlickPayment::where('status', 'failed')
                                    ->where('created_at', '>=', $cutoff)
                                    ->where('retry_count', '<', 3)
                                    ->get();
        
        $this->info("Found {$failedPayments->count()} failed payments to retry");
        
        foreach ($failedPayments as $payment) {
            $this->retryPayment($payment);
        }
    }
}

๐Ÿš€ Performance Optimization

Database Optimization

// Add indexes for performance
Schema::table('slick_payments', function (Blueprint $table) {
    $table->index(['status', 'created_at']);
    $table->index(['gateway', 'gateway_transaction_id']);
    $table->index(['owner_type', 'owner_id']);
});

// Optimize queries with eager loading
$payments = SlickPayment::with([
    'paymentMethod:id,last_four,brand',
    'billingContact:id,name,email',
    'owner:id,name'
])->where('status', 'completed')
  ->orderBy('created_at', 'desc')
  ->paginate(25);

Caching Strategy

use Illuminate\Support\Facades\Cache;

class PaymentService
{
    public function getGatewayConfig(string $gateway): array
    {
        return Cache::remember(
            "payment_gateway_config_{$gateway}",
            now()->addHours(1),
            fn() => config("slick-payment-gateway.drivers.{$gateway}")
        );
    }
    
    public function getUserPaymentMethods(User $user): Collection
    {
        return Cache::tags(['user_payment_methods', "user_{$user->id}"])
                   ->remember(
                       "user_payment_methods_{$user->id}",
                       now()->addMinutes(30),
                       fn() => $user->slickPaymentMethods()->active()->get()
                   );
    }
}

Async Processing

use Itul\SlickPaymentGateway\Jobs\ProcessWebhookJob;
use Itul\SlickPaymentGateway\Jobs\SendPaymentNotificationJob;

// Queue heavy operations
class PaymentController extends Controller
{
    public function processPayment(Request $request)
    {
        $payment = SlickPaymentGateway::processPayment($paymentRequest);
        
        if ($payment->success) {
            // Queue notification instead of sending immediately
            SendPaymentNotificationJob::dispatch($payment)->delay(now()->addMinutes(1));
            
            // Queue analytics update
            UpdateAnalyticsJob::dispatch($payment);
        }
        
        return response()->json($payment);
    }
}

๐ŸŒ Multi-Language Support

Localization Setup

// resources/lang/en/slick-payment-gateway.php
return [
    'payment' => [
        'success' => 'Payment processed successfully',
        'failed' => 'Payment failed: :reason',
        'pending' => 'Payment is being processed',
        'cancelled' => 'Payment was cancelled'
    ],
    'errors' => [
        'invalid_payment_method' => 'Invalid payment method',
        'insufficient_funds' => 'Insufficient funds',
        'expired_card' => 'Payment method has expired',
        'gateway_error' => 'Payment gateway error'
    ]
];

// Usage in your application
echo __('slick-payment-gateway::payment.success');

Multi-Currency Support

// Configure currencies per gateway
'drivers' => [
    'stripe' => [
        'supported_currencies' => ['USD', 'EUR', 'GBP', 'CAD', 'AUD'],
        'default_currency' => 'USD'
    ],
    'paypal' => [
        'supported_currencies' => ['USD', 'EUR', 'GBP', 'CAD', 'AUD', 'JPY'],
        'default_currency' => 'USD'  
    ]
];

// Currency conversion helper
class CurrencyService
{
    public function convertAmount(float $amount, string $from, string $to): float
    {
        if ($from === $to) return $amount;
        
        $rate = $this->getExchangeRate($from, $to);
        return round($amount * $rate, 2);
    }
}

๐Ÿ“ฑ API Integration

RESTful API Endpoints

// API Routes (protected by Sanctum/Passport)
Route::middleware(['auth:sanctum'])->prefix('api/v1/payments')->group(function () {
    // Payment methods
    Route::get('/payment-methods', [ApiPaymentController::class, 'getPaymentMethods']);
    Route::post('/payment-methods', [ApiPaymentController::class, 'storePaymentMethod']);
    Route::delete('/payment-methods/{id}', [ApiPaymentController::class, 'deletePaymentMethod']);
    
    // Payments
    Route::post('/process', [ApiPaymentController::class, 'processPayment']);
    Route::post('/refund', [ApiPaymentController::class, 'refundPayment']);
    
    // Subscriptions
    Route::post('/subscriptions', [ApiPaymentController::class, 'createSubscription']);
    Route::delete('/subscriptions/{id}', [ApiPaymentController::class, 'cancelSubscription']);
});

API Controller Example

class ApiPaymentController extends Controller
{
    public function processPayment(Request $request)
    {
        $request->validate([
            'payment_method_id' => 'required|exists:slick_payment_methods,id',
            'amount' => 'required|numeric|min:0.01|max:999999.99',
            'currency' => 'required|string|size:3',
            'description' => 'nullable|string|max:255'
        ]);
        
        try {
            $payment = SlickPaymentGateway::processPayment(
                PaymentRequest::make($request->all())
            );
            
            return response()->json([
                'success' => $payment->success,
                'transaction_id' => $payment->transactionId,
                'status' => $payment->status,
                'message' => $payment->message
            ]);
            
        } catch (\Exception $e) {
            return response()->json([
                'success' => false,
                'message' => 'Payment processing error'
            ], 500);
        }
    }
}

Mobile SDK Integration

// React Native / Flutter example
const processPayment = async (paymentData) => {
    try {
        const response = await fetch('/api/v1/payments/process', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Authorization': `Bearer ${userToken}`
            },
            body: JSON.stringify(paymentData)
        });
        
        const result = await response.json();
        
        if (result.success) {
            showSuccessMessage('Payment processed successfully!');
            navigateToSuccessScreen(result.transaction_id);
        } else {
            showErrorMessage(result.message);
        }
    } catch (error) {
        showErrorMessage('Network error occurred');
    }
};

๐Ÿ› Troubleshooting

Common Issues & Solutions

1. Payment Method Token Validation Fails

// Problem: Token format validation fails
// Solution: Ensure client-side tokenization is working

// Check token format
if (!str_starts_with($token, 'pm_')) { // Stripe
    throw new InvalidArgumentException('Invalid Stripe payment method token');
}

if (!str_starts_with($token, 'PAYID-')) { // PayPal
    throw new InvalidArgumentException('Invalid PayPal payment method token');
}

2. Multi-Tenant Data Isolation Issues

// Problem: Cross-tenant data access
// Solution: Verify tenant scoping

// Check current tenant
$currentTenant = SlickTenantConnection::current();
if (!$currentTenant) {
    throw new \Exception('No tenant context available');
}

// Verify model uses SassAware trait
class User extends Model
{
    use \Itul\SlickPaymentGateway\Traits\SassAware; // Required for tenant scoping
}

3. Webhook Signature Verification Fails

// Problem: Webhooks fail validation
// Solution: Check webhook secrets and payload format

// Debug webhook signature
Log::debug('Webhook signature debug', [
    'received_signature' => $request->header('Stripe-Signature'),
    'calculated_signature' => $this->calculateExpectedSignature($payload),
    'webhook_secret' => substr(config('slick-payment-gateway.drivers.stripe.webhook_secret'), 0, 10) . '...'
]);

4. Database Migration Issues

# Problem: Migration conflicts
# Solution: Clear and re-run migrations

php artisan migrate:rollback --path=/database/migrations/*slick*
php artisan migrate --path=/database/migrations/*slick*

# Or use package migrations specifically
php artisan migrate --path=vendor/itul/slick-payment-gateway/database/migrations

5. Rate Limiting Issues

// Problem: Too many requests error
// Solution: Adjust rate limiting configuration

// In config/slick-payment-gateway.php
'security' => [
    'rate_limiting' => [
        'payments' => 120, // Increase from 60 to 120 per minute
        'webhooks' => 2000, // Increase from 1000 to 2000 per minute
    ],
],

Debug Mode

// Enable detailed logging for debugging
// In .env
SLICK_DEBUG_MODE=true
LOG_LEVEL=debug

// Check logs
tail -f storage/logs/laravel.log | grep slick

๐Ÿ“š Additional Resources

Official Documentation

Payment Gateway Documentation

๐Ÿ“„ License

The MIT License (MIT). Please see License File for more information.

๐Ÿ† Credits

Slick Payment Gateway is created and maintained by iTul.

Core Team

  • Brandon Moore - Lead Developer & Architect
  • Contributors - See Contributors

๐Ÿš€ What's Next?

Roadmap

  • v2.1: GraphQL API support
  • v2.2: Apple Pay & Google Pay integration
  • v2.3: Advanced fraud detection
  • v3.0: Blockchain payment support
**Made with โค๏ธ by [iTul](https://i-tul.com)** *Secure โ€ข Scalable โ€ข PCI Compliant โ€ข Developer Friendly* [โฌ† Back to Top](#-slick-payment-gateway)