Skip to content
Arbinel

Billing

Billing lets users and organizations buy add-ons that raise their quota limits, from an Add-ons tab in their settings. Arbinel sells them as monthly subscriptions through Creem, which acts as merchant of record and handles payment, tax and invoices. Forgejo has no billing feature, so everything on this page is specific to Arbinel.

Billing builds on quotas. Read Quotas first, and CI quota or Egress quota if you sell those.

Each product names one or more quota subjects and an amount per unit. While an add-on counts, Arbinel adds the amount times the number of units to every rule of the owner that names one of those subjects.

  • An add-on only raises rules that exist, and it matches subjects exactly. It never raises the [quota.default] or [quota.ci.default] fallbacks.
  • The Add-ons tab only offers a product to an owner who has a rule, not already unlimited, that names one of its subjects.
  • A rule naming several of the product’s subjects is raised once.
  • Several add-ons on one rule add up, and they add to any quota grants.

Naming a parent subject and its children in one product sells a shared pool. With SUBJECTS = size:assets:all, size:assets:packages:all, size:assets:attachments:all, the parent’s rule limits the total, and every per-type rule is raised along with it.

An add-on counts while Creem reports its subscription as active, trialing, past_due, unpaid or scheduled_cancel. past_due and unpaid mean Creem is still retrying a payment, and scheduled_cancel runs until the paid period ends. Any other status, including a status Creem adds later, does not count.

  1. Create a store in Creem. Start in test mode, which Arbinel uses by default.
  2. Create one recurring monthly product per add-on you want to sell. The product’s price is the price of one unit. Arbinel reads prices from Creem, so a price change there needs no configuration change.
  3. Create an API key, and keep it for [billing.creem] API_KEY.
  4. Register a webhook endpoint for <ROOT_URL>/-/billing/creem/webhook, with checkout.completed and the subscription.* events. Keep its signing secret for [billing.creem] WEBHOOK_SECRET.
  5. Make sure your quota groups have rules that name the subjects your products raise.
  6. Configure Arbinel as below and restart it.
  7. Buy each add-on once in test mode, then set TEST_MODE = false with your live API key and webhook secret.
[quota]
ENABLED = true

[billing]
ENABLED = true

[billing.creem]
TEST_MODE = false
API_KEY = creem_xxx
WEBHOOK_SECRET = whsec_xxx

[billing.product.ci-1000]
CREEM_PRODUCT_ID = prod_xxx
NAME = 1,000 CI minutes
SUBJECTS = time:ci:all
AMOUNT = 1000m
MAX_UNITS = 20

[billing.product.storage-10]
CREEM_PRODUCT_ID = prod_yyy
NAME = 10 GiB storage
SUBJECTS = size:all
AMOUNT = 10GiB
Key Default Meaning
[billing] ENABLED false Sells add-ons. Needs [quota] ENABLED. While it is off, nothing is sold and no limit differs from what the quota groups say.
TEST_MODE true Sends every request to Creem’s test API, test-api.creem.io.
API_KEY none Creem API key. Required while billing is on.
WEBHOOK_SECRET none Signing secret of the webhook endpoint. Required while billing is on.

One section per product. The key after billing.product. is what Arbinel stores with each add-on, so you can re-price a product or move it to another Creem product without a data migration. Use lower-case letters, digits, - and _.

Key Default Meaning
CREEM_PRODUCT_ID none The Creem product. Required, and unique across sections.
NAME the key Name shown on the Add-ons tab.
TYPE fixed fixed for a product sold in units, metered for metered bandwidth.
SUBJECTS none Comma-separated quota subjects. All time: subjects or all size subjects, not both. Required.
AMOUNT none What one unit adds: a duration such as 1000m for time: subjects, a size such as 10GiB otherwise. Required.
MAX_UNITS 20 The most units one owner can buy of the product.

time: limits reset every month, so a CI add-on adds its amount per month. A storage add-on raises a limit that does not reset.

Arbinel refuses to start when:

  • billing is on and [quota] ENABLED is false, or API_KEY, WEBHOOK_SECRET or every product section is missing,
  • a product section is invalid, even while billing is off, so you notice a broken section as soon as you add it,
  • a subject is unknown, or counted in a different unit than AMOUNT,
  • a product raises time: subjects while [quota.ci] ENABLED is false.

It only logs a warning when no quota rule names a product’s subject, because the Add-ons tab then offers the product to nobody.

Removing a product section stops its add-ons from raising anything, and the Add-ons tab shows them as No longer sold. Creem keeps billing them until they are cancelled, so cancel them in Creem first.

One product may have TYPE = metered. It is billed by the download traffic above the owner’s free egress limit, not by units. It needs the egress quota to be fed by your edge proxy.

[billing.product.bandwidth]
CREEM_PRODUCT_ID = prod_zzz
NAME = Bandwidth
TYPE = metered
SUBJECTS = egress:all
EVENT_NAME = bandwidth
PRICE_PER_GIB = 5
DEFAULT_CAP = 1000
MAX_CAP = 100000
MIN_CHARGE = 500
Key Default Meaning
SUBJECTS egress:all The only subject a metered product can raise.
EVENT_NAME none Event name of the Creem meter. Required.
PRICE_PER_GIB none Price per GiB in cents, excluding VAT. Required.
DEFAULT_CAP 1000 Monthly cap in cents, excluding VAT, for an owner who has not set one.
MAX_CAP 100000 The highest monthly cap an owner can set, in cents.
MIN_CHARGE 500 Cents of usage to collect before reporting it, so Creem’s fee per charge stays small.

AMOUNT and MAX_UNITS do not apply and are refused.

The owner sets a monthly cap in money. Arbinel raises their egress:all rules by the traffic that cap buys at PRICE_PER_GIB. With the defaults, a cap of 1,000 cents at 5 cents per GiB buys 200 GiB.

Every hour, the billing_report_usage cron task calculates each owner’s billable traffic for the month. This is the traffic they downloaded above their egress limit without add-ons, up to the amount their cap buys. Traffic past the cap, served while the edge proxy’s deny list catches up, is never billed. Arbinel waits until the usage is worth MIN_CHARGE, or the subscription ends, and then sends it to Creem as events named EVENT_NAME. For the first six hours of a month, it also reports the previous month, for traffic the aggregators file late.

Each owner, whether a user or an organization, gets its own Creem customer for bandwidth, under a billing e-mail entered at checkout. The billing e-mail must be one of the buyer’s verified addresses, and for an organization it must not be the buyer’s primary address.

The product needs exactly one usage price, which Arbinel checks before each report:

  • settlement mode postpaid,
  • no free allowance, because the egress quota rule is the free allowance,
  • a unit price per MiB equal to PRICE_PER_GIB divided by 1024,
  • a meter whose event name is EVENT_NAME, which sums the event property mib.

If any of these do not match, the cron task fails with an error and reports nothing until you fix it. Arbinel computes the owner’s limit from PRICE_PER_GIB, so the price in Creem must match it.

Creem signs each webhook body. Arbinel checks the signature and refuses deliveries older than 24 hours. It treats a delivery only as a signal, and records the subscription as Creem’s API reports it. As a result, duplicate or out-of-order deliveries cause no problems. If Creem’s API cannot be reached, Arbinel answers 503 so that Creem retries.

Task Schedule What it does
billing_reconcile @midnight Synchronizes add-ons with Creem in case webhooks were lost. Records subscriptions that carry Arbinel’s metadata but have no row yet, corrects drifted rows, and stops counting add-ons Creem no longer knows.
billing_report_usage @every 1h Reports metered bandwidth to Creem. Does nothing without a metered product.

If Creem does not know most of the add-ons Arbinel counts, billing_reconcile changes nothing and fails. This usually means that the API key belongs to another store or to the other mode. In that case, cancelling every add-on would be wrong.

The admin page of a user account lists its add-ons, with product, quantity, status, period end and Creem subscription.

An owner with add-ons that Creem still bills cannot be deleted. That also applies to a user who bought add-ons for an organization. Cancel the add-ons first. Deletion works once they have ended, which is at the end of the paid period. Bandwidth needs up to two more days after it ends, to bill the last downloads.

Usage and add-ons describes billing from the owner’s side.