spyrit/smoney
sMoney is a Laravel package for working with monetary values and currencies, providing helpers to format, convert, and calculate money so pricing and financial data stay consistent and precise across your application.
spyrit/smoney is a thin PHP wrapper around the S-Money (S-Money by Natixis) REST API. It is a raw client, not a Laravel package — expect no service provider, no facade, and no config file. With 0 stars and 0 dependents, treat it as a starting point to vendor/fork, not as a maintained dependency.
Minimal install:
composer require spyrit/smoney
If Composer refuses to resolve it against your Laravel version, inspect the constraints before fighting them:
composer show spyrit/smoney --all
composer why-not spyrit/smoney
Typical blockers are an old PHP floor (^5.6 || ^7.0) and a Guzzle major version that conflicts with guzzlehttp/guzzle pulled in by Laravel/Socialite/etc. If so, either run it in a separate small service or fork it and bump composer.json (see Gotchas).
Where to look first, in this order:
vendor/spyrit/smoney/README.md and any examples/ directory — the official S-Money docs are the only real API reference and may no longer be online.vendor/spyrit/smoney/src/ — confirm the actual FQCN of the client, the exception classes, and how credentials are passed (constructor vs. setter).client_id / client_secret (or API key) and the sandbox account.Your first use case should be read-only: instantiate the client against sandbox, authenticate, and perform a GET — list payments, fetch a user, or read a wallet balance. Do not start with payment creation; a successful read proves credentials, base URL, and TLS are correct without moving money.
// config/smoney.php
return [
'base_uri' => env('SMONEY_BASE_URI', 'https://sandbox.s-money.fr/...'),
'client_id' => env('SMONEY_CLIENT_ID'),
'client_secret' => env('SMONEY_CLIENT_SECRET'),
'timeout' => env('SMONEY_TIMEOUT', 15),
];
Bind the client as a singleton. Since the package has no provider, write your own and keep the vendor client out of your application code:
// app/Providers/SmoneyServiceProvider.php
public function register(): void
{
$this->app->singleton(\SMoney\Client::class, fn () => new \SMoney\Client(
config('smoney.base_uri'),
config('smoney.client_id'),
config('smoney.client_secret'),
));
}
(Verify the real class name and signature in src/ — the repo is a fork of the official v1 client and the constructor may take an options array or a Guzzle client.)
Wrap it in a domain service, not in controllers. Every call site should go through one class so the eventual fork/replacement is a one-file change:
final class SmoneyGateway
{
public function __construct(private \SMoney\Client $client) {}
public function createPayment(PaymentIntent $intent): PaymentResult
{
// map your domain object to the vendor payload here
}
}
Queue anything that mutates money. Payment creation, refunds, and payouts should be jobs with retries and a stored idempotency key so a redelivered job doesn't double-charge.
Put incoming notifications behind a dedicated route. S-Money pushes payment/transfer status via HTTP callbacks. Keep the controller tiny: verify signature/secret, persist the raw payload, dispatch a job, return 200 quickly. Store the raw body — you will need it for replay when debugging.
Keep amounts as integers in minor units end-to-end. Convert at the boundary only. Floating-point euros in your DB will eventually disagree with the provider by a cent.
Test with a Guzzle mock handler, not the real API:
$mock = new \GuzzleHttp\Handler\MockHandler([new \GuzzleHttp\Psr7\Response(200, [], '{"..."}')]);
Inject the HandlerStack into the client in a test binding. If the package hides its Guzzle client, use Mockery on the wrapper instead — this is a strong argument for the SmoneyGateway seam.
composer require spyrit/smoney:1.x.y), commit composer.lock, and vendor the source or fork it into packages/ with a path repository so you can patch it when PHP/Guzzle move.try/catch, normalize into your own exception hierarchy, and always log the status code plus the provider's error code — never the client_secret.timeout and connect_timeout explicitly; the default is "wait forever", which is unacceptable on a checkout path. Retry only idempotent GETs automatically.SmoneyGateway thin so a replacement (PayPlug, Mangopay, Stripe) is a swap, not a rewrite.How can I help you explore Laravel packages today?