A flexible payment processing system for Laravel using the Factory Pattern. This package provides a unified interface for multiple payment processors, making it easy to add new payment gateways without changing your application code.
- Factory Pattern: Easy to extend with new payment processors
- Unified Interface: Consistent API across all payment processors
- Laravel Integration: Native Laravel package with service provider
- Database Models: Built-in models for transactions, payments, and payment methods
- Configurable: Flexible configuration system
- Testable: Comprehensive test suite included
- Multiple Processors: Support for custom processors
- Refund Support: Full and partial refunds
- Authorization & Capture: Pre-authorization and later capture
- Payment Methods: Store and manage customer payment methods
- Webhooks: Built-in webhook handling support
- Security: Encrypted sensitive data storage
- Logging: Comprehensive payment logging
composer require asciisd/cashier-corePublish the configuration file:
php artisan vendor:publish --provider="Asciisd\CashierCore\CashierCoreServiceProvider" --tag="config"Publish and run the migrations:
php artisan vendor:publish --provider="Asciisd\CashierCore\CashierCoreServiceProvider" --tag="migrations"
php artisan migrateAdd the following environment variables to your .env file:
# Default processor
CASHIER_DEFAULT_PROCESSOR=stripe
# General Settings
CASHIER_CURRENCY=USD
CASHIER_LOGGING_ENABLED=trueuse Asciisd\CashierCore\Facades\PaymentFactory;
// Create a payment processor
$processor = PaymentFactory::create('stripe');
// Process a payment
$result = $processor->charge([
'amount' => 20, // $20.00
'currency' => 'USD',
'source' => 'tok_visa',
'description' => 'Order #12345',
'metadata' => [
'order_id' => '12345',
'customer_id' => 'cust_123',
],
]);
if ($result->isSuccessful()) {
echo "Payment successful! Transaction ID: {$result->transactionId}";
} else {
echo "Payment failed: {$result->message}";
}// Check available processors
$processors = PaymentFactory::getProcessorNames();
// Returns: []$processor = PaymentFactory::create('stripe');
// Full refund
$refundResult = $processor->refund('ch_transaction_id');
// Partial refund
$partialRefundResult = $processor->refund('ch_transaction_id', 500); // $5.00
if ($refundResult->isSuccessful()) {
echo "Refund successful! Refund ID: {$refundResult->refundId}";
}$processor = PaymentFactory::create('stripe');
// Authorize payment
$authResult = $processor->authorize([
'amount' => 3000,
'currency' => 'USD',
'source' => 'tok_visa',
'description' => 'Pre-authorization',
]);
if ($authResult->isSuccessful()) {
// Later, capture the payment
$captureResult = $processor->capture($authResult->transactionId, 2500);
}Add the Payable trait to your models that can make payments:
use Asciisd\CashierCore\Traits\Payable;
class User extends Model
{
use Payable;
// Your model code...
}This provides helpful methods:
$user = User::find(1);
// Get all transactions
$transactions = $user->transactions;
// Get successful transactions
$successful = $user->getSuccessfulTransactions();
// Get total amount spent
$totalSpent = $user->getTotalSpent(); // Returns total amount
$formattedTotal = $user->getFormattedTotalSpent(); // Returns formatted string
// Payment methods
$paymentMethods = $user->paymentMethods;
$defaultMethod = $user->getDefaultPaymentMethod();use Asciisd\CashierCore\Models\Transaction;
// Create a transaction record
$transaction = Transaction::create([
'processor_name' => 'stripe',
'processor_transaction_id' => $result->transactionId,
'payable_type' => 'App\\Models\\User',
'payable_id' => $user->id,
'amount' => $result->amount,
'currency' => $result->currency,
'status' => $result->status,
'description' => 'Order payment',
'processed_at' => now(),
]);
// Query transactions
$successfulTransactions = Transaction::successful()->get();
$stripeTransactions = Transaction::byProcessor('stripe')->get();
$recentTransactions = Transaction::where('created_at', '>=', now()->subDays(7))->get();use Asciisd\CashierCore\Models\PaymentMethod;
// Create a payment method
$paymentMethod = PaymentMethod::create([
'user_type' => 'App\\Models\\User',
'user_id' => $user->id,
'processor_name' => 'stripe',
'processor_payment_method_id' => 'pm_1234567890',
'type' => 'credit_card',
'brand' => 'visa',
'last_four' => '4242',
'exp_month' => 12,
'exp_year' => 2025,
'is_default' => true,
]);
// Make it the default
$paymentMethod->makeDefault();
// Check expiration
if ($paymentMethod->is_expired) {
// Handle expired payment method
}- Create a class that extends
AbstractPaymentProcessor:
use Asciisd\CashierCore\Abstracts\AbstractPaymentProcessor;
use Asciisd\CashierCore\DataObjects\PaymentResult;
use Asciisd\CashierCore\DataObjects\RefundResult;
class CustomProcessor extends AbstractPaymentProcessor
{
protected array $supportedFeatures = ['charge', 'refund'];
public function getName(): string
{
return 'custom';
}
public function charge(array $data): PaymentResult
{
$validatedData = $this->validatePaymentData($data);
// Your payment processing logic here
return $this->createSuccessResult(
transactionId: 'custom_' . uniqid(),
amount: $validatedData['amount'],
currency: $validatedData['currency']
);
}
public function refund(string $transactionId, ?int $amount = null): RefundResult
{
// Your refund logic here
}
}- Register it in your configuration:
// config/cashier-core.php
'processors' => [
'custom' => [
'class' => \App\PaymentProcessors\CustomProcessor::class,
'config' => [
'api_key' => env('CUSTOM_API_KEY'),
'secret' => env('CUSTOM_SECRET'),
],
],
],- Use it through the factory:
$processor = PaymentFactory::create('custom');use Asciisd\CashierCore\Exceptions\InvalidPaymentDataException;
use Asciisd\CashierCore\Exceptions\PaymentProcessingException;
use Asciisd\CashierCore\Exceptions\ProcessorNotFoundException;
try {
$processor = PaymentFactory::create('stripe');
$result = $processor->charge($paymentData);
} catch (InvalidPaymentDataException $e) {
// Handle validation errors
echo "Invalid payment data: {$e->getMessage()}";
} catch (PaymentProcessingException $e) {
// Handle payment processing errors
echo "Payment failed: {$e->getMessage()}";
if ($e->getTransactionId()) {
echo "Transaction ID: {$e->getTransactionId()}";
}
} catch (ProcessorNotFoundException $e) {
// Handle unknown processor
echo "Processor not found: {$e->getMessage()}";
}Run the test suite:
composer testRun tests with coverage:
composer test-coverageThe package provides extensive configuration options. See the published config file for all available settings:
- Processors: Configure multiple payment processors
- Currency: Set default and supported currencies
- Database: Customize table names and connections
- Webhooks: Configure webhook handling
- Logging: Control payment logging
- Security: Configure data encryption and masking
- Retry Logic: Set up retry mechanisms for failed payments
- Feature Flags: Enable/disable specific features
The Factory Pattern makes it easy to add new processors:
- Implement the
PaymentProcessorInterface - Extend
AbstractPaymentProcessorfor common functionality - Register in configuration
- Use immediately through the factory
- Data Encryption: Sensitive data is encrypted before storage
- Card Masking: Credit card numbers are automatically masked
- Webhook Verification: Secure webhook signature verification
- Input Validation: Comprehensive input validation for all processors
Check the examples/BasicUsage.php file for comprehensive usage examples covering:
- Basic payment processing
- Refund handling
- Authorization and capture
- Payment method management
- Error handling
- Custom processor registration
- Database queries
Contributions are welcome! Please feel free to submit a Pull Request.
The MIT License (MIT). Please see License File for more information.
For support, please open an issue on GitHub or contact us at [email protected].