subtain / laravel-payments
A clean, extensible multi-gateway payment package for Laravel. Supports Fanbasis, PremiumPay, Match2Pay out of the box — and any custom gateway you build.
Requires
- php: ^8.1
- illuminate/contracts: ^10.0|^11.0|^12.0|^13.0
- illuminate/database: ^10.0|^11.0|^12.0|^13.0
- illuminate/http: ^10.0|^11.0|^12.0|^13.0
- illuminate/routing: ^10.0|^11.0|^12.0|^13.0
- illuminate/support: ^10.0|^11.0|^12.0|^13.0
- illuminate/validation: ^10.0|^11.0|^12.0|^13.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0|^10.0
- phpunit/phpunit: ^10.0|^11.0
README
A unified, production-ready payment SDK for Laravel. Write your checkout and webhook logic once — switch gateways, add new ones, and scale without changing a line of application code.
Features
- 4 production-ready gateways — Fanbasis, Match2Pay (crypto), Rebornpay (UPI/India), PremiumPay
- Payment records — full DB tracking with polymorphic ownership, status machine, and audit trail
- Automatic webhook handling — signature verification, status transitions, event dispatch for every gateway
- Discount codes — gateway-agnostic, auto-applied and auto-recorded on webhook confirmation
- Sandbox mode — simulate full payment flows without real charges, on any environment
- Centralized logging — per-gateway channel routing, level control, sensitive field redaction
- API key fingerprinting — post-rotation audit trail on every payment record and log entry
- Extensible — add any PSP in 3 steps, gets all features for free
Supported Gateways
| Gateway | Type | Checkout | Webhooks | Full API | Docs |
|---|---|---|---|---|---|
| Fanbasis | Cards, MoR | One-time, Subscriptions, Embedded, Static | 12 event types, HMAC-SHA256 | Customers, Subscribers, Discount Codes, Products, Transactions, Refunds, Webhooks | → |
| Match2Pay | Crypto (USDT, BTC, ETH, BNB, 40+) | API checkout, 2-step selection | SHA-384 (DONE only) | Deposits, Withdrawals | → |
| Rebornpay | UPI / IMPS (India) | API checkout | MD5 + Python-repr | Pay-in, Status checks, UTR storage | → |
| PremiumPay | Cards | API checkout | Callback-based | Checkout only | See below |
| Your gateway | Any | Implement interface | Your logic | Your logic | → Custom gateways |
Documentation
| Topic | Description |
|---|---|
| Payments | PaymentService, CheckoutRequest, Payment model, status machine, HasPayments trait, webhooks, events |
| Discount Codes | Creating codes, auto-apply, validate, audit trail, model API, idempotency |
| Sandbox Mode | Environment sandbox, per-user bypass, confirm endpoint, QA flows |
| Logging | Channel routing, log levels, redaction, recipes for Slack/Telegram/DB/ClickHouse |
| Key Fingerprinting | Post-rotation auditing, database column, log context, custom gateways |
| Custom Gateways | Interface, registration, PaymentLogger usage, what you get for free |
| Fanbasis | All checkout modes, full API suite, webhook event reference |
| Match2Pay | Crypto checkout, cryptocurrency reference, withdrawal API, wallet expiry |
| Rebornpay | UPI/IMPS, INR conversion, status checks, UTR storage, signature verification |
Install
composer require subtain/laravel-payments php artisan vendor:publish --tag=payments-config php artisan vendor:publish --tag=payments-migrations php artisan migrate
Quick Start
1. Set gateway credentials
FANBASIS_API_KEY=your-key FANBASIS_WEBHOOK_SECRET=your-secret
Each gateway has its own env vars. See the gateway docs linked above.
2. Initiate a payment
use Subtain\LaravelPayments\PaymentService; use Subtain\LaravelPayments\DTOs\CheckoutRequest; $result = app(PaymentService::class)->initiate( gateway: 'fanbasis', request: new CheckoutRequest( amount: 299.00, productName: 'Pro Plan', webhookUrl: route('payments.webhook', 'fanbasis'), successUrl: 'https://app.com/success', invoiceId: (string) $order->id, ), payable: $order, ); return redirect($result->redirectUrl);
The package creates an lp_payments record, calls the gateway, and returns the checkout URL. When the webhook arrives, it verifies the signature, updates the payment status, and dispatches events — all automatically.
3. Handle the result
// In your EventServiceProvider protected $listen = [ \Subtain\LaravelPayments\Events\PaymentSucceeded::class => [ \App\Listeners\FulfillOrder::class, ], \Subtain\LaravelPayments\Events\PaymentFailed::class => [ \App\Listeners\NotifyCustomer::class, ], ];
class FulfillOrder { public function handle(PaymentSucceeded $event): void { $payment = $event->payment; // Payment model $order = $payment->payable; // your Order model // Provision accounts, send emails, etc. } }
Gateways
Fanbasis
use Subtain\LaravelPayments\Facades\Payment; // One-time Payment::gateway('fanbasis')->checkout(new CheckoutRequest( amount: 299.00, productName: 'Pro Plan', webhookUrl: route('payments.webhook', 'fanbasis'), successUrl: 'https://app.com/success', )); // Subscription Payment::gateway('fanbasis')->checkout(new CheckoutRequest( amount: 29.99, productName: 'Monthly', webhookUrl: route('payments.webhook', 'fanbasis'), extra: ['subscription' => ['frequency_days' => 30]], )); // Full API access $fb = Payment::gateway('fanbasis'); $fb->customers()->list(['search' => 'jane@example.com']); $fb->refunds()->create('txn_abc'); $fb->webhooks()->create([...]);
Match2Pay (Crypto)
Payment::gateway('match2pay')->checkout(new CheckoutRequest( amount: 299.00, currency: 'USD', webhookUrl: route('payments.webhook', 'match2pay'), successUrl: 'https://app.com/success', extra: [ 'payment_currency' => 'USX', // USDT TRC20 'payment_gateway_name' => 'USDT TRC20', // omit for 2-step selection ], ));
→ Full Match2Pay documentation
Rebornpay (UPI / IMPS — India)
Payment::gateway('rebornpay')->checkout(new CheckoutRequest( amount: 92000.00, currency: 'INR', invoiceId: 'order_123', customerName: 'Raj Kumar', successUrl: 'https://app.com/success', ));
→ Full Rebornpay documentation
PremiumPay
Payment::gateway('premiumpay')->checkout(new CheckoutRequest( amount: 99.00, invoiceId: 'inv_456', customerEmail: 'user@example.com', customerIp: request()->ip(), productName: 'Starter Package', successUrl: 'https://app.com/success', webhookUrl: route('payments.webhook', 'premiumpay'), ));
Discount Codes
Gateway-agnostic. The package validates, calculates the discount, and records usage after webhook confirmation — automatically.
// Pass discountCode on the CheckoutRequest — that's all $result = app(PaymentService::class)->initiate( gateway: 'fanbasis', request: new CheckoutRequest( amount: 299.00, webhookUrl: route('payments.webhook', 'fanbasis'), discountCode: $request->input('discount_code'), // 'LAUNCH50' userId: auth()->id(), ), payable: $order, ); // Gateway receives 249.00. usage is auto-recorded on successful webhook.
use Subtain\LaravelPayments\Models\DiscountCode; DiscountCode::create([ 'code' => 'LAUNCH50', 'type' => 'fixed', 'value' => 50, 'max_total_uses' => 100, 'max_uses_per_user' => 1, ]);
Sandbox Mode
Simulate the full payment flow without real charges. Every DB record, log, and event fires identically.
# .env.local or .env.staging PAYMENTS_SANDBOX=true
QA trigger endpoint (simulates a successful webhook):
GET /payments/webhook/sandbox/confirm/{invoice_id}
Per-user bypass on production (internal QA accounts):
'sandbox' => [ 'bypass_user_ids' => [1, 42], 'bypass_roles' => ['qa_tester'], ],
Logging
Zero config required. Logs to your app's default channel out of the box.
// config/lp_payments.php 'logging' => [ 'level' => env('PAYMENTS_LOG_LEVEL', 'info'), 'channels' => [ 'default' => env('PAYMENTS_LOG_CHANNEL', null), 'match2pay' => 'slack', // route a gateway to Slack 'rebornpay' => 'telegram', // or Telegram ], 'redact' => ['api_key', 'secret', 'token', 'password'], ],
Log format:
[payments:fanbasis:checkout] checkout.initiated
[payments:match2pay:webhook] webhook.signature_failed
API Key Fingerprinting
Every lp_payments row stores a first4****last4 fingerprint of the API key active at initiation time. Solve post-rotation audit questions instantly.
$payment->key_fingerprint; // "sk_l****z789" Payment::where('key_fingerprint', 'sk_l****z789')->get();
→ Full key fingerprinting documentation
Add a Custom Gateway
// 1. Implement the interface (3 methods) class StripeGateway implements PaymentGateway { public function name(): string { return 'stripe'; } public function checkout(CheckoutRequest $request): CheckoutResult { /* ... */ } public function parseWebhook(array $payload): WebhookResult { /* ... */ } public function verifyWebhook(array $payload, array $headers = []): bool { /* ... */ } } // 2. Register in config/lp_payments.php 'stripe' => [ 'driver' => \App\Gateways\StripeGateway::class, 'api_key' => env('STRIPE_API_KEY'), 'key_fields' => ['api_key'], ], // 3. Use it — identical to built-in gateways Payment::gateway('stripe')->checkout($checkoutRequest); app(PaymentService::class)->initiate('stripe', $checkoutRequest, $order);
Your gateway automatically gets DB tracking, webhook handling, sandbox support, logging, key fingerprinting, and discounts.
→ Full custom gateway documentation
Testing
Payment::shouldReceive('gateway->checkout') ->andReturn(new CheckoutResult(redirectUrl: 'https://test.com', gateway: 'fanbasis'));
For full integration tests, use PAYMENTS_SANDBOX=true in phpunit.xml and hit the sandbox confirm endpoint to simulate successful payments.
Requirements
- PHP 8.1+
- Laravel 10 / 11 / 12
License
MIT — LICENSE
Author
Syed Subtain Haider — GitHub