PhoenixKitBilling

ElixirLicense: MIT

Billing module for PhoenixKit. Drop-in payments, subscriptions, invoices, orders, and multi-currency support with Stripe, PayPal, Razorpay, and EveryPay integration.

Features

Installation

Add phoenix_kit_billing to your dependencies in mix.exs:

def deps do
[
{:phoenix_kit_billing, "~> 0.3"}
]
end

Then fetch dependencies:

mix deps.get

Note: 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.

Quick Start

  1. Add the dependency to mix.exs
  2. Run mix deps.get
  3. Enable the module in admin settings (billing_enabled: true)
  4. Configure at least one payment provider in admin settings
  5. Orders and invoices are available at /admin/billing

Usage

Order-to-Invoice Workflow

alias PhoenixKitBilling
# 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)

Subscriptions

# 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.

Payment Providers

Supported payment providers:

ProviderCodeNotes
Stripe:stripeCards and wallets; signed webhooks
PayPal:paypalPayPal and cards; signed webhooks
Razorpay:razorpayIndia (UPI, cards); signed webhooks
EveryPay:everypayEveryPay AS gateway (Baltics), API v4; callbacks verified by server-side status re-fetch

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.url

Webhooks are handled automatically at /webhooks/billing/:provider.

Host setup — raw body reader. Webhook signature verification needs the raw request body, so your endpoint must wire PhoenixKitBilling.Plugs.CacheBodyReader into Plug.Parsers:

plug Plug.Parsers,
parsers: [:urlencoded, :multipart, :json],
body_reader: {PhoenixKitBilling.Plugs.CacheBodyReader, :read_body, []},
json_decoder: Phoenix.json_library()

mix phoenix_kit_billing.install does this for you. Without it, webhooks return 400 with :no_raw_body before processing.

Real-Time Events

Subscribe to billing events in your LiveViews:

def mount(_params, _session, socket) do
PhoenixKitBilling.Events.subscribe_orders()
{:ok, socket}
end
def handle_info({:order_created, order}, socket) do
# Update UI
{:noreply, socket}
end

Settings

KeyTypeDefaultDescription
billing_enabledbooleanfalseEnable/disable the billing system
billing_default_currencystring"EUR"Default currency for new orders
billing_tax_enabledbooleanfalseEnable tax calculations
billing_company_namestringCompany name on invoices
billing_company_addressstringCompany address on invoices

Provider-specific settings (API keys, webhook secrets) are configured per-provider in the admin panel at /admin/settings/billing/providers.

Invoice Status Workflow

StatusDescription
"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
draftsentpaid
overduepaid
void

Permissions

The module declares permissions via permission_metadata/0:

Use Scope.has_module_access?/2 to check permissions in your application.

CSS Requirements

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.

Architecture

lib/
├── phoenix_kit_billing.ex # Main module (context + PhoenixKit.Module behaviour)
└── phoenix_kit_billing/
├── mix_tasks/
│ └── phoenix_kit_billing.install.ex # Install mix task
├── 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
│ └── everypay.ex # EveryPay 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

Database Tables

TableDescription
phoenix_kit_billing_profilesUser billing information (UUIDv7 PK)
phoenix_kit_currenciesCurrency definitions
phoenix_kit_ordersOrders with line items
phoenix_kit_invoicesInvoices with receipt data
phoenix_kit_transactionsPayment/refund transaction ledger
phoenix_kit_subscriptionsActive subscriptions
phoenix_kit_subscription_typesSubscription plan definitions
phoenix_kit_payment_methodsSaved payment methods from providers
phoenix_kit_payment_optionsAvailable payment options
phoenix_kit_webhook_eventsProvider webhook event log

Development

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

Testing

The module ships its own test harness — a DataCase/LiveCase, a test Endpoint + Router, and core's versioned migrations run via PhoenixKit.Migration.ensure_current/2 (no parent app required):

createdb phoenix_kit_billing_test # one-time: create the test database
mix test # run the suite

Use PhoenixKitBilling.DataCase for schema/context tests and PhoenixKitBilling.LiveCase for admin LiveView tests.

Troubleshooting

Billing not appearing in admin

Webhooks not processing

Subscription renewals not running

License

MIT — see LICENSE for details.