sirumobile/siru-php-sdk
A PHP SDK for integrating Siru Mobile payments into your app, offering a simple client for the Siru Mobile API so you can initiate and manage mobile and carrier-billing payments from Laravel or plain PHP projects.
Install and inspect before wiring anything up. The package is a thin, low-traffic wrapper (0 stars, no dependents, last release Dec 2021), so budget 15 minutes to read the source before you commit to it:
composer require sirumobile/siru-php-sdk
Then look at, in order:
composer.json — check the PHP constraint and the guzzlehttp/guzzle / psr/* versions it pins. This is the single most likely reason installation fails on a modern Laravel app (Laravel 10/11 ship Guzzle 7.x; a 2021 SDK may pin ^6.0).src/ — find the main client class and the request/response value objects. Note whether the client takes config in the constructor or via setters.README.md — for the endpoint hostnames (sandbox vs. production) and the signing scheme.There is almost certainly no Laravel service provider. Don't expect auto-discovery, a publishable config, or a facade. Register it yourself in AppServiceProvider:
// app/Providers/AppServiceProvider.php
public function register(): void
{
$this->app->singleton(\Siru\Client::class, fn () => new \Siru\Client(
merchantId: config('services.siru.merchant_id'),
secret: config('services.siru.secret'),
endpoint: config('services.siru.endpoint'),
));
}
// config/services.php
'siru' => [
'merchant_id' => env('SIRU_MERCHANT_ID'),
'secret' => env('SIRU_SECRET'),
'endpoint' => env('SIRU_ENDPOINT', 'https://sandbox.siru.example/'),
],
First use case: a carrier-billing / mobile-payment checkout. Typical shape is create payment → redirect the user to the gateway URL → receive a server-to-server callback → confirm status. Start by getting the sandbox create-payment call returning a redirect URL in a Tinker session or a throwaway artisan command; that validates credentials, signing, and endpoint in one shot before you touch a controller.
Wrap it, don't sprinkle it. Because the SDK is unmaintained, put your own interface in front of it so a swap (or a fork into app/Support/Siru) is a one-file change:
interface PaymentGateway
{
public function createPayment(Order $order): PaymentRedirect;
public function fetchStatus(string $reference): PaymentStatus;
}
Bind the SDK-backed implementation in the container. Your controllers, jobs, and tests then never reference Siru\* directly.
Initiation flow. Do the create-payment call inside a DB transaction that also writes your own payments row with a generated reference — never rely on the gateway's ID as your primary key, and never trust a client-supplied amount:
public function store(CheckoutRequest $request, PaymentGateway $gateway)
{
$order = Order::create([...]); // amount in minor units, from your own cart
$redirect = $gateway->createPayment($order);
$order->payments()->create([
'reference' => $order->reference,
'status' => 'pending',
'redirect_url' => $redirect->url,
]);
return redirect()->away($redirect->url);
}
Callback controller. This is where most of the day-to-day work is. Keep it dumb: verify, persist, dispatch, return 200.
public function __invoke(Request $request, PaymentGateway $gateway)
{
abort_unless($gateway->verifySignature($request->getContent(), $request->headers->all()), 403);
$payload = $request->all();
$payment = Payment::where('reference', $payload['reference'])->firstOrFail();
// idempotency: callbacks get retried
if ($payment->status !== 'pending') {
return response('ok');
}
$payment->update(['status' => $payload['status'], 'raw' => $payload]);
PaymentReceived::dispatch($payment);
return response('ok');
}
Order state machine + events. Map gateway statuses onto your own small enum (Pending, Paid, Failed, Cancelled) and fire domain events. Listeners handle fulfilment, receipt email, and stock decrement — not the controller, and not the SDK callback path.
Reconciliation job. Callbacks get lost. Schedule an hourly job that finds pending payments older than N minutes and polls status via the SDK, then routes through the same PaymentReceived/PaymentFailed handling. Make the event handling idempotent so the poller and the webhook can race safely.
Testing. Swap the gateway binding for a fake in tests (that's the payoff of the interface). If you want to test the SDK layer itself, inject a Guzzle client with a MockHandler — the SDK almost certainly accepts a ClientInterface or a base URI, which lets you replay captured sandbox responses without network calls. Use Http::fake() only if the SDK is built on the Laravel HTTP client (it isn't; it predates it).
"sirumobile/siru-php-sdk": "*" plus a composer.json conflict/replace or a vcs fork if Guzzle constraints clash. Check for #[\ReturnTypeWillChange]-era issues and dynamic-property deprecations in PHP 8.2 (common in SDKs of that vintage).$request->getContent()), never over $request->all() — PHP's array reordering and numeric-string coercion will change the payload and produce a mismatch you'll chase for hours. Compare with hash_equals(), not ===.abort_if(app()->isProduction() && str_contains(config('services.siru.endpoint'), 'sandbox'), 500).VerifyCsrfToken (bootstrap/app.php → validateCsrfTokens(except: ['siru/*']) in Laravel 11) and, critically, exclude it from any auth middleware. Signature verification is the only authentication.decimal(10,2) column will cause off-by-100 errors. Store amount_minor as an integer and cast once at the boundary.debug. Payment payloads contain MSISDN/phone numbers — PII under GDPR. Log the reference and status only; keep the raw payload in an encrypted column if you need it for disputes.PaymentGateway implementation with retry() and a circuit breaker.src/ into app/Support/Siru behind your interface once it works. You get PHP 8 type declarations, tests, and a namespace you control — and you stop being one composer update away from a broken checkout.How can I help you explore Laravel packages today?