├── .gitignore ├── src ├── Concerns │ ├── ManagesPerson.php │ ├── ManagesBalance.php │ ├── ManagesPayout.php │ ├── ManagesTransfer.php │ ├── ManagesAccountLink.php │ └── ManagesAccount.php ├── Exceptions │ ├── AccountNotFoundException.php │ └── AccountAlreadyExistsException.php ├── Contracts │ └── StripeAccount.php ├── CashierConnectServiceProvider.php └── Billable.php ├── composer.json ├── database └── migrations │ └── 2020_12_01_000001_create_account_columns.php ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | /vendor 3 | composer.lock 4 | /phpunit.xml 5 | .phpunit.result.cache 6 | -------------------------------------------------------------------------------- /src/Concerns/ManagesPerson.php: -------------------------------------------------------------------------------- 1 | string('stripe_account_id')->nullable()->index(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | * 28 | * @return void 29 | */ 30 | public function down() 31 | { 32 | Schema::table('users', function (Blueprint $table) { 33 | $table->dropColumn([ 34 | 'stripe_account_id', 35 | ]); 36 | }); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Concerns/ManagesBalance.php: -------------------------------------------------------------------------------- 1 | assertAccountExists(); 27 | 28 | // Create the payload for retrieving balance. 29 | $options = array_merge([ 30 | 'stripe_account' => $this->stripeAccountId(), 31 | ], $this->stripeAccountOptions()); 32 | 33 | return Balance::retrieve($options); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Marius "ExpDev07" Richardsen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Contracts/StripeAccount.php: -------------------------------------------------------------------------------- 1 | registerMigrations(); 25 | $this->registerPublishing(); 26 | } 27 | 28 | /** 29 | * Register the package migrations. 30 | * 31 | * @return void 32 | */ 33 | protected function registerMigrations() 34 | { 35 | if (Cashier::$runsMigrations && $this->app->runningInConsole()) { 36 | $this->loadMigrationsFrom(__DIR__.'/../database/migrations'); 37 | } 38 | } 39 | 40 | /** 41 | * Register the package's publishable resources. 42 | * 43 | * @return void 44 | */ 45 | protected function registerPublishing() 46 | { 47 | if ($this->app->runningInConsole()) { 48 | $this->publishes([ 49 | __DIR__.'/../database/migrations' => $this->app->databasePath('migrations'), 50 | ], 'cashier-connect-migrations'); 51 | } 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/Concerns/ManagesPayout.php: -------------------------------------------------------------------------------- 1 | assertAccountExists(); 33 | 34 | // Create the payload for payout. 35 | $options = array_merge($options, [ 36 | 'amount' => $amount, 37 | 'currency' => Str::lower($currency), 38 | 'arrival_date' => $arrival->timestamp, 39 | ]); 40 | 41 | return Payout::create($options, $this->stripeAccountOptions([], true)); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/Billable.php: -------------------------------------------------------------------------------- 1 | hasStripeAccountId()) { 41 | $options['stripe_account'] = $this->stripeAccountId(); 42 | } 43 | 44 | // Workaround for Cashier 12.x 45 | if (version_compare(Cashier::VERSION, '12.15.0', '<=')) { 46 | return array_merge(Cashier::stripeOptions($options)); 47 | } 48 | 49 | $stripeOptions = Cashier::stripe($options); 50 | 51 | return array_merge($options, [ 52 | 'api_key' => $stripeOptions->getApiKey() 53 | ]); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/Concerns/ManagesTransfer.php: -------------------------------------------------------------------------------- 1 | assertAccountExists(); 32 | 33 | // Create payload for the transfer. 34 | $options = array_merge([ 35 | 'destination' => $this->stripeAccountId(), 36 | 'amount' => $amount, 37 | 'currency' => Str::lower($currency), 38 | ], $options); 39 | 40 | return Transfer::create($options, $this->stripeAccountOptions()); 41 | } 42 | 43 | /** 44 | * Reverses a transfer back to the Connect Platform. This means the Stripe account will 45 | * 46 | * @param Transfer $transfer The transfer to reverse. 47 | * @param bool $refundFee Whether to refund the application fee too. 48 | * @param int|null $amount The amount to reverse. 49 | * @param array $options Any additional options. 50 | * @return TransferReversal 51 | * @throws AccountNotFoundException|ApiErrorException 52 | */ 53 | public function reverseTransferFromStripeAccount(Transfer $transfer, $refundFee = false, ?int $amount = null, array $options = []): TransferReversal 54 | { 55 | $this->assertAccountExists(); 56 | 57 | // Create payload for the transfer reversal. 58 | $options = array_merge([ 59 | 'amount' => $amount, 60 | 'refund_application_fee' => $refundFee, 61 | ], $options); 62 | 63 | return Transfer::createReversal($transfer->id, $options, $this->stripeAccountOptions()); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/Concerns/ManagesAccountLink.php: -------------------------------------------------------------------------------- 1 | assertAccountExists(); 31 | 32 | $options = array_merge([ 33 | 'type' => $type, 34 | 'account' => $this->stripeAccountId(), 35 | ], $options); 36 | 37 | return AccountLink::create($options, $this->stripeAccountOptions())->url; 38 | } 39 | 40 | /** 41 | * Generates a redirect response to the account link URL for Stripe. 42 | * 43 | * @param $type 44 | * @param $options 45 | * @return RedirectResponse 46 | * @throws AccountNotFoundException|ApiErrorException 47 | */ 48 | public function redirectToAccountLink(string $type, array $options = []): RedirectResponse 49 | { 50 | return new RedirectResponse($this->accountLinkUrl($type, $options)); 51 | } 52 | 53 | /** 54 | * Gets an URL for Stripe account onboarding. 55 | * 56 | * @param $return_url 57 | * @param $refresh_url 58 | * @param $options 59 | * @return string 60 | * @throws AccountNotFoundException|ApiErrorException 61 | */ 62 | public function accountOnboardingUrl(string $return_url, string $refresh_url, array $options = []): string 63 | { 64 | $options = array_merge([ 65 | 'return_url' => $return_url, 66 | 'refresh_url' => $refresh_url, 67 | ], $options); 68 | 69 | return $this->accountLinkUrl('account_onboarding', $options); 70 | } 71 | 72 | /** 73 | * Generates a redirect response to the account onboarding URL for Stripe. 74 | * 75 | * @param $return_url 76 | * @param $refresh_url 77 | * @param $options 78 | * @return RedirectResponse 79 | * @throws AccountNotFoundException|ApiErrorException 80 | */ 81 | public function redirectToAccountOnboarding(string $return_url, string $refresh_url, array $options= []) 82 | { 83 | return new RedirectResponse($this->accountOnboardingUrl($return_url, $refresh_url, $options)); 84 | } 85 | 86 | /** 87 | * Gets the Stripe account dashboard login URL. 88 | * 89 | * @param $options 90 | * @return string 91 | * @throws AccountNotFoundException|ApiErrorException 92 | */ 93 | public function accountDashboardUrl(array $options = []): ?string 94 | { 95 | $this->assertAccountExists(); 96 | 97 | // Can only create login link if details has been submitted. 98 | return $this->hasSubmittedAccountDetails() 99 | ? Account::createLoginLink($this->stripeAccountId(), $options, $this->stripeAccountOptions())->url 100 | : null; 101 | } 102 | 103 | /** 104 | * Generates a redirect response to the account dashboard login for Stripe. 105 | * 106 | * @return RedirectResponse 107 | * @throws AccountNotFoundException|ApiErrorException 108 | */ 109 | public function redirectToAccountDashboard(): RedirectResponse 110 | { 111 | return new RedirectResponse($this->accountDashboardUrl()); 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /src/Concerns/ManagesAccount.php: -------------------------------------------------------------------------------- 1 | stripe_account_id; 27 | } 28 | 29 | /** 30 | * Determine if the entity has a Stripe account ID. 31 | * 32 | * @return bool 33 | */ 34 | public function hasStripeAccountId(): bool 35 | { 36 | return ! is_null($this->stripeAccountId()); 37 | } 38 | 39 | /** 40 | * Gets the account email to use for Stripe. 41 | * 42 | * @return string 43 | */ 44 | public function stripeAccountEmail(): string 45 | { 46 | return $this->email; 47 | } 48 | 49 | /** 50 | * Determine if the entity has a Stripe account ID and throw an exception if not. 51 | * 52 | * @return void 53 | * @throws AccountNotFoundException 54 | */ 55 | protected function assertAccountExists(): void 56 | { 57 | if (! $this->hasStripeAccountId()) { 58 | throw new AccountNotFoundException('Stripe account does not exist.'); 59 | } 60 | } 61 | 62 | /** 63 | * Checks if the model has submitted their details. 64 | * 65 | * @return bool 66 | * @throws AccountNotFoundException|ApiErrorException 67 | */ 68 | public function hasSubmittedAccountDetails(): bool 69 | { 70 | $this->assertAccountExists(); 71 | 72 | return $this->asStripeAccount()->details_submitted; 73 | } 74 | 75 | /** 76 | * Checks if the model has completed on-boarding process by having submitted their details. 77 | * 78 | * @return bool 79 | * @throws AccountNotFoundException|ApiErrorException 80 | */ 81 | public function hasCompletedOnboarding() 82 | { 83 | return $this->hasSubmittedAccountDetails(); 84 | } 85 | 86 | /** 87 | * Get the Stripe account for the model. 88 | * 89 | * @return Account 90 | * @throws AccountNotFoundException|ApiErrorException 91 | */ 92 | public function asStripeAccount(): Account 93 | { 94 | $this->assertAccountExists(); 95 | 96 | return Account::retrieve($this->stripeAccountId(), $this->stripeAccountOptions()); 97 | } 98 | 99 | /** 100 | * Create a Stripe account for the given model. 101 | * 102 | * @param string $type 103 | * @param array $options 104 | * @return Account 105 | * @throws AccountAlreadyExistsException|ApiErrorException 106 | */ 107 | public function createAsStripeAccount(string $type = 'express', array $options = []): Account 108 | { 109 | // Check if model already has a connected Stripe account. 110 | if ($this->hasStripeAccountId()) { 111 | throw new AccountAlreadyExistsException('Stripe account already exists.'); 112 | } 113 | 114 | // Create payload. 115 | $options = array_merge([ 116 | 'type' => $type, 117 | 'email' => $this->stripeAccountEmail(), 118 | ], $options); 119 | 120 | // Create account. 121 | $account = Account::create($options, $this->stripeAccountOptions()); 122 | 123 | // Save the id. 124 | $this->stripe_account_id = $account->id; 125 | $this->save(); 126 | 127 | return $account; 128 | } 129 | 130 | /** 131 | * Get the Stripe account instance for the current model or create one. 132 | * 133 | * @param string $type 134 | * @param array $options 135 | * @return Account 136 | * @throws AccountNotFoundException|AccountAlreadyExistsException|ApiErrorException 137 | */ 138 | public function createOrGetStripeAccount(string $type = 'express', array $options = []): Account 139 | { 140 | // Return Stripe account if exists, otherwise create new. 141 | return $this->hasStripeAccountId() 142 | ? $this->asStripeAccount() 143 | : $this->createAsStripeAccount($type, $options); 144 | } 145 | 146 | /** 147 | * Deletes the Stripe account for the model. 148 | * 149 | * @return Account 150 | * @throws AccountNotFoundException|ApiErrorException 151 | */ 152 | public function deleteStripeAccount(): Account 153 | { 154 | $this->assertAccountExists(); 155 | 156 | // Process account delete. 157 | $account = $this->asStripeAccount(); 158 | $account->delete(); 159 | 160 | // Wipe account id reference from model. 161 | $this->stripe_account_id = null; 162 | $this->save(); 163 | 164 | return $account; 165 | } 166 | 167 | /** 168 | * Deletes the Stripe account if it exists and re-creates it. 169 | * 170 | * @param string $type 171 | * @param array $options 172 | * @return Account 173 | * @throws AccountNotFoundException|AccountAlreadyExistsException|ApiErrorException 174 | */ 175 | public function deleteAndCreateStripeAccount(string $type = 'express', array $options = []): Account 176 | { 177 | // Delete account if it already exists. 178 | if ($this->hasStripeAccountId()) { 179 | $this->deleteStripeAccount(); 180 | } 181 | 182 | // Create account and return it. 183 | return $this->createAsStripeAccount($type, $options); 184 | } 185 | 186 | /** 187 | * Update the underlying Stripe account information for the model. 188 | * 189 | * @param array $options 190 | * @return Account 191 | * @throws AccountNotFoundException|ApiErrorException 192 | */ 193 | public function updateStripeAccount(array $options = []): Account 194 | { 195 | $this->assertAccountExists(); 196 | 197 | return Account::update($this->stripeAccountId(), $options, $this->stripeAccountOptions()); 198 | } 199 | 200 | } 201 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | laravel-cashier-stripe-connect 3 |

4 | 5 |

6 | Total Downloads 7 | Latest Stable Version 8 | License 9 |

10 | 11 | > ## Newer Package Available 12 | > A more comprehensive package is now available, built upon this package so can be upgraded to from this package, it offers multi-model, multi-tenancy support, UUID support and the following features: 13 | > * Multi Model support - Previously only supported the User model, now any model can have the Connect Billable trait added to it and immediately inherit functionality. 14 | > * Tenancy for Laravel Support (Multi Tenant SaaS Plugin) 15 | > * Manage connected account onboarding 16 | > * Direct Charges 17 | > * Destination Charges 18 | > * Connected account customer management (Direct Customers) 19 | > * Connected account payment method management 20 | > * Connected account subscriptions ( Direct Subscriptions ) 21 | > * Connected account product & price management 22 | > * Connect Webhook Support (On behalf of connected accounts) 23 | > * Connected Account Apple Pay Domain Registering 24 | > 25 | > ### [Click here to access the new package](https://github.com/l4nos/laravel-cashier-stripe-connect) 26 | > As a result of the new package, this package will no longer be maintained. 27 | 28 |

29 | 30 | Buy Me a Coffee at ko-fi.com 31 | 32 |

33 | 34 | > 💲 Adds Stripe Connect functionality to Laravel's main billing package, Cashier. Simply works as a drop-in on top of Cashier, with no extra configuration. 35 | 36 | ## Installation 37 | 38 | 1. Enable Stripe Connect in your [dashboard settings](https://dashboard.stripe.com/settings). 39 | 2. Install Cashier: ``composer require laravel/cashier``. 40 | 3. Install package: ``composer require expdev07/laravel-cashier-stripe-connect``. 41 | 4. Run migrations: ``php artisan migrate``. 42 | 5. Configure Stripe keys for Cashier: [Cashier Docs](https://laravel.com/docs/9.x/billing#api-keys). 43 | 44 | **Note:** the package will not work as intended if you do not install [Laravel's official Cashier package](https://laravel.com/docs/8.x/billing) first. 45 | 46 | ## Use 47 | 48 | The library builds on the official [Cashier](https://laravel.com/docs/8.x/billing) library, so getting up and started is a breeze. 49 | 50 | ### Setup model 51 | 52 | Add the ``Billable`` traits to your model. You can use them individually or together. You can also create your own ``Billable`` trait and put them together there. In 53 | addition, the model should also implement the ``StripeAccount`` interface. 54 | 55 | ```php 56 | namespace App\Models; 57 | 58 | use Illuminate\Foundation\Auth\User as Authenticatable; 59 | use ExpDev07\CashierConnect\Contracts\StripeAccount; 60 | use Laravel\Cashier\Billable as CashierBillable; 61 | use ExpDev07\CashierConnect\Billable as ConnectBillable; 62 | 63 | class User extends Authenticatable implements StripeAccount 64 | { 65 | use CashierBillable; 66 | use ConnectBillable; 67 | 68 | /// 69 | 70 | } 71 | ``` 72 | 73 | ### Create controller 74 | 75 | Create a controller to manage on-boarding process. The example below registers an Express account for the user. 76 | 77 | ```php 78 | namespace App\Http\Controllers; 79 | 80 | use App\Http\Controllers\Controller; 81 | use App\Models\User; 82 | use Illuminate\Http\RedirectResponse; 83 | use Illuminate\Http\Request; 84 | use URL; 85 | 86 | class StripeController extends Controller 87 | { 88 | 89 | /** 90 | * Creates an onboarding link and redirects the user there. 91 | * 92 | * @param Request $request 93 | * @return RedirectResponse 94 | */ 95 | public function board(Request $request): RedirectResponse 96 | { 97 | return $this->handleBoardingRedirect($request->user()); 98 | } 99 | 100 | /** 101 | * Handles returning from completing the onboarding process. 102 | * 103 | * @param Request $request 104 | * @return RedirectResponse 105 | */ 106 | public function returning(Request $request): RedirectResponse 107 | { 108 | return $this->handleBoardingRedirect($request->user()); 109 | } 110 | 111 | /** 112 | * Handles refreshing of onboarding process. 113 | * 114 | * @param Request $request 115 | * @return RedirectResponse 116 | */ 117 | public function refresh(Request $request): RedirectResponse 118 | { 119 | return $this->handleBoardingRedirect($request->user()); 120 | } 121 | 122 | /** 123 | * Handles the redirection logic of Stripe onboarding for the given user. Will 124 | * create account and redirect user to onboarding process or redirect to account 125 | * dashboard if they have already completed the process. 126 | * 127 | * @param User $user 128 | * @return RedirectResponse 129 | */ 130 | private function handleBoardingRedirect(User $user): RedirectResponse 131 | { 132 | // Redirect to dashboard if onboarding is already completed. 133 | if ($user->hasStripeAccountId() && $user->hasCompletedOnboarding()) { 134 | return $user->redirectToAccountDashboard(); 135 | } 136 | 137 | // Delete account if already exists and create new express account with 138 | // weekly payouts. 139 | $user->deleteAndCreateStripeAccount('express', [ 140 | 'settings' => [ 141 | 'payouts' => [ 142 | 'schedule' => [ 143 | 'interval' => 'weekly', 144 | 'weekly_anchor' => 'friday', 145 | ] 146 | ] 147 | ] 148 | ]); 149 | 150 | // Redirect to Stripe account onboarding, with return and refresh url, otherwise. 151 | return $user->redirectToAccountOnboarding( 152 | URL::to('/api/stripe/return?api_token=' . $user->api_token), 153 | URL::to('/api/stripe/refresh?api_token=' . $user->api_token) 154 | ); 155 | } 156 | 157 | } 158 | ``` 159 | 160 | ## Example 161 | 162 | ```php 163 | // Get user. This user has added the Billable trait and implements StripeAccount. 164 | $user = User::query()->find(1); 165 | 166 | // Transfer 10 USD to the user. 167 | $user->transferToStripeAccount(1000); 168 | 169 | // Payout 5 dollars to the user's bank account, which will arrive in 1 week. 170 | $user->payoutStripeAccount(500, Date::now()->addWeek()); 171 | 172 | ``` 173 | 174 | ## License 175 | 176 | Please refer to [LICENSE.md](https://github.com/ExpDev07/laravel-cashier-stripe-connect/blob/main/LICENSE) for this project's license. 177 | 178 | ## Contributors 179 | 180 | This list only contains some of the most notable contributors. For the full list, refer to [GitHub's contributors graph](https://github.com/ExpDev07/laravel-cashier-stripe-connect/graphs/contributors). 181 | * ExpDev07 (Marius) - creator and maintainer. 182 | * Haytam Bakouane [(hbakouane)](https://github.com/hbakouane) - contributor. 183 | 184 | ## Thanks to 185 | 186 | [Taylor Otwell](https://twitter.com/taylorotwell) for his amazing framework and [all the contributors of Cashier](https://github.com/laravel/cashier-stripe/graphs/contributors). 187 | --------------------------------------------------------------------------------