orkestra/transactor
Orkestra Transactor is a Laravel package for clean, reliable database transaction handling. Wrap operations in transactions, manage nested transactions, retry deadlocks, and run after-commit callbacks with a simple, expressive API.
This is a framework-agnostic payment library that happens to run fine inside Laravel — there is no Laravel service provider, facade, or config file shipped with it. Budget an hour to write that glue yourself; it is the same hour every project spends on it.
composer require <vendor>/<package>:^1.2. The 1.2.0 release widens support to Symfony 2.3+/3.0+ components, which is mostly a dependency-resolution concern for you: Laravel's own constraints usually pull a modern symfony/* tree, so run composer why symfony/http-foundation if resolution fights back.TransactorInterface and one concrete implementation (start with the Authorize.Net card transactor added in this release). Everything else — accounts, credentials, responses — is shaped by that interface.TransactorInterface::createCredentials(), intended to return a default credential set for the transactor. That is your hook: implement it (or subclass the shipped transactor) to read from config('services.authorize_net'), which in turn reads env().Account, call the transactor's authorize/purchase method, inspect the response object. Do this in a Tinker session or an artisan command against the Authorize.Net sandbox before touching a controller.Keep the transactor behind your own interface. The package's API is stable-ish but has already shipped one BC break (see below). Wrap it:
interface PaymentGateway {
public function charge(Money $amount, CardDetails $card): PaymentResult;
}
Bind your adapter to TransactorInterface in the container and inject your interface into controllers and jobs. Tests then fake at your boundary instead of mocking a vendor object graph.
Credential resolution belongs in one place. Implement createCredentials() to pull from config(), not env(). Centralize per-gateway config (driver, login_id, transaction_key, sandbox, default_currency) in config/services.php and validate it in a service provider so a missing key fails at boot, not mid-checkout.
Multi-gateway: use a manager, not conditionals. If you support more than one processor, resolve a transactor by name from a small factory, mirroring Laravel's own manager pattern. The Authorize.Net card and ACH transactors added in 1.2.0 are distinct collaborators — treat "card" and "e-check" as separate drivers rather than branching inside one class.
Swiped/track data is now a two-step flow. Since 1.2.0, SwipedCardAccount no longer parses account information out of track data. Decode explicitly with a TrackDecoder, then construct the account from the decoded values:
$decoded = $trackDecoder->decode($trackData);
$account = new CardAccount($decoded->getAccountNumber(), $decoded->getExpiration());
For anything where you must retain the raw track, use the new EncryptedSwipedCardAccount. Keep decoding in a dedicated service so the PCI-sensitive string touches exactly one class and never reaches a model, a log, or a session.
Tokenization plumbing is available but thin. 1.2.0 adds basic support for tokenizing sensitive account data. Use it for repeat billing: tokenize once at card capture, persist only the token plus last-four/brand, then charge against the token thereafter. Expect to own the persistence layer — the package supplies the value objects, not the storage.
Persist payment records in the same transaction as the order. Write your payments row, then call the transactor, then update the row — or better, write a pending row, call out, update to succeeded/failed. Never rely on the HTTP response alone to decide whether money moved; store the gateway transaction ID and raw response.
Log both directions, redacted. A decorator around your adapter that logs the request shape and the full response (minus PAN, CVV, track data) will save you hours during the first production dispute.
The 1.2.0 BC break will bite you if you wrote a custom transactor. TransactorInterface gained createCredentials(). Any hand-rolled implementation is now a fatal "abstract method not implemented" error at class load. Add the method and have it return a sensible default credential object.
SwipedCardAccount now fails silently-ish. The account no longer populates itself from track data, so a reader-upgraded codebase will start throwing validation or "missing account number" errors deep in the transactor rather than at the call site. Grep for SwipedCardAccount before you upgrade and route every construction through a TrackDecoder.
Beware env() inside createCredentials(). If that method runs per-request (it may, depending on when transactors are constructed), env() returns null once php artisan config:cache has run. Always go through config().
Amounts are almost certainly minor units and integers. Do not pass floats. Use an integer-cents value object and convert at the edge; currency must be explicit for Authorize.Net, even when it feels implicit.
Symfony version drift is the most likely install failure. If your composer.json locks older symfony/* packages for other reasons, 1.2.0's widened constraints may not help you. Check composer update --dry-run before committing to the upgrade.
Responses are objects, not booleans. Read the response code and message; many gateways return HTTP 200 with a declined transaction. Treat exceptions as transport failures and response codes as business outcomes — your retry logic differs for each.
Extension points worth knowing:
TransactorInterface directly for an unsupported gateway — it is a small interface, and createCredentials() makes it self-configuring.TrackDecoder implementations for non-standard readers.Debugging checklist: confirm you are pointed at the sandbox endpoint; dump the credential object first (gateways fail opaquely on bad credentials); enable your redacted response logger; and reproduce outside Laravel with a plain PHP script — if it fails there too, the problem is configuration, not your service container.
Upgrade order for 1.2.0: add createCredentials() to custom transactors → migrate swiped-card flows to explicit TrackDecoder → add tests around tokenization → deploy. Do not bundle this with other dependency upgrades.
How can I help you explore Laravel packages today?