itul / slick-payment-gateway
A Laravel package for integrating multiple payment gateways with PCI compliant tokenization
Requires
- php: ^8.1
- guzzlehttp/guzzle: >=7.0
- laravel/framework: >=10.0
- paypal/paypal-checkout-sdk: >=1.0
- stripe/stripe-php: >=10.0
Requires (Dev)
- laravel/pint: ^1.15
- nunomaduro/larastan: ^2.9
- orchestra/testbench: ^8.0.0|^9.0
- phpunit/phpunit: ^10.5|^11.0
README
A PCI DSS compliant Laravel package for payment processing with multiple gateway support. Currently in active development - production deployment should only be done after thorough testing.
π§ Current Development Status
Feature | Status | Notes |
---|---|---|
Stripe Integration | β Complete | Payment processing, refunds, webhook verification |
PayPal Integration | π‘ Partial | Basic payments, webhook verification enhanced |
Authorize.Net Integration | π‘ Partial | Basic payments, webhook verification enhanced |
PCI Compliance | β Compliant | Client-side tokenization only, no raw card data |
Multi-Tenant SaaS | β Complete | Full tenant isolation and configuration |
Webhook Security | β Secured | Proper signature verification implemented |
Testing Suite | π‘ Basic | Core tests implemented, expanding coverage |
β¨ 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 (complete), PayPal & Authorize.Net (basic)
- π Enterprise Security - Laravel encryption, audit logging, proper webhook verification
- π― Developer Friendly - Clean APIs, comprehensive error handling
- β‘ Production Focused - Rate limiting, webhook handling, comprehensive logging
π 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 (Complete Support)
STRIPE_SECRET_KEY=sk_test_...
STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
# PayPal Configuration (Basic Support)
PAYPAL_CLIENT_ID=your_client_id
PAYPAL_CLIENT_SECRET=your_client_secret
PAYPAL_MODE=sandbox
# Authorize.Net Configuration (Basic Support)
AUTHORIZE_NET_API_LOGIN_ID=your_login_id
AUTHORIZE_NET_TRANSACTION_KEY=your_transaction_key
AUTHORIZE_NET_SANDBOX=true
4. Your First Payment β‘
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;
echo "Status: " . $payment->status;
} else {
echo "Error: " . $payment->message;
}
π³ Payment Method Management
Client-Side Tokenization (PCI Compliant)
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 only the 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)
{
$request->validate([
'payment_method_token' => 'required|string',
'owner_type' => 'required|string',
'owner_id' => 'required|integer'
]);
try {
$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'
]);
} catch (\Exception $e) {
return response()->json([
'error' => 'Failed to save payment method: ' . $e->getMessage()
], 400);
}
}
π° Payment Processing
Stripe Payments (Complete Implementation)
use Itul\SlickPaymentGateway\DTOs\PaymentRequest;
use Itul\SlickPaymentGateway\Facades\SlickPaymentGateway;
// Configure for Stripe (most complete implementation)
config(['slick-payment-gateway.default_driver' => 'stripe']);
$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'
]
])
);
if ($result->success) {
// Payment completed
$transactionId = $result->transactionId;
$status = $result->status; // 'completed', 'pending', etc.
// Access Stripe-specific data
$clientSecret = $result->gatewayResponse['client_secret'];
return redirect()->route('payment.success', $transactionId);
} else {
return back()->withErrors(['payment' => $result->message]);
}
PayPal & Authorize.Net Payments (Basic Implementation)
// PayPal and Authorize.Net have basic payment processing
// More advanced features are planned for future releases
$payment = SlickPaymentGateway::processPayment(
PaymentRequest::make([
'payment_method_id' => '1',
'amount' => 50.00,
'currency' => 'USD',
'description' => 'Service Payment'
])
);
π Refund Processing
use Itul\SlickPaymentGateway\DTOs\RefundRequest;
// Full refund
$refund = SlickPaymentGateway::refundPayment(
RefundRequest::make([
'transaction_id' => 'pi_1234567890', // Stripe payment intent ID
'reason' => 'Customer requested cancellation'
])
);
// Partial refund
$partialRefund = SlickPaymentGateway::refundPayment(
RefundRequest::make([
'transaction_id' => 'pi_1234567890',
'amount' => 25.00, // Refund $25 of original $100
'reason' => 'Partial service credit'
])
);
if ($refund->success) {
echo "Refund ID: " . $refund->transactionId;
} else {
echo "Refund failed: " . $refund->message;
}
π’ 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
]);
π‘ Webhook Security
Secure Webhook Endpoints
The package now includes proper webhook signature verification:
// Webhook URLs with enhanced security:
// Stripe: https://yourapp.com/api/slick/payment-webhooks/stripe
// PayPal: https://yourapp.com/api/slick/payment-webhooks/paypal
// Authorize.Net: https://yourapp.com/api/slick/payment-webhooks/authorize-net
Webhook Verification
All webhooks now use cryptographic signature verification:
- Stripe: Uses
Webhook::constructEvent()
with shared secret - PayPal: Validates required headers and transmission signatures
- Authorize.Net: Uses SHA-512 HMAC signature verification
π§ͺ Testing
# Run the test suite
php artisan test
# Run specific tests
php artisan test --filter=StripeDriverTest
php artisan test --filter=GatewayConnectionTest
Test Gateway Connections
// Test your gateway configurations
Route::post('/test-gateway/{driver}', function($driver) {
$controller = new \Itul\SlickPaymentGateway\Http\Controllers\GatewayController();
try {
$result = $controller->test(request(), $driver);
return response()->json(['success' => true, 'message' => 'Connection successful']);
} catch (\Exception $e) {
return response()->json(['success' => false, 'message' => $e->getMessage()], 400);
}
});
π¨ Error Handling
The package includes comprehensive error handling:
use Itul\SlickPaymentGateway\Exceptions\{
PaymentGatewayException,
InvalidPaymentMethodException,
InsufficientFundsException,
GatewayConfigurationException,
WebhookVerificationException
};
try {
$payment = SlickPaymentGateway::processPayment($request);
} catch (InvalidPaymentMethodException $e) {
return response()->json(['error' => 'Please update your payment method'], 400);
} catch (InsufficientFundsException $e) {
return response()->json(['error' => 'Payment declined - insufficient funds'], 400);
} catch (GatewayConfigurationException $e) {
Log::error('Gateway configuration error', ['error' => $e->getMessage()]);
return response()->json(['error' => 'Payment system temporarily unavailable'], 500);
} catch (PaymentGatewayException $e) {
return response()->json(['error' => 'Payment processing error'], 500);
}
βοΈ Configuration
Complete Configuration
// 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
],
],
];
π Roadmap
Version 2.1 (Planned)
- Complete PayPal subscription support
- Enhanced Authorize.Net features
- GraphQL API support
Version 2.2 (Planned)
- Apple Pay & Google Pay integration
- Advanced fraud detection
- Expanded webhook event handling
Version 2.3 (Planned)
- Advanced analytics dashboard
- Multi-currency conversion
- Enhanced reporting features
β οΈ Important Notes
Development Status
- Stripe integration is complete and production-ready
- PayPal and Authorize.Net have basic functionality - test thoroughly before production
- Webhook security has been significantly enhanced from initial versions
- Multi-tenant features are fully implemented and tested
Before Production Deployment
- β Test all payment flows with your chosen gateways
- β Verify webhook endpoints are working correctly
- β Configure proper SSL certificates for webhook security
- β Set up monitoring for payment failures and webhook issues
- β Test multi-tenant isolation if using SaaS mode
Security Best Practices
- Never process raw card data - always use client-side tokenization
- Regularly rotate webhook secrets
- Monitor PCI audit logs for suspicious activity
- Use HTTPS everywhere - no exceptions
π 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
Made by iTul
Secure β’ Scalable β’ PCI Compliant β’ Developer Friendly
Local demo & tests (Null driver)
The package includes a Null gateway driver and a seeding command so you can demo the full flow without touching real payment APIs.
1) Install dev dependencies
composer install
2) Ensure the Null driver is registered
In config/slick-payment-gateway.php
, the services
array contains 'null' => Itul\SlickPaymentGateway\Services\NullGatewayService::class
.
Optionally set the default driver for quick demos:
// config/slick-payment-gateway.php
return [
'default_driver' => 'null',
'services' => [
'stripe' => Itul\SlickPaymentGateway\Services\StripeGatewayService::class,
'paypal' => Itul\SlickPaymentGateway\Services\PayPalGatewayService::class,
'authorize_net' => Itul\SlickPaymentGateway\Services\AuthorizeNetGatewayService::class,
'quickbooks' => Itul\SlickPaymentGateway\Services\QuickBooksGatewayService::class,
'null' => Itul\SlickPaymentGateway\Services\NullGatewayService::class,
],
];
3) Migrate package tables
php artisan migrate
4) Seed fake βremoteβ records
Create remote-only records in the Null driver and print their IDs:
php artisan slick:seed-null-gateway --type=payment --count=3
Or create remote records and local rows pointing to them:
php artisan slick:seed-null-gateway --type=order --count=2 --create-local
5) Exercise the model trait
use Itul\SlickPaymentGateway\Models\SlickPayment;
// Create a local model and send to the gateway (create or update)
$pay = new SlickPayment([
'amount' => 12.34,
'currency' => 'USD',
'description' => 'Demo',
]);
$pay->save();
$pay->gatewaySend(); // sets gateway_record_*
// Read from the gateway and sync back into the model
$pay->gatewayRead();
// Delete in the gateway and clear local gateway_record_* fields
$pay->gatewayDelete();
6) Run tests
./vendor/bin/phpunit
Note: Field mappings are centralized and immutable inside FieldMappingRegistry
. Developers cannot customize mappings via config or by altering app models.