├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── public └── .gitkeep ├── src ├── Mmanos │ └── Billing │ │ ├── BillingServiceProvider.php │ │ ├── CustomerBillableTrait.php │ │ ├── CustomerBillableTrait │ │ ├── Billing.php │ │ ├── Charge.php │ │ ├── Charges.php │ │ ├── Creditcard.php │ │ ├── Creditcards.php │ │ ├── Invoice.php │ │ ├── InvoiceItem.php │ │ ├── Invoices.php │ │ └── Subscriptions.php │ │ ├── EloquentBillableRepository.php │ │ ├── Facades │ │ └── Billing.php │ │ ├── Gateways │ │ ├── Braintree │ │ │ ├── Card.php │ │ │ ├── Charge.php │ │ │ ├── Customer.php │ │ │ ├── Gateway.php │ │ │ ├── Invoice.php │ │ │ ├── Subscription.php │ │ │ └── WebhookController.php │ │ ├── CardInterface.php │ │ ├── ChargeInterface.php │ │ ├── CustomerInterface.php │ │ ├── GatewayInterface.php │ │ ├── InvoiceInterface.php │ │ ├── Local │ │ │ ├── Card.php │ │ │ ├── Charge.php │ │ │ ├── Customer.php │ │ │ ├── Gateway.php │ │ │ ├── Invoice.php │ │ │ ├── Models │ │ │ │ ├── Card.php │ │ │ │ ├── Charge.php │ │ │ │ ├── Coupon.php │ │ │ │ ├── Customer.php │ │ │ │ ├── Invoice.php │ │ │ │ ├── Invoice │ │ │ │ │ └── Item.php │ │ │ │ ├── Plan.php │ │ │ │ ├── Subscription.php │ │ │ │ └── migration.php │ │ │ └── Subscription.php │ │ ├── Stripe │ │ │ ├── Card.php │ │ │ ├── Charge.php │ │ │ ├── Customer.php │ │ │ ├── Gateway.php │ │ │ ├── Invoice.php │ │ │ ├── Subscription.php │ │ │ └── WebhookController.php │ │ ├── SubscriptionInterface.php │ │ └── WebhookController.php │ │ ├── Stubs │ │ ├── CustomerMigration.stub │ │ └── SubscriptionMigration.stub │ │ ├── SubscriptionBillableTrait.php │ │ └── SubscriptionBillableTrait │ │ └── Subscription.php ├── commands │ ├── CustomerTableCommand.php │ ├── LocalCreateCouponCommand.php │ ├── LocalCreatePlanCommand.php │ └── SubscriptionTableCommand.php ├── config │ └── config.php ├── controllers │ └── .gitkeep ├── lang │ └── .gitkeep ├── migrations │ └── .gitkeep └── views │ └── invoice.blade.php └── tests └── .gitkeep /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.phar 3 | composer.lock 4 | .DS_Store 5 | Thumbs.db 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mmanos/laravel-billing", 3 | "description": "A billing package for Laravel 4.", 4 | "keywords": ["laravel", "billing", "stripe", "braintree", "cashier", "payments", "subscriptions", "charges"], 5 | "authors": [ 6 | { 7 | "name": "Mark Manos", 8 | "email": "mark@airpac.com" 9 | } 10 | ], 11 | "require": { 12 | "php": ">=5.4.0", 13 | "illuminate/support": "~4.1", 14 | "nesbot/carbon": "~1.0" 15 | }, 16 | "require-dev": { 17 | "stripe/stripe-php": "~1.9", 18 | "braintree/braintree_php" : "2.33.0" 19 | }, 20 | "suggest": { 21 | "stripe/stripe-php": "Add the Stripe gateway SDK.", 22 | "braintree/braintree_php": "Add the Braintree gateway SDK." 23 | }, 24 | "autoload": { 25 | "classmap": [ 26 | "src/migrations", 27 | "src/controllers", 28 | "src/commands" 29 | ], 30 | "psr-0": { 31 | "Mmanos\\Billing\\": "src/" 32 | } 33 | }, 34 | "minimum-stability": "stable", 35 | "license": "MIT" 36 | } 37 | -------------------------------------------------------------------------------- /public/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmanos/laravel-billing/aa7baf7862d8cf0ac3ea2f7b9c2a34ab5b8d5c06/public/.gitkeep -------------------------------------------------------------------------------- /src/Mmanos/Billing/BillingServiceProvider.php: -------------------------------------------------------------------------------- 1 | package('mmanos/laravel-billing'); 23 | } 24 | 25 | /** 26 | * Register the service provider. 27 | * 28 | * @return void 29 | */ 30 | public function register() 31 | { 32 | $this->app->bindShared('billing.gateway', function ($app) { 33 | switch (Config::get('laravel-billing::default')) { 34 | case 'stripe': 35 | return new \Mmanos\Billing\Gateways\Stripe\Gateway; 36 | case 'braintree': 37 | return new \Mmanos\Billing\Gateways\Braintree\Gateway; 38 | case 'local': 39 | return new \Mmanos\Billing\Gateways\Local\Gateway; 40 | default: 41 | return null; 42 | } 43 | }); 44 | 45 | $this->app->bindShared('command.laravel-billing.customer-table', function ($app) { 46 | return new CustomerTableCommand; 47 | }); 48 | $this->commands('command.laravel-billing.customer-table'); 49 | 50 | $this->app->bindShared('command.laravel-billing.subscription-table', function ($app) { 51 | return new SubscriptionTableCommand; 52 | }); 53 | $this->commands('command.laravel-billing.subscription-table'); 54 | 55 | $this->app->bindShared('command.laravel-billing.local-create-plan', function ($app) { 56 | return new LocalCreatePlanCommand; 57 | }); 58 | $this->commands('command.laravel-billing.local-create-plan'); 59 | 60 | $this->app->bindShared('command.laravel-billing.local-create-coupon', function ($app) { 61 | return new LocalCreateCouponCommand; 62 | }); 63 | $this->commands('command.laravel-billing.local-create-coupon'); 64 | } 65 | 66 | /** 67 | * Get the services provided by the provider. 68 | * 69 | * @return array 70 | */ 71 | public function provides() 72 | { 73 | return array(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Mmanos/Billing/CustomerBillableTrait.php: -------------------------------------------------------------------------------- 1 | readyForBilling()) { 16 | return null; 17 | } 18 | 19 | return Billing::customer($this->billing_id); 20 | } 21 | 22 | /** 23 | * Return a customer billing helper object. 24 | * 25 | * @return CustomerBillableTrait\Billing 26 | */ 27 | public function billing() 28 | { 29 | return new CustomerBillableTrait\Billing($this); 30 | } 31 | 32 | /** 33 | * Return a customer creditcards helper object. 34 | * 35 | * @return CustomerBillableTrait\Creditcards 36 | */ 37 | public function creditcards() 38 | { 39 | return new CustomerBillableTrait\Creditcards($this); 40 | } 41 | 42 | /** 43 | * Return a customer invoices helper object. 44 | * 45 | * @return CustomerBillableTrait\Invoices 46 | */ 47 | public function invoices() 48 | { 49 | return new CustomerBillableTrait\Invoices($this); 50 | } 51 | 52 | /** 53 | * Return a customer charges helper object. 54 | * 55 | * @return CustomerBillableTrait\Charges 56 | */ 57 | public function charges() 58 | { 59 | return new CustomerBillableTrait\Charges($this); 60 | } 61 | 62 | /** 63 | * Return a customer subscriptions helper object. 64 | * 65 | * @param mixed $plan 66 | * 67 | * @return CustomerBillableTrait\Subscriptions 68 | */ 69 | public function subscriptions($plan = null) 70 | { 71 | return new CustomerBillableTrait\Subscriptions($this, $plan); 72 | } 73 | 74 | /** 75 | * Determine if the entity is a Billing customer. 76 | * 77 | * @return bool 78 | */ 79 | public function readyForBilling() 80 | { 81 | return !empty($this->billing_id); 82 | } 83 | 84 | /** 85 | * Return the Eloquent models acting as billing subscriptions for this customer model. 86 | * The subscription models can be defined in one of the following ways: 87 | * - A 'subscriptionmodels' relationship on this model. 88 | * - A 'subscriptionmodels' method on this model that returns one of the following: 89 | * - A collection 90 | * - An array of collections 91 | * - An array of models 92 | * 93 | * @return array 94 | */ 95 | public function subscriptionModelsArray() 96 | { 97 | // Note: Laravel throws a LogicException if a subscriptions method exists but 98 | // doesn't return a valid relationship. 99 | try { 100 | if ($collection = $this->subscriptionmodels) { 101 | return $collection->all(); 102 | } 103 | } catch (LogicException $e) {} 104 | 105 | if (method_exists($this, 'subscriptionmodels')) { 106 | $subscriptions = array(); 107 | 108 | foreach ($this->subscriptionmodels() as $value) { 109 | if ($value instanceof \Illuminate\Support\Collection) { 110 | $subscriptions = array_merge($subscriptions, $value->all()); 111 | } 112 | else if ($value instanceof \Illuminate\Database\Eloquent\Model) { 113 | $subscriptions[] = $value; 114 | } 115 | } 116 | 117 | return $subscriptions; 118 | } 119 | 120 | // Check for customer/subscription on the same model. 121 | if (method_exists($this, 'gatewaySubscription')) { 122 | return array($this); 123 | } 124 | 125 | return null; 126 | } 127 | 128 | /** 129 | * Getter for billing_cards property. 130 | * 131 | * @param string $value 132 | * 133 | * @return array 134 | */ 135 | public function getBillingCardsAttribute($value) 136 | { 137 | return $value ? json_decode($value, true) : array(); 138 | } 139 | 140 | /** 141 | * Setter for billing_cards property. 142 | * 143 | * @param array $value 144 | * 145 | * @return void 146 | */ 147 | public function setBillingCardsAttribute($value) 148 | { 149 | $this->attributes['billing_cards'] = empty($value) ? null : json_encode($value); 150 | } 151 | 152 | /** 153 | * Getter for billing_discounts property. 154 | * 155 | * @param string $value 156 | * 157 | * @return array 158 | */ 159 | public function getBillingDiscountsAttribute($value) 160 | { 161 | return $value ? json_decode($value, true) : array(); 162 | } 163 | 164 | /** 165 | * Setter for billing_discounts property. 166 | * 167 | * @param array $value 168 | * 169 | * @return void 170 | */ 171 | public function setBillingDiscountsAttribute($value) 172 | { 173 | $this->attributes['billing_discounts'] = empty($value) ? null : json_encode($value); 174 | } 175 | 176 | /** 177 | * Register a customerCreated model event with the dispatcher. 178 | * 179 | * @param \Closure|string $callback 180 | * 181 | * @return void 182 | */ 183 | public static function customerCreated($callback) 184 | { 185 | static::listenForCustomerEvents(); 186 | static::registerModelEvent('customerCreated', $callback); 187 | } 188 | 189 | /** 190 | * Register a customerDeleted model event with the dispatcher. 191 | * 192 | * @param \Closure|string $callback 193 | * 194 | * @return void 195 | */ 196 | public static function customerDeleted($callback) 197 | { 198 | static::listenForCustomerEvents(); 199 | static::registerModelEvent('customerDeleted', $callback); 200 | } 201 | 202 | /** 203 | * Register a creditcardAdded model event with the dispatcher. 204 | * 205 | * @param \Closure|string $callback 206 | * 207 | * @return void 208 | */ 209 | public static function creditcardAdded($callback) 210 | { 211 | static::listenForCustomerEvents(); 212 | static::registerModelEvent('creditcardAdded', $callback); 213 | } 214 | 215 | /** 216 | * Register a creditcardRemoved model event with the dispatcher. 217 | * 218 | * @param \Closure|string $callback 219 | * 220 | * @return void 221 | */ 222 | public static function creditcardRemoved($callback) 223 | { 224 | static::listenForCustomerEvents(); 225 | static::registerModelEvent('creditcardRemoved', $callback); 226 | } 227 | 228 | /** 229 | * Register a creditcardUpdated model event with the dispatcher. 230 | * 231 | * @param \Closure|string $callback 232 | * 233 | * @return void 234 | */ 235 | public static function creditcardUpdated($callback) 236 | { 237 | static::listenForCustomerEvents(); 238 | static::registerModelEvent('creditcardUpdated', $callback); 239 | } 240 | 241 | /** 242 | * Register a creditcardChanged model event with the dispatcher. 243 | * 244 | * @param \Closure|string $callback 245 | * 246 | * @return void 247 | */ 248 | public static function creditcardChanged($callback) 249 | { 250 | static::listenForCustomerEvents(); 251 | static::registerModelEvent('creditcardChanged', $callback); 252 | } 253 | 254 | /** 255 | * Register a discountAdded model event with the dispatcher. 256 | * 257 | * @param \Closure|string $callback 258 | * 259 | * @return void 260 | */ 261 | public static function discountAdded($callback) 262 | { 263 | static::listenForCustomerEvents(); 264 | static::registerModelEvent('discountAdded', $callback); 265 | } 266 | 267 | /** 268 | * Register a discountRemoved model event with the dispatcher. 269 | * 270 | * @param \Closure|string $callback 271 | * 272 | * @return void 273 | */ 274 | public static function discountRemoved($callback) 275 | { 276 | static::listenForCustomerEvents(); 277 | static::registerModelEvent('discountRemoved', $callback); 278 | } 279 | 280 | /** 281 | * Register a discountUpdated model event with the dispatcher. 282 | * 283 | * @param \Closure|string $callback 284 | * 285 | * @return void 286 | */ 287 | public static function discountUpdated($callback) 288 | { 289 | static::listenForCustomerEvents(); 290 | static::registerModelEvent('discountUpdated', $callback); 291 | } 292 | 293 | /** 294 | * Register a discountChanged model event with the dispatcher. 295 | * 296 | * @param \Closure|string $callback 297 | * 298 | * @return void 299 | */ 300 | public static function discountChanged($callback) 301 | { 302 | static::listenForCustomerEvents(); 303 | static::registerModelEvent('discountChanged', $callback); 304 | } 305 | 306 | /** 307 | * Register a invoiceCreated model event with the dispatcher. 308 | * Triggered via webhook. 309 | * 310 | * @param \Closure|string $callback 311 | * 312 | * @return void 313 | */ 314 | public static function invoiceCreated($callback) 315 | { 316 | static::registerModelEvent('invoiceCreated', $callback); 317 | } 318 | 319 | /** 320 | * Register a invoicePaymentSucceeded model event with the dispatcher. 321 | * Triggered via webhook. 322 | * 323 | * @param \Closure|string $callback 324 | * 325 | * @return void 326 | */ 327 | public static function invoicePaymentSucceeded($callback) 328 | { 329 | static::registerModelEvent('invoicePaymentSucceeded', $callback); 330 | } 331 | 332 | /** 333 | * Register a invoicePaymentFailed model event with the dispatcher. 334 | * Triggered via webhook. 335 | * 336 | * @param \Closure|string $callback 337 | * 338 | * @return void 339 | */ 340 | public static function invoicePaymentFailed($callback) 341 | { 342 | static::registerModelEvent('invoicePaymentFailed', $callback); 343 | } 344 | 345 | /** 346 | * Fire the given event for the model. 347 | * 348 | * @param string $event 349 | * 350 | * @return mixed 351 | */ 352 | public function fireCustomerEvent($event) 353 | { 354 | if ( ! isset(static::$dispatcher)) return true; 355 | 356 | // We will append the names of the class to the event to distinguish it from 357 | // other model events that are fired, allowing us to listen on each model 358 | // event set individually instead of catching event for all the models. 359 | $event = "eloquent.{$event}: ".get_class($this); 360 | 361 | $args = array_merge(array($this), array_slice(func_get_args(), 1)); 362 | 363 | return static::$dispatcher->fire($event, $args); 364 | } 365 | 366 | /** 367 | * Register listeners for model change events so we can trigger our own 368 | * custom events. 369 | * 370 | * @return void 371 | */ 372 | protected static function listenForCustomerEvents() 373 | { 374 | static $listening_for_customer_events; 375 | if ($listening_for_customer_events) { 376 | return; 377 | } 378 | 379 | static::saved(function ($model) { 380 | $original = $model->getOriginal(); 381 | 382 | if ($model->isDirty('billing_id')) { 383 | if (empty($original['billing_id']) && !empty($model->billing_id)) { 384 | $model->fireCustomerEvent('customerCreated'); 385 | } 386 | else if (empty($model->billing_id) && !empty($original['billing_id'])) { 387 | $model->fireCustomerEvent('customerDeleted'); 388 | } 389 | } 390 | if ($model->isDirty('billing_cards')) { 391 | if (count($model->billing_cards) > count(json_decode($model->getOriginal('billing_cards'), true))) { 392 | $model->fireCustomerEvent('creditcardAdded'); 393 | } 394 | else if (count($model->billing_cards) < count(json_decode($model->getOriginal('billing_cards'), true))) { 395 | $model->fireCustomerEvent('creditcardRemoved'); 396 | } 397 | else if (!empty($model->billing_cards)) { 398 | $model->fireCustomerEvent('creditcardUpdated'); 399 | } 400 | $model->fireCustomerEvent('creditcardChanged'); 401 | } 402 | if ($model->isDirty('billing_discounts')) { 403 | if (count($model->billing_discounts) > count(json_decode($model->getOriginal('billing_discounts'), true))) { 404 | $model->fireCustomerEvent('discountAdded'); 405 | } 406 | else if (count($model->billing_discounts) < count(json_decode($model->getOriginal('billing_discounts'), true))) { 407 | $model->fireCustomerEvent('discountRemoved'); 408 | } 409 | else if (!empty($model->billing_discounts)) { 410 | $model->fireCustomerEvent('discountUpdated'); 411 | } 412 | $model->fireCustomerEvent('discountChanged'); 413 | } 414 | }); 415 | 416 | $listening_for_customer_events = true; 417 | } 418 | } 419 | -------------------------------------------------------------------------------- /src/Mmanos/Billing/CustomerBillableTrait/Billing.php: -------------------------------------------------------------------------------- 1 | model = $model; 60 | $this->customer = $this->model->gatewayCustomer(); 61 | $this->info = $this->customer ? $this->customer->info() : array(); 62 | } 63 | 64 | /** 65 | * Create this customer in the billing gateway. 66 | * 67 | * @param array $properties 68 | * 69 | * @return Billing 70 | */ 71 | public function create(array $properties = array()) 72 | { 73 | if ($this->model->readyForBilling()) { 74 | return $this; 75 | } 76 | 77 | $this->customer = \Mmanos\Billing\Facades\Billing::customer()->create(array_merge($properties, array( 78 | 'coupon' => $this->coupon, 79 | 'card_token' => $this->card_token, 80 | ))); 81 | 82 | $this->refresh(); 83 | 84 | if ($this->with_subscriptions) { 85 | $this->createPendingSubscriptions(); 86 | } 87 | 88 | return $this; 89 | } 90 | 91 | /** 92 | * Update this customer in the billing gateway. 93 | * 94 | * @param array $properties 95 | * 96 | * @return Billing 97 | */ 98 | public function update(array $properties = array()) 99 | { 100 | if (!$this->model->readyForBilling()) { 101 | return $this; 102 | } 103 | 104 | $this->customer->update(array_merge($properties, array( 105 | 'coupon' => $this->coupon, 106 | 'card_token' => $this->card_token, 107 | ))); 108 | 109 | $this->refresh(); 110 | 111 | // If card changed, refresh all subscription records that reference an invalid card. 112 | if ($this->card_token && ($subscriptions = $this->model->subscriptionModelsArray())) { 113 | $cards = $this->model->billing_cards; 114 | foreach ($subscriptions as $subscription) { 115 | if (!$subscription->billingIsActive()) { 116 | continue; 117 | } 118 | 119 | $valid_card = false; 120 | if (!empty($subscription->billing_card) && !empty($cards)) { 121 | foreach ($cards as $card) { 122 | if ($subscription->billing_card == Arr::get($card, 'id')) { 123 | $valid_card = true; 124 | break; 125 | } 126 | } 127 | } 128 | 129 | if (!$valid_card) { 130 | $subscription->subscription()->refresh(); 131 | } 132 | } 133 | } 134 | 135 | if ($this->with_subscriptions) { 136 | $this->createPendingSubscriptions(); 137 | } 138 | 139 | return $this; 140 | } 141 | 142 | /** 143 | * Delete this customer in the billing gateway. 144 | * 145 | * @param array $properties 146 | * 147 | * @return Billing 148 | */ 149 | public function delete(array $properties = array()) 150 | { 151 | if (!$this->model->readyForBilling()) { 152 | return $this; 153 | } 154 | 155 | try { 156 | $this->customer->delete(); 157 | } catch (Exception $e) {} 158 | 159 | $this->refresh(); 160 | 161 | // Deactivate all customer subscriptions. 162 | if ($subscriptions = $this->model->subscriptionModelsArray()) { 163 | foreach ($subscriptions as $subscription) { 164 | $subscription->billing_subscription_ends_at = $subscription->billingIsActive() ? date('Y-m-d H:i:s') : null; 165 | $subscription->billing_active = 0; 166 | $subscription->billing_subscription = null; 167 | $subscription->billing_trial_ends_at = null; 168 | $subscription->save(); 169 | } 170 | } 171 | 172 | return $this; 173 | } 174 | 175 | /** 176 | * Refresh local model data for this customer. 177 | * 178 | * @return Billing 179 | */ 180 | public function refresh() 181 | { 182 | $info = array(); 183 | if ($this->customer) { 184 | try { 185 | $info = $this->customer->info(); 186 | } catch (Exception $e) {} 187 | } 188 | 189 | if (!empty($info)) { 190 | $this->model->billing_id = $this->customer->id(); 191 | $this->model->billing_discounts = Arr::get($info, 'discounts'); 192 | 193 | $cards = array(); 194 | foreach ($this->customer->cards() as $card) { 195 | $cards[] = $card->info(); 196 | } 197 | $this->model->billing_cards = $cards; 198 | } 199 | else { 200 | $this->model->billing_id = null; 201 | $this->model->billing_cards = null; 202 | $this->model->billing_discounts = null; 203 | } 204 | 205 | $this->model->save(); 206 | 207 | $this->info = $info; 208 | 209 | return $this; 210 | } 211 | 212 | /** 213 | * Create all pending (usually trialing) customer subscriptions in the billing gateway. 214 | * To activate, the subscription must: 215 | * - Not already be active 216 | * - Not be free 217 | * - Not require a credit card up front 218 | * - Already have a plan selected 219 | * 220 | * @return Billing 221 | */ 222 | public function createPendingSubscriptions() 223 | { 224 | foreach ($this->model->subscriptionModelsArray() as $model) { 225 | if ($model->billingIsActive()) { 226 | continue; 227 | } 228 | if ($model->billing_free) { 229 | continue; 230 | } 231 | if ($model->requiresCardUpFront()) { 232 | continue; 233 | } 234 | if (!$model->billing_plan) { 235 | continue; 236 | } 237 | 238 | $model->subscription($model->billing_plan)->create(); 239 | } 240 | 241 | return $this; 242 | } 243 | 244 | /** 245 | * The coupon to apply to a new customer. 246 | * 247 | * @param string $coupon 248 | * 249 | * @return Billing 250 | */ 251 | public function withCoupon($coupon) 252 | { 253 | $this->coupon = $coupon; 254 | return $this; 255 | } 256 | 257 | /** 258 | * The credit card token to assign to a new customer. 259 | * 260 | * @param string $card_token 261 | * 262 | * @return Billing 263 | */ 264 | public function withCardToken($card_token) 265 | { 266 | $this->card_token = $card_token; 267 | return $this; 268 | } 269 | 270 | /** 271 | * Create all subscriptions in the billing gateway after this action. 272 | * Useful if subscription trials have already been started but no 273 | * billing customer was created yet (cardUpFront = false). 274 | * 275 | * @return Billing 276 | */ 277 | public function withSubscriptions() 278 | { 279 | $this->with_subscriptions = true; 280 | return $this; 281 | } 282 | 283 | /** 284 | * Convert this instance to an array. 285 | * 286 | * @return array 287 | */ 288 | public function toArray() 289 | { 290 | return $this->info; 291 | } 292 | 293 | /** 294 | * Dynamically check a values existence from the customer. 295 | * 296 | * @param string $key 297 | * 298 | * @return bool 299 | */ 300 | public function __isset($key) 301 | { 302 | return isset($this->info[$key]); 303 | } 304 | 305 | /** 306 | * Dynamically get values from the customer. 307 | * 308 | * @param string $key 309 | * 310 | * @return mixed 311 | */ 312 | public function __get($key) 313 | { 314 | return isset($this->info[$key]) ? $this->info[$key] : null; 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /src/Mmanos/Billing/CustomerBillableTrait/Charge.php: -------------------------------------------------------------------------------- 1 | model = $model; 46 | $this->charge = $charge; 47 | $this->info = $charge->info(); 48 | } 49 | 50 | /** 51 | * Capture this charge in the billing gateway. 52 | * 53 | * @param array $properties 54 | * 55 | * @return Charge 56 | */ 57 | public function capture(array $properties = array()) 58 | { 59 | if (!$this->model->readyForBilling()) { 60 | return $this; 61 | } 62 | 63 | $this->charge->capture($properties); 64 | $this->info = $this->charge->info(); 65 | 66 | return $this; 67 | } 68 | 69 | /** 70 | * Refund this charge in the billing gateway. 71 | * 72 | * @param array $properties 73 | * 74 | * @return Charge 75 | */ 76 | public function refund(array $properties = array()) 77 | { 78 | if (!$this->model->readyForBilling()) { 79 | return $this; 80 | } 81 | 82 | $this->charge->refund($properties); 83 | $this->info = $this->charge->info(); 84 | 85 | return $this; 86 | } 87 | 88 | /** 89 | * Return the invoice object associated with this charge. 90 | * 91 | * @return Invoice 92 | */ 93 | public function invoice() 94 | { 95 | if (empty($this->invoice_id)) { 96 | return null; 97 | } 98 | 99 | if (null !== $this->invoice) { 100 | return $this->invoice ? $this->invoice : null; 101 | } 102 | 103 | $this->invoice = false; 104 | if ($invoice = $this->model->invoices()->find($this->invoice_id)) { 105 | $this->invoice = $invoice; 106 | } 107 | 108 | return $this->invoice ? $this->invoice : null; 109 | } 110 | 111 | /** 112 | * Convert this instance to an array. 113 | * 114 | * @return array 115 | */ 116 | public function toArray() 117 | { 118 | return $this->info; 119 | } 120 | 121 | /** 122 | * Dynamically check a values existence from the charge. 123 | * 124 | * @param string $key 125 | * 126 | * @return bool 127 | */ 128 | public function __isset($key) 129 | { 130 | return isset($this->info[$key]); 131 | } 132 | 133 | /** 134 | * Dynamically get values from the charge. 135 | * 136 | * @param string $key 137 | * 138 | * @return mixed 139 | */ 140 | public function __get($key) 141 | { 142 | return isset($this->info[$key]) ? $this->info[$key] : null; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/Mmanos/Billing/CustomerBillableTrait/Charges.php: -------------------------------------------------------------------------------- 1 | model = $model; 52 | } 53 | 54 | /** 55 | * Fetch the credit charge. 56 | * 57 | * @return array 58 | */ 59 | public function get() 60 | { 61 | $charges = array(); 62 | 63 | foreach ($this->model->gatewayCustomer()->charges() as $charge) { 64 | $charges[] = new Charge($this->model, $charge); 65 | } 66 | 67 | return $charges; 68 | } 69 | 70 | /** 71 | * Find and return the first credit card. 72 | * 73 | * @return Charge 74 | */ 75 | public function first() 76 | { 77 | return Arr::get($this->get(), 0); 78 | } 79 | 80 | /** 81 | * Find and return a credit card. 82 | * 83 | * @param mixed $id 84 | * 85 | * @return Charge 86 | */ 87 | public function find($id) 88 | { 89 | try { 90 | return new Charge($this->model, $this->model->gatewayCustomer()->charge($id)); 91 | } catch (\Exception $e) {} 92 | 93 | return null; 94 | } 95 | 96 | /** 97 | * Create this charge in the billing gateway. 98 | * 99 | * @param int $amount 100 | * @param array $properties 101 | * 102 | * @return Charge 103 | */ 104 | public function create($amount, array $properties = array()) 105 | { 106 | if (!$this->model->readyForBilling()) { 107 | if ($this->card_token) { 108 | $this->model->billing()->withCardToken($this->card_token)->create($properties); 109 | if (!empty($this->model->billing_cards)) { 110 | $this->card_token = null; 111 | } 112 | } 113 | else { 114 | $this->model->billing()->create($properties); 115 | } 116 | } 117 | 118 | if ($this->card_token) { 119 | $this->card = $this->model->creditcards()->create($this->card_token)->id; 120 | $this->card_token = null; 121 | } 122 | 123 | $gateway_charge = \Mmanos\Billing\Facades\Billing::charge(null, $this->model->gatewayCustomer())->create($amount, array_merge($properties, array( 124 | 'card_token' => $this->card_token, 125 | 'card' => $this->card, 126 | ))); 127 | 128 | return new Charge($this->model, $gateway_charge); 129 | } 130 | 131 | /** 132 | * The credit card token to assign to a new charge. 133 | * 134 | * @param string $card_token 135 | * 136 | * @return Charges 137 | */ 138 | public function withCardToken($card_token) 139 | { 140 | $this->card_token = $card_token; 141 | return $this; 142 | } 143 | 144 | /** 145 | * The credit card id or array to assign to a new charge. 146 | * 147 | * @param string|array $card 148 | * 149 | * @return Charges 150 | */ 151 | public function withCard($card) 152 | { 153 | $this->card = is_array($card) ? Arr::get($card, 'id') : $card; 154 | return $this; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/Mmanos/Billing/CustomerBillableTrait/Creditcard.php: -------------------------------------------------------------------------------- 1 | model = $model; 40 | $this->card = $card; 41 | 42 | if (empty($info)) { 43 | $info = $card->info(); 44 | } 45 | 46 | $this->info = $info; 47 | } 48 | 49 | /** 50 | * Update this customer credit card in the billing gateway. 51 | * 52 | * @param array $properties 53 | * 54 | * @return Creditcard 55 | */ 56 | public function update(array $properties) 57 | { 58 | if (!$this->model->readyForBilling()) { 59 | return $this; 60 | } 61 | 62 | $this->card->update($properties); 63 | $this->info = $this->card->info(); 64 | 65 | $cards = array(); 66 | foreach ($this->model->billing_cards as $c) { 67 | $cards[] = (Arr::get($c, 'id') == $this->id) ? $this->info : $c; 68 | } 69 | $this->model->billing_cards = $cards; 70 | $this->model->save(); 71 | 72 | return $this; 73 | } 74 | 75 | /** 76 | * Delete this customer credit card in the billing gateway. 77 | * 78 | * @return Creditcard 79 | */ 80 | public function delete() 81 | { 82 | if (!$this->model->readyForBilling()) { 83 | return $this; 84 | } 85 | 86 | $this->card->delete(); 87 | 88 | $cards = array(); 89 | foreach ($this->model->billing_cards as $c) { 90 | if (Arr::get($c, 'id') != $this->id) { 91 | $cards[] = $c; 92 | } 93 | } 94 | $this->model->billing_cards = $cards; 95 | $this->model->save(); 96 | 97 | // Refresh all subscription records that referenced this card. 98 | if ($subscriptions = $this->model->subscriptionModelsArray()) { 99 | foreach ($subscriptions as $subscription) { 100 | if ($subscription->billingIsActive() && $subscription->billing_card == $this->id) { 101 | $subscription->subscription()->refresh(); 102 | } 103 | } 104 | } 105 | 106 | $this->info = array('id' => $this->id); 107 | 108 | return $this; 109 | } 110 | 111 | /** 112 | * Convert this instance to an array. 113 | * 114 | * @return array 115 | */ 116 | public function toArray() 117 | { 118 | return $this->info; 119 | } 120 | 121 | /** 122 | * Dynamically check a values existence from the creditcard. 123 | * 124 | * @param string $key 125 | * 126 | * @return bool 127 | */ 128 | public function __isset($key) 129 | { 130 | return isset($this->info[$key]); 131 | } 132 | 133 | /** 134 | * Dynamically get values from the creditcard. 135 | * 136 | * @param string $key 137 | * 138 | * @return mixed 139 | */ 140 | public function __get($key) 141 | { 142 | return isset($this->info[$key]) ? $this->info[$key] : null; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/Mmanos/Billing/CustomerBillableTrait/Creditcards.php: -------------------------------------------------------------------------------- 1 | model = $model; 38 | } 39 | 40 | /** 41 | * Fetch the credit cards. 42 | * 43 | * @return array 44 | */ 45 | public function get() 46 | { 47 | $cards = array(); 48 | 49 | foreach ($this->model->billing_cards as $info) { 50 | $cards[] = new Creditcard( 51 | $this->model, 52 | $this->model->gatewayCustomer()->card(Arr::get($info, 'id')), 53 | $info 54 | ); 55 | } 56 | 57 | return $cards; 58 | } 59 | 60 | /** 61 | * Find and return the first credit card. 62 | * 63 | * @return Creditcard 64 | */ 65 | public function first() 66 | { 67 | if (empty($this->model->billing_cards)) { 68 | return null; 69 | } 70 | 71 | $info = $this->model->billing_cards[0]; 72 | 73 | return new Creditcard( 74 | $this->model, 75 | $this->model->gatewayCustomer()->card(Arr::get($info, 'id')), 76 | $info 77 | ); 78 | } 79 | 80 | /** 81 | * Find and return a credit card. 82 | * 83 | * @param mixed $id 84 | * 85 | * @return Creditcard 86 | */ 87 | public function find($id) 88 | { 89 | foreach ($this->model->billing_cards as $info) { 90 | if (Arr::get($info, 'id') == $id) { 91 | return new Creditcard( 92 | $this->model, 93 | $this->model->gatewayCustomer()->card(Arr::get($info, 'id')), 94 | $info 95 | ); 96 | } 97 | } 98 | 99 | return null; 100 | } 101 | 102 | /** 103 | * Add a new credit card to this customer in the billing gateway. 104 | * 105 | * @param string $card_token 106 | * 107 | * @return Creditcard 108 | */ 109 | public function create($card_token) 110 | { 111 | if (!$this->model->readyForBilling()) { 112 | return; 113 | } 114 | 115 | $card = new Creditcard( 116 | $this->model, 117 | $this->model->gatewayCustomer()->card()->create($card_token) 118 | ); 119 | 120 | $this->model->billing_cards = array_merge($this->model->billing_cards, array($card->toArray())); 121 | $this->model->save(); 122 | 123 | return $card; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/Mmanos/Billing/CustomerBillableTrait/Invoice.php: -------------------------------------------------------------------------------- 1 | model = $model; 40 | $this->invoice = $invoice; 41 | $this->info = $invoice->info(); 42 | } 43 | 44 | /** 45 | * Return an array of line items for this invoice. 46 | * 47 | * @return array 48 | */ 49 | public function items() 50 | { 51 | $items = array(); 52 | 53 | foreach ($this->items as $item) { 54 | $items[] = new InvoiceItem($this->model, $this->invoice, $item); 55 | } 56 | 57 | return $items; 58 | } 59 | 60 | /** 61 | * Get the invoice view. 62 | * 63 | * @param array $data 64 | * 65 | * @return View 66 | */ 67 | public function view(array $data = array()) 68 | { 69 | return View::make( 70 | 'laravel-billing::invoice', 71 | array_merge($data, array('invoice' => $this)) 72 | ); 73 | } 74 | 75 | /** 76 | * Get the rendered HTML content of the invoice view. 77 | * 78 | * @param array $data 79 | * 80 | * @return string 81 | */ 82 | public function render(array $data = array()) 83 | { 84 | return $this->view($data)->render(); 85 | } 86 | 87 | /** 88 | * Convert this instance to an array. 89 | * 90 | * @return array 91 | */ 92 | public function toArray() 93 | { 94 | return $this->info; 95 | } 96 | 97 | /** 98 | * Dynamically check a values existence from the invoice. 99 | * 100 | * @param string $key 101 | * 102 | * @return bool 103 | */ 104 | public function __isset($key) 105 | { 106 | return isset($this->info[$key]); 107 | } 108 | 109 | /** 110 | * Dynamically get values from the invoice. 111 | * 112 | * @param string $key 113 | * 114 | * @return mixed 115 | */ 116 | public function __get($key) 117 | { 118 | return isset($this->info[$key]) ? $this->info[$key] : null; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/Mmanos/Billing/CustomerBillableTrait/InvoiceItem.php: -------------------------------------------------------------------------------- 1 | model = $model; 47 | $this->invoice = $invoice; 48 | $this->info = $item; 49 | } 50 | 51 | /** 52 | * Return the subscription helper object associated with this invoice item. 53 | * 54 | * @return \Mmanos\Billing\SubscriptionBillableTrait\Subscription 55 | */ 56 | public function subscription() 57 | { 58 | if (empty($this->subscription_id)) { 59 | return null; 60 | } 61 | 62 | if (null !== $this->subscription) { 63 | return $this->subscription ? $this->subscription : null; 64 | } 65 | 66 | $this->subscription = false; 67 | if ($subscription = $this->model->subscriptions()->find($this->subscription_id)) { 68 | $this->subscription = $subscription; 69 | } 70 | 71 | return $this->subscription ? $this->subscription : null; 72 | } 73 | 74 | /** 75 | * Convert this instance to an array. 76 | * 77 | * @return array 78 | */ 79 | public function toArray() 80 | { 81 | return $this->info; 82 | } 83 | 84 | /** 85 | * Dynamically check a values existence from the invoice. 86 | * 87 | * @param string $key 88 | * 89 | * @return bool 90 | */ 91 | public function __isset($key) 92 | { 93 | return isset($this->info[$key]); 94 | } 95 | 96 | /** 97 | * Dynamically get values from the invoice. 98 | * 99 | * @param string $key 100 | * 101 | * @return mixed 102 | */ 103 | public function __get($key) 104 | { 105 | return isset($this->info[$key]) ? $this->info[$key] : null; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/Mmanos/Billing/CustomerBillableTrait/Invoices.php: -------------------------------------------------------------------------------- 1 | model = $model; 38 | } 39 | 40 | /** 41 | * Fetch the invoices. 42 | * 43 | * @return array 44 | */ 45 | public function get(array $parameters = array()) 46 | { 47 | $invoices = array(); 48 | 49 | foreach ($this->model->gatewayCustomer()->invoices($parameters) as $invoice) { 50 | $invoices[] = new Invoice($this->model, $invoice); 51 | } 52 | 53 | return $invoices; 54 | } 55 | 56 | /** 57 | * Find and return the first invoice. 58 | * 59 | * @return Invoice 60 | */ 61 | public function first() 62 | { 63 | return Arr::get($this->get(), 0); 64 | } 65 | 66 | /** 67 | * Find and return a credit card. 68 | * 69 | * @param mixed $id 70 | * 71 | * @return Invoice 72 | */ 73 | public function find($id) 74 | { 75 | try { 76 | return new Invoice($this->model, $this->model->gatewayCustomer()->invoice($id)); 77 | } catch (\Exception $e) {} 78 | 79 | return null; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Mmanos/Billing/CustomerBillableTrait/Subscriptions.php: -------------------------------------------------------------------------------- 1 | model = $model; 81 | $this->plan = $plan; 82 | } 83 | 84 | /** 85 | * Fetch the credit subscription. 86 | * 87 | * @return array 88 | */ 89 | public function get() 90 | { 91 | $subscriptions = array(); 92 | 93 | foreach ($this->model->subscriptionModelsArray() as $subscription) { 94 | $subscriptions[] = new \Mmanos\Billing\SubscriptionBillableTrait\Subscription( 95 | $subscription, 96 | $subscription->gatewaySubscription(), 97 | null, 98 | array( 99 | 'id' => $subscription->billing_subscription, 100 | 'plan' => $subscription->billing_plan, 101 | 'amount' => $subscription->billing_amount, 102 | 'interval' => $subscription->billing_interval, 103 | 'active' => $subscription->billing_active, 104 | 'quantity' => $subscription->billing_quantity, 105 | 'started_at' => null, 106 | 'period_started_at' => null, 107 | 'period_ends_at' => null, 108 | 'trial_ends_at' => $subscription->billing_trial_ends_at, 109 | 'card' => $subscription->billing_card, 110 | 'discounts' => $subscription->billing_subscription_discounts, 111 | ) 112 | ); 113 | } 114 | 115 | return $subscriptions; 116 | } 117 | 118 | /** 119 | * Find and return the first credit card. 120 | * 121 | * @return \Mmanos\Billing\SubscriptionBillableTrait\Subscription 122 | */ 123 | public function first() 124 | { 125 | return Arr::get($this->get(), 0); 126 | } 127 | 128 | /** 129 | * Find and return a credit card. 130 | * 131 | * @param mixed $id 132 | * 133 | * @return \Mmanos\Billing\SubscriptionBillableTrait\Subscription 134 | */ 135 | public function find($id) 136 | { 137 | foreach ($this->get() as $subscription) { 138 | if ($subscription->id == $id) { 139 | return $subscription; 140 | } 141 | } 142 | 143 | return null; 144 | } 145 | 146 | /** 147 | * Create this subscription in the billing gateway. 148 | * 149 | * @param \Illuminate\Database\Eloquent\Model $model 150 | * @param array $properties 151 | * 152 | * @return \Mmanos\Billing\SubscriptionBillableTrait\Subscription 153 | */ 154 | public function create(\Illuminate\Database\Eloquent\Model $model, array $properties = array()) 155 | { 156 | $subscription = $model->subscription($this->plan); 157 | 158 | if ($this->card_token) { 159 | $subscription->withCardToken($this->card_token); 160 | } 161 | if ($this->card) { 162 | $subscription->withCard($this->card); 163 | } 164 | if ($this->coupon) { 165 | $subscription->withCoupon($this->coupon); 166 | } 167 | if ($this->skip_trial) { 168 | $subscription->skipTrial(); 169 | } 170 | if ($this->is_free) { 171 | $subscription->isFree(); 172 | } 173 | 174 | return $subscription->create($properties); 175 | } 176 | 177 | /** 178 | * The coupon to apply to a new subscription. 179 | * 180 | * @param string $coupon 181 | * 182 | * @return Subscriptions 183 | */ 184 | public function withCoupon($coupon) 185 | { 186 | $this->coupon = $coupon; 187 | return $this; 188 | } 189 | 190 | /** 191 | * The credit card token to assign to a new subscription. 192 | * 193 | * @param string $card_token 194 | * 195 | * @return Subscriptions 196 | */ 197 | public function withCardToken($card_token) 198 | { 199 | $this->card_token = $card_token; 200 | return $this; 201 | } 202 | 203 | /** 204 | * The credit card id or array to assign to a new subscription. 205 | * 206 | * @param string|array $card 207 | * 208 | * @return Subscriptions 209 | */ 210 | public function withCard($card) 211 | { 212 | $this->card = is_array($card) ? Arr::get($card, 'id') : $card; 213 | return $this; 214 | } 215 | 216 | /** 217 | * Indicate that no trial should be enforced on the operation. 218 | * 219 | * @return Subscriptions 220 | */ 221 | public function skipTrial() 222 | { 223 | $this->skip_trial = true; 224 | return $this; 225 | } 226 | 227 | /** 228 | * Indicate that this subscription should be free and not stored in the billing gateway. 229 | * 230 | * @return Subscriptions 231 | */ 232 | public function isFree() 233 | { 234 | $this->is_free = true; 235 | return $this; 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /src/Mmanos/Billing/EloquentBillableRepository.php: -------------------------------------------------------------------------------- 1 | where('billing_id', $billing_id)->first()) { 24 | return $customer; 25 | } 26 | } 27 | 28 | return null; 29 | } 30 | 31 | /** 32 | * Find a billing subscription implementation by subscription ID. 33 | * 34 | * @param mixed $subscription_id 35 | * 36 | * @return \Illuminate\Database\Eloquent\Model 37 | */ 38 | public static function findSubscription($subscription_id) 39 | { 40 | if (!$subscription_id) { 41 | return null; 42 | } 43 | 44 | foreach (Config::get('laravel-billing::subscription_models') as $model) { 45 | $query = new $model; 46 | 47 | if ($subscription = $query->where('billing_subscription', $subscription_id)->first()) { 48 | return $subscription; 49 | } 50 | } 51 | 52 | return null; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Mmanos/Billing/Facades/Billing.php: -------------------------------------------------------------------------------- 1 | gateway = $gateway; 42 | 43 | if ($id instanceof Braintree_CreditCard) { 44 | $this->braintree_card = $id; 45 | $this->id = $this->braintree_card->token; 46 | } 47 | else if (null !== $id) { 48 | $this->id = $id; 49 | } 50 | } 51 | 52 | /** 53 | * Gets the id of this instance. 54 | * 55 | * @return mixed 56 | */ 57 | public function id() 58 | { 59 | return $this->id; 60 | } 61 | 62 | /** 63 | * Gets info for a card. 64 | * 65 | * @return array|null 66 | */ 67 | public function info() 68 | { 69 | if (!$this->id) { 70 | return null; 71 | } 72 | 73 | if (!$this->braintree_card) { 74 | $this->braintree_card = Braintree_CreditCard::find($this->id); 75 | } 76 | 77 | if (!$this->braintree_card) { 78 | return null; 79 | } 80 | 81 | $return = array( 82 | 'id' => $this->id, 83 | 'last4' => $this->braintree_card->last4, 84 | 'brand' => $this->braintree_card->cardType, 85 | 'exp_month' => $this->braintree_card->expirationMonth, 86 | 'exp_year' => $this->braintree_card->expirationYear, 87 | 'name' => $this->braintree_card->cardholderName, 88 | 'address_line1' => null, 89 | 'address_line2' => null, 90 | 'address_city' => null, 91 | 'address_state' => null, 92 | 'address_zip' => null, 93 | 'address_country' => null, 94 | ); 95 | 96 | if ($this->braintree_card->billingAddress) { 97 | $return = array_merge($return, array( 98 | 'address_line1' => $this->braintree_card->billingAddress->streetAddress, 99 | 'address_line2' => $this->braintree_card->billingAddress->extendedAddress, 100 | 'address_city' => $this->braintree_card->billingAddress->locality, 101 | 'address_state' => $this->braintree_card->billingAddress->region, 102 | 'address_zip' => $this->braintree_card->billingAddress->postalCode, 103 | 'address_country' => $this->braintree_card->billingAddress->countryName, 104 | )); 105 | } 106 | 107 | return $return; 108 | } 109 | 110 | /** 111 | * Create a new card. 112 | * 113 | * @param string $card_token 114 | * 115 | * @return Card 116 | */ 117 | public function create($card_token) 118 | { 119 | // Braintree does not support creating a card from a token. 120 | // You must use their transparent redirect. 121 | return $this; 122 | } 123 | 124 | /** 125 | * Update a card. 126 | * 127 | * @param array $properties 128 | * 129 | * @return Card 130 | */ 131 | public function update(array $properties = array()) 132 | { 133 | $props = array(); 134 | 135 | if (!empty($properties['name'])) { 136 | $props['cardholderName'] = $properties['name']; 137 | } 138 | if (!empty($properties['exp_month'])) { 139 | $props['expirationMonth'] = $properties['exp_month']; 140 | } 141 | if (!empty($properties['exp_year'])) { 142 | $props['expirationYear'] = $properties['exp_year']; 143 | } 144 | if (!empty($properties['address_line1'])) { 145 | $props['billingAddress']['streetAddress'] = $properties['address_line1']; 146 | } 147 | if (!empty($properties['address_line2'])) { 148 | $props['billingAddress']['extendedAddress'] = $properties['address_line2']; 149 | } 150 | if (!empty($properties['address_city'])) { 151 | $props['billingAddress']['locality'] = $properties['address_city']; 152 | } 153 | if (!empty($properties['address_state'])) { 154 | $props['billingAddress']['region'] = $properties['address_state']; 155 | } 156 | if (!empty($properties['address_zip'])) { 157 | $props['billingAddress']['postalCode'] = $properties['address_zip']; 158 | } 159 | if (!empty($properties['address_country'])) { 160 | $props['billingAddress']['countryName'] = $properties['address_country']; 161 | } 162 | 163 | if (!empty($props['billingAddress'])) { 164 | $props['billingAddress']['options'] = array('updateExisting' => true); 165 | } 166 | 167 | Braintree_CreditCard::update($this->id, $props); 168 | $this->braintree_card = null; 169 | 170 | return $this; 171 | } 172 | 173 | /** 174 | * Delete a card. 175 | * 176 | * @return Card 177 | */ 178 | public function delete() 179 | { 180 | Braintree_CreditCard::delete($this->id); 181 | $this->braintree_card = null; 182 | return $this; 183 | } 184 | 185 | /** 186 | * Gets the native card response. 187 | * 188 | * @return Braintree_CreditCard 189 | */ 190 | public function getNativeResponse() 191 | { 192 | $this->info(); 193 | return $this->braintree_card; 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/Mmanos/Billing/Gateways/Braintree/Charge.php: -------------------------------------------------------------------------------- 1 | gateway = $gateway; 50 | $this->braintree_customer = $customer; 51 | 52 | if ($id instanceof Braintree_Transaction) { 53 | $this->braintree_charge = $id; 54 | $this->id = $this->braintree_charge->id; 55 | } 56 | else if (null !== $id) { 57 | $this->id = $id; 58 | } 59 | } 60 | 61 | /** 62 | * Gets the id of this instance. 63 | * 64 | * @return mixed 65 | */ 66 | public function id() 67 | { 68 | return $this->id; 69 | } 70 | 71 | /** 72 | * Gets info for a charge. 73 | * 74 | * @return array|null 75 | */ 76 | public function info() 77 | { 78 | if (!$this->id || !$this->braintree_customer) { 79 | return null; 80 | } 81 | 82 | if (!$this->braintree_charge) { 83 | $this->braintree_charge = Braintree_Transaction::find($this->id); 84 | 85 | if ($this->braintree_charge->customer && $this->braintree_customer->id != $this->braintree_charge->customer['id']) { 86 | return $this->braintree_charge = null; 87 | } 88 | } 89 | 90 | if (!$this->braintree_charge) { 91 | return null; 92 | } 93 | 94 | $paid = in_array($this->braintree_charge->status, array('submitted_for_settlement', 'settled', 'settling')); 95 | $refunded = empty($this->braintree_charge->refundIds) ? false : true; 96 | $captured = true; 97 | if (!$refunded && in_array($this->braintree_charge->status, array('voided'))) { 98 | $refunded = true; 99 | } 100 | if (!in_array($this->braintree_charge->status, array('authorized', 'authorization_expired'))) { 101 | $captured = false; 102 | } 103 | 104 | return array( 105 | 'id' => $this->id, 106 | 'created_at' => date('Y-m-d H:i:s', $this->braintree_charge->createdAt->getTimestamp()), 107 | 'amount' => ((float) $this->braintree_charge->amount * 100), 108 | 'paid' => $paid, 109 | 'refunded' => $refunded, 110 | 'captured' => $captured, 111 | 'card' => $this->braintree_charge->creditCardDetails->token, 112 | 'invoice_id' => $this->id, 113 | 'description' => null, 114 | ); 115 | } 116 | 117 | /** 118 | * Create a new charge. 119 | * 120 | * @param int $amount 121 | * @param array $properties 122 | * 123 | * @return Charge 124 | */ 125 | public function create($amount, array $properties = array()) 126 | { 127 | if (!$token = Arr::get($properties, 'card')) { 128 | $cards = $this->braintree_customer->creditCards; 129 | $token = $cards[0]->token; 130 | } 131 | 132 | $braintree_charge = Braintree_Transaction::sale(array( 133 | 'customerId' => $this->braintree_customer->id, 134 | 'amount' => number_format($amount / 100, 2), 135 | 'paymentMethodToken' => $token, 136 | ))->transaction; 137 | 138 | $this->id = $braintree_charge->id; 139 | $this->braintree_charge = null; 140 | 141 | if (Arr::get($properties, 'capture', true)) { 142 | $this->capture(); 143 | } 144 | 145 | return $this; 146 | } 147 | 148 | /** 149 | * Capture a preauthorized charge. 150 | * 151 | * @param array $properties 152 | * 153 | * @return Charge 154 | */ 155 | public function capture(array $properties = array()) 156 | { 157 | if ($amount = Arr::get($properties, 'amount')) { 158 | Braintree_Transaction::submitForSettlement($this->id, number_format($amount / 100, 2)); 159 | } 160 | else { 161 | Braintree_Transaction::submitForSettlement($this->id); 162 | } 163 | 164 | $this->braintree_charge = null; 165 | 166 | return $this; 167 | } 168 | 169 | /** 170 | * Refund a charge. 171 | * 172 | * @param array $properties 173 | * 174 | * @return Charge 175 | */ 176 | public function refund(array $properties = array()) 177 | { 178 | $this->info(); 179 | 180 | if (in_array($this->braintree_charge->status, array('settled', 'settling'))) { 181 | if ($amount = Arr::get($properties, 'amount')) { 182 | Braintree_Transaction::refund($this->id, number_format($amount / 100, 2)); 183 | } 184 | else { 185 | Braintree_Transaction::refund($this->id); 186 | } 187 | } 188 | else { 189 | Braintree_Transaction::void($this->id); 190 | } 191 | 192 | $this->braintree_charge = null; 193 | 194 | return $this; 195 | } 196 | 197 | /** 198 | * Gets the native charge response. 199 | * 200 | * @return Braintree_Transaction 201 | */ 202 | public function getNativeResponse() 203 | { 204 | $this->info(); 205 | return $this->braintree_charge; 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/Mmanos/Billing/Gateways/Braintree/Customer.php: -------------------------------------------------------------------------------- 1 | gateway = $gateway; 43 | 44 | if ($id instanceof Braintree_Customer) { 45 | $this->braintree_customer = $id; 46 | $this->id = $this->braintree_customer->id; 47 | } 48 | else if (null !== $id) { 49 | $this->id = $id; 50 | } 51 | } 52 | 53 | /** 54 | * Gets the id of this instance. 55 | * 56 | * @return mixed 57 | */ 58 | public function id() 59 | { 60 | return $this->id; 61 | } 62 | 63 | /** 64 | * Gets info for a customer. 65 | * 66 | * @return array|null 67 | */ 68 | public function info() 69 | { 70 | if (!$this->id) { 71 | return null; 72 | } 73 | 74 | if (!$this->braintree_customer) { 75 | $this->braintree_customer = Braintree_Customer::find($this->id); 76 | } 77 | 78 | if (!$this->braintree_customer) { 79 | return null; 80 | } 81 | 82 | return array( 83 | 'id' => $this->id, 84 | 'description' => null, 85 | 'email' => $this->braintree_customer->email, 86 | 'created_at' => date('Y-m-d H:i:s', $this->braintree_customer->createdAt->getTimestamp()), 87 | 'discounts' => array(), // Customer-specific discounts not supported. 88 | ); 89 | } 90 | 91 | /** 92 | * Create a new customer. 93 | * 94 | * @param array $properties 95 | * 96 | * @return Customer 97 | */ 98 | public function create(array $properties = array()) 99 | { 100 | $this->braintree_customer = Braintree_Customer::create(array( 101 | 'email' => Arr::get($properties, 'email'), 102 | ))->customer; 103 | 104 | $this->id = $this->braintree_customer->id; 105 | 106 | return $this; 107 | } 108 | 109 | /** 110 | * Update a customer. 111 | * 112 | * @param array $properties 113 | * 114 | * @return Customer 115 | */ 116 | public function update(array $properties = array()) 117 | { 118 | $props = array(); 119 | if (!empty($properties['email'])) { 120 | $props['email'] = $properties['email']; 121 | } 122 | 123 | Braintree_Customer::update($this->id, $props); 124 | $this->braintree_customer = null; 125 | 126 | return $this; 127 | } 128 | 129 | /** 130 | * Delete a customer. 131 | * 132 | * @return Customer 133 | */ 134 | public function delete() 135 | { 136 | Braintree_Customer::delete($this->id); 137 | $this->braintree_customer = null; 138 | return $this; 139 | } 140 | 141 | /** 142 | * Gets all subscriptions for a customer. 143 | * 144 | * @return array 145 | */ 146 | public function subscriptions() 147 | { 148 | $this->info(); 149 | 150 | if (!$this->braintree_customer) { 151 | return array(); 152 | } 153 | 154 | $subscriptions_array = array(); 155 | foreach ($this->braintree_customer->creditCards as $card) { 156 | foreach ($card->subscriptions as $subscription) { 157 | $subscriptions_array[] = $this->gateway->subscription($subscription, $this); 158 | } 159 | } 160 | 161 | return $subscriptions_array; 162 | } 163 | 164 | /** 165 | * Gets all credit cards for a customer. 166 | * 167 | * @return array 168 | */ 169 | public function cards() 170 | { 171 | $this->info(); 172 | 173 | if (!$this->braintree_customer) { 174 | return array(); 175 | } 176 | 177 | $cards = $this->braintree_customer->creditCards; 178 | 179 | $cards_array = array(); 180 | foreach ($cards as $card) { 181 | $cards_array[] = $this->card($card); 182 | } 183 | 184 | return $cards_array; 185 | } 186 | 187 | /** 188 | * Fetch a customer card instance. 189 | * 190 | * @param mixed $id 191 | * 192 | * @return Card 193 | */ 194 | public function card($id = null) 195 | { 196 | return new Card($this->gateway, $id); 197 | } 198 | 199 | /** 200 | * Gets all invoices for a customer. 201 | * 202 | * @return array 203 | */ 204 | public function invoices(array $parameters = array()) 205 | { 206 | if (!$this->id) { 207 | return array(); 208 | } 209 | 210 | $invoices = Braintree_Transaction::search(array( 211 | Braintree_TransactionSearch::customerId()->is($this->id), 212 | )); 213 | 214 | $invoices_array = array(); 215 | foreach ($invoices as $invoice) { 216 | if (empty($invoice->subscriptionId)) { 217 | continue; 218 | } 219 | 220 | $invoices_array[] = $this->invoice($invoice); 221 | } 222 | 223 | return $invoices_array; 224 | } 225 | 226 | /** 227 | * Fetch an invoice instance. 228 | * 229 | * @param mixed $id 230 | * 231 | * @return Invoice 232 | */ 233 | public function invoice($id = null) 234 | { 235 | return new Invoice($this->gateway, $this->getNativeResponse(), $id); 236 | } 237 | 238 | /** 239 | * Gets all charges for a customer. 240 | * 241 | * @return array 242 | */ 243 | public function charges() 244 | { 245 | if (!$this->id) { 246 | return array(); 247 | } 248 | 249 | $charges = Braintree_Transaction::search(array( 250 | Braintree_TransactionSearch::customerId()->is($this->id), 251 | )); 252 | 253 | $charges_array = array(); 254 | foreach ($charges as $charge) { 255 | $charges_array[] = $this->charge($charge); 256 | } 257 | 258 | return $charges_array; 259 | } 260 | 261 | /** 262 | * Fetch an charge instance. 263 | * 264 | * @param mixed $id 265 | * 266 | * @return Charge 267 | */ 268 | public function charge($id = null) 269 | { 270 | return new Charge($this->gateway, $this->getNativeResponse(), $id); 271 | } 272 | 273 | /** 274 | * Gets the native customer response. 275 | * 276 | * @return Braintree_Customer 277 | */ 278 | public function getNativeResponse() 279 | { 280 | $this->info(); 281 | return $this->braintree_customer; 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /src/Mmanos/Billing/Gateways/Braintree/Gateway.php: -------------------------------------------------------------------------------- 1 | connection = $connection; 29 | 30 | Braintree_Configuration::environment($connection['environment']); 31 | Braintree_Configuration::merchantId($connection['merchant']); 32 | Braintree_Configuration::publicKey($connection['public']); 33 | Braintree_Configuration::privateKey($connection['private']); 34 | } 35 | 36 | /** 37 | * Fetch a customer instance. 38 | * 39 | * @param mixed $id 40 | * 41 | * @return Customer 42 | */ 43 | public function customer($id = null) 44 | { 45 | return new Customer($this, $id); 46 | } 47 | 48 | /** 49 | * Fetch a subscription instance. 50 | * 51 | * @param mixed $id 52 | * @param Customer $customer 53 | * 54 | * @return Subscription 55 | */ 56 | public function subscription($id = null, CustomerInterface $customer = null) 57 | { 58 | if ($customer) { 59 | $customer = $customer->getNativeResponse(); 60 | } 61 | 62 | return new Subscription($this, $customer, $id); 63 | } 64 | 65 | /** 66 | * Fetch a charge instance. 67 | * 68 | * @param mixed $id 69 | * @param Customer $customer 70 | * 71 | * @return Charge 72 | */ 73 | public function charge($id = null, CustomerInterface $customer = null) 74 | { 75 | if ($customer) { 76 | $customer = $customer->getNativeResponse(); 77 | } 78 | 79 | return new Charge($this, $customer, $id); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Mmanos/Billing/Gateways/Braintree/Invoice.php: -------------------------------------------------------------------------------- 1 | gateway = $gateway; 50 | $this->braintree_customer = $customer; 51 | 52 | if ($id instanceof Braintree_Transaction) { 53 | $this->braintree_invoice = $id; 54 | $this->id = $this->braintree_invoice->id; 55 | } 56 | else if (null !== $id) { 57 | $this->id = $id; 58 | } 59 | } 60 | 61 | /** 62 | * Gets the id of this instance. 63 | * 64 | * @return mixed 65 | */ 66 | public function id() 67 | { 68 | return $this->id; 69 | } 70 | 71 | /** 72 | * Gets info for an invoice. 73 | * 74 | * @return array|null 75 | */ 76 | public function info() 77 | { 78 | if (!$this->id || !$this->braintree_customer) { 79 | return null; 80 | } 81 | 82 | if (!$this->braintree_invoice) { 83 | $this->braintree_invoice = Braintree_Transaction::find($this->id); 84 | 85 | if ($this->braintree_invoice->customer && $this->braintree_customer->id != $this->braintree_invoice->customer['id']) { 86 | return $this->braintree_invoice = null; 87 | } 88 | } 89 | 90 | if (!$this->braintree_invoice) { 91 | return null; 92 | } 93 | 94 | $discounts = array(); 95 | $total_off = 0; 96 | foreach ($this->braintree_invoice->discounts as $discount) { 97 | $discounts[] = array( 98 | 'coupon' => $discount->id, 99 | 'amount_off' => ((float) $discount->amount * 100), 100 | 'percent_off' => null, 101 | 'started_at' => null, 102 | 'ends_at' => null, 103 | ); 104 | $total_off += ((float) $discount->amount * 100); 105 | } 106 | 107 | $period_started_at = null; 108 | $period_ends_at = null; 109 | if ($this->braintree_invoice->subscriptionDetails) { 110 | if ($this->braintree_invoice->subscriptionDetails->billingPeriodStartDate) { 111 | $period_started_at = date('Y-m-d H:i:s', $this->braintree_invoice->subscriptionDetails->billingPeriodStartDate->getTimestamp()); 112 | } 113 | if ($this->braintree_invoice->subscriptionDetails->billingPeriodEndDate) { 114 | $period_ends_at = date('Y-m-d H:i:s', $this->braintree_invoice->subscriptionDetails->billingPeriodEndDate->getTimestamp()); 115 | } 116 | } 117 | 118 | $items = array(array( 119 | 'id' => $this->braintree_invoice->subscriptionId ?: 1, 120 | 'amount' => ((float) $this->braintree_invoice->amount * 100) + $total_off, 121 | 'period_start' => $period_started_at, 122 | 'period_end' => $period_ends_at, 123 | 'description' => null, 124 | 'subscription_id' => $this->braintree_invoice->subscriptionId, 125 | 'quantity' => 1, 126 | )); 127 | 128 | $paid = in_array($this->braintree_invoice->status, array('submitted_for_settlement', 'settled', 'settling')); 129 | $closed = $paid || in_array($this->braintree_invoice->status, array('voided')); 130 | 131 | return array( 132 | 'id' => $this->id, 133 | 'date' => date('Y-m-d H:i:s', $this->braintree_invoice->createdAt->getTimestamp()), 134 | 'total' => ((float) $this->braintree_invoice->amount * 100), 135 | 'subtotal' => ((float) $this->braintree_invoice->amount * 100) + $total_off, 136 | 'amount' => ((float) $this->braintree_invoice->amount * 100), 137 | 'starting_balance' => null, 138 | 'ending_balance' => null, 139 | 'closed' => $closed, 140 | 'paid' => $paid, 141 | 'discounts' => $discounts, 142 | 'items' => $items, 143 | ); 144 | } 145 | 146 | /** 147 | * Gets the native invoice response. 148 | * 149 | * @return Braintree_Transaction 150 | */ 151 | public function getNativeResponse() 152 | { 153 | $this->info(); 154 | return $this->braintree_invoice; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/Mmanos/Billing/Gateways/Braintree/Subscription.php: -------------------------------------------------------------------------------- 1 | gateway = $gateway; 51 | $this->braintree_customer = $customer; 52 | 53 | if ($id instanceof Braintree_Subscription) { 54 | $this->braintree_subscription = $id; 55 | $this->id = $this->braintree_subscription->id; 56 | } 57 | else if (null !== $id) { 58 | $this->id = $id; 59 | } 60 | } 61 | 62 | /** 63 | * Gets the id of this instance. 64 | * 65 | * @return mixed 66 | */ 67 | public function id() 68 | { 69 | return $this->id; 70 | } 71 | 72 | /** 73 | * Gets info for a subscription. 74 | * 75 | * @return array|null 76 | */ 77 | public function info() 78 | { 79 | if (!$this->id) { 80 | return null; 81 | } 82 | 83 | if (!$this->braintree_subscription) { 84 | $this->braintree_subscription = Braintree_Subscription::find($this->id); 85 | } 86 | 87 | if (!$this->braintree_subscription) { 88 | return null; 89 | } 90 | 91 | $trial_ends_at = null; 92 | if ($this->braintree_subscription->trialPeriod) { 93 | $created_at = clone $this->braintree_subscription->createdAt; 94 | $trial_ends_at = date('Y-m-d H:i:s', $created_at->add( 95 | date_interval_create_from_date_string( 96 | $this->braintree_subscription->trialDuration . ' ' 97 | . $this->braintree_subscription->trialDurationUnit . 's' 98 | ) 99 | )->getTimestamp()); 100 | } 101 | 102 | $period_started_at = date('Y-m-d H:i:s', $this->braintree_subscription->createdAt->getTimestamp()); 103 | if ($this->braintree_subscription->billingPeriodStartDate) { 104 | $period_started_at = date('Y-m-d H:i:s', $this->braintree_subscription->billingPeriodStartDate->getTimestamp()); 105 | } 106 | $period_ends_at = $trial_ends_at; 107 | if ($this->braintree_subscription->billingPeriodEndDate) { 108 | $period_ends_at = date('Y-m-d H:i:s', $this->braintree_subscription->billingPeriodEndDate->getTimestamp()); 109 | } 110 | 111 | $interval = 1; 112 | foreach (Braintree_Plan::all() as $plan) { 113 | if ($plan->id == $this->braintree_subscription->planId) { 114 | $interval = $plan->billingFrequency; 115 | break; 116 | } 117 | } 118 | 119 | $discounts = array(); 120 | foreach ($this->braintree_subscription->discounts as $discount) { 121 | $started_at = date('Y-m-d H:i:s', $this->braintree_subscription->firstBillingDate->getTimestamp()); 122 | $ends_at = null; 123 | if (!$discount->neverExpires) { 124 | $cycle = $interval * 60 * 60 * 24 * 30; 125 | $ends_at = date('Y-m-d H:i:s', strtotime($started_at) + ($cycle * $discount->numberOfBillingCycles)); 126 | } 127 | 128 | $discounts[] = array( 129 | 'coupon' => $discount->id, 130 | 'amount_off' => ((float) $discount->amount * 100), 131 | 'percent_off' => null, 132 | 'started_at' => $started_at, 133 | 'ends_at' => $ends_at, 134 | ); 135 | } 136 | 137 | return array( 138 | 'id' => $this->id, 139 | 'plan' => $this->braintree_subscription->planId, 140 | 'amount' => ((float) $this->braintree_subscription->price * 100), 141 | 'interval' => ($interval == 1) ? 'month' : (($interval == 3) ? 'quarter' : 'year'), 142 | 'active' => ('Canceled' != $this->braintree_subscription->status), 143 | 'quantity' => 1, 144 | 'started_at' => date('Y-m-d H:i:s', $this->braintree_subscription->createdAt->getTimestamp()), 145 | 'period_started_at' => $period_started_at, 146 | 'period_ends_at' => $period_ends_at, 147 | 'trial_ends_at' => $trial_ends_at, 148 | 'card' => $this->braintree_subscription->paymentMethodToken, 149 | 'discounts' => $discounts, 150 | ); 151 | } 152 | 153 | /** 154 | * Create a new subscription. 155 | * 156 | * @param mixed $plan 157 | * @param array $properties 158 | * 159 | * @return Subscription 160 | */ 161 | public function create($plan, array $properties = array()) 162 | { 163 | if (!$token = Arr::get($properties, 'card')) { 164 | $cards = $this->braintree_customer->creditCards; 165 | $token = $cards[0]->token; 166 | } 167 | 168 | $props = array( 169 | 'paymentMethodToken' => $token, 170 | 'planId' => $plan, 171 | ); 172 | 173 | if (!empty($properties['coupon'])) { 174 | $props['discounts']['add'][] = array('inheritedFromId' => $properties['coupon']); 175 | } 176 | 177 | if (!empty($properties['trial_ends_at'])) { 178 | $now = time(); 179 | $tends = strtotime($properties['trial_ends_at']); 180 | if ($tends < $now) { 181 | $props['trialDuration'] = 0; 182 | $props['trialDurationUnit'] = 'day'; 183 | } 184 | else if ($tends - $now < (60*60*24*30)) { 185 | $props['trialDuration'] = round(($tends - $now) / (60*60*24)); 186 | $props['trialDurationUnit'] = 'day'; 187 | } 188 | else { 189 | $props['trialDuration'] = round(($tends - $now) / (60*60*24*30)); 190 | $props['trialDurationUnit'] = 'month'; 191 | } 192 | } 193 | 194 | $braintree_subscription = Braintree_Subscription::create($props)->subscription; 195 | 196 | $this->id = $braintree_subscription->id; 197 | $this->braintree_subscription = null; 198 | 199 | return $this; 200 | } 201 | 202 | /** 203 | * Update a subscription. 204 | * 205 | * @param array $properties 206 | * 207 | * @return Subscription 208 | */ 209 | public function update(array $properties = array()) 210 | { 211 | $info = $this->info(); 212 | 213 | // Braintree won't let you reactivate a canceled subscription. So create a new one. 214 | if ('Canceled' == $this->braintree_subscription->status) { 215 | $trial_ends_at = null; 216 | if ($this->braintree_subscription->trialPeriod) { 217 | $created_at = clone $this->braintree_subscription->createdAt; 218 | $trial_ends_at = date('Y-m-d H:i:s', $created_at->add( 219 | date_interval_create_from_date_string( 220 | $this->braintree_subscription->trialDuration . ' ' 221 | . $this->braintree_subscription->trialDurationUnit . 's' 222 | ) 223 | )->getTimestamp()); 224 | } 225 | 226 | return $this->create( 227 | Arr::get($properties, 'plan', $this->braintree_subscription->planId), 228 | array_merge(array( 229 | 'card' => $this->braintree_subscription->paymentMethodToken, 230 | 'trial_ends_at' => $trial_ends_at, 231 | ), $properties) 232 | ); 233 | } 234 | 235 | // Braintree won't let you update the trial period. 236 | // So if we want to cancel an existing trial period, delete the subscription and recreate it. 237 | if ($info['trial_ends_at'] 238 | && strtotime($info['trial_ends_at']) > time() 239 | && !empty($properties['trial_ends_at']) 240 | && strtotime($properties['trial_ends_at']) <= time() 241 | ) { 242 | $plan = Arr::get($properties, 'plan', $this->braintree_subscription->planId); 243 | $props = array_merge(array( 244 | 'card' => $this->braintree_subscription->paymentMethodToken, 245 | 'trial_ends_at' => $properties['trial_ends_at'], 246 | ), $properties); 247 | 248 | $this->cancel(); 249 | $this->braintree_subscription = null; 250 | $this->id = null; 251 | 252 | return $this->create($plan, $props); 253 | } 254 | 255 | $props = array(); 256 | 257 | if (!empty($properties['plan'])) { 258 | $props['planId'] = $properties['plan']; 259 | 260 | foreach (Braintree_Plan::all() as $plan) { 261 | if ($plan->id == $properties['plan']) { 262 | $props['price'] = $plan->price; 263 | } 264 | } 265 | } 266 | if (!empty($properties['coupon'])) { 267 | $props['discounts']['add']['inheritedFromId'] = $properties['coupon']; 268 | } 269 | 270 | if (!empty($properties['card'])) { 271 | $props['paymentMethodToken'] = $properties['card']; 272 | } 273 | 274 | Braintree_Subscription::update($this->id, $props); 275 | $this->braintree_subscription = null; 276 | 277 | return $this; 278 | } 279 | 280 | /** 281 | * Cancel a subscription. 282 | * 283 | * @param bool $at_period_end 284 | * 285 | * @return Subscription 286 | */ 287 | public function cancel($at_period_end = true) 288 | { 289 | Braintree_Subscription::cancel($this->id); 290 | $this->braintree_subscription = null; 291 | return $this; 292 | } 293 | 294 | /** 295 | * Gets the native subscription response. 296 | * 297 | * @return Braintree_Customer 298 | */ 299 | public function getNativeResponse() 300 | { 301 | $this->info(); 302 | return $this->braintree_subscription; 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /src/Mmanos/Billing/Gateways/Braintree/WebhookController.php: -------------------------------------------------------------------------------- 1 | kind)); 25 | 26 | if (method_exists($this, $method)) { 27 | return $this->{$method}($payload); 28 | } 29 | else { 30 | return $this->missingMethod(); 31 | } 32 | } 33 | 34 | /** 35 | * Handle a canceled subscription from Braintree. 36 | * 37 | * @param Braintree_WebhookNotification $payload 38 | * 39 | * @return \Symfony\Component\HttpFoundation\Response 40 | */ 41 | protected function handleSubscriptionCanceled(Braintree_WebhookNotification $payload) 42 | { 43 | if ($payload->subscription->id) { 44 | if ($subscription = $this->getSubscription($payload->subscription->id)) { 45 | $subscription->subscription()->refresh(); 46 | } 47 | } 48 | 49 | return new Response('Webhook Handled', 200); 50 | } 51 | 52 | /** 53 | * Handle a subscription who's trial just ended from Braintree. 54 | * 55 | * @param Braintree_WebhookNotification $payload 56 | * 57 | * @return \Symfony\Component\HttpFoundation\Response 58 | */ 59 | protected function handleSubscriptionTrialEnded(Braintree_WebhookNotification $payload) 60 | { 61 | if ($payload->subscription->id) { 62 | if ($subscription = $this->getSubscription($payload->subscription->id)) { 63 | $subscription->fireSubscriptionEvent('trialWillEnd', array( 64 | 'days' => 0, 65 | )); 66 | } 67 | } 68 | 69 | return new Response('Webhook Handled', 200); 70 | } 71 | 72 | /** 73 | * Handle a successful subscription (invoice) charge from Braintree. 74 | * 75 | * @param Braintree_WebhookNotification $payload 76 | * 77 | * @return \Symfony\Component\HttpFoundation\Response 78 | */ 79 | protected function handleSubscriptionChargedSuccessfully(Braintree_WebhookNotification $payload) 80 | { 81 | if ($payload->subscription->id && !empty($payload->subscription->transactions)) { 82 | if ($subscription = $this->getSubscription($payload->subscription->id)) { 83 | if ($customer = $subscription->customer()) { 84 | if ($invoice = $customer->invoices()->find($payload->subscription->transactions[0]->id)) { 85 | $customer->fireCustomerEvent('invoicePaymentSucceeded', $invoice); 86 | } 87 | } 88 | } 89 | } 90 | 91 | return new Response('Webhook Handled', 200); 92 | } 93 | 94 | /** 95 | * Handle a failed subscription (invoice) charge from Braintree. 96 | * 97 | * @param Braintree_WebhookNotification $payload 98 | * 99 | * @return \Symfony\Component\HttpFoundation\Response 100 | */ 101 | protected function handleSubscriptionChargedUnsuccessfully(Braintree_WebhookNotification $payload) 102 | { 103 | if ($payload->subscription->id && !empty($payload->subscription->transactions)) { 104 | if ($subscription = $this->getSubscription($payload->subscription->id)) { 105 | if ($customer = $subscription->customer()) { 106 | if ($invoice = $customer->invoices()->find($payload->subscription->transactions[0]->id)) { 107 | $customer->fireCustomerEvent('invoicePaymentFailed', $invoice, array()); 108 | } 109 | } 110 | } 111 | } 112 | 113 | return new Response('Webhook Handled', 200); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/Mmanos/Billing/Gateways/CardInterface.php: -------------------------------------------------------------------------------- 1 | gateway = $gateway; 48 | $this->local_customer = $customer; 49 | 50 | if ($id instanceof Models\Card) { 51 | $this->local_card = $id; 52 | $this->id = $this->local_card->id; 53 | } 54 | else if (null !== $id) { 55 | $this->id = $id; 56 | } 57 | } 58 | 59 | /** 60 | * Gets the id of this instance. 61 | * 62 | * @return mixed 63 | */ 64 | public function id() 65 | { 66 | return $this->id; 67 | } 68 | 69 | /** 70 | * Gets info for a card. 71 | * 72 | * @return array|null 73 | */ 74 | public function info() 75 | { 76 | if (!$this->id || !$this->local_customer) { 77 | return null; 78 | } 79 | 80 | if (!$this->local_card) { 81 | $this->local_card = $this->local_customer->cards()->where('id', $this->id)->first(); 82 | $this->gateway->apiDelay(); 83 | } 84 | 85 | if (!$this->local_card) { 86 | return null; 87 | } 88 | 89 | return array( 90 | 'id' => $this->id, 91 | 'last4' => $this->local_card->last4, 92 | 'brand' => $this->local_card->brand, 93 | 'exp_month' => $this->local_card->exp_month, 94 | 'exp_year' => $this->local_card->exp_year, 95 | 'name' => $this->local_card->name, 96 | 'address_line1' => $this->local_card->address_line1, 97 | 'address_line2' => $this->local_card->address_line2, 98 | 'address_city' => $this->local_card->address_city, 99 | 'address_state' => $this->local_card->address_state, 100 | 'address_zip' => $this->local_card->address_zip, 101 | 'address_country' => $this->local_card->address_country, 102 | ); 103 | } 104 | 105 | /** 106 | * Create a new card. 107 | * 108 | * @param string $card_token 109 | * 110 | * @return Card 111 | */ 112 | public function create($card_token) 113 | { 114 | $properties = json_decode($card_token, true); 115 | 116 | $this->local_card = Models\Card::create(array( 117 | 'customer_id' => $this->local_customer->id, 118 | 'last4' => Arr::get($properties, 'last4'), 119 | 'brand' => Arr::get($properties, 'brand', 'Visa'), 120 | 'exp_month' => Arr::get($properties, 'exp_month'), 121 | 'exp_year' => Arr::get($properties, 'exp_year'), 122 | 'name' => Arr::get($properties, 'name'), 123 | 'address_line1' => Arr::get($properties, 'address_line1'), 124 | 'address_line2' => Arr::get($properties, 'address_line2'), 125 | 'address_city' => Arr::get($properties, 'address_city'), 126 | 'address_state' => Arr::get($properties, 'address_state'), 127 | 'address_zip' => Arr::get($properties, 'address_zip'), 128 | 'address_country' => Arr::get($properties, 'address_country'), 129 | )); 130 | 131 | $this->gateway->apiDelay(); 132 | 133 | $this->id = $this->local_card->id; 134 | 135 | return $this; 136 | } 137 | 138 | /** 139 | * Update a card. 140 | * 141 | * @param array $properties 142 | * 143 | * @return Card 144 | */ 145 | public function update(array $properties = array()) 146 | { 147 | $this->info(); 148 | 149 | if (!empty($properties['brand'])) { 150 | $this->local_card->brand = $properties['brand']; 151 | } 152 | if (!empty($properties['last4'])) { 153 | $this->local_card->last4 = $properties['last4']; 154 | } 155 | if (!empty($properties['name'])) { 156 | $this->local_card->name = $properties['name']; 157 | } 158 | if (!empty($properties['exp_month'])) { 159 | $this->local_card->exp_month = $properties['exp_month']; 160 | } 161 | if (!empty($properties['exp_year'])) { 162 | $this->local_card->exp_year = $properties['exp_year']; 163 | } 164 | if (!empty($properties['address_line1'])) { 165 | $this->local_card->address_line1 = $properties['address_line1']; 166 | } 167 | if (!empty($properties['address_line2'])) { 168 | $this->local_card->address_line2 = $properties['address_line2']; 169 | } 170 | if (!empty($properties['address_city'])) { 171 | $this->local_card->address_city = $properties['address_city']; 172 | } 173 | if (!empty($properties['address_state'])) { 174 | $this->local_card->address_state = $properties['address_state']; 175 | } 176 | if (!empty($properties['address_zip'])) { 177 | $this->local_card->address_zip = $properties['address_zip']; 178 | } 179 | if (!empty($properties['address_country'])) { 180 | $this->local_card->address_country = $properties['address_country']; 181 | } 182 | 183 | $this->local_card->save(); 184 | $this->gateway->apiDelay(); 185 | 186 | return $this; 187 | } 188 | 189 | /** 190 | * Delete a card. 191 | * 192 | * @return Card 193 | */ 194 | public function delete() 195 | { 196 | $this->info(); 197 | $this->local_card->delete(); 198 | $this->gateway->apiDelay(); 199 | $this->local_card = null; 200 | return $this; 201 | } 202 | 203 | /** 204 | * Gets the native card response. 205 | * 206 | * @return Models\Card 207 | */ 208 | public function getNativeResponse() 209 | { 210 | $this->info(); 211 | return $this->local_card; 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/Mmanos/Billing/Gateways/Local/Charge.php: -------------------------------------------------------------------------------- 1 | gateway = $gateway; 48 | $this->local_customer = $customer; 49 | 50 | if ($id instanceof Models\Charge) { 51 | $this->local_charge = $id; 52 | $this->id = $this->local_charge->id; 53 | } 54 | else if (null !== $id) { 55 | $this->id = $id; 56 | } 57 | } 58 | 59 | /** 60 | * Gets the id of this instance. 61 | * 62 | * @return mixed 63 | */ 64 | public function id() 65 | { 66 | return $this->id; 67 | } 68 | 69 | /** 70 | * Gets info for a charge. 71 | * 72 | * @return array|null 73 | */ 74 | public function info() 75 | { 76 | if (!$this->id || !$this->local_customer) { 77 | return null; 78 | } 79 | 80 | if (!$this->local_charge) { 81 | $this->local_charge = $this->local_customer->charges()->where('id', $this->id)->first(); 82 | $this->gateway->apiDelay(); 83 | } 84 | 85 | if (!$this->local_charge) { 86 | return null; 87 | } 88 | 89 | return array( 90 | 'id' => $this->id, 91 | 'created_at' => (string) $this->local_charge->created_at, 92 | 'amount' => $this->local_charge->amount, 93 | 'paid' => $this->local_charge->paid, 94 | 'refunded' => $this->local_charge->refunded, 95 | 'captured' => $this->local_charge->captured, 96 | 'card' => $this->local_charge->card_id, 97 | 'invoice_id' => null, 98 | 'description' => $this->local_charge->description, 99 | ); 100 | } 101 | 102 | /** 103 | * Create a new charge. 104 | * 105 | * @param int $amount 106 | * @param array $properties 107 | * 108 | * @return Charge 109 | */ 110 | public function create($amount, array $properties = array()) 111 | { 112 | $card_id = empty($properties['card']) ? null : $properties['card']; 113 | if (!empty($properties['card_token'])) { 114 | $card_id = $this->gateway->customer($this->local_customer) 115 | ->card() 116 | ->create($properties['card_token']) 117 | ->id(); 118 | } 119 | if (!$card_id) { 120 | $card_id = $this->local_customer->cards->first()->id; 121 | } 122 | 123 | $this->local_charge = Models\Charge::create(array( 124 | 'customer_id' => $this->local_customer->id, 125 | 'amount' => $amount, 126 | 'card_id' => Models\Card::find($card_id)->id, 127 | 'description' => Arr::get($properties, 'description'), 128 | 'paid' => false, 129 | 'captured' => false, 130 | 'refunded' => false, 131 | )); 132 | 133 | $this->gateway->apiDelay(); 134 | 135 | $this->id = $this->local_charge->id; 136 | 137 | if (Arr::get($properties, 'capture', true)) { 138 | $this->local_charge->capture(); 139 | } 140 | 141 | return $this; 142 | } 143 | 144 | /** 145 | * Capture a preauthorized charge. 146 | * 147 | * @param array $properties 148 | * 149 | * @return Charge 150 | */ 151 | public function capture(array $properties = array()) 152 | { 153 | $this->info(); 154 | $this->local_charge->capture(); 155 | $this->gateway->apiDelay(); 156 | return $this; 157 | } 158 | 159 | /** 160 | * Refund a charge. 161 | * 162 | * @param array $properties 163 | * 164 | * @return Charge 165 | */ 166 | public function refund(array $properties = array()) 167 | { 168 | $this->info(); 169 | $this->local_charge->refund(); 170 | $this->gateway->apiDelay(); 171 | return $this; 172 | } 173 | 174 | /** 175 | * Gets the native charge response. 176 | * 177 | * @return Models\Charge 178 | */ 179 | public function getNativeResponse() 180 | { 181 | $this->info(); 182 | return $this->local_charge; 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/Mmanos/Billing/Gateways/Local/Customer.php: -------------------------------------------------------------------------------- 1 | gateway = $gateway; 40 | 41 | if ($id instanceof Models\Customer) { 42 | $this->local_customer = $id; 43 | $this->id = $this->local_customer->id; 44 | } 45 | else if (null !== $id) { 46 | $this->id = $id; 47 | } 48 | } 49 | 50 | /** 51 | * Gets the id of this instance. 52 | * 53 | * @return mixed 54 | */ 55 | public function id() 56 | { 57 | return $this->id; 58 | } 59 | 60 | /** 61 | * Gets info for a customer. 62 | * 63 | * @return array|null 64 | */ 65 | public function info() 66 | { 67 | if (!$this->id) { 68 | return null; 69 | } 70 | 71 | if (!$this->local_customer) { 72 | $this->local_customer = Models\Customer::find($this->id); 73 | $this->gateway->apiDelay(); 74 | } 75 | 76 | if (!$this->local_customer) { 77 | return null; 78 | } 79 | 80 | $discounts = array(); 81 | if ($this->local_customer->coupon) { 82 | $ends_at = null; 83 | if ($this->local_customer->coupon->duration_in_months) { 84 | $ends_at = $this->local_customer->created_at->copy()->addMonths( 85 | $this->local_customer->coupon->duration_in_months 86 | ); 87 | } 88 | 89 | $discounts[] = array( 90 | 'coupon' => $this->local_customer->coupon->code, 91 | 'amount_off' => $this->local_customer->coupon->amount_off, 92 | 'percent_off' => $this->local_customer->coupon->percent_off, 93 | 'started_at' => (string) $this->local_customer->created_at, 94 | 'ends_at' => (string) $ends_at, 95 | ); 96 | } 97 | 98 | return array( 99 | 'id' => $this->id, 100 | 'description' => $this->local_customer->description, 101 | 'email' => $this->local_customer->email, 102 | 'created_at' => (string) $this->local_customer->created_at, 103 | 'discounts' => $discounts, 104 | ); 105 | } 106 | 107 | /** 108 | * Create a new customer. 109 | * 110 | * @param array $properties 111 | * 112 | * @return Customer 113 | */ 114 | public function create(array $properties = array()) 115 | { 116 | $coupon_id = null; 117 | if ($coupon = Arr::get($properties, 'coupon')) { 118 | $coupon_id = Models\Coupon::where('code', $coupon)->first()->id; 119 | } 120 | 121 | $this->local_customer = Models\Customer::create(array( 122 | 'description' => Arr::get($properties, 'description'), 123 | 'email' => Arr::get($properties, 'email'), 124 | 'coupon_id' => $coupon_id, 125 | )); 126 | 127 | $this->gateway->apiDelay(); 128 | 129 | $this->id = $this->local_customer->id; 130 | 131 | if ($token = Arr::get($properties, 'card_token')) { 132 | $this->card()->create($token); 133 | } 134 | 135 | return $this; 136 | } 137 | 138 | /** 139 | * Update a customer. 140 | * 141 | * @param array $properties 142 | * 143 | * @return Customer 144 | */ 145 | public function update(array $properties = array()) 146 | { 147 | $this->info(); 148 | 149 | if (!empty($properties['description'])) { 150 | $this->local_customer->description = $properties['description']; 151 | } 152 | if (!empty($properties['email'])) { 153 | $this->local_customer->email = $properties['email']; 154 | } 155 | if (!empty($properties['coupon'])) { 156 | if ($coupon_model = Models\Coupon::where('code', $properties['coupon'])->first()) { 157 | $this->local_customer->coupon_id = $coupon_model->id; 158 | } 159 | } 160 | 161 | $this->local_customer->save(); 162 | 163 | if (!empty($properties['card_token'])) { 164 | $token = $properties['card_token']; 165 | if ($card = $this->local_customer->cards->first()) { 166 | $this->card($card)->update(json_decode($token, true)); 167 | } 168 | else { 169 | $this->card()->create($token); 170 | } 171 | } 172 | 173 | $this->gateway->apiDelay(); 174 | $this->local_subscription = null; 175 | 176 | return $this; 177 | } 178 | 179 | /** 180 | * Delete a customer. 181 | * 182 | * @return Customer 183 | */ 184 | public function delete() 185 | { 186 | $this->info(); 187 | foreach ($this->subscriptions() as $subscription) { 188 | $subscription->cancel(false); 189 | } 190 | $this->local_customer->delete(); 191 | $this->gateway->apiDelay(); 192 | $this->local_customer = null; 193 | return $this; 194 | } 195 | 196 | /** 197 | * Gets all subscriptions for a customer. 198 | * 199 | * @return array 200 | */ 201 | public function subscriptions() 202 | { 203 | $this->info(); 204 | 205 | if (!$this->local_customer) { 206 | return array(); 207 | } 208 | 209 | $subscriptions = $this->local_customer->subscriptions; 210 | $this->gateway->apiDelay(); 211 | 212 | $subscriptions_array = array(); 213 | foreach ($subscriptions as $subscription) { 214 | if ($subscription->gracePeriodEnded()) { 215 | continue; 216 | } 217 | 218 | $subscriptions_array[] = $this->gateway->subscription($subscription, $this); 219 | } 220 | 221 | return $subscriptions_array; 222 | } 223 | 224 | /** 225 | * Gets all credit cards for a customer. 226 | * 227 | * @return array 228 | */ 229 | public function cards() 230 | { 231 | $this->info(); 232 | 233 | if (!$this->local_customer) { 234 | return array(); 235 | } 236 | 237 | $cards = $this->local_customer->cards; 238 | $this->gateway->apiDelay(); 239 | 240 | $cards_array = array(); 241 | foreach ($cards as $card) { 242 | $cards_array[] = $this->card($card); 243 | } 244 | 245 | return $cards_array; 246 | } 247 | 248 | /** 249 | * Fetch a customer card instance. 250 | * 251 | * @param mixed $id 252 | * 253 | * @return Card 254 | */ 255 | public function card($id = null) 256 | { 257 | return new Card($this->gateway, $this->getNativeResponse(), $id); 258 | } 259 | 260 | /** 261 | * Gets all invoices for a customer. 262 | * 263 | * @return array 264 | */ 265 | public function invoices(array $parameters = array()) 266 | { 267 | $this->info(); 268 | 269 | if (!$this->local_customer) { 270 | return array(); 271 | } 272 | 273 | foreach ($this->local_customer->subscriptions as $subscription) { 274 | if ($subscription->gracePeriodEnded()) { 275 | continue; 276 | } 277 | 278 | $subscription->process(); 279 | } 280 | 281 | $invoices = $this->local_customer->invoices()->limit(100)->get(); 282 | $this->gateway->apiDelay(); 283 | 284 | $invoices_array = array(); 285 | foreach ($invoices as $invoice) { 286 | $invoices_array[] = $this->invoice($invoice); 287 | } 288 | 289 | return $invoices_array; 290 | } 291 | 292 | /** 293 | * Fetch an invoice instance. 294 | * 295 | * @param mixed $id 296 | * 297 | * @return Invoice 298 | */ 299 | public function invoice($id = null) 300 | { 301 | return new Invoice($this->gateway, $this->getNativeResponse(), $id); 302 | } 303 | 304 | /** 305 | * Gets all charges for a customer. 306 | * 307 | * @return array 308 | */ 309 | public function charges() 310 | { 311 | $this->info(); 312 | 313 | if (!$this->local_customer) { 314 | return array(); 315 | } 316 | 317 | $charges = $this->local_customer->charges()->limit(100)->get(); 318 | $this->gateway->apiDelay(); 319 | 320 | $charges_array = array(); 321 | foreach ($charges as $charge) { 322 | $charges_array[] = $this->charge($charge); 323 | } 324 | 325 | return $charges_array; 326 | } 327 | 328 | /** 329 | * Fetch a charge instance. 330 | * 331 | * @param mixed $id 332 | * 333 | * @return Charge 334 | */ 335 | public function charge($id = null) 336 | { 337 | return new Charge($this->gateway, $this->getNativeResponse(), $id); 338 | } 339 | 340 | /** 341 | * Gets the native customer response. 342 | * 343 | * @return Models\Customer 344 | */ 345 | public function getNativeResponse() 346 | { 347 | $this->info(); 348 | return $this->local_customer; 349 | } 350 | } 351 | -------------------------------------------------------------------------------- /src/Mmanos/Billing/Gateways/Local/Gateway.php: -------------------------------------------------------------------------------- 1 | connection = $connection; 30 | 31 | Config::set('database.connections.billinglocal', Arr::get($connection, 'database')); 32 | 33 | $path = Arr::get($connection, 'database.database'); 34 | if (!file_exists($path) && is_dir(dirname($path))) { 35 | touch($path); 36 | } 37 | 38 | if (!Schema::connection('billinglocal')->hasTable('customers')) { 39 | include_once 'Models/migration.php'; 40 | } 41 | } 42 | 43 | /** 44 | * Fetch a customer instance. 45 | * 46 | * @param mixed $id 47 | * 48 | * @return Customer 49 | */ 50 | public function customer($id = null) 51 | { 52 | return new Customer($this, $id); 53 | } 54 | 55 | /** 56 | * Fetch a subscription instance. 57 | * 58 | * @param mixed $id 59 | * @param Customer $customer 60 | * 61 | * @return Subscription 62 | */ 63 | public function subscription($id = null, CustomerInterface $customer = null) 64 | { 65 | if ($customer) { 66 | $customer = $customer->getNativeResponse(); 67 | } 68 | 69 | return new Subscription($this, $customer, $id); 70 | } 71 | 72 | /** 73 | * Fetch a charge instance. 74 | * 75 | * @param mixed $id 76 | * @param CustomerInterface $customer 77 | * 78 | * @return Charge 79 | */ 80 | public function charge($id = null, CustomerInterface $customer = null) 81 | { 82 | if ($customer) { 83 | $customer = $customer->getNativeResponse(); 84 | } 85 | 86 | return new Charge($this, $customer, $id); 87 | } 88 | 89 | /** 90 | * Simulate an API delay. 91 | * 92 | * @return void 93 | */ 94 | public function apiDelay() 95 | { 96 | $delay = Config::get('laravel-billing::gateways.local.api_delay_ms'); 97 | if (empty($delay)) { 98 | return; 99 | } 100 | 101 | $ms = $delay * 1000; 102 | $threshold = $ms * .5; 103 | $sleep = rand($ms-$threshold, $ms+$threshold); 104 | 105 | usleep($sleep); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/Mmanos/Billing/Gateways/Local/Invoice.php: -------------------------------------------------------------------------------- 1 | gateway = $gateway; 48 | $this->local_customer = $customer; 49 | 50 | if ($id instanceof Models\Invoice) { 51 | $this->local_invoice = $id; 52 | $this->id = $this->local_invoice->id; 53 | } 54 | else if (null !== $id) { 55 | $this->id = $id; 56 | } 57 | } 58 | 59 | /** 60 | * Gets the id of this instance. 61 | * 62 | * @return mixed 63 | */ 64 | public function id() 65 | { 66 | return $this->id; 67 | } 68 | 69 | /** 70 | * Gets info for an invoice. 71 | * 72 | * @return array|null 73 | */ 74 | public function info() 75 | { 76 | if (!$this->id || !$this->local_customer) { 77 | return null; 78 | } 79 | 80 | if (!$this->local_invoice) { 81 | $this->local_invoice = $this->local_customer->invoices()->where('id', $this->id)->first(); 82 | $this->gateway->apiDelay(); 83 | } 84 | 85 | if (!$this->local_invoice) { 86 | return null; 87 | } 88 | 89 | $discounts = array(); 90 | if ($this->local_invoice->coupon) { 91 | $started_at = $this->local_invoice->subscription 92 | ? $this->local_invoice->subscription->created_at 93 | : $this->local_invoice->customer->created_at; 94 | 95 | $ends_at = null; 96 | if ($this->local_invoice->coupon->duration_in_months) { 97 | if ($this->local_invoice->subscription) { 98 | $ends_at = $this->local_invoice->subscription->created_at->copy()->addMonths( 99 | $this->local_invoice->coupon->duration_in_months 100 | ); 101 | } 102 | else { 103 | $ends_at = $this->local_invoice->customer->created_at->copy()->addMonths( 104 | $this->local_invoice->coupon->duration_in_months 105 | ); 106 | } 107 | } 108 | 109 | $discounts[] = array( 110 | 'coupon' => $this->local_invoice->coupon->code, 111 | 'amount_off' => $this->local_invoice->coupon->amount_off, 112 | 'percent_off' => $this->local_invoice->coupon->percent_off, 113 | 'started_at' => (string) $started_at, 114 | 'ends_at' => $ends_at ? (string) $ends_at : null, 115 | ); 116 | } 117 | 118 | $items = array(); 119 | foreach ($this->local_invoice->items as $line) { 120 | $item = array( 121 | 'id' => $line->id, 122 | 'amount' => $line->amount, 123 | 'period_start' => $line->period_started_at ? (string) $line->period_started_at : null, 124 | 'period_end' => $line->period_ends_at ? (string) $line->period_ends_at : null, 125 | 'description' => $line->description, 126 | 'subscription_id' => $line->subscription_id, 127 | 'quantity' => $line->quantity, 128 | ); 129 | 130 | $items[] = $item; 131 | } 132 | 133 | return array( 134 | 'id' => $this->id, 135 | 'date' => (string) $this->local_invoice->period_started_at, 136 | 'total' => $this->local_invoice->subtotal, 137 | 'subtotal' => $this->local_invoice->subtotal, 138 | 'amount' => $this->local_invoice->amount, 139 | 'starting_balance' => 0, 140 | 'ending_balance' => 0, 141 | 'closed' => $this->local_invoice->closed, 142 | 'paid' => $this->local_invoice->paid, 143 | 'discounts' => $discounts, 144 | 'items' => $items, 145 | ); 146 | } 147 | 148 | /** 149 | * Gets the native invoice response. 150 | * 151 | * @return Models\Invoice 152 | */ 153 | public function getNativeResponse() 154 | { 155 | $this->info(); 156 | return $this->local_invoice; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/Mmanos/Billing/Gateways/Local/Models/Card.php: -------------------------------------------------------------------------------- 1 | belongsTo('Mmanos\Billing\Gateways\Local\Models\Customer')->withTrashed(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Mmanos/Billing/Gateways/Local/Models/Charge.php: -------------------------------------------------------------------------------- 1 | belongsTo('Mmanos\Billing\Gateways\Local\Models\Customer')->withTrashed(); 15 | } 16 | 17 | public function card() 18 | { 19 | return $this->belongsTo('Mmanos\Billing\Gateways\Local\Models\Card')->withTrashed(); 20 | } 21 | 22 | public function capture() 23 | { 24 | $this->captured = 1; 25 | $this->paid = 1; 26 | $this->refunded = 0; 27 | $this->save(); 28 | } 29 | 30 | public function refund() 31 | { 32 | if (!$this->paid) { 33 | return; 34 | } 35 | 36 | $this->refunded = 1; 37 | $this->save(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Mmanos/Billing/Gateways/Local/Models/Coupon.php: -------------------------------------------------------------------------------- 1 | belongsTo('Mmanos\Billing\Gateways\Local\Models\Coupon'); 15 | } 16 | 17 | public function cards() 18 | { 19 | return $this->hasMany('Mmanos\Billing\Gateways\Local\Models\Card'); 20 | } 21 | 22 | public function invoices() 23 | { 24 | return $this->hasMany('Mmanos\Billing\Gateways\Local\Models\Invoice'); 25 | } 26 | 27 | public function subscriptions() 28 | { 29 | return $this->hasMany('Mmanos\Billing\Gateways\Local\Models\Subscription'); 30 | } 31 | 32 | public function charges() 33 | { 34 | return $this->hasMany('Mmanos\Billing\Gateways\Local\Models\Charge'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Mmanos/Billing/Gateways/Local/Models/Invoice.php: -------------------------------------------------------------------------------- 1 | belongsTo('Mmanos\Billing\Gateways\Local\Models\Customer')->withTrashed(); 17 | } 18 | 19 | public function coupon() 20 | { 21 | return $this->belongsTo('Mmanos\Billing\Gateways\Local\Models\Coupon')->withTrashed(); 22 | } 23 | 24 | public function subscription() 25 | { 26 | return $this->belongsTo('Mmanos\Billing\Gateways\Local\Models\Subscription')->withTrashed(); 27 | } 28 | 29 | public function items() 30 | { 31 | return $this->hasMany('Mmanos\Billing\Gateways\Local\Models\Invoice\Item'); 32 | } 33 | 34 | public function getSubtotalAttribute($value) 35 | { 36 | $subtotal = 0; 37 | foreach ($this->items as $item) { 38 | $subtotal += $item->amount; 39 | } 40 | 41 | return $subtotal; 42 | } 43 | 44 | public function getAmountAttribute($value) 45 | { 46 | $subtotal = $this->subtotal; 47 | $discount = 0; 48 | 49 | if ($this->coupon) { 50 | if ($this->coupon->percent_off) { 51 | $discount = $subtotal * ($this->coupon->percent_off / 100); 52 | } 53 | else if ($this->coupon->amount_off) { 54 | $discount = $subtotal - $this->coupon->amount_off; 55 | } 56 | } 57 | 58 | $amount = $subtotal - $discount; 59 | if ($amount < 0) { 60 | return 0; 61 | } 62 | 63 | return $amount; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Mmanos/Billing/Gateways/Local/Models/Invoice/Item.php: -------------------------------------------------------------------------------- 1 | belongsTo('Mmanos\Billing\Gateways\Local\Models\Invoice')->withTrashed(); 17 | } 18 | 19 | public function subscription() 20 | { 21 | return $this->belongsTo('Mmanos\Billing\Gateways\Local\Models\Subscription')->withTrashed(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Mmanos/Billing/Gateways/Local/Models/Plan.php: -------------------------------------------------------------------------------- 1 | interval) { 16 | case 'yearly': 17 | $date->addYear(); 18 | break; 19 | 20 | case 'quarterly': 21 | $date->addMonths(3); 22 | break; 23 | 24 | case 'weekly': 25 | $date->addWeek(); 26 | break; 27 | 28 | case 'monthly': 29 | default: 30 | $date->addMonth(); 31 | } 32 | 33 | return $date; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Mmanos/Billing/Gateways/Local/Models/Subscription.php: -------------------------------------------------------------------------------- 1 | belongsTo('Mmanos\Billing\Gateways\Local\Models\Customer')->withTrashed(); 18 | } 19 | 20 | public function plan() 21 | { 22 | return $this->belongsTo('Mmanos\Billing\Gateways\Local\Models\Plan')->withTrashed(); 23 | } 24 | 25 | public function card() 26 | { 27 | return $this->belongsTo('Mmanos\Billing\Gateways\Local\Models\Card'); 28 | } 29 | 30 | public function coupon() 31 | { 32 | return $this->belongsTo('Mmanos\Billing\Gateways\Local\Models\Coupon')->withTrashed(); 33 | } 34 | 35 | public function getPeriodStartedAtAttribute($value) 36 | { 37 | if ($this->trial_ends_at && $this->trial_ends_at->timestamp > time()) { 38 | return $this->created_at; 39 | } 40 | 41 | $first_started = $this->trial_ends_at ? $this->trial_ends_at : $this->created_at; 42 | $period_started = $first_started->copy(); 43 | while (!$period_started->isFuture()) { 44 | $this->plan->addInterval($period_started); 45 | } 46 | 47 | return $period_started; 48 | } 49 | 50 | public function getPeriodEndsAtAttribute($value) 51 | { 52 | if ($this->trial_ends_at && $this->trial_ends_at->timestamp > time()) { 53 | return $this->trial_ends_at; 54 | } 55 | 56 | return $this->plan->addInterval($this->period_started_at->copy()); 57 | } 58 | 59 | public function gracePeriodEnded() 60 | { 61 | if (!$this->cancel_at) { 62 | return false; 63 | } 64 | 65 | if ($this->cancel_at->timestamp > time()) { 66 | return false; 67 | } 68 | 69 | $this->delete(); 70 | 71 | return true; 72 | } 73 | 74 | public function process() 75 | { 76 | if ($this->cancel_at || $this->trashed()) { 77 | return; 78 | } 79 | 80 | $first_started = $this->trial_ends_at ? $this->trial_ends_at : $this->created_at; 81 | $period_started = $first_started->copy(); 82 | 83 | $oldest_invoice = $this->customer->invoices() 84 | ->where('subscription_id', $this->id) 85 | ->orderBy('period_started_at', 'DESC') 86 | ->first(); 87 | 88 | $oldest_at = $oldest_invoice 89 | ? $oldest_invoice->period_started_at 90 | : $period_started->copy()->subDay(); 91 | 92 | while (!$period_started->isFuture()) { 93 | if ($period_started->gt($oldest_at)) { 94 | $this->createInvoice( 95 | $period_started, 96 | !$this->customer->cards->isEmpty() 97 | ); 98 | } 99 | 100 | $this->plan->addInterval($period_started); 101 | } 102 | } 103 | 104 | public function createInvoice($period_started, $paid = true) 105 | { 106 | $period_end = $this->plan->addInterval($period_started->copy()); 107 | 108 | $invoice = Invoice::create(array( 109 | 'customer_id' => $this->customer->id, 110 | 'subscription_id' => $this->id, 111 | 'closed' => 0, 112 | 'paid' => (int) $paid, 113 | 'coupon_id' => $this->coupon_id, 114 | 'period_started_at' => $period_started, 115 | 'period_ends_at' => $period_end, 116 | )); 117 | 118 | Invoice\Item::create(array( 119 | 'invoice_id' => $invoice->id, 120 | 'subscription_id' => $this->id, 121 | 'description' => null, 122 | 'amount' => $this->plan->amount, 123 | 'period_started_at' => $period_started, 124 | 'period_ends_at' => $period_end, 125 | )); 126 | 127 | return $invoice; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/Mmanos/Billing/Gateways/Local/Models/migration.php: -------------------------------------------------------------------------------- 1 | create('coupons', function ($table) { 4 | $table->increments('id'); 5 | $table->string('code'); 6 | $table->integer('percent_off')->nullable(); 7 | $table->integer('amount_off')->nullable(); 8 | $table->integer('duration_in_months')->nullable(); 9 | $table->timestamps(); 10 | $table->softDeletes(); 11 | 12 | $table->unique('code', 'coupon_code'); 13 | }); 14 | Schema::connection('billinglocal')->create('customers', function ($table) { 15 | $table->increments('id'); 16 | $table->integer('coupon_id')->nullable(); 17 | $table->string('email')->nullable(); 18 | $table->text('description')->nullable(); 19 | $table->timestamps(); 20 | $table->softDeletes(); 21 | 22 | $table->index('email', 'customer_email'); 23 | }); 24 | Schema::connection('billinglocal')->create('cards', function ($table) { 25 | $table->increments('id'); 26 | $table->integer('customer_id')->unsigned(); 27 | $table->string('last4')->nullable(); 28 | $table->string('brand')->nullable(); 29 | $table->string('exp_month')->nullable(); 30 | $table->string('exp_year')->nullable(); 31 | $table->string('name')->nullable(); 32 | $table->string('address_line1')->nullable(); 33 | $table->string('address_line2')->nullable(); 34 | $table->string('address_city')->nullable(); 35 | $table->string('address_state')->nullable(); 36 | $table->string('address_zip')->nullable(); 37 | $table->string('address_country')->nullable(); 38 | $table->timestamps(); 39 | $table->softDeletes(); 40 | 41 | $table->index('customer_id', 'customer_cards'); 42 | }); 43 | Schema::connection('billinglocal')->create('plans', function ($table) { 44 | $table->increments('id'); 45 | $table->string('key'); 46 | $table->string('name'); 47 | $table->integer('amount')->default(0); 48 | $table->string('interval')->default('monthly'); 49 | $table->integer('trial_period_days')->nullable(); 50 | $table->timestamps(); 51 | $table->softDeletes(); 52 | 53 | $table->unique('key', 'plan_key'); 54 | }); 55 | Schema::connection('billinglocal')->create('invoices', function ($table) { 56 | $table->increments('id'); 57 | $table->integer('customer_id')->unsigned(); 58 | $table->integer('subscription_id')->unsigned()->nullable(); 59 | $table->tinyInteger('closed')->default(0); 60 | $table->tinyInteger('paid')->default(0); 61 | $table->integer('coupon_id')->nullable(); 62 | $table->timestamp('period_started_at')->nullable(); 63 | $table->timestamp('period_ends_at')->nullable(); 64 | $table->timestamps(); 65 | $table->softDeletes(); 66 | 67 | $table->unique(array('customer_id', 'period_ends_at'), 'customer_invoices'); 68 | }); 69 | Schema::connection('billinglocal')->create('invoice_items', function ($table) { 70 | $table->increments('id'); 71 | $table->integer('invoice_id')->unsigned(); 72 | $table->integer('subscription_id')->unsigned()->nullable(); 73 | $table->integer('amount')->default(0); 74 | $table->text('description')->nullable(); 75 | $table->timestamp('period_started_at')->nullable(); 76 | $table->timestamp('period_ends_at')->nullable(); 77 | $table->integer('quantity')->default(1); 78 | $table->timestamps(); 79 | $table->softDeletes(); 80 | 81 | $table->unique('invoice_id', 'invoice_id_items'); 82 | }); 83 | Schema::connection('billinglocal')->create('subscriptions', function ($table) { 84 | $table->increments('id'); 85 | $table->integer('customer_id')->unsigned(); 86 | $table->integer('plan_id')->unsigned(); 87 | $table->integer('card_id')->unsigned()->nullable(); 88 | $table->integer('coupon_id')->nullable(); 89 | $table->integer('quantity')->default(1); 90 | $table->timestamp('trial_ends_at')->nullable(); 91 | $table->timestamp('cancel_at')->nullable(); 92 | $table->timestamps(); 93 | $table->softDeletes(); 94 | 95 | $table->index('customer_id', 'customer_subscriptions'); 96 | }); 97 | Schema::connection('billinglocal')->create('charges', function ($table) { 98 | $table->increments('id'); 99 | $table->integer('customer_id')->unsigned(); 100 | $table->integer('card_id')->unsigned()->nullable(); 101 | $table->integer('amount'); 102 | $table->tinyInteger('paid')->default(0); 103 | $table->tinyInteger('refunded')->default(0); 104 | $table->tinyInteger('captured')->default(0); 105 | $table->text('description')->nullable(); 106 | $table->timestamps(); 107 | $table->softDeletes(); 108 | 109 | $table->index('customer_id', 'customer_charges'); 110 | }); 111 | -------------------------------------------------------------------------------- /src/Mmanos/Billing/Gateways/Local/Subscription.php: -------------------------------------------------------------------------------- 1 | gateway = $gateway; 49 | $this->local_customer = $customer; 50 | 51 | if ($id instanceof Models\Subscription) { 52 | $this->local_subscription = $id; 53 | $this->id = $this->local_subscription->id; 54 | } 55 | else if (null !== $id) { 56 | $this->id = $id; 57 | } 58 | } 59 | 60 | /** 61 | * Gets the id of this instance. 62 | * 63 | * @return mixed 64 | */ 65 | public function id() 66 | { 67 | return $this->id; 68 | } 69 | 70 | /** 71 | * Gets info for a subscription. 72 | * 73 | * @return array|null 74 | */ 75 | public function info() 76 | { 77 | if (!$this->id || !$this->local_customer) { 78 | return null; 79 | } 80 | 81 | if (!$this->local_subscription) { 82 | $this->local_subscription = $this->local_customer->subscriptions()->where('id', $this->id)->first(); 83 | $this->gateway->apiDelay(); 84 | 85 | if ($this->local_subscription) { 86 | $this->local_subscription->process(); 87 | } 88 | } 89 | 90 | if (!$this->local_subscription) { 91 | return null; 92 | } 93 | 94 | if ($this->local_subscription->gracePeriodEnded()) { 95 | return $this->local_subscription = null; 96 | } 97 | 98 | $discounts = array(); 99 | if ($this->local_subscription->coupon) { 100 | $started_at = $this->local_subscription->subscription->created_at; 101 | 102 | $ends_at = null; 103 | if ($this->local_subscription->coupon->duration_in_months) { 104 | $ends_at = $started_at->copy()->addMonths( 105 | $this->local_subscription->coupon->duration_in_months 106 | ); 107 | } 108 | 109 | $discounts[] = array( 110 | 'coupon' => $this->local_subscription->coupon->code, 111 | 'amount_off' => $this->local_subscription->coupon->amount_off, 112 | 'percent_off' => $this->local_subscription->coupon->percent_off, 113 | 'started_at' => (string) $started_at, 114 | 'ends_at' => $ends_at ? (string) $ends_at : null, 115 | ); 116 | } 117 | 118 | return array( 119 | 'id' => $this->id, 120 | 'plan' => $this->local_subscription->plan->key, 121 | 'amount' => $this->local_subscription->plan->amount, 122 | 'interval' => $this->local_subscription->plan->interval, 123 | 'active' => $this->local_subscription->cancel_at ? false : true, 124 | 'quantity' => $this->local_subscription->quantity, 125 | 'started_at' => (string) $this->local_subscription->created_at, 126 | 'period_started_at' => (string) $this->local_subscription->period_started_at, 127 | 'period_ends_at' => (string) $this->local_subscription->period_ends_at, 128 | 'trial_ends_at' => $this->local_subscription->trial_ends_at ? (string) $this->local_subscription->trial_ends_at : null, 129 | 'card' => $this->local_subscription->card_id, 130 | 'discounts' => $discounts, 131 | ); 132 | } 133 | 134 | /** 135 | * Create a new subscription. 136 | * 137 | * @param mixed $plan 138 | * @param array $properties 139 | * 140 | * @return Subscription 141 | */ 142 | public function create($plan, array $properties = array()) 143 | { 144 | $card_id = empty($properties['card']) ? null : $properties['card']; 145 | if (!empty($properties['card_token'])) { 146 | $card_id = $this->gateway->customer($this->local_customer) 147 | ->card() 148 | ->create($properties['card_token']) 149 | ->id(); 150 | } 151 | if (!$card_id) { 152 | $card_id = $this->local_customer->cards->first()->id; 153 | } 154 | 155 | $coupon_id = null; 156 | if ($coupon = Arr::get($properties, 'coupon')) { 157 | $coupon_id = Models\Coupon::where('code', $coupon)->first()->id; 158 | } 159 | 160 | $plan = Models\Plan::where('key', $plan)->first(); 161 | 162 | $trial_ends_at = null; 163 | if (!empty($properties['trial_ends_at'])) { 164 | $trial_ends_at = date('Y-m-d H:i:s', strtotime((string) $properties['trial_ends_at'])); 165 | if (strtotime($trial_ends_at) <= time()) { 166 | $trial_ends_at = date('Y-m-d H:i:s'); 167 | } 168 | } 169 | else if ($plan->trial_period_days) { 170 | $trial_ends_at = (string) Carbon::now()->addDays($plan->trial_period_days); 171 | } 172 | 173 | $this->local_subscription = Models\Subscription::create(array( 174 | 'customer_id' => $this->local_customer->id, 175 | 'plan_id' => $plan->id, 176 | 'card_id' => Models\Card::find($card_id)->id, 177 | 'coupon_id' => $coupon_id, 178 | 'quantity' => Arr::get($properties, 'quantity', 1), 179 | 'trial_ends_at' => $trial_ends_at, 180 | 'cancel_at' => null, 181 | )); 182 | 183 | $this->gateway->apiDelay(); 184 | 185 | $this->id = $this->local_subscription->id; 186 | 187 | $this->local_subscription->process(); 188 | 189 | return $this; 190 | } 191 | 192 | /** 193 | * Update a subscription. 194 | * 195 | * @param array $properties 196 | * 197 | * @return Subscription 198 | */ 199 | public function update(array $properties = array()) 200 | { 201 | $this->info(); 202 | 203 | if (!empty($properties['plan'])) { 204 | $this->local_subscription->plan_id = Models\Plan::where('key', $properties['plan'])->first()->id; 205 | } 206 | if (!empty($properties['quantity'])) { 207 | $this->local_subscription->quantity = $properties['quantity']; 208 | } 209 | if (!empty($properties['trial_ends_at'])) { 210 | $trial_ends_at = date('Y-m-d H:i:s', strtotime((string) $properties['trial_ends_at'])); 211 | if (strtotime($properties['trial_ends_at']) <= time()) { 212 | if ($this->local_subscription->trial_ends_at 213 | && (strtotime((string) $this->local_subscription->trial_ends_at) > time()) 214 | ) { 215 | $this->local_subscription->trial_ends_at = date('Y-m-d H:i:s'); 216 | } 217 | } 218 | else { 219 | $this->local_subscription->trial_ends_at = $trial_ends_at; 220 | } 221 | } 222 | if (!empty($properties['coupon'])) { 223 | $this->local_subscription->coupon_id = $properties['coupon']; 224 | } 225 | 226 | if (!empty($properties['card_token'])) { 227 | $card_id = $this->gateway->customer($this->local_customer) 228 | ->card() 229 | ->create($properties['card_token']) 230 | ->id(); 231 | 232 | $this->local_subscription->card_id = Models\Card::find($card_id)->id; 233 | } 234 | else if (!empty($properties['card'])) { 235 | $this->local_subscription->card_id = Models\Card::find($properties['card'])->id; 236 | } 237 | 238 | $this->local_subscription->cancel_at = null; 239 | 240 | $this->local_subscription->save(); 241 | $this->gateway->apiDelay(); 242 | $this->local_subscription = null; 243 | 244 | return $this; 245 | } 246 | 247 | /** 248 | * Cancel a subscription. 249 | * 250 | * @param bool $at_period_end 251 | * 252 | * @return Subscription 253 | */ 254 | public function cancel($at_period_end = true) 255 | { 256 | $this->info(); 257 | 258 | if ($at_period_end) { 259 | $this->local_subscription->cancel_at = $this->local_subscription->period_ends_at; 260 | $this->local_subscription->save(); 261 | } 262 | else { 263 | $this->local_subscription->delete(); 264 | $this->local_subscription = null; 265 | } 266 | 267 | $this->gateway->apiDelay(); 268 | 269 | return $this; 270 | } 271 | 272 | /** 273 | * Gets the native subscription response. 274 | * 275 | * @return Models\Subscription 276 | */ 277 | public function getNativeResponse() 278 | { 279 | $this->info(); 280 | return $this->local_subscription; 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /src/Mmanos/Billing/Gateways/Stripe/Card.php: -------------------------------------------------------------------------------- 1 | gateway = $gateway; 50 | $this->stripe_customer = $customer; 51 | 52 | if ($id instanceof Stripe_Card) { 53 | $this->stripe_card = $id; 54 | $this->id = $this->stripe_card->id; 55 | } 56 | else if (null !== $id) { 57 | $this->id = $id; 58 | } 59 | } 60 | 61 | /** 62 | * Gets the id of this instance. 63 | * 64 | * @return mixed 65 | */ 66 | public function id() 67 | { 68 | return $this->id; 69 | } 70 | 71 | /** 72 | * Gets info for a card. 73 | * 74 | * @return array|null 75 | */ 76 | public function info() 77 | { 78 | if (!$this->id || !$this->stripe_customer) { 79 | return null; 80 | } 81 | 82 | if (!$this->stripe_card) { 83 | $this->stripe_card = $this->stripe_customer->cards->retrieve($this->id); 84 | } 85 | 86 | if (!$this->stripe_card) { 87 | return null; 88 | } 89 | 90 | return array( 91 | 'id' => $this->id, 92 | 'last4' => $this->stripe_card->last4, 93 | 'brand' => $this->stripe_card->brand, 94 | 'exp_month' => $this->stripe_card->exp_month, 95 | 'exp_year' => $this->stripe_card->exp_year, 96 | 'name' => $this->stripe_card->name, 97 | 'address_line1' => $this->stripe_card->address_line1, 98 | 'address_line2' => $this->stripe_card->address_line2, 99 | 'address_city' => $this->stripe_card->address_city, 100 | 'address_state' => $this->stripe_card->address_state, 101 | 'address_zip' => $this->stripe_card->address_zip, 102 | 'address_country' => $this->stripe_card->address_country, 103 | ); 104 | } 105 | 106 | /** 107 | * Create a new card. 108 | * 109 | * @param string $card_token 110 | * 111 | * @return Card 112 | */ 113 | public function create($card_token) 114 | { 115 | $stripe_card = $this->stripe_customer->cards->create(array( 116 | 'card' => $card_token, 117 | )); 118 | 119 | $this->id = $stripe_card->id; 120 | 121 | return $this; 122 | } 123 | 124 | /** 125 | * Update a card. 126 | * 127 | * @param array $properties 128 | * 129 | * @return Card 130 | */ 131 | public function update(array $properties = array()) 132 | { 133 | $this->info(); 134 | 135 | if (!empty($properties['name'])) { 136 | $this->stripe_card->name = $properties['name']; 137 | } 138 | if (!empty($properties['exp_month'])) { 139 | $this->stripe_card->exp_month = $properties['exp_month']; 140 | } 141 | if (!empty($properties['exp_year'])) { 142 | $this->stripe_card->exp_year = $properties['exp_year']; 143 | } 144 | if (!empty($properties['address_line1'])) { 145 | $this->stripe_card->address_line1 = $properties['address_line1']; 146 | } 147 | if (!empty($properties['address_line2'])) { 148 | $this->stripe_card->address_line2 = $properties['address_line2']; 149 | } 150 | if (!empty($properties['address_city'])) { 151 | $this->stripe_card->address_city = $properties['address_city']; 152 | } 153 | if (!empty($properties['address_state'])) { 154 | $this->stripe_card->address_state = $properties['address_state']; 155 | } 156 | if (!empty($properties['address_zip'])) { 157 | $this->stripe_card->address_zip = $properties['address_zip']; 158 | } 159 | if (!empty($properties['address_country'])) { 160 | $this->stripe_card->address_country = $properties['address_country']; 161 | } 162 | 163 | $this->stripe_card->save(); 164 | $this->stripe_card = null; 165 | 166 | return $this; 167 | } 168 | 169 | /** 170 | * Delete a card. 171 | * 172 | * @return Card 173 | */ 174 | public function delete() 175 | { 176 | $this->info(); 177 | $this->stripe_card->delete(); 178 | $this->stripe_card = null; 179 | return $this; 180 | } 181 | 182 | /** 183 | * Gets the native card response. 184 | * 185 | * @return Stripe_Card 186 | */ 187 | public function getNativeResponse() 188 | { 189 | $this->info(); 190 | return $this->stripe_card; 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/Mmanos/Billing/Gateways/Stripe/Charge.php: -------------------------------------------------------------------------------- 1 | gateway = $gateway; 50 | $this->stripe_customer = $customer; 51 | 52 | if ($id instanceof Stripe_Charge) { 53 | $this->stripe_charge = $id; 54 | $this->id = $this->stripe_charge->id; 55 | } 56 | else if (null !== $id) { 57 | $this->id = $id; 58 | } 59 | } 60 | 61 | /** 62 | * Gets the id of this instance. 63 | * 64 | * @return mixed 65 | */ 66 | public function id() 67 | { 68 | return $this->id; 69 | } 70 | 71 | /** 72 | * Gets info for a charge. 73 | * 74 | * @return array|null 75 | */ 76 | public function info() 77 | { 78 | if (!$this->id || !$this->stripe_customer) { 79 | return null; 80 | } 81 | 82 | if (!$this->stripe_charge) { 83 | $this->stripe_charge = Stripe_Charge::retrieve($this->id); 84 | 85 | if ($this->stripe_customer->id != $this->stripe_charge->customer) { 86 | return $this->stripe_charge = null; 87 | } 88 | } 89 | 90 | if (!$this->stripe_charge) { 91 | return null; 92 | } 93 | 94 | return array( 95 | 'id' => $this->id, 96 | 'created_at' => date('Y-m-d H:i:s', $this->stripe_charge->created), 97 | 'amount' => $this->stripe_charge->amount, 98 | 'paid' => $this->stripe_charge->paid, 99 | 'refunded' => $this->stripe_charge->refunded, 100 | 'captured' => $this->stripe_charge->captured, 101 | 'card' => $this->stripe_charge->card ? $this->stripe_charge->card->id : null, 102 | 'invoice_id' => $this->stripe_charge->invoice, 103 | 'description' => $this->stripe_charge->description, 104 | ); 105 | } 106 | 107 | /** 108 | * Create a new charge. 109 | * 110 | * @param int $amount 111 | * @param array $properties 112 | * 113 | * @return Charge 114 | */ 115 | public function create($amount, array $properties = array()) 116 | { 117 | $card = empty($properties['card']) ? null : $properties['card']; 118 | if (!empty($properties['card_token'])) { 119 | $card = $properties['card_token']; 120 | } 121 | 122 | $stripe_charge = Stripe_Charge::create(array( 123 | 'amount' => $amount, 124 | 'customer' => $this->stripe_customer->id, 125 | 'currency' => Arr::get($properties, 'currency', 'usd'), 126 | 'description' => Arr::get($properties, 'description') ? Arr::get($properties, 'description') : null, 127 | 'capture' => Arr::get($properties, 'capture', true), 128 | 'card' => $card, 129 | )); 130 | 131 | $this->id = $stripe_charge->id; 132 | 133 | return $this; 134 | } 135 | 136 | /** 137 | * Capture a preauthorized charge. 138 | * 139 | * @param array $properties 140 | * 141 | * @return Charge 142 | */ 143 | public function capture(array $properties = array()) 144 | { 145 | $this->info(); 146 | $this->stripe_charge->capture(array( 147 | 'amount' => Arr::get($properties, 'amount') ? Arr::get($properties, 'amount') : null, 148 | )); 149 | $this->stripe_charge = null; 150 | return $this; 151 | } 152 | 153 | /** 154 | * Refund a charge. 155 | * 156 | * @param array $properties 157 | * 158 | * @return Charge 159 | */ 160 | public function refund(array $properties = array()) 161 | { 162 | $this->info(); 163 | $this->stripe_charge->refunds->create(array( 164 | 'amount' => Arr::get($properties, 'amount') ? Arr::get($properties, 'amount') : null, 165 | 'reason' => Arr::get($properties, 'reason') ? Arr::get($properties, 'reason') : null, 166 | )); 167 | $this->stripe_charge = null; 168 | return $this; 169 | } 170 | 171 | /** 172 | * Gets the native charge response. 173 | * 174 | * @return Stripe_Charge 175 | */ 176 | public function getNativeResponse() 177 | { 178 | $this->info(); 179 | return $this->stripe_charge; 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/Mmanos/Billing/Gateways/Stripe/Customer.php: -------------------------------------------------------------------------------- 1 | gateway = $gateway; 43 | 44 | if ($id instanceof Stripe_Customer) { 45 | $this->stripe_customer = $id; 46 | $this->id = $this->stripe_customer->id; 47 | } 48 | else if (null !== $id) { 49 | $this->id = $id; 50 | } 51 | } 52 | 53 | /** 54 | * Gets the id of this instance. 55 | * 56 | * @return mixed 57 | */ 58 | public function id() 59 | { 60 | return $this->id; 61 | } 62 | 63 | /** 64 | * Gets info for a customer. 65 | * 66 | * @return array|null 67 | */ 68 | public function info() 69 | { 70 | if (!$this->id) { 71 | return null; 72 | } 73 | 74 | if (!$this->stripe_customer) { 75 | $this->stripe_customer = Stripe_Customer::retrieve($this->id); 76 | } 77 | 78 | if (!$this->stripe_customer || property_exists($this->stripe_customer, 'deleted')) { 79 | return null; 80 | } 81 | 82 | $discounts = array(); 83 | if ($this->stripe_customer->discount) { 84 | $discounts[] = array( 85 | 'coupon' => $this->stripe_customer->discount->coupon->id, 86 | 'amount_off' => $this->stripe_customer->discount->coupon->amount_off, 87 | 'percent_off' => $this->stripe_customer->discount->coupon->percent_off, 88 | 'started_at' => date('Y-m-d H:i:s', $this->stripe_customer->discount->start), 89 | 'ends_at' => $this->stripe_customer->discount->end ? date('Y-m-d H:i:s', $this->stripe_customer->discount->end) : null, 90 | ); 91 | } 92 | 93 | return array( 94 | 'id' => $this->id, 95 | 'description' => $this->stripe_customer->description, 96 | 'email' => $this->stripe_customer->email, 97 | 'created_at' => date('Y-m-d H:i:s', $this->stripe_customer->created), 98 | 'discounts' => $discounts, 99 | ); 100 | } 101 | 102 | /** 103 | * Create a new customer. 104 | * 105 | * @param array $properties 106 | * 107 | * @return Customer 108 | */ 109 | public function create(array $properties = array()) 110 | { 111 | $stripe_customer = Stripe_Customer::create(array( 112 | 'description' => Arr::get($properties, 'description') ? Arr::get($properties, 'description') : null, 113 | 'email' => Arr::get($properties, 'email') ? Arr::get($properties, 'email') : null, 114 | 'coupon' => Arr::get($properties, 'coupon') ? Arr::get($properties, 'coupon') : null, 115 | 'card' => Arr::get($properties, 'card_token') ? Arr::get($properties, 'card_token') : null, 116 | )); 117 | 118 | $this->id = $stripe_customer->id; 119 | 120 | return $this; 121 | } 122 | 123 | /** 124 | * Update a customer. 125 | * 126 | * @param array $properties 127 | * 128 | * @return Customer 129 | */ 130 | public function update(array $properties = array()) 131 | { 132 | $this->info(); 133 | 134 | if (!empty($properties['description'])) { 135 | $this->stripe_customer->description = $properties['description']; 136 | } 137 | if (!empty($properties['email'])) { 138 | $this->stripe_customer->email = $properties['email']; 139 | } 140 | if (!empty($properties['coupon'])) { 141 | $this->stripe_customer->coupon = $properties['coupon']; 142 | } 143 | if (!empty($properties['card_token'])) { 144 | $this->stripe_customer->card = $properties['card_token']; 145 | } 146 | 147 | $this->stripe_customer->save(); 148 | $this->stripe_customer = null; 149 | 150 | return $this; 151 | } 152 | 153 | /** 154 | * Delete a customer. 155 | * 156 | * @return Customer 157 | */ 158 | public function delete() 159 | { 160 | $this->info(); 161 | $this->stripe_customer->delete(); 162 | $this->stripe_customer = null; 163 | return $this; 164 | } 165 | 166 | /** 167 | * Gets all subscriptions for a customer. 168 | * 169 | * @return array 170 | */ 171 | public function subscriptions() 172 | { 173 | $this->info(); 174 | 175 | if (!$this->stripe_customer) { 176 | return array(); 177 | } 178 | 179 | $subscriptions = $this->stripe_customer->subscriptions->all(); 180 | 181 | $subscriptions_array = array(); 182 | foreach ($subscriptions->data as $subscription) { 183 | $subscriptions_array[] = $this->gateway->subscription($subscription, $this); 184 | } 185 | 186 | return $subscriptions_array; 187 | } 188 | 189 | /** 190 | * Gets all credit cards for a customer. 191 | * 192 | * @return array 193 | */ 194 | public function cards() 195 | { 196 | $this->info(); 197 | 198 | if (!$this->stripe_customer) { 199 | return array(); 200 | } 201 | 202 | $cards = $this->stripe_customer->cards->all(); 203 | 204 | $cards_array = array(); 205 | foreach ($cards->data as $card) { 206 | $cards_array[] = $this->card($card); 207 | } 208 | 209 | return $cards_array; 210 | } 211 | 212 | /** 213 | * Fetch a customer card instance. 214 | * 215 | * @param mixed $id 216 | * 217 | * @return Card 218 | */ 219 | public function card($id = null) 220 | { 221 | return new Card($this->gateway, $this->getNativeResponse(), $id); 222 | } 223 | 224 | /** 225 | * Gets all invoices for a customer. 226 | * 227 | * @return array 228 | */ 229 | public function invoices(array $parameters = array()) 230 | { 231 | $this->info(); 232 | 233 | if (!$this->stripe_customer) { 234 | return array(); 235 | } 236 | 237 | $parameters = array_merge(array( 238 | 'limit' => 100, 239 | ), $parameters); 240 | 241 | $parameters['customer'] = $this->id; 242 | 243 | $invoices = Stripe_Invoice::all($parameters); 244 | 245 | $invoices_array = array(); 246 | foreach ($invoices->data as $invoice) { 247 | $invoices_array[] = $this->invoice($invoice); 248 | } 249 | 250 | return $invoices_array; 251 | } 252 | 253 | /** 254 | * Fetch an invoice instance. 255 | * 256 | * @param mixed $id 257 | * 258 | * @return Invoice 259 | */ 260 | public function invoice($id = null) 261 | { 262 | return new Invoice($this->gateway, $this->getNativeResponse(), $id); 263 | } 264 | 265 | /** 266 | * Gets all charges for a customer. 267 | * 268 | * @return array 269 | */ 270 | public function charges() 271 | { 272 | $this->info(); 273 | 274 | if (!$this->stripe_customer) { 275 | return array(); 276 | } 277 | 278 | $charges = Stripe_Charge::all(array( 279 | 'customer' => $this->id, 280 | 'limit' => 100, 281 | )); 282 | 283 | $charges_array = array(); 284 | foreach ($charges->data as $charge) { 285 | $charges_array[] = $this->charge($charge); 286 | } 287 | 288 | return $charges_array; 289 | } 290 | 291 | /** 292 | * Fetch a charge instance. 293 | * 294 | * @param mixed $id 295 | * 296 | * @return Charge 297 | */ 298 | public function charge($id = null) 299 | { 300 | return new Charge($this->gateway, $this->getNativeResponse(), $id); 301 | } 302 | 303 | /** 304 | * Gets the native customer response. 305 | * 306 | * @return Stripe_Customer 307 | */ 308 | public function getNativeResponse() 309 | { 310 | $this->info(); 311 | return $this->stripe_customer; 312 | } 313 | } 314 | -------------------------------------------------------------------------------- /src/Mmanos/Billing/Gateways/Stripe/Gateway.php: -------------------------------------------------------------------------------- 1 | connection = $connection; 29 | 30 | Stripe::setApiKey($connection['secret']); 31 | } 32 | 33 | /** 34 | * Fetch a customer instance. 35 | * 36 | * @param mixed $id 37 | * 38 | * @return Customer 39 | */ 40 | public function customer($id = null) 41 | { 42 | return new Customer($this, $id); 43 | } 44 | 45 | /** 46 | * Fetch a subscription instance. 47 | * 48 | * @param mixed $id 49 | * @param Customer $customer 50 | * 51 | * @return Subscription 52 | */ 53 | public function subscription($id = null, CustomerInterface $customer = null) 54 | { 55 | if ($customer) { 56 | $customer = $customer->getNativeResponse(); 57 | } 58 | 59 | return new Subscription($this, $customer, $id); 60 | } 61 | 62 | /** 63 | * Fetch a charge instance. 64 | * 65 | * @param mixed $id 66 | * @param CustomerInterface $customer 67 | * 68 | * @return Charge 69 | */ 70 | public function charge($id = null, CustomerInterface $customer = null) 71 | { 72 | if ($customer) { 73 | $customer = $customer->getNativeResponse(); 74 | } 75 | 76 | return new Charge($this, $customer, $id); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Mmanos/Billing/Gateways/Stripe/Invoice.php: -------------------------------------------------------------------------------- 1 | gateway = $gateway; 50 | $this->stripe_customer = $customer; 51 | 52 | if ($id instanceof Stripe_Invoice) { 53 | $this->stripe_invoice = $id; 54 | $this->id = $this->stripe_invoice->id; 55 | } 56 | else if (null !== $id) { 57 | $this->id = $id; 58 | } 59 | } 60 | 61 | /** 62 | * Gets the id of this instance. 63 | * 64 | * @return mixed 65 | */ 66 | public function id() 67 | { 68 | return $this->id; 69 | } 70 | 71 | /** 72 | * Gets info for an invoice. 73 | * 74 | * @return array|null 75 | */ 76 | public function info() 77 | { 78 | if (!$this->id || !$this->stripe_customer) { 79 | return null; 80 | } 81 | 82 | if (!$this->stripe_invoice) { 83 | $this->stripe_invoice = Stripe_Invoice::retrieve($this->id); 84 | 85 | if ($this->stripe_customer->id != $this->stripe_invoice->customer) { 86 | return $this->stripe_invoice = null; 87 | } 88 | } 89 | 90 | if (!$this->stripe_invoice) { 91 | return null; 92 | } 93 | 94 | $discounts = array(); 95 | if ($this->stripe_invoice->discount) { 96 | $discounts[] = array( 97 | 'coupon' => $this->stripe_invoice->discount->coupon->id, 98 | 'amount_off' => $this->stripe_invoice->discount->coupon->amount_off, 99 | 'percent_off' => $this->stripe_invoice->discount->coupon->percent_off, 100 | 'started_at' => date('Y-m-d H:i:s', $this->stripe_invoice->discount->start), 101 | 'ends_at' => $this->stripe_invoice->discount->end ? date('Y-m-d H:i:s', $this->stripe_invoice->discount->end) : null, 102 | ); 103 | } 104 | 105 | $items = array(); 106 | foreach ($this->stripe_invoice->lines->data as $line) { 107 | $item = array( 108 | 'id' => $line->id, 109 | 'amount' => $line->amount, 110 | 'period_start' => null, 111 | 'period_end' => null, 112 | 'description' => $line->description, 113 | 'subscription_id' => ('subscription' == $line->type) ? $line->id : $line->subscription, 114 | 'quantity' => $line->quantity, 115 | ); 116 | 117 | if ($line->period && $line->period->start) { 118 | $item['period_start'] = date('Y-m-d H:i:s', $line->period->start); 119 | } 120 | if ($line->period && $line->period->end) { 121 | $item['period_end'] = date('Y-m-d H:i:s', $line->period->end); 122 | } 123 | 124 | $items[] = $item; 125 | } 126 | 127 | return array( 128 | 'id' => $this->id, 129 | 'date' => date('Y-m-d H:i:s', $this->stripe_invoice->date), 130 | 'total' => $this->stripe_invoice->total, 131 | 'subtotal' => $this->stripe_invoice->subtotal, 132 | 'amount' => $this->stripe_invoice->amount_due, 133 | 'starting_balance' => $this->stripe_invoice->starting_balance, 134 | 'ending_balance' => $this->stripe_invoice->ending_balance, 135 | 'closed' => $this->stripe_invoice->closed, 136 | 'paid' => $this->stripe_invoice->paid, 137 | 'discounts' => $discounts, 138 | 'items' => $items, 139 | ); 140 | } 141 | 142 | /** 143 | * Gets the native invoice response. 144 | * 145 | * @return Stripe_Invoice 146 | */ 147 | public function getNativeResponse() 148 | { 149 | $this->info(); 150 | return $this->stripe_invoice; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/Mmanos/Billing/Gateways/Stripe/Subscription.php: -------------------------------------------------------------------------------- 1 | gateway = $gateway; 50 | $this->stripe_customer = $customer; 51 | 52 | if ($id instanceof Stripe_Subscription) { 53 | $this->stripe_subscription = $id; 54 | $this->id = $this->stripe_subscription->id; 55 | } 56 | else if (null !== $id) { 57 | $this->id = $id; 58 | } 59 | } 60 | 61 | /** 62 | * Gets the id of this instance. 63 | * 64 | * @return mixed 65 | */ 66 | public function id() 67 | { 68 | return $this->id; 69 | } 70 | 71 | /** 72 | * Gets info for a subscription. 73 | * 74 | * @return array|null 75 | */ 76 | public function info() 77 | { 78 | if (!$this->id || !$this->stripe_customer) { 79 | return null; 80 | } 81 | 82 | if (!$this->stripe_subscription) { 83 | $this->stripe_subscription = $this->stripe_customer->subscriptions->retrieve($this->id); 84 | } 85 | 86 | if (!$this->stripe_subscription) { 87 | return null; 88 | } 89 | 90 | $trial_end = $this->stripe_subscription->trial_end; 91 | 92 | $discounts = array(); 93 | if ($this->stripe_subscription->discount) { 94 | $discounts[] = array( 95 | 'coupon' => $this->stripe_subscription->discount->coupon->id, 96 | 'amount_off' => $this->stripe_subscription->discount->coupon->amount_off, 97 | 'percent_off' => $this->stripe_subscription->discount->coupon->percent_off, 98 | 'started_at' => date('Y-m-d H:i:s', $this->stripe_subscription->discount->start), 99 | 'ends_at' => $this->stripe_subscription->discount->end ? date('Y-m-d H:i:s', $this->stripe_subscription->discount->end) : null, 100 | ); 101 | } 102 | 103 | return array( 104 | 'id' => $this->id, 105 | 'plan' => $this->stripe_subscription->plan->id, 106 | 'amount' => $this->stripe_subscription->plan->amount, 107 | 'interval' => $this->stripe_subscription->plan->interval, 108 | 'active' => ('canceled' != $this->stripe_subscription->status && !$this->stripe_subscription->cancel_at_period_end), 109 | 'quantity' => $this->stripe_subscription->quantity, 110 | 'started_at' => date('Y-m-d H:i:s', $this->stripe_subscription->start), 111 | 'period_started_at' => date('Y-m-d H:i:s', $this->stripe_subscription->current_period_start), 112 | 'period_ends_at' => date('Y-m-d H:i:s', $this->stripe_subscription->current_period_end), 113 | 'trial_ends_at' => $trial_end ? date('Y-m-d H:i:s', $trial_end) : null, 114 | 'card' => $this->stripe_customer->default_card, 115 | 'discounts' => $discounts, 116 | ); 117 | } 118 | 119 | /** 120 | * Create a new subscription. 121 | * 122 | * @param mixed $plan 123 | * @param array $properties 124 | * 125 | * @return Subscription 126 | */ 127 | public function create($plan, array $properties = array()) 128 | { 129 | $trial_end = null; 130 | if (!empty($properties['trial_ends_at'])) { 131 | $trial_end = strtotime($properties['trial_ends_at']); 132 | if ($trial_end <= time()) { 133 | $trial_end = 'now'; 134 | } 135 | } 136 | 137 | // Note: Stripe does not yet support specifying an existing card for a subscription. 138 | // This feature is coming in a future relase, however. 139 | // Currently you can only specify a card token and the same card is used for all 140 | // customer subscriptions. 141 | $stripe_subscriptions = $this->stripe_customer->subscriptions; 142 | 143 | if ( ! $stripe_subscriptions) { 144 | throw new \Exception("Stripe Customer does not exist."); 145 | } 146 | 147 | $stripe_subscription = $stripe_subscriptions->create(array( 148 | 'plan' => $plan, 149 | 'quantity' => Arr::get($properties, 'quantity') ? Arr::get($properties, 'quantity') : null, 150 | 'trial_end' => $trial_end, 151 | 'coupon' => Arr::get($properties, 'coupon') ? Arr::get($properties, 'coupon') : null, 152 | 'card' => Arr::get($properties, 'card_token') ? Arr::get($properties, 'card_token') : null, 153 | )); 154 | 155 | $this->id = $stripe_subscription->id; 156 | 157 | return $this; 158 | } 159 | 160 | /** 161 | * Update a subscription. 162 | * 163 | * @param array $properties 164 | * 165 | * @return Subscription 166 | */ 167 | public function update(array $properties = array()) 168 | { 169 | $this->info(); 170 | 171 | if (!empty($properties['plan'])) { 172 | $this->stripe_subscription->plan = $properties['plan']; 173 | } 174 | if (!empty($properties['quantity'])) { 175 | $this->stripe_subscription->quantity = $properties['quantity']; 176 | } 177 | if (!empty($properties['trial_ends_at'])) { 178 | if (strtotime($properties['trial_ends_at']) <= time()) { 179 | $this->stripe_subscription->trial_end = 'now'; 180 | } 181 | else { 182 | $this->stripe_subscription->trial_end = strtotime($properties['trial_ends_at']); 183 | } 184 | } 185 | if (isset($properties['prorate'])) { 186 | $this->stripe_subscription->prorate = $properties['prorate']; 187 | } 188 | if (!empty($properties['coupon'])) { 189 | $this->stripe_subscription->coupon = $properties['coupon']; 190 | } 191 | 192 | if (!empty($properties['card_token'])) { 193 | $this->stripe_subscription->card = $properties['card_token']; 194 | } 195 | else if (!empty($properties['card'])) { 196 | $this->stripe_subscription->card = $properties['card']; 197 | } 198 | 199 | $this->stripe_subscription->save(); 200 | $this->stripe_subscription = null; 201 | 202 | return $this; 203 | } 204 | 205 | /** 206 | * Cancel a subscription. 207 | * 208 | * @param bool $at_period_end 209 | * 210 | * @return Subscription 211 | */ 212 | public function cancel($at_period_end = true) 213 | { 214 | $this->info(); 215 | $this->stripe_subscription->cancel(array('at_period_end' => $at_period_end)); 216 | $this->stripe_subscription = null; 217 | return $this; 218 | } 219 | 220 | /** 221 | * Gets the native subscription response. 222 | * 223 | * @return Stripe_Customer 224 | */ 225 | public function getNativeResponse() 226 | { 227 | $this->info(); 228 | return $this->stripe_subscription; 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /src/Mmanos/Billing/Gateways/Stripe/WebhookController.php: -------------------------------------------------------------------------------- 1 | {$method}($payload); 23 | } 24 | else { 25 | return $this->missingMethod(); 26 | } 27 | } 28 | 29 | /** 30 | * Handle a newly created Stripe invoice. 31 | * 32 | * @param array $payload 33 | * 34 | * @return \Symfony\Component\HttpFoundation\Response 35 | */ 36 | protected function handleInvoiceCreated(array $payload) 37 | { 38 | if ($customer = $this->getCustomer($payload['data']['object']['customer'])) { 39 | $customer->fireCustomerEvent( 40 | 'invoiceCreated', 41 | $customer->invoices()->find($payload['data']['object']['id']) 42 | ); 43 | } 44 | 45 | return new Response('Webhook Handled', 200); 46 | } 47 | 48 | /** 49 | * Handle a failed payment from a Stripe invoice. 50 | * 51 | * @param array $payload 52 | * 53 | * @return \Symfony\Component\HttpFoundation\Response 54 | */ 55 | protected function handleInvoicePaymentFailed(array $payload) 56 | { 57 | if ($customer = $this->getCustomer($payload['data']['object']['customer'])) { 58 | $next_attempt = Arr::get($payload, 'data.object.next_payment_attempt'); 59 | 60 | $data = array( 61 | 'attempt_count' => Arr::get($payload, 'data.object.attempt_count'), 62 | 'next_attempt' => Carbon::createFromTimeStamp($next_attempt), 63 | ); 64 | 65 | $customer->fireCustomerEvent( 66 | 'invoicePaymentFailed', 67 | $customer->invoices()->find($payload['data']['object']['id']), 68 | $data 69 | ); 70 | } 71 | 72 | return new Response('Webhook Handled', 200); 73 | } 74 | 75 | /** 76 | * Handle a successful payment from a Stripe invoice. 77 | * 78 | * @param array $payload 79 | * 80 | * @return \Symfony\Component\HttpFoundation\Response 81 | */ 82 | protected function handleInvoicePaymentSucceeded(array $payload) 83 | { 84 | if ($customer = $this->getCustomer($payload['data']['object']['customer'])) { 85 | $customer->fireCustomerEvent( 86 | 'invoicePaymentSucceeded', 87 | $customer->invoices()->find($payload['data']['object']['id']) 88 | ); 89 | } 90 | 91 | return new Response('Webhook Handled', 200); 92 | } 93 | 94 | /** 95 | * Handle a Stripe subscription that has been canceled/deleted. 96 | * 97 | * @param array $payload 98 | * 99 | * @return \Symfony\Component\HttpFoundation\Response 100 | */ 101 | protected function handleCustomerSubscriptionDeleted(array $payload) 102 | { 103 | if ($subscription = $this->getSubscription($payload['data']['object']['id'])) { 104 | if ($subscription->billingIsActive()) { 105 | $subscription->subscription()->refresh(); 106 | } 107 | } 108 | 109 | return new Response('Webhook Handled', 200); 110 | } 111 | 112 | /** 113 | * Handle a Stripe subscription who's trial is going to end in three days. 114 | * 115 | * @param array $payload 116 | * 117 | * @return \Symfony\Component\HttpFoundation\Response 118 | */ 119 | protected function handleCustomerSubscriptionTrialWillEnd(array $payload) 120 | { 121 | if ($subscription = $this->getSubscription($payload['data']['object']['id'])) { 122 | $subscription->fireSubscriptionEvent('trialWillEnd', array( 123 | 'days' => 3, 124 | )); 125 | } 126 | 127 | return new Response('Webhook Handled', 200); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/Mmanos/Billing/Gateways/SubscriptionInterface.php: -------------------------------------------------------------------------------- 1 | string('billing_id')->nullable(); 18 | $table->text('billing_cards')->nullable(); 19 | $table->text('billing_discounts')->nullable(); 20 | 21 | $table->index('billing_id', 'billing_id_index'); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | * 28 | * @return void 29 | */ 30 | public function down() 31 | { 32 | Schema::table('customer_table', function(Blueprint $table) 33 | { 34 | $table->dropIndex('billing_id_index'); 35 | 36 | $table->dropColumn('billing_id', 'billing_cards', 'billing_discounts'); 37 | }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Mmanos/Billing/Stubs/SubscriptionMigration.stub: -------------------------------------------------------------------------------- 1 | tinyInteger('billing_active')->default(0); 18 | $table->string('billing_subscription')->nullable(); 19 | $table->tinyInteger('billing_free')->default(0); 20 | $table->string('billing_plan', 25)->nullable(); 21 | $table->integer('billing_amount')->default(0); 22 | $table->string('billing_interval')->nullable(); 23 | $table->integer('billing_quantity')->default(0); 24 | $table->string('billing_card')->nullable(); 25 | $table->timestamp('billing_trial_ends_at')->nullable(); 26 | $table->timestamp('billing_subscription_ends_at')->nullable(); 27 | $table->text('billing_subscription_discounts')->nullable(); 28 | 29 | $table->index('billing_subscription', 'billing_subscription_index'); 30 | $table->index('billing_card', 'billing_card_index'); 31 | }); 32 | } 33 | 34 | /** 35 | * Reverse the migrations. 36 | * 37 | * @return void 38 | */ 39 | public function down() 40 | { 41 | Schema::table('subscription_table', function(Blueprint $table) 42 | { 43 | $table->dropIndex('billing_subscription_index'); 44 | $table->dropIndex('billing_card_index'); 45 | 46 | $table->dropColumn( 47 | 'billing_active', 'billing_subscription', 'billing_free', 'billing_plan', 'billing_amount', 48 | 'billing_interval', 'billing_quantity', 'billing_card', 'billing_trial_ends_at', 49 | 'billing_subscription_ends_at', 'billing_subscription_discounts' 50 | ); 51 | }); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Mmanos/Billing/SubscriptionBillableTrait.php: -------------------------------------------------------------------------------- 1 | everSubscribed()) { 16 | return null; 17 | } 18 | 19 | if ($customer = $this->customer()) { 20 | $customer = $customer->gatewayCustomer(); 21 | } 22 | 23 | return Billing::subscription($this->billing_subscription, $customer); 24 | } 25 | 26 | /** 27 | * Return the subscription billing helper object. 28 | * 29 | * @param mixed $plan 30 | * 31 | * @return SubscriptionBillableTrait\Subscription 32 | */ 33 | public function subscription($plan = null) 34 | { 35 | return new SubscriptionBillableTrait\Subscription($this, $this->gatewaySubscription(), $plan); 36 | } 37 | 38 | /** 39 | * Determine if the entity is within their trial period. 40 | * 41 | * @return bool 42 | */ 43 | public function onTrial() 44 | { 45 | if ($this->billing_trial_ends_at) { 46 | return time() < strtotime($this->billing_trial_ends_at); 47 | } 48 | 49 | return false; 50 | } 51 | 52 | /** 53 | * Determine if the entity is on grace period after cancellation. 54 | * 55 | * @return bool 56 | */ 57 | public function onGracePeriod() 58 | { 59 | if ($this->billing_subscription_ends_at) { 60 | return time() < strtotime($this->billing_subscription_ends_at); 61 | } 62 | 63 | return false; 64 | } 65 | 66 | /** 67 | * Determine if the entity has an active subscription. 68 | * 69 | * @return bool 70 | */ 71 | public function subscribed() 72 | { 73 | if ($this->billing_free) { 74 | if (!$this->billing_subscription_ends_at 75 | || time() < strtotime($this->billing_subscription_ends_at) 76 | ) { 77 | return true; 78 | } 79 | } 80 | 81 | if (!isset($this->cardUpFront) || $this->cardUpFront) { 82 | return $this->billingIsActive() || $this->onGracePeriod(); 83 | } 84 | 85 | return $this->billingIsActive() || $this->onGracePeriod() || $this->onTrial(); 86 | } 87 | 88 | /** 89 | * Determine if the entity's trial has expired. 90 | * 91 | * @return bool 92 | */ 93 | public function expired() 94 | { 95 | return !$this->subscribed() && $this->billing_trial_ends_at && strtotime($this->billing_trial_ends_at) <= time(); 96 | } 97 | 98 | /** 99 | * Determine if the entity had a subscription which is no longer active. 100 | * 101 | * @return bool 102 | */ 103 | public function canceled() 104 | { 105 | return $this->everSubscribed() && !$this->billingIsActive(); 106 | } 107 | 108 | /** 109 | * Deteremine if the user has ever been subscribed. 110 | * 111 | * @return bool 112 | */ 113 | public function everSubscribed() 114 | { 115 | return !empty($this->billing_subscription); 116 | } 117 | 118 | /** 119 | * Determine if the entity has a current subscription. 120 | * 121 | * @return bool 122 | */ 123 | public function billingIsActive() 124 | { 125 | return $this->billing_active; 126 | } 127 | 128 | /** 129 | * Whether or not this model requires a credit card up front. 130 | * 131 | * @return bool 132 | */ 133 | public function requiresCardUpFront() 134 | { 135 | if (!isset($this->cardUpFront) || $this->cardUpFront) { 136 | return true; 137 | } 138 | 139 | return false; 140 | } 141 | 142 | /** 143 | * Return the Eloquent model acting as the billing customer for this model. 144 | * The customer model can be defined in one of the following ways: 145 | * - A 'customermodel' relationship on this model. 146 | * - A 'customermodel' method on this model that returns a valid Eloquent model. 147 | * 148 | * @return \Illuminate\Database\Eloquent\Model 149 | */ 150 | public function customer() 151 | { 152 | // Note: Laravel throws a LogicException if a customer method exists but 153 | // doesn't return a valid relationship. 154 | try { 155 | if ($customer = $this->customermodel) { 156 | return $customer; 157 | } 158 | } catch (LogicException $e) {} 159 | 160 | if (method_exists($this, 'customermodel')) { 161 | return $this->customermodel(); 162 | } 163 | 164 | // Check for customer/subscription on the same model. 165 | if (method_exists($this, 'gatewayCustomer')) { 166 | return $this; 167 | } 168 | 169 | return null; 170 | } 171 | 172 | /** 173 | * Getter for billing_subscription_discounts property. 174 | * 175 | * @param string $value 176 | * 177 | * @return array 178 | */ 179 | public function getBillingSubscriptionDiscountsAttribute($value) 180 | { 181 | return $value ? json_decode($value, true) : array(); 182 | } 183 | 184 | /** 185 | * Setter for billing_subscription_discounts property. 186 | * 187 | * @param array $value 188 | * 189 | * @return void 190 | */ 191 | public function setBillingSubscriptionDiscountsAttribute($value) 192 | { 193 | $this->attributes['billing_subscription_discounts'] = empty($value) ? null : json_encode($value); 194 | } 195 | 196 | /** 197 | * Register a billingActivated model event with the dispatcher. 198 | * 199 | * @param \Closure|string $callback 200 | * 201 | * @return void 202 | */ 203 | public static function billingActivated($callback) 204 | { 205 | static::listenForSubscriptionEvents(); 206 | static::registerModelEvent('billingActivated', $callback); 207 | } 208 | 209 | /** 210 | * Register a billingCanceled model event with the dispatcher. 211 | * 212 | * @param \Closure|string $callback 213 | * 214 | * @return void 215 | */ 216 | public static function billingCanceled($callback) 217 | { 218 | static::listenForSubscriptionEvents(); 219 | static::registerModelEvent('billingCanceled', $callback); 220 | } 221 | 222 | /** 223 | * Register a planSwapped model event with the dispatcher. 224 | * 225 | * @param \Closure|string $callback 226 | * 227 | * @return void 228 | */ 229 | public static function planSwapped($callback) 230 | { 231 | static::listenForSubscriptionEvents(); 232 | static::registerModelEvent('planSwapped', $callback); 233 | } 234 | 235 | /** 236 | * Register a planChanged model event with the dispatcher. 237 | * 238 | * @param \Closure|string $callback 239 | * 240 | * @return void 241 | */ 242 | public static function planChanged($callback) 243 | { 244 | static::listenForSubscriptionEvents(); 245 | static::registerModelEvent('planChanged', $callback); 246 | } 247 | 248 | /** 249 | * Register a subscriptionIncremented model event with the dispatcher. 250 | * 251 | * @param \Closure|string $callback 252 | * 253 | * @return void 254 | */ 255 | public static function subscriptionIncremented($callback) 256 | { 257 | static::listenForSubscriptionEvents(); 258 | static::registerModelEvent('subscriptionIncremented', $callback); 259 | } 260 | 261 | /** 262 | * Register a subscriptionDecremented model event with the dispatcher. 263 | * 264 | * @param \Closure|string $callback 265 | * 266 | * @return void 267 | */ 268 | public static function subscriptionDecremented($callback) 269 | { 270 | static::listenForSubscriptionEvents(); 271 | static::registerModelEvent('subscriptionDecremented', $callback); 272 | } 273 | 274 | /** 275 | * Register a billingResumed model event with the dispatcher. 276 | * 277 | * @param \Closure|string $callback 278 | * 279 | * @return void 280 | */ 281 | public static function billingResumed($callback) 282 | { 283 | static::listenForSubscriptionEvents(); 284 | static::registerModelEvent('billingResumed', $callback); 285 | } 286 | 287 | /** 288 | * Register a trialExtended model event with the dispatcher. 289 | * 290 | * @param \Closure|string $callback 291 | * 292 | * @return void 293 | */ 294 | public static function trialExtended($callback) 295 | { 296 | static::listenForSubscriptionEvents(); 297 | static::registerModelEvent('trialExtended', $callback); 298 | } 299 | 300 | /** 301 | * Register a trialChanged model event with the dispatcher. 302 | * 303 | * @param \Closure|string $callback 304 | * 305 | * @return void 306 | */ 307 | public static function trialChanged($callback) 308 | { 309 | static::listenForSubscriptionEvents(); 310 | static::registerModelEvent('trialChanged', $callback); 311 | } 312 | 313 | /** 314 | * Register a trialWillEnd model event with the dispatcher. 315 | * Triggered via webhook. 316 | * 317 | * @param \Closure|string $callback 318 | * 319 | * @return void 320 | */ 321 | public static function trialWillEnd($callback) 322 | { 323 | static::registerModelEvent('trialWillEnd', $callback); 324 | } 325 | 326 | /** 327 | * Register a subscriptionDiscountAdded model event with the dispatcher. 328 | * 329 | * @param \Closure|string $callback 330 | * 331 | * @return void 332 | */ 333 | public static function subscriptionDiscountAdded($callback) 334 | { 335 | static::listenForSubscriptionEvents(); 336 | static::registerModelEvent('subscriptionDiscountAdded', $callback); 337 | } 338 | 339 | /** 340 | * Register a subscriptionDiscountRemoved model event with the dispatcher. 341 | * 342 | * @param \Closure|string $callback 343 | * 344 | * @return void 345 | */ 346 | public static function subscriptionDiscountRemoved($callback) 347 | { 348 | static::listenForSubscriptionEvents(); 349 | static::registerModelEvent('subscriptionDiscountRemoved', $callback); 350 | } 351 | 352 | /** 353 | * Register a subscriptionDiscountUpdated model event with the dispatcher. 354 | * 355 | * @param \Closure|string $callback 356 | * 357 | * @return void 358 | */ 359 | public static function subscriptionDiscountUpdated($callback) 360 | { 361 | static::listenForSubscriptionEvents(); 362 | static::registerModelEvent('subscriptionDiscountUpdated', $callback); 363 | } 364 | 365 | /** 366 | * Register a subscriptionDiscountChanged model event with the dispatcher. 367 | * 368 | * @param \Closure|string $callback 369 | * 370 | * @return void 371 | */ 372 | public static function subscriptionDiscountChanged($callback) 373 | { 374 | static::listenForSubscriptionEvents(); 375 | static::registerModelEvent('subscriptionDiscountChanged', $callback); 376 | } 377 | 378 | /** 379 | * Fire the given event for the model. 380 | * 381 | * @param string $event 382 | * 383 | * @return mixed 384 | */ 385 | public function fireSubscriptionEvent($event) 386 | { 387 | if ( ! isset(static::$dispatcher)) return true; 388 | 389 | // We will append the names of the class to the event to distinguish it from 390 | // other model events that are fired, allowing us to listen on each model 391 | // event set individually instead of catching event for all the models. 392 | $event = "eloquent.{$event}: ".get_class($this); 393 | 394 | $args = array_merge(array($this), array_slice(func_get_args(), 1)); 395 | 396 | return static::$dispatcher->fire($event, $args); 397 | } 398 | 399 | /** 400 | * Register listeners for model change events so we can trigger our own 401 | * custom events. 402 | * 403 | * @return void 404 | */ 405 | protected static function listenForSubscriptionEvents() 406 | { 407 | static $listening_for_subscription_events; 408 | if ($listening_for_subscription_events) { 409 | return; 410 | } 411 | 412 | static::saved(function ($model) { 413 | $original = $model->getOriginal(); 414 | 415 | if ($model->isDirty('billing_active')) { 416 | if (empty($original['billing_active']) && !empty($model->billing_active)) { 417 | $model->fireSubscriptionEvent('billingActivated'); 418 | 419 | if ($model->isDirty('billing_subscription_ends_at')) { 420 | if (empty($model->billing_subscription_ends_at) && !empty($original['billing_subscription_ends_at'])) { 421 | $model->fireSubscriptionEvent('billingResumed'); 422 | } 423 | } 424 | } 425 | else if (empty($model->billing_active) && !empty($original['billing_active'])) { 426 | $model->fireSubscriptionEvent('billingCanceled'); 427 | } 428 | } 429 | if ($model->isDirty('billing_plan')) { 430 | if (!empty($original['billing_plan']) && !empty($model->billing_plan)) { 431 | $model->fireSubscriptionEvent('planSwapped'); 432 | } 433 | if (!empty($model->billing_plan)) { 434 | $model->fireSubscriptionEvent('planChanged'); 435 | } 436 | } 437 | if ($model->isDirty('billing_quantity')) { 438 | if ($model->billing_quantity > 0 && $model->getOriginal('billing_quantity') > 0) { 439 | if ($model->billing_quantity > $model->getOriginal('billing_quantity')) { 440 | $model->fireSubscriptionEvent('subscriptionIncremented'); 441 | } 442 | else { 443 | $model->fireSubscriptionEvent('subscriptionDecremented'); 444 | } 445 | } 446 | } 447 | if ($model->isDirty('billing_trial_ends_at')) { 448 | if (!empty($model->billing_trial_ends_at) && !empty($original['billing_trial_ends_at'])) { 449 | if (strtotime($model->billing_trial_ends_at) > strtotime($model->getOriginal('billing_trial_ends_at'))) { 450 | $model->fireSubscriptionEvent('trialExtended'); 451 | } 452 | } 453 | $model->fireSubscriptionEvent('trialChanged'); 454 | } 455 | if ($model->isDirty('billing_subscription_discounts')) { 456 | if (count($model->billing_subscription_discounts) > count(json_decode($model->getOriginal('billing_subscription_discounts'), true))) { 457 | $model->fireSubscriptionEvent('subscriptionDiscountAdded'); 458 | } 459 | else if (count($model->billing_subscription_discounts) < count(json_decode($model->getOriginal('billing_subscription_discounts'), true))) { 460 | $model->fireSubscriptionEvent('subscriptionDiscountRemoved'); 461 | } 462 | else if (!empty($model->billing_subscription_discounts)) { 463 | $model->fireSubscriptionEvent('subscriptionDiscountUpdated'); 464 | } 465 | $model->fireSubscriptionEvent('subscriptionDiscountChanged'); 466 | } 467 | }); 468 | 469 | $listening_for_subscription_events = true; 470 | } 471 | } 472 | -------------------------------------------------------------------------------- /src/commands/CustomerTableCommand.php: -------------------------------------------------------------------------------- 1 | createBaseMigration(); 32 | 33 | file_put_contents($full_path, $this->getMigrationStub()); 34 | 35 | $this->info('Migration created successfully!'); 36 | 37 | $this->call('dump-autoload'); 38 | } 39 | 40 | /** 41 | * Create a base migration file for the customers. 42 | * 43 | * @return string 44 | */ 45 | protected function createBaseMigration() 46 | { 47 | $name = 'add_customer_billing_columns_to_' . $this->argument('table'); 48 | 49 | $path = $this->laravel['path'].'/database/migrations'; 50 | 51 | return $this->laravel['migration.creator']->create($name, $path); 52 | } 53 | 54 | /** 55 | * Get the contents of the customer migration stub. 56 | * 57 | * @return string 58 | */ 59 | protected function getMigrationStub() 60 | { 61 | $stub = file_get_contents(__DIR__.'/../Mmanos/Billing/Stubs/CustomerMigration.stub'); 62 | 63 | $stub = str_replace('customer_table', $this->argument('table'), $stub); 64 | $stub = str_replace( 65 | 'AddCustomerBillingColumnsTo', 66 | 'AddCustomerBillingColumnsTo' . Str::studly($this->argument('table')), 67 | $stub 68 | ); 69 | 70 | return $stub; 71 | } 72 | 73 | /** 74 | * Get the console command arguments. 75 | * 76 | * @return array 77 | */ 78 | protected function getArguments() 79 | { 80 | return array( 81 | array('table', InputArgument::REQUIRED, 'The name of your customer billable table.'), 82 | ); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/commands/LocalCreateCouponCommand.php: -------------------------------------------------------------------------------- 1 | error('Not configured to use the "local" driver.'); 36 | } 37 | 38 | // Init gateway. 39 | Facades\Billing::customer(); 40 | 41 | $code = $this->ask('What is the coupon Code (eg. 20percentoff)?'); 42 | $percent_off = $this->ask('What is the coupon Percent Off (enter for none)?'); 43 | $amount_off = $this->ask('What is the coupon Amount Off (in cents) (enter for none)?'); 44 | $duration_in_months = $this->ask('How many months should this coupon last (press enter for unlimited)?'); 45 | 46 | $coupon = Gateways\Local\Models\Coupon::create(array( 47 | 'code' => $code, 48 | 'percent_off' => $percent_off ? $percent_off : null, 49 | 'amount_off' => $amount_off ? $amount_off : null, 50 | 'duration_in_months' => $duration_in_months ? $duration_in_months : null, 51 | )); 52 | 53 | $this->info('Coupon created successfully: ' . $coupon->id); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/commands/LocalCreatePlanCommand.php: -------------------------------------------------------------------------------- 1 | error('Not configured to use the "local" driver.'); 36 | } 37 | 38 | // Init gateway. 39 | Facades\Billing::customer(); 40 | 41 | $key = $this->ask('What is the plan ID (eg. basic or pro)?'); 42 | $amount = $this->ask('What is the plan Amount (in cents)?'); 43 | $interval = $this->ask('What is the plan Interval (eg. monthly or yearly)?'); 44 | $trial_period_days = $this->ask('How many days of trial do you want to give (press enter for none)?'); 45 | 46 | $plan = Gateways\Local\Models\Plan::create(array( 47 | 'key' => $key, 48 | 'name' => ucwords($key), 49 | 'amount' => $amount, 50 | 'interval' => $interval, 51 | 'trial_period_days' => $trial_period_days, 52 | )); 53 | 54 | $this->info('Plan created successfully: ' . $plan->id); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/commands/SubscriptionTableCommand.php: -------------------------------------------------------------------------------- 1 | createBaseMigration(); 32 | 33 | file_put_contents($full_path, $this->getMigrationStub()); 34 | 35 | $this->info('Migration created successfully!'); 36 | 37 | $this->call('dump-autoload'); 38 | } 39 | 40 | /** 41 | * Create a base migration file for the subscriptions. 42 | * 43 | * @return string 44 | */ 45 | protected function createBaseMigration() 46 | { 47 | $name = 'add_subscription_billing_columns_to_' . $this->argument('table'); 48 | 49 | $path = $this->laravel['path'].'/database/migrations'; 50 | 51 | return $this->laravel['migration.creator']->create($name, $path); 52 | } 53 | 54 | /** 55 | * Get the contents of the subscription migration stub. 56 | * 57 | * @return string 58 | */ 59 | protected function getMigrationStub() 60 | { 61 | $stub = file_get_contents(__DIR__.'/../Mmanos/Billing/Stubs/SubscriptionMigration.stub'); 62 | 63 | $stub = str_replace('subscription_table', $this->argument('table'), $stub); 64 | $stub = str_replace( 65 | 'AddSubscriptionBillingColumnsTo', 66 | 'AddSubscriptionBillingColumnsTo' . Str::studly($this->argument('table')), 67 | $stub 68 | ); 69 | 70 | return $stub; 71 | } 72 | 73 | /** 74 | * Get the console command arguments. 75 | * 76 | * @return array 77 | */ 78 | protected function getArguments() 79 | { 80 | return array( 81 | array('table', InputArgument::REQUIRED, 'The name of your subscription billable table.'), 82 | ); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/config/config.php: -------------------------------------------------------------------------------- 1 | 'local', 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Customer Models 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Define all of the model classes that act as a billing customer. 26 | | 27 | */ 28 | 29 | 'customer_models' => array('User'), 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Subscription Models 34 | |-------------------------------------------------------------------------- 35 | | 36 | | Define all of the model classes that act as a billing subscription. 37 | | 38 | */ 39 | 40 | 'subscription_models' => array('User'), 41 | 42 | /* 43 | |-------------------------------------------------------------------------- 44 | | Gateway Connections 45 | |-------------------------------------------------------------------------- 46 | | 47 | | Here you may configure the connection information for the gateway that 48 | | is used by your application. A default configuration has been added 49 | | for each gateway shipped with Billing. You are free to add more. 50 | | 51 | */ 52 | 53 | 'gateways' => array( 54 | 55 | 'stripe' => array( 56 | 'secret' => '', 57 | ), 58 | 59 | 'braintree' => array( 60 | 'environment' => '', 61 | 'merchant' => '', 62 | 'public' => '', 63 | 'private' => '', 64 | ), 65 | 66 | 'local' => array( 67 | 'database' => array( 68 | 'driver' => 'sqlite', 69 | 'database' => storage_path().'/meta/billing-local.sqlite', 70 | 'prefix' => '', 71 | ), 72 | 'api_delay_ms' => 200, 73 | ), 74 | 75 | ), 76 | 77 | ); 78 | -------------------------------------------------------------------------------- /src/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmanos/laravel-billing/aa7baf7862d8cf0ac3ea2f7b9c2a34ab5b8d5c06/src/controllers/.gitkeep -------------------------------------------------------------------------------- /src/lang/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmanos/laravel-billing/aa7baf7862d8cf0ac3ea2f7b9c2a34ab5b8d5c06/src/lang/.gitkeep -------------------------------------------------------------------------------- /src/migrations/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmanos/laravel-billing/aa7baf7862d8cf0ac3ea2f7b9c2a34ab5b8d5c06/src/migrations/.gitkeep -------------------------------------------------------------------------------- /src/views/invoice.blade.php: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 |

20 | @if (isset($product)) 21 | Product: {{ $product }}
22 | @endif 23 | Invoice ID: {{ $invoice->id }}
24 | Invoice Date: {{ date('M jS, Y', strtotime($invoice->date)) }}
25 |

26 | 27 | 28 | @if (isset($vat)) 29 |

{{ $vat }}

30 | @endif 31 | 32 |

33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | @foreach ($invoice->items() as $item) 44 | 45 | @if ($item->subscription_id) 46 | 61 | 68 | @else 69 | 70 | 71 | @endif 72 | 73 | @if ($item->amount >= 0) 74 | 75 | @else 76 | 77 | @endif 78 | 79 | @endforeach 80 | 81 | 82 | @if ($invoice->subtotal) 83 | 84 | 85 | 86 | 87 | 88 | @endif 89 | 90 | 91 | @if ($invoice->discounts) 92 | @foreach ($invoice->discounts as $discount) 93 | 94 | 103 | 104 | 113 | 114 | @endforeach 115 | @endif 116 | 117 | 118 | @if ($invoice->total && $invoice->discounts) 119 | 120 | 121 | 122 | 123 | 124 | @endif 125 | 126 | 127 | @if ($invoice->starting_balance) 128 | 129 | 130 | 131 | 138 | 139 | @endif 140 | 141 | 142 | 143 | 144 | 146 | 147 |
DescriptionDateAmount
47 | @if ($item->description) 48 | {{ $item->description }} 49 | @else 50 | Subscription 51 | 52 | @if ($item->subscription()) 53 | to {{ ucwords(str_replace(array('_', '-'), ' ', $item->subscription()->plan)) }} 54 | @endif 55 | 56 | @if ($item->quantity > 1) 57 | (x{{ $item->quantity }}) 58 | @endif 59 | @endif 60 | 62 | @if ($item->period_start && $item->period_end) 63 | {{ date('M jS, Y', strtotime($item->period_start)) }} 64 | - 65 | {{ date('M jS, Y', strtotime($item->period_end)) }} 66 | @endif 67 | {{ $item->description }} ${{ number_format($item->amount / 100, 2) }}-${{ number_format(abs($item->amount) / 100, 2) }}
 Subtotal:${{ number_format($invoice->subtotal / 100, 2) }}
95 | {{ array_get($discount, 'coupon') }} 96 | 97 | @if (array_get($discount, 'amount_off')) 98 | (${{ array_get($discount, 'amount_off') / 100 }} Off) 99 | @else 100 | ({{ array_get($discount, 'percent_off') }}% Off) 101 | @endif 102 |   105 | 106 | @if (array_get($discount, 'amount_off')) 107 | -${{ number_format(abs(array_get($discount, 'amount_off') / 100), 2) }} 108 | @else 109 | -${{ number_format($invoice->subtotal * (array_get($discount, 'percent_off') / 100) / 100, 2) }} 110 | @endif 111 | 112 |
 Total:${{ number_format($invoice->total / 100, 2) }}
 Starting Customer Balance: 132 | @if ($invoice->starting_balance >= 0) 133 | ${{ number_format($invoice->starting_balance / 100, 2) }} 134 | @else 135 | -${{ number_format(abs($invoice->starting_balance) / 100, 2) }} 136 | @endif 137 |
 Amount Paid: 145 | ${{ number_format($invoice->amount / 100, 2) }}
148 | -------------------------------------------------------------------------------- /tests/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmanos/laravel-billing/aa7baf7862d8cf0ac3ea2f7b9c2a34ab5b8d5c06/tests/.gitkeep --------------------------------------------------------------------------------