Ga naar inhoud

Loyaliteit (X-Coins)

De webshop kent een loyaliteitsprogramma waarbij professionele klanten punten ("X-Coins") sparen op hun bestellingen. Het programma steunt op twee lagen:

  1. Interne registratie (src/Atraxion/Loyalty/) — een lokale database met een toestandsmachine die elke verdiende portie punten bewaakt (pendingconfirmedpaid of forfeited).
  2. Externe provider Arteel (src/Arteel/) — de REST-API-client van Arteel, het externe beloningsportaal waar de klant zijn punten uiteindelijk inwisselt.

Het programma is niet gebonden aan een verkoopkanaal: het is een eigenschap van de klant en zijn bestellingen, niet van een channel. In de mini-shop-flow worden geen punten toegekend (de mini-shop-modus blokkeert dat). De toegankelijkheid is per land (BE/NL/LU) en per klant (via tags en regels) bepaald — zie Scoping hieronder.

Toegankelijkheid (eligibility)

Context::showXCoins() bepaalt of X-Coins voor de huidige bezoeker actief zijn. Dat is alleen het geval als aan alle voorwaarden is voldaan:

  • de gebruiker is ingelogd en niet in mini-shop-modus;
  • het is niet de inwisselperiode (zie hieronder);
  • Login::hasOptedInToLoyaltyProgram() is waar;
  • de gebruiker is niet uitgesloten (tag Geen X-Coins of een match op een uitsluitingsregel);
  • de guard-permissie x-coins is voldaan.

showXCoinsMenu() gebruikt daarnaast isEligibleForLoyaltyProgram() (land BE/NL/LU + tag Wel X-Coins), zodat in aanmerking komende klanten die nog niet zijn opgemeld, het menu-item wel zien en zich kunnen aanmelden.

Inwisselperiode

LoyaltyProgram (src/Atraxion/Loyalty/LoyaltyProgram.php) kent een jaarlijkse inwisselperiode. Tijdens die periode geeft isRedemptionPeriod() waar terug (LoyaltyProgram.php:37) en stopt het sparen: klanten besteden dan hun saldo in plaats van bij te verdienen.

Aanmelden (opt-in)

De opt-in loopt via LoyaltyProgramController (_core/config/pages.xml:78) op de pagina loyalty-program, met als URL's /getrouwheidsprogramma (NL), /programme-de-fidelite (FR) en /loyalty-program (EN). De string x-coins in de codebase is een Guard-permissiesleutel, geen route:

  1. een uitgesloten of afgemelde gebruiker wordt teruggestuurd naar /;
  2. ?forceOptOut=true meldt de gebruiker direct af;
  3. een POST met loyaltyPointsOptIn=1 valideert de gegevens en roept optIn() aan;
  4. bij opt-in wordt een PUT naar Arteel gestuurd om het gebruikersprofiel aan te maken/bij te werken, en wordt de e-mailtemplate business-owner-x-coins verstuurd (zie Notificaties & e-mails).

Op de klant (customer_user) leggen de velden loyaltyOptDate en loyaltyOptStatus (opt-in/opt-out) de aanmeldstatus vast; loyaltyPoints cachet het Arteel-saldo.

Punten verdienen

Punten worden bepaald door regels in customer_loyalty_points_rules, gemodelleerd als LoyaltyPointsRule (src/Atraxion/Loyalty/Entity/LoyaltyPointsRule.php). Een regel heeft een rule_type (cart, article of brand), een amount_type (percentage of fixed), een geldigheidsperiode, een minimaal winkelwagenbedrag en koppelingen naar artikelen, merken, banden-/velgtypes en voertuigcategorieën. De percentageformule is round((percentage/100) × prijs × 200) — elke euro kortingswaarde levert 200 punten op.

De berekening gebeurt in LoyaltyPointsCalculator (src/Atraxion/Loyalty/Service/LoyaltyPointsCalculator.php):

  • calculateByCartArticles() telt de artikel- en merkregels per orderregel op;
  • calculateByCartTotal() past de eerste passende winkelwagenregel toe op het in aanmerking komende totaal;
  • daarnaast kan een coupon van het type loyalty extra punten toekennen.

In de checkout (CartController rond regel 1616) worden bij een opgemelde gebruiker de punten berekend, en na het opslaan van de order via attributeLoyaltyPoints() per bron een LoyaltyPoints-record aangemaakt in status pending.

Bij een gesplitste bestelling (OrderSplitter) krijgt alleen de eerste order de winkelwagentotaal- en couponpunten; alle deelorders krijgen wel hun artikelpunten. attributeLoyaltyPoints() keert direct terug zodra showXCoins() onwaar is — dit voorkomt dat punten worden toegekend aan niet-opgemelde gebruikers.

Punten bevestigen en uitbetalen

De toestandsmachine van LoyaltyPoints (src/Atraxion/Loyalty/Entity/LoyaltyPoints.php) loopt pendingconfirmedpaid (of forfeited vanuit elke niet-eindstatus). Het netto saldo is max(points − penaltyPoints, 0).

  • Bevestigen (confirmByOrder()) brengt punten van pending naar confirmed. Dit gebeurt na een geslaagde betaling (doFinishOrder() / CheckPaymentStatus::checkPaymentStatus(), zie Betalingen) en bij het binnenkomen van de eerste Odoo-factuur via SynchroniseOrders.
  • Uitbetalen (payout(), src/Atraxion/Loyalty/Service/LoyaltyPointsService.php:100) brengt confirmed-punten naar paid en duwt ze via ArteelCustomerLoyaltyService::addPoints() als delta naar Arteel.

De opt-in-controle bij uitbetaling

payout() controleert expliciet of de gebruiker is opgemeld (LoyaltyPointsService.php:110). Is dat niet zo, dan worden de punten niet uitbetaald maar verbeurd (forfeit()): een niet-opgemelde gebruiker heeft geen Arteel-profiel om punten te ontvangen, dus een push zou eindeloos op een 404 blijven hangen. Dit is de correctie voor de eerder opgetreden situatie waarbij een gesplitste checkout punten toekende aan niet-opgemelde gebruikers.

Arteel-API

De Arteel-client (src/Arteel/Service/ArteelClient.php) gebruikt OAuth2 (client_credentials) met een gecachet token. Belangrijke endpoints:

Doel Aanroep
Gebruiker upserten PUT /api/users
Punten toevoegen (delta) POST /api/users/{id}/points (projectId=368, occasionId=382)
Saldo opvragen GET /api/users/{id}/points
Alle saldo's GET /api/users/points
Portaal-SSO JWT-getekende URL (getSingleSignOnUrl())

De customerInternalId waaronder een klant bij Arteel bekend staat is doorgaans de OdooId, tenzij overschreven via het JSON-veld arteelUserOverride op customer_user. Arteel-gebruikers worden ingedeeld in landengroepen (BE→409, NL→422, LU→423; in non-prod 123). De ArteelCustomerLoyaltyService werkt het profiel ook bij vanuit de Odoo-synchronisatie (SynchronisePartners).

CLI-commando's

Commando Beschrijving
atx:loyalty:payout-confirmed-points Duwt alle confirmed-punten naar Arteel (productie-cron)
atx:loyalty:void-orphaned-points [--force] Verbeurt (dry-run of geforceerd) pending/confirmed-punten van niet-opgemelde gebruikers
atx:loyalty:add-missing-points [--dry-run] [--force] Verzoent het lokale paid-totaal met het Arteel-saldo en vult het verschil aan
atx:loyalty:debug-user <email> Inspecteert/herstelt de punten van één gebruiker
arteel:bulk-create-users <ids...> Pusht specifieke Login-ID's naar Arteel
arteel:find-user <needle> Zoekt een gebruiker in de bulk-punten-export
arteel:reset-users Zet alle Arteel-punten op 0

Scoping

  • Per land: alleen BE, NL en LU komen in aanmerking (isEligibleForLoyaltyProgram()).
  • Per klant: regels koppelen many-to-many naar klanten, klant-tags en klantcategorieën; uitsluiting via de tag Geen X-Coins of een uitsluitingsregel (LoyaltyPointsExclusionRule).
  • Niet per channel: loyaliteit is niet gebonden aan een verkoopkanaal, maar de mini-shop-modus verhindert puntentoekenning in de mini-shop-flow.

Zie ook