Support upgrading of user's plan through Stripe
MODIFY
.env.example
.env.example
@@ -37,3 +37,7 @@ SUGGESTION_BOX_ENABLED=false
PLANS_ENABLED=false
PLANS_STANDARD_MAXIMUM_SPACES=2
PLANS_PREMIUM_MAXIMUM_SPACES=null
+
+STRIPE_KEY=
+STRIPE_SECRET=
+STRIPE_PREMIUM_PLAN_PRICE_ID=

MODIFY
.env.travis
.env.travis
@@ -37,3 +37,7 @@ SUGGESTION_BOX_ENABLED=false
PLANS_ENABLED=true
PLANS_STANDARD_MAXIMUM_SPACES=2
PLANS_PREMIUM_MAXIMUM_SPACES=null
+
+STRIPE_KEY=
+STRIPE_SECRET=
+STRIPE_PREMIUM_PLAN_PRICE_ID=

ADD
app/Actions/CreateStripeCheckoutAction.php
+<?php
+
+namespace App\Actions;
+
+use App\Exceptions\UserNotFoundException;
+use App\Exceptions\UserStripelessException;
+use App\Models\User;
+use Stripe\StripeClient;
+
+class CreateStripeCheckoutAction
+{
+ public function execute(int $userId): string
+ {
+ $user = User::find($userId);
+
+ if (!$user) {
+ throw new UserNotFoundException();
+ }
+
+ if (!$user->stripe_customer_id) {
+ throw new UserStripelessException();
+ }
+
+ $stripe = new StripeClient(config('stripe.secret'));
+
+ $stripeCheckout = $stripe->checkout->sessions->create([
+ 'success_url' => route('settings.billing'),
+ 'cancel_url' => route('settings.billing'),
+ 'mode' => 'subscription',
+ 'payment_method_types' => ['card'],
+ 'customer' => $user->stripe_customer_id,
+ 'line_items' => [
+ [
+ 'price' => config('stripe.premium_plan_price_id'),
+ 'quantity' => 1
+ ]
+ ]
+ ]);
+
+ return $stripeCheckout->id;
+ }
+}

ADD
app/Actions/CreateStripeCustomerAction.php
+<?php
+
+namespace App\Actions;
+
+use App\Exceptions\UserNotFoundException;
+use App\Models\User;
+use Stripe\StripeClient;
+
+class CreateStripeCustomerAction
+{
+ public function execute(int $userId): void
+ {
+ $user = User::find($userId);
+
+ if (!$user) {
+ throw new UserNotFoundException();
+ }
+
+ $stripe = new StripeClient(config('stripe.secret'));
+
+ $customer = $stripe->customers->create([
+ 'name' => $user->name,
+ 'email' => $user->email
+ ]);
+
+ $user->update([
+ 'stripe_customer_id' => $customer->id
+ ]);
+ }
+}

ADD
app/Exceptions/UserStripelessException.php
+<?php
+
+namespace App\Exceptions;
+
+use Exception;
+
+class UserStripelessException extends Exception
+{
+ //
+}

MODIFY
app/Http/Controllers/SettingsController.php
app/Http/Controllers/SettingsController.php
namespace App\Http\Controllers;
+use App\Actions\CreateStripeCheckoutAction;
+use App\Actions\CreateStripeCustomerAction;
use Illuminate\Http\Request;
use App\Mail\PasswordChanged;
use App\Models\Currency;
@@ -120,4 +122,17 @@ class SettingsController extends Controller
return view('settings.billing', ['user' => $user]);
}
+
+ public function postUpgrade(Request $request)
+ {
+ $user = $request->user();
+
+ if (!$user->stripe_customer_id) {
+ (new CreateStripeCustomerAction())->execute($user->id);
+ }
+
+ $stripeCheckoutId = (new CreateStripeCheckoutAction())->execute($user->id);
+
+ return view('stripe_checkout_redirect', ['stripeCheckoutId' => $stripeCheckoutId]);
+ }
}

MODIFY
app/Models/User.php
app/Models/User.php
@@ -15,6 +15,7 @@ class User extends Authenticatable
'password',
'verification_token',
'last_verification_mail_sent_at',
+ 'stripe_customer_id',
'plan'
];

MODIFY
composer.json
composer.json
"laravel/tinker": "^2.0",
"martinlindhe/laravel-vue-i18n-generator": "^0.1.39",
"nesbot/carbon": "^2.32",
+ "stripe/stripe-php": "^7.44",
"symfony/dom-crawler": "^5.1",
"symfony/process": "^5.0"
},

MODIFY
composer.lock
composer.lock
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "eecd2d8d9d6ba271f2f66cc8b02e6712",
+ "content-hash": "89086ecf8de284c17ef7eea0461c77c2",
"packages": [
{
"name": "asm89/stack-cors",
],
"time": "2020-03-29T20:13:32+00:00"
},
+ {
+ "name": "stripe/stripe-php",
+ "version": "v7.44.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/stripe/stripe-php.git",
+ "reference": "b39ccc77358fbe0d462e6cc89ac01b7a0c896f35"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/stripe/stripe-php/zipball/b39ccc77358fbe0d462e6cc89ac01b7a0c896f35",
+ "reference": "b39ccc77358fbe0d462e6cc89ac01b7a0c896f35",
+ "shasum": ""
+ },
+ "require": {
+ "ext-curl": "*",
+ "ext-json": "*",
+ "ext-mbstring": "*",
+ "php": ">=5.6.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "2.16.1",
+ "php-coveralls/php-coveralls": "^2.1",
+ "phpunit/phpunit": "^5.7",
+ "squizlabs/php_codesniffer": "^3.3",
+ "symfony/process": "~3.4"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Stripe\\": "lib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Stripe and contributors",
+ "homepage": "https://github.com/stripe/stripe-php/contributors"
+ }
+ ],
+ "description": "Stripe PHP Library",
+ "homepage": "https://stripe.com/",
+ "keywords": [
+ "api",
+ "payment processing",
+ "stripe"
+ ],
+ "time": "2020-07-20T21:25:50+00:00"
+ },
{
"name": "swiftmailer/swiftmailer",
"version": "v6.2.3",
"platform": {
"php": ">=5.6.4"
},
- "platform-dev": []
+ "platform-dev": [],
+ "plugin-api-version": "1.1.0"
}

ADD
config/stripe.php
+<?php
+
+return [
+ 'key' => env('STRIPE_KEY'),
+ 'secret' => env('STRIPE_SECRET'),
+ 'premium_plan_price_id' => env('STRIPE_PREMIUM_PLAN_PRICE_ID')
+];

MODIFY
resources/views/settings/layout.blade.php
resources/views/settings/layout.blade.php
{{ csrf_field() }}
@yield('settings_body')
</form>
+ @yield('settings_body_formless')
</div>
</div>
</div>

ADD
resources/views/stripe_checkout_redirect.blade.php
+<!DOCTYPE html>
+<html>
+ <body>
+ <script src="https://js.stripe.com/v3"></script>
+ <script>
+ var stripe = Stripe("{{ config('stripe.key') }}");
+
+ stripe.redirectToCheckout({
+ sessionId: '{{ $stripeCheckoutId }}'
+ }).then(function (result) {
+ // If `redirectToCheckout` fails due to a browser or network
+ // error, display the localized error message to your customer
+ // using `result.error.message`.
+ });
+ </script>
+ </body>
+</html>

MODIFY
routes/web.php
routes/web.php
@@ -122,6 +122,7 @@ Route::group(['middleware' => ['auth']], function () {
Route::get('/settings/account', [SettingsController::class, 'getAccount'])->name('account');
Route::get('/settings/preferences', [SettingsController::class, 'getPreferences'])->name('preferences');
Route::get('/settings/billing', [SettingsController::class, 'getBilling'])->name('billing')->middleware('stripe');
+ Route::post('/settings/billing/upgrade', [SettingsController::class, 'postUpgrade'])->name('billing.upgrade')->middleware('stripe');
Route::get('/settings/spaces', [SettingsController::class, 'getSpaces'])->name('spaces.index');
});

Support upgrading of user's plan through Stripe