payment/saferpay-bundle
A Symfony bundle for integrating the Saferpay payment gateway into PHP applications. It streamlines configuration and payment processing, helping you accept secure online card payments with less boilerplate.
Treat this as a protocol reference, not an installable dependency. The package is a Symfony bundle (payment/saferpay-bundle) with a DI extension, Resources/config/ service definitions, and the older Payment/JMS payment abstractions. There is no Laravel service provider, no facade, no publishable config, and no queue/event hooks — so composer require-ing it into a Laravel app just gets you a dormant bundle plus its transitive baggage.
Minimal first steps:
git clone https://github.com/payment-legacy/SaferpayBundle into a scratch directory and read it. Confirm a LICENSE exists before copying anything.composer.json first. Note the PHP constraint, Guzzle version, and whether it pulls in payment/… abstractions you do not want. If it targets PHP 5.6/7.x-era deps, expect mcrypt, each(), or create_function style artifacts — a hard stop on PHP 8.2/8.3 without patches.DependencyInjection/ + Resources/config/services.* — reveals the credential keys (account ID, terminal ID, customer/signature ID, environment) and which endpoint URLs are baked in.Extract, don't bridge. Bootstrapping Symfony's DI container alongside Laravel's to reuse ~a dozen request builders is a bad trade. Lift the payload shapes into app-owned DTOs and reimplement the transport with Illuminate\Support\Facades\Http. Define your own SaferpayGateway implementing an application-owned contract:
interface PaymentGateway {
public function authorize(AuthorizeRequest $r): PaymentResult;
public function capture(string $transactionId, int $minorAmount): PaymentResult;
public function refund(string $transactionId, int $minorAmount): PaymentResult;
public function status(string $transactionId): PaymentStatus;
public function tokenize(TokenizeRequest $r): StoredInstrument;
}
Everything downstream depends on the interface, never on Saferpay types — that is what makes the next provider swap bounded.
Config goes in Laravel config, not the bundle's. config/services.php → saferpay.customer_id, terminal_id, username, password, base_url, all from env(). No committed credentials, no parameters.yml-style files.
Strangler migration when replacing an existing integration:
PaymentGateway contract and point current call sites at it.Never trust the redirect alone. Two paths into the same state machine:
/checkout/return → dispatch a queued ConfirmSaferpayPayment job.Idempotency by construction. Generate your own order_reference per attempt, put a unique index on it, and reuse it on retry. Guard capture/refund with a state-transition check (authorized → captured only) so a double-submitted form or a queue retry cannot double-charge or double-refund.
Amounts are integer minor units, everywhere. Currency + int cents through DTOs, database, and API payload. A float anywhere in the amount path is a defect.
Queue the slow parts. No synchronous Saferpay calls in the request cycle beyond the initial Initialize. Status polling, notification processing, and reconciliation run as jobs with backoff and a circuit breaker around the provider.
Scheduled reconciliation command. Nightly php artisan saferpay:reconcile --since=yesterday comparing your transactions table against Saferpay's transaction API, keyed by transactionId/orderReference, diffed against finance settlement reports. This is the control that catches lost callbacks and rounding mismatches.
Testing from recorded fixtures. Take the sandbox request/response pairs from your spike and commit them as JSON fixtures; drive Http::fake([...]) with them so contract tests cover every lifecycle state (authorized, captured, failed, expired, refunded, partially refunded) without hitting the network.
config/ publish. Any guide implying composer require + php artisan is wrong.Authentication challenge flow. That passes in sandbox and fails on live EU/EEA card traffic — test a challenge-required card, not just a frictionless one.password/customer_id before logging any request or response — you will need those structured logs during disputes.mcrypt/each()/create_function, timezone handling on expiry timestamps.LICENSE file means no reuse — read it before lifting code, and check transitive deps aren't themselves abandoned.PaymentGateway contract and a SaferpayClient behind it. Budget ~10–15% of one engineer in steady state for owning that fork, plus a re-review whenever Worldline announces an API deprecation.transactionId/orderReference. Without both, disputes become archaeology.How can I help you explore Laravel packages today?