Skip to content

Creating a Billable App

Farid Movsumov edited this page Dec 25, 2023 · 7 revisions

Introduction

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.

SPA Mode (Important please read)

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/{plain_id] etc, but remember to pass the shop and host param so that you can be returned correctly.

Options / Setup

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:

billing_enabled (required)

SHOPIFY_BILLING_ENABLED=0 to disable billing state for application (default)

SHOPIFY_BILLING_ENABLED=1 to enable billing state for application

billing_freemium_enabled

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)

billing_redirect (required)

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.

Creating Plans

All plans are stored in the plans table.

Values

  • type; either RECURRING for recurring, ONETIME for single (required)
  • name; the name of the plan (required)
  • price; the price of the plan (required)
  • interval; either EVERY_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 have on_install as true)

Example Setup

# 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);

On-Install

Its important to flag one plan's on_install to true, so a shop can be charged billing during installation.

Notes

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.

Flow

Once you've set the required environment variables, you're ready to go.

Here's how the flow works:

  1. Shop installs the app
  2. Shop accepts the app's permissions
  3. Shop is presented with the billing screen to accept or decline
    • 3a. If they accept, charge is activated, freemium is disabled, charge ID 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

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().

Freemium Mode

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().

Upgrading or Downgrading Plans (recurring/one-time)

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.

Creating Usage Charges

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>

Billable Middleware

The middleware will check the following:

  1. If billing is enabled
  2. If the shop is not grandfthered
  3. If the shop is not set to freemium
  4. If the shop is not on a billing plan

If all checks pass, the request will be redirected to the billing flow

Usage

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');

Declined Charges

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.