libre-informatique/sylius-paybox-bundle
Sylius bundle that integrates the Paybox payment gateway, letting stores offer Paybox as a checkout payment method. Handles gateway configuration and payment flow inside Sylius. Useful for French e-commerce projects that need Paybox support alongside existing Sylius payment options.
First, confirm the stack. This is a Sylius (Symfony) bundle, not a Laravel package despite the listing. If you are on Laravel, stop here and port the Paybox protocol logic (parameter construction, HMAC signing, callback verification) into your own service, or look at an Omnipay Paybox driver. Everything below assumes Sylius 1.x with Payum.
Minimal steps to first payment attempt
composer.json — declared constraints for sylius/sylius, payum/core, symfony/*, and PHP. This tells you immediately whether it will even install against your pinned versions.DependencyInjection/ and Resources/config/ — how the gateway factory is registered and which services are wired.Resources/translations/ and any form types — the admin-facing payment method configuration fields.composer require libre-informatique/sylius-paybox-bundle
Enable the bundle in config/bundles.php, then check whether the service definitions resolve (bin/console debug:container | grep -i paybox).Resources/config/services.xml is the first place to look.PBX_SITE, PBX_RANG, PBX_IDENTIFIANT, PBX_HMAC) is what the bundle will map them to.paid in the Sylius admin.Where to look first when something is wrong: Sylius's payment state machine (sylius_order_payment_state_machine), then the bundle's callback controller/action, then var/log/dev.log filtered on the Paybox order reference. The order of those three matches the order in which things usually break.
Configure payment methods in code, not the admin UI. Admin-created payment methods are clickops that diverge between staging and production. Define them in fixtures or a data migration so the merchant ID, site/rank, and callback URLs are reproducible and diffable:
// src/DataFixtures/PaymentMethodFixture.php
$paybox = $this->paymentMethodFactory->createWithGateway('paybox');
$paybox->setCode('PAYBOX');
$paybox->setName('Card (Paybox)');
$paybox->setEnabled(true);
$paybox->setGatewayConfig($this->gatewayConfigFactory->createConfig([
'payum.factory_name' => 'paybox',
'payum.factory_config' => [
'site' => '%env(PAYBOX_SITE)%',
'rank' => '%env(PAYBOX_RANK)%',
'identifier' => '%env(PAYBOX_IDENTIFIER)%',
'hmac_key' => '%env(PAYBOX_HMAC_KEY)%',
'sandbox' => '%env(bool:PAYBOX_SANDBOX)%',
],
]));
Push every credential through env vars and Symfony secrets — never commit the HMAC key.
Treat the callback as the source of truth, the return URL as UX. A pattern that holds up: the customer-facing return page reads the current payment state from Sylius and renders "thank you" / "pending" / "failed" — it does not mutate state. The server-to-server IPN handler is the only thing that transitions the order to paid, and only after HMAC verification and amount/currency re-validation against the order. If you invert this (trusting the GET return), a user who edits the query string or hits back/refresh can produce phantom paid orders.
Wrap the callback handler in your own thin controller. The bundle's callback endpoint is the highest-risk code in your application. Vendor the repo, then put your own controller in front of it that: (a) logs the raw payload with the order reference, (b) rejects anything failing signature verification with a 403 and an alert, (c) is idempotent — if the order's payment is already completed, return 200 and do nothing. This gives you an interception point for debugging and a place to add reconciliation logic without forking the internals further.
Feature-flag it as a second payment method. Run Paybox alongside your incumbent gateway for at least one full settlement cycle. Route by channel, currency, or a PAYMENTS_PAYBOX_ENABLED flag, and reconcile Paybox's back-office settlement report against Sylius payments daily. Only after a clean cycle do you flip the default.
Map gateway errors to Sylius payment states explicitly. Paybox returns response codes; don't let an unrecognized code fall through to "pending" silently. A small mapping service — authorized / declined / cancelled / technical-error → completed / failed / cancelled / processing — keeps the order state machine honest and makes the failure dashboard meaningful.
Add a reconciliation command early. A console command that pulls yesterday's Paybox settlement file and diffs it against Sylius payments in a terminal state catches the failures that webhooks never report: lost callbacks, duplicate charges, amount mismatches. This is the single highest-value piece of code you will write around this bundle.
The adoption signal is the biggest gotcha. Two stars and zero dependents means no community pressure to keep up with Sylius/Payum/PHP releases, no test suite you can trust, and a bus factor of one. Plan from day one to fork into your own namespace. Budget roughly 1–2 days per Sylius minor upgrade for re-validation.
Version drift is the first wall you'll hit. Sylius 1.12+/2.x and PHP 8.2+ may already be outside the bundle's declared constraints. Resolve this before writing a line of integration code — if composer require fails, you're forking or abandoning, and you want to know that on day one, not day five.
Verify the signature yourself, line by line. The security-critical path is HMAC generation on the outgoing request and verification on the callback. Read it, and write a negative test: forge a callback with a tampered amount and assert it is rejected. There are no tests in the repo covering this; if you don't add one, nothing does.
Re-validate the amount and currency server-side. The callback tells you what was paid. Compare it to the order total and currency before transitioning state — a bundle that trusts the posted amount is a financial-loss vector. This is a common omission in bundles of this size and is cheap to add.
Assume no SCA/3DS2 until you see it. EU 3-D Secure v2 has been mandatory since 2021. If the bundle predates it or omits the authentication-result validation, EU-issued cards will decline at scale — and quietly, only visible in your decline rate. Check whether the gateway config exposes a 3DS flag and whether the response handler parses the authentication status. If it doesn't, this is a fork-level change, not a config tweak.
Callback idempotency and ordering. Paybox (like most PSPs) can send duplicates and can deliver out of order. Two guards: check current payment state before acting (already-completed → no-op) and never move a payment backwards from a terminal state. Without these you get double stock decrements and double emails.
The return URL and the callback URL are different things. Register both with Paybox during merchant onboarding, and make sure the callback URL is publicly reachable over HTTPS with no auth wall, no locale prefix that redirects, and no trailing-slash mismatch. Callback-URL misconfiguration is the single most common cause of "orders stuck in awaiting payment."
Refunds, authorize/capture, tokenization, multi-currency: assume absent. Verify each explicitly against the code before promising it to anyone. A bundle this size typically implements authorize-and-capture-in-one and nothing else. Partial refunds and separate capture are almost always a fork.
Instrument the failure modes you can't see. Alert on orders in awaiting_payment beyond a threshold, on callback verification failures (any nonzero count is an incident), and on decline-rate jumps that suggest a 3DS regression. Reconciliation against settlement files catches the rest.
Extension points worth knowing: the Sylius payment state machine (add your own transitions/callbacks rather than editing bundle code), Payum action ordering (you can register your own action before/after the bundle's in the gateway factory), and the gateway config array (extend it rather than hardcoding parameters). Prefer all three over patching the bundle's classes — it makes the eventual rebase onto an upstream fix survivable, assuming one ever lands.
How can I help you explore Laravel packages today?