How Trile works
This page walks the whole flow once, so the rest of the docs have a shared map.
The objects
Section titled “The objects”Product ──has many──> Price ──referenced by──> Subscription ──generates──> Invoice │Customer ──owns──> Wallet ──funds──────────────────-┘ (deducted each cycle)- Product — the thing you sell (e.g. “Pro plan”).
- Price — a recurring amount + interval on a product (e.g. NPR 499 / month).
- Customer — who you bill.
- Wallet — the customer’s prepaid balance. Funded via eSewa, Khalti, bank transfer, IME Pay.
- Subscription — a customer committed to a price. Drives billing.
- Invoice — a statement generated each cycle and paid from the wallet.
See Object IDs for the full prefix list.
The lifecycle
Section titled “The lifecycle”- Set up your catalog. Create a product and a recurring price.
- A customer subscribes. Either you call the API directly, or you send them to a hosted checkout session where they top up and authorize.
- The first cycle charges immediately (unless the price has a trial). The amount is
deducted from the customer’s wallet. If the wallet is short, creation fails with
insufficient_funds. - Each renewal, the billing engine deducts the next cycle from the wallet and generates an
invoice. On success the invoice is
paid; on a short wallet the subscription goespast_due. - Your system stays in sync via webhooks (
subscription.created,invoice.paid,invoice.payment_failed, …) and the event log.
Where the work happens
Section titled “Where the work happens”The billing engine doesn’t run inside the HTTP request. Trile runs three process roles:
| Process | Responsibility |
|---|---|
| api | Serves the /v1 HTTP API — what your integration talks to. Enqueues jobs, never blocks on them. |
| scheduler | Fires the daily billing tick, reconciliation, and idempotency-key cleanup. |
| worker | Executes billing charges, webhook deliveries, and reconciliation jobs from the queue. |
You only ever talk to api. The async split is why webhook delivery and renewals are eventually-consistent — design your integration to be webhook- and event-driven, not synchronous-only.
Go build it: Quickstart.