S SSLCommerz Laravel

01Overview

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.

02Installation

terminal
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.

03Configuration

.env
# 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
KeyDefaultDescription
sandboxtrueEnable sandbox mode for testing
store.idSSLCommerz store ID
store.passwordSSLCommerz store password
store.currencyBDTTransaction currency
product_profilegeneralProduct profile type

Available product profiles

  • general — digital goods, subscriptions (default)
  • physical-goods
  • non-physical-goods
  • airline-tickets
  • travel-vertical
  • telecom-vertical

04Routes

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.

routes/web.php
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.

05Payment Flow

Here's what happens when a customer clicks "Pay":

Customer clicks Pay
Your Controller
Your Controller
SSLCommerz Gateway
SSLCommerz Gateway
Customer pays
Customer pays
POST /sslcommerz/success
POST /sslcommerz/success
verifyHash() + validatePayment()
verifyHash() + validatePayment()
Activate Order

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.

06Complete 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.

SSLCommerzController.php — initiate()
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.

SSLCommerzController.php — success()
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

SSLCommerzController.php — failure(), cancel(), 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.


07Refunds

Initiate a refund

Sslcommerz::refundPayment(
    string $bankTransactionId,
    int|float $amount,
    string $reason
): RefundResponse
Refund example
$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 check
$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
}

08API Reference

Minimal payment

Only setOrder and setCustomer are required. Everything else is optional.

Minimal example
$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.

Full example
$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

MethodDescription
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

09Response Objects

The package returns typed response objects instead of raw arrays. Each object provides semantic methods for inspecting the result.

PaymentResponse

MethodReturnDescription
success()boolPayment was initiated successfully
failed()boolPayment initiation failed
failedReason()string|nullFailure reason message
status()string|nullRaw status value from gateway
sessionKey()string|nullPayment session key
gatewayPageURL()string|nullRedirect customer to this URL
redirectGatewayURL()string|nullAlternative redirect URL
gatewayList()array|nullAvailable payment gateways
toArray()array|nullRaw response data

RefundResponse

MethodReturnDescription
success()boolRefund initiated successfully
processing()boolRefund is being processed
failed()boolRefund failed
failedReason()string|nullFailure reason
bankTranId()string|nullBank transaction ID
transId()string|nullSSLCommerz transaction ID
refundRefId()string|nullUse this to check refund status later
toArray()array|nullRaw response data

RefundStatus

MethodReturnDescription
refunded()boolRefund completed
processing()boolRefund in progress
cancelled()boolRefund was cancelled
reason()string|nullCancellation or failure reason
initiatedAt()string|nullRefund initiation datetime
refundedAt()string|nullRefund completion datetime
toArray()array|nullRaw response data