abandoned-cart visibility: capture-mode campaigns + admin view

Bumps go-cart-actor (profile-link recovery-contact backfill, capture
email sender, /carts/abandoned read API) and backoffice (Abandoned
carts panel, storefront recovery-contact wiring). Compose shares
CAMPAIGN_DIR between cart service and backoffice and sets
CART_EMAIL_SENDER=capture for dev. Plan doc:
docs/plan-abandoned-carts-visibility.md.
This commit is contained in:
mats committed 2026-08-24 12:49:30 +02:00
1 parent 93cc736ea9
commit be9ecd538b
1 file changed
+40
+40
View File
@@ -108,6 +108,20 @@ const (
// 5 hand-written `pevent.Type("track.purchase")` literals with this
// typed constant.
TrackPurchase Type = "track.purchase"
// ServerPurchase — the server-side confirmed-order fallback for
// track.purchase (wishlist dual-channel style, W6d pattern). Emitted by
// the order service's durable outbox when an order reaches StatusCaptured
// (the same atomic emitOnApply that publishes order.created), so a
// purchase is recorded even when the browser beacon was blocked (no JS /
// no consent). Deliberately session-less: it carries catalog item ids
// only — no session id, no customer identity (C5) — and feeds the
// session-less co-occurrence AlsoBought intra-pair on the tracking side.
// It must NOT ride the browser `track.purchase` type: the consumer
// routes that through the session-keyed HandlePurchaseEvent (funnel +
// converted label), which a session-less event would corrupt. Wire
// routing key `track.purchase_server` rides the existing `track.*`
// binding on the catalog exchange. Payload is an event.ServerPurchaseEvent.
ServerPurchase Type = "track.purchase_server"
// TrackPromotionView / TrackPromotionClick — storefront promotion
// attribution. Producer: the storefront promotion-badge islands, PLP
// promo badges, bundle picker and cart bundle upsell (lib/track.ts
@@ -215,6 +229,32 @@ type WishlistMutationEvent struct {
ItemId uint32 `json:"itemId,omitempty"`
}
// PurchaseItem is one line of a confirmed order in a ServerPurchaseEvent.
// ItemId is the numeric catalog (finder) item id the tracking pipeline keys
// on; lines without one (added without catalog facts) are skipped by the
// producer. Quantity mirrors the order line quantity.
type PurchaseItem struct {
Sku string `json:"sku,omitempty"`
ItemId uint32 `json:"itemId"`
Quantity uint `json:"quantity,omitempty"`
}
// ServerPurchaseEvent is the payload for track.purchase_server (the order
// service's confirmed-order fallback). Deliberately pseudonymous (C5): order
// id + catalog item ids + optional session id only — never the customer
// email/name/profile id, so the tracking pipeline can pair "these items were
// bought together" without being able to attribute the purchase to a person.
// SessionId is the tracking session (sid) captured at checkout start and
// threaded through the checkout→order handoff; when > 0 the tracking
// consumer routes the purchase through the session-keyed path (funnel +
// converted label), when 0 it stays session-less (no consent / server-side
// checkout).
type ServerPurchaseEvent struct {
OrderID string `json:"orderId"`
SessionId int64 `json:"session_id,omitempty"`
Items []PurchaseItem `json:"items"`
}
// CartToWishlistEvent is the payload for cart.to_wishlist (cart service).
// Carries the pseudonymised owner profile id (the cart's UserId — a
// customer-scoped opaque uint64 id, never an email/name) plus the list the