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

cartalyst/stripe

Cartalyst Stripe is a PHP/Laravel wrapper around the Stripe API, offering a fluent, object-oriented SDK for charges, customers, subscriptions, invoices, coupons, and webhooks. It streamlines payment integration with clean syntax and Laravel support.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit. cartalyst/stripe is a thin, object-oriented adapter over Stripe's REST API, delivered as a Guzzle-backed PHP client with an optional Laravel service provider, Stripe facade, and publishable config/stripe.php. Architecturally it is plumbing: resource factories (customers(), charges(), paymentIntents(), subscriptions(), invoices(), refunds(), tokens(), plans(), coupons()) that map one-to-one onto Stripe endpoints and return plain PHP arrays. It resolves through the container singleton (stripe), which is the correct extension point for interception, logging, subclassing, or a gradual per-method migration. It is not a wrapper around stripe/stripe-php; it carries its own resource classes. That matters for any program that assumes "one Stripe client" — adopting this package alongside the official SDK means two client stacks, two response shapes (array vs \Stripe\* objects), and two exception hierarchies in the same codebase.

Critically for product planning: this package ships no billing logic. There is no proration engine, dunning state machine, trial lifecycle, tax determination, usage rating, or invoicing workflow. Subscriptions are CRUD over Stripe's objects. Everything above that line is internal scope, not vendor scope.

Integration feasibility. Low friction on the happy path. composer require cartalyst/stripe plus a vendor:publish for the service provider, then STRIPE_SECRET in .env. Multi-account support is the package's strongest genuine differentiator: a default profile plus a profiles array, with Stripe::profile('eu')->customers()->create([...]) at the call site. That unlocks regional entities, separate brands, and clean test/live separation without forking the integration — validate against your go-to-market structure before committing.

Technical risk. The release date is the risk, not the API surface. Last release 2021-10-09 (v2.4.6), 278 stars, zero dependents, no security-response commitment. Specific risks a TPM should own explicitly:

  • Compatibility drift. The last release predates Laravel 9; auto-discovery manifests can be stale and the locked Guzzle version needs a CVE check. Both must be verified in a throwaway branch before "adoption" is treated as a decision rather than a rewrite.
  • API-version staleness. Pin api_version in config rather than relying on the account default — otherwise a dashboard-side bump silently changes response shapes under you. Confirm the version the package targets is still accepted by Stripe.
  • Webhooks are DIY. No \Stripe\Webhook::constructEvent; you either pull stripe-php in just for HMAC verification or compute it yourself against the Stripe-Signature header (t and v1, signed payload ${t}.${body}, constant-time compare) before parsing JSON.
  • Refund semantics changed in 2.4.6 (refunds keyed off a charge vs. a payment_intent are different call shapes) — the exact spot where a version bump misbehaves silently.
  • Surface caps at 2021. Payment method types, PaymentIntent/SetupIntent flows, and webhook event shapes added after that date are unavailable through this client.

Key questions to resolve before greenlighting:

  1. Are we greenfield or incumbent? Greenfield adoption of an unmaintained wrapper is a decision we will re-litigate within a year; incumbent reads as "low switching urgency, start budgeting migration."
  2. Does the locked dependency set resolve against our PHP and Laravel versions, and is the Guzzle version CVE-clean?
  3. Which subscription features are actually in scope — trials, plan changes, proration, tax, entitlements, usage-based pricing? If any of those are core, a thin wrapper is the wrong tier regardless of code quality.
  4. Do we need Stripe capabilities introduced after 2021 (newer payment methods, current webhook shapes)? If yes, this is disqualified for anything touching that flow.
  5. Who owns the contract-test suite against Stripe test mode? Because the package is frozen, those tests are the only barrier between API drift and production.
  6. Do we already depend on stripe/stripe-php anywhere? If so, plan for two client stacks or make the migration now.

Integration Approach

Stack fit. Laravel/PHP services are the natural home. The service provider auto-registers the Stripe facade and container binding; the published config/stripe.php supplies a default profile name and a profiles array for multi-account. The package is also framework-agnostic — instantiate Cartalyst\Stripe\Stripe directly in non-Laravel PHP — which helps if some services are outside Laravel. If billing is owned by a dedicated non-PHP service, this wrapper adds no leverage.

Migration path — build the exit on day one. The single most important architectural decision is not to call the facade from controllers or models. Put one internal interface in front of it immediately, shaped around our operations rather than Stripe's resource tree:

interface BillingGateway
{
    public function createCustomer(User $user): array;
    public function charge(int $amountCents, string $currency, string $paymentMethodId, string $customerId): array;
    public function refund(string $paymentIntentId, ?int $amountCents = null): array;
}

Implement it with Stripe::… today; swap to stripe/stripe-php or Cashier later without touching call sites. This is both good design and migration insurance, and it converts the dependency into a contained, replaceable adapter — a bounded backlog item with a named owner, not an emergency and not a re-architecture. The container stripe binding is the cleanest interception point for per-request logging and for a gradual, per-method cutover to the official SDK.

Compatibility constraints to lock down:

  • PHP and Laravel versions confirmed by an actual composer require in a throwaway branch, not by reading the README.
  • api_version pinned explicitly in config; never inherit the account default.
  • Guzzle version audited for CVE exposure.
  • Laravel auto-discovery manifest verified on our version rather than assumed.

Sequencing (recommended order of work):

  1. Spike branch (timeboxed). Resolve dependencies, audit Guzzle, confirm the targeted api_version is accepted by Stripe. Gate: all three pass, or adoption becomes a rewrite with no intermediate state.
  2. Wire the facade behind BillingGateway. No direct facade calls anywhere else, ever.
  3. End-to-end proof in test mode: customer create + PaymentIntent create/confirm. PaymentIntents (not legacy Charges) are the correct target because any EU/UK launch requires SCA/3DS-compliant flows; the v2.4.6 refunds-with-PaymentIntents update is the relevant signal and also the last one.
  4. Webhooks: dedicated controller, CSRF exempted, signature verified before parse, event ID persisted and deduped, handlers idempotent, and objects re-fetched by ID rather than trusting the event payload shape (event schemas drift across API versions and a 2021-pinned library will not track them). Push work onto a queue job.
  5. Subscriptions and refunds, then grep every refunds()->create call site to confirm which identifier it passes.
  6. Contract tests + nightly test-mode suite covering customer create, PaymentIntent create/confirm, full and partial refund, subscription create/cancel, and webhook signature rejection for a tampered body.

Data modeling guidance to enforce from the start: store IDs (cus_…, pi_…, sub_…, in_…) with a stripe_account column for multi-profile installs; treat the database as an index into Stripe, not a copy of it. Pass a deterministic idempotency key on every mutating call ("refund:{$order->id}", not a random UUID) — it is the only protection against double charges from retried jobs, double-clicked submits, and queue redeliveries. Centralise minor-unit formatting; do not scatter * 100 through the codebase (JPY and KRW take 1999 meaning ¥1999).

Operational Impact

Maintenance. Effectively unmaintained upstream. Zero dependents and a release three-plus years old is not a support story — internal engineering owns the code, the security posture, and the Stripe-compatibility posture. The mitigation is structural: the code is a thin adapter over a well-documented public API, the BillingGateway interface bounds the blast radius, and the migration is schedulable rather than urgent. Name an owner and put the migration on the roadmap with a horizon, rather than leaving it as ambient risk.

Support. No vendor support, no security-response commitment, no SLA. Community-only for questions. Escalation path is internal engineering plus reading src/Api/*.php when behavior is undocumented — each resource is a thin wrapper over POST /v1/..., so the source is the specification. Budget for that onboarding cost; there is no vendor to call.

Scaling. Synchronous Guzzle HTTP against Stripe's rate limits, so throughput is bounded by Stripe rather than by the package. Push all mutating calls onto queue workers with retries and deterministic idempotency keys; keep webhook processing on its own queue so Stripe retries do not back up customer-facing traffic. Multi-profile configuration scales cleanly across accounts; per-account rate budgets and per-account keys need to be tracked deliberately rather than assumed.

Failure modes to instrument for:

  • Silent API drift — Stripe changes response shapes or endpoint semantics; the 2021-pinned client passes them through. Detection: contract tests in test mode, run nightly.
  • Double charges — missing or non-deterministic idempotency keys on retries.
  • Refund identifier confusion — charge-keyed vs. PaymentIntent-keyed call shapes (2.4.6 boundary).
  • Webhook forgery or replay — parsing JSON before verifying the signature, or missing event-ID dedupe.
  • Currency bugs — zero-decimal currencies mishandled by scattered * 100.
  • 500s leaking Stripe detail — catching \Stripe\* exceptions instead of Cartalyst\Stripe\Exception\StripeException subclasses; register a handler, log error type and decline code, return a generic message to the customer, never the raw message.
  • Credential leakage — if Guzzle request logging is enabled, strip the Authorization header.
  • CVE in the locked Guzzle — no upstream patch path; audit on a schedule.

Ramp-up. One to two days for a competent Laravel developer to become productive: config/stripe.php first, then the facade root and src/Api/ class names (self-documenting and one-to-one with REST endpoints), then the array-vs-object difference from the official SDK — that is the biggest day-to-day adjustment and the most common source of bugs for anyone with prior stripe-php experience. Budget separately for the billing logic the package does not ship: proration, dunning, trial state machines, tax, and revenue recognition are product scope discovered in week three if not planned in week one. Treat the contract-test suite as the onboarding artifact that makes the boundary legible to new hires, and document the BillingGateway interface as the sanctioned entry point so no one shortcuts to the facade.

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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
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