// Package recovery is the v0 abandoned-cart-recovery seam. // // The contract is intentionally narrow: a Scanner finds candidates whose last // mutation is within an "abandoned" window and that have BOTH an attachable // contact (email and/or push tokens) and items in the cart, then hands them // to a Notifier. The Notifier decides what to do — for now a logging shim. // // The seam intentionally avoids: // // - Email/push-token delivery (lives in the notifier impl). // - Idempotency tracking ("did we already notify?"). v0: scanner windows // are sized so a cart falls in once per detection threshold. Real // idempotency is the next pass — see suggest_followups / plan-commerce- // maturity C3. // - Cross-pod coordination. Each cart pod owns its own shard of event logs // (replicated read cache, no leader), so per-pod scanning is correct. // A future AMQP fan-out of `cart.recovery.candidate` would let a single // dedicated notifier consume instead — out of scope here. package recovery import ( "context" "git.k6n.net/mats/go-cart-actor/pkg/cart" ) // RecoveryCandidate is the projectable "we'd remind this shopper" shape. // Fields surface just enough for a future email template / push payload; the // underlying grain carries everything else. type RecoveryCandidate struct { CartID cart.CartId UserID string // empty if the cart hasn't been linked to a profile Email string PushTokens []cart.PushToken ItemCount int TotalIncVat int64 // minor units (money.Cents == int64) Currency string // LastChange is best-effort: the scanner reads the event-log file's // modification time. A pending notifier shouldn't use it as the canonical // "when was this cart touched?" wall clock — see cart.LastChange. LastChangeUnix int64 } // Notifier is the seam a real email/push implementation plugs into. The // LoggingNotifier is the v0 default. The cart service ships the seam even // before a real send exists so call-sites and tracking can be wired once. type Notifier interface { NotifyAbandoned(ctx context.Context, c RecoveryCandidate) error }