Ga naar inhoud

Kortingen & coupons

Kortingen lopen in deze shop via een couponsysteem. Een coupon is een code die de klant in de winkelwagen invoert; bij een geldige code wordt een beloning (reward) toegepast. Het systeem staat volledig onder src/Atraxion/Coupon/ (moderne Doctrine/DI-laag) en kent vier beloningstypes: een bedrag- of percentage-korting, een gratis artikel, gratis montage en loyaliteitspunten.

Datamodel

De centrale entiteit is Coupon (src/Atraxion/Coupon/Entity/Coupon.php), met de code (varchar) als primaire sleutel. Een coupon heeft één beloning. De beloningen zijn gemodelleerd met Single Table Inheritance in de tabel coupon_rewards; de discriminator staat in src/Atraxion/Coupon/Resources/doctrine/Reward.AbstractReward.orm.xml:7-12:

Discriminator Klasse Betekenis
discount Entity/Reward/Discount.php Vast bedrag of percentage korting op het winkelwagentotaal
gift Entity/Reward/Gift.php Gratis artikel toegevoegd aan de winkelwagen
free_assembly Entity/Reward/FreeAssembly.php Gratis montage op één set wielen (montageprijs op 0)
loyalty Entity/Reward/LoyaltyPoints.php Kent X-Coins / loyaliteitspunten toe

De korting kent twee subtypes (Entity/Reward/Discount.php:7-8): TYPE_FIXED (fixed) en TYPE_PERCENTAGE (percentage), met een amount-veld. Gift heeft een artikel-FK, een quantity en een vlag trackStock; LoyaltyPoints een puntenaantal. Beloningen hebben meertalige omschrijvingen via coupon_reward_translations (enkelvoud/meervoud per taal, Entity/Reward/RewardTranslation.php).

De belangrijkste tabellen:

Tabel Inhoud
coupons de coupon zelf (code, geldigheid, limieten, FK naar beloning)
coupon_rewards de beloning (STI)
coupon_reward_translations meertalige beloningsteksten
coupon_used gebruikslog: code + klant + order (Entity/CouponSpent.php)

Op de orders-tabel staan daarnaast discount (het toegepaste bedrag) en CouponId (FK naar coupons.code, ON DELETE SET NULL).

De coupon coupons bevat onder meer: startsAt/endsAt, active, limitedToOwnedStock, usesPerCustomer (NULL = onbeperkt), minimumOrderAmount, vehicleCategories (simple_array) en audit-velden createdBy/createdAt/updatedAt.

Validatie bij de checkout

De code wordt toegepast via CartController::applyCouponIfPresent() (_core/controller/classes/atx/customers/cart/CartController.php:484). De code komt uit de POST-parameter coupon_code of uit $_SESSION['coupon_code'] (CartController.php:486). De flow:

flowchart TD
    A[coupon_code uit POST of sessie] --> B[CouponRepository::findByCode]
    B --> C[CouponService::validate → CouponValidator]
    C -->|fouten| X[code uit sessie verwijderen, melding tonen]
    C -->|geldig| D[CouponService::apply → RewardHandler]
    D --> E[beloning op de winkelwagen toegepast]

CouponService (src/Atraxion/Coupon/CouponService.php) biedt validate(), isValid(), apply() en undo(). De CouponValidator (src/Atraxion/Coupon/Validator/CouponValidator.php) controleert onder meer:

  • isActive() en of de huidige datum binnen startsAt/endsAt valt (CouponValidator.php:26);
  • minimumOrderAmountvolledig winkelwagentotaal (artikelen + transportkosten + recyclingkosten + betaalkosten − korting, via Cart::getTotalCost(), CouponValidator.php:31–32);
  • limitedToOwnedStock → de winkelwagen moet minstens één Atraxion-eigen artikel bevatten;
  • merk-, artikel-, banden-/velg-/accessoiretype-, voertuigcategorie-, klant-tag-, specifieke-klant- en klantcategorie-restricties;
  • usesPerCustomer (geteld uit coupon_used);
  • voor gift: Atraxion-voorraad > 0;
  • voor loyalty: de klant moet zijn opgemeld voor het loyaliteitsprogramma; is dat niet zo, dan wordt hij automatisch opgemeld via customerLoyaltyService->optIn() (CouponValidator.php:115-117).

Beloningen toepassen

CouponService::apply() delegeert naar de juiste handler (src/Atraxion/Coupon/Handler/):

  • Korting (DiscountHandler): bij een vast bedrag wordt Cart::setDiscount(amount) gezet (mits kleiner dan het totaal); bij een percentage totalArticles × amount / 100. OrderBuilder::addDiscount() kopieert cart->getDiscount() naar de order en trekt het van het ordertotaal af.
  • Gratis artikel (GiftHandler): maakt een CouponGift aan via Cart::setCouponGift(). Het cadeau-artikel wordt nooit als orderregel aangemaakt. Bij het aanmaken van de order wordt uitsluitend de couponcode ('Code ' . $coupon->getCode()) als interne notitie aan de order toegevoegd (CartController.php:1613). Het cadeau is dus niet zichtbaar als orderregel in de order of ERP-export — alleen de code-notitie verwijst ernaar.
  • Gratis montage (FreeAssemblyHandler): itereert de voertuigsets, stopt na de eerste (break) en zet setAssemblyUnitPrice(0) op zowel getFrontWheelSet() als getRearWheelSet() van die set — dus op twee WheelSetLineItems (voor- én achteras), niet slechts één (FreeAssemblyHandler.php:31–47).
  • Loyaliteitspunten (LoyaltyPointsHandler): zet Cart::setLoyaltyPoints(amount); bij het opslaan van de order worden deze als bron coupon aan de loyaliteitspunten toegevoegd in status pending (LoyaltyPoints.php:24). Ze worden op dat moment niet onmiddellijk uitbetaald: payoutByOrder() verwerkt alleen confirmed-punten, maar de coupon-punten zijn op dat ogenblik nog pending. Ze volgen de normale toestandsmachine: pendingconfirmed (na betaling via CheckPaymentStatus.php:117) → paid (via de dagelijkse cron atx:loyalty:payout-confirmed-points).

Bij het persisteren van de order (CartController.php:1647) wordt het gebruik geregistreerd via $coupon->addUse($newOrder) in coupon_used.

Scoping

Er is geen per-channel-scoping: coupons gelden over alle channels heen. Alle beperkingen lopen via optionele join-tabellen op coupons:

Beperking Join-tabel Sleutel
Merken coupon_limits_brands BrandId
Klant-tags coupon_limits_tags tag_id
Specifieke klanten coupon_limits_customers Klantnr
Klantcategorieën coupon_limit_customer_categories Klantcatcode
Bandtypes coupon_limit_tyre_types TypeId
Velgtypes coupon_limit_wheel_types Typenr
Specifieke artikelen coupon_limit_articles Artnr
Voertuigcategorieën kolom coupons.vehicleCategories (simple_array)

De scoping is dus per merk / artikel / type / voertuigcategorie en per klant / klant-tag / klantcategorie — niet per verkoopkanaal.

Beheer in de backoffice

Coupons worden beheerd in de backoffice via _admin/controller/classes/atx/AdminCouponsController.php en de bijbehorende view _admin/view/classes/atx/AdminCouponsView.php. Functies: een filterbare lijst, inline aan/uit zetten per coupon, een aanmaak-/bewerkformulier met alle scoping-opties, en een XLSX-export van alle coupongegevens.

Zie ook