-
-
Notifications
You must be signed in to change notification settings - Fork 116
Creating a Billable App
First of all, its best to read up on Shopify's documentation to get to know how it works internally.
To enable billing on your app, there is configuration already in config/shopify-app.php
which is based on environment variables. Using these variables, here is what you need to setup.
- Options
- Creating Plans
- Flow
- Grandfathered Mode
- Freemium Mode
- Upgrading or Downgrading Plans
- Usage Charges
- Billable Middleware
- Declined charge handling
If you have SHOPIFY_FRONTEND_ENGINE="REACT"
enabled, then you cannot use the "Billable" middleware.
This is because "SPA mode" overrides the authentication process to allow you to manage the JWT token yourself. The caveat is that the "user" is not logged into the Laravel auth manager.
The solution is to manage the billing yourself within the SPA app.
You can still use the billing routes as usual to push your customers from your SPA app to the billing accept screens etc using /billing/{plan_id]
etc, but remember to pass the shop
and host
param so that you can be returned correctly.
Note: Below are examples, all based on env variables. You're free to use the standard config/shopify-app.php
file as well
Here is an example setup to get a basic app into billable mode:
SHOPIFY_BILLING_ENABLED=1
Here is an explanation of the configuration, many are already set with sane-defaults:
SHOPIFY_BILLING_ENABLED=0
to disable billing state for application (default)
SHOPIFY_BILLING_ENABLED=1
to enable billing state for application
SHOPIFY_BILLING_FREEMIUM_ENABLED=0
to disallow freemium use (default)
SHOPIFY_BILLING_FREEMIUM_ENABLED=1
to enable freemium use (skip billing section for a plan on install)
SHOPIFY_BILLING_REDIRECT="/url/path"
for setting the path to handle the accepting or declining of the application charge (default: /billing/process)
Note: It is not recommended to change this unless you're using custom logic or overriding the billing controller.
All plans are stored in the plans
table.
-
type
; eitherRECURRING
for recurring,ONETIME
for single (required) -
name
; the name of the plan (required) -
price
; the price of the plan (required) -
interval
; eitherEVERY_30_DAYS
for pay per month,ANNUAL
for pay per year -
capped_amount
; the maximum the plan will charge the shop for usage charges (required only for recurring type plans and if you plan to issue usage charges) -
terms
; the terms to display for the usage charge/capped_amount (required only for recurring type plans and if you plan to issue usage charges) -
trial_days
; number of trial days for your plan -
test
;boolean
value denoting if the plan is in test mode (good for development) -
on_install
;boolean
value denoting if the plan will be presented on install (only one plan can haveon_install
astrue
)
# Create a recurring "Demo" plan for $5.00, with 7 trial days, which will be presented on install to the shop and have the ability to issue usage charges to a maximum of $10.00
INSERT INTO plans (`type`,`name`,`price`,`interval`,`capped_amount`,`terms`,`trial_days`,`test`,`on_install`,`created_at`,`updated_at`) VALUES
('RECURRING','Test Plan',5.00,'EVERY_30_DAYS',10.00,'Test terms',7,FALSE,1,NULL,NULL);
Its important to flag one plan's on_install
to true, so a shop can be charged billing during installation.
If you plan on creating a "free" recurring plan, and only utilizing usage charges, then you must set the plan's price
to 0.00
, set the capped_amount
and the terms
for Shopify to accept the 0.00
charge through the API.
Once you've set the required environment variables, you're ready to go.
Here's how the flow works:
- Shop installs the app
- Shop accepts the app's permissions
- Shop is presented with the billing screen to accept or decline
- 3a. If they accept, charge is activated, freemium is disabled, charge ID (plan_id on users table) is saved to database, shop is sent to your app's homepage
- 3b. If they decline, charge is marked declined, shop is sent to error screen saying they did not pay.
Note: If billing is being applied on top of an existing app, when the shop accesses your app, they will be sent to the billing screen.
Grandfathered mode is useful when you wish to give a shop free access to your app. A simple boolean flag in the database (grandfathered
) tells the billable middleware to let them through.
You can check a shop's grandfathered status via: $shop->isGrandfathered()
.
This mode, if enabled through the config, will allow shops to by-pass billing and use the app in freemium
mode, and you can present a plan to them later.
You can check a shop's freemium status via: $shop->isFreemium()
.
Shopify internally handles cancelling the previous plan. You simply need to direct the shop to the billing screen to accept the new charge.
<a href="{{ URL::tokenRoute('new-page', ['plan' => 1, 'shop' => Auth::user()->name ]) }}">Move to plan 1</a>
The easiest way is to use the tokenRoute method, as this will try and pass host along for you. If you use a normal route link, then you need to make sure you also pass host
along with shop
<p><a href="{{ route('billing', ['plan' => 2, 'shop' => Auth::user()->name, 'host' => app('request')->input('host') ]) }}">Upgrade</a></p>
The above will direct the shop to the billing screen and present them with plan ID #2 from the plans
table. You can also simply issue a redirect
in a controller.
If a shop is on a recurring plan, with usage charge abilities, you can direct the shop (either through POST/GET) to the usage charge route. The package will take care of issuing the charge, storing it in the database, and redirection.
Example GET
:
# App/Http/Controllers/Example
# ...
public function index()
{
// Description and price of usage charge (the only two parameters required)
$charge = [
'description' => 'Five e-mails',
'price' => 1.00,
'redirect' => route('example.success') // Optional, if not supplied redirect goes back to previous page with flash `success`=`true`
];
// Create a signature to prevent tampering
$signature = Util::createHmac(['data' => $charge, 'buildQuery' => true], Config::get('shopify-app.api_secret'));
// Create the route
$usageChargeRoute = route('billing.usage_charge', array_merge($charge, ['signature' => $signature->toNative()]));
return view('example.index', compact('usageChargeRoute'));
}
In the view:
<a href="{{ $usageChargeRoute }}">Add more emails!</a>
The middleware will check the following:
- If billing is enabled
- If the shop is not grandfthered
- If the shop is not set to freemium
- If the shop is not on a billing plan
If all checks pass, the request will be redirected to the billing flow
Currently its used only on the app's home route. In most cases, this is all you will need because every time a shop clicks your app from their Shopify admin, it will go through to the home route by default which includes the shop authorization middleware, and the billable middleware.
If you'd like to use it on more routes, here's an example usage:
Route::get(
'/settings',
'App\Controllers\SettingsController@index'
)
->middleware(['auth.shopify', 'billable']) // <---
->name('settings');
With the latest admin UI changes in Shopify, cancelled charges now open up the slide up a admin settings. When you close this if you are on anything below v17.2.0
you will get an error. This is because shopify redirect back to the billing/process
route without a chargeId.
v17.2.0
will now handle this and push them back the home route of the app.