omnipay/stripe
Omnipay Stripe is a Stripe payment gateway driver for the Omnipay PHP library. It lets you create charges, manage customers and cards, issue refunds, and handle tokens through a consistent Omnipay API, with support for standard Stripe test and live workflows.
Pull in the driver with Omnipay 3.x (the only supported major line):
composer require omnipay/stripe:^4 omnipay/common:^3
Create the gateway by short name and give it a secret key. There is no testMode switch — test vs. live is decided entirely by the key prefix (sk_test_… vs sk_live_…):
use Omnipay\Omnipay;
$gateway = Omnipay::create('Stripe'); // = Omnipay\Stripe\Gateway
$gateway->setApiKey(config('services.stripe.secret'));
The first use case is almost always a one-shot card charge with a token produced by Stripe.js/Elements (never post raw PANs through your server):
$response = $gateway->purchase([
'amount' => '25.00', // decimal string, not float
'currency' => 'USD',
'token' => $request->input('stripeToken'), // tok_...
'description' => 'Order #1042',
'metadata' => ['order_id' => 1042],
])->send();
if ($response->isSuccessful()) {
$chargeId = $response->getTransactionReference(); // ch_...
} elseif ($response->isRedirect()) {
return redirect()->away($response->getRedirectUrl()); // SCA / 3DS
} else {
report($response->getMessage());
}
Where to look first:
src/Gateway.php — the list of supported operations (purchase, authorize, capture, refund, void, createCustomer, createSubscription, fetchTransaction, fetchBalance, …), the default parameters, and the pinned API_VERSION.src/Message/*Request.php — the authoritative source for accepted parameter names and how each one maps onto Stripe's REST payload. The README is thin; these classes are the real documentation.Omnipay\Common\Message\ResponseInterface — isSuccessful(), isRedirect(), isPending(), getCode(), getMessage(), getData(), getTransactionReference() are the only response API you should rely on.In Laravel, bind it once rather than calling Omnipay::create() per request:
$this->app->singleton(\Omnipay\Stripe\Gateway::class, fn () => Omnipay::create('Stripe')
->setApiKey(config('services.stripe.secret')));
Authorize → capture → refund/void. Use this whenever fulfilment is delayed (shipping, inventory reservation). authorize() creates an uncaptured charge; capture() settles all or part of it; void() releases an uncaptured authorization; refund() works against a captured charge or payment intent.
$auth = $gateway->authorize(['amount' => '25.00', 'currency' => 'USD', 'token' => $tok])->send();
$ref = $auth->getTransactionReference();
$gateway->capture(['amount' => '20.00', 'transactionReference' => $ref])->send(); // partial capture
$gateway->void(['transactionReference' => $ref])->send();
$gateway->refund(['amount' => '5.00', 'transactionReference' => $ref, 'reason' => 'requested_by_customer'])->send();
Store the charge/PI reference, the captured amount, and the response getData() JSON in your orders table — that reference is the key for every later capture, refund, void, and fetchTransaction() reconciliation.
SCA / 3DS redirect flow. When Stripe returns requires_action, the driver hands back a redirect response with the next_action.redirect_to_url URL. On the return URL (Stripe appends payment_intent and payment_intent_client_secret), confirm:
$response = $gateway->completePurchase([
'transactionReference' => $request->query('payment_intent'),
])->send();
if ($response->isSuccessful()) {
// mark paid; do not trust the query string alone — this call re-fetches from Stripe
}
Never mark an order paid on the strength of the browser redirect alone. Use completePurchase() and the webhook, and make both idempotent.
Saved customers and cards. Create a customer with a token as the initial source, then charge later with customerReference (no token needed):
$customer = $gateway->createCustomer(['email' => $user->email, 'source' => $token])->send();
$cusId = $customer->getCustomerReference();
$gateway->createCard(['customerReference' => $cusId, 'token' => $newToken])->send();
$gateway->purchase(['amount' => '9.99', 'currency' => 'USD', 'customerReference' => $cusId])->send();
List/delete cards and customers through listCards, deleteCard, updateCustomer, deleteCustomer. Keep Stripe as the system of record; mirror only the IDs you need.
Subscriptions, plans, invoices. createPlan (amount, currency, interval, id), createSubscription (customerReference, plan), cancelSubscription/updateSubscription (subscriptionReference), createInvoice/payInvoice/fetchInvoice (invoiceReference). Treat plan/subscription changes as your own state machine and reconcile with fetchSubscription — the local DB will drift if you only react to webhooks.
Webhooks. The package ships no signature-verification helper, so do it yourself (or add stripe/stripe-php purely for Webhook::constructEvent):
[$t, $v1] = /* parse "t=...,v1=..." from Stripe-Signature */;
$expected = hash_hmac('sha256', $t.'.'.$request->getContent(), $secret);
abort_unless(hash_equals($expected, $v1) && abs(time() - (int) $t) < 300, 400);
Queue the handling (payment_intent.succeeded, charge.refunded, invoice.paid), key idempotency off event.id, and re-fetch the object with fetchTransaction() before mutating local state — events can arrive out of order and be redelivered.
Multi-gateway abstraction. The reason to use Omnipay at all is that purchase()/refund() look identical across Stripe, PayPal, etc. Keep gateway-specific code out of controllers: depend on a small PaymentGateway interface in your domain, inject the Omnipay gateway, and translate responses into your own result object. Swapping providers then means adding one adapter.
getAmountInteger() in the driver before going live in those markets. Always pass '10.00', never a float.Stripe-Version header from that era. Response mapping reflects that API version, so newer Stripe concepts — SetupIntents, Payment Element, Tax, newer next_action shapes — are absent or partially mapped. Do not mix stripe/stripe-php object assumptions with this driver's arrays.stripe/stripe-php dependency. Requests go straight to the REST API via Omnipay's PSR-18 HTTP client, so you get no \Stripe\* classes and no webhook helper.Idempotency-Key, but the driver does not expose it as a first-class parameter in every release. Subclass the request to add the header, or dedupe with a unique orders.idempotency_key before calling send(). Retries after a timeout are the classic double-charge.send() returns a response; transport and validation failures throw. Catch Omnipay\Common\Exception\InvalidRequestException (missing/malformed params), InvalidResponseException, and the PSR-18 client exceptions. Branch on isSuccessful()/isRedirect() — never on HTTP status — and always log getCode() plus getMessage().$response->redirect() writes raw headers and exits. In Laravel use return redirect()->away($response->getRedirectUrl()); also guard against a null URL before redirecting.token vs card vs customerReference: prefer tokens; the card array path (Omnipay\Common\CreditCard) puts you in PCI scope and is best reserved for server-side test fixtures.stripeAccount (acct_…, sent as the Stripe-Account header) and, for destination charges, applicationFee. Confirm the exact parameter name in the relevant *Request class — it has changed across releases.$gateway->setHttpClient($mock) to assert outgoing payloads in unit tests; use sk_test_… plus cards 4242424242424242 (success) and 4000002500003155 (3DS challenge) for the integration path. Test mode follows the key, so a setTestMode() call is meaningless here.fetchTransaction() for anything older than a few minutes still in pending, and fetchBalance()/listTransactions() for daily settlement checks. Webhooks alone are not a ledger.setHttpClient() for middleware (logging, retries), and a subclass overriding API_VERSION if you truly need a newer Stripe version — but verify the response mapping still matches, since that is the part most likely to break.How can I help you explore Laravel packages today?