Getting Started with AshBilling

Copy Markdown View Source

AshBilling brings declarative billing, subscription lifecycles, and payment providers to the Ash Framework.

Inspired by Laravel Cashier and Rails Pay, it provides unified DSL extensions, native Ash authorization policies, and an automated synchronization engine for payment providers like Stripe.


1. Installation

If you are using Igniter, install ash_billing with a single command:

mix igniter.install ash_billing

Igniter automatically configures your customer and subscription resources, adds the required router scope, and configures runtime environment variables.

Manual Installation

Add ash_billing to your mix.exs:

def deps do
  [
    {:ash_billing, "~> 0.1.1"}
  ]
end

Then fetch your dependencies:

mix deps.get

Configure your credentials in config/runtime.exs:

config :ash_billing,
  stripe_api_key: System.get_env("STRIPE_SECRET_KEY")

2. Resource Setup

2.1 The Customer Resource (e.g. User or Organization)

Add AshBilling.Resource to your user or account resource:

defmodule MyApp.Accounts.User do
  use Ash.Resource,
    domain: MyApp.Accounts,
    data_layer: AshPostgres.DataLayer,
    extensions: [AshBilling.Resource]

  attributes do
    uuid_primary_key :id
    attribute :email, :string, public?: true
    attribute :name, :string, public?: true
  end

  billing do
    # Defaults to :stripe_customer_id
    customer_id_attribute :stripe_customer_id
    provider AshBilling.Providers.Stripe
  end
end

The extension injects :stripe_customer_id into your resource attributes automatically.

2.2 The Subscription Resource

Define a dedicated subscription resource with AshBilling.Subscription:

defmodule MyApp.Billing.Subscription do
  use Ash.Resource,
    domain: MyApp.Billing,
    data_layer: AshPostgres.DataLayer,
    extensions: [AshBilling.Subscription]

  attributes do
    uuid_primary_key :id
  end

  billing_subscription do
    customer_relationship :user
    provider AshBilling.Providers.Stripe
  end
end

AshBilling.Subscription automatically manages:

  • Attributes: :provider_subscription_id, :status, :price_id, :current_period_end, :trial_ends_at, :cancel_at_period_end.
  • Calculations: :active? (boolean, true if status is active, trialing, or past_due with remaining period) and :on_trial?.

3. Webhook Integration

Mount AshBilling.Plug in your Phoenix router:

# lib/my_app_web/router.ex
scope "/webhooks" do
  forward "/stripe", AshBilling.Plug,
    provider: AshBilling.Providers.Stripe,
    webhook_secret: {System, :get_env, ["STRIPE_WEBHOOK_SECRET"]},
    customer_resource: MyApp.Accounts.User,
    subscription_resource: MyApp.Billing.Subscription
end

All incoming webhooks will have their HMAC-SHA256 signature verified and their customer and subscription records updated idempotently in real time.


4. Checkout Sessions and Billing Portal

4.1 Initiating Checkout

Redirect users to Stripe Checkout to collect payment details:

{:ok, session} =
  AshBilling.create_checkout_session(user,
    mode: :subscription,
    line_items: [
      %{price: "price_pro_monthly", quantity: 1}
    ],
    success_url: "https://myapp.com/billing?status=success",
    cancel_url: "https://myapp.com/billing?status=cancelled"
  )

# Redirect the user to session.url

4.2 Customer Billing Portal

Generate a link to the self-service Stripe Customer Portal where users can update payment cards or view invoices:

{:ok, portal} =
  AshBilling.create_portal_session(user,
    return_url: "https://myapp.com/settings"
  )

# Redirect the user to portal.url

5. Authorizing with Native Ash Policies

Use built-in Ash policy checks to restrict access to paying subscribers:

defmodule MyApp.Content.Document do
  use Ash.Resource,
    domain: MyApp.Content,
    authorizers: [Ash.Policy.Authorizer]

  policies do
    # Only active subscribers or trialing users can read premium documents
    policy action_type(:read) do
      authorize_if AshBilling.Checks.Subscribed
    end

    # Only users subscribed to the Enterprise price tier can export
    policy action(:export) do
      authorize_if {AshBilling.Checks.HasPrice, price: "price_enterprise_annual"}
    end
  end
end

6. Pre-flight Diagnostics

Before booting your application, you can verify that all API credentials, webhook secrets, and registered resources are properly configured:

# Raises an informative error if configuration is incomplete
AshBilling.verify_configuration!(MyApp.Accounts.User)