storetown-media / module-surcharge-max

storetown-media/module-surcharge-max

Magento 2 surcharge module with category- and product-level rules, named reusable rules with per-product binding and bulk-assign from the catalog grid, tier and customer-group pricing, conditional caps (min/max/free-threshold) and country-based shipping restriction.

magento2-module Compatibility: 2.4.7-2.4.9 Code Quality: Fail Tests: N/A Security: Pass proprietary

STM Surcharge Max for Magento 2

Rule-based surcharges for Adobe Commerce and Magento Open Source. Adds a
configurable surcharge to the cart and order totals — per category, per product,
per customer group or by quantity tier — with caps, free-shipping thresholds and
country restrictions.

Built and maintained by Storetown Media, an
e-commerce agency from Tornesch near Hamburg, and used in live customer projects.

Features

  • Named, reusable rules. Maintain surcharge rules under Catalog → STM
    Surcharge Rules
    (name, cost, calculation mode, active flag, sort order) and
    bind them to products.
  • Bulk assignment from the product grid. Select products — including
    select-all across pages — and assign a rule in one pass via the
    Assign STM Surcharge Rule mass action.
  • Per-product override. A product-level amount always wins over the rule.
  • Category surcharges. Apply a surcharge to one or more category IDs.
  • Tier pricing. Different surcharge amounts by quantity.
  • Customer group pricing. Different amounts per customer group.
  • Conditions and limits. Minimum amount, maximum cap, free above a cart
    subtotal, and restriction by shipping country.
  • Storefront notice. Optional custom notice on the product page.
  • Multi-language. German and English translations included.

Resolution order

For every quote item the surcharge is resolved in this order:

  1. stm_surcharge_amount — per-product override
  2. Rule cost — from the bound rule
  3. Customer group price
  4. Tier price
  5. Section default

Products bound to a rule are eligible even without a category assignment.

Requirements

  • PHP 8.1 or newer
  • Magento Open Source / Adobe Commerce 2.4.x (magento/framework >= 103.0)

Installation

composer require storetown-media/module-surcharge-max
bin/magento module:enable STM_SurchargeMax
bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento cache:flush

Configuration

Stores → Configuration → STM → Category Surcharge

Section What it does
General Settings Switch the module on or off
Surcharge Configuration Category IDs, cost, label, calculation mode
Tier Pricing Amounts by quantity
Customer Group Pricing Amounts per customer group
Conditions & Limits Minimum, cap, free-above threshold, country restriction

Access is controlled by the ACL resource STM_SurchargeMax::rules with the
sub-permissions rules_save, rules_delete and rules_assign.

Support

License

Proprietary — see LICENSE.txt. Changes are documented in
CHANGELOG.md.

Changelog

Alle nennenswerten Änderungen an diesem Modul werden in dieser Datei dokumentiert.
Das Format basiert auf Keep a Changelog
und das Projekt folgt Semantic Versioning.

[2.4.0] - 2026-04-26

Multi-Rule-Engine. Admin kann benannte Surcharge-Regeln pflegen und mehreren
Produkten per Bulk-Aktion aus dem Produkt-Grid zuordnen. Lean-Scope (Name,
Kosten, Berechnungsmodus, Aktiv-Flag, Sortierung); Conditions/Date-Range
sind für eine spätere Minor reserviert.

Hinzugefügt

  • Neue DB-Tabelle stm_surchargemax_rule (rule_id, name, cost,
    calculation_mode, is_active, sort_order, created_at, updated_at).
    Index (is_active, sort_order) für die Resolver-Lookups.
  • Neues Produkt-Attribut stm_surcharge_rule_id (int, Scope=Store,
    Attribut-Gruppe STM Surcharge Max). Quelle-Klasse
    STM\SurchargeMax\Model\Config\Source\Rule listet aktive Regeln nach
    sort_order / name. Setup-Patch AddRuleAttribute.
  • Resolver-Erweiterung in Model/Total/Quote/Surcharge.php und
    ViewModel/Product/ShippingNotice.php. Neue Priority-Chain pro Item:
    stm_surcharge_amount (per-Produkt-Override) > Rule-Kosten > Customer-Group >
    Staffel > Section-Default. Rule-bound Produkte sind eligible auch ohne
    stm_surcharge_enabled und ohne Kategorie-Zuordnung. Per-Request-Memoization
    des Rule-Lookups.
  • Adminhtml-CRUD für Regeln: Menüpunkt unter Catalog → STM Surcharge Rules, eigener Route-FrontName stm_surchargemax. Listing-Grid mit
    Filter/Sort/Bookmarks und Massenaktionen Delete / Enable / Disable;
    Edit-Formular mit Validation.
  • Mass-Action im Produkt-Grid: "Assign STM Surcharge Rule". Selektion
    wird via Magento\Ui\Component\MassAction\Filter aufgelöst (Select-All
    über Pages funktioniert), Chooser-Page zeigt Anzahl + Rule-Dropdown,
    Magento\Catalog\Model\Product\Action::updateAttributes setzt
    stm_surcharge_rule_id indexfreundlich in einem Pass.
  • ACL-Resourcen: STM_SurchargeMax::rules (Sub-Nodes rules_save,
    rules_delete, rules_assign).
  • Übersetzungen: 60 neue Strings in i18n/de_DE.csv und
    i18n/en_US.csv.

Geändert

  • etc/db_schema_whitelist.json um die neue Tabelle erweitert.
  • etc/di.xml: virtualType STM\SurchargeMax\Model\ResourceModel\Rule\Grid\Collection
    als SearchResult-Datasource für das Listing-UI-Component, plus Mapping in
    Magento\Framework\View\Element\UiComponent\DataProvider\CollectionFactory.
  • Surcharge::collect-Constructor: 6. Parameter
    RuleCollectionFactory $ruleCollectionFactory (war zuvor 5 nach
    License-Removal).

[2.3.0] - 2026-04-26

Erste Adobe-Marketplace-fähige Variante. Lizenzpflichtige Funktionen
(STM_License) wurden entfernt; alle Settings sind ab dieser Version
unter dem zentralen STM-Tab erreichbar.

Umbenannt

  • Modul umbenannt von STM_RohreShipping nach STM_SurchargeMax (finaler
    Marketplace-Name). PHP-Namespace STM\RohreShipping\STM\SurchargeMax\,
    Composer-Package stm/module-rohre-shippingstoretown-media/module-surcharge-max,
    Section-ID stm_rohreshipping/*stm_surchargemax/*,
    ACL-Resource STM_RohreShipping::configSTM_SurchargeMax::config.
  • DB-Spalten stm_rohre_surcharge / base_stm_rohre_surcharge und der
    Total-Code stm_rohre_surcharge bleiben unverändert (Persistence-Stabilität).
  • Setup-Patches MigrateConfigPaths und AddProductAttributes deklarieren
    über getAliases() ihre alten Klassen-Namen, damit bereits angewendete
    Patches nicht erneut laufen.
  • Neuer Setup-Patch RenameSectionPath migriert Konfigurationswerte aus dem
    Interim-Pfad stm_rohreshipping/* (Pre-Rename-Builds) idempotent nach
    stm_surchargemax/*.

Geändert

  • Backend-Konfiguration ist jetzt unter Shop > Konfiguration > STM > Category Surcharge
    zu finden
    (vorher: Sales > Shipping Methods > STM Zuschnittkosten). Die Settings
    sind in fünf logische Gruppen aufgeteilt: General, Surcharge Configuration,
    Tier Pricing, Customer Group Pricing, Product Page Notice — analog zu den
    übrigen STM-Marketplace-Modulen.
  • Section-ID umbenannt von carriers/stmrohreshipping/* nach
    stm_surchargemax/{group}/{field}. Die XPaths in Model/Total/Quote/Surcharge.php,
    ViewModel/Product/ShippingNotice.php und Block/Checkout/SurchargeInit.php
    wurden entsprechend aktualisiert.
  • etc/config.xml — Default-Werte in die neue Section/Group-Struktur verschoben.

Entfernt (Marketplace-Variante)

  • STM_License-Integration vollständig entfernt: Composer-Dependency
    stm/module-license, Modul-Sequence-Eintrag STM_License in module.xml,
    Extension-Registrierung in etc/di.xml sowie Konstruktor-Injektion und
    licenseService->isValid()-Aufruf in Model/Total/Quote/Surcharge.php.
    Die lizenzpflichtige Variante des Moduls wird in einem separaten Branch
    weitergeführt; diese Marketplace-Variante ist nach Adobes Vorgaben frei
    von Drittpartei-Lizenzprüfungen.

Hinzugefügt

  • Produkt-Level-Zuschlag: zwei neue Produkt-Attribute (Scope=Store,
    Attribut-Gruppe STM Surcharge Max auf der Produkt-Edit-Seite):
    • stm_surcharge_enabled (yes/no) — markiert ein einzelnes Produkt als
      zuschlagspflichtig, unabhängig von der Kategorie-Zuordnung. Damit lassen
      sich Zuschläge auf Produkte erheben, die zu keiner Surcharge-Kategorie
      gehören (z.B. ein einzelner Sonderartikel).
    • stm_surcharge_amount (decimal) — überschreibt den Per-Unit-Zuschlag
      für genau dieses Produkt. Hat Priorität vor Customer-Group-, Tier- und
      Default-Preis. Damit lassen sich Premium-Produkte mit höherem Zuschlag
      versehen, während andere Produkte derselben Kategorie den Default zahlen.
  • Eligibility-Logik: Cart-Item ist zuschlagspflichtig wenn (Produkt in
    Surcharge-Kategorie) ODER (stm_surcharge_enabled = yes).
  • Cost-Priorität pro Item: Produkt-Override > Customer-Group-Preis > Tier-Preis > Default.
  • Modus-Semantik mit Overrides:
    • Per item: Summe aller (Per-Unit-Cost × Menge) über beide Buckets (Override + Standard).
    • Per order: Maximum aller anwendbaren Per-Unit-Costs in der Order
      (höchster Zuschlag-Wert gewinnt — semantisch "der teuerste Zuschlag-Artikel
      bestimmt die Order-Surcharge").
  • Setup/Patch/Data/AddProductAttributes.php — Data-Patch der die zwei
    Attribute beim setup:upgrade idempotent anlegt.
  • Conditions & Limits-Gruppe in der Admin-Section (sortOrder 45):
    • min_amount (decimal) — Wenn der berechnete Surcharge unter diesem Betrag
      läge, wird er auf 0 gesetzt (skip). Intent: "kleine Beträge nicht erheben",
      nicht "auf Minimum heben".
    • max_amount (decimal) — Cap für den berechneten Surcharge. Übersteigt der
      Wert das Maximum, wird er auf max_amount gekappt.
    • free_threshold (decimal) — Surcharge entfällt vollständig sobald die
      Cart-Subtotal (netto, vor Steuer) ≥ threshold ist. Klassischer
      "ab X € versandkostenfrei"-Pattern, übertragen auf den Surcharge.
    • restrict_by_country (yes/no) + allowed_countries (multiselect) —
      Surcharge gilt nur, wenn die Versandadresse in einem der ausgewählten
      Länder liegt. Source-Model Magento\Directory\Model\Config\Source\Country.
    • Logik in Surcharge::collect(): Country-Check als Early-Out (vor
      analyzeCart()), Free-Threshold gegen quote->getSubtotal() (Subtotal-
      Collector ist bei totals_sort=65 bereits gelaufen), Min/Max-Caps am Ende
      der Calculation, vor setTotalAmount().
  • Setup/Patch/Data/MigrateConfigPaths.php — Data-Patch, der bestehende
    carriers/stmrohreshipping/*-Konfigurationswerte beim setup:upgrade
    idempotent in die neuen Pfade übernimmt (Default- und Store-Scope).
  • Setup/Uninstall.php — entfernt jetzt sowohl die neuen als auch die
    Alt-Pfade aus core_config_data.

[2.2.0] - 2026-03-09

Hinzugefügt

  • STM License Integration — Lizenzprüfung über stm/module-license Modul
  • di.xml — Extension-Registrierung bei STM\License\Model\ExtensionRegistry (Key: stm_rohre_shipping)

Geändert

  • composer.json — Dependency stm/module-license: ^1.0 hinzugefügt
  • module.xmlSTM_License in Modul-Sequence aufgenommen

[2.1.0] - 2026-03-07

Hinzugefügt

Admin-Backend (Order / Invoice / Creditmemo)
  • Zuschnittkosten-Anzeige im Admin Order View (zwischen Versand und Steuer)
  • Zuschnittkosten-Anzeige im Admin Invoice View und Creditmemo View
  • Zuschnittkosten-Anzeige beim Erstellen neuer Invoices und Creditmemos
  • Adminhtml-Block Block\Adminhtml\Sales\Totals\Surcharge mit initTotals()
  • Layout-XMLs: sales_order_view, sales_order_invoice_view, sales_order_invoice_new,
    sales_order_creditmemo_view, sales_order_creditmemo_new
Datenbank & Persistenz
  • Deklaratives DB-Schema (db_schema.xml) — Spalten stm_rohre_surcharge und
    base_stm_rohre_surcharge auf quote, sales_order, sales_invoice, sales_creditmemo
  • db_schema_whitelist.json für Magento 2.4+ Kompatibilität
  • Fieldset-Mapping (fieldset.xml) für automatische Daten-Übertragung
    Quote→Order (sales_convert_quote) und Order→Invoice/Creditmemo (sales_convert_order)
  • Observer AddSurchargeToOrder auf Event sales_model_service_quote_submit_before als
    Safety-Net für die Quote→Order-Datenübertragung
Invoice & Creditmemo Collectors
  • Model\Total\Invoice\Surcharge — liest Zuschlag von Order, addiert zum Invoice Grand Total
  • Model\Total\Creditmemo\Surcharge — liest Zuschlag von Order, addiert zum Creditmemo Grand Total
  • Registrierung in sales.xml (Sections order_invoice und order_creditmemo, sort_order 399)
E-Mail-Integration
  • Zuschnittkosten in der Bestellbestätigungs-E-Mail (sales_email_order_items.xml)
  • Zuschnittkosten in der Invoice-E-Mail (sales_email_order_invoice_items.xml)
  • Zuschnittkosten in der Creditmemo-E-Mail (sales_email_order_creditmemo_items.xml)
Frontend Kundenbereich
  • Zuschnittkosten in "Meine Bestellungen" → Bestelldetails (sales_order_view.xml)
Warenkorb & Checkout
  • Hyvä-Cart-Totals: Alpine.js-Template mit x-for/x-if für total_segments
    (php-cart/totals/surcharge.phtml)
  • IA24 OnePageCheckout: SurchargeInit-Block liefert JSON-Daten für Client-seitige Berechnung
  • Client-seitige Recalculation bei Versandart-Wechsel (checkout-surcharge.js
    mit MutationObserver)
  • Cart-Surcharge DOM-Injection als Fallback (cart-surcharge.js)
  • SurchargeInit.php erzwingt setTotalsCollectedFlag(false) vor collectTotals()
Steuer
  • Zuschnittkosten-Steuer wird automatisch zum Tax-Total addiert (gleicher MwSt.-Satz
    wie erstes steuerpflichtiges Produkt im Warenkorb)
Qualitätssicherung
  • 43 Unit Tests, 53 Assertions — 100% bestanden
  • PHPCS Magento2-Standard: 0 Errors, 0 Warnings
  • setup:di:compile erfolgreich
  • Testabdeckung: Quote-Total-Collector, Observer, ViewModel, Config-Source-Models

Geändert

  • Model\Total\Quote\Surcharge::collect() setzt Zuschlag nun auch auf $quote->setData()
    (zusätzlich zu $total->setTotalAmount()) für Persistenz und Fieldset-Mapping
  • etc/sales.xml erweitert um Invoice- und Creditmemo-Sections
  • etc/config.xml totals_sort auf 65 angepasst

[2.0.0] - 2025-11-15

Hinzugefügt

Kernfunktionalität
  • Zuschlag-Berechnung: Pro Bestellung (einmalig) oder pro Artikel (Menge × Zuschlag)
  • Schwellenwert-Scope: Nur Nicht-Zuschlag-Produkte oder gesamter Warenkorb
  • Staffelpreise: Mengenbasierte Zuschlag-Staffelung mit dynamischer Admin-Tabelle
  • Kundengruppen-Preise: Individuelle Kosten pro Kundengruppe mit konfigurierbarem
    Zuschlag, Standard-Versand und Free-Shipping-Schwelle
Admin-Konfiguration
  • Vollständiges system.xml mit Validierung auf allen Feldern
  • Deutsche Labels und Hilfe-Kommentare für jedes Konfigurationsfeld
  • Dynamische Admin-Tabellen für Staffelpreise und Kundengruppen-Preise
  • CustomerGroupColumn mit automatischer Gruppenauswahl aus Magento
Produktseite
  • Konfigurierbare Hinweis-Box mit eigenen Farben (Hintergrund, Rahmen, Text)
  • Platzhalter {{label}} und {{cost}} für dynamischen Hinweistext
  • Benutzerdefinierter Hinweistext im Admin konfigurierbar
  • ViewModel Product\ShippingNotice (ersetzt deprecated Registry-Block)
  • Hyvä-kompatibles Template (shipping_notice_hyva.phtml)
Architektur & Code-Qualität
  • PHPUnit Unit Tests für alle Kernkomponenten
  • Setup\Uninstall Interface für saubere Deinstallation (entfernt Config-Daten)
  • di.xml mit CustomerSession Proxy (vermeidet Session-Initialisierung im Konstruktor)
  • ACL-Resource STM_SurchargeMax::config für Admin-Berechtigungen
  • Composer-Installation via storetown-media/module-surcharge-max
  • Mehrsprachigkeit: de_DE.csv und en_US.csv Übersetzungen

Geändert

  • Carrier-Model komplett refactored: declare(strict_types=1), typed Properties
  • Mehrere Kategorie-IDs kommagetrennt konfigurierbar (statt nur eine)
  • Zuschlag-Bezeichnung konfigurierbar (nicht mehr hardcoded "Zuschnittkosten")
  • Free-Shipping-Schwelle bezieht sich standardmäßig auf Nicht-Zuschlag-Produkte
  • Länderbeschränkung nutzt Magento-Konfiguration (specificcountry) statt hardcoded 'DE'
  • Templates: escapeHtml(), ARIA-Attribute, CSS-Klassen statt Inline-Styles

Entfernt

  • Observer mit echo-Ausgabe (AddShippingNotice.php)
  • Deprecated Registry-Block (Block/Product/ShippingNotice.php)
  • Hardcoded Länderbeschränkung auf 'DE'
  • Überflüssige Dateien: registration.xml, events.txt, .html, Cart-Template-Override

Behoben

  • Typo "Zuschitt" → "Zuschnitt" in allen Texten und Übersetzungen
  • Observer-Ordner PSR-4-Verstoß (observer/Observer/)
  • system.xml fehlerhafter Root-Tag
  • PHPCS: Alle Zeilen auf ≤ 120 Zeichen (Multi-Line-Umbrüche in XML)

[1.0.0] - 2024-06-01

Features

  • Kategorie-basierter Versandkosten-Zuschlag für konfigurierbare Produktkategorien
  • Standard-Versandkosten (Flatrate) für alle nicht-zuschlagpflichtigen Produkte
  • Kostenlose Lieferung ab konfigurierbarem Schwellenwert
  • Produktseiten-Hinweis für Zuschlag-Produkte
  • Länderbeschränkung (hardcoded auf Deutschland)
Versions
Version Stability QA Status Compatibility Released
v2.4.1 stable Fail Magento 2.4.7-2.4.9 Details 2026-08-23 10:50:42

Requires 2

Package Constraint
magento/framework >=103.0
php >=8.1

Requires-dev 1

Package Constraint
phpunit/phpunit >=9.5

Compatibility

Each Magento release line is installed on its supported PHP versions, then the module is built (DI compilation + static-content deploy) and its unit and integration suites are run. The matrix shows the lines and PHP versions the module is confirmed to install and run on. Code-quality results further down (phpstan, phpcs, …) are reported separately and never affect compatibility.

Compatibility matrix (Magento × PHP)
Magento PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
2.4.7 Pass Pass
2.4.8 Pass Pass
2.4.9 Pass Pass

Code Quality

Advisory checks against the module's source. Static analysis runs once across the whole module; PHPStan re-runs per Magento + PHP version because resolvable symbols differ between releases. These NEVER affect the Compatibility badge. A phpcs finding can't make a module incompatible.

Static analysis

Coding standards (phpcs), mess detection (phpmd), copy-pasted code (cpd), PHP cross-version compatibility, composer.json validity. Each runs once for the whole module.

Static analysis results
Tool Status Findings Summary
PHPCS Pass 0
PHPMD Warning 47 47 rule violations (UnusedPrivateField:47)
Cpd Pass 0
Composer validate Info 1 valid; 1 advisory note (composer validate --strict)

PHPStan

Type-checks the module's PHP against a real Magento install at the configured gate level. Re-runs per Magento and PHP version because resolvable symbols differ between releases.

PHPStan results by Magento and PHP version
Magento PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
2.4.7 14 14
2.4.8 14 14
2.4.9 13 13

Tests

Unit and integration suites, run for each applicable Magento and PHP version. A test failure speaks to the module's behaviour, not its compatibility with a Magento line, so it is reported here separately and never reddens the compatibility matrix.

Unit tests

Unit tests results by Magento and PHP version
Magento PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
2.4.7 N/A N/A
2.4.8 N/A N/A
2.4.9 N/A N/A

Integration tests

Integration tests results by Magento and PHP version
Magento PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
2.4.7 N/A N/A
2.4.8 N/A N/A
2.4.9 N/A N/A

Security

Security checks run directly against the module: an audit of its declared dependencies for known vulnerabilities (composer audit) and a scan of its source for malware and web-shell signatures. Each runs once. A malware detection fails the version outright.

Security results
Tool Status Findings Summary
Composer audit Pass 0
Malware scan Pass 0
License
proprietary
Homepage
https://www.storetown-media.de
Authors

More from Storetown Media

View vendor
Make it pay

Turn an existing module into recurring revenue.

If you already maintain a Magento 2 module on GitHub or GitLab, listing it on Packagento takes about five minutes. We mirror your tags, handle distribution signing, and route paid licenses through Stripe Connect, so you can keep shipping the way you already do.