goldnead/statamic-offers
Statamic Offers is a Laravel package that adds offer management to Statamic CMS. Editors can create and organize offers in the control panel, while developers can surface them in Antlers templates for promotions, deals, or listings.
type: offer, id = handle, number = seats accepted in the period). Product can now decide what an offer is and where it appears in a funnel without inventing an application-side registry. This is the packaging layer: naming, tiers, launch offers, and which offer a given funnel step points at.Basket::firstPaymentCent() (the v1.15.0 capability) remains the single source of truth for the checkout figure across trials, coupons, setup fees, and bumps. The funnel/offer layer decides what is presented; firstPaymentCent() decides what is charged today. Keep these two concerns separate in roadmap discussions — conflating them is how display/booking drift returns.statamic-brand-context ^1.17. That is a real constraint on your CMS/upgrade calendar and couples content-driven marketing pages to the billing surface. Worth an explicit decision, not an incidental one, especially if you run Statamic for landing pages and campaign microsites.netCent() semantics remain untouched; both the v1.15.0 payment figure and this release's offer mapping are additive. Finance-facing reporting keeps its existing meaning while checkout display and funnel composition gain dedicated, auditable surfaces.Adopt (or keep) it when:
statamic-brand-context ^1.17) and want content-managed funnel/offer pages backed by the same model as billing.Look elsewhere or proceed with caution when:
Adoption checklist: pin the version; upgrade statamic-brand-context to ^1.17 in the same change window and smoke-test content pages; add a regression test asserting firstPaymentCent() equals the amount recorded by Subscriptions::start()/Checkout::start() across trial, coupon, setup-fee, and bump combinations; add a second test that each funnel-mapped offer handle resolves and reports the expected seat count; document the trial-vs-coupon precedence and the definition of "period" for seat acceptance in your pricing policy; and flag any funnel step whose offer can be exhausted, so the depleted-state UX is designed rather than discovered.
To executives: Two releases land the same theme — making money-facing surfaces consistent with the system that actually bills. v1.15.0 closed a checkout-integrity gap: customers were shown a "due today" figure that differed from what was charged, a direct driver of support load, refunds, and lost trust; Basket::firstPaymentCent() makes the displayed amount the same calculation the billing engine uses across paid trials, coupons, setup fees, and bumps, and it is additive with no migration. v1.18.0 extends the same principle to what we sell and where: offers become real, referenceable objects in the funnel map, so marketing can define an offer — including a seat cap — once and point any funnel step at it. Practically, that means faster campaign setup, no divergent offer definitions across landing pages, and a configurable lever for limited-seat launches. Two costs to note: the release hard-requires statamic-brand-context ^1.17, so it is coupled to our CMS upgrade calendar; and seat counts should be treated as capacity intent until engineering confirms enforcement under concurrent checkout.
To engineering: v1.18.0 adds offers to the funnels map as addressable objects — type: offer, id = handle, number = seats accepted in the period — and takes a hard dependency on statamic-brand-context ^1.17, so schedule the upgrade with the CMS change rather than after it. This is additive to the v1.15.0 payment work: Basket::firstPaymentCent() still mirrors the charging path of Subscriptions::start() and Checkout::start() (a paid trial replaces the main line price and consumes the payment's single discount slot, so coupons do not stack; otherwise basket minus coupon, plus setup fee and ticked bumps), and netCent() semantics are unchanged. Actions: adopt firstPaymentCent() for every "due today" surface (checkout, cart summary, confirmation emails) and delete locally reimplemented totals; add a parity test against the booked amount — the motivating staging case was 65 EUR displayed vs. 45 EUR booked, and 37 EUR vs. 27 EUR with a 10 EUR coupon; resolve offer handles through the funnels map instead of hard-coding offer data; add a test per funnel-mapped offer handle and seat count; and verify what "accepted in the period" means in code and whether concurrent checkouts can exceed number. Flag any product policy that expects coupons to stack with trials, and any funnel step with a finite seat count that lacks a designed depleted state, before rollout.
How can I help you explore Laravel packages today?