snowcap/ogone-bundle
Snowcap Ogone Bundle integrates the Ogone (Ingenico) payment gateway into Symfony applications, handling payment requests, redirects, and transaction feedback with configurable credentials so you can accept online payments quickly.
Minimal wiring for a Symfony 2.x app (the only Symfony version this bundle supports):
composer require "snowcap/ogone-bundle:~1.0"
app/AppKernel.php:
new Snowcap\OgoneBundle\SnowcapOgoneBundle(),
DependencyInjection/Configuration.php for the exact key names in your checkout — they vary between 1.x tags):
# app/config/config.yml
snowcap_ogone:
pspid: "%ogone_pspid%"
sha_in: "%ogone_sha_in%" # SHA-IN passphrase from Ogone back-office
sha_out: "%ogone_sha_out%" # SHA-OUT passphrase
mode: test # test | prod
Put the credentials in parameters.yml / environment-specific config and keep mode: test locally.Where to look first, in this order:
DependencyInjection/Configuration.php — the authoritative list of config keys.Resources/config/services.xml — the service IDs you'll inject (typically a snowcap_ogone.payment-style service and possibly an event dispatcher hook).Controller/ — the redirect action and the feedback (callback) action.First use case: a single one-off payment. From a checkout controller, build a payment request (amount, currency, order reference, accept/decline/cancel/exception URLs), generate the SHASIGN, and render a form that auto-posts to Ogone's hosted payment page. Then implement a separate, unauthenticated feedback route that validates the SHA-OUT signature and marks the order paid.
Hosted-page redirect (the normal flow). Rather than crafting the form by hand, let the bundle's service produce the parameters/URL. The shape is always the same: assemble an associative array of Ogone fields, sort it by key, append the SHA-IN passphrase, hash (SHA-1 in old accounts, SHA-256 in newer ones), and render:
$params = [
'PSPID' => $pspid,
'ORDERID' => $order->getReference(),
'AMOUNT' => (int) round($order->getTotal() * 100), // minor units
'CURRENCY' => 'EUR',
'LANGUAGE' => 'en_US',
'ACCEPTURL' => $this->generateUrl('ogone_accept', [], true),
'DECLINEURL' => $this->generateUrl('ogone_decline', [], true),
'EXCEPTIONURL' => $this->generateUrl('ogone_exception', [], true),
'CANCELURL' => $this->generateUrl('ogone_cancel', [], true),
];
The bundle exposes this through a service; inject it into your checkout controller and let it compute SHASIGN and hand back the parameter set rather than reimplementing the hash yourself.
Decouple order handling from the bundle using events. This is the pattern day-to-day usage should follow: keep your Order/Payment domain out of the bundle entirely. On the feedback route, after SHA-OUT validation succeeds, dispatch your own domain event (PaymentReceived, PaymentFailed) or, if the bundle defines one, subscribe to its feedback event. Your listener updates the order aggregate, writes a Payment row with the PAYID, STATUS, and raw payload, and sends confirmation mail.
Two distinct endpoints, two distinct jobs.
ACCEPTURL, DECLINEURL, …) are for the user's browser — show a receipt page, but never mark an order paid based on them (a user can hit them directly).Status-code dispatch. Route on Ogone's STATUS field rather than on the URL you were called back on:
switch ((int) $request->get('STATUS')) {
case 5: // authorised
case 9: // payment requested / captured
$this->markPaid($payId); break;
case 1: // cancelled by customer
case 2: // declined
$this->markFailed($payId); break;
default: // 51, 91 … treat as pending, reconcile later
$this->markPending($payId); break;
}
Idempotency. Ogone retries feedback. Key your processing on PAYID/ORDERID + status and short-circuit if you've already recorded that transition. Use a DB transaction or unique constraint so a retried callback can't double-credit an order.
Environment switching. Keep mode: test for dev/test and mode: prod for production in per-environment config files. Never rely on a single global config, and never commit the prod PSPID/passphrases.
Alias / token payments (if your account supports it). For repeat customers the pattern is: include ALIAS on the initial payment (ALIASUSAGE), then on subsequent charges use the bundle's DirectLink-style call with the stored alias rather than re-running the hosted page. Treat stored aliases as sensitive — they live in your DB, not in Ogone's.
SHASIGN, sorted by key (ASCII, case-sensitive), joined as KEY=value pairs, with the passphrase appended at the end for SHA-1-in / SHA-OUT (older accounts). Whether values are URL-encoded before hashing is the classic ambiguity — the spec says hash the raw values, then URL-encode the whole form afterwards. If signatures fail, log the exact pre-hash string and compare it byte-for-byte against what Ogone sees.AMOUNT is an integer in cents; a float or a decimal point silently breaks the payment. Round, don't truncate.EUR), others numeric (978). This is an account-level setting on Ogone's side, not a bundle bug — if Ogone rejects currency, this is why.PARAM= changes the hash. The bundle's builder should handle this; verify it does.ACCEPTURL et al. Use generateUrl(..., [], true) / Request::getSchemeAndHttpHost(); Symfony 2's relative URLs fail silently.POST from Ogone's IP range, and don't rely on session state — the callback has no session.STATUS alone for reconciliation. Statuses like 51 (authorisation waiting) and 91 (authorisation unknown) are pending, not success — schedule a reconciliation job that re-queries the order rather than marking paid on the first callback.%parameters% and constructor injection without autowiring; modern Symfony (5/6/7) will not boot this bundle. If you're on a current stack, treat the bundle as a reference implementation and either fork it or integrate against Worldline/Ogone's HTTP API directly.$_POST/request body on the feedback route and keep Ogone's transaction log open side-by-side. Compare the SHASIGN you received with one you compute; a mismatch pinpoints a missing/renamed field instantly.CN, PAYID, NCERROR, NCSTATUS, NCERRORPLUS — NCERRORPLUS is where the human-readable failure reason lives, and it's the first place to look when a decline is mysterious.composer.json, vendor the code (or a fork) if Composer resolution is unreliable, and budget for eventually replacing it with Worldline's maintained SDK or a thin direct-HTTP client. Extension points in a fork: the signature builder (override/inject), the feedback controller (decorate it to add your event dispatch), and the service definitions (replace the payment service with your own implementation).How can I help you explore Laravel packages today?