ets/payment-ogone-bundle
Ogone payment provider for JMSPaymentCoreBundle, letting Symfony apps process payments through the Ogone gateway via JMSPaymentCore. Install and usage docs included.
First, the reality check: this is a Symfony bundle, not a Laravel package. It extends JMSPaymentCoreBundle and wires itself through Symfony's DI container, PaymentInstruction/Plugin abstractions, and event dispatchers. You cannot composer require it into a Laravel app and expect it to boot. What you can do is use it as a reference implementation — or, in a hybrid shop, pull in jms/payment-core-bundle plus this provider and drive them from a thin Laravel bridge.
Minimal path for a Laravel dev:
Resources/doc/install.rst and Resources/doc/usage.rst on GitHub — they're short and are the only real docs.DependencyInjection/Configuration.php to see the exact config keys (PSPID, API user/password, SHA passphrase, mode).OgoneDirectLinkPlugin for server-to-server, the e-commerce/redirect plugin for hosted-page checkout) — this is where the signing logic lives.First real use case: a redirect checkout. Build an order/payment instruction, hand it to the provider plugin, get back the Ogone orderstandard.asp form/redirect URL with signed parameters, send the customer there, then handle Ogone's POST-back (feedback) on a webhook route.
Where to look first: Resources/doc/usage.rst → the plugin class → the signature helper. Everything else is Symfony plumbing you'll throw away.
Wrap the bundle's request-building and signature logic in a single injectable service so controllers stay dumb:
final class OgoneGateway
{
public function __construct(private array $config) {}
public function redirectUrl(Order $order): string
{
$params = [
'PSPID' => $this->config['pspid'],
'ORDERID' => $order->reference,
'AMOUNT' => (int) round($order->total * 100), // minor units
'CURRENCY' => '978', // EUR, ISO 4217 numeric
'LANGUAGE' => 'en_US',
'ACCEPTURL' => route('ogone.accept'),
'DECLINEURL' => route('ogone.decline'),
'EXCEPTIONURL'=> route('ogone.exception'),
'CANCELURL' => route('ogone.cancel'),
];
$params['SHASIGN'] = $this->sign($params);
return $this->config['endpoint'].'?'.http_build_query($params);
}
}
Ogone POSTs back to your ACCEPTURL. Treat that POST as untrusted until SHASIGN verifies (with the feedback passphrase, which is often different from the SHA-IN passphrase — check your Ogone account).
Route::post('/ogone/feedback', function (Request $request, OgoneGateway $gw) {
abort_unless($gw->verify($request->post()), 403);
$order = Order::where('reference', $request->input('orderID'))->firstOrFail();
if ($request->input('STATUS') === '5') { // 5 = payment accepted
$order->markPaid($request->input('PAYID'));
}
return response('OK');
})->withoutMiddleware(['csrf']); // Ogone won't send your token
If you're keeping the Symfony stack around:
PaymentInstruction → your basket/order snapshot + amount/currency.Plugin::approve($instruction) → performs the Ogone call (DirectLink) or produces the redirect.Plugin::complete($instruction) on feedback → marks the instruction complete and raises the bundle's events.payment.success without importing Symfony's dispatcher.OGONE_PSPID, OGONE_SHA_IN, OGONE_SHA_OUT, OGONE_MODE=test|prod. Test endpoints are secure.ogone.com/ncol/test/....PAYID/ORDERID so repeated feedback POSTs don't double-fulfil.The bundle will not install cleanly in Laravel. It requires Symfony's HTTP kernel, DI, and jms/payment-core-bundle. Attempting to load it via a Laravel service provider will fail on missing Symfony services. Extract, don't integrate.
Signature ordering rules are unforgiving. Ogone's SHASIGN is computed over parameters sorted alphabetically by key, case-insensitively, joined as KEY=VALUE pairs — excluding empty values and never including SHASIGN itself. The bundle's helper does this correctly; copy it rather than reinventing if you port. Watch the case: ACCEPTURL vs accepturl has bitten plenty of people.
SHA-1 vs SHA-256. Ogone accounts can be configured for either. The digest algorithm must match the account setting or every payment fails signature validation with no useful error message. This is the single most common "it works in test but not prod" cause.
Amounts are in minor units and currency is numeric. AMOUNT is cents (19.99 → 1999) and CURRENCY is ISO-4217 numeric (978 for EUR), not EUR. Passing the wrong one gives a generic error page at Ogone, not an API error.
Passphrases are per-account and per-configuration. SHA-IN (request signing) and SHA-OUT (response signing) are distinct, and the "feedback" passphrase may differ from the e-commerce one. If verification fails in staging, check which passphrase type the account uses.
Webhooks need CSRF exemption and a 200 response body. Ogone expects a plain OK/200; anything else triggers retries. Always verify the signature before touching the database, but persist the raw payload even for invalid ones so you can debug.
Old Symfony baggage. Last release is 2025, but the codebase carries legacy Symfony patterns (annotations, older component APIs). If you're reading it for reference, prefer the newer branches/tags and cross-check against the Symfony version constraints in composer.json rather than assuming modern style.
Status codes matter more than the redirect. Customers will hit ACCEPTURL even for declined payments in some configurations. Always branch on STATUS (5 = accepted, 9 = cancelled, 2 = declined-ish) rather than trusting which URL Ogone redirected to.
Only 12 stars and zero dependents means thin community support. There are no Stack Overflow answers waiting for you; expect to read the plugin source, and keep your own tests around the signature and feedback parsing — they're the parts that break silently.
Extension points you can still learn from: the bundle separates "plugin" (payment flow) from "signature/parameter" helpers and config. Mirror that split in your Laravel port — a OgoneSigner, an OgoneGateway, and a OgoneFeedbackHandler — and you'll be able to swap Ogone for Stripe/Adyen later without touching controllers.
How can I help you explore Laravel packages today?