mpp/universign-bundle
MPP Universign Bundle integrates Universign electronic signature services into Symfony/PHP applications, providing configuration and service wiring to initiate and manage secure e-signature workflows with minimal setup.
mpp/universign-bundle is a Symfony bundle from Mon Petit Placement that wraps Universign, the French qualified-trust e-signature provider (advanced/qualified signatures, timestamping, SMS OTP signer authentication). It is almost certainly an internal extraction: one bundle, zero stars, zero dependents, and a very recent release date. Treat it as readable reference code first, dependency second.
The first thing to resolve is framework fit. It is a Symfony bundle, not a Laravel package. There is no UniversignServiceProvider, no facade, no publishable config, and the bundle's runtime contract is a Symfony DI container (DependencyInjection/Configuration.php, Resources/config/services.xml|yaml, autowired service ids). You have three realistic options:
Http::withBasicAuth(...) against the transaction endpoints). This is what most Laravel teams should do — the bundle is mostly config tree + a thin client, and you'd be re-implementing ~50 lines to avoid dragging in a container bridge.POST /signature-requests) that your Laravel app calls. Good if you want the vendor's exact request shaping.If you're on Symfony after all, or you go with option 3:
composer require mpp/universign-bundle
Then register the bundle, and read DependencyInjection/Configuration.php before the README — for bundles with thin docs, the config tree is the actual documentation: it tells you exactly which env vars exist (credentials, sandbox vs production endpoint, signature level, callback/return URLs). Expect something like:
# config/packages/mpp_universign.yaml (illustrative — confirm keys in Configuration.php)
mpp_universign:
login: '%env(UNIVERSIGN_LOGIN)%'
password: '%env(UNIVERSIGN_PASSWORD)%'
sandbox: '%env(bool:UNIVERSIGN_SANDBOX)%'
Then look at, in order: the client/API service class (the thing you'll actually inject), Resources/config/ for how it's constructed, and Tests/ for the only working usage examples you'll get.
First use case to prove out: create one signature transaction for a single PDF (a quote, a mandate, a T&Cs acceptance) with one signer, get back a signing URL, redirect the user, and handle the return. That single flow exercises credentials, document upload, signer validation, and status retrieval — i.e. every part that usually breaks.
Put an adapter in front of it, even if you like the API. Define App\Contracts\SignatureGateway (request(...): SignatureRef, status(SignatureRef): SignatureStatus) and implement it with the bundle's client. Providers like Universign get swapped, get renegotiated, or get augmented with a second provider for international signers; a two-method interface costs nothing now and saves a refactor later. It also gives you a fake for tests.
Persist, don't just redirect. The transaction id returned by the provider is your only handle on the signature. Store it immediately, before the redirect, alongside what you sent:
signature_requests: id, provider, provider_ref, status, document_hash,
signer_email, signer_phone, signature_level,
requested_at, expires_at, completed_at, raw_payload (json)
Keeping raw_payload and the document hash is cheap and is exactly what audit/compliance will ask for when someone disputes a signature six months later.
Never trust the return URL as the source of truth. Signers close tabs, mobile browsers drop the redirect, and Universign's callback can arrive before or after the user lands back on your site. Implement three layers:
signed. Payloads are hints, not facts.php artisan schedule): poll anything stuck in pending older than N minutes; this catches the callbacks you never received.Queue the outbound call. The request to create a transaction is a network call to a third party and can take seconds on large PDFs. Dispatch a job from the controller, return immediately, and let the job create the transaction and email/notify the signer. Same for webhook processing: acknowledge 200 fast, then dispatch() the state transition.
Make signature level and document handling config, not literals. Simple vs advanced vs qualified changes the legal weight and the signer UX (SMS OTP, ID verification, longer flows). Put it in config/universign.php per document type, not inline at the call site. For documents: keep an immutable copy on S3/disk, pass a signed URL or base64 depending on what the client supports, and hash what you actually sent.
Testing. Keep a FakeSignatureGateway bound in testing that records requests and returns canned refs — assert your flow (persistence, redirect, webhook transitions) without network. Then have a tiny, separately-tagged suite that hits Universign's sandbox for the one happy path, so an SDK upgrade breaks loudly in CI rather than in production.
"mpp/universign-bundle": "1.x.y"), read the diff before every bump, and consider vendoring/forking the client class outright if you only need two or three methods.Configuration/Extension rather than Universign.signature_requests for an existing pending row for that document before calling out; treat the provider call as non-idempotent.Http middleware) that dumps request/response bodies to a dedicated log channel — and redact the password and any base64 PDF body, or you'll blow up your log storage and leak PII.services.yaml. The config tree is the intended customization surface; anything beyond it belongs in your adapter.UniversignClient of your own with Http:: and a queued job, and you'll have less coupling, better logging, and no dependency on a package with one maintainer and no users.How can I help you explore Laravel packages today?