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.
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.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.
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.
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.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.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.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.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()).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.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.'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.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.StripeConnectService and assert on your own persistence and redirect behavior instead.How can I help you explore Laravel packages today?