Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Firstdata Laravel Package

omnipay/firstdata

View on GitHub
Deep Wiki
Context7

Getting Started

The omnipay/firstdata driver plugs First Data's Global Gateway (e4) into the framework-agnostic Omnipay payment library. In a Laravel app you rarely touch it directly — you install it, wrap it in a service provider, and call it from a payment service.

1. Install and check the HTTP adapter. Omnipay 3 uses PSR-18 discovery, so the app needs a PSR-18 client. Most Laravel apps already have Guzzle, but the adapter is explicit:

composer require omnipay/firstdata php-http/guzzle7-adapter

The driver's last release was February 2019, so pin the major version (^2.0) and verify it resolves against your PHP and omnipay/common versions before doing anything else.

2. Put credentials in config, not code. First Data Global Gateway e4 needs a store ID, a shared secret, and (depending on how the account was provisioned) an API password. The driver's getDefaultParameters() is the source of truth for exact parameter names in your installed version — check it before wiring config.

// config/services.php
'firstdata' => [
    'store_id'      => env('FIRSTDATA_STORE_ID'),
    'shared_secret' => env('FIRSTDATA_SHARED_SECRET'),
    'api_password'  => env('FIRSTDATA_API_PASSWORD'),
    'test_mode'     => env('FIRSTDATA_TEST_MODE', true),
],

3. Bind the gateway once. Create it in a service provider so the driver's XML signing and endpoint selection live in one place:

// app/Providers/FirstDataServiceProvider.php
public function register(): void
{
    $this->app->singleton(GlobalGateway::class, function () {
        $gateway = Omnipay::create('FirstData_GlobalGateway'); // confirm short name in README
        $gateway->setStoreId(config('services.firstdata.store_id'));
        $gateway->setSharedSecret(config('services.firstdata.shared_secret'));
        $gateway->setApiPassword(config('services.firstdata.api_password'));
        $gateway->setTestMode(config('services.firstdata.test_mode'));

        return $gateway;
    });
}

4. First use case: a single-shot purchase(). This is the shortest path to a working checkout and validates credentials, currency, and test-mode setup before you add authorize/capture complexity:

$response = $gateway->purchase([
    'amount'      => number_format($order->total_cents / 100, 2, '.', ''), // "10.00", not 1000
    'currency'    => 'USD',
    'description' => "Order #{$order->id}",
    'card'        => $request->only(['number', 'expiryMonth', 'expiryYear', 'cvv', 'firstName', 'lastName']),
])->send();

if ($response->isSuccessful()) {
    $order->update([
        'status'        => 'paid',
        'gateway_ref'   => $response->getTransactionReference(),
        'auth_code'     => $response->getAuthorizationCode(),
    ]);
}

Run it against the demo endpoint first (testMode = true), then swap in live credentials.

Implementation Patterns

Authorize, then capture. For anything beyond a simple sale — partial shipments, delayed settlement, inventory checks — use authorize() at checkout and capture() when you ship. The reference returned by authorize is what capture, refund, and void all key off of:

$auth = $gateway->authorize([...])->send();

$capture = $gateway->capture([
    'amount'               => '10.00',
    'currency'             => 'USD',
    'transactionReference' => $auth->getTransactionReference(),
])->send();

Store the reference, not the card. Persist getTransactionReference() (and getAuthorizationCode() for support tickets) on the order row. Every subsequent operation — refund, void, capture — is a lookup against that reference, so losing it means manual reconciliation with First Data. Never store PAN/CVV.

Keep Omnipay objects out of your domain. Wrap the gateway behind your own interface (App\Payments\PaymentGateway) that takes a DTO and returns a result object with success, reference, message, and code. Controllers and models shouldn't call $response->getData() directly — that makes swapping gateways (or adding a second one) a rewrite instead of a config change.

Idempotency is your job. The driver won't stop a double-submit from creating two charges. Wrap the call in DB::transaction() with lockForUpdate() on the order, disable the submit button, and use a unique invoice/order reference so you can spot duplicates during reconciliation. Generate and store a UUID per attempt if the order ID isn't stable yet.

Map declines to redirects, not exceptions. A declined card is a normal 200 response with isSuccessful() === false. Only transport failures throw. Catch \Omnipay\Common\Exception\InvalidCreditCardException (card-number/expiry validation happens client-side in the CreditCard object) and convert it to a Laravel validation error so users get a form message instead of a 500.

Fire side effects via events. After a successful charge, emit OrderPaid and let queued listeners send receipts and trigger fulfillment. This keeps the HTTP request fast and keeps payment code decoupled from email/inventory code.

Test with a mock PSR-18 client, not Http::fake(). Laravel's Http::fake() won't intercept Omnipay's traffic because the driver uses its own client. Inject a mock via setHttpClient() (php-http/mock-client or a hand-rolled Omnipay\Common\Http\ClientInterface) and assert on the response handling. Reserve one live smoke test per deploy for the demo endpoint.

Log the wire traffic, not just the outcome. Add a listener that logs $response->getData() and getMessage() on failure. For the request side, inject a Guzzle client with Middleware::log() (or HTTPlug's LoggerPlugin) via setHttpClient() — Omnipay itself is silent by default.

Gotchas and Tips

  • The package is effectively unmaintained. 21 stars, last release 2019, and First Data Global Gateway e4 is a legacy product that Fiserv has been steering merchants away from. Pin the version, add it to your dependency-review list, and treat it as a migration candidate rather than a long-term platform.
  • Authentication failures are opaque. A wrong sharedSecret, mismatched store ID, or clock skew surfaces as a generic auth error with little detail. Check server time (NTP) first, then credentials, then that test/live credentials aren't crossed.
  • Amounts are decimal strings. Pass "10.00", not 1000 and not 10.0. Send an integer cents value and you'll charge a hundred times too much. Keep money as integer minor units in the database and format at the boundary.
  • One currency per merchant account. Requesting a currency the account isn't provisioned for will be converted or rejected depending on config. Capture currency at order time and don't let it change later.
  • Void ≠ refund. Void only works before the daily batch settles; after that you need a refund() against the original reference. Track settlement timing if you offer same-day cancellation.
  • No webhooks. Everything is synchronous request/response, so a timeout leaves you genuinely unsure whether the charge landed. Never blind-retry a purchase — mark the order "pending review", reconcile against First Data's reporting, and only then retry with a fresh reference.
  • Tokenization and 3DS support are thin. Don't assume createCard()/stored-card support or SCA readiness. If you need recurring billing or EU strong customer authentication, validate against the installed driver or choose a different gateway.
  • Test cards are specific. The demo gateway accepts the usual 4111 1111 1111 1111 with any future expiry and a valid CVV shape, but rejects patterns it considers malformed. If a "valid" test card declines, that's usually the demo gateway, not your code.
  • Check isSuccessful(), never the message string. A 200 HTTP status can still be a decline. Use getCode() and getData() for gateway-specific fields, and treat getMessage() as display text only.
  • Extension points. Subclass the gateway and override createRequest() to add custom transaction types, inject a custom HTTP client for logging/retries/timeouts (set timeouts on the client, not the gateway), and keep the shared omnipay/common version aligned if you use other Omnipay drivers in the same app.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource