Billing Overview
High Level Domain
Fundamental Concepts
Service Entitlement vs money
Entitlement: what the customer can still use:
- placement_credit (sponsored placements)
- 16 Sponsored Placement coins left
- gig_credit_cents (stored value credits measured in cents)
- 23717 gig credits left (cents value of $237.17)
Money: is what finance cares about:
- deferred revenue (you owe since delivery)
- principal liability (refundable stored value)
- recognised revenue (earned)
The term "Entitlement" is important to memorise and understand. It will be used extensively throughout the documentation. It is an umbrella term to represent "what users CAN do on the platform", which can be anything that requires a "stored value" (i.e. credits):
- creating a
Career::Jobpost forXplacement credits - creating a
Gig::Jobwith shifts forYgig credits - creating a
Ad::Campaginover 7 days forZplacement credits
Ledger vs Projection
Ledger
- append-only journal of all balance-affecting actions
- grant, reserve, release, consume, adjust
Projection
- Cached "current state" tables for fast reads (e.g. balances, active holds)
- Projections are rebuildable from the ledger
Deferred Revenue vs Principal liability
Deferred Revenue
- money received in the bank account, but service is not yet provided (i.e.
SFRS(I) 15)
Principal Liability
- money/credits refundable to the customer
- behaves like a "stored value" that we owe back (not revenue)
Reservation is not consumption
- Reserve moves units from
available -> reserved - Consume reduces units
- usually from reserved, sometimes directly from available
- Ensures company cannot overspend by:
- multiple gig job postings
- multi-day campaigns where credits cost more than what they have
Why Gig is special?
Gig Credits require:
- FIFO (first in first out)
- Per-lot platform fee rates
- We MUST track consumption against purchase batches
Sponsored Placements (Ads, Career Job Post, Boost):
- does not need per-batch tracking like Gig
- track proportionally
Xero-friendly exports
- Finance process is lump-sum journaling (daily or monthly)
- Internal system still needs customer-level statements and auditability (SOA for companies)
- Design should support both without refactors
Overview
::Billing will be the bounded context that manages:
- customer entitlements (units)
- deferred revenue / liability movements (money)
- reservations and consumption
- statement of accounts (SOA)
- finance exports compatible with current journaling workflow and future Xero Integration
Products/Instruments supported
Placement Credits (visibility-driven, pool deferred revenue)
- Ads (display placements)
- Job Boost (featured placement for a duration)
- Careers Job Postings (per post/per application later)
Gig Credits (stored value + FIFO lots for platform fee):
- credit represents wage value
- platform fee deferred and recognised by FIFO lots
Subscription (Workforce, future)
- introduce later as another entitlement type
- seat-days
- seat-months
- or contract schedules
Glossary
| term | description | examples |
|---|---|---|
| Entitlement Type | defines unit type and accounting policy | placement_credit, gig_credit_cents |
| Ledger Entry | one atomic balance-changing record (append only) | - |
| Balance | current available/reserved units and deferred money for a given entitlement type | 10030cents available gig credits, 10 placement credits reserved for 7 day campaign |
| Hold | active reservation projection keyed by a reference | Gig::Shift#123, Ad::CampaignPlacement#456 |
| Lot | gig purchase batch with its own platform fee rate. Used for FIFO allocation | Company A would have 3 Billing::Lot if they purchased gig credits 3 different times. Each lot would store the platform fee margin. |
| Allocation | mapping from a ledger entry to one or more lots (gig only) | - |
| Reference | external domain object that caused the billing event | Ads::Campaign, Careers::Job, Gig::Shift (reserve), Gig::ShiftSettlement (consume) |
| Product | global definition of what is being sold (stable); used to derive entitlements granted | 1000 Placement Credit Pack without the price attached to it. We can sell the same pack across different countries. |
| Offer | market-specific price list row for a product (currency, taxes, seller legal entity, gig fee terms) | 1000 Placement Credit Pack in SG costs $100, while in ID it might cost IDR 1,000,000. Tax calculation also is different in each country |
| Legal Entity | your seller-of-record for a jurisdiction (drives invoice numbering + tax treatment) | Represents Jod legal entities in different countries. Possible to have multiple entities in a single country. |
| Bill-To Profile | customer billing recipient details (address/contact), owned by the customer company | Company A might use the platform, but it's sister company might be the one that makes payments on behalf of Company A. |
| Invoice | customer-facing commercial document generated from an offer snapshot | - |
| Payment | offline bank transfer record + verification status | Each invoice tied to one or more payments recorded manually by business/finance team |
| Posting | the idempotent internal action that grants entitlements into the ledger once an invoice is settled | An accounting action and not a "product use-case". |
Modelling canonical "financial primitives"
Stable finance primitives we can use instead of using domains (Ads/Gig/etc.).
These primitives apply to all instruments listed in billing_entitlement_types (e.g. gig_credit_cents, placement_credits, subscriptions, etc.)
- Only the policies differ per entitlement type.
| term | description | example |
|---|---|---|
| Grant | increase available entitlements (usually after payment verification) | Company made payment via bank transfer. Business marks invoice as paid and credits will be "granted" to the company's billing account |
| Reserve (Hold) | move available to reserved (to prevent overspend) | 100 credits reserved after company posts a Gig::Shift, 20 credits reserved after company creates an Ad::Campaign |
| Release | move reserved back to available (campaign cancelled, shift cancelled) | Previously reserved 100 credits moved back into usable credits cause Gig::Shift was cancelled. |
| Consume | reduce reserved or available (service delivered) | Gig::ShiftSettlement created, Daily for a 7 day ad campaign. |
| Adjust | manual corrections | Think CAFS |
Entitlement TYpes
Placement Credits (placement_credits)
Services granted:
- Ads (display placements)
- Job Boost (featured placement for a duration)
- Careers Job Posting (per post / per application)
Finance Policy:
- Units are pooled regardless of price and time of purchase
- no per-purchase unit price tracking like in Gig
- Money tracking:
deferred_revenue_cents(remaining)- revenue recognised proportionally at consumption time:
recognised = units_consumed * (deferred_revenue_before / units_before)
Gig Credits (gig_credit_cents)
- Units represents the wage value (in cents)
- Requires FIFO lots because platform fee rate can differ per purchase/invoice.
- Money tracking:
- principal liability
- Jod owes stored value back or owes to provide a service like finding applicants for Gig or placing an ad on the website.
- platform fee deferred and recognised based on FIFO lot allocations
- principal liability
- Reservation may span multiple lots
Subscriptions (future)
Subscriptions (e.g. for Workforce management) will be introduced as another entitlement type (seat-days, seat-months) and/or based on the contract from sales.
Projections (fast reads) vs ledger (truth)
Two main projections:
billing_entitlement_balances- what is the available
gig_credits_cents/placement_credits? - what is the reserved
gig_credits_cents/placement_credits? - what is the deferred revenue existing on
gig_credits_cents/placement_credits?
- what is the available
billing_entitlement_holds- what active holds (
gig_credits_cents/placement_credits) exist for a given reference (e.g.Gig::Shift,Ad::Campaign,Boost::Job)
- what active holds (
Both should be updated in the same DB transaction as when creating a ledger entry record. Both are rebuildable from ledger (and lots for gig)
Considering Xero Integration
We design for two export modes for Xero automation:
- Journal mode
- Aggregate daily totals across the platform
- movement in deferred revenue
- recognised revenue
- gig credits consumed (liability reduction)
- insurance deductions/sponsored amounts
- Export as journal lines (CSV now, API later)
- Aggregate daily totals across the platform
- Invoice mode (future)
- Later we want per-customer sales invoices in Xero
- Keep
billing_invoicesandbilling_payments - Ledger remains the "delivery and recognition" system of record
- Keep
- Later we want per-customer sales invoices in Xero
Ledger stores recognition snapshots at the moment of consumption.
- ensure can export accounting entries later without recomputing history and risking drift.