ForgeBilling

A billing portal your app adds — a catalog of plans, subscriptions, invoices, payment methods, and transactions, plus a ready-made web UI under /billing. It bills a tenant or an individual user, and lets you plug in any payment provider.

Overview

ForgeBilling gives your app the day-to-day machinery of selling something: you define billable plans, hand one to whoever you bill, and the component tracks the resulting subscription, generates invoices, records payment methods, and files transactions against each charge. Out of the box it ships a full portal your customers use to pick a plan, enter payment details, and view their invoices.

What it gives you

  • Plans with a price, interval & features
  • Per-customer subscriptions with status
  • Invoices, line items & transactions
  • A ready-made billing portal at /billing
  • Extensible payment-provider interface
  • CLI to create plans and assign them

Prerequisites: ForgeBilling requires the routing layer, the database and ORM layers, and the view and component layers (for its portal UI). Payment itself is a provider you plug in — a manual provider ships so the whole flow works immediately.

Who You Bill

Before anything else, the component works out who the current billing entity is. A resolver looks for, in order: the active tenant (provided by ForgeMultiTenant), then the signed-in user (provided by the auth layer). This is what makes the component work across either shape of app:

  • A multi-tenant app bills each tenant — its subscription is keyed by tenant_id.
  • A plain single-workspace app bills the current user, keyed by the user id.

When neither a tenant nor a user is present, the component proceeds with an empty view rather than failing — so the portal stays browsable.

Installation

php forge.php package:install-module --module=ForgeBilling

Installing migrates the module's tables and mounts the billing routes and middleware. Six tables back the component, all held in the app's shared database (each carrying a tenant_id column where the billing entity is a tenant):

  • billing_plans — the plan catalog.
  • billing_subscriptions — one per billing entity, linked to a plan.
  • invoices & invoice_items — bills and their line items.
  • payment_methods — saved payment details per entity.
  • transactions — a record of every charge attempt.

Plans

A plan is a priced tier: a name, a slug, an amount and currency, a billing interval, and an optional feature list (useful for displaying what a plan includes). Intervals are free-form strings such as monthly, yearly, weekly, or one_time.

# Create a monthly plan for $15 USD
php forge.php modules:billing:plan:create --name=Pro --slug=pro \
    --amount=15 --currency=USD --interval=monthly \
    --features=api_access,custom_domain

Create, list, disable, or delete plans from the same command surface or from your code. Disabling a plan retires it from the portal; existing subscriptions are left untouched.

Subscriptions

A subscription links a billing entity to a plan and carries a status — active, trial, past_due, canceled, or expired — plus optional trial, period-end, and cancel timestamps. Assign a tenant to a plan from the command line:

# Give the Pro plan to a tenant
php forge.php modules:billing:tenant:assign --tenant=upper --plan=plan-pro

From the portal, subscribing requires a saved payment method first; once one exists the flow activates the subscription for the current billing entity and rolls its period forward. Users can cancel from the subscription page, which marks it canceled.

In a request, the subscription is resolved once up front and then read consistently by the rest of the component — the page you render sees a stable, current subscription throughout.

Invoices

Each chargeable period earns an invoice with a human-friendly number, an amount and currency, and a status — pending, paid, overdue, canceled, or refunded. Line items are stored alongside it so an invoice shows exactly what was billed.

For recurring billing, the component fires a GenerateInvoiceEvent per due subscription: the listener creates the invoice, records its line item, and rolls the period end forward by the plan's interval. You can review what's due and dispatch it from the command line (see below).

Payments & Providers

Charging money goes through a payment provider. The component defines a small PaymentProvider contract — charge, refund, tokenize, and a name — and keeps a registry of providers. A manual provider ships by default so the flow works end to end, and you register your own (Stripe, a gateway, or an in-house ledger) with the same interface:

use Modules\ForgeBilling\Services\PaymentProviderRegistry;
use Modules\ForgeBilling\Contracts\PaymentProviderInterface;

// Resolve the registry and add your provider
$registry = $container->get(PaymentProviderRegistry::class);
$registry->register(new MyProvider());

A provider's charge returns a result with a transaction id and whether it succeeded; the component records that attempt in transactions and, on success, marks the invoice paid. Payment methods are saved per billing entity and the registry holds whichever provider your app has configured.

Bring the actual card processor: the bundled manual provider always approves charges, which is perfect for development or for apps that collect payment outside the portal. For real card processing, register a provider backed by your processor.

The Billing Portal

The component ships a complete, styled portal mounted under /billing. It resolves the current billing entity automatically and renders a dashboard, the priced plans, invoice history and detail, saved payment methods, and a subscription page:

  • Overview (/billing) — current plan, subscription status, latest invoice, and recent invoices.
  • Plans (/billing/plans) — the active plan grid with a subscribe action and a "current plan" marker.
  • Invoices (/billing/invoices and /billing/invoices/{id}) — invoice history and a line-item detail view.
  • Payment Methods (/billing/payment-methods) — add and remove saved payment methods.
  • Subscription (/billing/subscription) — current plan details and a cancel action.

The portal is ordinary views and components, so you style and regroup it to match your app rather than forking a black box.

From Your Code

Small helpers answer billing questions anywhere in your app — badges, dashboards, or gating UI:

billing_subscription();  // ?BillingSubscription — the current entity's plan
billing_is_active();     // bool — subscription is active or on trial
billing_on_trial();      // bool — currently on a trial

Underneath, each concern is its own service you can inject — plans, subscriptions, invoices, payment methods, and payments:

use Modules\ForgeBilling\Services\BillingPlanService;
use Modules\ForgeBilling\Services\BillingSubscriptionService;
use Modules\ForgeBilling\Services\InvoiceService;
use Modules\ForgeBilling\Services\PaymentService;

$plans    = $container->get(BillingPlanService::class);
$subs     = $container->get(BillingSubscriptionService::class);
$invoices = $container->get(InvoiceService::class);
$pay      = $container->get(PaymentService::class);

$plan    = $plans->create('Pro', 'pro', 15.0, 'USD', 'monthly');
$sub     = $subs->assign('tenant-id', $plan->id);
$invoice = $invoices->create('tenant-id', $sub->id, $plan->amount, 'USD');
$result  = $pay->charge('tenant-id', $invoice->id, $plan->amount, 'USD');

The services keep the branch logic out of your code so building your own checkout, admin, or reporting screen is a matter of calling them.

CLI Commands

Manage plans, assignments, and recurring billing from the command line:

# Plans
php forge.php modules:billing:plan:list
php forge.php modules:billing:plan:create --name=Pro --slug=pro \
    --amount=15 --currency=USD --interval=monthly
php forge.php modules:billing:plan:disable --id=plan-pro

# Assign a plan to a billing entity
php forge.php modules:billing:tenant:assign --tenant=upper --plan=plan-pro

# Recurring billing — preview, or dispatch and process due invoices
php forge.php modules:billing:generate-invoices --dry-run
php forge.php modules:billing:generate-invoices --process

Plan creation and assignment accept interactive prompts when you omit values. The invoice generator finds active subscriptions whose period is due, prints a summary in dry-run, and dispatches the invoice events when told to process.

Billing vs. SaaS

ForgeBilling and ForgeSaas sit at different layers and work well together:

  • ForgeBilling handles money and entitlement records — plans with a price, subscriptions, invoices, payment methods, and transactions. This is where the customer's account relationship lives.
  • ForgeSaas decides what a subscription may do — it turns a plan's feature list and limits into runtime checks (RequiresFeature, RequiresPlan, WithinLimit) that gate your routes.

They don't call each other directly, but they're natural companions: ForgeBilling records the plan and the charge, ForgeSaas enforces what that plan unlocks. You can use either alone — bill customers without gating features, or gate features without charging.

Running both in a multi-tenant app, the same tenant id links a billing_subscriptions row (the charge) to a tenant_subscriptions row (the entitlement), so keep the id in sync across the two when you create a tenant.