Spree Loyalty

Gem Version GitHub Release License: MIT

A POS-agnostic points loyalty program for Spree Commerce, an open source e-commerce platform built with Ruby on Rails. Customers earn points on completed orders and redeem them for real Spree::StoreCredit — spendable immediately through the store's existing Store Credit payment method, with no new checkout/payment code required.

Why POS-agnostic?

Most restaurant/retail loyalty platforms (Punchh, Square Loyalty, and similar) issue points as a program tied to one POS or one brand — a point earned under one system generally isn't redeemable under another, even across brands owned by the same company (Yum! Brands keeps Taco Bell Rewards, KFC's program, and Pizza Hut Rewards entirely separate, for example). This extension takes the opposite approach on purpose: loyalty lives entirely in Spree, independent of whatever payment gateway or point-of-sale system a store also happens to use.

What this does

  • Points accrue automatically on order completionSpreeLoyalty::OrderCompletedSubscriber reacts to Spree's own order.completed event; no coupling to any specific payment or POS integration.
  • Only logged-in customers earn points. Guest checkouts don't accrue — loyalty requires an account, the same way most real loyalty programs identify a customer.
  • Points earn on item_total, not the order total — delivery/shipping fees are excluded from the earn calculation by design.
  • Redemption grants real Spree::StoreCredit, not a bespoke discount/coupon mechanism — reuses Spree's own mature, already-checkout-integrated store credit system instead of building a parallel one.
  • A full ledger, not just a running balance — every earn, redemption, and manual adjustment is a SpreeLoyalty::Transaction row, so a balance is always reconstructable and auditable.
  • Admin visibility — read-only Loyalty Accounts and Loyalty Transactions pages, plus a manual points-adjustment action for service recovery (comping or correcting a balance).
  • A small Store API surface for the storefront — GET /api/v3/store/loyalty_account and POST /api/v3/store/loyalty_account/redeem, authenticated the same way any other customer Store API endpoint is.

Installation

  1. Add this extension to your Gemfile with this line:

    bundle add spree_loyalty
    
  2. Run the install generator

    bundle exec rails g spree_loyalty:install
    
  3. Restart your server

If your server was running, restart it so that it can find the assets properly.

Configuration

SpreeLoyalty::Config (a Spree::Preferences::Configuration, settable the usual Spree way — e.g. in a config/initializers/spree.rb block):

Preference Default Meaning
points_per_dollar 1 Points earned per whole dollar of order.item_total.
point_value_cents 1 Cents of store credit granted per point redeemed (default: 100 points = $1.00).
minimum_redemption_points 100 Smallest redemption a customer can make in one request.
SpreeLoyalty::Config.points_per_dollar = 2
SpreeLoyalty::Config.minimum_redemption_points = 50

How redemption reaches checkout

Redeeming points calls Spree::StoreCredit.create!(user:, store:, amount:, category: "Loyalty Rewards", ...) directly — the same model Spree's own gift card system uses. The store's existing Spree::PaymentMethod::StoreCredit payment method (present in any standard Spree install) then picks it up automatically at checkout. No new payment gateway, no new checkout step, no storefront changes beyond a page to let the customer trigger the redemption.

Storefront integration

The two Store API endpoints this extension adds are enough to build a full "My Rewards" account page: fetch the balance/config with a GET, submit a redemption with a POST. See this extension's own reference implementation in the companion spree_storefront_web Next.js app (/account/rewards) for the full pattern — a balance display, a redemption form, and an order-confirmation "You earned N points on this order!" message computed from item_total * points_per_dollar (client-side, using the same math the real accrual runs server-side, since the subscriber that actually credits the account runs asynchronously and may not have finished by the time the confirmation page renders).

Data model

  • SpreeLoyalty::Account — one per [user, store], points_balance and lifetime_points_earned.
  • SpreeLoyalty::Transaction — the ledger. kind is earned, redeemed, or adjusted; source is a polymorphic reference to the originating Spree::Order (earn) or Spree::StoreCredit (redemption), nil for a manual admin adjustment.

Releasing a new version

bundle exec gem bump -p -t
bundle exec gem release

For more options please see gem-release README

Contributing

If you'd like to contribute, please take a look at the instructions for installing dependencies and crafting a good pull request.