rennokki / chargeswarm
Chargeswarm is a Laravel package that helps you handling subscriptions using the Chargebee service.
Requires
- chargebee/chargebee-php: ^2.5.2
- laravel/framework: ~5.5.0|~5.6.0|~5.7.0
Requires (Dev)
- orchestra/database: ~3.5.0|~3.6.0
- orchestra/testbench: ~3.5.0|~3.6.0
- phpunit/phpunit: ^6.2|^7.0
README
The package is not maintained anymore
Try to fork your own version of the package or use Laravel Spark for a complete experience on plans.
Laravel Chargeswarm
Laravel Chargeswarm is a Laravel Cashier-alike package that will help you befriend the bees and have a great SaaS system for your app. This package provides methods to create, update, cancel or resume subscriptions and also to handle the webhooks in style!
Also, Chargeswarm provides support to handle countable resources. For this, it's recommended to use Chargebee's Metadata, with or without webhooks.
Advantages of Chargebee
Chargebee is not a payment provider. In fact, Chargebee is a manager for SaaS, while you can use any kind of payment gateway. The same as Stripe, you can fully use features like Chargebee's metadata to carry out information for your plans.
This package also supports tracking consumption for countable features. Stay tuned until the end of the documentation to know what's all about.
Upgrading from 1.2.* to 1.3.*
The 1.3 version uses config file's array to retrieve the env variables, instead of a "raw" pass.
In your chargeswarm.php
file, add the following lines:
'site' => env('CHARGEBEE_SITE', ''), 'key' => env('CHARGEBEE_KEY', ''), 'gateway' => env('CHARGEBEE_GATEWAY', ''),
Installation
Install the package:
$ composer require rennokki/chargeswarm
If your Laravel version does not support package discovery, add this line in the providers
array in your config/app.php
file:
Rennokki\Chargeswarm\ChargeswarmServiceProvider::class,
Publish the config file & migration files:
$ php artisan vendor:publish
Migrate the database:
$ php artisan migrate
Add the Billable
trait to your Eloquent model:
use Rennokki\Chargeswarm\Traits\Billable; class User extends Model { use Billable; ... }
Do not forget to add your site & your API key, as well as the gateway option in your .env
file:
CHARGEBEE_SITE=site-test
CHARGEBEE_KEY=test_...
CHARGEBEE_GATEWAY=stripe
Usage
If you are familiar with Cashier's source code, this is kinda' close as structure. To subscribe your users, we'll use a subscription builder. In any other cases, we'll be using methods called from each subscription.
Any of the fields are optional, with the exception of the plan_id
parameter and create
method.
$subscription = $user->subscription('plan_id') ->withCoupon('coupon') ->withAddons(['addon1', 'addon2']) ->billingCycles(12) ->withQuantity(3) ->startsOn(...) // date or Carbon ->onTrial() // overwrites the trial ->trialEndsOn(...) // date or Carbon ->withInvoiceNotes(...) ->create('stripe_or_braintree_token'); $user->subscribed('plan_id'); // true $user->activeSubscriptions()->count(); // 1
Also, if you plan to add some more data to your customer, use the withCustomerData()
method. All fields are optional and can be set to null
:
$user->subscription('plan_id') ->withCustomerData('email@google.com', 'John', 'Smith', 'Company Name') ->... ->create('token');
If you also plan on adding billing details, this one's a bit much longer. If you don't want to use certain fields, set them to null
.
$user->subscription('plan_id') ->withCustomerData('email@google.com', 'John', 'Smith') ->withBilling( 'email@google.com', 'John', 'Smith', 'Street...', 'City', 'State', 'Zip code', 'Country', 'Company name' ) ->... ->create('token');
Swap to another plan
You can simply swap a subscription's plan using the swap()
method called within the subscription. If the subscription is not active, it will return false.
$subscription = $user->activeSubscriptions()->first(); $subscription = $subscription->swap('new_plan_id'); // updated subscription
The plan swapping is done right now. If you wish to swap the plan after the current term ends, you can set true
as a second parameter.
$subscription = $subscription->swap('new_plan_id', true);
Change plan quantity & billing cycles
There are two methods that allows you to change plan quantity and the billing cycles.
$subscription->changeQuantity(12); $subscription->changeBillingCycles(12);
Again, if you plan to do the change after the current term ends, pass true
as second parameter.
Change trial end & ending term
If you wish to change the trial date or the ending term, you can do it, but only if the plan is not cancelled. The parameter accepted can either be a valid date string or a Carbon instance.
$subscription->changeTrialEnd(Carbon::create(...)); $subscription->changeTermEnd(Carbon::create(...));
Cancelling & Resuming subscriptions
You can set your plans to be on trial. If you plan to cancel a subscription, you can do so using the cancel()
method. However, if the subscription is not expired (the expiration date did not pass), it will still be available through the trial, but it would be marked as cancelled.
$subscripton->cancel(); $subscription->cancelled(); // true
It can later be resumed, if the user decides to go on with the subscription:
$subscription->resume(); $subscription->active(); // true $subscription->onTrial(); // true
Cancelling it immediately would cancel the subscription without being able to be resumed again:
$subscription->cancelImmediately(); $subscription->active(); // false $subscription->onTrial(); // false
However, the cancelled subscription can be reactivated
instead of resumed
:
$subscription->reactivate(); $subscription->active(); // true
Invoices
Invoices are a legal way to track expenses made by the user. Chargeswarm allows you to get invoices from a subscription.
$subscription->invoices(); // array with Chargebee_Invoice elements
Since invoices are paginated, you can specify a $limit
and a $nextOffset
.
$subscription->invoices($limit, $nextOffset);
You can also get the invoices by calling the method from your billable model, but you also require the subscription id.
$user->invoices($subscriptionId, $limit, $nextOffset);
Parsing invoices can be done in one way and thus you can also get the $nextOffset
needed:
$invoices = $user->invoices($subscriptionId, $limit, $nextOffset); foreach ($invoices as $invoice) { $invoice = $invoice->invoice(); ... } $nextPageOfInvoices = $user->invoices($subscriptionId, $limit, $invoices->nextOffset());
You can also gather invoices directly form a subscription, with the same method for offsetting:
$invoices = $subscription->invoices($limit, $nextOffset); foreach ($invoices as $invoice) { $invoice = $invoice->invoice(); ... } $nextPageOfInvoices = $subscription->invoices($limit, $invoices->nextOffset());
Be careful, sometimes the offset doesn't exist. Make sure you validate it before.
Plan
To retrieve the plan the current subscription has, you can call plan()
. This allows you to handle metadata from a specific plan, which your subscription belongs to.
$plan = $subscription->plan(); $metadata = $plan->metaData; // json object
Webhooks
Anytime something happens, Chargebee will send a POST
request to a configured webhook. Fortunately, Chargeswarm can do this for you and has a ton of support when it comes to webhooks.
To handle all Chargebee's webhooks automatically, all you have to do is to declare a route like this in your routes/web.php
or routes/api.php
file with the following controller:
Route::post('/webhooks/chargebee', '\Rennokki\Chargeswarm\Http\Controllers\ChargebeeWebhookController@handleWebhook');
Also, in case you have CSRF protection on, make sure you disable it in your VerifyCsrfToken.php
file:
protected $except = [ 'webhooks/chargebee', ];
Pre-defined webhooks
There are more than 20 pre-defined events & webhooks, but you can extend it using any of the Chargebee's events due to friendly syntax that will be explained later. Each time a webhook fires, no matter the event, you will receive a \Rennokki\Chargeswarm\Events\WebhookReceived
event that carries out as variable an $event->payload
JSON object.
Additionally, for these pre-defined webhooks, you will also receive a specific event. You can find a list of pre-defined webhooks and their paired events here.
Unfortunately, for any other class method you declare, other than those defined earlier, you will not receive events. The only event that triggers is the \Rennokki\Chargeswarm\Events\WebhookReceived
event, which triggers automatically on each webhook received.
By default, the following controller methods automatically do the logic for your plans. I recommend NOT overwriting these unless you know what you do:
handleSubscriptionCancelled
handlePaymentSucceeded
handlePaymentRefunded
handleSubscriptionDeleted
handleSubscriptionRenewed
For these four, instead, i recommend listening their paired events to handle your own logic. In case you want to implement any other handler, you are free to do it by extending the controller, but remember that events associated with the hooks are also triggered.
Extending the Controller
Customizing webhooks can be done simply by extending your controller from Rennokki\Chargeswarm\Http\Controllers\ChargebeeWebhookController
:
use Rennokki\Chargeswarm\Http\Controllers\ChargebeeWebhookController; class MyController extends ChargebeeWebhookController { public function handleSubscriptionResumed($payload, $storedSubscription, $subscription, $plan) { // $payload is the JSON Object with the request // $storedSubscription is the stored subscription (if any) // $subscription is the subscription data (equivalent of $payload->content->subscription), if any // $plan is the plan object of the subscription } }
After extending it, make sure you are using your controller with the same @handleWebhook
method:
Route::post('/webhooks/chargebee', 'MyController@handleWebhook');
Customizing webhooks
You can customize any kind of method in your controller that follows the following rule:
MyController@handle{EventNameInStudlyCase}($payload, $storedSubscription, $subscription)
For example, since card_added
Chargebee event is not pre-defined nor added, you can simply add this method in your controller:
public function handleCardAdded($payload, $storedSubscription, $subscription, $plan) { // your logic here // only $payload is not null. // The rest of the variables injected can be null or not, if the // subscription object exists }
All controller methods and events accept 4 parameters: $payload
, $storedSubscription
, $subscription
and $plan
.
Events
As stated earlier, the \Rennokki\Chargeswarm\Events\WebhookReceived
event fires automatically. In addition to that, each of the listed method here automatically fires the paired event.
If you are not familiar with events, check Laravel's Official Documentation on Events that teaches you what are events, how to handle them and, more important, how to listen to them.
All events send 4 parameters to their listeners: $payload
, $storedSubscription
, $subscription
and $plan
. They can be accessed in your listener using the event instance.
For example, each time the @handleSubscriptionResumed
is called, we can listen to the \Rennokki\Chargeswarm\Events\SubscriptionResumed
event and implement our logic:
class MyListener { public function handle(SubscriptionResumed $event) { // $event->payload // $event->storedSubscription // $event->subscription // $event->plan } }
Countable features
Let's say you run your own newsletters app that bills users using SaaS and you give your users 5.000
newsletters, monthly, that they can send.
On subscribing or after the subscription renews (which can be done by listening to the \Rennokki\Chargeswarm\Events\SubscriptionRenewed
event), you can simply call createUsage()
in your logic, for example:
public function handle() { $subscription = $event->storedSubscription; $subscription->createUsage('monthly.emails', 5000); ... }
Later, you can consume
or unconsume
them all around your app by calling the methods within the subscription:
$subscription->consume('monthly.emails', 10); // sent 10 mails
Reversing the effect can be useful in cases with errors, for example. When the mailserver fails, but your app doesn't. To reverse it, you can unconsume it:
$subscription->unconsume('monthly.emails', 10); // undo-ed 10 from the quota
Consuming or unconsuming inexistent, unset, usages will give you a false
. Also, if the amount consumed is higher than the one remaining, you will also get a false
.
$subscription->consume('daily.emails', 10); // false
Unconsuming does not falls below zero. If you unconsume more than you have used, the overflow won't hit and the used
attribute will be set to 0.
If you plan receiving all the usages, you can do so by calling the usages()
relationship within the subscription:
$usages = $subscription->usages()->get();