Gold

Gold is Helium’s approach to predictably and accurately billing merchants for Shopify apps.

For more information about the goals and design of this project, please review Gold's design document.

Tour

Gold is comprised of a few pieces:

  • Gold::BillingController allows merchants to select tiers, set up charges, and review their billing information.
  • Gold::Billing ties together all pieces of a shop's billing information.
  • Gold::Machine defines the state that a shop is in and what transitions can be made from that state to others.
  • Gold::Transition records these state transitions in the database.
  • Gold::Tier holds definitions of tiers that are available for merchants to select from. Apps using Gold define these in their config/tiers.yml file.
  • Gold::Concerns::Gilded is a mixin to an app's shop class to give it Gold's functionality. This app-specific shop class is configured with the Gold module's #shop_class= setter.

Installation

Add this line to your application's Gemfile:

gem "shopify-gold", require: "gold"

And then execute:

$ bundle

You will need to apply migrations to your app's Shop class and bring in the other Gold models to your database:

$ rake gold_engine:install:migrations
$ rake db:migrate

Mount the engine in your config/routes.rb file:

mount Gold::Engine, at: "/billing"

Note that by default, the engine's name is gold_engine. If you must change this, you'll need to define a new helper method gold_engine and reference your custom as param.

Add module to your Shop class:

include Gold::Concerns::Gilded

Add the following methods to your Shop class: #shopify_domain, which responds with the *.myshopify.com domain of the shop #shopify_plan_name, which responds to the plan name of the Shopify store (e.g. "unlimited", "affilaite") #qualifies_for_tier?(_tier), return a boolean if the shop should qualify to a tier #with_shopify_session, provided by the Shopify App gem

After shop properties have been updated (say, after a shop/update webhook), call: shop.billing.after_shop_update!. This tells Gold something has changed about the shop, which will run any operations (example affiliate -> paid) that might be relevant.

Call the AfterAuthenticateJob inline when a user logs in. In shopify_app.rb initializer:

config.after_authenticate_job = { job: Gold::AfterAuthenticateJob, inline: true }

Or in your own job:

Gold::AfterAuthenticateJob.perform_now(shop_domain: shop_domain)

Background tasks

Gold runs background jobs to queue events under the gold queue. For example, if you're using Sidekiq, run:

$ bundle exec sidekiq -q gold

Configuration

Add gold.rb within the initializers folder of your project. Below are Gold's configuration options:

Gold.configure do |config|
  # The class name (string) of the the app's Active Record Shop model
  config.shop_class = "Shop"

  # The attribute of the *.myshopify.com domain on the Shop model
  config.shop_domain_attribute = "shopify_domain"

  # Should Gold create test charges? Helpful in a development environment
  config.test_charge = false

  # The logger Gold will use
  config.logger = Logger.new(STDOUT)

  # How many days we'll wait before cleaning up an uninstalled shop
  config.days_until_cleanup = 30

  # How many days we'll wait before we uninstall a shop without payment
  config.days_until_delinquent = 7

  # The name of the app using gold (used in the charge name)
  config.app_name = "My App"

  # The email Gold uses to send emails from
  config.contact_email = "[email protected]"

  # The URL to a plan comparison page for the app
  config.plan_comparison_url = "https://heliumdev.com/pricing"

  # The API version used by Shopify (https://help.shopify.com/en/api/versioning)
  config.shopify_api_version = "2019-04"

  # If Gold is allowed to cancel charges (paid -> free) automatically
  config.allow_automated_charge_cancellation = true

  config.admin_credentials = {
    user: "admin",
    password: "password123"
  }

end

Hooks

You can hook into Gold's lifecycle by providing a Proc. Available hooks:

Gold.configure do |config|
  config.on_install = proc {
    puts "Installing Gold..."
  }

  config.on_terms = proc {
    puts "Accepted terms from Gold..."
  }

  config.on_apply_tier = proc { |billing, tier_id|
    puts "Applied tier..."
  }

  config.on_activate_charge = proc { |billing, charge|
    puts "Activated charge..."
  }

  config.on_uninstall = proc {
    puts "Uninstalling Gold..."
  }

  config.on_cleanup = proc { |billing|
    puts "Cleaning up shop"
  }

  config.on_check_charge = proc { |billing, active_charge|
    puts "Check charge for shop"
  }
end

Adding tiers

To add tiers to your app, add a config/tiers.yml file. Here's an example file:

- id: basic
  name: "Basic"
  description: "Shops just gettings started with a few number of custom fields."
  features:
    feature_1: Yes
    featured_2: No
  pricing:
    trial_days: 14
    monthly: "10.00"
  qualifications:
    max_customers: 1000

Controller and views

Ok, so it's time to get paid. Let's add our module to show merchants tier choices.

Include the following in your ApplicationController:

include Gold::Concerns::MerchantFacing

When you want to put an action or controller behind a paywall, add:

before_action :confront_mandatory_billing_action

You can override any view file by matching Gold's path pattern under views. The most notable are likely

Admin interface

Gold can manage billing for each shop through an admin interface. First, you'll want to setup some basic HTTP auth credentials in your Gold config initializer.

Gold.configure do |config|
  config.admin_credentials = {
    user: "admin",
    password: "password123"
  }
end

Mount the engine in routes: mount Gold::AdminEngine, at: '/admin', as: 'gold_admin_engine'

You can inject a partial anywhere in your own admin interface to include Gold's admin management by providing an instance of Billing: render "gold/admin/billing/overview", billing: @shop.billing

This partial expects the following instance variables to be defined from your controller:

@active_charge = ShopifyAPI::RecurringApplicationCharge.current
@credits = ShopifyAPI::ApplicationCredit.all || []

Migration

If you have existing charges that you want to migrate to Gold on a specific plan, use the BillingMigrator.new([Shop], [tier_id], [start_at]) class. Example: Gold::BillingMigrator.new(@shop, "basic", 5.days.ago)

The shop will automatically be pushed through the state machine (new, accepted_terms, etc). Note that the tier_id must match a tier setup in Gold. If that shop does not have a charge that matches the tier's price, a new charge will be requested.

Contributing

Run tests with:

$ bin/rails test

Run lint/style checks with:

$ bundle exec rubocop

If you want to see a visual representation of Gold::Machine, you can run bin/rails app:gold:diagram. You will need to have the Graphviz tool installed, which you can do on Mac via Homebrew with brew install graphviz

Release

To release gem, run gem release, which depends on having gem-release installed on your system.