Markus Michalski FAQ

Extended Voucher Rules: Technical Details

Technical details of Extended Voucher Rules: module ID, validation order, reservation mechanism, extended shop classes, services, template extension and database tables.

Deutsche Version

Back to overview

Extended Voucher Rules: Technical Details

Live Demo: Try this and other plugins live — no installation, no risk: demo.markus-michalski.net

Module at a glance

Property Value
Composer package mmd/oxid7-voucherrules
Module ID mmd_voucherrules
Namespace Mmd\VoucherRules (PSR-4, directory src/)
Target directory mmd/voucherrules
Metadata version 2.1
Events onActivate creates the tables, onDeactivate is intentionally empty

Validation order

The central check lives in VoucherValidationService::validate(). It is called from the extended Voucher class after OXID's standard check has passed. The rules run in this order, and the first exception thrown ends the check:

  1. Payment restriction
  2. Delivery restriction
  3. First order
  4. Time window
  5. Combinations (maximum, then block list)
  6. Minimum order value per payment method
  7. Minimum order value per delivery method

Consequence of the order: as long as a payment or delivery rule is still pending, the rules after it (for example the time window) are not evaluated yet.

graph TD
    A[VoucherValidationService.validate] --> B[Payment restriction]
    B --> C[Delivery restriction]
    C --> D[First order]
    D --> E[Time window]
    E --> F[Combinations]
    F --> G[Min order value payment]
    G --> H[Min order value delivery]
    H --> I[All rules passed]
    B -->|payment missing or not matching| P[VoucherRulePendingException]
    C -->|delivery missing or not matching| P
    G -->|payment missing or value not reached| P
    H -->|delivery missing or value not reached| P
    D -->|not a first order| V[VoucherRuleViolationException]
    E -->|outside the window| V
    F -->|blocked or maximum| V

Behaviour per rule

Rule Condition Result
Payment Restriction present, no payment method in the session reserved (hint "payment pending")
Payment Restriction present, payment method not allowed reserved (hint "payment not matching")
Delivery analogous to payment reserved
First order Option set and the user has non-cancelled orders (OXSTORNO = 0, same shop); without a user ID nothing is checked rejected
Time window Active windows exist, none matches weekday and time of day rejected
Combination Maximum > 0 and number of distinct series of the other applied vouchers (excluding the voucher itself, reserved ones not counted) >= maximum, or a blocked pair with an applied voucher rejected
Min order value Entries exist, no payment/delivery method selected reserved
Min order value Entry for the selected payment/delivery method, basket value lower reserved (hint with amount)

The weekday is determined with date('N'), the time of day with the database's CURTIME(). The window check is CURTIME() BETWEEN STARTTIME AND ENDTIME, so a window entered as HH:MM ends at second 00.

Reservation mechanism

A voucher with an open condition raises a VoucherRulePendingException. The class Extension\Model\Voucher catches it, remembers the voucher number and stores a hint. The exception is re-thrown so that OXID applies no discount. A VoucherRuleViolationException removes the reservation and rejects the voucher. Both exceptions extend OXID's VoucherException.

Data is stored in the session under two keys: mmd_voucherrules_pending (list of voucher numbers) and mmd_voucherrules_hints (hints with key and minimum value). The session values paymentid and sShipSet are read.

graph TD
    A[Customer selects payment or delivery method] --> B[Basket.calculateBasket]
    B --> C{Reservations in the session?}
    C -->|No| D[Normal calculation]
    C -->|Yes| E[addVoucher per reserved number]
    E --> F{Result}
    F -->|passed| G[Voucher active, reservation removed]
    F -->|still open| H[stays reserved]
    F -->|permanently invalid| I[Reservation removed]

Auto-activation only runs if a payment or delivery method is set in the session. After Order::finalizeOrder(), reservations, hints as well as paymentid and sShipSet are deleted. With an empty basket (Basket::calculateBasket()), only reservations and hints are deleted.

Extended shop classes

OXID class Module class Purpose
Voucher Extension\Model\Voucher Extended check in checkVoucherAvailability() and checkBasketVoucherAvailability(), reservation
VoucherSerie Extension\Model\VoucherSerie Access to the first-order flag and the maximum
Basket Extension\Model\Basket Auto-activation and hints in calculateBasket(), hides red error banners for reservations
BasketController Extension\Controller\BasketController addVoucher() without error banner for a reservation, action removePendingVoucher
ViewConfig Extension\Core\ViewConfig getVoucherRulesHints(), getPendingVouchers(), hasPendingVouchers() for templates
Order Extension\Model\Order Cleanup in finalizeOrder()

Admin controllers

Controller ID Class Tab ID in menu.xml
mmd_voucherrules_main VoucherRulesMain tbclvoucherserie_rules_main
mmd_voucherrules_timewindow VoucherRulesTimeWindow tbclvoucherserie_rules_timewindow
mmd_voucherrules_combination VoucherRulesCombination tbclvoucherserie_rules_combination
mmd_voucherrules_payment_minval VoucherRulesPaymentMinVal tbclvoucherserie_rules_payment_minval

The tabs are attached to the menu item mxvouchers (main menu mxshopsett). All writing actions check the session challenge; deleting and changing additionally verify the series and shop of the entry.

Services and extensibility

The services and repositories are marked public in the Symfony container (services.yaml) and can be fetched through the ContainerFactory: VoucherValidationService, FirstOrderCheckService, TimeWindowCheckService, CombinationCheckService, VoucherRestrictionHintService as well as the repositories VoucherRulesConfigRepository, PaymentRestrictionRepository, DeliveryRestrictionRepository, TimeWindowRepository, CombinationRepository, PaymentMinValueRepository and DeliveryMinValueRepository. There is no documented extension API and no events.

Template extension

For the Apex theme the module ships two template extensions:

For other themes the same pattern can be implemented in the theme's checkout summary.

Database tables

Seven tables with the prefix mmd_voucherrules_, all with OXSHOPID; the voucher series is stored in OXVOUCHERSERIEID:

Table Purpose Key columns
mmd_voucherrules_config Settings per series (unique per series and shop) FIRSTORDERONLY, MAXCOMBINED
mmd_voucherrules_payment Allowed payment methods OXPAYMENTID
mmd_voucherrules_delivery Allowed delivery methods OXDELIVERYSETID
mmd_voucherrules_timewindow Time windows WEEKDAYS (bitmask bit 0 = Monday to bit 6 = Sunday, 127 = all), STARTTIME, ENDTIME, ACTIVE
mmd_voucherrules_combination Blocked pairs, series IDs stored in sorted order OXVOUCHERSERIEID_A, OXVOUCHERSERIEID_B
mmd_voucherrules_payment_minval Minimum order value per payment method OXPAYMENTID, MINORDERVALUE
mmd_voucherrules_delivery_minval Minimum order value per delivery method OXDELIVERYSETID, MINORDERVALUE

The tables are created on activation with CREATE TABLE IF NOT EXISTS. Data is kept on deactivation.