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
Automated Installation via Igniter (Recommended)
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"}
]
endThen 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
endThe 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
endAshBilling.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
endAll 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.url4.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.url5. 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
end6. 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)