diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000000000000000000000000000000000000..9ab59b180fb670006ac910a2bc4524a1376dcea0
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,15 @@
+
+# Ignore developer files when exporting
+.gitattributes         export-ignore
+.gitignore             export-ignore
+.gitlab-ci.yml         export-ignore
+.phive                 export-ignore
+captainhook.json       export-ignore
+phpcs.xml.dist         export-ignore
+phpstan-baseline.neon  export-ignore
+phpstan.dist.neon      export-ignore
+phpunit.dist.xml       export-ignore
+tests                  export-ignore
+
+# Explicitly set file type and line endings for PHP files, improves git diff output
+*.php     text eol=lf diff=php
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index f4da20891502a3992abf2981d8f6c46e33868ab6..e25391c1f85364060ac958b37e8e35c6c51beafe 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,11 @@
 tools/
 phpstan.neon
 .phpunit.result.cache
-phpunit.xml
\ No newline at end of file
+phpunit.xml
+tools/
+
+phpstan.neon
+
+.phpunit.result.cache
+
+phpunit.xml
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b8d20acb6c365ad8036a7b36d47dabd876d08efa..b5a64b401e554341447c74d7cf93a89ac95a3fdb 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,3 +1,17 @@
 include:
-  - project: 'quiqqer/stabilization/semantic-release'
-    file: '/ci-templates/.gitlab-ci.yml'
+  - component: dev.quiqqer.com/quiqqer/stabilization/ci-cd-components/quiqqer-package-bundle/quiqqer-package-bundle@main
+
+# Remove the entire phpunit-php8.1 block, to allow PHPUnit to run on PHP 8.1 in your pipeline
+phpunit-php8.1:
+  rules:
+    - when: never
+
+# Remove the entire phpunit-php8.2 block, to allow PHPUnit to run on PHP 8.2 in your pipeline
+phpunit-php8.2:
+  rules:
+    - when: never
+
+# Remove the entire phpunit-php8.3 block, to allow PHPUnit to run on PHP 8.3 in your pipeline
+phpunit-php8.3:
+  rules:
+    - when: never
\ No newline at end of file
diff --git a/.phive/phars.xml b/.phive/phars.xml
index a1315a09b4adad780a9c5e52f74835c708c5c7d5..5bfa092bfad10dad9d23240281a5a2041acb815b 100644
--- a/.phive/phars.xml
+++ b/.phive/phars.xml
@@ -1,4 +1,8 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <phive xmlns="https://phar.io/phive">
-  <phar name="phpstan" version="^1.10.67" installed="1.10.67" location="./tools/phpstan" copy="false"/>
+  <phar name="phpstan" version="1.11.8" installed="1.11.8" location="./tools/phpstan" copy="false"/>
+  <phar name="phpunit" version="^10.5.20" installed="10.5.20" location="./tools/phpunit" copy="false"/>
+  <phar name="phpcs" version="^3.10.1" installed="3.10.1" location="./tools/phpcs" copy="false"/>
+  <phar name="phpcbf" version="^3.10.1" installed="3.10.1" location="./tools/phpcbf" copy="false"/>
+  <phar name="captainhook" version="^5.23.3" installed="5.23.3" location="./tools/captainhook" copy="false"/>
 </phive>
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000000000000000000000000000000000000..4a69a59b440e5beec561eca1e341509bd5a18688
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,3 @@
+# Contributing
+
+This package follows the [QUIQQER contribution guidelines](https://dev.quiqqer.com/quiqqer/stabilization/documentation/-/wikis/home).
\ No newline at end of file
diff --git a/bin/frontend/controls/CouponCodeInput.css b/bin/frontend/controls/CouponCodeInput.css
index f88583bf327d4ea95860ec1f8d75685875c44635..cf95da74bce32e5ee81a6bcd8f46648d4cec707c 100644
--- a/bin/frontend/controls/CouponCodeInput.css
+++ b/bin/frontend/controls/CouponCodeInput.css
@@ -29,3 +29,26 @@ div + .quiqqer-coupons-field {
 .quiqqer-coupons-remove .fa {
     margin-right: 0;
 }
+
+/**
+ * Simple Checkout
+ */
+
+.quiqqer-coupons-field-simpleCheckout {
+    margin-top: 20px;
+    margin-left: 0 !important;
+}
+
+.quiqqer-coupons-field-simpleCheckout .quiqqer-coupons-couponcodeinput {
+    background: var(--_sectionBgColor);
+    display: flex;
+    padding: var(--_sectionPadding);
+}
+
+.quiqqer-coupons-field-simpleCheckout .quiqqer-coupons-couponcodeinput label {
+    flex-grow: 1;
+}
+
+.quiqqer-coupons-field-simpleCheckout .quiqqer-coupons-couponcodeinput-input {
+    width: calc(100% - 1rem);
+}
\ No newline at end of file
diff --git a/bin/frontend/controls/CouponCodeInput.js b/bin/frontend/controls/CouponCodeInput.js
index 7bd7b9b5bc358b327e17d32807a5e658cbdf1e11..d24f612bc509cd9148015d714acce8a3d3f6c53f 100644
--- a/bin/frontend/controls/CouponCodeInput.js
+++ b/bin/frontend/controls/CouponCodeInput.js
@@ -20,25 +20,25 @@ define('package/quiqqer/coupons/bin/frontend/controls/CouponCodeInput', [
     'text!package/quiqqer/coupons/bin/frontend/controls/CouponCodeInput.html',
     'css!package/quiqqer/coupons/bin/frontend/controls/CouponCodeInput.css'
 
-], function (QUI, QUIControl, QUILoader, Sessions, CouponCodes, QUIAjax, QUILocale, Mustache, template) {
-    "use strict";
+], function(QUI, QUIControl, QUILoader, Sessions, CouponCodes, QUIAjax, QUILocale, Mustache, template) {
+    'use strict';
 
     var lg = 'quiqqer/coupons';
 
     return new Class({
 
         Extends: QUIControl,
-        Type   : 'package/quiqqer/coupons/bin/frontend/controls/CouponCodeInput',
+        Type: 'package/quiqqer/coupons/bin/frontend/controls/CouponCodeInput',
 
         Binds: [
             '$submit'
         ],
 
-        initialize: function (options) {
+        initialize: function(options) {
             this.parent(options);
 
-            this.$Input   = null;
-            this.Loader   = new QUILoader();
+            this.$Input = null;
+            this.Loader = new QUILoader();
             this.$running = false;
 
             this.addEvents({
@@ -49,23 +49,27 @@ define('package/quiqqer/coupons/bin/frontend/controls/CouponCodeInput', [
         /**
          * Event: onInject
          */
-        $onInject: function () {
-            var self     = this;
+        $onInject: function() {
+            var self = this;
             var lgPrefix = 'controls.frontend.CouponCodeInput.template.';
 
             this.$Elm.addClass('quiqqer-coupons-field');
 
+            if (this.$Elm.getParent('[data-name="quiqqer-simple-checkout"]')) {
+                this.$Elm.addClass('quiqqer-coupons-field-simpleCheckout');
+            }
+
             this.$Elm.set('html', Mustache.render(template, {
-                labelInput           : QUILocale.get(lg, lgPrefix + 'labelInput'),
+                labelInput: QUILocale.get(lg, lgPrefix + 'labelInput'),
                 labelInputPlaceholder: QUILocale.get(lg, lgPrefix + 'labelInputPlaceholder'),
-                submitBtnText        : QUILocale.get(lg, lgPrefix + 'submitBtnText')
+                submitBtnText: QUILocale.get(lg, lgPrefix + 'submitBtnText')
             }));
 
             this.Loader.inject(this.$Elm);
 
             this.$Input = this.$Elm.getElement('input[name="code"]');
 
-            this.$Input.addEvent('keyup', function (event) {
+            this.$Input.addEvent('keyup', function(event) {
                 if (event.code === 13) {
                     self.$submit();
                 }
@@ -73,7 +77,7 @@ define('package/quiqqer/coupons/bin/frontend/controls/CouponCodeInput', [
 
             this.$Elm.getElement(
                 'button.quiqqer-coupons-couponcodeinput-btn'
-            ).addEvent('click', function (event) {
+            ).addEvent('click', function(event) {
                 event.stop();
                 self.$submit();
             });
@@ -81,23 +85,23 @@ define('package/quiqqer/coupons/bin/frontend/controls/CouponCodeInput', [
             if (this.isInOrder()) {
                 var OrderProcess = this.getOrderProcess();
 
-                new Promise(function (resolve) {
+                new Promise(function(resolve) {
                     if (!OrderProcess.isLoaded()) {
                         return OrderProcess.addEvent('onLoad', resolve);
                     }
 
                     resolve();
-                }).then(function () {
+                }).then(function() {
                     if (OrderProcess.getAttribute('basketEditable') === false) {
                         // remove coupon codes button
                         new Element('button', {
                             'class': 'quiqqer-coupons-remove',
-                            'html' : '<span class="fa fa-close"></span>',
-                            title  : QUILocale.get(lg, 'remove.coupons.button'),
-                            events : {
-                                click: function (e) {
+                            'html': '<span class="fa fa-close"></span>',
+                            title: QUILocale.get(lg, 'remove.coupons.button'),
+                            events: {
+                                click: function(e) {
                                     e.stop();
-                                    self.removeCouponsFromOrder().catch(function (e) {
+                                    self.removeCouponsFromOrder().catch(function(e) {
                                         console.error(e);
                                     });
                                 }
@@ -111,7 +115,7 @@ define('package/quiqqer/coupons/bin/frontend/controls/CouponCodeInput', [
         /**
          * Submit a CouponCode
          */
-        $submit: function () {
+        $submit: function() {
             if (this.$running) {
                 return;
             }
@@ -127,18 +131,56 @@ define('package/quiqqer/coupons/bin/frontend/controls/CouponCodeInput', [
             this.$running = true;
             this.Loader.show();
 
+            // simple order
+            const SimpleCheckoutNode = this.getElm().getParent('[data-name="quiqqer-simple-checkout"]');
+
+            if (SimpleCheckoutNode) {
+                const SimpleCheckout = QUI.Controls.getById(SimpleCheckoutNode.get('data-quiid'));
+                let orderHash = window.location.hash.substring(1);
+
+                if (!orderHash || orderHash === '') {
+                    orderHash = SimpleCheckout.getAttribute('orderHash');
+                }
+
+                QUIAjax.get('package_quiqqer_order-simple-checkout_ajax_frontend_getOrder', (response) => {
+                    CouponCodes.addCouponCodeToBasket(code, response.order.uuid).then(() => {
+                        if (SimpleCheckout) {
+                            SimpleCheckout.$refreshBasket().then(() => {
+                                this.$running = false;
+                                this.Loader.hide();
+                            });
+
+                            return;
+                        }
+
+                        this.$running = false;
+                        this.Loader.hide();
+                    }).catch((err) => {
+                        console.error(err);
+
+                        this.$running = false;
+                        this.Loader.hide();
+                    });
+                }, {
+                    'package': 'quiqqer/order-simple-checkout',
+                    orderHash: orderHash
+                });
+
+                return;
+            }
+
             // get order process
             if (!this.isInOrder()) {
-                QUIAjax.get('package_quiqqer_order_ajax_frontend_basket_getLastOrder', function (order) {
-                    CouponCodes.addCouponCodeToBasket(code, order.hash).then(function (redeemed) {
+                QUIAjax.get('package_quiqqer_order_ajax_frontend_basket_getLastOrder', function(order) {
+                    CouponCodes.addCouponCodeToBasket(code, order.hash).then(function(redeemed) {
                         if (!redeemed) {
                             self.$running = false;
                             self.Loader.hide();
                             return;
                         }
 
-                        self.$addCouponCodeToSession(code).then(function () {
-                            QUIAjax.get('package_quiqqer_order_ajax_frontend_basket_getOrderProcessUrl', function (url) {
+                        self.$addCouponCodeToSession(code).then(function() {
+                            QUIAjax.get('package_quiqqer_order_ajax_frontend_basket_getOrderProcessUrl', function(url) {
                                 window.location = url + '?coupon=' + code;
                             }, {
                                 'package': 'quiqqer/order'
@@ -156,9 +198,9 @@ define('package/quiqqer/coupons/bin/frontend/controls/CouponCodeInput', [
 
             OrderProcess.Loader.show();
 
-            OrderProcess.getOrder().then(function (orderHash) {
+            OrderProcess.getOrder().then(function(orderHash) {
                 return CouponCodes.addCouponCodeToBasket(code, orderHash);
-            }).then(function (redeemed) {
+            }).then(function(redeemed) {
                 self.$running = false;
 
                 if (redeemed === false) {
@@ -167,7 +209,7 @@ define('package/quiqqer/coupons/bin/frontend/controls/CouponCodeInput', [
                     return;
                 }
 
-                self.$addCouponCodeToSession(code).then(function () {
+                self.$addCouponCodeToSession(code).then(function() {
                     OrderProcess.reload();
                 });
             });
@@ -178,15 +220,15 @@ define('package/quiqqer/coupons/bin/frontend/controls/CouponCodeInput', [
          * @param code
          * @return {Promise}
          */
-        $addCouponCodeToSession: function (code) {
-            return Sessions.get('quiqqer-coupons').then(function (coupons) {
+        $addCouponCodeToSession: function(code) {
+            return Sessions.get('quiqqer-coupons').then(function(coupons) {
                 if (!coupons || typeOf(coupons) !== 'array') {
                     coupons = [];
                 }
 
                 coupons.push(code);
 
-                coupons = coupons.filter(function (value, index, self) {
+                coupons = coupons.filter(function(value, index, self) {
                     return self.indexOf(value) === index;
                 });
 
@@ -198,7 +240,7 @@ define('package/quiqqer/coupons/bin/frontend/controls/CouponCodeInput', [
          * Is the coupon in the order process?
          * @return {boolean}
          */
-        isInOrder: function () {
+        isInOrder: function() {
             var OrderProcessNode = this.getElm().getParent(
                 '[data-qui="package/quiqqer/order/bin/frontend/controls/OrderProcess"]'
             );
@@ -211,7 +253,7 @@ define('package/quiqqer/coupons/bin/frontend/controls/CouponCodeInput', [
          *
          * @return {Object}
          */
-        getOrderProcess: function () {
+        getOrderProcess: function() {
             var OrderProcessNode = this.getElm().getParent(
                 '[data-qui="package/quiqqer/order/bin/frontend/controls/OrderProcess"]'
             );
@@ -224,19 +266,19 @@ define('package/quiqqer/coupons/bin/frontend/controls/CouponCodeInput', [
          *
          * @return {Promise}
          */
-        removeCouponsFromOrder: function () {
+        removeCouponsFromOrder: function() {
             if (!this.isInOrder()) {
                 return Promise.resolve();
             }
 
             var self = this;
 
-            return new Promise(function (resolve) {
+            return new Promise(function(resolve) {
                 var OrderProcess = self.getOrderProcess();
 
                 OrderProcess.Loader.show();
-                OrderProcess.getOrder().then(function (orderHash) {
-                    QUIAjax.get('package_quiqqer_coupons_ajax_frontend_removeCoupons', function () {
+                OrderProcess.getOrder().then(function(orderHash) {
+                    QUIAjax.get('package_quiqqer_coupons_ajax_frontend_removeCoupons', function() {
                         resolve();
                         OrderProcess.reload();
                     }, {
diff --git a/captainhook.json b/captainhook.json
new file mode 100644
index 0000000000000000000000000000000000000000..3702e1a358868bedd5ff4c7eae40bb1abb589267
--- /dev/null
+++ b/captainhook.json
@@ -0,0 +1,13 @@
+{
+    "pre-commit": {
+        "enabled": true,
+        "actions": [
+            {
+                "action": "\\CaptainHook\\App\\Hook\\PHP\\Action\\Linting"
+            },
+            {
+                "action": "composer test"
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/composer.json b/composer.json
index 925f04f4c931c09ab0cb5065bdd4a8b41f196386..fb16364f45b47345262479388fc608f9a84068e2 100644
--- a/composer.json
+++ b/composer.json
@@ -1,28 +1,71 @@
 {
-  "name": "quiqqer/coupons",
-  "type": "quiqqer-module",
-  "description": "Coupons for QUIQQER",
-  "license": "GPL-3.0+",
-  "authors": [
-    {
-      "name": "Patrick M\u00fcller",
-      "email": "support@pcsg.de",
-      "homepage": "https://www.pcsg.de",
-      "role": "Developer"
+    "name": "quiqqer/coupons",
+    "type": "quiqqer-module",
+    "description": "Coupons for QUIQQER",
+    "license": "GPL-3.0+",
+    "authors": [
+        {
+            "name": "Patrick Müller",
+            "email": "support@pcsg.de",
+            "homepage": "https://www.pcsg.de",
+            "role": "Developer"
+        }
+    ],
+    "support": {
+        "email": "support@pcsg.de"
+    },
+    "require": {
+        "quiqqer/core": "^2",
+        "quiqqer/erp": "^3.2",
+        "quiqqer/discount": "^2",
+        "quiqqer/products": "^2",
+        "quiqqer/order": "^2"
+    },
+    "autoload": {
+        "psr-4": {
+            "QUI\\ERP\\Coupons\\": "src/QUI/ERP/Coupons"
+        }
+    },
+    "scripts": {
+        "test": [
+            "@dev:lint",
+            "@dev:phpunit"
+        ],
+        "dev:phpunit": "./tools/phpunit",
+        "dev:lint": [
+            "@dev:lint:phpstan",
+            "@dev:lint:style"
+        ],
+        "dev:lint:phpstan": "./tools/phpstan",
+        "dev:lint:style": "./tools/phpcs",
+        "dev:lint:style:fix": "./tools/phpcbf",
+        "dev:init": [
+            "@dev:init:check-requirements",
+            "@dev:init:tools",
+            "@dev:init:git-hooks"
+        ],
+        "dev:init:check-requirements": [
+            "which composer > /dev/null || (echo 'Error: composer has to be globally installed'; exit 1)",
+            "which phive > /dev/null || (echo 'Error: PHIVE has to be globally installed'; exit 1)"
+        ],
+        "dev:init:tools": "phive install --temporary",
+        "dev:init:git-hooks": "./tools/captainhook install --only-enabled --force"
+    },
+    "scripts-aliases": {
+        "test": [
+            "dev:test"
+        ]
+    },
+    "scripts-descriptions": {
+        "test": "Runs linting, static analysis, and unit tests.",
+        "dev:phpunit": "Run PHPUnit test suites",
+        "dev:lint": "Run PHPStan and code style check",
+        "dev:lint:phpstan": "Run PHPStan",
+        "dev:lint:style": "Run code style check (PHP_CodeSniffer)",
+        "dev:lint:style:fix": "Try to fix code style errors automatically",
+        "dev:init": "Initialize the developer tooling (tools and git hooks)",
+        "dev:init:check-requirements": "Check if the necessary requirements are met",
+        "dev:init:tools": "Install all developer tools (requires PHIVE)",
+        "dev:init:git-hooks": "Install all git hooks (may require tools to be installed)"
     }
-  ],
-  "support": {
-    "email": "support@pcsg.de"
-  },
-  "require": {
-    "quiqqer/core": "^2",
-    "quiqqer/erp": "^3.2",
-    "quiqqer/discount": "^2",
-    "quiqqer/products": "^2"
-  },
-  "autoload": {
-    "psr-4": {
-      "QUI\\ERP\\Coupons\\": "src/QUI/ERP/Coupons"
-    }
-  }
-}
+}
\ No newline at end of file
diff --git a/events.xml b/events.xml
index f267a3e3c895402b6d0115c714f6a578f9faf23f..ba9ec1f86adc1f0bab8b186757181b1e0e78fb08 100644
--- a/events.xml
+++ b/events.xml
@@ -42,4 +42,7 @@
     <event on="onQuiqqerOrderSuccessful" fire="\QUI\ERP\Coupons\Events::removeCouponsFromSession"/>
     <event on="onQuiqqer::order::orderProcessFinish" fire="\QUI\ERP\Coupons\Events::removeCouponsFromSession"/>
     <event on="onQuiqqerMigrationV2" fire="\QUI\ERP\Coupons\Events::onQuiqqerMigrationV2"/>
+
+    <!-- simple order compatibility -->
+    <event on="onQuiqqer::simple::order::basket::middle" fire="\QUI\ERP\Coupons\Events::templateOrderSimpleOrder"/>
 </events>
diff --git a/phpcs.xml.dist b/phpcs.xml.dist
new file mode 100644
index 0000000000000000000000000000000000000000..d48084fbdc86a2dbfb78dd89ab2e0b0ff2322ba7
--- /dev/null
+++ b/phpcs.xml.dist
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<ruleset>
+    <!-- Use PSR-12 ruleset -->
+    <rule ref="PSR12"/>
+
+    <!-- Only scan *.php files -->
+    <arg name="extensions" value="php"/>
+
+    <!-- Ignore warnings -->
+    <arg name="warning-severity" value="0"/>
+
+    <!-- Process 64 (or number of CPU cores) files in parallel -->
+    <arg name="parallel" value="64"/>
+
+    <!-- Output relative file paths, by setting the current folder as the basepath -->
+    <arg name="basepath" value="."/>
+
+    <!-- Show colored output -->
+    <arg name="colors"/>
+
+    <!-- Scan everything in the current folder -->
+    <file>.</file>
+</ruleset>
diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..66cad0d6e45338268da2661f09f15a75d969595d 100644
--- a/phpstan-baseline.neon
+++ b/phpstan-baseline.neon
@@ -0,0 +1,71 @@
+parameters:
+	ignoreErrors:
+		-
+			message: "#^Call to an undefined method QUI\\\\ERP\\\\Products\\\\Interfaces\\\\PriceFactorInterface\\:\\:setVat\\(\\)\\.$#"
+			count: 1
+			path: ajax/backend/getCouponPrice.php
+
+		-
+			message: "#^Dead catch \\- Exception is never thrown in the try block\\.$#"
+			count: 1
+			path: ajax/delete.php
+
+		-
+			message: "#^Call to an undefined method QUI\\\\ERP\\\\Products\\\\Interfaces\\\\PriceFactorInterface\\:\\:setVat\\(\\)\\.$#"
+			count: 1
+			path: src/QUI/ERP/Coupons/CouponCode.php
+
+		-
+			message: "#^Method QUI\\\\ERP\\\\Coupons\\\\CouponCode\\:\\:getDiscounts\\(\\) should return array\\<QUI\\\\ERP\\\\Discount\\\\Discount\\> but returns array\\<int\\<0, max\\>, QUI\\\\CRUD\\\\Child\\>\\.$#"
+			count: 1
+			path: src/QUI/ERP/Coupons/CouponCode.php
+
+		-
+			message: "#^Offset 'standard' on array\\{title\\: array\\{de\\: 'Gutschein \\- Versand', en\\: 'Coupon delivery'\\}, type\\: 'ProductAttributeList', public\\: true, standard\\: false, requiredField\\: true, options\\: array\\{entries\\: array\\{array\\{title\\: array\\{de\\: 'per E \\- Mail', en\\: 'via email'\\}, sum\\: 0, type\\: 2, selected\\: true, userinput\\: false\\}, array\\{title\\: array\\{de\\: 'per Post', en\\: 'via mail'\\}, sum\\: 0, type\\: 2, selected\\: false, userinput\\: false\\}\\}\\}\\}\\|array\\{title\\: array\\{de\\: 'Gutschein Wert', en\\: 'Coupon amount'\\}, type\\: 'FloatType', public\\: false, standard\\: false, requiredField\\: true\\}\\|array\\{title\\: array\\{de\\: 'Gutschein\\-Code als…', en\\: 'Provide coupon code…'\\}, description\\: array\\{de\\: 'Der Gutschein wird…', en\\: 'The coupon is also…'\\}, type\\: 'BoolType', public\\: false, standard\\: false, requiredField\\: false\\}\\|array\\{title\\: array\\{de\\: 'Gutschein\\-Code ist…', en\\: 'Coupon code is…'\\}, description\\: array\\{de\\: 'Übertragbare…', en\\: 'Transferable…'\\}, type\\: 'BoolType', public\\: false, standard\\: false, requiredField\\: false\\}\\|array\\{title\\: array\\{de\\: 'Gutschein\\-Code per…', en\\: 'Send coupon code…'\\}, description\\: array\\{de\\: 'Der Gutschein\\-Code…', en\\: 'The coupon code is…'\\}, type\\: 'BoolType', public\\: false, standard\\: false, requiredField\\: false\\}\\|array\\{title\\: array\\{de\\: 'Gutschein\\-Code…', en\\: 'Coupon code…'\\}, type\\: 'IntType', public\\: false, standard\\: false, requiredField\\: true\\}\\|array\\{title\\: array\\{de\\: 'Gutschein…', en\\: 'Coupon description'\\}, description\\: array\\{de\\: 'Diese Beschreibung…', en\\: 'This description…'\\}, type\\: 'InputMultiLang', public\\: false, standard\\: false, requiredField\\: false\\}\\|array\\{title\\: array\\{de\\: 'Ist Einzweck…', en\\: 'Is single purpose…'\\}, description\\: array\\{de\\: 'Einzweck\\-Gutscheine…', en\\: 'Single\\-purpose…'\\}, type\\: 'BoolType', public\\: false, standard\\: false, requiredField\\: false\\}\\|array\\{title\\: array\\{de\\: 'Kunde darf…', en\\: 'Customer can choose…'\\}, description\\: array\\{de\\: 'Ist diese Funktion…', en\\: 'if this option is…'\\}, type\\: 'BoolType', public\\: false, standard\\: false, requiredField\\: false\\} in empty\\(\\) always exists and is always falsy\\.$#"
+			count: 1
+			path: src/QUI/ERP/Coupons/Events.php
+
+		-
+			message: "#^Parameter \\#1 \\$id of static method QUI\\\\ERP\\\\Coupons\\\\Handler\\:\\:getCouponCode\\(\\) expects int, string\\|false given\\.$#"
+			count: 1
+			path: src/QUI/ERP/Coupons/Handler.php
+
+		-
+			message: "#^Call to method createPDF\\(\\) on an unknown class QUI\\\\HtmlToPdf\\\\Document\\.$#"
+			count: 1
+			path: src/QUI/ERP/Coupons/Products/Handler.php
+
+		-
+			message: "#^Call to method setAttribute\\(\\) on an unknown class QUI\\\\HtmlToPdf\\\\Document\\.$#"
+			count: 7
+			path: src/QUI/ERP/Coupons/Products/Handler.php
+
+		-
+			message: "#^Call to method setContentHTML\\(\\) on an unknown class QUI\\\\HtmlToPdf\\\\Document\\.$#"
+			count: 1
+			path: src/QUI/ERP/Coupons/Products/Handler.php
+
+		-
+			message: "#^Call to method setFooterHTML\\(\\) on an unknown class QUI\\\\HtmlToPdf\\\\Document\\.$#"
+			count: 1
+			path: src/QUI/ERP/Coupons/Products/Handler.php
+
+		-
+			message: "#^Call to method setHeaderHTML\\(\\) on an unknown class QUI\\\\HtmlToPdf\\\\Document\\.$#"
+			count: 1
+			path: src/QUI/ERP/Coupons/Products/Handler.php
+
+		-
+			message: "#^Instantiated class QUI\\\\HtmlToPdf\\\\Document not found\\.$#"
+			count: 1
+			path: src/QUI/ERP/Coupons/Products/Handler.php
+
+		-
+			message: "#^Negated boolean expression is always false\\.$#"
+			count: 1
+			path: src/QUI/ERP/Coupons/Products/Handler.php
+
+		-
+			message: "#^Parameter \\#1 \\$key of method QUI\\\\ERP\\\\Accounting\\\\Article\\:\\:getCustomField\\(\\) expects string, int given\\.$#"
+			count: 1
+			path: src/QUI/ERP/Coupons/Products/Handler.php
diff --git a/phpstan.dist.neon b/phpstan.dist.neon
index a545a041a3c5b0c758d99bf8c1c49b2a83252eaa..18db987295f4cd08f7b698474102b19b6ecf75da 100644
--- a/phpstan.dist.neon
+++ b/phpstan.dist.neon
@@ -2,7 +2,7 @@ includes:
     - phpstan-baseline.neon
 
 parameters:
-    level: 1
+    level: 5
     paths:
         - src
         - ajax
diff --git a/phpunit.dist.xml b/phpunit.dist.xml
new file mode 100644
index 0000000000000000000000000000000000000000..f6c7becf0c12757beb871a9333e2d81e02aa7cae
--- /dev/null
+++ b/phpunit.dist.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit bootstrap="tests/phpunit-bootstrap.php">
+    <testsuites>
+        <testsuite name="Tests">
+            <directory>tests/</directory>
+        </testsuite>
+    </testsuites>
+</phpunit>
diff --git a/src/QUI/ERP/Coupons/Events.php b/src/QUI/ERP/Coupons/Events.php
index c2e5774aecfb16769c9acf304b9e26b087bc3f15..d528a8367b4157c14ca0c2b597140814616363a0 100644
--- a/src/QUI/ERP/Coupons/Events.php
+++ b/src/QUI/ERP/Coupons/Events.php
@@ -27,6 +27,7 @@
 use function is_string;
 use function json_decode;
 use function json_encode;
+use function method_exists;
 
 /**
  * Class Events
@@ -63,12 +64,12 @@ public static function onAdminLoadFooter(): void
      *
      * @param Collector $Collector
      * @param mixed $Basket
-     * @param AbstractOrder|null $Order
+     * @param mixed $Order
      */
     public static function templateOrderProcessBasketEnd(
         Collector $Collector,
         mixed $Basket,
-        AbstractOrder $Order = null
+        mixed $Order = null
     ): void {
         if (
             !($Basket instanceof Basket)
@@ -96,6 +97,26 @@ public static function templateOrderProcessBasketEnd(
         );
     }
 
+    public static function templateOrderSimpleOrder(
+        Collector $Collector,
+        AbstractOrder $Order
+    ): void {
+        if ($Order instanceof QUI\ERP\Order\OrderInProcess && isset($_GET['coupon'])) {
+            try {
+                $code = Handler::sanitizeCode($_GET['coupon']);
+
+                $CouponCode = Handler::getCouponCodeByCode($code);
+                $CouponCode->checkRedemption(QUI::getUserBySession());
+                $CouponCode->addToOrder($Order);
+            } catch (Exception) {
+            }
+        }
+
+        $Collector->append(
+            '<div data-qui="package/quiqqer/coupons/bin/frontend/controls/CouponCodeInput"></div>'
+        );
+    }
+
     /**
      * @param QUI\ERP\Order\OrderProcess $OrderProcess
      * @throws QUI\ERP\Order\Exception
@@ -193,7 +214,9 @@ public static function onQuiqqerOrderBasketRemovePos(
             $Order->setData('quiqqer-coupons', $orderCoupons);
 
             try {
-                $Order->save();
+                if (method_exists($Order, 'save')) {
+                    $Order->save();
+                }
             } catch (QUI\Exception) {
             }
         }
diff --git a/tests/phpunit-bootstrap.php b/tests/phpunit-bootstrap.php
new file mode 100644
index 0000000000000000000000000000000000000000..eca92fd67bed8ae4ec424ed82d300119d792f042
--- /dev/null
+++ b/tests/phpunit-bootstrap.php
@@ -0,0 +1,11 @@
+<?php
+
+if (!defined('QUIQQER_SYSTEM')) {
+    define('QUIQQER_SYSTEM', true);
+}
+
+if (!defined('QUIQQER_AJAX')) {
+    define('QUIQQER_AJAX', true);
+}
+
+require_once __DIR__ . '/../../../../bootstrap.php';