PHPackages                             abderrahimghazali/sylius-loyalty-plugin - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. abderrahimghazali/sylius-loyalty-plugin

ActiveSylius-plugin[Utility &amp; Helpers](/categories/utility)

abderrahimghazali/sylius-loyalty-plugin
=======================================

Points-based loyalty and rewards system for Sylius 2.x

v2.0.0(2mo ago)248↓90%MITPHPPHP ^8.2CI passing

Since Apr 20Pushed 2mo agoCompare

[ Source](https://github.com/abderrahimghazali/sylius-loyalty-plugin)[ Packagist](https://packagist.org/packages/abderrahimghazali/sylius-loyalty-plugin)[ Docs](https://github.com/abderrahimghazali/sylius-loyalty-plugin)[ RSS](/packages/abderrahimghazali-sylius-loyalty-plugin/feed)WikiDiscussions main Synced 3w ago

READMEChangelog (1)Dependencies (8)Versions (2)Used By (0)

 [    ![Sylius Logo](https://camo.githubusercontent.com/ea9dddc934264aa7ec01cf3202c500f3d8b04448bce2571bdc74230efddda88f/68747470733a2f2f6d656469612e73796c6975732e636f6d2f73796c6975732d6c6f676f2d3830302e706e67)  ](https://sylius.com)

Sylius Loyalty Plugin
=====================

[](#sylius-loyalty-plugin)

 A points-based loyalty and rewards system for [Sylius 2.x](https://sylius.com) e-commerce stores.

 [![CI](https://github.com/abderrahimghazali/sylius-loyalty-plugin/actions/workflows/ci.yaml/badge.svg)](https://github.com/abderrahimghazali/sylius-loyalty-plugin/actions/workflows/ci.yaml) [![Latest Version](https://camo.githubusercontent.com/0091ef6655c451227de66647cd7d36d4a9261c82438f35172b26fb58dfd96eb5/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6162646572726168696d6768617a616c692f73796c6975732d6c6f79616c74792d706c7567696e2e737667)](https://packagist.org/packages/abderrahimghazali/sylius-loyalty-plugin) [![PHP Version](https://camo.githubusercontent.com/ca0f2dd312ec89b2712bd39ad448d43ff78513b347c9869db08d6cc5fdf0aa23/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f6162646572726168696d6768617a616c692f73796c6975732d6c6f79616c74792d706c7567696e2e737667)](https://packagist.org/packages/abderrahimghazali/sylius-loyalty-plugin) [![License](https://camo.githubusercontent.com/7013272bd27ece47364536a221edb554cd69683b68a46fc0ee96881174c4214c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c75652e737667)](LICENSE) [![Sylius 2.x](https://camo.githubusercontent.com/47390b9b61d6f22d062322df61d2d225ceaf33f7bb75b99592c7ba766049f3e5/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f73796c6975732d322e782d677265656e2e737667)](https://packagist.org/packages/abderrahimghazali/sylius-loyalty-plugin) [![Symfony 7.x](https://camo.githubusercontent.com/fe777aec93f146d83db2752ebd66562c8d4d1be48fa4a1477c51c0dbab74e381/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f73796d666f6e792d372e782d626c61636b2e737667)](https://packagist.org/packages/abderrahimghazali/sylius-loyalty-plugin) [![PHPStan Level 5](https://camo.githubusercontent.com/0729e562e10fac943b16dbb271b4af26488f779a33fc82cc3eef1e37a432c0b4/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048505374616e2d6c6576656c253230352d627269676874677265656e2e737667)](https://img.shields.io/badge/PHPStan-level%205-brightgreen)

---

Overview
--------

[](#overview)

SyliusLoyaltyPlugin adds a complete loyalty program to any Sylius 2.x store. Customers earn points on purchases, redeem them as discounts on the cart page, unlock tier-based multipliers, and receive bonus points for registration, birthdays, and first orders — all configurable per channel from the admin panel.

### Key Features

[](#key-features)

- **Multi-channel** — Each channel has independent earning rates, redemption rates, expiry, and bonus settings
- **Loyalty rules** — Override earning rates for specific products with per-rule points configuration and multi-channel support
- **Points earning** — Configurable default points per currency unit on every order
- **Cart redemption** — Spend points as a monetary discount on the cart page
- **Points expiry** — Automatic expiration with cron command + 30-day warnings
- **Bonus events** — Registration, birthday, and first-order bonuses (toggle on/off per channel)
- **Tier system** — Bronze / Silver / Gold with earning multipliers (tiers only go up)
- **Admin panel** — Full management: accounts, transactions, manual adjustments, per-channel config
- **Customer account** — Points balance, tier badge, paginated transaction history with running balance
- **REST API** — Headless-ready endpoints for balance and redemption
- **Workflow integration** — Points deducted on order complete, restored on cancel/refund
- **Translations** — English, French, German, Spanish, Polish, Portuguese

Requirements
------------

[](#requirements)

DependencyVersionPHP^8.2Sylius~2.1Symfony^7.0Installation
------------

[](#installation)

```
composer require abderrahimghazali/sylius-loyalty-plugin
```

### 1. Register the plugin

[](#1-register-the-plugin)

```
// config/bundles.php
return [
    // ...
    Abderrahim\SyliusLoyaltyPlugin\SyliusLoyaltyPlugin::class => ['all' => true],
];
```

### 2. Import routes

[](#2-import-routes)

```
# config/routes/sylius_loyalty.yaml
sylius_loyalty:
    resource: '@SyliusLoyaltyPlugin/config/routes.yaml'
```

### 3. Extend your Order entity

[](#3-extend-your-order-entity)

Add the loyalty trait to your Order entity so customers can redeem points on the cart page:

```
// src/Entity/Order/Order.php
namespace App\Entity\Order;

use Abderrahim\SyliusLoyaltyPlugin\Entity\Order\LoyaltyOrderInterface;
use Abderrahim\SyliusLoyaltyPlugin\Entity\Order\LoyaltyOrderTrait;
use Sylius\Component\Core\Model\Order as BaseOrder;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
#[ORM\Table(name: 'sylius_order')]
class Order extends BaseOrder implements LoyaltyOrderInterface
{
    use LoyaltyOrderTrait;

    // ...existing code...
}
```

### 4. Register the Stimulus controller (for the cart widget)

[](#4-register-the-stimulus-controller-for-the-cart-widget)

Add the plugin JS dependency to your `package.json`:

```
{
    "dependencies": {
        "@abderrahimghazali/sylius-loyalty-plugin": "file:vendor/abderrahimghazali/sylius-loyalty-plugin/assets"
    }
}
```

Register the controller in `assets/shop/controllers.json`:

```
{
    "controllers": {
        "@abderrahimghazali/sylius-loyalty-plugin": {
            "loyalty-redemption": {
                "enabled": true,
                "fetch": "eager"
            }
        }
    }
}
```

Then rebuild assets:

```
npm install
npm run build
```

### 5. Run migrations

[](#5-run-migrations)

```
php bin/console doctrine:migrations:diff
php bin/console doctrine:migrations:migrate
```

### 6. Seed the default configuration

[](#6-seed-the-default-configuration)

```
php bin/console loyalty:install
```

This creates a default loyalty configuration for each channel. You can then customize settings per channel from the admin panel under **Configuration &gt; Loyalty configuration**.

### 7. Set up cron jobs

[](#7-set-up-cron-jobs)

```
# Expire old points (run daily)
php bin/console loyalty:expire-points

# Award birthday bonuses (run daily)
php bin/console loyalty:birthday-bonus
```

Architecture
------------

[](#architecture)

### Domain Model

[](#domain-model)

```
Channel ──1:1──▶ LoyaltyConfiguration
                    (earning rate, redemption rate, expiry, bonuses)

Channel ──N:M──▶ LoyaltyRule ──N:M──▶ Product
                    (name, enabled, points rate per product)

Customer ──1:1──▶ LoyaltyAccount ──1:N──▶ PointTransaction
                        │                    (earn/redeem/expire/adjust/bonus)
                        │
                        └───N:1──▶ LoyaltyTier
                                   (Bronze/Silver/Gold)

```

Points are **shared across channels** (one account per customer), while earning/redemption rates are **configured per channel**.

### Entities

[](#entities)

EntityPurpose`LoyaltyAccount`Per-customer account with balance, lifetime points, tier`PointTransaction`Ledger entry — signed points, type, optional order link, expiry`LoyaltyTier`Tier with min-points threshold, earning multiplier, color`LoyaltyConfiguration`Per-channel config: earning rate, redemption rate, expiry, bonuses`LoyaltyRule`Per-product earning rate override, multi-channel### Sylius Integration Points

[](#sylius-integration-points)

Extension PointWhat It Does`OrderProcessorInterface` (priority 5)Applies loyalty discount adjustment after taxes`sylius.order.post_complete` eventAwards earn points + first-order bonus on order completion`sylius.order.post_cancel` eventRevokes earned points`sylius.customer.post_register` eventAwards registration bonus`workflow.sylius_order_checkout.completed.complete`Deducts redeemed points from balance`workflow.sylius_order.completed.cancel`Restores redeemed points`workflow.sylius_payment.completed.refund`Restores redeemed points on refund`sylius.menu.admin.main` eventAdds menu items under Customers, Marketing &amp; ConfigurationTwig hooksCart widget, cart/checkout summary, customer show section, account menuMulti-Channel Support
---------------------

[](#multi-channel-support)

Each Sylius channel can have its own loyalty configuration:

SettingUS StoreEU StoreB2B StoreEarning rate1 pt / $12 pts / €1DisabledRedemption rate100 pts = $150 pts = €1N/ARegistration bonus100 pts200 pts0Birthday bonus200 pts500 pts0Tiers enabledYesYesNoPoints are shared across channels — a customer earns on one store and redeems on another. Rates are applied based on the order's channel.

Manage per-channel settings at **Configuration &gt; Loyalty configuration** in the admin panel.

Shop Features
-------------

[](#shop-features)

### Cart Redemption Widget

[](#cart-redemption-widget)

On the cart page, logged-in customers can redeem points as a discount. The widget uses Symfony UX Live Components for seamless interaction without page reloads.

- Input field with placeholder showing available balance
- "Apply points" button (triggers Live Component re-render)
- Applied state shows a badge with points count, discount value, and a remove button
- Loyalty discount line appears in the cart summary and throughout the checkout sidebar
- Automatic clamping: can't exceed balance or order total

### Customer Account — Loyalty Page

[](#customer-account--loyalty-page)

Accessible from the account sidebar menu:

- Current balance + redeemable monetary value
- Tier badge with multiplier info
- Expiry warning for points expiring within 30 days
- Paginated transaction history table with running balance column

Admin Features
--------------

[](#admin-features)

### Loyalty Accounts

[](#loyalty-accounts)

Grid view of all customer loyalty accounts with balance, lifetime points, tier, and status. Click through to a detail page showing paginated transaction history.

### Manual Point Adjustment

[](#manual-point-adjustment)

From any loyalty account detail page, admins can add or deduct points with a required reason field. Positive values create an `Adjust` (credit) transaction, negative values create a `Deduct` (debit) transaction.

### Tier Management

[](#tier-management)

Full CRUD for loyalty tiers under **Marketing &gt; Loyalty tiers**. Code and position are auto-generated from the tier name.

FieldDescriptionNameDisplay name (e.g., "Bronze") — code is auto-generatedMin PointsLifetime points threshold to reach this tierMultiplierEarning multiplier (e.g., 1.5x for Silver)ColorBadge color (hex, rendered in admin and shop)### Per-Channel Configuration

[](#per-channel-configuration)

Under **Configuration &gt; Loyalty configuration**, admins see a table of all channels and can configure each independently:

- Points per currency unit
- Redemption rate (points per 1 currency unit)
- Expiry period in days
- Enable/disable tier system
- Toggle and configure bonus events (registration, birthday, first order)

Settings are stored in the database and take effect immediately without redeployment.

### Loyalty Rules

[](#loyalty-rules)

Under **Marketing &gt; Loyalty rules**, admins can override the default earning rate for specific products. Each rule has:

- **Name** — descriptive label for the rule
- **Products** — select one or more products via autocomplete
- **Points per currency unit** — custom earning rate (set to 0 to exclude products from earning)
- **Channels** — select which channels the rule applies to
- **Enabled** — toggle the rule on/off

When a product in an order matches a loyalty rule for that channel, the rule's rate is used instead of the channel's default rate.

**Example rules:**

RuleProductsRateChannelsDouble on gift setsGift Set A, Gift Set B2 pts/€1AllNo points on gift cardsGift Card0 pts/€1AllPremium bonusLimited Edition Watch10 pts/€1Fashion Web StoreAPI Endpoints (Headless)
------------------------

[](#api-endpoints-headless)

For headless/SPA storefronts, the plugin provides REST endpoints. All shop endpoints verify the authenticated user owns the order.

### Shop API

[](#shop-api)

```
POST   /api/v2/shop/orders/{tokenValue}/loyalty-redemption
       Body: { "pointsToRedeem": 500 }
       → { "pointsRedeemed": 500, "discountAmount": 500, "orderTotal": 9500 }

DELETE /api/v2/shop/orders/{tokenValue}/loyalty-redemption
       → { "pointsRedeemed": 0, "discountAmount": 0, "orderTotal": 10000 }

GET    /api/v2/shop/loyalty/account
       → Balance, lifetime points, tier info

```

### Admin API

[](#admin-api)

```
GET    /api/v2/admin/loyalty/accounts
GET    /api/v2/admin/loyalty/accounts/{id}
PATCH  /api/v2/admin/loyalty/accounts/{id}

```

Edge Cases Handled
------------------

[](#edge-cases-handled)

- Pessimistic DB locking prevents double-spend on concurrent checkouts, cancellations, and refunds
- API endpoints require `ROLE_USER` and verify the authenticated user owns the order
- Balance cannot go negative (clamped in service layer + guard in entity)
- Points redemption cannot exceed available balance (clamped, validated in cart form and API)
- Discount cannot exceed order total (capped, points recalculated)
- Guest checkouts cannot use loyalty points (guarded)
- Disabled accounts are excluded from earning and redemption
- Duplicate point awards are prevented (idempotent per order)
- Duplicate revocations are prevented (idempotent per order)
- First-order bonus awarded only once (idempotent)
- Points are reserved at cart, deducted only on order completion
- Cancelled/refunded orders restore redeemed points (idempotent, locked)
- Birthday bonus awarded at most once per calendar year per channel
- Tiers only upgrade, never demote (based on lifetime points)
- CSRF protection on all admin forms
- Admin point adjustment descriptions are sanitized
- Expire points command uses batched processing for large datasets

Translations
------------

[](#translations)

The plugin ships with translations for:

LanguageFileEnglish`messages.en.yaml`French`messages.fr.yaml`German`messages.de.yaml`Spanish`messages.es.yaml`Polish`messages.pl.yaml`Portuguese`messages.pt.yaml`Running Tests
-------------

[](#running-tests)

```
composer install
vendor/bin/phpunit
```

71 unit tests covering entities, services, order processing, and all event listeners.

Screenshots
-----------

[](#screenshots)

**Cart — Redeem points**

[![Cart redemption widget](docs/images/cart-redemption.png)](docs/images/cart-redemption.png)

**Customer Account — Loyalty points &amp; transaction history**

[![Customer loyalty account](docs/images/customer-loyalty-account.png)](docs/images/customer-loyalty-account.png)

**Admin — Loyalty tiers management**

[![Admin loyalty tiers](docs/images/admin-loyalty-tiers.png)](docs/images/admin-loyalty-tiers.png)

License
-------

[](#license)

This plugin is released under the [MIT License](LICENSE).

###  Health Score

40

—

FairBetter than 86% of packages

Maintenance86

Actively maintained with recent releases

Popularity11

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity46

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

67d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/e1be9cf8e271987c350c614aa618f0e68c7f7409634e51f8c8fe105490899b0f?d=identicon)[abderrahim.ghazali](/maintainers/abderrahim.ghazali)

---

Top Contributors

[![abderrahimghazali](https://avatars.githubusercontent.com/u/12643883?v=4)](https://github.com/abderrahimghazali "abderrahimghazali (111 commits)")

---

Tags

ecommerceloyaltyphpsyliussylius-pluginsymfonysyliusecommercerewardssylius-pluginpointsloyalty

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/abderrahimghazali-sylius-loyalty-plugin/health.svg)

```
[![Health](https://phpackages.com/badges/abderrahimghazali-sylius-loyalty-plugin/health.svg)](https://phpackages.com/packages/abderrahimghazali-sylius-loyalty-plugin)
```

###  Alternatives

[easycorp/easyadmin-bundle

Admin generator for Symfony applications

4.3k17.5M376](/packages/easycorp-easyadmin-bundle)[sylius/sylius

E-Commerce platform for PHP, based on Symfony framework.

8.5k5.8M712](/packages/sylius-sylius)[rcsofttech/audit-trail-bundle

Enterprise-grade, high-performance Symfony audit trail bundle. Automatically track Doctrine entity changes with split-phase architecture, multiple transports (HTTP, Queue, Doctrine), and sensitive data masking.

1155.2k](/packages/rcsofttech-audit-trail-bundle)[forumify/forumify-platform

122.0k12](/packages/forumify-forumify-platform)[2lenet/crudit-bundle

The easy like Crud'it Bundle.

1615.6k12](/packages/2lenet-crudit-bundle)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
