Billing module for PhoenixKit. Drop-in payments, subscriptions, invoices, orders, and multi-currency support with Stripe, PayPal, and Razorpay integration.
- Orders & invoices — full order-to-invoice-to-receipt workflow with status tracking and print views
- Multi-provider payments — Stripe, PayPal, and Razorpay via a unified Provider behaviour with hosted checkout
- Internal subscription control — subscriptions managed in your database, not by providers; automatic renewals and dunning
- Multi-currency — currency definitions with exchange rates
- Billing profiles — individual and company billing details with address, tax ID, and IBAN validation
- Transactions & refunds — complete payment ledger with credit notes and partial refunds
- Real-time updates — PubSub events for orders, invoices, transactions, subscriptions, and profiles
- Admin dashboard — LiveViews for managing all billing entities, settings, and provider configuration
- User dashboard — "My Orders" and "Billing Profiles" pages for end users
- Print views — invoice, receipt, credit note, and payment confirmation print layouts
- Auto-discovery — implements
PhoenixKit.Modulebehaviour; PhoenixKit finds it at startup with zero config
Add phoenix_kit_billing to your dependencies in mix.exs:
def deps do
[
{:phoenix_kit_billing, "~> 0.1.0"}
]
endThen fetch dependencies:
mix deps.getNote: For development or if not yet published to Hex, you can use:
{:phoenix_kit_billing, github: "BeamLabEU/phoenix_kit_billing"}
PhoenixKit auto-discovers the module at startup — no additional configuration needed.
- Add the dependency to
mix.exs - Run
mix deps.get - Enable the module in admin settings (
billing_enabled: true) - Configure at least one payment provider in admin settings
- Orders and invoices are available at
/admin/billing
alias PhoenixKit.Modules.Billing
# Create an order
{:ok, order} = Billing.create_order(user, %{
line_items: [%{description: "Widget", quantity: 1, unit_price: Decimal.new("29.99")}],
currency: "EUR"
})
# Confirm the order
{:ok, order} = Billing.confirm_order(order)
# Generate an invoice from the order
{:ok, invoice} = Billing.create_invoice_from_order(order)
# Send the invoice to the customer
{:ok, invoice} = Billing.send_invoice(invoice)
# Mark as paid (generates receipt automatically)
{:ok, invoice} = Billing.mark_invoice_paid(invoice)# Create a subscription type (plan)
{:ok, type} = Billing.create_subscription_type(%{
name: "Pro Monthly",
interval: "month",
price: Decimal.new("19.99"),
currency: "EUR"
})
# Create a subscription for a user
{:ok, subscription} = Billing.create_subscription(user, type)Subscription renewals and failed-payment retries (dunning) are handled automatically by Oban workers.
Providers are configured in admin settings. The system uses hosted checkout — users are redirected to the provider's payment page:
# Create a checkout session for an invoice
{:ok, session} = Billing.create_checkout_session(invoice, :stripe, %{
success_url: "https://example.com/success",
cancel_url: "https://example.com/cancel"
})
# Redirect user to session.urlWebhooks are handled automatically at /webhooks/billing/:provider.
Subscribe to billing events in your LiveViews:
def mount(_params, _session, socket) do
PhoenixKit.Modules.Billing.Events.subscribe_orders()
{:ok, socket}
end
def handle_info({:order_created, order}, socket) do
# Update UI
{:noreply, socket}
end| Key | Type | Default | Description |
|---|---|---|---|
billing_enabled |
boolean | false |
Enable/disable the billing system |
billing_default_currency |
string | "EUR" |
Default currency for new orders |
billing_tax_enabled |
boolean | false |
Enable tax calculations |
billing_company_name |
string | — | Company name on invoices |
billing_company_address |
string | — | Company address on invoices |
Provider-specific settings (API keys, webhook secrets) are configured per-provider in the admin panel at /admin/settings/billing/providers.
| Status | Description |
|---|---|
"draft" |
Invoice created, not yet sent |
"sent" |
Invoice sent to customer |
"paid" |
Payment received, receipt generated |
"overdue" |
Past due date, not yet paid |
"void" |
Cancelled / voided |
draft → sent → paid
↘
overdue → paid
↘
void
The module declares permissions via permission_metadata/0:
"billing"— access to billing admin dashboard and all sub-pages- Settings pages require the same
"billing"permission
Use Scope.has_module_access?/2 to check permissions in your application.
This module implements css_sources/0 returning [:phoenix_kit_billing], so PhoenixKit's installer automatically adds the correct @source directive to your app.css for Tailwind scanning. No manual configuration needed.
lib/
├── mix/tasks/phoenix_kit_billing.install.ex # Install mix task
└── phoenix_kit/modules/billing/
├── billing.ex # Main module (context + PhoenixKit.Module behaviour)
├── events.ex # PubSub event broadcasts
├── paths.ex # Centralized URL path helpers
├── supervisor.ex # OTP Supervisor
├── providers/
│ ├── provider.ex # Provider behaviour (9 callbacks)
│ ├── providers.ex # Provider registry and routing
│ ├── stripe.ex # Stripe implementation
│ ├── paypal.ex # PayPal implementation
│ └── razorpay.ex # Razorpay implementation
├── schemas/
│ ├── billing_profile.ex # User billing information
│ ├── currency.ex # Currency definitions
│ ├── invoice.ex # Invoice with receipt
│ ├── order.ex # Order with line items
│ ├── payment_method.ex # Saved payment methods
│ ├── subscription.ex # Subscription records
│ ├── subscription_type.ex # Subscription plan definitions
│ ├── transaction.ex # Payment/refund transactions
│ └── webhook_event.ex # Provider webhook log
├── workers/
│ ├── subscription_renewal_worker.ex # Oban: subscription renewals
│ └── subscription_dunning_worker.ex # Oban: failed payment retries
└── web/ # Admin LiveViews, controllers, components
| Table | Description |
|---|---|
phoenix_kit_billing_profiles |
User billing information (UUIDv7 PK) |
phoenix_kit_currencies |
Currency definitions |
phoenix_kit_orders |
Orders with line items |
phoenix_kit_invoices |
Invoices with receipt data |
phoenix_kit_transactions |
Payment/refund transaction ledger |
phoenix_kit_subscriptions |
Active subscriptions |
phoenix_kit_subscription_types |
Subscription plan definitions |
phoenix_kit_payment_methods |
Saved payment methods from providers |
phoenix_kit_payment_options |
Available payment options |
phoenix_kit_webhook_events |
Provider webhook event log |
mix deps.get # Install dependencies
mix test # Run tests
mix format # Format code
mix credo --strict # Static analysis (strict mode)
mix dialyzer # Type checking
mix docs # Generate documentation
mix precommit # Compile + format + credo + dialyzer
mix quality # Format + credo + dialyzer- Verify
billing_enabledistruein settings - Ensure the module is listed as a dependency in the parent app's
mix.exs - Check that
enabled?/0is not returningfalse(requires database access)
- Verify webhook secrets are configured in provider settings
- Check that webhook URLs are registered with the provider (e.g.,
https://yourdomain.com/webhooks/billing/stripe) - Review
phoenix_kit_webhook_eventstable for received events
- Ensure Oban is configured in the parent app with the
billingqueue - Check Oban dashboard for failed jobs
MIT — see LICENSE for details.