> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fourdos.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Revenue

> How sales and refunds reach 4D, how they're totalled, and who can see them

Revenue is the money your members spend with the club - tickets, merch,
memberships, anything you sell. 4D records each sale and refund as a
revenue event, totals them in your reporting currency, and ties them back to
the member who paid where it can.

Revenue is a reporting figure, separate from [XP](https://docs.fourdos.dev/concepts/xp.md). Recording
revenue never awards XP on its own.

## Two ways in

Revenue totals are trusted figures, so they only come from your servers,
never from a browser:

**Module ingest - `POST /v1/ingest`.** An installed module whose event
projects to the canonical `commerce.purchase.completed` or
`commerce.refund.issued` type records a sale or refund when the canonical
payload carries an integer `amount` and an ISO 4217 `currency`. Ticket sales
and refunds from the ticketing module arrive this way from version 1.1.0;
an installation pinned to an earlier version records no revenue until it
adopts a newer one. The category comes from the
canonical `product_type` when it is a lowercase slug (`ticket`, `merch`),
and is `purchase` otherwise. A purchase without `amount` and `currency`
still flows through as an event and can still earn XP under your rules - it
just records no revenue. See
[Publish a private module](https://docs.fourdos.dev/walkthroughs/publish-a-private-module.md).

**Revenue API - `POST /v1/revenue`.** Anything else you sell, reported by
your backend with an organization `org_` key. Never call it with the
publishable keys or from the [engagement SDK](https://docs.fourdos.dev/sdk/engagement-sdk.md). See
[Record revenue](https://docs.fourdos.dev/walkthroughs/record-revenue.md).

## Money is minor units plus a currency

Every amount is a positive integer in the currency's **minor units**, with
an upper-case ISO 4217 code beside it:

| You mean | `amount` / `amount_minor` | `currency` |
| - | - | - |
| €19.99 | `1999` | `EUR` |
| £25.00 | `2500` | `GBP` |
| ¥1,200 | `1200` | `JPY` (no minor unit) |

No floats, no decimal strings. A refund is its own event with a positive
amount; its type says it subtracts.

## Reporting currency

Each club picks one reporting currency in the console under
**Setup › Settings** (administrators only). Totals only include events in
that currency:

* Events in any other currency are still stored and appear in the recent
  events list, and the Revenue page counts them as **not included**.
* There is no FX conversion. If you sell in two currencies, choose the one
  you report in, or convert before you send.
* Until a currency is set, the revenue screens say so instead of guessing,
  and nothing is totalled. Events you send in the meantime are kept and
  count as soon as you pick their currency.

## Sales, refunds and net

**Net revenue = sales − refunds**, both in the reporting currency. The
Revenue page also shows gross sales, refunds, the number of orders (sales),
and **paying members** - distinct buyers behind those sales. Days are UTC
days.

## Retries are safe

Each event carries your `event_id`, and revenue is idempotent on it. A
resend with the same id and the same type, amount and currency changes
nothing and is reported as a `duplicate`. The same id with different money
is `rejected` with `idempotency_conflict` - correct a mistake with a refund
under a new id, not by resending. Module events dedupe the same way on their
ingest `event_id`.

## Linking buyers to members

Name the buyer with `member_external_id` (your id for the member) or the
platform `user_id`. A buyer who isn't a member yet is still recorded - the
sale counts toward totals - and is linked automatically if they later join
with that `external_id`. A sale with no buyer at all counts toward totals
but not toward paying members.

## Where it shows up

* **Operate › Revenue** - net, gross, refunds, orders and paying members
  over 7, 30 or 90 days, a daily trend, a breakdown by category, and the
  most recent events.
* The **tenant dashboard** gains a revenue row.
* **Member detail** shows the member's lifetime spend, and the **members
  list** has a Spend column.
* The **Revenue dashboard card** shows the same report, by category, on any
  role dashboard that includes it (the prebuilt CFO role does).
* The **AI analyst and insights** can answer revenue questions.
* The **Fan base value** dashboard card, also shown on the Simple view's
  Audience page, estimates what your fan base is worth (see below).

Seeing any of this takes the **View revenue** capability. The prebuilt CFO
role has it; add it to a custom role under **Team → Roles**. See
[Team](https://docs.fourdos.dev/concepts/team.md).

## Fan base value

The Fan base value is an estimate built from the last year of your own data:

**Active fans × ARPU × enrichment × retention ÷ discount rate**

* **Active fans** are current members with activity (an engagement event or
  earned XP) in the last 365 days.
* **ARPU** is the year's net revenue in the reporting currency per active
  fan. With less than a year of revenue, the year is projected from what you
  have.
* **Enrichment** is the share of profile fields active fans have filled in:
  date of birth, country, first and last name, plus any custom fields you
  enable.
* **Retention** is the share of fans active in one window who are active
  again in the next. Windows are a year once you have two years of activity,
  and half your history before that. It needs at least 28 days of activity.
* **Discount rate** is yours to set under **Setup › Settings**. It is 12%
  until you choose one.

It needs a reporting currency. It is a management estimate, not an amount
you can book as an asset.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.