Seam closures (round 2)
Four cross-cutting additions that closed documented seams across
commerce, checkout,
subscriptions, and the storefront. None is a
new module; each is a wire-through that made an existing surface complete.
tax:collected liability leg
Every order that reaches placed books a balanced transaction to
accounting with the deterministic idempotency key
order-revenue:{order_id}. Seam-closure round 2 split the revenue leg so
collected sales tax is never booked as income:
- debit
cash:clearingfor the fulltotal_cents, - credit
revenue:salesfor(subtotal − discount + shipping), - credit the
tax:collectedliability leg for the tax portion.
An untaxed order keeps the historical two-leg shape (no tax leg). The
posting is written to commerce.revenue_postings in the same transaction as
the placed flip and drained by the revenue-posting sweep. Refund reversals
mirror the same shape under order-revenue-refund:{order_id}:{refund_ref}.
Proportional discount/tax/COGS recognition across split partial refunds remains out of scope — each partial refund reverses its own
amount_centscorrectly, but the split apportionment is a documented follow-up.
Order ship_to
The saved-address picker is now persisted on the order’s own
ship_to column (previously only an audit-entry field). At checkout,
parseShippingAddress(body.shipping_address) is forwarded into
commerce.createOrder(...) as a first-class shipTo, and recorded on the
order.created audit entry for the actor trail. The storefront BFF forwards a
fully-validated address only when the store’s address-book gate is on and
a shopper is signed in; absent (a caller not yet upgraded) -> the legacy
behavior, no ship-to.
Subscribe-and-save at checkout
POST /v1/tenants/:tenant/checkout accepts subscribe: true.
When set, the order is placed and its first charge captured as normal, then
every subscription-eligible line in the cart also starts a
subscription: the card is vaulted via payments’ vaultCard (deterministic
key checkout:{key}:vault) and the subscription is created in
subscriptions with that vault token + the plan’s
cadence. The subscribe receipt is returned on the checkout response (empty
unless subscribe was set).
Partial-failure, surfaced not swallowed: if the card cannot be vaulted
after the order is already placed and charged, checkout writes a
subscription.vault_failed_after_placement audit entry and returns
placed_but_card_not_vaulted naming the order — never a false 2xx. A retry
with the same Idempotency-Key replays the original placement and vault
rather than re-charging.
Wishlist
A per-store wishlist. The guest half is client-side,
localStorage-backed under the load-bearing namespaced key
fd_wishlist:{storeKey} (two tenants on shared localStorage must never see
each other’s saved products); a wishlist entry is an opaque product ref (the
catalog slug). A signed-in shopper’s wishlist lives server-side in
persons, and the guest list is merged into it on sign-in.
Storefront BFF routes:
| Method | Path | Purpose |
|---|---|---|
GET | /api/wishlist | Read the signed-in shopper’s server wishlist. |
POST | /api/wishlist | Add a product ref. |
DELETE | /api/wishlist | Remove a product ref. |
POST | /api/wishlist/merge | Merge the guest localStorage list into the server list on sign-in. |
Manifest gate
behavior.wishlist.enabled— default disabled: the heart toggle, the/account/wishlistpage, and the/api/wishlist/*BFF routes all answer only when a store opts in. A new surface ships dark.
Seams (honest notes)
- The tax leg is binary (tax vs no-tax shape); it does not yet model
multi-jurisdiction tax breakdown — that is the tax-provider seam checkout
refuses-closed on rather than charging
$0tax. ship_tois only populated when the address-book gate is on and a shopper is signed in — a guest checkout order carries no ship-to today.