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

Stripe Laravel Package

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.

Deep Wiki
Context7

Getting Started

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')));

Implementation Patterns

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.

Gotchas and Tips

  • Amounts are decimal strings, converted to minor units. Omnipay multiplies by 100 internally. Zero-decimal currencies (JPY, KRW, VND) will be charged 100× too much unless the request class handles the exponent — check getAmountInteger() in the driver before going live in those markets. Always pass '10.00', never a float.
  • The package is stale (last release 2022) and pins a 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.
  • No 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 is on you. Stripe honours 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.
  • Metadata limits: 50 keys, 40-char keys, 500-char values, scalar values only. Nested arrays are silently rejected by Stripe — flatten them.
  • 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.
  • Stripe Connect: pass 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.
  • Testing: inject a mock PSR-18 client via $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.
  • Reconciliation: schedule a job that calls fetchTransaction() for anything older than a few minutes still in pending, and fetchBalance()/listTransactions() for daily settlement checks. Webhooks alone are not a ledger.
  • Extension points: custom request classes registered on the gateway, 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.
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.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib