kptive/payment-sips-bundle
A Symfony bundle for integrating the SIPS (Atos Worldline) payment gateway, providing configuration and services to process payments. Check maintenance status and Symfony/PHP compatibility before adopting.
Look at the source before you look at the install docs. This is a Symfony 2 bundle built on JMSPaymentCoreBundle, not a Laravel package. There is no composer require path into a Laravel app that ends with working code — the bundle relies on Symfony bundle registration, the DI container, YAML/XML service definitions, and JMS's PaymentPluginInterface. Read it as a reference implementation of the SIPS wire protocol, then port the ~200 lines that matter into a first-party class.
Files worth reading first, in order:
SipsPlugin class (extends JMS AbstractPlugin) — see exactly what approve() / approveAndDeposit() send to the gateway.DATA= payload returned by SIPS is decoded and mapped to a status.The key architectural fact: this bundle shells out to Atos' SIPS CGI binaries (request and response executables) rather than speaking HTTP. Your port should do the same or replace it with the bank's HTTP connector — confirm which generation your contract provisions before writing anything.
First use case — a Laravel spike:
// config/sips.php
return [
'merchant_id' => env('SIPS_MERCHANT_ID'),
'pathfile' => env('SIPS_PATHFILE'),
'currency' => 'EUR',
'mode' => env('SIPS_MODE', 'TEST'),
];
// app/Payments/SipsGateway.php
class SipsGateway
{
public function authorize(Order $order): string
{
// build parm string from merchant_id, amount (minor units),
// currency, order ref; invoke the SIPS `request` binary;
// return the redirect URL / auto-submitting form HTML
}
public function handleReturn(Request $request): PaymentResult
public function handleCallback(Request $request): PaymentResult
}
Persist the gateway reference (order_id / transaction id) before redirecting the customer.
Wrap it behind your own interface, always. The day-to-day value is the protocol detail, not the class hierarchy. Define PaymentGateway in your app with authorize(), handleReturn(), handleCallback(), so the SIPS implementation is swappable the day Worldline migrates you.
Two distinct flows — handle them separately:
| Flow | Trigger | Trust level | What to do |
|---|---|---|---|
| Return | Customer's browser lands back on your normal_return_url |
Untrusted | Display result UI; do not mark the order paid from here alone |
| Callback | SIPS server posts to your callback URL | Verify signature, then trust | Update order state, queue downstream work, ACK fast |
The bundle supports both; a common port mistake is only wiring up the return URL and letting the callback rot.
Recommended Laravel package shape:
// Service provider binds config + gateway
$this->app->singleton(PaymentGateway::class, SipsGateway::class);
$this->mergeConfigFrom(__DIR__.'/../config/sips.php', 'sips');
// Domain events, fired from the callback handler only
PaymentSucceeded::class; // includes gateway reference + amount
PaymentFailed::class;
Callback handler — the pattern that works:
public function handleCallback(Request $request): Response
{
$data = $this->sips->decode($request->input('DATA'));
if (! $this->sips->verifySignature($data, $request->input('seal'))) {
Log::warning('sips.signature_mismatch', ['ref' => $data['order_id'] ?? null]);
return response('bad signature', 400);
}
// Idempotent on gateway reference
$txn = Transaction::firstOrCreate(
['gateway_ref' => $data['transaction_id']],
['order_id' => $data['order_id'], 'amount' => $data['amount']]
);
if ($txn->wasRecentlyCreated) {
$txn->order->markPaid();
event(new PaymentSucceeded($txn));
}
return response('OK', 200); // SIPS expects a fast 200
}
Request building — the fields that matter: merchant id, amount in the gateway's minor-unit convention, currency (ISO 4217 numeric), customer language, order reference, and the normal/error return URLs. Get these wrong and the callback will never arrive — the failure surfaces at the customer, not at request-build time.
Reconciliation cron, not optional. A scheduled job that polls unsettled transactions older than N minutes against the bank's reporting catches the "customer paid, callback lost" case. Write it before launch, not after the first incident.
The Laravel incompatibility is structural, not cosmetic. No bundle registration, no tag-based plugin discovery, no Symfony YAML service graph. If you try to composer require this into Laravel you will end up patching the bundle itself — a fork you now own with a second dead dependency (JMSPaymentCoreBundle) underneath.
Amount formatting is the #1 bug. SIPS uses minor units and decimal-comma conventions depending on locale. A €12.50 order can easily become 1250 or 12,50 or 0.125. Write a per-currency unit test asserting the exact byte string sent to the gateway.
Signature verification needs hash_equals, not ==. The bundle predates wide adoption of constant-time comparison; do not copy an == from it. Also decode the DATA= payload before comparing — a mismatch between raw vs. decoded is a classic false-failure.
Do not trust the return URL for payment confirmation. Browser refresh re-hits the return endpoint and will double-apply state if you let it. Make the return handler read-only; only the callback (or reconciliation) mutates order status.
The bank rotates pathfiles and endpoints unannounced. Your pathfile config must be reloadable without a deploy, and connection errors to the SIPS endpoint should be a distinct alert class from payment failures — otherwise an endpoint change looks like a payments outage.
Sandbox credentials are the critical path. Worldline/Atos sandbox provisioning often takes weeks and runs through the acquirer's onboarding desk, not GitHub. Start the credential request the same day you start the code spike; if the test creds don't arrive, the whole thing is blocked.
Shelling out to request/response binaries (as this bundle does) is fragile: binary path config, permission, cwd for the pathfile, and stderr swallowing all bite. Capture exit codes and stderr explicitly, and prefer the bank's HTTP connector if your contract offers one.
Log every gateway call with the reference and the raw response. When reconciliation and the bank disagree, the raw DATA blob and your outbound parm string are the only things that will resolve it.
Extend nothing in the bundle. Every feature request against this code (refunds, 3DS2, multi-currency, tokenisation) is a migration signal, not an enhancement ticket. Track it behind your PaymentGateway interface and swap providers at the boundary.
How can I help you explore Laravel packages today?