sylius/payum-bundle
Sylius PayumBundle integrates the Payum payment abstraction with Sylius, enabling configurable payment gateways, secure capture and authorization workflows, and seamless checkout payment processing for Symfony-based e-commerce stores.
First, the honest part: you cannot composer require sylius/payum-bundle into a Laravel app and get anything usable. It is a Symfony bundle with hard dependencies on Sylius\Bundle\ResourceBundle, Sylius\Bundle\CoreBundle, and the Sylius DI container. There is no service provider, no publishable config, no facade. It arrives in a project the only way it is meant to: transitively, as part of sylius/sylius. As a Laravel developer your first move is to decide which of these you are actually doing:
payum/payum core directly, or vendor SDKs / Cashier / Omnipay. Use this bundle as a reference implementation for the patterns (gateway factories, token lifecycle, encrypted credentials, payment state machine), not as a dependency.If you're in a Sylius app, look here first:
config/packages/sylius_payum.yaml — the sylius_payum.gateways tree. This is where every gateway lives.Sylius\Bundle\PayumBundle\DependencyInjection\Configuration — the authoritative schema for gateway config and GatewayFactory names.src/Sylius/Bundle/PayumBundle/Payum/Action/ — the authorize/capture/refund/status actions that drive the payment state machine.PaymentMethod admin form (PayumGatewayConfigType), which is where a gateway name gets bound to a Sylius payment method and its encrypted credentials.debug:container for payum, payum.gateway_factory, and the sylius.payment state machine.First use case to get working end to end: add the offline gateway to a payment method, run a checkout, and watch the payment transition from cart → new → processing → completed via the state machine. That proves config wiring, storage, and the state machine without touching a PSP. Then add a sandboxed Stripe or PayPal gateway and repeat.
Config-first gateway registration. Define gateways under sylius_payum.gateways.<name> with a factory and payum options. A PaymentMethod then references that gateway by name, and the gateway config (API keys, sandbox flags) is stored encrypted on the GatewayConfig entity — not in YAML. This is the day-to-day pattern: YAML declares which kind of gateway, admin/DB holds the credentials per channel.
Payment method → gateway mapping. Sylius's GatewayConfig is keyed by factory name + config, and each PaymentMethod points at one. Multi-channel and multi-store falls out naturally: different channels get different PaymentMethod records pointing at different gateway configs. If you're rebuilding this in Laravel, that is the entity model to copy — credentials as a separate encrypted record, referenced by a payment method, not inlined into an order.
Custom gateway factories for uncovered PSPs. This is the extension point you'll actually use. Implement Payum\Core\GatewayFactory (extend AbstractGatewayFactory), register it with the payum.gateway_factory tag, and define the action pipeline (CaptureAction, NotifyAction, etc.). Do not fork the bundle or patch Sylius — the read-only repo means your patch will be erased on the next composer update. Own your factory in your own bundle.
Deferred capture. Auth-then-capture is the flow this bundle was shaped around: authorize on checkout, capture from the admin or a queue worker. The pattern is a command/console invocation of $gateway->execute(new Capture($token)) driven by an order-management action. In a Laravel-first org, replicate that with a queued job and an explicit state column — do not do capture inline in a request.
Webhook / notify handling. Payum issues token-based URLs (capture, notify, status) rather than Laravel-style signed routes. The pattern is: the PSP redirects to a Payum-generated URL containing a token, the token resolves back to a stored payment, and NotifyAction advances the state. Treat these as idempotent and order-agnostic — webhooks replay and arrive out of order. Verify by replaying the same notification twice and confirming no double state transition.
Storage. Payum's StorageInterface is implemented against Sylius entities via Doctrine. If you ever port this to Eloquent, that interface is the seam: implement it over your payments and payment_tokens tables, and keep token rows append-only.
Testing. Use the gateway's sandbox credentials plus a small console command that executes a Capture/Refund on a fake token. This is the fastest way to debug a wedged payment without clicking through checkout. Sylius's sylius.behat payment contexts cover the happy path; add explicit tests for refund and partial refund — those are where state machine bugs hide.
sylius/payum-bundle independently. It's a read-only subtree split of the Sylius monorepo. Pin sylius/sylius and let this track it; independent pinning causes version drift against core and you will debug a class that isn't the one loading.gateway → factory → action → request/reply → token → storage is steep, and fewer engineers know it than know Laravel. Budget for one person to become the Payum owner; you will need them at 2 a.m.GatewayConfig unreadable and payments failing in ways that look like PSP errors. Prove rotation in staging before go-live.localhost URLs sent to the PSP — checkouts that hang at "waiting for payment" with no obvious error. Verify generated URLs from the CLI and from behind the load balancer.debug:container --tag=payum.gateway_factory confirms your factory is registered; debug:container payum confirms the gateway is built.PaymentRequest abstraction. If this is a multi-year build, write the migration into your upgrade plan now while the codebase is small, and keep your PSP-specific logic isolated in your own gateway factories so the eventual swap is a small diff.How can I help you explore Laravel packages today?