Documentation
Integrate SSLCommerz payments into your Laravel application in minutes.
Overview
A fluent, facade-driven Laravel package for SSLCommerz payment gateway. Supports the complete payment lifecycle — initiating payments, validating transactions, processing refunds, and verifying hash integrity.
Requirements: PHP 8.2+ and Laravel 10, 11, 12, or 13.
Installation
composer require hasinhayder/sslcommerz-laravel php artisan sslcommerz-laravel:install
This publishes the configuration file to config/sslcommerz.php. The package auto-registers via Laravel's package discovery — no manual service provider registration needed.
Configuration
# Sandbox mode (default: true) SSLC_SANDBOX=true # Store credentials (required) SSLC_STORE_ID=your_store_id SSLC_STORE_PASSWORD=your_store_password # Currency (default: BDT) SSLC_STORE_CURRENCY=BDT # Callback route names (optional, set to match your routes) SSLC_ROUTE_SUCCESS=sslc.success SSLC_ROUTE_FAILURE=sslc.failure SSLC_ROUTE_CANCEL=sslc.cancel SSLC_ROUTE_IPN=sslc.ipn
| Key | Default | Description |
|---|---|---|
| sandbox | true | Enable sandbox mode for testing |
| store.id | — | SSLCommerz store ID |
| store.password | — | SSLCommerz store password |
| store.currency | BDT | Transaction currency |
| product_profile | general | Product profile type |
Available product profiles
general— digital goods, subscriptions (default)physical-goodsnon-physical-goodsairline-ticketstravel-verticaltelecom-vertical
Routes
You need two route groups: one for the initiation entry point (GET), and one for SSLCommerz callbacks (POST). The callback routes must exclude CSRF verification because SSLCommerz posts to them from its servers.
use App\Http\Controllers\SSLCommerzController; use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken; // Entry point: user clicks "Pay" on your checkout page Route::get('sslcommerz/initiate/{order}', [SSLCommerzController::class, 'initiate']) ->name('sslc.initiate'); // Callbacks: SSLCommerz posts back to these after payment Route::controller(SSLCommerzController::class) ->prefix('sslcommerz') ->name('sslc.') ->withoutMiddleware([VerifyCsrfToken::class]) ->group(function () { Route::post('success', 'success')->name('success'); Route::post('failure', 'failure')->name('failure'); Route::post('cancel', 'cancel')->name('cancel'); Route::post('ipn', 'ipn')->name('ipn'); });
CSRF exemption is required. SSLCommerz sends POST requests from its own servers to your callback URLs. Without withoutMiddleware([VerifyCsrfToken::class]), Laravel will reject them with a 419 error.
Auto-generated callback URLs: If you set SSLC_ROUTE_SUCCESS etc. in your .env, the package automatically generates the full callback URLs from your route names. You don't need to call setCallbackUrls() manually.
Payment Flow
Here's what happens when a customer clicks "Pay":
In parallel, SSLCommerz also sends an IPN (Instant Payment Notification) directly to your server. Always handle IPN to catch payments that complete after the user closes their browser.
Complete Controller
A production-ready controller covering the full payment lifecycle. Adapt the Order model references to match your application.
Initiate a payment
Store order context in the session before redirecting. You'll retrieve it in the success callback to create your database records.
use HasinHayder\Sslcommerz\Facades\Sslcommerz; use Illuminate\Support\Facades\Log; use Illuminate\Support\Str; public function initiate(Request $request, Order $order) { $user = auth()->user(); $invoiceId = 'INV-' . strtoupper(Str::random(10)) . '-' . $order->id; $amount = $order->total; // Store context for the success callback session([ 'sslc_order_id' => $order->id, 'sslc_user_id' => $user->id, 'sslc_invoice' => $invoiceId, ]); try { $response = Sslcommerz::setOrder($amount, $invoiceId, $order->product_name) ->setCustomer($user->name, $user->email) ->setShippingInfo(1, 'Dhaka, Bangladesh') ->makePayment(); if ($response->success()) { return redirect($response->gatewayPageURL()); } Log::error('SSLCommerz initiation failed', [ 'status' => $response->status(), 'order_id' => $order->id, ]); return back()->withErrors(['Payment initiation failed. Please try again.']); } catch (\Exception $e) { Log::error('SSLCommerz exception', ['error' => $e->getMessage()]); return back()->withErrors(['Connection error. Please try again.']); } }
Handle success callback
Always verify the hash first, then validate the payment. Retrieve the order context from session and guard against duplicate processing.
public function success(Request $request) { // 1. Verify hash — prevents tampered data if (! Sslcommerz::verifyHash($request->all())) { Log::warning('Hash verification failed', $request->all()); return redirect()->route('home') ->withErrors(['Payment verification failed.']); } $transactionId = $request->input('tran_id'); $amount = $request->input('amount'); // 2. Validate payment with SSLCommerz if (! Sslcommerz::validatePayment($request->all(), $transactionId, $amount)) { Log::warning('Payment validation failed', ['tran_id' => $transactionId]); return redirect()->route('home') ->withErrors(['Payment could not be validated.']); } // 3. Retrieve order from session $orderId = session('sslc_order_id'); $userId = session('sslc_user_id'); if (! $orderId || ! $userId) { return redirect()->route('home') ->withErrors(['Session expired. Contact support with ID: ' . $transactionId]); } $order = Order::find($orderId); // 4. Prevent duplicate processing if (! $order->isPaid()) { $order->update([ 'status' => 'paid', 'transaction_id' => $transactionId, 'payment_gateway' => 'sslcommerz', 'paid_at' => now(), ]); } // 5. Clean up session session()->forget(['sslc_order_id', 'sslc_user_id', 'sslc_invoice']); return redirect()->route('orders.show', $order) ->with('success', 'Payment successful!'); }
Handle failure, cancel, and IPN
public function failure(Request $request) { Log::warning('SSLCommerz payment failed', $request->all()); session()->forget(['sslc_order_id', 'sslc_user_id', 'sslc_invoice']); return redirect()->route('home') ->withErrors(['Payment could not be processed.']); } public function cancel(Request $request) { Log::info('SSLCommerz payment cancelled', $request->all()); session()->forget(['sslc_order_id', 'sslc_user_id', 'sslc_invoice']); return redirect()->route('home') ->with('info', 'Payment was cancelled.'); } /** * IPN — server-to-server notification from SSLCommerz. * Do NOT redirect. Always return JSON. */ public function ipn(Request $request) { if (! Sslcommerz::verifyHash($request->all())) { return response()->json(['error' => 'Hash verification failed'], 400); } $transactionId = $request->input('tran_id'); $status = $request->input('status'); if ($status === 'VALID' || $status === 'VALIDATED') { $order = Order::where('transaction_id', $transactionId)->first(); if ($order && ! $order->isPaid()) { $order->update(['status' => 'paid', 'paid_at' => now()]); } } return response()->json(['received' => true], 200); }
Session context is key. Store order details in the session before redirecting to SSLCommerz. Retrieve them in the success callback to create your database records. Always forget session keys after processing to prevent stale data.
IPN is server-to-server. Never redirect from an IPN handler. Always return a JSON response. SSLCommerz sends IPNs independently of the user's browser flow — use it as a safety net for payments that complete after the user navigates away.
Duplicate prevention. SSLCommerz may call your success URL multiple times for the same transaction. Always check if the order is already marked as paid before creating records.
Refunds
Initiate a refund
Sslcommerz::refundPayment( string $bankTransactionId, int|float $amount, string $reason ): RefundResponse
$refund = Sslcommerz::refundPayment( $order->bank_tran_id, $order->total, 'Customer requested refund' ); if ($refund->success()) { $order->update(['refund_ref_id' => $refund->refundRefId()]); } elseif ($refund->processing()) { // Refund is being processed by the bank } elseif ($refund->failed()) { Log::error($refund->failedReason()); }
Check refund status
Sslcommerz::checkRefundStatus(string $refundRefId): RefundStatus
$status = Sslcommerz::checkRefundStatus($order->refund_ref_id); if ($status->refunded()) { $order->update(['status' => 'refunded', 'refunded_at' => $status->refundedAt()]); } elseif ($status->cancelled()) { $order->update(['status' => 'refund_failed']); } elseif ($status->processing()) { // Still being processed by the bank }
API Reference
Minimal payment
Only setOrder and setCustomer are required. Everything else is optional.
$response = Sslcommerz::setOrder($amount, $invoiceId, 'Premium Plan') ->setCustomer($user->name, $user->email) ->makePayment(); if ($response->success()) { return redirect($response->gatewayPageURL()); } // Handle failure Log::error($response->failedReason());
Full chain with all options
Every method returns SslcommerzClient for chaining. Add shipping, gateway restrictions, custom fields, or override the callback URLs.
$response = Sslcommerz::setOrder($amount, $invoiceId, 'Wireless Headphones', 'Electronics') ->setCustomer( $user->name, $user->email, '01700000000', '123 Mirpur Road', 'Dhaka', 'Dhaka', '1216' ) ->setShippingInfo(2, '456 Gulshan Ave', $user->name, 'Dhaka') ->setGateways(['visa', 'mastercard', 'bkash']) ->setProductProfile('physical-goods') ->makePayment([ 'value_a' => $order->id, // passes through to SSLCommerz 'value_b' => 'customer_ref_' . $user->id, ]);
Custom fields via makePayment(): The array passed to makePayment() is merged into the SSLCommerz request payload. Use value_a through value_f to pass your own identifiers — they come back unchanged in the callback and IPN, making reconciliation easy.
Method signatures
Sslcommerz::setOrder( int|float $amount, string $invoiceId, string $productName, string $productCategory = ' ' ): SslcommerzClient Sslcommerz::setCustomer( string $name, string $email, string $phone = ' ', string $address = ' ', string $city = ' ', string $state = ' ', string $postal = ' ', string $country = 'Bangladesh', ?string $fax = null ): SslcommerzClient Sslcommerz::setShippingInfo( int $quantity, string $address, ?string $name = null, ?string $city = null, ?string $state = null, ?string $postal = null, ?string $country = null ): SslcommerzClient
Optional chainable methods
| Method | Description |
|---|---|
| setCallbackUrls($success, $failure, $cancel, $ipn) | Override auto-generated callback URLs |
| setGateways(array $gateways) | Restrict to specific gateways, e.g. ['visa', 'mastercard'] |
| setProductProfile(string $profile) | Set product profile type |
Validation and verification
makePayment(array $additionalData = []): PaymentResponse validatePayment( array $payload, string $transactionId, int|float $amount, string $currency = 'BDT' ): bool verifyHash(array $data): bool
Response Objects
The package returns typed response objects instead of raw arrays. Each object provides semantic methods for inspecting the result.
PaymentResponse
| Method | Return | Description |
|---|---|---|
| success() | bool | Payment was initiated successfully |
| failed() | bool | Payment initiation failed |
| failedReason() | string|null | Failure reason message |
| status() | string|null | Raw status value from gateway |
| sessionKey() | string|null | Payment session key |
| gatewayPageURL() | string|null | Redirect customer to this URL |
| redirectGatewayURL() | string|null | Alternative redirect URL |
| gatewayList() | array|null | Available payment gateways |
| toArray() | array|null | Raw response data |
RefundResponse
| Method | Return | Description |
|---|---|---|
| success() | bool | Refund initiated successfully |
| processing() | bool | Refund is being processed |
| failed() | bool | Refund failed |
| failedReason() | string|null | Failure reason |
| bankTranId() | string|null | Bank transaction ID |
| transId() | string|null | SSLCommerz transaction ID |
| refundRefId() | string|null | Use this to check refund status later |
| toArray() | array|null | Raw response data |
RefundStatus
| Method | Return | Description |
|---|---|---|
| refunded() | bool | Refund completed |
| processing() | bool | Refund in progress |
| cancelled() | bool | Refund was cancelled |
| reason() | string|null | Cancellation or failure reason |
| initiatedAt() | string|null | Refund initiation datetime |
| refundedAt() | string|null | Refund completion datetime |
| toArray() | array|null | Raw response data |