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

Laravel Stripe Connect Laravel Package

rap2hpoutre/laravel-stripe-connect

A Laravel package for integrating Stripe Connect. It helps create and manage connected accounts, generate onboarding links, and handle Stripe Connect flows with simple configuration and Laravel-friendly APIs.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Assess before installing. This package was last released in January 2018 and is archived. It targets Laravel 5.x. On Laravel 9/10+ expect the service provider, the published migration, and the pinned stripe/stripe-php constraint to need adjustment. Two viable approaches:

  • composer require rap2hpoutre/laravel-stripe-connect on an older app and use it as-is.
  • Read the package source (it is small — a service provider, config, a migration, a trait, and one controller with two actions) and copy those files into app/ as your own code. This is usually the right call for a new project.

2. Register the provider (only needed on Laravel < 5.5, or if you vendored the files):

// config/app.php
Rap2hpoutre\LaravelStripeConnect\LaravelStripeConnectServiceProvider::class,

3. Publish config and migration:

php artisan vendor:publish --provider="Rap2hpoutre\LaravelStripeConnect\LaravelStripeConnectServiceProvider"

Check the provider for the actual tags — you generally want the config and the migration (which adds the Stripe account columns to users). Open the published migration before running it and confirm the table name and column names fit your schema.

4. Configure credentials. In .env:

STRIPE_CLIENT_ID=ca_...
STRIPE_SECRET=sk_...
STRIPE_CONNECT_REDIRECT_URI=https://yourapp.test/stripe/callback

Then in the Stripe Dashboard: enable Connect, and register the redirect URI so it matches your config character for character.

5. Add the trait to the model that owns the connected account:

use Rap2hpoutre\LaravelStripeConnect\Traits\HasStripeAccount;

class User extends Authenticatable
{
    use HasStripeAccount;
}

6. First use case — the "Connect with Stripe" button. Run php artisan route:list | grep -i stripe to see the exact route names the package registers, then link to the redirect route from your settings page:

<a href="{{ route('stripe-connect.redirect') }}" class="btn">Connect with Stripe</a>

Clicking it sends the user to Stripe's OAuth consent screen; Stripe redirects back to the callback route, which exchanges the code and persists the account ID on the authenticated user. At that point auth()->user()->stripe_account_id is populated and you can start making connected-account API calls.

Implementation Patterns

Store-then-use: the account ID is the integration point. The package's job ends once the account ID is on your model. Everything downstream is plain stripe/stripe-php:

\Stripe\Stripe::setApiKey(config('services.stripe.secret'));

// Acting on behalf of a connected account
$balance = \Stripe\Balance::retrieve(
    [],
    ['stripe_account' => $user->stripe_account_id]
);

// Platform-level: create a charge directly on the connected account
\Stripe\Charge::create([
    'amount'   => 1000,
    'currency' => 'eur',
    'source'   => $token,
], ['stripe_account' => $user->stripe_account_id]);

Wrap this in a small StripeConnectService (app/Services/StripeConnect.php) with methods like balanceFor(User $user), chargeFor(User $user, array $payload). That gives you one seam to mock in tests and one place to enforce that stripe_account_id is non-null.

Gate features on connection state. A RequiresStripeAccount middleware keeps half-onboarded users out of payout screens:

public function handle($request, Closure $next)
{
    abort_unless($request->user()?->stripe_account_id, 403, 'Connect a Stripe account first.');

    return $next($request);
}

Guard the onboarding routes. Check whether the package applies auth middleware to its routes; if not, wrap them yourself by re-registering the routes in routes/web.php before the provider's routes load, or apply middleware in a service provider's boot():

Route::middleware(['web', 'auth'])->group(function () {
    Route::get('stripe/connect', [App\Http\Controllers\StripeConnectController::class, 'redirect'])
        ->name('stripe-connect.redirect');
    // callback route deliberately OUTSIDE auth if you use state-based validation
});

Extend the callback rather than editing vendor code. Subclass the package controller, override the callback action, call parent::callback($request), then run your own post-connect work (send a welcome email, sync the account's display_name, set a stripe_connected_at timestamp), then redirect wherever you want:

public function callback(Request $request)
{
    $response = parent::callback($request);

    $account = \Stripe\Account::retrieve(
        $request->user()->stripe_account_id
    );
    $request->user()->update([
        'stripe_connected_at'    => now(),
        'stripe_account_country' => $account->country,
    ]);

    return redirect()->route('settings.payouts')
        ->with('status', 'Stripe connected.');
}

Reconnect / disconnect flows. Disconnecting is just nulling the columns plus a fresh OAuth round-trip. Make it explicit so support can resolve a broken connection without a DB console:

$user->forceFill(['stripe_account_id' => null, 'stripe_access_token' => null])->save();

Non-User models. For team-based or seller-based ownership, publish the migration, change the table, and put the trait on Team. Everywhere you reference auth()->user()->stripe_account_id, replace it with $team->stripe_account_id — the package only assumes "the authenticated model".

Handle deauthorization. Stripe sends account.application.deauthorized when a seller revokes access from their dashboard. The package predates this concern; add a webhook endpoint (outside CSRF) that clears the stored account ID, otherwise you'll show "Connected" for accounts you can no longer act on.

Gotchas and Tips

  • Redirect URI must match exactly. A mismatch (http vs https, www, trailing slash, wrong port on Valet/Herd) produces Stripe's redirect_uri_mismatch before your app is ever hit — so nothing appears in storage/logs. Check the Stripe dashboard first when debugging onboarding.
  • Client IDs are per-mode. Test and live modes have different ca_... client IDs. A live client ID with a test secret (or vice versa) yields invalid_client on callback. Keep both in .env and never copy a live value into .env.testing.
  • No OAuth state parameter = account-linking CSRF. Verify whether this version sends/validates state. If it doesn't, an attacker can send a victim through an OAuth flow that links the attacker's Stripe account to the victim's user row. Add a session-bound state in your redirect override and validate it in the callback.
  • You may not need to store the access token. For Standard accounts, you authenticate with your platform secret key plus the Stripe-Account header. The per-account token is optional. If the package stores it in plaintext (it likely does), either encrypt the column with an encrypted cast or stop persisting it.
  • Debugging checklist, in order: php artisan route:list | grep -i stripe (routes exist?) → config('services.stripe.*') / config('stripe-connect.*') values (correct mode?) → php artisan config:cache after any .env change → Stripe Dashboard → Connect → Accounts (did an account actually get created?) → storage/logs/laravel.log (the package swallows Stripe exceptions in some paths, so wrap the callback call in a try/catch that logs $e->getStripeCode()).
  • Config caching bites. Any env() read inside a published config file is resolved once at cache time. Changing .env without php artisan config:cache (or config:clear in dev) leaves you onboarding against a stale client ID.
  • Migration collisions. The published migration assumes a users table and names columns generically. On modern Laravel it may also lack the anonymous-class/return new class style. Edit the file before running it rather than after.
  • Route namespace changes. Pre-Laravel-8 controllers used string namespaces ('Rap2hpoutre\...\Controller@callback'). On Laravel 8+ this fails with "Target class does not exist" unless you set RouteServiceProvider::$namespace. Vendoring the controller into App\Http\Controllers sidesteps this entirely.
  • Extension points are thin. There are no publishable views and no events. Your levers are: publish and edit config, publish and edit the migration, subclass the controller, and register routes before the package's. Treat the package as a starter kit you own, not a dependency you extend.
  • Pin stripe/stripe-php. A 2018 package has a loose or old constraint. Lock a recent SDK explicitly in your own composer.json and smoke-test the two package calls, or better, use the SDK directly and use the package only for the OAuth redirect.
  • Test mode end-to-end. Test the full loop with Stripe test-mode Connect (you can create test connected accounts instantly). Don't try to unit-test the OAuth exchange — stub your StripeConnectService and assert on your own persistence and redirect behavior instead.
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.
takashato/laravel-vietqr
aitor24/laralang
kraenzle-ritter/entity-relations
dindigital/din-image
pomirleanu/image-colors
sawmainek/laravel-api-generator
delatbabel/jsonresponses
mouhamedfd/permissions-generator
eduplus/qr
laravel2580/idehelpers
delatbabel/viewpages
anavel/gettext
muhamadrezaar/highcharts
delatbabel/keylists
frankkessler/salesforce-laravel-oauth2-rest
anavel/foundation
wdmtech/enviricons
eii/laravel-captcha
muka/shape-reader
tdt/json