Files
caravel/backend/spec/billing.md
T
2026-03-25 17:11:35 -07:00

51 lines
2.3 KiB
Markdown

# `pub struct Billing`
Billing is a service which polls the database, creates invoices, and attempts to collect payment for invoices.
Members:
- `nwc_url: String` - a nostr wallet connect URL used to **create** invoices
- `repo: Repo`
- `robot: Robot`
## `pub fn new(repo: Repo, robot: Robot) -> Self`
- Reads environment and populates members
## `pub async fn start(self)`
Calls `self.tick` in a loop every hour.
## `pub async fn tick(self)`
Iterates over `repo.list_activity` since last run and does the following:
- For any `create_relay|update_relay|activate_relay` activity if this is the first non-free relay for the tenant, update tenant's billing anchor to the time the relay was created.
Also iterates over `repo.list_tenants()` and for each tenant calls `self.generate_invoice_if_due(tenant)` and `self.collect_outstanding(tenant)`.
## `async fn generate_invoice_if_due(&self, tenant: &Tenant)`
- Skip tenants that have a `pending` invoice or have no active non-free relays.
- Compute current billing period from `tenant.billing_anchor` as rolling monthly windows: `[period_start, period_end)`.
- Only generate an invoice once the period has closed (`now >= period_end`).
- Load activity needed to compute usage for the tenant using `repo.list_activity`.
- Calculate how many hours (rounded up) each relay was active during the window per paid plan.
- If total invoice amount is 0, return.
- Generate a `bolt11` invoice. If this fails, log the error and return.
- Generate an invoice for the tenant and an invoice item for each relay.
- Persist invoice + items atomically using `repo.create_invoice`.
## `async fn collect_outstanding(&self, tenant: &Tenant)`
- Load `pending` tenant invoices and attempt to collect each one.
- If `attempted_at` is less than 24 hours ago, skip it.
- If the `bolt11` invoice has been paid out of band, call `repo.mark_invoice_paid` and return.
- If the tenant has a `nwc_url`, attempt to pay the invoice with nwc.
- If collection succeeds, call `repo.mark_invoice_paid`.
- If collection fails, populate `repo.mark_invoice_attempted`.
- If nwc isn't set up or fails and `sent_at` is not set:
- Send a NIP 17 DM to the user with the invoice included.
- Call `repo.mark_invoice_sent`.
- If the invoice is 7 days past `created_at`, call `repo.mark_invoice_closed`.