Comment implémenter un système de points de récompense personnalisé dans Magento 2
How to Implement a Custom Reward Points System in Magento 2
If you’re building or upgrading a Magento 2 store and want to reward your clients for loyalty, a custom reward points system is a smart way to keep buyers coming back. Think of it as a lightweight loyalty program that you tailor to your entreprise rules. This post walks you through the practical étapes to design, implement, and operate a custom reward points system inside Magento 2. We’ll cover architecture, modèle de donnéess, commande integration, an admin tableau de bord, and sécurité measures to prevent abuse. The goal is to give you a clear blueprint you can adapt quickly—with real code samples you can copy, tweak, and ship.
Why build a custom reward points system in Magento 2?
- Full control: You’re not limited by an off-the-shelf extension’s roadmap or tarification model. Vous pouvez tailor the experience to your store, products, and client segments.
- Cost predictability: Tandis que there’s initial development cost, you avoid ongoing per-seat or per-transaction fees from tiers extensions.
- Seamless integration: A custom system can share the same modèle de données as your commandes, clients, and products, reducing data fragmentation.
Dans ce guide, nous’ll assume you’re comfortable with Magento 2 module development and SQL. If you’re newer to Magento 2, you can still follow the concepts and adapt the code to your level of expertise. We’ll keep exemples concrete and include étape-by-étape instructions and code you can drop into a local or staging environment for test.
Architecture aperçu: the schéma de base de données for loyalty points
At the core, you need to track a few things clearly: the client’s current balance, individual accrual actions, and redemptions. A clean modèle de données helps with rapporting and fraud prevention. Here’s a pragmatic, extensible schema you can start from. It uses a mix of balances, transactions, and rule definitions so you can evolve without rewriting the wheel.
-- Basic balance per customer (multi-website support could be added later).
CREATE TABLE magefine_reward_balance (
balance_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
customer_id BIGINT UNSIGNED NOT NULL,
website_id SMALLINT UNSIGNED DEFAULT 0,
points_balance INT NOT NULL DEFAULT 0,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (balance_id),
KEY idx_customer (customer_id),
KEY idx_website (website_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Points transactions (accruals, redemptions, expirations, adjustments)
CREATE TABLE magefine_reward_transactions (
transaction_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
balance_id BIGINT UNSIGNED NOT NULL,
customer_id BIGINT UNSIGNED NOT NULL,
order_id BIGINT UNSIGNED NULL,
points_change INT NOT NULL,
action VARCHAR(64) NOT NULL, -- e.g., 'ORDER_ACCRUAL', 'REDEMPTION', 'MANUAL_ADJUST'
notes TEXT NULL,
created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (transaction_id),
KEY idx_balance (balance_id),
KEY idx_order (order_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Rule definitions for accrual and redemption logic
CREATE TABLE magefine_reward_rules (
rule_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
rule_type ENUM('ACCRUAL','REDEMPTION') NOT NULL,
name VARCHAR(128) NOT NULL,
points INT NOT NULL,
active TINYINT(1) NOT NULL DEFAULT 1,
condition_json TEXT NULL, -- JSON to describe conditions (e.g., product_id, category, cart value)
expires_after_days INT NULL,
created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (rule_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Admin-friendly summary view (optional but helpful for reporting)
CREATE TABLE magefine_reward_summaries (
summary_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
customer_id BIGINT UNSIGNED NOT NULL,
total_accrued INT NOT NULL DEFAULT 0,
total_spent INT NOT NULL DEFAULT 0,
last_update TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (summary_id),
KEY idx_customer (customer_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Notes on the schema:
- The balance table is intentionally lean. Si vous have mulconseille websites, you can add a website_id colonne to scope balances per site.
- Transactions capture every change in points and tie it back to a client and optionally an commande. This makes audits straightforward and enables robust rapporting.
- Rules keep the logic editable from the panneau d'administration. They peut être simple (e.g., 1 point per $1) or complex (e.g., +50 points on first purchase over $100, only for new clients).
In a declarative schema setup (Magento 2.3+), you could define some of these tables with db_schema.xml, but you can also bootstrap the core structure with a data correctif if you’re not using declarative schemes for all tables yet. The important part is that you provide a clear, versioned path to evolve the schema without manual SQL déploiements in production.
Module skeleton: scaffolding a Magento 2 module
Let’s lay out a minimal Magento 2 module to house the reward points logic. You’ll want to place this in app/code/Magefine/RewardPoints (assuming you’re building this locally). Below are the essential fichiers and contenus. Copy-paste these as a starting point, then adapt to your needs.
// app/code/Magefine/RewardPoints/registration.php
<?php
use Magento\Framework\Component\ComponentRegistrar;
ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magefine_RewardPoints', __DIR__);
?>
// app/code/Magefine/RewardPoints/etc/module.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://schemas.magento.com/module.xsd">
<module name="Magefine_RewardPoints" setup_version="1.0.0">
<sequence>
<module name="Magento_Framework"/>
</sequence>
</module>
</config>
That’s the bare minimum to enable a Magento 2 module. From here, you’ll extend fonctionality with observateurs, models, and UI composants. The next sections show comment wire the entreprise logic for accrual, redemption, and admin management.
Defining the acquisition and redemption rules: a practical approche
Rules are the heart of a flexible loyalty program. You don’t want hard-coded numbers scattered across code. Instead, store rules in the database and expose them through a small API inside your module. Here’s a practical, scalable approche:
- Store two rule types: ACCRUAL (earning points) and REDEMPTION (spending points).
- Use a JSON condition champ to describe rule applicability (e.g., product category, cart subtotal, groupe de clients).
- Provide a simple UI in the Magento Admin to enable/disable rules and adjust valeurs.
Example: a simple accrual rule could be: "Earn 1 point per $1 spent, up to a max of 100 points per commande, for standard clients." The corresponding SQL-encoded JSON condition could be {"minOrderValue":100,"allowedCustomerGroups":["GENERAL","VIP"]}.
Code sample: a lightweight rule evaluation service
This code snippet shows a simple PHP class that evaluates accrual rules for an commande. It’s intentionally straightforward so you can extend it later with more complex conditions and caching.
// app/code/Magefine/RewardPoints/Model/RuleEngine.php
namespace Magefine\RewardPoints\Model;
class RuleEngine
{
protected $rules;
public function __construct($rules)
{
// $rules is an array loaded from magefine_reward_rules
$this->rules = $rules;
}
public function calculateAccrual($order)
{
$orderValue = (float)$order->getSubtotal();
$customerGroup = $order->getCustomerGroupId();
$points = 0;
foreach ($this->rules as $rule) {
if ($rule['rule_type'] !== 'ACCRUAL' || !$rule['active']) {
continue;
}
// Simple numeric condition checks
$cond = json_decode($rule['condition_json'] ?? '{}', true);
if (isset($cond['minOrderValue']) && $orderValue < $cond['minOrderValue']) {
continue;
}
if (isset($cond['allowedCustomerGroups']) && !in_array($customerGroup, $cond['allowedCustomerGroups'])) {
continue;
}
$points += $rule['points'];
}
// You can add caps, or multipliers here
return max(0, (int)$points);
}
}
?>
In a real implémentation, you’d fetch rules from the database, possibly using a repository or a data correctif to seed initial rules. The important part is that the engine is isolated, easy to test, and pluggable from a service layer.
Step-by-étape: enabling accrual after commande completion
A straightforward way to credit points is to listen to the commande finalization event and then push a transaction to the balance. Below is a simplified exemple using an observateur. This demonstrates the flow without getting bogged down in injection de dépendances boilerplate. Vous pouvez adapt to your project’s code style and DI patterns.
// app/code/Magefine/RewardPoints/Observer/AwardPointsAfterOrder.php
namespace Magefine\RewardPoints\Observer;
use Magento\Framework\Event\ObserverInterface;
use Magento\Sales\Model\Order;
class AwardPointsAfterOrder implements ObserverInterface
{
protected $_balanceRepo;
protected $_txnRepo;
protected $_ruleEngine;
public function __construct(
\Magefine\RewardPoints\Api\BalanceRepositoryInterface $balanceRepo,
\Magefine\RewardPoints\Api\TransactionRepositoryInterface $txnRepo,
\Magefine\RewardPoints\Model\RuleEngine $ruleEngine
) {
$this->_balanceRepo = $balanceRepo;
$this->_txnRepo = $txnRepo;
$this->_ruleEngine = $ruleEngine;
}
public function execute(\Magento\Framework\Event\Observer $observer)
{
/** @var Order $order */
$order = $observer->getEvent()->getOrder();
if (!$order || !$order->getId()) {
return;
}
// Basic safeguards: only award for completed orders
if ($order->getState() !== \Magento\Sales\Model\Order::STATE_COMPLETE) {
return;
}
// Load or create balance for customer
$customerId = (int)$order->getCustomerId();
$balance = $this->_balanceRepo->loadByCustomer($customerId);
$pointsToAdd = $this->_ruleEngine->calculateAccrual($order);
if ($pointsToAdd <= 0) {
return;
}
// Create a transaction; update balance atomically in a real impl
$this->_txnRepo->credit($balance->getBalanceId(), $pointsToAdd, [
'order_id' => $order->getId(),
'notes' => 'Accrual for order #' . $order->getIncrementId()
]);
}
}
?>
Register this observateur in etc/events.xml to listen for the commande completion event. The exact event name can vary by Magento version, but a common choice is sales_commande_place_after or sales_commande_facture_pay. The point is: ensure accrual happens after the commande is fully validated and recorded.
Integnote with the paiement: spending points at paiement
Customers devrait être able to spend their points to reduce the cart total. Implementing a custom total or using a règle de remise is one way to do this. Here’s a compact exemple showing the idea of applying points during quote total collection. C'est a simplified illustration that you can extend with real calculations, currency precision, and client permissions.
// app/code/Magefine/RewardPoints/Model/Checkout/Total/PointsDiscount.php
namespace Magefine\RewardPoints\Model\Checkout\Total;
class PointsDiscount extends \Magento\Quote\Model\Quote\TotalsCollector
{
public function collect(\Magento\Quote\Model\Quote $quote, \Magento\Quote\Model\Quote\Address $address, \Magento\Quote\Model\Quote\ItemCollection $items)
{
// Pseudo-logic: read the points the customer has and the max discount they can use
$pointsAvailable = 100; // replace with real balance lookup
$cartTotal = $address->getSubtotal();
$pointsToSpend = min($pointsAvailable, (int)floor($cartTotal));
// Apply discount by modifying address totals
$discount = $pointsToSpend; // 1 point = $1 in this example
$address->setDiscountAmount(-$discount);
$address->setBaseDiscountAmount(-$discount);
// Persist the redemption to a transaction log in a real implementation
return $this;
}
}
Note: Magento 2 total collectors require proper wiring via di.xml and a totals.xml declaration. This snippet focuses on the conceptual approche; you’ll need to adapt to your module’s architecture and ensure currency precision, calcul de taxe, and coupon compatibility are correct for your store.
Admin tableau de bord: a simple interface to manage points, rules, and redemptions
A utilisateur-friendly panneau d'administration is essential. You’ll typically build UI composants that let admins do les éléments suivants without touching the database manually:
- Create and toggle accrual/redemption rules (ACCRUAL/REDEMPTION).
- Set points valeurs, expiration, and applicable conditions (cart subtotal, groupe de clients, product categories).
- Manually add or deduct points for a client (for refunds, goodwill, or corrections).
- Monitor a client’s balance and transaction history for audits.
Here’s a minimal UI flow to illustrate how you might wire a UI Component grid for rules using Magento 2 UI composants. This exemple shows the XML structure and a brief PHP back-office glue. In a real system you’d build full CRUD (Create, Read, Update, Delete) with proper repositories and data providers.
// app/code/Magefine/RewardPoints/view/adminhtml/ui_component/reward_rules_listing.xml
<?xml version="1.0" encoding="UTF-8" ?>
< uiConfig xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_class.xsd">
<dataSource >
<dataSourceName>reward_rules_data_source</dataSourceName>
<dataProvider>RewardRulesDataProvider</dataProvider>
</dataSource>
<settings>
<spinner visibility="visible" />
</settings>
</uiConfig>
Below is a simplified PHP snippet for a repository fetch that your grid might rely on. In a full implémentation, you’d wire up repositories, recherche criteria, and a UI data provider to serve JSON to the grid.
// app/code/Magefine/RewardPoints/Api/RuleRepositoryInterface.php
namespace Magefine\RewardPoints\Api;
interface RuleRepositoryInterface
{
public function getList();
public function getById($ruleId);
public function save(array $data);
public function delete($ruleId);
}
On the admin side, you’ll expose the champs via UI composants: rule name, type, points, active flag, and condition JSON. The clé is to keep the UI equitable: make it clear what each rule does and show pavis of the impact on a sample commande.
Security and fraud prevention: validations and safeguards
Loyalty systems are tempting for abuse. A few practical safeguards help keep the system honest without annoying your clients:
- Order-based validation: Only award points after the commande reaches a completed state. Don’t credit for pending commandes or commandes canceled during paiement.
- Per-commande caps: Limit the maximum points that peut être earned per commande to prevent analysts from exploiting large discounts.
- Fraud checks: Basic validations like IP/region matching, unusual spikes in points, and cross-checking with transactions to avoid duplicate accruals.
- Time-based controls: Apply expiration days consistently. Expire points after a set period and warn utilisateurs before expiration.
- Auditing: Every balance change is logged as a transaction with a reason. In case of disputes, the admin can avis logs quickly.
Voici a simple exemple of a fraud guard in the accrual engine. It prevents awarding points if the commande valeur is suspiciously low relative to the utilisateur’s typical spend, or if the same commande triggers mulconseille accruals in a short time window.
// Pseudo-code inside Magefine_RewardPoints/Model/RuleEngine.php
public function isSuspiciousOrder($order) {
// Basic heuristic: high velocity of accruals from the same customer
$recentTransactions = $this->txnRepo->countRecentByCustomer($order->getCustomerId(), 24 * 60);
if ($recentTransactions > 3) {
return true;
}
// Compare order total to historical averages (simplified)
$avg = $this->getCustomerAverageOrderValue($order->getCustomerId());
if ($order->getSubtotal() < $avg * 0.1) {
return true;
}
return false;
}
Trust but verify. In production, you’d want a modular, auditable compliance layer with rate limits, a separate fraud service, and clear rapporting in the panneau d'administration. Vous pouvez also integrate with your existing sécurité stack (SAML SSO, MFA for admin actions, access controls, etc.).
Testing and déploiement: a practical checklist
Avant you push to production, validate les éléments suivants:
- Schema is versioned and déployerable via data correctifs or declarative schema without destructive changes.
- Rule engine loads rules from the DB and applies them deterministically to each commande.
- Accrual and redemption transactions are logged and readable in admin tableau de bords.
- Checkout flow supports point redemption with accurate currency handling for tax and discounts.
- Security controls are in place: per-commande caps, fraud validations, and audit trails.
- Performance: test with thousands of commandes in staging to ensure the engine scales gracefully.
Recommendations for test: write test unitaires for the rule engine, test d'intégrations for commande completion events, and UI tests for the admin tableau de bord. Include data correctiftures to replicate typical client journeys. A robust test suite saves you time when you scale.
Best practices and considerations for MageFine clients
- Keep the reward rules simple at first. Start with one or two rules and a conservative cap. Add complexity as you gain confidence.
- Make the admin console friendly. A few well-placed stats, a compact transaction history, and a quick create/edit flux de travail will reduce friction for store admins.
- Document your rules. A concise internal wiki or comment in the codebase will help your future self and teammates understand why a rule exists and comment adjust it.
- Think about seasonal campaigns. Vous pouvez reuse the same rule engine with a dynamic JSON condition to run promotions without new code déploiements.
- Ensure performance. Keep balance lookups cached for active clients and load only necessary data into memory during accrual computation.
Deployment plan and migration notes
When you’re ready to déployer to production, follow a careful migration plan:
- Back up the database and enable a fonctionnalité flag to disable accrual during migration.
- Apply schema changes with a data correctif or declarative schema approche. Verify integrity with checksums.
- Deploy the module code to a test environment first, then to staging before production.
- Perform end-to-end tests of commande placement, accrual, and redemption flows using the admin UI and client-facing paiement.
- Enable monitoring and alerting for any anomalies in points balances or transactions.
Résumé and next étapes
Building a custom reward points system in Magento 2 vous donne precise control over how client loyalty works, how points are earned and spent, and how fraud is prevented. The architecture outlined here provides a solid foundation: a lean balance table, a detailed transactions log, and a rules engine that can gligne with your entreprise. Start small, validate every piece, and iterate. If you’re planning a Magento 2 loyalty fonctionnalité for a gligneing store, a well-structured, tested implémentation will save you time and reduce risk in the long run.
Appendix: quick reference for readers
- Database foundations: magefine_reward_balance, magefine_reward_transactions, magefine_reward_rules, magefine_reward_summaries.
- Key events: commande completion for accrual, paiement total calculation for redemption.
- Admin tooling: UI composants and repositories to manage rules and avis balances.
If you’re considering a ready-made route, MageFine also offers Magento hosting and extensions that align with loyalty and reward management. Use this guide as a blueprint to tailor a solution that fits your exact entreprise rules and client journeys.