Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Spark Overview

Spark Overview

An overview on what Laravel Spark is, why you would choose it, and how to get started building your next SaaS app.

Patrick Guevara

November 03, 2016
Tweet

More Decks by Patrick Guevara

Other Decks in Programming

Transcript

  1. INTRODUCTION PATRICK GUEVARA ▸ Back-end developer at Metric Loop ▸

    We ❤ Laravel ▸ Helix is based on Spark ▸ Moose
  2. WHAT IS SPARK? OPINIONATED BOILERPLATE ▸ Software-as-a-Service boilerplate ▸ Built

    on Laravel 5.3 ▸ $99/site ($299 for unlimited sites)* *as of October 31, 2016
  3. WHAT IS SPARK? COSTS MONEY ▸ Built ground-up by Taylor

    Otwell (creator of Laravel) ▸ Could arguably save you weeks+ of dev time
  4. DOCUMENTATION GETTING STARTED ▸ via Spark Installer (Mac or Linux)

    composer global require laravel/installer cd ~/Code git clone https://github.com/laravel/spark-installer.git composer install export PATH=$PATH:~/Code/spark-installer spark register <token-value> spark new project-name
  5. DOCUMENTATION GETTING STARTED ▸ via Composer Install (Mac, Linux, Windows)

    // terminal laravel new project-name // composer.json “require” : { // ... “laravel/spark”: “~3.0”, “laravel/cashier”: “~7.0” }, // ... "repositories": [ { "type": "composer", "url": "https://spark-satis.laravel.com" } ], // config/app.php ‘providers’ => [ // ... Laravel\Spark\Providers\SparkServiceProvider::class, Laravel\Cashier\CashierServiceProvider::class, ]; // terminal php artisan spark:install —force // config/app.php ‘providers’ => [ // ... App\Providers\SparkServiceProvider:class, ];
  6. DOCUMENTATION ANNOUNCEMENTS ▸ Announcements to your Users ▸ Works without

    any extra configuration ▸ Fires AnnouncementCreated event
  7. DOCUMENTATION API: GET STARTED ▸ To enable API support… //

    SparkServiceProvider.php protected $usesApi = true;
  8. DOCUMENTATION API: ABILITIES ▸ Define Abilities // SparkServiceProvider::booted() Spark::tokensCan([ ‘read-servers’

    => ‘Read Servers’, ‘create-servers’ => ‘Create Servers’, ‘delete-servers’ => ‘Delete Servers’, ]); ▸ Check Abilities if (Auth::user()->token()->can(‘read-servers’)) { // Secret readonly stuff }
  9. DOCUMENTATION API: ROUTING ▸ API routes file // RouteServiceProvider.php protected

    function mapApiRoutes(Router $router) { $router->group([ ‘namespace’ => $this->namespace, ‘middleware’ => ‘api’, ‘prefix’ => ‘api’, ], function ($router) { require base_path(‘routes/api.php’); }); }
  10. DOCUMENTATION API: API-DRIVEN APPS ▸ Users can create API tokens

    ▸ Append ?api_token=<SomeRandomToken> to query string ▸ Don’t forget auth:api ! Route::get(‘/api/users’, [ ‘middleware’ => ‘auth:api’ ], function () { // List of users requiring API auth });
  11. DOCUMENTATION API: SHARING API WITH JAVASCRIPT FRONTEND ▸ Caveat! Requires

    Vue.js ▸ Transient, short-lived API tokens ▸ Automatically refreshed this.$http.get(‘api/users’) .then(response => { this.users = response.data; });
  12. DOCUMENTATION BILLING ▸ Configuration ▸ User / Team Billing ▸

    Constrain Access to Plans ▸ Check Subscription Status ▸ No Credit Card Upfront / Require Credit Card Upfront ▸ Promotions ▸ Events
  13. DOCUMENTATION BILLING: PROVIDER CONFIGURATION ▸ Stripe or Braintree ▸ .env

    ▸ Webhooks point to /webhook/stripe or /webhook/braintree STRIPE_KEY= STRIPE_SECRET= BRAINTREE_ENV= BRAINTREE_MODEL= BRAINTREE_MERCHANT_ID= BRAINTREE_PUBLIC_KEY= BRAINTREE_SECRET_KEY=
  14. DOCUMENTATION BILLING: APP CONFIGURATION ▸ Create plans in Stripe then…

    // SparkServiceProvider::booted() Spark::plan(‘Pro’, ‘monthly-pro’) ->price(20) ->features([‘Feature 1’, ‘Feature 2’, ‘Feature 3’]); Spark::plan(‘Pro’, ‘yearly-pro’) ->price(100) ->yearly() ->features([‘Feature 1’, ’Feature 2’, ‘Feature 3’]);
  15. DOCUMENTATION BILLING: TEAMS ▸ Use the installer: spark new project-name

    --team-billing ▸ User/Team/Both // SparkServiceProvider::booted() Spark::teamPlan(‘Basic’, ‘team-basic’) ->price(10) ->maxTeamMembers(5) ->features([‘Five Team Members’, ‘Feature 2’, ‘Feature 3’])
  16. DOCUMENTATION BILLING: ARCHIVING PLANS ▸ Archive plans without screwing existing

    customers // SparkServiceProvider::booted() Spark::plan(‘Pro’, ‘yearly-pro’) ->archived() ->price(100) ->yearly() ->features([ /* features */ ]);
  17. DOCUMENTATION BILLING: CONSTRAIN ACCESS TO PLANS // SparkServiceProvider::booted() Spark::checkPlanEligibilityUsing(function ($user,

    $plan) { if ($plan->name == ‘pro’ && count($user->todos) > 20) { return false; } }); Spark::checkPlanEligibilityUsing(function ($user, $plan) { if ($plan->name == ‘pro’ && count($user->todos) > 20) { throw IneligibleForPlan::because(‘Too many todos!’); } }); Spark::checkPlanEligibilityUsing(‘EligibilityChecker@handle’);
  18. DOCUMENTATION BILLING: CHECK SUBSCRIPTION STATUS // routes/web.php Route::get(‘/paid’, [ ‘middleware’

    => ‘subscribed’ ], function () { // User must be subscribed to see this. }); Route::get(‘/team-paid’, [ ‘middleware’ => ‘teamSubscribed’ ], function () { // Team must be subscribed to see this. });
  19. DOCUMENTATION BILLING: NO CARD UP FRONT ▸ Default, using Stripe

    // SparkServiceProvider::booted() Spark::useStripe()->noCardUpFront()->trialDays(10); Spark::useStripe()->noCardUpFront()->teamTrialDays(10); ▸ Checking “Generic Trial” $user->onGenericTrial(); $team->onGenericTrial();
  20. DOCUMENTATION BILLING: REQUIRE CARD UP FRONT ▸ Remove noCardUpFront() and

    trialDays() from plan definition in SparkServiceProvider::booted() method ▸ Define plan // SparkServiceProvider::booted() Spark::plan(‘Basic’, ‘team-basic’) ->price(10) ->trialDays(10) ->features([ /* features */ ]); ▸ Warning! This behavior might be annoying for customers.
  21. DOCUMENTATION BILLING: EVENTS ▸ Namespace Laravel\Spark\Events PaymentMethod\BillingAddressUpdated PaymentMethod\VatIdUpdated Subscription\SubscriptionCancelled Subscription\SubscriptionUpdated

    Subscription\UserSubscribed // And some team-specific events... Teams\Subscription\SubscriptionCancelled Teams\Subscription\SubscriptionUpdated Teams\Subscription\TeamSubscribed
  22. DOCUMENTATION DEVELOPER KIOSK: CONFIGURATION ▸ Default middleware Route::get(‘/dev’, [‘middleware’ =>

    ‘dev’], function () { // Developers only... }); ▸ Who are the developers? // SparkServiceProvider protected $developers = [ ‘[email protected]’, ];
  23. DOCUMENTATION DEVELOPER KIOSK: METRICS ▸ Performance / Revenue metrics ▸

    Monthly recurring revenue ▸ Total revenue ▸ New registrations ▸ Number of subscriptions per plan
  24. DOCUMENTATION DEVELOPER KIOSK: METRICS ▸ Key Performance Indicators // app/Console/Kernel.php

    protected function schedule(Schedule $schedule) { // All your other commands... $schedule->command(‘spark:kpi’)->dailyAt(’23:55’); }
  25. DOCUMENTATION NOTIFICATIONS ▸ Inject the repository use Laravel\Spark\Contracts\Repositories\NotificationRepository; public function

    __construct(NotificationRepository ($notifications) { $this->notifications = $notifications; } ▸ Then create a notification $this->notifications->create($user, [ ‘icon’ => ‘fa-users’, ‘body’ => ‘A team member completed a task!’, ‘action_text’ => ‘View Task’, ‘action_url’ => ‘/link/to/task’ ]); // This fires a Laravel\Spark\Events\NotificationCreated event
  26. DOCUMENTATION NOTIFICATIONS: LARAVEL 5.3 ▸ First, create a new notification

    class ▸ Then, set the SparkChannel on the notification use Laravel\Spark\Notifications\SparkChannel; public function via($notifiable) { return [SparkChannel::class]; } php artisan make:notification TaskCompleted
  27. DOCUMENTATION NOTIFICATIONS: LARAVEL 5.3 ▸ Define the toSpark() method use

    Laravel\Spark\Notifications\SparkNotification; public function toSpark($notifiable) { return (new SparkNotification) ->action(‘View Task’, ‘/link/to/task’) ->icon(‘fa-users’) ->body(‘A team member completed a task!’); }
  28. DOCUMENTATION NOTIFICATIONS: LARAVEL 5.3 // Notify a single user... $user->notify(new

    TaskCompleted); Notification::send($user, new TaskCompleted); // Notify all team members... $team->notify(new TaskCompleted); Notification::send($team, new TaskCompleted);
  29. DOCUMENTATION SUPPORT REQUESTS ▸ Caveat! Requires Vue.js // SparkServiceProvider protected

    $sendSupportEmailsTo = ‘[email protected]’; ▸ Customize destination // SparkServiceProvider Spark::swap(‘SendSupportEmail@handle’, function (array $data) { // $data[‘from’]; // $data[‘subject’]; // $data[‘message’]; });
  30. DOCUMENTATION TEAMS ▸ Using CanJoinTeams ▸ Teams by Path ▸

    Roles ▸ Teams Without Team Billing ▸ Events
  31. DOCUMENTATION TEAMS // App/User.php <?php namespace App; use Laravel\Spark\CanJoinTeams; use

    Laravel\Spark\User as SparkUser; class User extends SparkUser { use CanJoinTeams; } // SparkServiceProvider::booted() Spark::noAdditionalTeams(); // SparkServiceProvider::register() Spark::referToTeamAs(‘band’);
  32. DOCUMENTATION TEAMS: CANJOINTEAMS TRAIT foreach ($user->teams as $team) { //

    Iterate over each team... } if ($user->hasTeams()) { // The user belongs to at least one team... } if ($user->onTeam($team)) { // The user belongs to the given team... } if ($user->ownsTeam($team)) { // The user owns the given team... } foreach ($user->ownedTeams as $team) { // Iterate through all teams the user owns... } foreach ($user->invitations as $invitation) { // Iterate through all pending invitations for user... }
  33. DOCUMENTATION TEAMS: CURRENT TEAM ▸ Use the currentTeam property ▸

    Is null if User does not belong to a Team ▸ Default behavior is to issue warning via VerifyUserHasTeam middleware echo $user->currentTeam->name; if ($user->currentTeam) { // Belongs to a team }
  34. DOCUMENTATION TEAMS: BY PATH (GITHUB STYLE) // SparkServiceProvider::booted() Spark::identifyTeamsByPath(); //

    routes/web.php Route::get(‘/{team_slug}/projects’, ‘ProjectController@index’); ▸ https://your-spark-app.com/laravel-atx/
  35. DOCUMENTATION TEAMS: BY PATH (SUBDOMAIN) ▸ https://laravel-atx.your-spark-app.com/ ▸ ¯\_(ツ)_/¯ ▸

    Wildcard subdomain not supported in Laravel Valet… yet. ▸ ~/.composer/vendor/laravel/valet/server.php
  36. DOCUMENTATION TEAMS: ROLES ▸ Defaults: owner and member ▸ Define

    more roles // SparkServiceProvider::booted() Spark::useRoles([ ‘member’ => ‘Member’, ‘vip’ => ‘VIP’, ]); // Then you can do this if ($user->roleOn($team) === ‘vip’) { // This user is a VIP on the given team... }
  37. DOCUMENTATION TEAMS: WITHOUT TEAM BILLING ▸ Add CanJoinTeams to User

    model ▸ Define plan instead of teamPlan // SparkServiceProvider::booted() Spark::plan(‘Pro’, ‘yearly-pro’) ->price(100) ->yearly() ->maxTeams(5);
  38. DOCUMENTATION TEAMS: EVENTS ▸ Namespace Laravel\Spark\Events Teams\DeletingTeam Teams\TeamCreated Teams\TeamDeleted Teams\TeamMemberAdded

    Teams\TeamMemberRemoved Teams\UserInvitedToTeam Teams\Subscription\SubscriptionCancelled Teams\Subscription\SubscriptionUpdated Teams\Subscription\TeamSubscribed
  39. DOCUMENTATION 2FA: USAGE ▸ Uses Authy by default ▸ Configure

    AUTHY_SECRET in .env // SparkServiceProvider::booted() Spark::useTwoFactorAuth();
  40. DOCUMENTATION 2FA: CUSTOM PROVIDER ▸ Use Spark::swap() // SparkServiceProvider::booted() Spark::swap(‘EnableTwoFactorAuth’,

    function ($user, $country, $phone) { // }); Spark::swap(‘DisableTwoFactorAuth’, function ($user) { // }); Spark::swap(‘VerifyTwoFactorAuthToken’, function ($user, $token) { // });
  41. DOCUMENTATION 2FA: CUSTOM PROVIDER ▸ Define a EnableTwoFactorAuthUsingProvider class //

    SparkServiceProvider::booted() Spark::swap(‘EnableTwoFactorAuth’, ’EnableTwoFactorAuthUsingGoogle@handle’); ▸ Examples of provider implementation included EnableTwoFactorAuthUsingAuthy DisableTwoFactorAuthUsingAuthy VerifyTwoFactorAuthTokensUsingAuthy
  42. BUT WHY SPARK? IT’S NOT A GOOD FIT FOR… ▸

    An existing SaaS app / non-SaaS app ▸ Other payment providers ▸ Other JS frameworks (?)
  43. BUT WHY SPARK? IT’S A GOOD FIT FOR… ▸ A

    fresh SaaS app ▸ Stripe/Braintree ▸ Vue.js ▸ Source Diving!
  44. Q&A