This runbook covers connecting an Inventory storefront to Polar
for real checkout, pointing Polar’s webhook at Tuturuuu, and how a paid sale
flows into the workspace finance ledger.
Never paste Polar tokens, client secrets, or webhook secrets into source,
docs, or chat. They are supplied only through environment variables and the
per-workspace integration panel, which stores them encrypted.
How the flow works
- A shopper checks out on a
checkoutMode: 'polar' storefront. Tuturuuu creates
a Polar checkout and stores polar_checkout_id on the
private.inventory_checkout_sessions row (status reserved).
- The shopper pays on Polar. Polar sends an
order.updated webhook to Tuturuuu.
syncInventoryPolarOrder marks the checkout completed and, when the order
is paid, books the revenue into the workspace finance ledger
(wallet_transactions) using the product’s finance category and the
workspace default wallet. This is idempotent — a sale books at most one
transaction.
Money and currency
Inventory commerce money (storefront listing prices, bundle prices,
compare_at_price, checkout session/line amounts, settlement ledger entries,
and costing figures) is stored in integer minor units of the row’s
currency — cents for USD/EUR, but whole units for zero-decimal currencies like
JPY/VND. This matches how Polar represents amounts, so the product/bundle/
checkout sync passes the stored value straight through without scaling.
Conversion is centralized in @tuturuuu/utils/money:
majorToMinor(amount, currency) / minorToMajor(minor, currency) — convert
at input/storage and read/display boundaries (currency-aware: USD ×100,
JPY/VND ×1).
formatMoneyFromMinor(minor, currency) — the canonical display formatter.
- The shared
MoneyInput (@tuturuuu/ui/money-input) takes and emits minor
units while editing in localized major units.
Two boundaries convert explicitly:
- The finance ledger (
wallet_transactions.amount) stores major units, so
recordInventorySaleFinanceTransaction converts the checkout’s minor-unit
total back to major units when booking revenue.
- Promotions (
workspace_promotions.value) remain in major units (shared with
finance invoices); promotions-polar multiplies by 100 at the Polar boundary.
Migrating from the previous whole-dollar storage: the
..._inventory_commerce_minor_units migration multiplies existing inventory
commerce amounts by each row’s currency factor. Shared tables
(inventory_products, workspace_promotions, finance wallets) are left as-is.
Bundles and listings ↔ Polar products
Publishing is automatic: creating or updating a listing/bundle schedules a
best-effort push to Polar (a product with a fixed price per currency). Standalone
bundles with no storefront publish against the workspace-level Polar integration
and are priced in USD. Two-way sync:
- App → Polar: name, description, and price sync on every write. Archiving a
listing/bundle (or deleting it) archives its Polar product so it is no longer
buyable; re-publishing un-archives it.
- Polar → App:
product.created/updated webhooks apply name, description,
and price back onto the mapped inventory row.
Environment variables
Set these on apps/pay (reference by name only). Inventory-only deployments
that create Polar products also need the access token and sandbox/currency
configuration in their owning runtime:
| Variable | Purpose |
|---|
POLAR_SANDBOX | true to use Polar’s sandbox; unset/false for production. |
POLAR_ACCESS_TOKEN | Platform-level Polar organization access token. |
POLAR_WEBHOOK_SECRET | Secret used to verify incoming Polar webhook signatures. Must match the value Polar shows when you create the webhook. |
POLAR_CURRENCIES | Optional allowlist of supported storefront currencies. |
For sandbox testing, create the credentials at
https://sandbox.polar.sh/dashboard/<org>/settings and the OAuth app /
organization token under User Settings → Developer.
Connect a workspace to Polar
- Open Inventory → Overview → Polar settings in the workspace.
- Choose the environment (sandbox or production) and paste the Polar
organization access token. It is encrypted per workspace before storage;
only the last 4 characters are ever shown again.
- Save. Tuturuuu validates the token and provisions a private
inventory_checkout product in your Polar org.
In the Polar dashboard for your org → Settings → Webhooks → Add Endpoint:
-
URL — the canonical Pay webhook endpoint:
https://pay.tuturuuu.com/api/payment/webhooks
For local development, expose localhost with a tunnel (e.g. cloudflared
or ngrok) and use the tunnel’s HTTPS URL with the same path.
-
Format —
Raw.
-
Events — subscribe at minimum to:
checkout.created
checkout.updated
order.created
order.updated
-
Copy the webhook secret Polar generates and set it as
POLAR_WEBHOOK_SECRET on apps/pay. Signature verification fails closed if
it does not match.
Sandbox changes never touch your live Polar account and never move real money
— the dashboard shows a “Payments are not processed” banner.
Verify a paid sale books finance
- Place a sandbox checkout on a
polar storefront and complete payment.
- Confirm Polar delivered the
order.updated event (Webhooks → Deliveries).
- The checkout session flips to
completed with polar_status = paid.
- A
wallet_transactions row appears in the workspace finance ledger for the
sale total, linked back via inventory_checkout_sessions.finance_transaction_id.
If no transaction appears, check that the workspace has a default wallet set
(finance config default_wallet_id) — booking is skipped without one.