├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── composer.json ├── phpunit.xml ├── readme.md ├── src ├── Plan.php ├── PlanConfig.php ├── PlanConfigServiceProvider.php ├── functions.php └── plans.php └── tests └── PlanConfigTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.phar 3 | composer.lock 4 | .DS_Store -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 7.0 5 | - 7.1 6 | - 7.2 7 | 8 | before_script: 9 | - composer self-update 10 | - composer install --prefer-source --no-interaction --dev 11 | 12 | script: vendor/bin/phpunit -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | #Changelog 2 | 3 | All Notable changes to this package` will be documented in this file 4 | 5 | ## Version 1.4.1 6 | 7 | - Allow passing a wildcard `*` as the `$key` to return the user's entire plan config along with overrides. 8 | 9 | ## Version 1.4.0 10 | 11 | - Significant refactor. The 2nd argument for the `plan()` helper function now allows for passing a $user object. 12 | - Removed the following methods 13 | - **getPlanOfUser** 14 | - **getUserPlan** 15 | - **getAllowedOverrides** 16 | - **getPlanOverrides** 17 | 18 | ## Version 1.3.0 19 | 20 | ### Added 21 | 22 | - Added new way to override a plan's config using an attribute on the user model. Requires updating app/config/plans.php with new section (see README). 23 | 24 | ## Version 1.2.0 25 | 26 | ### Added 27 | 28 | - Unit tests along with travis ci 29 | - Added optional Facade to use 30 | - Refactored code so its easier to test 31 | - Added new methods -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "seanstewart/plan-config", 3 | "description": "Plan config allows you to easily define attributes and limits for your SaaS application subscription plans.", 4 | "keywords": ["subscription plans", "saas", "plans", "config", "laravel", "laravel5"], 5 | "homepage": "https://github.com/theseanstewart/Plan-Config", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Sean Stewart", 10 | "email": "sean@ekoim.com" 11 | } 12 | ], 13 | "require": { 14 | "php": ">=7.0", 15 | "illuminate/support": "~5.5" 16 | }, 17 | "require-dev": { 18 | "mockery/mockery": "~1.0", 19 | "phpunit/phpunit": "~6.0", 20 | "orchestra/testbench": "^3.2" 21 | }, 22 | "autoload": { 23 | "psr-4": { 24 | "Seanstewart\\PlanConfig\\": "src/" 25 | }, 26 | "files": [ 27 | "src/functions.php" 28 | ] 29 | }, 30 | "extra": { 31 | "laravel": { 32 | "providers": [ 33 | "Seanstewart\\PlanConfig\\PlanConfigServiceProvider" 34 | ], 35 | "aliases": { 36 | "Plan": "Seanstewart\\PlanConfig\\Plan" 37 | } 38 | } 39 | }, 40 | "minimum-stability": "stable" 41 | } 42 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./tests/ 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Plan Config 2 | 3 | This Laravel 5 package makes it easy to manage the rules/limits of your SaaS app subscription plans. 4 | 5 | ## How to install 6 | 7 | Require the package with composer 8 | 9 | ``` 10 | composer require seanstewart/plan-config 11 | ``` 12 | 13 | Laravel 5.5 uses Package Auto-Discovery and it is not necessary to manually add the ServiceProvider. 14 | 15 | ### Laravel 5.5+: 16 | 17 | If you don't use auto-discovery, add the ServiceProvider to the providers array in config/app.php 18 | 19 | ```php 20 | 'providers' => [ 21 | Seanstewart\PlanConfig\PlanConfigServiceProvider::class 22 | ]; 23 | ``` 24 | 25 | Include the facade (optional) in `app/config/app.php`. 26 | 27 | ```php 28 | 'aliases' => [ 29 | 'Plan' => Seanstewart\PlanConfig\Plan::class 30 | ]; 31 | ``` 32 | 33 | Then you will need to generate your config by running the command 34 | 35 | ```js 36 | php artisan vendor:publish --provider="Seanstewart\PlanConfig\PlanConfigServiceProvider" 37 | ``` 38 | 39 | ## How to Use 40 | 41 | Let's say your app has subscription plans that limit the number of widgets a user can add. You would have some sort of logic that checks the number of widgets a user is allowed to have in their account. With Plan Config you can do that by calling the helper function plan(). 42 | 43 | ```php 44 | if($this->getCurrentNumberOfWidgets < plan('limits.widgets')) 45 | { 46 | // Allow the user to add a new widget 47 | } 48 | ``` 49 | 50 | The plan() helper function knows what plan the current user is subscribed to and grabs the limits you defined in your plans.php config file. You can use the helper function anywhere in your application (views, controllers, models, middleware, etc.). Using the previous example, your plan config file would look like this: 51 | 52 | ```php 53 | 'plans' => [ 54 | 55 | 'bronze' => [ 56 | 'limits' => [ 57 | 'widgets' => 5 58 | ] 59 | ], 60 | 61 | 'silver' => [ 62 | 'limits' => [ 63 | 'widgets' => 10 64 | ] 65 | ], 66 | 67 | //...and so on 68 | 69 | ] 70 | ``` 71 | 72 | If your user is subscribed to the silver plan, they could only add 10 widgets. You can even adapt it to use other attributes, like a title, description, or pricing for your plans. 73 | 74 | ```php 75 | 'plans' => [ 76 | 77 | 'bronze' => [ 78 | 'title' => 'Bronze Plan', 79 | 'description' => 'This is some description for a Bronze Plan', 80 | 'price' => '19.00', 81 | 'currency' => 'USD', 82 | 'limits' => [ 83 | 'widgets' => 5 84 | ] 85 | ], 86 | 87 | 'silver' => [ 88 | 'title' => 'Silver Plan', 89 | 'description' => 'This is some description for the Silver Plan', 90 | 'price' => '29.00', 91 | 'currency' => 'USD', 92 | 'limits' => [ 93 | 'widgets' => 10 94 | ] 95 | ], 96 | 97 | //...and so on 98 | 99 | ] 100 | ``` 101 | 102 | 103 | #### Get a Plan's Config Without the User 104 | 105 | In the case where a specific plan config is needed, you can pass in the plan's code as a string for the 2nd argument: 106 | 107 | ``` 108 | plan('limits.widgets', 'bronze'); // Returns 5 109 | plan('limits.widgets', 'silver'); // Returns 10 110 | ``` 111 | 112 | # Configuring Your Plans 113 | 114 | To configure your plans, open up app/plans.php and start adding your plan details. By default the package assumes that you're using laravel's built in Auth, and that the user's plan is stored in the User model. You can set the field used to determine the user's plan in the config... 115 | 116 | ```php 117 | 'plan_field' => 'stripe_plan' 118 | ``` 119 | 120 | To configure your plans, add your plan data in the 'plans' array. 121 | 122 | ```php 123 | 'plans' => [ 124 | 125 | 'bronze' => [ 126 | 'limits' => [ 127 | 'widgets' => 5 128 | ] 129 | ], 130 | 131 | 'silver' => [ 132 | 'limits' => [ 133 | 'widgets' => 10 134 | ] 135 | ], 136 | 137 | //...and so on 138 | 139 | ] 140 | ``` 141 | 142 | If you have rules that apply to all plans, you can define a default or fallback plan. In the config file, set your fallback plan... 143 | 144 | ```php 145 | 'fallback_plan' => '_default', 146 | ``` 147 | 148 | And then define the _default plan in your plans array. 149 | 150 | ```php 151 | 'plans' => [ 152 | 153 | '_default' => [ 154 | 'limits' => [ 155 | 'purple_widgets' => 20 156 | ] 157 | ] 158 | 159 | 'bronze' => [ 160 | 'limits' => [ 161 | 'widgets' => 5 162 | ] 163 | ], 164 | 165 | 'silver' => [ 166 | 'limits' => [ 167 | 'widgets' => 10 168 | ] 169 | ], 170 | 171 | ] 172 | ``` 173 | 174 | In the above example, calling plan('limits.purple_widgets') will give you the value from the fallback plan. 175 | 176 | Alternatively you can use the facade and call Plan::get('limits.purple_widgets') 177 | 178 | ### Overrides 179 | 180 | Plan config data can be overridden on the user level by setting an override attribute in the config. 181 | 182 | ``` 183 | 'overrides' => [ 184 | 185 | // The user model attribute that stores the attributes that can be changed 186 | 'user_model_attribute' => 'plan_overrides', 187 | 188 | // The keys that are allowed to be changed. Set to all by default (['*']). 189 | 'allowed' => ['*'], 190 | 191 | ] 192 | 193 | ``` 194 | 195 | In the above example, you would create the *plan_overrides* attribute on the user model. This field should be casted as an array and should include the list of keys and values that should be overridden for the given user. 196 | 197 | #### Example 198 | 199 | **Plan Config with Overrides:** 200 | ``` 201 | 'overrides' => [ 202 | 203 | 'user_model_attribute' => 'plan_overrides', 204 | 205 | 'allowed' => ['limits.apples', 'limits.bananas'], 206 | 207 | ], 208 | 209 | 210 | 'plans' => [ 211 | 212 | '_default' => [ 213 | 'limits' => [ 214 | 'purple_widgets' => 20 215 | ] 216 | ] 217 | 218 | 'bronze' => [ 219 | 'limits' => [ 220 | 'widgets' => 5, 221 | 'apples' => 10, 222 | 'bananas' => 15 223 | ] 224 | ], 225 | 226 | 'silver' => [ 227 | 'limits' => [ 228 | 'widgets' => 10, 229 | 'apples' => 15, 230 | 'bananas' => 20 231 | ], 232 | ], 233 | 234 | ] 235 | ``` 236 | 237 | **User Model** *plan_overrides* attribute: 238 | ``` 239 | ['limits.widgets' => 1, 'limits.apples' => 50, 'limits.bananas' => 100] 240 | ``` 241 | 242 | Would result in the following... 243 | 244 | | User's Plan | Key | Call | Result | Overridden? | 245 | | --- | --- | --- | --- | --- | 246 | | silver | limits.widgets | `plan('limits.widgets');` | **10** | No | 247 | | silver | limits.apples | `plan('limits.apples');` | **50** | Yes | 248 | | silver | limits.bananas | `plan('limits.bananas');` | **100** | Yes | 249 | | silver | limits.bananas | `plan('limits.bananas', 'silver')` | 20 | No | 250 | | bronze | limits.bananas | `plan('limits.bananas', 'silver')` | 20 | No | 251 | | bronze | limits.bananas | `plan('limits.bananas', $user)` | 100 | Yes | 252 | 253 | If you want to return a user's entire plan config (along with overrides), you can pass in `*` as the first argument. 254 | 255 | ``` 256 | plan('*'); 257 | ``` 258 | 259 | # Why I created this 260 | 261 | I've always found that managing subscriptions and plans for a SaaS app can be complicated. I felt like storing these values in a database isn't the best approach considering a lot of your values and limits will not change frequently. When building [Election Runner](https://electionrunner.com), a web application that allows schools & organizations to run elections, we needed something to accomplish exactly this. Hopefully others will find this as useful as we do! 262 | 263 | -------------------------------------------------------------------------------- /src/Plan.php: -------------------------------------------------------------------------------- 1 | auth = $auth; 52 | $this->config = $config; 53 | } 54 | 55 | /** 56 | * Get the config key 57 | * @param $key 58 | * @param string|object|array $plan 59 | * @return bool 60 | */ 61 | public function get($key, $plan = null) 62 | { 63 | // If a string is provided, then we want to look up the key for a given plan 64 | if (is_string($plan)) 65 | { 66 | return $this->getPlanKey($key, $plan); 67 | } 68 | 69 | // If the provided plan is not a string, then we are looking up a plan for a specific user and not 70 | // the actual config for a specific plan. This allows us to factor in the user's overrides 71 | $this->setContext($plan); 72 | 73 | return $this->getPlanKey($key, $this->getCurrentUserPlan()); 74 | } 75 | 76 | /** 77 | * Returns all plans 78 | * @return mixed 79 | */ 80 | public function getAllPlans() 81 | { 82 | return $this->getConfig('plans'); 83 | } 84 | 85 | /** 86 | * @param $plan 87 | * @return mixed 88 | */ 89 | public function getPlan($plan) 90 | { 91 | $plans = $this->getConfig('plans'); 92 | 93 | return Arr::get($plans, $plan, $this->getFallbackPlan()); 94 | } 95 | 96 | /** 97 | * @param $key 98 | * @param $plan 99 | * @return bool 100 | */ 101 | public function getPlanKey($key, $plan) 102 | { 103 | $plan = $this->getPlan($plan); 104 | 105 | // Since the key should be sent as array_dot 106 | // we need to convert the plan array to array_dot 107 | $planDot = Arr::dot($plan); 108 | 109 | // Merge the plan data with the overrides 110 | $planDot = array_merge($planDot, $this->getAllowedOverrides()); 111 | 112 | if($key == '*') 113 | { 114 | return $planDot; 115 | } 116 | 117 | return Arr::get($planDot, $key, null); 118 | } 119 | 120 | /** 121 | * Returns the default fallback plan 122 | * @return mixed 123 | */ 124 | public function getFallbackPlan() 125 | { 126 | return Arr::get($this->getAllPlans(), $this->getConfig('fallback_plan'), []); 127 | } 128 | 129 | /** 130 | * Returns the config for this package. 131 | * If a key is provided, then the value of the key will be returned. 132 | * If a key is provided and it doesn't exist, then the default value will be returned. 133 | * will be returned. 134 | * @param null $key 135 | * @param null $default 136 | * @return mixed 137 | */ 138 | public function getConfig($key = null, $default = null) 139 | { 140 | $config = Config::get('plans'); 141 | 142 | return $key ? Arr::get($config, $key, $default) : $config; 143 | } 144 | 145 | /** 146 | * Returns the user's plan overrides with only the keys allowed from the config 147 | * @return array 148 | */ 149 | public function getAllowedOverrides() 150 | { 151 | $allowed = $this->getConfig('overrides.allowed'); 152 | 153 | $overrides = $this->getCurrentUserPlanOverrides(); 154 | 155 | // If we are allowing all overrides, then return all 156 | if ($allowed == ['*']) 157 | { 158 | return Arr::dot($overrides); 159 | } 160 | 161 | // Only return the overrides that are allowed 162 | return Arr::only(Arr::dot($overrides), $allowed); 163 | } 164 | 165 | /** 166 | * Returns the user's plan overrides 167 | * @param $user 168 | * @return array 169 | */ 170 | public function extractUserPlanOverrides($user) 171 | { 172 | $attribute = $this->getConfig('overrides.user_model_attribute'); 173 | 174 | // If this value is null, return an empty array 175 | if (!$attribute) 176 | { 177 | return []; 178 | } 179 | 180 | // Get the overrides from the user. If none, return an empty array. 181 | return Arr::get($user, $attribute, []); 182 | } 183 | 184 | /** 185 | * Returns the user's plan 186 | * @param $user 187 | * @return string 188 | */ 189 | public function extractUserPlan($user) 190 | { 191 | $modelPlanAttribute = $this->getConfig('plan_field'); 192 | 193 | return Arr::get($user, $modelPlanAttribute); 194 | } 195 | 196 | /** 197 | * @return array 198 | */ 199 | public function getAuthenticatedUser() 200 | { 201 | return Auth::check() ? Auth::getUser() : []; 202 | } 203 | 204 | /** 205 | * Sets the user context for looking up the plan 206 | * @param $user 207 | */ 208 | public function setContext($user) 209 | { 210 | // If a user isn't provided, then we want to get the authenticated user 211 | if(!$user) 212 | { 213 | $user = $this->getAuthenticatedUser(); 214 | } 215 | 216 | $this->currentUserPlan = $this->extractUserPlan($user); 217 | $this->currentUserPlanOverrides = $this->extractUserPlanOverrides($user); 218 | } 219 | 220 | /** 221 | * Provides the current user's plan 222 | * @return mixed 223 | */ 224 | public function getCurrentUserPlan() 225 | { 226 | return $this->currentUserPlan; 227 | } 228 | 229 | /** 230 | * Provides the current user's overrides 231 | * @return array 232 | */ 233 | public function getCurrentUserPlanOverrides() 234 | { 235 | return $this->currentUserPlanOverrides; 236 | } 237 | 238 | } -------------------------------------------------------------------------------- /src/PlanConfigServiceProvider.php: -------------------------------------------------------------------------------- 1 | publishes([ 15 | __DIR__.'/plans.php' => config_path('plans.php') 16 | ]); 17 | } 18 | 19 | /** 20 | * Register the service provider. 21 | * 22 | * @return void 23 | */ 24 | public function register() 25 | { 26 | $this->app->bind('planconfig', function () 27 | { 28 | return $this->app->make('Seanstewart\PlanConfig\PlanConfig'); 29 | }); 30 | } 31 | } -------------------------------------------------------------------------------- /src/functions.php: -------------------------------------------------------------------------------- 1 | get($key, $plan); 17 | } 18 | 19 | return $planConfig; 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /src/plans.php: -------------------------------------------------------------------------------- 1 | 'stripe_plan', 15 | 16 | 17 | /* 18 | |-------------------------------------------------------------------------- 19 | | Fallback Plan 20 | |-------------------------------------------------------------------------- 21 | | 22 | | The fallback plan will be used if one of the requested attributes 23 | | is not found in the user's subscription plan. If you don't define a 24 | | default fallback plan, then set this to false. 25 | */ 26 | 27 | 'fallback_plan' => '_default', 28 | 29 | /* 30 | |-------------------------------------------------------------------------- 31 | | Overrides 32 | |-------------------------------------------------------------------------- 33 | | 34 | | Here you can specify which keys can have their values changed using a model attribute 35 | */ 36 | 37 | 'overrides' => [ 38 | 39 | // The user model attribute that stores the attributes that can be changed 40 | 'user_model_attribute' => 'plan_overrides', 41 | 42 | // The keys that are allowed to be changed. Set to all by default (['*']). 43 | 'allowed' => ['*'], 44 | 45 | ], 46 | 47 | /* 48 | |-------------------------------------------------------------------------- 49 | | Subscription plans 50 | |-------------------------------------------------------------------------- 51 | | 52 | | Here you may define all of the plans that you offer, along with the 53 | | limits for each plan. The default plan will be used if no plan matches 54 | | the one provided when calling the plan() helper function. 55 | */ 56 | 57 | 'plans' => [ 58 | 59 | '_default' => [ 60 | 61 | ], 62 | 63 | 'bronze' => [ 64 | 'title' => 'Bronze Plan', 65 | 'description' => 'This is some description for a Bronze Plan', 66 | 'price' => '19.00', 67 | 'currency' => 'USD', 68 | 'limits' => [ 69 | 'widgets' => 5 70 | ] 71 | ], 72 | 73 | 'silver' => [ 74 | 'title' => 'Silver Plan', 75 | 'description' => 'This is some description for the Silver Plan', 76 | 'price' => '29.00', 77 | 'currency' => 'USD', 78 | 'limits' => [ 79 | 'widgets' => 10 80 | ] 81 | ], 82 | 83 | //...and so on 84 | 85 | ] 86 | ]; 87 | -------------------------------------------------------------------------------- /tests/PlanConfigTest.php: -------------------------------------------------------------------------------- 1 | config = new Config(); 23 | $this->auth = new Auth(); 24 | $this->planConfig = new Seanstewart\PlanConfig\PlanConfig($this->config, $this->auth); 25 | } 26 | 27 | protected function getPackageProviders($app) 28 | { 29 | return [\Seanstewart\PlanConfig\PlanConfigServiceProvider::class]; 30 | } 31 | 32 | protected function getPackageAliases($app) 33 | { 34 | return [ 35 | 'Plan' => Plan::class 36 | ]; 37 | } 38 | 39 | /** 40 | * The getMock() method was deprecated in phpunit 6.0. Instead of going through the trouble updating mocks in the 41 | * tests, we just recreated the method from previous versions of phpunit but customized for our use case. 42 | * 43 | * Returns a mock object for the specified class. 44 | * 45 | * @param string $originalClassName Name of the class to mock. 46 | * @param array|null $methods When provided, only methods whose names are in the array 47 | * are replaced with a configurable test double. The behavior 48 | * of the other methods is not changed. 49 | * Providing null means that no methods will be replaced. 50 | * @param array $arguments Parameters to pass to the original class' constructor. 51 | * 52 | * @return PHPUnit_Framework_MockObject_MockObject 53 | */ 54 | private function getMock($originalClassName, $methods = [], array $arguments = []) 55 | { 56 | return $this->getMockBuilder($originalClassName) 57 | ->setConstructorArgs($arguments) 58 | ->setMethods($methods) 59 | ->getMock(); 60 | } 61 | 62 | /** 63 | * @test 64 | * @group function 65 | */ 66 | public function it_tests_global_function_plan() 67 | { 68 | Plan::shouldReceive('get') 69 | ->once() 70 | ->with('key', 'plan') 71 | ->andReturn('foo!'); 72 | 73 | $this->assertEquals('foo!', plan('key', 'plan')); 74 | 75 | Plan::shouldReceive('get') 76 | ->once() 77 | ->with('key', null) 78 | ->andReturn('foo!'); 79 | 80 | $this->assertEquals('foo!', plan('key')); 81 | } 82 | 83 | /** 84 | * @test 85 | * @group function 86 | */ 87 | public function it_tests_global_function_plan_no_key() 88 | { 89 | Plan::shouldReceive('get')->never(); 90 | 91 | $this->assertInstanceOf(Seanstewart\PlanConfig\PlanConfig::class, plan(null)); 92 | } 93 | 94 | /** 95 | * @test 96 | */ 97 | public function it_tests_get_with_null_for_plan() 98 | { 99 | 100 | $planConfig = $this->getMock('SeanStewart\PlanConfig\PlanConfig', ['setContext', 'getCurrentUserPlan', 'getPlanKey'], [$this->config, $this->auth]); 101 | 102 | $planConfig->expects($this->once()) 103 | ->method('setContext') 104 | ->with(null); 105 | 106 | $planConfig->expects($this->once()) 107 | ->method('getCurrentUserPlan') 108 | ->willReturn('current_user_plan'); 109 | 110 | $planConfig->expects($this->once()) 111 | ->method('getPlanKey') 112 | ->with('key', 'current_user_plan') 113 | ->willReturn(true); 114 | 115 | $this->assertTrue($planConfig->get('key')); 116 | } 117 | 118 | /** 119 | * @test 120 | */ 121 | public function it_tests_get_with_plan() 122 | { 123 | $planConfig = $this->getMock('SeanStewart\PlanConfig\PlanConfig', ['setContext', 'getCurrentUserPlan', 'getPlanKey'], [$this->config, $this->auth]); 124 | 125 | $planConfig->expects($this->never()) 126 | ->method('setContext'); 127 | 128 | $planConfig->expects($this->never()) 129 | ->method('getCurrentUserPlan'); 130 | 131 | $planConfig->expects($this->once()) 132 | ->method('getPlanKey') 133 | ->with('key', 'userPlan') 134 | ->willReturn(true); 135 | 136 | $this->assertTrue($planConfig->get('key', 'userPlan')); 137 | } 138 | 139 | /** 140 | * @test 141 | */ 142 | public function it_tests_get_with_user() 143 | { 144 | $planConfig = $this->getMock('SeanStewart\PlanConfig\PlanConfig', ['setContext', 'getCurrentUserPlan', 'getPlanKey'], [$this->config, $this->auth]); 145 | 146 | $user = ['id' => 123]; 147 | 148 | $planConfig->expects($this->once()) 149 | ->method('setContext') 150 | ->with($user); 151 | 152 | $planConfig->expects($this->once()) 153 | ->method('getCurrentUserPlan') 154 | ->willReturn('current_user_plan'); 155 | 156 | $planConfig->expects($this->once()) 157 | ->method('getPlanKey') 158 | ->with('key', 'current_user_plan') 159 | ->willReturn(true); 160 | 161 | $this->assertTrue($planConfig->get('key', $user)); 162 | } 163 | 164 | /** 165 | * @test 166 | */ 167 | public function it_tests_get_all_plans() 168 | { 169 | $planConfig = $this->getMock('SeanStewart\PlanConfig\PlanConfig', ['getConfig', 'getFallbackPlan'], [$this->config, $this->auth]); 170 | 171 | $plans = [ 172 | 'plan_1' => ['foo1' => 'bar1'], 173 | 'plan_2' => ['foo2' => 'bar2'], 174 | ]; 175 | 176 | $planConfig->expects($this->once()) 177 | ->method('getConfig') 178 | ->with('plans') 179 | ->willReturn($plans); 180 | 181 | $this->assertEquals($plans, $planConfig->getAllPlans($plans)); 182 | } 183 | 184 | /** 185 | * @test 186 | */ 187 | public function it_gets_plan() 188 | { 189 | $planConfig = $this->getMock('SeanStewart\PlanConfig\PlanConfig', ['getConfig', 'getFallbackPlan'], [$this->config, $this->auth]); 190 | 191 | $fallbackPlan = ['bar' => 'foo']; 192 | 193 | $planGroup1 = [ 194 | 'plan_1' => ['foo1' => 'bar1'], 195 | 'plan_2' => ['foo2' => 'bar2'], 196 | ]; 197 | 198 | $planGroup2 = [ 199 | 'plan_3' => ['foo3' => 'bar3'] 200 | ]; 201 | 202 | $planConfig->expects($this->exactly(2)) 203 | ->method('getConfig') 204 | ->withConsecutive(['plans', null], ['plans', null]) 205 | ->willReturnOnConsecutiveCalls($planGroup1, $planGroup2); 206 | 207 | $planConfig->expects($this->exactly(2)) 208 | ->method('getFallbackPlan') 209 | ->willReturnOnConsecutiveCalls($fallbackPlan, $fallbackPlan); 210 | 211 | $this->assertEquals($planGroup1['plan_1'], $planConfig->getPlan('plan_1')); 212 | $this->assertEquals($fallbackPlan, $planConfig->getPlan('plan_2')); 213 | } 214 | 215 | /** 216 | * @test 217 | */ 218 | public function it_gets_fallback_plan() 219 | { 220 | $planConfig = $this->getMock('SeanStewart\PlanConfig\PlanConfig', ['getConfig', 'getAllPlans'], [$this->config, $this->auth]); 221 | 222 | $plans = [ 223 | 'default' => ['bar' => 'foo'], 224 | 'plan_1' => ['foo1' => 'bar1'], 225 | 'plan_2' => ['foo2' => 'bar2'] 226 | ]; 227 | 228 | $planConfig->expects($this->once()) 229 | ->method('getConfig') 230 | ->with('fallback_plan') 231 | ->willReturn('default'); 232 | 233 | $planConfig->expects($this->once()) 234 | ->method('getAllPlans') 235 | ->willReturn($plans); 236 | 237 | $this->assertEquals($plans['default'], $planConfig->getFallbackPlan()); 238 | } 239 | 240 | /** 241 | * @test 242 | */ 243 | public function it_gets_fallback_plan_no_fallback() 244 | { 245 | $planConfig = $this->getMock('SeanStewart\PlanConfig\PlanConfig', ['getConfig', 'getAllPlans'], [$this->config, $this->auth]); 246 | 247 | $plans = [ 248 | 'plan_1' => ['foo1' => 'bar1'], 249 | 'plan_2' => ['foo2' => 'bar2'], 250 | ]; 251 | 252 | $planConfig->expects($this->once()) 253 | ->method('getConfig') 254 | ->with('fallback_plan') 255 | ->willReturn('default'); 256 | 257 | $planConfig->expects($this->once()) 258 | ->method('getAllPlans') 259 | ->willReturn($plans); 260 | 261 | $this->assertEquals([], $planConfig->getFallbackPlan()); 262 | } 263 | 264 | /** 265 | * @test 266 | */ 267 | public function it_gets_config() 268 | { 269 | $config = [ 270 | 'plans' => [ 271 | 'foo' => [ 272 | 'bar' => 'foobar' 273 | ] 274 | ] 275 | ]; 276 | 277 | $this->config->shouldReceive('get') 278 | ->with('plans') 279 | ->andReturn($config['plans']); 280 | 281 | $this->assertEquals($config['plans'], $this->planConfig->getConfig()); 282 | $this->assertEquals('foobar', $this->planConfig->getConfig('foo.bar')); 283 | $this->assertEquals(null, $this->planConfig->getConfig('foo.bar2')); 284 | $this->assertEquals('default', $this->planConfig->getConfig('foo.bar2', 'default')); 285 | } 286 | 287 | /** 288 | * @test 289 | */ 290 | public function it_tests_get_plan_key() 291 | { 292 | $planConfig = $this->getMock('SeanStewart\PlanConfig\PlanConfig', ['getPlan', 'getAllowedOverrides'], [$this->config, $this->auth]); 293 | 294 | $completePlan = ['foo' => ['bar' => 'value'], 'barfoo' => 'abc123']; 295 | 296 | $planConfig->expects($this->exactly(4)) 297 | ->method('getPlan') 298 | ->with('plan') 299 | ->willReturnOnConsecutiveCalls( 300 | ['foo' => ['bar' => 'value']], 301 | ['foo' => 'bar'], 302 | ['foo' => ['bar' => 'value'], 'barfoo' => 'abc123'], 303 | $completePlan 304 | ); 305 | 306 | $planConfig->expects($this->exactly(4)) 307 | ->method('getAllowedOverrides') 308 | ->willReturnOnConsecutiveCalls([], [], ['barfoo' => 'barfoobarfoo'], ['barfoo' => 'barfoobarfoo']); 309 | 310 | $this->assertEquals('value', $planConfig->getPlanKey('foo.bar', 'plan')); 311 | $this->assertNull($planConfig->getPlanKey('foo.bar', 'plan')); 312 | $this->assertEquals('barfoobarfoo', $planConfig->getPlanKey('barfoo', 'plan')); 313 | $this->assertEquals([ 314 | 'foo.bar' => 'value', 315 | 'barfoo' => 'barfoobarfoo' 316 | ], $planConfig->getPlanKey('*', 'plan')); 317 | 318 | } 319 | 320 | /** 321 | * @test 322 | */ 323 | public function it_tests_get_plan_key_with_overrides() 324 | { 325 | $planConfig = $this->getMock('SeanStewart\PlanConfig\PlanConfig', ['getPlan', 'getAllowedOverrides'], [$this->config, $this->auth]); 326 | 327 | $planConfig->expects($this->exactly(2)) 328 | ->method('getPlan') 329 | ->with('plan') 330 | ->willReturnOnConsecutiveCalls(['foo' => ['bar' => 'value']], ['foo' => 'bar']); 331 | 332 | $planConfig->expects($this->exactly(2)) 333 | ->method('getAllowedOverrides') 334 | ->willReturnOnConsecutiveCalls(['foo.bar' => 'override', 'extra.key' => 'foo'], []); 335 | 336 | $this->assertEquals('override', $planConfig->getPlanKey('foo.bar', 'plan')); 337 | $this->assertEquals('bar', $planConfig->getPlanKey('foo', 'plan')); 338 | } 339 | 340 | /** 341 | * @test 342 | */ 343 | public function it_tests_extract_user_plan_overrides() 344 | { 345 | $planConfig = $this->getMock('SeanStewart\PlanConfig\PlanConfig', ['getConfig'], [$this->config, $this->auth]); 346 | 347 | $overrides = ['foo.bar' => 'foo']; 348 | 349 | $user1 = []; 350 | $user2 = ['plan_overrides' => $overrides]; 351 | 352 | $planConfig->expects($this->exactly(2)) 353 | ->method('getConfig') 354 | ->with('overrides.user_model_attribute', null) 355 | ->willReturn('plan_overrides'); 356 | 357 | $this->assertEquals([], $planConfig->extractUserPlanOverrides($user1)); 358 | $this->assertEquals($overrides, $planConfig->extractUserPlanOverrides($user2)); 359 | } 360 | 361 | /** 362 | * @test 363 | */ 364 | public function it_tests_get_allowed_overrides_no_overrides() 365 | { 366 | $planConfig = $this->getMock('SeanStewart\PlanConfig\PlanConfig', ['getConfig', 'getCurrentUserPlanOverrides'], [$this->config, $this->auth]); 367 | 368 | $overrides = null; 369 | 370 | $planConfig->expects($this->once()) 371 | ->method('getConfig') 372 | ->with('overrides.allowed') 373 | ->willReturn(null); 374 | 375 | $planConfig->expects($this->once()) 376 | ->method('getCurrentUserPlanOverrides') 377 | ->willReturn([]); 378 | 379 | $this->assertEquals([], $planConfig->getAllowedOverrides('plan_overrides')); 380 | } 381 | 382 | /** 383 | * @test 384 | */ 385 | public function it_tests_get_allowed_overrides_key_not_set() 386 | { 387 | $planConfig = $this->getMock('SeanStewart\PlanConfig\PlanConfig', ['getConfig', 'getCurrentUserPlanOverrides'], [$this->config, $this->auth]); 388 | 389 | $overrides = ['foo' => 'bar']; 390 | 391 | $planConfig->expects($this->once()) 392 | ->method('getConfig') 393 | ->with('overrides.allowed') 394 | ->willReturn(null); 395 | 396 | $planConfig->expects($this->once()) 397 | ->method('getCurrentUserPlanOverrides') 398 | ->willReturn($overrides); 399 | 400 | $this->assertEquals([], $planConfig->getAllowedOverrides('plan_overrides')); 401 | } 402 | 403 | /** 404 | * @test 405 | */ 406 | public function it_tests_get_allowed_overrides_allows_all_keys() 407 | { 408 | $planConfig = $this->getMock('SeanStewart\PlanConfig\PlanConfig', ['getConfig', 'getCurrentUserPlanOverrides'], [$this->config, $this->auth]); 409 | 410 | $overrides = ['foo' => ['bar' => 'foobar']]; 411 | 412 | $planConfig->expects($this->once()) 413 | ->method('getConfig') 414 | ->with('overrides.allowed') 415 | ->willReturn(['*']); 416 | 417 | $planConfig->expects($this->once()) 418 | ->method('getCurrentUserPlanOverrides') 419 | ->willReturn($overrides); 420 | 421 | $this->assertEquals(['foo.bar' => 'foobar'], $planConfig->getAllowedOverrides('plan_overrides')); 422 | } 423 | 424 | /** 425 | * @test 426 | */ 427 | public function it_tests_get_allowed_overrides_returns_allowed_only() 428 | { 429 | $planConfig = $this->getMock('SeanStewart\PlanConfig\PlanConfig', ['getConfig', 'getCurrentUserPlanOverrides'], [$this->config, $this->auth]); 430 | 431 | $overrides = [ 432 | 'foo' => ['bar' => 'foobar'], 433 | 'yes' => ['foobar' => 'yeah'], 434 | 'foobarfoobar' => 123, 435 | 'nooverride' => 'foo' 436 | ]; 437 | 438 | $planConfig->expects($this->once()) 439 | ->method('getConfig') 440 | ->with('overrides.allowed') 441 | ->willReturn(['foo.bar', 'yes.foobar', 'foobarfoobar']); 442 | 443 | $planConfig->expects($this->once()) 444 | ->method('getCurrentUserPlanOverrides') 445 | ->willReturn($overrides); 446 | 447 | $this->assertEquals([ 448 | 'foo.bar' => 'foobar', 449 | 'yes.foobar' => 'yeah', 450 | 'foobarfoobar' => 123 451 | ], $planConfig->getAllowedOverrides('plan_overrides')); 452 | } 453 | 454 | /** 455 | * @test 456 | */ 457 | public function it_tests_extract_user_plan() 458 | { 459 | $planConfig = $this->getMock('SeanStewart\PlanConfig\PlanConfig', ['getConfig'], [$this->config, $this->auth]); 460 | 461 | $planConfig->expects($this->once()) 462 | ->method('getConfig') 463 | ->with('plan_field') 464 | ->willReturn('stripe_plan'); 465 | 466 | $user = [ 467 | 'stripe_plan' => 'foobar' 468 | ]; 469 | 470 | $this->assertEquals('foobar', $planConfig->extractUserPlan($user)); 471 | } 472 | 473 | /** 474 | * @test 475 | */ 476 | public function it_tests_get_authenticated_user() 477 | { 478 | $planConfig = $this->getMock('SeanStewart\PlanConfig\PlanConfig', ['getPlan'], [$this->config, $this->auth]); 479 | 480 | Auth::shouldReceive('check') 481 | ->once() 482 | ->andReturn(true); 483 | 484 | $user = ['id' => 123]; 485 | 486 | Auth::shouldReceive('getUser') 487 | ->once() 488 | ->andReturn($user); 489 | 490 | $this->assertEquals($user, $planConfig->getAuthenticatedUser()); 491 | } 492 | 493 | /** 494 | * @test 495 | */ 496 | public function it_tests_get_authenticated_user_not_logged_in() 497 | { 498 | $planConfig = $this->getMock('SeanStewart\PlanConfig\PlanConfig', ['getPlan'], [$this->config, $this->auth]); 499 | 500 | Auth::shouldReceive('check') 501 | ->andReturn(false); 502 | 503 | Auth::shouldReceive('getUser')->never(); 504 | 505 | $this->assertEquals([], $planConfig->getAuthenticatedUser()); 506 | 507 | } 508 | 509 | /** 510 | * @test 511 | */ 512 | public function it_tests_set_context() 513 | { 514 | $planConfig = $this->getMock('SeanStewart\PlanConfig\PlanConfig', ['getAuthenticatedUser', 'extractUserPlan', 'extractUserPlanOverrides'], [$this->config, $this->auth]); 515 | 516 | $user = ['id' => 1]; 517 | 518 | $planConfig->expects($this->never()) 519 | ->method('getAuthenticatedUser'); 520 | 521 | $planConfig->expects($this->once()) 522 | ->method('extractUserPlan') 523 | ->with($user) 524 | ->willReturn('plan'); 525 | 526 | $planConfig->expects($this->once()) 527 | ->method('extractUserPlanOverrides') 528 | ->with($user) 529 | ->willReturn(['overrides']); 530 | 531 | $planConfig->setContext($user); 532 | 533 | $this->assertEquals('plan', $planConfig->getCurrentUserPlan()); 534 | $this->assertEquals(['overrides'], $planConfig->getCurrentUserPlanOverrides()); 535 | } 536 | } --------------------------------------------------------------------------------