Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
96.23% covered (success)
96.23%
51 / 53
71.43% covered (warning)
71.43%
5 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
SslCommerzDriver
96.23% covered (success)
96.23%
51 / 53
71.43% covered (warning)
71.43%
5 / 7
17
0.00% covered (danger)
0.00%
0 / 1
 key
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 label
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 configSchema
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 charge
96.55% covered (success)
96.55%
28 / 29
0.00% covered (danger)
0.00%
0 / 1
8
 verify
92.86% covered (success)
92.86%
13 / 14
0.00% covered (danger)
0.00%
0 / 1
3.00
 isImplemented
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 endpoint
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3namespace App\Services\Payment\Drivers;
4
5use App\Models\Order;
6use App\Services\Payment\PaymentResult;
7use Illuminate\Http\Request;
8use Illuminate\Support\Facades\Http;
9
10/**
11 * SSLCommerz — the dominant Bangladeshi payment aggregator; one integration
12 * covers bKash, Nagad, Rocket, Upay and cards through their own hosted
13 * checkout page, without this app touching each wallet's API directly.
14 * Session API v4 (https://developer.sslcommerz.com/doc/v4/) — plain REST,
15 * no SDK needed. Implemented per the public API docs; needs the operator's
16 * real Store ID/Password (sandbox or live) to actually process a payment.
17 */
18class SslCommerzDriver extends AbstractDriver
19{
20    public function key(): string
21    {
22        return 'sslcommerz';
23    }
24
25    public function label(): string
26    {
27        return 'SSLCommerz (bKash, Nagad, Rocket, cards & more)';
28    }
29
30    public function configSchema(): array
31    {
32        return [
33            'store_id' => ['type' => 'text', 'label' => 'Store ID', 'required' => true],
34            'store_password' => ['type' => 'password', 'label' => 'Store Password', 'required' => true],
35            'sandbox' => ['type' => 'toggle', 'label' => 'Sandbox mode', 'default' => true],
36        ];
37    }
38
39    public function charge(Order $order, array $config): PaymentResult
40    {
41        if (empty($config['store_id']) || empty($config['store_password'])) {
42            return PaymentResult::failed('SSLCommerz is not configured — add the Store ID and Password in Settings → Payment Gateways.');
43        }
44
45        $customer = $order->customer;
46
47        $response = Http::asForm()->post($this->endpoint($config, 'gwprocess/v4/api.php'), [
48            'store_id' => $config['store_id'],
49            'store_passwd' => $config['store_password'],
50            'total_amount' => number_format((float) $order->due, 2, '.', ''),
51            'currency' => 'BDT',
52            'tran_id' => (string) $order->bar_code,
53            'success_url' => route('payment.callback', ['gateway' => 'sslcommerz', 'result' => 'success', 'order' => $order->bar_code]),
54            'fail_url' => route('payment.callback', ['gateway' => 'sslcommerz', 'result' => 'fail', 'order' => $order->bar_code]),
55            'cancel_url' => route('payment.callback', ['gateway' => 'sslcommerz', 'result' => 'cancel', 'order' => $order->bar_code]),
56            'ipn_url' => route('payment.webhook', ['gateway' => 'sslcommerz', 'order' => $order->bar_code]),
57            'cus_name' => $customer->name ?? 'Customer',
58            'cus_email' => $customer->email ?: 'customer@example.com',
59            'cus_add1' => $customer->address ?: 'N/A',
60            'cus_city' => 'N/A',
61            'cus_country' => 'Bangladesh',
62            'cus_phone' => $customer->phone ?: 'N/A',
63            'shipping_method' => 'NO',
64            'product_name' => 'Order ' . $order->bar_code,
65            'product_category' => 'General',
66            'product_profile' => 'general',
67            'num_of_item' => 1,
68        ]);
69
70        $data = $response->json() ?? [];
71
72        if (($data['status'] ?? null) !== 'SUCCESS' || empty($data['GatewayPageURL'])) {
73            return PaymentResult::failed($data['failedreason'] ?? 'Could not start the SSLCommerz payment session.', $data);
74        }
75
76        return PaymentResult::redirect($data['GatewayPageURL'], (string) $order->bar_code);
77    }
78
79    public function verify(Request $request, array $config): PaymentResult
80    {
81        $valId = $request->input('val_id');
82
83        if (! $valId) {
84            return PaymentResult::failed('Missing validation id from SSLCommerz.');
85        }
86
87        $response = Http::get($this->endpoint($config, 'validator/api/validationserverAPI.php'), [
88            'val_id' => $valId,
89            'store_id' => $config['store_id'] ?? '',
90            'store_passwd' => $config['store_password'] ?? '',
91            'format' => 'json',
92        ]);
93
94        $data = $response->json() ?? [];
95        $status = $data['status'] ?? '';
96
97        if (in_array($status, ['VALID', 'VALIDATED'], true)) {
98            return PaymentResult::paid((string) ($data['tran_id'] ?? ''), $data);
99        }
100
101        return PaymentResult::failed('SSLCommerz could not validate this transaction.', $data);
102    }
103
104    public function isImplemented(): bool
105    {
106        return true;
107    }
108
109    private function endpoint(array $config, string $path): string
110    {
111        $host = (bool) ($config['sandbox'] ?? true) ? 'sandbox.sslcommerz.com' : 'securepay.sslcommerz.com';
112
113        return "https://{$host}/{$path}";
114    }
115}