Product handoff · Projects + Gateway

Multi-currency FX — what shipped

Pay in one currency, bill in another, and still read trustworthy cost & margin in a single reporting currency — with every converted number auditable back to its rate and date.

Status · Designed & prototyped Surface · 9 screens touched, 3 new Updated · Jun 2026

The one-paragraph version

The FX feature used to live as a separate exploration (slim mock screens in an fx/ folder). It is now woven into the real product — the actual cost dashboard, ticket, project, portfolio, reconciliation and org settings — not a parallel fork. On top of integration we hardened the trust story (locked vs stale rates, audit trail, no-guess gaps) and, most importantly, made the whole thing invisible to single-currency companies: their platform looks exactly as it did before FX existed, and the machinery only appears the moment a second currency is actually used.

Part 1

Integrated into the live product

A shared disclosure kit (the "this was converted" marker + hover popover showing native amount, rate, as-of date and source) now rides on top of the real screens. No separate FX fork to maintain.

1
Cost & margin dashboard

Revenue (billed USD/EUR), cost (paid PKR) and margin all read in reporting USD with an on every converted figure. A header Show in: USD / PKR toggle flips the view. A month with no rate on file shows as an honest gap, never a guessed bar.

pms2-cost.html
2
Ticket · Time & cost

Per-person cost carries the marker — layered on top of the existing pay-privacy mask, so a marker only ever appears when the viewer is allowed to see cost in the first place.

pms2-ticket.html → Time & cost
3
Project billing · "Billed in"

A project can be invoiced in its own currency; a live preview shows how that native revenue rolls up into reporting USD at the rate locked when each invoice books.

pms2-project.html → Settings → Billing
4
Portfolio & reconciliation roll-ups

The portfolio stat strip sums 4 currencies into reporting USD with a plain-language note; the reconciliation "margin leak" carries the same marker. Both honour the pay-privacy mask.

pms2-portfolio.html · pms2-reconciliation.html
5
New: FX rate management, legend & index

A dedicated FX rates screen (pair · rate · as-of · source, manual entry + future feed, freshness threshold) now lives in the Projects sidebar → Insights. Plus a spec/legend page and a one-page feature index.

pms2-fx-rates.html · legend · index

Part 2

Trust & correctness hardening

A converted number is only useful if people believe it. These five make the "every figure is auditable" promise real rather than decorative.

Part 3 · the important one

Single-currency companies see none of it

The biggest risk was burdening the majority of customers — who only ever deal in one currency — with machinery they don't need. The principle we shipped: FX is zero-config and self-arming, never an opt-out.

Single-currency org (default)

  • No markers, no currency toggles
  • No "FX rates" in the sidebar
  • Org setting reads a plain "Currency"
  • Project shows a quiet "Bill in another currency" link, not a picker
  • Identical to the pre-FX product

Multi-currency org (armed)

  • Full disclosure markers + popovers
  • "FX rates" appears in Insights
  • Org setting becomes "Reporting currency" + roll-up help
  • Per-project transaction-currency picker
  • Stale flags, audit trail, roll-up notes
Self-arming, not a setting to find. A company becomes multi-currency the moment they actually need it — choosing another currency on a project, or flipping "Turn on multi-currency" in org settings. That single action reveals the machinery. Nobody has to opt out of complexity they don't have.
How to demo both: every FX screen carries a small ORG · Multi / Single switch in the bottom-left corner (prototype-only, hidden in print). Flip it to Single and watch all FX chrome disappear; flip back to Multi to see the full feature.

Quick reference

Where to look, and for what
ScreenWhat to look for
Cost & marginHero markers · "Show in" toggle · amber stale forecast · missing-rate gap on the trend
Ticket · TimePer-person cost marker that respects the pay-privacy mask
Project · Billing"Billed in" picker + live roll-up preview (multi) / "Bill in another currency" link (single)
Portfolio4-currency stat strip · roll-up note · excluded-project "Add the rate" strip
ReconciliationMargin-leak cost marker + reporting/native toggle
FX ratesThe rate table that powers every conversion · stale vs fresh · empty state
Org settings"Reporting currency" ↔ "Currency" · audit log entries for rate edits
FX indexOne-page tour of the whole feature

For engineering planning

Locked decisions & scope

Decisions locked

  • Reporting currency is the default view; native is one hover/toggle away
  • Rate is locked at booking per transaction — actuals never drift
  • Forecasts use the latest rate and can go stale (per-figure flag, org-set threshold, default 7d)
  • Convert-then-sum with a ±rounding footnote
  • No rate on file ⇒ show native + a "needs rate" nudge — never a guessed number
  • All FX chrome gated on org being multi-currency

Not in this scope

  • Live rate-feed automation (UI stub present)
  • Historical rate backfill / re-statement
  • Cross-pair math beyond into-reporting conversion
  • Real RBAC wiring (demonstrated via role preview)
  • FX on Symphora's own subscription billing
Backend shape: an org.reporting_currency setting · a per-transaction fx_rate + fx_rate_date captured at booking · a rate table with as-of history and a freshness threshold · convert-then-sum roll-up. Full detail in the dev handover.