ReferenceSeam closures

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:clearing for the full total_cents,
  • credit revenue:sales for (subtotal − discount + shipping),
  • credit the tax:collected liability 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_cents correctly, 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 paymentsvaultCard (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:

MethodPathPurpose
GET/api/wishlistRead the signed-in shopper’s server wishlist.
POST/api/wishlistAdd a product ref.
DELETE/api/wishlistRemove a product ref.
POST/api/wishlist/mergeMerge the guest localStorage list into the server list on sign-in.

Manifest gate

  • behavior.wishlist.enabled — default disabled: the heart toggle, the /account/wishlist page, 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 $0 tax.
  • ship_to is only populated when the address-book gate is on and a shopper is signed in — a guest checkout order carries no ship-to today.