accelerando.wiki ↗ app ↗ github

Discounts, Segments & Abandonment Nudges

The last slice of the ecommerce arc. Where the audit log stops being a defensive tool and starts driving revenue.

Every prior slice let the merchant *see* their business honestly. This slice lets them *act* on it — coupon codes to close price-sensitive customers, segments to send targeted offers, and the killer view: abandonment nudges that recover carts most storefronts write off as lost.

Four entities, seventeen tools, four views. Small surface. Big lever.


The three parts

Discount codes

Three kinds — percentage, fixed, free_shipping. Every field a Stripe-style coupon has: min-subtotal gate, start/end dates, per-code usage cap. Codes are tenant-unique + uppercased on save. Deactivating a code preserves its redemption history for accounting; it just stops accepting new uses.

The killer detail: redemptions are audit-logged. DiscountRedemption records the cart or order, the customer, the amount taken off, and the exact code. Marketing can prove attribution to the accountant, and the accountant can prove the discount was applied at the customer's request (not a merchant-side fudge).

Customer segments

Six built-in rule kinds — all_customers, repeat_buyer, first_time, high_value, lapsed, cart_abandoner. A segment is a *named query*, not a stored membership list. That's the audit-log-thesis at work: membership derives from customer state, so a lapsed customer who orders again automatically leaves the "Lapsed" segment. No manual sync to forget.

Params tune the threshold — {"min_orders": 3} for repeat_buyer, {"days_since_last_order": 60} for lapsed. The member_count field is a denormalized cache refreshed on-demand via refresh_segment_member_count.

Abandonment nudges

The recovery workflow. scan_abandonment_candidates finds open carts idle >24 hours (default) with no nudge in the last 72 hours (default). The merchant selects candidates, optionally attaches a discount code (usually a higher-percentage one reserved for recovery), and fires send_nudge. Each nudge is a record; the record IS the send.

State machine: sent → clicked → recovered, with expired as the terminal timeout path. When the customer completes an order after clicking, mark_nudge_recovered stamps order.from_nudge_id — revenue attribution flows from the abandoned cart to the recovered order via one field.


The killer dashboard signal

The marketing dashboard surfaces one signal that most ecom platforms bury:

Abandonment candidates: N

That's how many carts, right now, are sitting open waiting to be reclaimed. Not "estimated recoverable revenue" — the actual carts, with actual customers, with actual subtotals. One click to scan, one click to send. The dashboard also surfaces recovered revenue over the last 30 days — closed-loop attribution back to nudged carts, so the merchant can see *this workflow paid for itself*.


Named refuses

Every gate refuses with a named rule:

| Attempt | Refuse | |---|---| | Create a percentage discount with value 150 | percentage_value_out_of_range | | Create a duplicate code | discount_code_already_exists | | Apply a code to a cart that isn't open | cart_not_open | | Apply an expired code | discount_invalid:expired | | Apply below min_subtotal | discount_invalid:min_subtotal_not_met | | Apply a code past its usage cap | discount_invalid:usage_cap_reached | | Send a nudge on a converted cart | nudge_requires_open_cart | | Skip clicked and mark expired twice | nudge_transition_illegal:expired→expired |

Every refuse writes a rejection event to the audit log. Merchants see exactly which rule fired.


Idempotency at every seam


The full ecommerce arc

Slice O closes the arc. What was built:

| Slice | Entity | Killer view | |---|---|---| | K | Product / ProductVariant / InventoryAdjustment | Adjustment reasons — the primary defense against shrinkage | | L | Cart / CartLine / EcomOrder / EcomOrderLine | Snapshot-not-reference on OrderLines | | M | StripeConfig / PaymentEvent | Idempotent-by-event-id webhook handler | | N | Shipment / ReturnAuthorization | RMA workflow — inventory-restore gated on resellable | | O | Discount / DiscountRedemption / CustomerSegment / AbandonmentNudge | Nudge funnel + revenue-recovery attribution |

Every entity is a .agi file. Every write is a git commit. Every refuse has a name. Every transition has a rule. The complete customer journey — cart placement → payment → fulfillment → return or reorder → segment change → discount redemption → nudged recovery — is one filesystem walk away.

That's the point. That's the whole point.


What's NOT in this slice