From 0cf75f514343c73a34aa99a9fdea9389aac5fca2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Patrick=20M=C3=BCller?= <p.mueller@pcsg.de>
Date: Thu, 20 Feb 2025 12:28:48 +0000
Subject: [PATCH] feat: sepa payment

---
 .phive/phars.xml                              |  10 +-
 ajax/confirmSubscription.php                  |  12 +-
 ajax/createSubscription.php                   |  21 +-
 bin/controls/PaymentDisplay.js                |  73 +-
 bin/controls/Recurring/PaymentDisplay.js      |   7 +-
 composer.json                                 |   4 +
 cron.xml                                      |  14 +
 locale.xml                                    | 148 +++-
 phpstan-baseline.neon                         | 664 +-----------------
 settings.xml                                  |  67 +-
 .../Payments/Stripe/AbstractBasePayment.php   |  70 +-
 .../Payments/Stripe/ChargeDisputeService.php  | 155 ++++
 .../Stripe/Cron/PendingPaymentsService.php    | 140 ++++
 src/QUI/ERP/Payments/Stripe/Events.php        |  16 +-
 .../Stripe/OrderProcessingService.php         | 244 +++++++
 .../OrderProcessingServiceInterface.php       |  19 +
 .../PaymentMethods/AbstractBrowserPay.php     |  30 +-
 .../Stripe/PaymentMethods/ApplePay.php        |   2 +-
 .../Payments/Stripe/PaymentMethods/Card.php   |   5 +-
 .../Stripe/PaymentMethods/GooglePay.php       |   2 +-
 .../Stripe/PaymentMethods/MicrosoftPay.php    |   2 +-
 .../AbstractBaseRecurringPayment.php          |  40 +-
 .../Recurring/AbstractRecurringBrowserPay.php |  24 +
 .../PaymentMethods/Recurring/ApplePay.php     |   2 +-
 .../Stripe/PaymentMethods/Recurring/Card.php  |   5 +-
 .../PaymentMethods/Recurring/GooglePay.php    |   2 +-
 .../PaymentMethods/Recurring/MicrosoftPay.php |   2 +-
 .../PaymentMethods/Recurring/SepaDebit.php    | 207 ++++++
 .../Recurring/Subscriptions.php               | 172 +++--
 .../Stripe/PaymentMethods/SepaDebit.php       | 179 +++++
 src/QUI/ERP/Payments/Stripe/Provider.php      |  79 ++-
 src/QUI/ERP/Payments/Stripe/Utils.php         | 193 ++++-
 .../ERP/Payments/Stripe/WebhookHandler.php    |  66 ++
 33 files changed, 1817 insertions(+), 859 deletions(-)
 create mode 100644 src/QUI/ERP/Payments/Stripe/ChargeDisputeService.php
 create mode 100644 src/QUI/ERP/Payments/Stripe/Cron/PendingPaymentsService.php
 create mode 100644 src/QUI/ERP/Payments/Stripe/OrderProcessingService.php
 create mode 100644 src/QUI/ERP/Payments/Stripe/OrderProcessingServiceInterface.php
 create mode 100644 src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/SepaDebit.php
 create mode 100644 src/QUI/ERP/Payments/Stripe/PaymentMethods/SepaDebit.php

diff --git a/.phive/phars.xml b/.phive/phars.xml
index 5bfa092..083bf35 100644
--- a/.phive/phars.xml
+++ b/.phive/phars.xml
@@ -1,8 +1,8 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <phive xmlns="https://phar.io/phive">
-  <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"/>
+  <phar name="phpstan" version="1.11.8" installed="1.11.8" location="./tools/phpstan" copy="true"/>
+  <phar name="phpunit" version="^10.5.20" installed="10.5.20" location="./tools/phpunit" copy="true"/>
+  <phar name="phpcs" version="^3.10.1" installed="3.10.1" location="./tools/phpcs" copy="true"/>
+  <phar name="phpcbf" version="^3.10.1" installed="3.10.1" location="./tools/phpcbf" copy="true"/>
+  <phar name="captainhook" version="^5.23.3" installed="5.23.3" location="./tools/captainhook" copy="true"/>
 </phive>
diff --git a/ajax/confirmSubscription.php b/ajax/confirmSubscription.php
index 81f0577..62da9f2 100644
--- a/ajax/confirmSubscription.php
+++ b/ajax/confirmSubscription.php
@@ -10,6 +10,7 @@
 use QUI\ERP\Payments\Stripe\PaymentMethods\Recurring\Subscriptions;
 use QUI\ERP\Payments\Stripe\AbstractBasePayment;
 use Stripe\Exception\CardException;
+use QUI\ERP\Payments\Stripe\PaymentMethods\Recurring\AbstractBaseRecurringPayment;
 
 /**
  * Confirm a Stripe Subscription
@@ -23,9 +24,18 @@ function ($orderHash) {
         try {
             $orderHash = Orthos::clear($orderHash);
             $Order = Handler::getInstance()->getOrderByHash($orderHash);
+            $paymentType = $Order->getPayment()->getPaymentType();
+
+            if (!($paymentType instanceof AbstractBaseRecurringPayment)) {
+                throw new StripeException("Cannot create Stripe subscription. Wrong order payment type.", 0, [
+                    'orderUuid' => $Order->getUUID(),
+                    'paymentTypeActual' => $paymentType::class
+                ]);
+            }
 
             return Subscriptions::confirmSubscription(
-                $Order->getPaymentDataEntry(AbstractBasePayment::ATTR_STRIPE_SUBSCRIPTION_ID)
+                $Order->getPaymentDataEntry(AbstractBasePayment::ATTR_STRIPE_SUBSCRIPTION_ID),
+                $paymentType
             );
         } catch (Exception $Exception) {
             if ($Exception instanceof CardException) {
diff --git a/ajax/createSubscription.php b/ajax/createSubscription.php
index a2b0e25..68cb11a 100644
--- a/ajax/createSubscription.php
+++ b/ajax/createSubscription.php
@@ -5,15 +5,15 @@
  */
 
 use QUI\ERP\Order\Handler;
-use QUI\ERP\Payments\Stripe\PaymentMethods\Recurring\AbstractBaseRecurringPayment;
-use QUI\Utils\Security\Orthos;
-use QUI\ERP\Payments\Stripe\StripeException;
 use QUI\ERP\Payments\Stripe\AbstractBasePayment;
+use QUI\ERP\Payments\Stripe\PaymentMethods\Recurring\AbstractBaseRecurringPayment;
 use QUI\ERP\Payments\Stripe\PaymentMethods\Recurring\Subscriptions;
-use Stripe\Subscription as StripeSubscription;
-use Stripe\Invoice as StripeInvoice;
+use QUI\ERP\Payments\Stripe\StripeException;
 use QUI\ERP\Payments\Stripe\Utils;
+use QUI\Utils\Security\Orthos;
 use Stripe\Exception\CardException;
+use Stripe\Invoice as StripeInvoice;
+use Stripe\Subscription as StripeSubscription;
 
 /**
  * Create a Stripe PaymentIntent
@@ -29,6 +29,15 @@ function ($orderHash, $paymentMethodId, $resetPaymentMethod = null) {
             $paymentMethodId = Orthos::clear($paymentMethodId);
             $Order = Handler::getInstance()->getOrderByHash($orderHash);
 
+            $paymentType = $Order->getPayment()->getPaymentType();
+
+            if (!($paymentType instanceof AbstractBaseRecurringPayment)) {
+                throw new StripeException("Cannot create Stripe subscription. Wrong order payment type.", 0, [
+                    'orderUuid' => $Order->getUUID(),
+                    'paymentTypeActual' => $paymentType::class
+                ]);
+            }
+
             /** @var AbstractBaseRecurringPayment $Payment */
             $Payment = $Order->getPayment()->getPaymentType();
 
@@ -66,7 +75,7 @@ function ($orderHash, $paymentMethodId, $resetPaymentMethod = null) {
                 }
             }
 
-            return Subscriptions::confirmSubscription($subscriptionId);
+            return Subscriptions::confirmSubscription($subscriptionId, $Payment);
         } catch (Exception $Exception) {
             if ($Exception instanceof CardException) {
                 QUI\System\Log::writeDebugException($Exception);
diff --git a/bin/controls/PaymentDisplay.js b/bin/controls/PaymentDisplay.js
index 80966ed..60fc8a5 100644
--- a/bin/controls/PaymentDisplay.js
+++ b/bin/controls/PaymentDisplay.js
@@ -39,7 +39,8 @@ define('package/quiqqer/payment-stripe/bin/controls/PaymentDisplay', [
             '$showMsg',
             '$onGeneralError',
             '$getPayBtn',
-            '$onBrowserPaySubmit'
+            '$onBrowserPaySubmit',
+            'getPaymentMode'
         ],
 
         options: {
@@ -111,9 +112,7 @@ define('package/quiqqer/payment-stripe/bin/controls/PaymentDisplay', [
             var self = this;
 
             var onStripeJsLoaded = function () {
-                self.$Stripe   = Stripe(self.getAttribute('publickey'));
-                self.$Elements = self.$Stripe.elements();
-
+                self.$Stripe = Stripe(self.getAttribute('publickey'));
                 self.$showStripeForm();
             };
 
@@ -144,16 +143,17 @@ define('package/quiqqer/payment-stripe/bin/controls/PaymentDisplay', [
          * Show Stripe payment method form
          */
         $showStripeForm: function () {
-            var self = this;
-
             this.$OrderProcess.Loader.show();
 
-            var PayBtnElm   = this.$Elm.getElement('.quiqqer-payment-stripe-submit'),
-                PaymentForm = this.$Elm.getElement('.quiqqer-payment-stripe-form');
+            const PayBtnElm     = this.$Elm.getElement('.quiqqer-payment-stripe-submit');
+            const PaymentForm   = this.$Elm.getElement('.quiqqer-payment-stripe-form');
+            const paymentMethod = this.getAttribute('paymentmethod');
 
-            switch (this.getAttribute('paymentmethod')) {
+            let elements;
+
+            switch (paymentMethod) {
                 case 'browser_pay':
-                    var PaymentRequest = self.$Stripe.paymentRequest({
+                    var PaymentRequest = this.$Stripe.paymentRequest({
                         country : this.getAttribute('country'),
                         currency: this.getAttribute('currency'),
                         total   : {
@@ -166,7 +166,8 @@ define('package/quiqqer/payment-stripe/bin/controls/PaymentDisplay', [
                         //requestPayerEmail: true,
                     });
 
-                    this.$StripeFormElement = this.$Elements.create('paymentRequestButton', {
+                    elements                = this.$Stripe.elements();
+                    this.$StripeFormElement = elements.create('paymentRequestButton', {
                         paymentRequest: PaymentRequest,
                         style         : {
                             base   : {
@@ -187,15 +188,15 @@ define('package/quiqqer/payment-stripe/bin/controls/PaymentDisplay', [
                         }
                     });
 
-                    PaymentRequest.canMakePayment().then(function (canMakePayment) {
+                    PaymentRequest.canMakePayment().then((canMakePayment) => {
                         if (!canMakePayment) {
-                            var browser = self.getAttribute('browser');
+                            var browser = this.getAttribute('browser');
 
                             switch (browser) {
                                 case 'Chrome':
                                 case 'Safari':
                                 case 'Edge':
-                                    self.$showErrorMsg(
+                                    this.$showErrorMsg(
                                         QUILocale.get(
                                             pkg,
                                             'controls.PaymentDisplay.browser_pay.error.cannot_use.' + browser
@@ -204,7 +205,7 @@ define('package/quiqqer/payment-stripe/bin/controls/PaymentDisplay', [
                                     break;
 
                                 default:
-                                    self.$showErrorMsg(
+                                    this.$showErrorMsg(
                                         QUILocale.get(
                                             pkg,
                                             'controls.PaymentDisplay.browser_pay.error.cannot_use'
@@ -212,27 +213,25 @@ define('package/quiqqer/payment-stripe/bin/controls/PaymentDisplay', [
                                     );
                             }
 
-                            self.fireEvent('processingError', [self]);
+                            this.fireEvent('processingError', [this]);
 
                             PaymentForm.destroy();
                             return;
                         }
 
-                        PaymentRequest.on('paymentmethod', self.$onBrowserPaySubmit);
+                        PaymentRequest.on('paymentmethod', this.$onBrowserPaySubmit);
 
-                        self.$StripeFormElement.mount(PaymentForm);
-                        self.$OrderProcess.Loader.hide();
+                        this.$StripeFormElement.mount(PaymentForm);
+                        this.$OrderProcess.Loader.hide();
                     });
 
                     break;
 
-                case 'card':
                 default:
-                    //this.$showMsg(QUILocale.get(pkg, 'PaymentDisplay.card.info'));
-
-                    this.$PayBtn = this.$getPayBtn().inject(PayBtnElm);
+                    let paymentMethodTypes = [paymentMethod];
+                    let elementType;
 
-                    this.$StripeFormElement = this.$Elements.create('card', {
+                    const elementOptions = {
                         style: {
                             base   : {
                                 color          : '#13151b',
@@ -250,14 +249,35 @@ define('package/quiqqer/payment-stripe/bin/controls/PaymentDisplay', [
                                 }
                             }
                         }
+                    };
+
+                    switch (paymentMethod) {
+                        case 'sepa_debit':
+                            elementOptions.supportedCountries = ['SEPA'];
+                            break;
+                    }
+
+                    this.$PayBtn = this.$getPayBtn().inject(PayBtnElm);
+
+                    elements = this.$Stripe.elements({
+                        mode              : this.getPaymentMode(),
+                        paymentMethodTypes: paymentMethodTypes,
+                        amount            : this.getAttribute('amount'),
+                        currency          : this.getAttribute('currency')
                     });
 
+                    this.$StripeFormElement = elements.create('payment', elementOptions);
+
                     this.$StripeFormElement.mount(PaymentForm);
                     this.$OrderProcess.Loader.hide();
                     break;
             }
         },
 
+        getPaymentMode: function () {
+            return 'payment';
+        },
+
         /**
          * Handle special Stripe paymentRequest submit (Browser Pay)
          *
@@ -365,7 +385,10 @@ define('package/quiqqer/payment-stripe/bin/controls/PaymentDisplay', [
             var self = this;
 
             this.$OrderProcess.Loader.show();
-            this.$Stripe.handleCardAction(Confirmation.clientSecret).then(function (result) {
+
+            this.$Stripe.handleNextAction({
+                clientSecret: Confirmation.clientSecret
+            }).then(function (result) {
                 if (result.error) {
                     self.$onStripeError(result.error);
                     return;
diff --git a/bin/controls/Recurring/PaymentDisplay.js b/bin/controls/Recurring/PaymentDisplay.js
index 7c449ab..47be043 100644
--- a/bin/controls/Recurring/PaymentDisplay.js
+++ b/bin/controls/Recurring/PaymentDisplay.js
@@ -27,7 +27,8 @@ define('package/quiqqer/payment-stripe/bin/controls/Recurring/PaymentDisplay', [
         Binds: [
             '$onPaymentFormSubmit',
             '$getPayBtn',
-            '$handleSubscriptionConfirmation'
+            '$handleSubscriptionConfirmation',
+            'getPaymentMode'
         ],
 
         initialize: function (options) {
@@ -161,6 +162,10 @@ define('package/quiqqer/payment-stripe/bin/controls/Recurring/PaymentDisplay', [
             }
         },
 
+        getPaymentMode: function () {
+            return 'subscription';
+        },
+
         /**
          * Get payment button
          *
diff --git a/composer.json b/composer.json
index 115e53b..d51010b 100644
--- a/composer.json
+++ b/composer.json
@@ -24,6 +24,10 @@
         "stripe/stripe-php": "^15",
         "cbschuld/browser.php": "^1"
     },
+    "suggest": {
+        "quiqqer/order": "Provide Stripe payments for ecoyn orders and subscriptions.",
+        "quiqqer/invoice": "Checks invoice payments with Stripe transactions."
+    },
     "autoload": {
         "psr-4": {
             "QUI\\ERP\\Payments\\Stripe\\": "src/QUI/ERP/Payments/Stripe"
diff --git a/cron.xml b/cron.xml
index 47225a2..dd33292 100644
--- a/cron.xml
+++ b/cron.xml
@@ -15,4 +15,18 @@
         </autocreate>
     </cron>
 
+    <cron exec="\QUI\ERP\Payments\Stripe\Cron\PendingPaymentsService::execute">
+        <title>
+            <locale group="quiqqer/payment-stripe" var="cron.PendingPaymentsService.title"/>
+        </title>
+        <description>
+            <locale group="quiqqer/payment-stripe" var="cron.PendingPaymentsService.description"/>
+        </description>
+
+        <autocreate>
+            <interval>0 * * * *</interval>
+            <active>1</active>
+        </autocreate>
+    </cron>
+
 </crons>
\ No newline at end of file
diff --git a/locale.xml b/locale.xml
index fc63bb9..aa93f33 100644
--- a/locale.xml
+++ b/locale.xml
@@ -77,6 +77,22 @@
             <de><![CDATA[Wiederkehrende Zahlungen mit Microsoft Pay (nur Edge Browser)]]></de>
             <en><![CDATA[Recurring payments with Microsoft Pay (Edge browser only)]]></en>
         </locale>
+        <locale name="payment.SepaDebit.title">
+            <de><![CDATA[Stripe - SEPA-Lastschrift]]></de>
+            <en><![CDATA[Stripe - SEPA Direct Debit]]></en>
+        </locale>
+        <locale name="payment.SepaDebit.description">
+            <de><![CDATA[SEPA-Lastschrift über Stripe]]></de>
+            <en><![CDATA[SEPA Direct Debit via Stripe]]></en>
+        </locale>
+        <locale name="payment.Recurring.SepaDebit.title">
+            <de><![CDATA[Stripe - SEPA-Lastschrift (Abonnements)]]></de>
+            <en><![CDATA[Stripe - SEPA Direct Debit (Subscriptions)]]></en>
+        </locale>
+        <locale name="payment.Recurring.SepaDebit.description">
+            <de><![CDATA[SEPA-Lastschrift über Stripe]]></de>
+            <en><![CDATA[SEPA Direct Debit via Stripe]]></en>
+        </locale>
 
         <locale name="payment.frontend.Card.title">
             <de><![CDATA[Kreditkarte]]></de>
@@ -86,6 +102,14 @@
             <de><![CDATA[Sichere Kreditkartenzahlung für die gängigsten Karten wie Visa/Visa Electron/Visa Debit, MasterCard/Maestro, Diners Club, Discover, China Union Pay, AMEX und JCB.]]></de>
             <en><![CDATA[Secure credit card payment for the most popular cards such as Visa/Visa Electron/Visa Debit, MasterCard/Maestro, Diners Club, Discover, China Union Pay, AMEX and JCB.]]></en>
         </locale>
+        <locale name="payment.frontend.SepaDebit.title">
+            <de><![CDATA[SEPA-Lastschrift]]></de>
+            <en><![CDATA[SEPA Direct Debit]]></en>
+        </locale>
+        <locale name="payment.frontend.SepaDebit.description">
+            <de><![CDATA[Zahlung mittels SEPA-Lastschriftverfahren]]></de>
+            <en><![CDATA[Payment via SEPA Direct Debit]]></en>
+        </locale>
         <locale name="payment.frontend.GooglePay.title">
             <de><![CDATA[Google Pay]]></de>
             <en><![CDATA[Google Pay]]></en>
@@ -143,6 +167,15 @@
             <de><![CDATA[Sichere Kreditkartenzahlung für die gängigsten Karten wie Visa/Visa Electron/Visa Debit, MasterCard/Maestro, Diners Club, Discover, China Union Pay, AMEX und JCB.]]></de>
             <en><![CDATA[Secure credit card payment for the most popular cards such as Visa/Visa Electron/Visa Debit, MasterCard/Maestro, Diners Club, Discover, China Union Pay, AMEX and JCB.]]></en>
         </locale>
+        <locale name="payment.frontend.Recurring.SepaDebit.title">
+            <de><![CDATA[SEPA-Lastschrift]]></de>
+            <en><![CDATA[SEPA Direct Debit]]></en>
+        </locale>
+        <locale name="payment.frontend.Recurring.SepaDebit.description">
+            <de><![CDATA[Zahlung mittels SEPA-Lastschriftverfahren]]></de>
+            <en><![CDATA[Payment via SEPA Direct Debit]]></en>
+        </locale>
+
 
         <locale name="payment.PaymentStep.title.ApplePay">
             <de><![CDATA[Bezahlung mit Apple Pay]]></de>
@@ -180,6 +213,22 @@
             <de><![CDATA[Bitte geben Sie Ihre Kreditkarten-Daten im sicheren Formular an und klicken auf <b>Regelmäßige Zahlung autorisieren</b>, um den Zahlungsvorgang zu starten.]]></de>
             <en><![CDATA[Please provide your credit card information in the secure form and click <b>Authorize recurring payment</b> to start the payment process.]]></en>
         </locale>
+        <locale name="payment.PaymentStep.title.SepaDebit">
+            <de><![CDATA[Bezahlung mit SEPA-Lastschrift (über Stripe)]]></de>
+            <en><![CDATA[Payment with SEPA Direct Debit (via Stripe)]]></en>
+        </locale>
+        <locale name="payment.PaymentStep.info.Card" html="true">
+            <de><![CDATA[Bitte geben Sie Ihre Kontodaten im sicheren Formular an und klicken auf <b>Jetzt bezahlen</b>, um den Zahlungsvorgang zu starten.]]></de>
+            <en><![CDATA[Please provide your bank account information in the secure form and click <b>Authorize recurring payment</b> to start the payment process.]]></en>
+        </locale>
+        <locale name="payment.Recurring.PaymentStep.info.Card" html="true">
+            <de><![CDATA[Bitte geben Sie Ihre Kreditkarten-Daten im sicheren Formular an und klicken auf <b>Regelmäßige Zahlung autorisieren</b>, um den Zahlungsvorgang zu starten.]]></de>
+            <en><![CDATA[Please provide your credit card information in the secure form and click <b>Authorize recurring payment</b> to start the payment process.]]></en>
+        </locale>
+        <locale name="payment.Recurring.PaymentStep.info.SepaDebit">
+            <de><![CDATA[Bitte geben Sie Ihre Kontodaten im sicheren Formular an und klicken auf <b>Regelmäßige Zahlung autorisieren</b>, um den Zahlungsvorgang zu starten.]]></de>
+            <en><![CDATA[Please provide your bank account information in the secure form and click <b>Authorize recurring payment</b> to start the payment process.]]></en>
+        </locale>
 
         <locale name="additional_invoice_text.MicrosoftPay">
             <de><![CDATA[Der Betrag wird automatisch über Ihr Microsoft Pay Konto eingezogen.]]></de>
@@ -197,6 +246,10 @@
             <de><![CDATA[Der Betrag wird automatisch über Ihre Kreditkarte eingezogen.]]></de>
             <en><![CDATA[The amount will be automatically collected via your credit card.]]></en>
         </locale>
+        <locale name="additional_invoice_text.SepaDebit">
+            <de><![CDATA[Der Betrag wird automatisch per SEPA-Lastschriftverfahren von Ihrem angegebenen Bankkonto eingezogen.]]></de>
+            <en><![CDATA[The amount will be collected automatically by SEPA Direct Debit from your specified bank account.]]></en>
+        </locale>
 
         <!-- Recurring payments -->
         <locale name="history.invoice.add_paymill_transaction">
@@ -215,6 +268,10 @@
             <de><![CDATA[Die Stripe Subscription [subscriptionId] konnte nicht gekündigt werden. Bitte prüfen Sie die Fehler-Logs.]]></de>
             <en><![CDATA[The Stripe Subscription [subscriptionId] could not be cancelled. Please check the error logs.]]></en>
         </locale>
+        <locale name="history.Invoice.stripe_charge_dispute_lost">
+            <de><![CDATA[Stripe Abonnement-Rechnungstransaktion "[stripeInvoiceId]" wurde angefochten (Stripe Anfechtungs ID: "[stripeDisputeId]"). Die Anfechtung wurde verloren. Die zugehörige ecoyn Transaktion "[quiqqerTransactionId]" wurde als fehlerhaft markiert.]]></de>
+            <en><![CDATA[Stripe subscription invoice transaction "[stripeInvoiceId]" was disputed (Stripe Dispute ID: "[stripeDisputeId]". The dispute was lost. The associated eyocn Transaktion "[quiqqerTransactionId]" was marked as erroneous.]]></en>
+        </locale>
 
         <!-- Settings -->
         <locale name="settings.menu.title">
@@ -314,6 +371,33 @@
 <li><b>{orderId}</b> - Order number (with prefix)</li>
 </ul>]]></en>
         </locale>
+        <locale name="settings.general.statement_descriptor.title">
+            <de><![CDATA[Zahlungsbeschreibung in Abrechnungen]]></de>
+            <en><![CDATA[Statement descriptor]]></en>
+        </locale>
+        <locale name="settings.general.statement_descriptor.description" html="true">
+            <de><![CDATA[Zahlungsbeschreibung, der in Abrechnungsvorgängen dem Benutzer angezeigt wird (z.B. auf dem Kontoauszug). <b>Gilt nicht</b> für Kartenzahlungen!
+            <br><br>Verfügbare Platzhalter:
+<ul>
+<li><b>{orderId}</b> - Bestellnummer (mit Präfix)</li>
+</ul>
+<p><b>Maximal 22 Zeichen inkl. konkreten Platzhalter-Werten!</b></p>]]></de>
+            <en><![CDATA[Statement descriptor that is shown to the user in billing procedures (e.g. account statement). <b>Not used</b> for credit card payments!
+<br><br>Available placeholders:
+<ul>
+<li><b>{orderId}</b> - Order number (with prefix)</li>
+</ul>
+<p><b>Maximum 22 characters including concrete placeholder values!</b></p>
+]]></en>
+        </locale>
+        <locale name="settings.general.notify_about_failed_payment.title">
+            <de><![CDATA[Benachrichtigung bei fehlgeschlagener Zahlung]]></de>
+            <en><![CDATA[Notification about failed payment]]></en>
+        </locale>
+        <locale name="settings.general.notify_about_failed_payment.description">
+            <de><![CDATA[Es soll eine Benachrichtigung per E-Mail an den Administrator gesendet werden, wenn eine Stripe-Zahlung fehlgeschlagen ist (Einmalzahlung / Abonnement-Zahlung).]]></de>
+            <en><![CDATA[An e-mail notification should be sent to the administrator if a Stripe payment has failed (one-time payment / subscription payment).]]></en>
+        </locale>
 
         <!-- Cron -->
         <locale name="cron.processUnpaidInvoices.title">
@@ -324,6 +408,14 @@
             <de><![CDATA[Für alle offenen Rechnungen mit wiederkehrenden Zahlungen die passenden Stripe-Transaktionen (Invoices) finden und intern buchen]]></de>
             <en><![CDATA[For all open invoices with recurring payments, find the matching Stripe transactions (Invoices) and book them internally]]></en>
         </locale>
+        <locale name="cron.PendingPaymentsService.title">
+            <de><![CDATA[Stripe: Ausstehende Zahlungen prüfen]]></de>
+            <en><![CDATA[Stripe: Check pending payments]]></en>
+        </locale>
+        <locale name="cron.PendingPaymentsService.description">
+            <de><![CDATA[Fragt ausstehende Transaktionen bei Stripe ab und bucht diese im ecoyn-System]]></de>
+            <en><![CDATA[Queries pending transactions from Stripe and books them in the ecoyn system]]></en>
+        </locale>
 
         <!-- Permissions -->
         <locale name="permission.quiqqer.payments.stripe._header">
@@ -359,6 +451,54 @@
 
     <groups name="quiqqer/payment-stripe" datatype="php">
 
+        <!-- Mails -->
+        <locale name="mail.payment_failed.order.subject">
+            <de><![CDATA[Stripe-Zahlung für Bestellung [orderId] fehlgeschlagen]]></de>
+            <en><![CDATA[Stripe payment for order [orderId] failed]]></en>
+        </locale>
+        <locale name="mail.payment_failed.order.body" html="true">
+            <de><![CDATA[
+<p>Stripe hat die Zahlung für Bestellung <b>[orderId]</b> als fehlgeschlagen gemeldet. Die Stripe Transaktions-ID lautet: [stripePaymentIntentId] (<a href="[paymentIntentUrl]" target="_blank">Ansicht im Stripe Dashboard</a>).</p>
+<p><b>Fehlermeldung:</b> [errorMsg]</p>
+]]></de>
+            <en><![CDATA[
+            <p>Stripe has reported the payment for order <b>[orderId]</b> as failed. The Stripe transaction ID is: [stripePaymentIntentId] (<a href=“[paymentIntentUrl]" target=“_blank”>view in the Stripe dashboard</a>).</p>
+<p><b>Error message:</b> [errorMsg]</p>
+]]></en>
+        </locale>
+        <locale name="mail.payment_failed.invoice.subject">
+            <de><![CDATA[Stripe-Zahlung für Rechnung [invoiceNo] fehlgeschlagen]]></de>
+            <en><![CDATA[Stripe payment for invoice [invoiceNo] failed]]></en>
+        </locale>
+        <locale name="mail.payment_failed.invoice.body" html="true">
+            <de><![CDATA[
+<p>Stripe hat die Zahlung für Rechnung <b>[invoiceNo]</b> als fehlgeschlagen gemeldet. Die Stripe Transaktions-ID lautet: [stripeInvoiceId] (<a href="[stripeInvoiceUrl]" target="_blank">Ansicht im Stripe Dashboard</a>).</p>
+<p><b>Fehlermeldung:</b> [errorMsg]</p>
+<p><b>Vertrag:</b> [contractNo]</p>
+]]></de>
+            <en><![CDATA[
+            <p>Stripe has reported the payment for invoice <b>[invoiceNo]</b> as failed. The Stripe transaction ID is: [stripeInvoiceId] ().</p>
+<p><b>Error message:</b> [errorMsg]</p>
+<p><b>Contract:</b> [contractNo]</p>
+]]></en>
+        </locale>
+        <locale name="mail.disputed_transaction.subject">
+            <de><![CDATA[Anfechtung einer Stripe-Transaktion!]]></de>
+            <en><![CDATA[Dispute of a Stripe transaction]]></en>
+        </locale>
+        <locale name="mail.disputed_transaction.body" html="true">
+            <de><![CDATA[
+<p>Eine Stripe-Transaktion i.H.v. [amount] [currency] wurde angefochten. <a href="[chargeUrl]" target="_blank">Ansicht im Stripe Dashboard</a>.</p>
+<p><b>Verknüpfte Bestellung:</b> [orderId]</p>
+<p><b>Begründungs Code:</b> [reason] (<a href="https://docs.stripe.com/disputes/categories" target="_blank">weitere Informationen</a>)</p>
+]]></de>
+            <en><![CDATA[
+<p>A stripe transaction in the amount of [amount] [currency] has been disputed. <a href="[chargeUrl]" target="_blank">View in Stripe dashboard</a>.</p>
+<p><b>Associated order:</b> [orderId]</p>
+<p><b>Reason code:</b> [reason] (<a href="https://docs.stripe.com/disputes/categories" target="_blank">additional information</a>)</p>
+]]></en>
+        </locale>
+
         <!-- Ajax messages -->
         <locale name="message.ajax.backend.settings.deleteBillingPlan.success">
             <de><![CDATA[Der Abrechnungsplan [planId] wurde erfolgreich gelöscht.]]></de>
@@ -521,12 +661,12 @@
             <en><![CDATA[Click here to pay your order with a value of [display_price] with your credit card]]></en>
         </locale>
         <locale name="PaymentDisplay.checkout_process">
-            <de><![CDATA[Zahlung mit Kreditkarte wird durchgeführt]]></de>
-            <en><![CDATA[Credit card payment is being processed]]></en>
+            <de><![CDATA[Zahlung wird durchgeführt]]></de>
+            <en><![CDATA[Payment is being processed]]></en>
         </locale>
         <locale name="PaymentDisplay.validation_error">
-            <de><![CDATA[Bei der Validierung Ihrer Kreditkarten-Daten ist ein Fehler aufgetreten. Bitte überprüfen Sie Ihre Eingaben noch einmal und korrigieren diese ggf.]]></de>
-            <en><![CDATA[An error occurred during the validation of your credit card data. Please check your entries again and correct them if necessary.]]></en>
+            <de><![CDATA[Bei der Validierung Ihrer Zahlungsdaten ist ein Fehler aufgetreten. Bitte überprüfen Sie Ihre Eingaben noch einmal und korrigieren diese ggf.]]></de>
+            <en><![CDATA[An error occurred during the validation of your payment data data. Please check your entries again and correct them if necessary.]]></en>
         </locale>
         <locale name="PaymentDisplay.service_provider_error">
             <de>
diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
index 02fd72b..b321498 100644
--- a/phpstan-baseline.neon
+++ b/phpstan-baseline.neon
@@ -1,683 +1,23 @@
 parameters:
 	ignoreErrors:
-		-
-			message: "#^Call to static method getInstance\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\Handler\\.$#"
-			count: 1
-			path: ajax/confirmPaymentIntent.php
-
-		-
-			message: "#^Call to static method getInstance\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\Handler\\.$#"
-			count: 1
-			path: ajax/confirmSubscription.php
-
-		-
-			message: "#^Call to static method getInstance\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\Handler\\.$#"
-			count: 1
-			path: ajax/createPaymentIntent.php
-
-		-
-			message: "#^Call to static method getInstance\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\Handler\\.$#"
-			count: 1
-			path: ajax/createSubscription.php
-
 		-
 			message: "#^Access to an undefined property Stripe\\\\StripeObject\\:\\:\\$type\\.$#"
 			count: 1
 			path: src/QUI/ERP/Payments/Stripe/AbstractBasePayment.php
 
-		-
-			message: "#^Call to method addHistory\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/AbstractBasePayment.php
-
-		-
-			message: "#^Call to method getPaymentDataEntry\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 4
-			path: src/QUI/ERP/Payments/Stripe/AbstractBasePayment.php
-
-		-
-			message: "#^Call to method getUUID\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/AbstractBasePayment.php
-
-		-
-			message: "#^Call to method setContent\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\Controls\\\\OrderProcess\\\\Processing\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/AbstractBasePayment.php
-
-		-
-			message: "#^Call to method setPaymentData\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/AbstractBasePayment.php
-
-		-
-			message: "#^Call to method setSuccessfulStatus\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/AbstractBasePayment.php
-
-		-
-			message: "#^Call to method setTitle\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\Controls\\\\OrderProcess\\\\Processing\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/AbstractBasePayment.php
-
-		-
-			message: "#^Call to method update\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/AbstractBasePayment.php
-
-		-
-			message: "#^Call to static method getInstance\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\Handler\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/AbstractBasePayment.php
-
-		-
-			message: "#^Parameter \\$Order of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\AbstractBasePayment\\:\\:addOrderHistoryEntry\\(\\) has invalid type QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/AbstractBasePayment.php
-
-		-
-			message: "#^Parameter \\$Order of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\AbstractBasePayment\\:\\:confirmPaymentIntent\\(\\) has invalid type QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/AbstractBasePayment.php
-
-		-
-			message: "#^Parameter \\$Order of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\AbstractBasePayment\\:\\:createPaymentIntent\\(\\) has invalid type QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/AbstractBasePayment.php
-
-		-
-			message: "#^Parameter \\$Order of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\AbstractBasePayment\\:\\:createPaymentIntentForOrder\\(\\) has invalid type QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/AbstractBasePayment.php
-
-		-
-			message: "#^Parameter \\$Order of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\AbstractBasePayment\\:\\:getGatewayDisplay\\(\\) has invalid type QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/AbstractBasePayment.php
-
-		-
-			message: "#^Parameter \\$Order of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\AbstractBasePayment\\:\\:getPaymentIntentByOrder\\(\\) has invalid type QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/AbstractBasePayment.php
-
-		-
-			message: "#^Parameter \\$Order of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\AbstractBasePayment\\:\\:saveOrder\\(\\) has invalid type QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/AbstractBasePayment.php
-
-		-
-			message: "#^Parameter \\$Step of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\AbstractBasePayment\\:\\:getGatewayDisplay\\(\\) has invalid type QUI\\\\ERP\\\\Order\\\\Controls\\\\OrderProcess\\\\Processing\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/AbstractBasePayment.php
-
-		-
-			message: "#^Parameter \\$Order of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\Events\\:\\:onPaymentsCanUsedInOrder\\(\\) has invalid type QUI\\\\ERP\\\\Order\\\\OrderInterface\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/Events.php
-
-		-
-			message: "#^Call to method getCurrency\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/AbstractBrowserPay.php
-
-		-
-			message: "#^Parameter \\#1 \\$Source of static method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\Utils\\:\\:getCentAmount\\(\\) expects QUI\\\\ERP\\\\ErpEntityInterface, QUI\\\\ERP\\\\Order\\\\AbstractOrder given\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/AbstractBrowserPay.php
-
-		-
-			message: "#^Parameter \\$Order of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\PaymentMethods\\\\AbstractBrowserPay\\:\\:createPaymentIntentForOrder\\(\\) has invalid type QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/AbstractBrowserPay.php
-
-		-
-			message: "#^Call to method getCustomer\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/ApplePay.php
-
-		-
-			message: "#^Call to method getCustomer\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceTemporary\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/ApplePay.php
-
-		-
-			message: "#^Call to method getCustomer\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceView\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/ApplePay.php
-
-		-
-			message: "#^PHPDoc tag @return with type mixed is not subtype of native type string\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/ApplePay.php
-
-		-
-			message: "#^Parameter \\$Invoice of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\PaymentMethods\\\\ApplePay\\:\\:getInvoiceInformationText\\(\\) has invalid type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/ApplePay.php
-
-		-
-			message: "#^Parameter \\$Invoice of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\PaymentMethods\\\\ApplePay\\:\\:getInvoiceInformationText\\(\\) has invalid type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceTemporary\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/ApplePay.php
-
-		-
-			message: "#^Parameter \\$Invoice of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\PaymentMethods\\\\ApplePay\\:\\:getInvoiceInformationText\\(\\) has invalid type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceView\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/ApplePay.php
-
-		-
-			message: "#^Call to method getCurrency\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Card.php
-
-		-
-			message: "#^Call to method getCustomer\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Card.php
-
-		-
-			message: "#^Call to method getCustomer\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceTemporary\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Card.php
-
-		-
-			message: "#^Call to method getCustomer\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceView\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Card.php
-
-		-
-			message: "#^PHPDoc tag @return with type mixed is not subtype of native type string\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Card.php
-
-		-
-			message: "#^Parameter \\#1 \\$Source of static method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\Utils\\:\\:getCentAmount\\(\\) expects QUI\\\\ERP\\\\ErpEntityInterface, QUI\\\\ERP\\\\Order\\\\AbstractOrder given\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Card.php
-
-		-
-			message: "#^Parameter \\$Invoice of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\PaymentMethods\\\\Card\\:\\:getInvoiceInformationText\\(\\) has invalid type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Card.php
-
-		-
-			message: "#^Parameter \\$Invoice of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\PaymentMethods\\\\Card\\:\\:getInvoiceInformationText\\(\\) has invalid type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceTemporary\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Card.php
-
-		-
-			message: "#^Parameter \\$Invoice of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\PaymentMethods\\\\Card\\:\\:getInvoiceInformationText\\(\\) has invalid type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceView\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Card.php
-
-		-
-			message: "#^Parameter \\$Order of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\PaymentMethods\\\\Card\\:\\:createPaymentIntentForOrder\\(\\) has invalid type QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Card.php
-
-		-
-			message: "#^Call to method getCustomer\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/GooglePay.php
-
-		-
-			message: "#^Call to method getCustomer\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceTemporary\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/GooglePay.php
-
-		-
-			message: "#^Call to method getCustomer\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceView\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/GooglePay.php
-
-		-
-			message: "#^PHPDoc tag @return with type mixed is not subtype of native type string\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/GooglePay.php
-
-		-
-			message: "#^Parameter \\$Invoice of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\PaymentMethods\\\\GooglePay\\:\\:getInvoiceInformationText\\(\\) has invalid type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/GooglePay.php
-
-		-
-			message: "#^Parameter \\$Invoice of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\PaymentMethods\\\\GooglePay\\:\\:getInvoiceInformationText\\(\\) has invalid type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceTemporary\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/GooglePay.php
-
-		-
-			message: "#^Parameter \\$Invoice of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\PaymentMethods\\\\GooglePay\\:\\:getInvoiceInformationText\\(\\) has invalid type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceView\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/GooglePay.php
-
-		-
-			message: "#^Call to method getCustomer\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/MicrosoftPay.php
-
-		-
-			message: "#^Call to method getCustomer\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceTemporary\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/MicrosoftPay.php
-
-		-
-			message: "#^Call to method getCustomer\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceView\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/MicrosoftPay.php
-
-		-
-			message: "#^PHPDoc tag @return with type mixed is not subtype of native type string\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/MicrosoftPay.php
-
-		-
-			message: "#^Parameter \\$Invoice of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\PaymentMethods\\\\MicrosoftPay\\:\\:getInvoiceInformationText\\(\\) has invalid type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/MicrosoftPay.php
-
-		-
-			message: "#^Parameter \\$Invoice of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\PaymentMethods\\\\MicrosoftPay\\:\\:getInvoiceInformationText\\(\\) has invalid type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceTemporary\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/MicrosoftPay.php
-
-		-
-			message: "#^Parameter \\$Invoice of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\PaymentMethods\\\\MicrosoftPay\\:\\:getInvoiceInformationText\\(\\) has invalid type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceView\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/MicrosoftPay.php
-
-		-
-			message: "#^Call to method getCurrency\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/AbstractBaseRecurringPayment.php
-
-		-
-			message: "#^Call to method getUUID\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/AbstractBaseRecurringPayment.php
-
-		-
-			message: "#^Call to method setContent\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\Controls\\\\OrderProcess\\\\Processing\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/AbstractBaseRecurringPayment.php
-
-		-
-			message: "#^Call to method setTitle\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\Controls\\\\OrderProcess\\\\Processing\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/AbstractBaseRecurringPayment.php
-
-		-
-			message: "#^Method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\PaymentMethods\\\\Recurring\\\\AbstractBaseRecurringPayment\\:\\:createSubscription\\(\\) should return string\\|null but returns int\\|false\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/AbstractBaseRecurringPayment.php
-
-		-
-			message: "#^Parameter \\#1 \\$Source of static method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\Utils\\:\\:getCentAmount\\(\\) expects QUI\\\\ERP\\\\ErpEntityInterface, QUI\\\\ERP\\\\Order\\\\AbstractOrder given\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/AbstractBaseRecurringPayment.php
-
-		-
-			message: "#^Parameter \\$Invoice of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\PaymentMethods\\\\Recurring\\\\AbstractBaseRecurringPayment\\:\\:captureSubscription\\(\\) has invalid type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/AbstractBaseRecurringPayment.php
-
-		-
-			message: "#^Parameter \\$Order of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\PaymentMethods\\\\Recurring\\\\AbstractBaseRecurringPayment\\:\\:createPaymentIntentForOrder\\(\\) has invalid type QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/AbstractBaseRecurringPayment.php
-
-		-
-			message: "#^Parameter \\$Order of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\PaymentMethods\\\\Recurring\\\\AbstractBaseRecurringPayment\\:\\:createSubscription\\(\\) has invalid type QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/AbstractBaseRecurringPayment.php
-
-		-
-			message: "#^Parameter \\$Order of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\PaymentMethods\\\\Recurring\\\\AbstractBaseRecurringPayment\\:\\:getGatewayDisplay\\(\\) has invalid type QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/AbstractBaseRecurringPayment.php
-
-		-
-			message: "#^Parameter \\$Order of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\PaymentMethods\\\\Recurring\\\\AbstractBaseRecurringPayment\\:\\:getSubscriptionIdByOrder\\(\\) has invalid type QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/AbstractBaseRecurringPayment.php
-
-		-
-			message: "#^Parameter \\$Step of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\PaymentMethods\\\\Recurring\\\\AbstractBaseRecurringPayment\\:\\:getGatewayDisplay\\(\\) has invalid type QUI\\\\ERP\\\\Order\\\\Controls\\\\OrderProcess\\\\Processing\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/AbstractBaseRecurringPayment.php
-
-		-
-			message: "#^Call to method getCustomer\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/ApplePay.php
-
-		-
-			message: "#^Call to method getCustomer\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceTemporary\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/ApplePay.php
-
-		-
-			message: "#^Call to method getCustomer\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceView\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/ApplePay.php
-
-		-
-			message: "#^PHPDoc tag @return with type mixed is not subtype of native type string\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/ApplePay.php
-
-		-
-			message: "#^Parameter \\$Invoice of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\PaymentMethods\\\\Recurring\\\\ApplePay\\:\\:getInvoiceInformationText\\(\\) has invalid type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/ApplePay.php
-
-		-
-			message: "#^Parameter \\$Invoice of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\PaymentMethods\\\\Recurring\\\\ApplePay\\:\\:getInvoiceInformationText\\(\\) has invalid type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceTemporary\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/ApplePay.php
-
-		-
-			message: "#^Parameter \\$Invoice of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\PaymentMethods\\\\Recurring\\\\ApplePay\\:\\:getInvoiceInformationText\\(\\) has invalid type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceView\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/ApplePay.php
-
-		-
-			message: "#^Call to method getArticles\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/BillingPlans.php
-
-		-
-			message: "#^Call to method getCurrency\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/BillingPlans.php
-
-		-
-			message: "#^Call to method getCustomer\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/BillingPlans.php
-
-		-
-			message: "#^Call to method getPaymentDataEntry\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/BillingPlans.php
-
-		-
-			message: "#^Call to method getPriceCalculation\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/BillingPlans.php
-
-		-
-			message: "#^Call to method getUUID\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/BillingPlans.php
-
-		-
-			message: "#^Parameter \\#1 \\$Source of static method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\Utils\\:\\:getCentAmount\\(\\) expects QUI\\\\ERP\\\\ErpEntityInterface, QUI\\\\ERP\\\\Order\\\\AbstractOrder given\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/BillingPlans.php
-
-		-
-			message: "#^Parameter \\$Order of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\PaymentMethods\\\\Recurring\\\\BillingPlans\\:\\:createBillingPlanFromOrder\\(\\) has invalid type QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/BillingPlans.php
-
-		-
-			message: "#^Parameter \\$Order of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\PaymentMethods\\\\Recurring\\\\BillingPlans\\:\\:getIdentificationHash\\(\\) has invalid type QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/BillingPlans.php
-
-		-
-			message: "#^Parameter \\$Order of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\PaymentMethods\\\\Recurring\\\\BillingPlans\\:\\:getStripeBillingPlanIdByOrder\\(\\) has invalid type QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/BillingPlans.php
-
-		-
-			message: "#^Call to method getCustomer\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/Card.php
-
-		-
-			message: "#^Call to method getCustomer\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceView\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/Card.php
-
-		-
-			message: "#^PHPDoc tag @return with type mixed is not subtype of native type string\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/Card.php
-
-		-
-			message: "#^Parameter \\$Invoice of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\PaymentMethods\\\\Recurring\\\\Card\\:\\:getInvoiceInformationText\\(\\) has invalid type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/Card.php
-
-		-
-			message: "#^Parameter \\$Invoice of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\PaymentMethods\\\\Recurring\\\\Card\\:\\:getInvoiceInformationText\\(\\) has invalid type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceView\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/Card.php
-
-		-
-			message: "#^Call to method getCustomer\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/GooglePay.php
-
-		-
-			message: "#^Call to method getCustomer\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceTemporary\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/GooglePay.php
-
-		-
-			message: "#^Call to method getCustomer\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceView\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/GooglePay.php
-
-		-
-			message: "#^PHPDoc tag @return with type mixed is not subtype of native type string\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/GooglePay.php
-
-		-
-			message: "#^Parameter \\$Invoice of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\PaymentMethods\\\\Recurring\\\\GooglePay\\:\\:getInvoiceInformationText\\(\\) has invalid type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/GooglePay.php
-
-		-
-			message: "#^Parameter \\$Invoice of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\PaymentMethods\\\\Recurring\\\\GooglePay\\:\\:getInvoiceInformationText\\(\\) has invalid type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceTemporary\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/GooglePay.php
-
-		-
-			message: "#^Parameter \\$Invoice of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\PaymentMethods\\\\Recurring\\\\GooglePay\\:\\:getInvoiceInformationText\\(\\) has invalid type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceView\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/GooglePay.php
-
-		-
-			message: "#^Call to method getCustomer\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/MicrosoftPay.php
-
-		-
-			message: "#^Call to method getCustomer\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceTemporary\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/MicrosoftPay.php
-
-		-
-			message: "#^Call to method getCustomer\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceView\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/MicrosoftPay.php
-
-		-
-			message: "#^PHPDoc tag @return with type mixed is not subtype of native type string\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/MicrosoftPay.php
-
-		-
-			message: "#^Parameter \\$Invoice of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\PaymentMethods\\\\Recurring\\\\MicrosoftPay\\:\\:getInvoiceInformationText\\(\\) has invalid type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/MicrosoftPay.php
-
-		-
-			message: "#^Parameter \\$Invoice of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\PaymentMethods\\\\Recurring\\\\MicrosoftPay\\:\\:getInvoiceInformationText\\(\\) has invalid type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceTemporary\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/MicrosoftPay.php
-
-		-
-			message: "#^Parameter \\$Invoice of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\PaymentMethods\\\\Recurring\\\\MicrosoftPay\\:\\:getInvoiceInformationText\\(\\) has invalid type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceView\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/MicrosoftPay.php
-
-		-
-			message: "#^Call to method addHistory\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/Subscriptions.php
-
-		-
-			message: "#^Call to method addHistory\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/Subscriptions.php
-
-		-
-			message: "#^Call to method addTransaction\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/Subscriptions.php
-
-		-
-			message: "#^Call to method calculatePayments\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/Subscriptions.php
-
-		-
-			message: "#^Call to method getAttribute\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/Subscriptions.php
-
-		-
-			message: "#^Call to method getCurrency\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
-			count: 4
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/Subscriptions.php
-
-		-
-			message: "#^Call to method getCustomer\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/Subscriptions.php
-
-		-
-			message: "#^Call to method getGlobalProcessId\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/Subscriptions.php
-
-		-
-			message: "#^Call to method getPaymentDataEntry\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/Subscriptions.php
-
-		-
-			message: "#^Call to method getPaymentDataEntry\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 4
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/Subscriptions.php
-
-		-
-			message: "#^Call to method getUUID\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
-			count: 3
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/Subscriptions.php
-
-		-
-			message: "#^Call to method getUUID\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/Subscriptions.php
-
-		-
-			message: "#^Call to method setPaymentData\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 4
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/Subscriptions.php
-
-		-
-			message: "#^Call to method update\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 3
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/Subscriptions.php
-
-		-
-			message: "#^Call to static method getInstance\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Handler\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/Subscriptions.php
-
-		-
-			message: "#^Method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\PaymentMethods\\\\Recurring\\\\Subscriptions\\:\\:createSubscription\\(\\) should return int\\|false but returns string\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/Subscriptions.php
-
-		-
-			message: "#^Parameter \\#1 \\$Source of static method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\Utils\\:\\:getCentAmount\\(\\) expects QUI\\\\ERP\\\\ErpEntityInterface, QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice given\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/Subscriptions.php
-
-		-
-			message: "#^Parameter \\$Invoice of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\PaymentMethods\\\\Recurring\\\\Subscriptions\\:\\:billSubscriptionBalance\\(\\) has invalid type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/Subscriptions.php
-
-		-
-			message: "#^Parameter \\$Invoice of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\PaymentMethods\\\\Recurring\\\\Subscriptions\\:\\:processDeniedTransactions\\(\\) has invalid type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/Subscriptions.php
-
-		-
-			message: "#^Parameter \\$Order of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\PaymentMethods\\\\Recurring\\\\Subscriptions\\:\\:createSubscription\\(\\) has invalid type QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/Subscriptions.php
-
 		-
 			message: "#^Variable \\$Payment in PHPDoc tag @var does not match assigned variable \\$paymentMethodData\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/Subscriptions.php
-
-		-
-			message: "#^Call to method getAttribute\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/Utils.php
-
-		-
-			message: "#^Call to method getCurrency\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
 			count: 1
-			path: src/QUI/ERP/Payments/Stripe/Utils.php
-
-		-
-			message: "#^Call to method getCustomer\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/Utils.php
-
-		-
-			message: "#^Call to method getPrefixedId\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/Utils.php
-
-		-
-			message: "#^Call to method getPriceCalculation\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/Utils.php
-
-		-
-			message: "#^Class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice not found\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/Utils.php
-
-		-
-			message: "#^Class QUI\\\\ERP\\\\Order\\\\AbstractOrder not found\\.$#"
-			count: 1
-			path: src/QUI/ERP/Payments/Stripe/Utils.php
+			path: src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/Subscriptions.php
 
 		-
 			message: "#^Parameter \\#1 \\$precision of method QUI\\\\ERP\\\\Accounting\\\\CalculationValue\\:\\:precision\\(\\) expects bool, int given\\.$#"
 			count: 1
 			path: src/QUI/ERP/Payments/Stripe/Utils.php
 
-		-
-			message: "#^Parameter \\$Order of method QUI\\\\ERP\\\\Payments\\\\Stripe\\\\Utils\\:\\:getPaymentDescriptionForOrder\\(\\) has invalid type QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
-			count: 2
-			path: src/QUI/ERP/Payments/Stripe/Utils.php
-
 		-
 			message: "#^Access to an undefined property Stripe\\\\StripeObject\\:\\:\\$object\\.$#"
-			count: 3
+			count: 5
 			path: src/QUI/ERP/Payments/Stripe/WebhookHandler.php
 
 		-
diff --git a/settings.xml b/settings.xml
index 127cc52..1a63a4e 100644
--- a/settings.xml
+++ b/settings.xml
@@ -8,6 +8,17 @@
                 <conf name="payment_description">
                     <type><![CDATA[string]]></type>
                 </conf>
+                <conf name="statement_descriptor">
+                    <type><![CDATA[string]]></type>
+                </conf>
+                <conf name="notify_about_failed_payment">
+                    <type><![CDATA[boolean]]></type>
+                    <defaultvalue>1</defaultvalue>
+                </conf>
+                <conf name="notify_about_failed_invoice_payment">
+                    <type><![CDATA[boolean]]></type>
+                    <defaultvalue>1</defaultvalue>
+                </conf>
             </section>
 
             <section name="api">
@@ -34,7 +45,6 @@
                     <type><![CDATA[string]]></type>
                 </conf>
             </section>
-
         </config>
 
         <window>
@@ -60,10 +70,34 @@
 
                         <input conf="general.payment_description" type="text">
                             <text>
-                                <locale group="quiqqer/payment-stripe" var="settings.general.payment_description.title"/>
+                                <locale group="quiqqer/payment-stripe"
+                                        var="settings.general.payment_description.title"/>
+                            </text>
+                            <description>
+                                <locale group="quiqqer/payment-stripe"
+                                        var="settings.general.payment_description.description"/>
+                            </description>
+                        </input>
+
+                        <input conf="general.statement_descriptor" type="text" maxlength="22">
+                            <text>
+                                <locale group="quiqqer/payment-stripe"
+                                        var="settings.general.statement_descriptor.title"/>
+                            </text>
+                            <description>
+                                <locale group="quiqqer/payment-stripe"
+                                        var="settings.general.statement_descriptor.description"/>
+                            </description>
+                        </input>
+
+                        <input conf="general.notify_about_failed_payment" type="checkbox">
+                            <text>
+                                <locale group="quiqqer/payment-stripe"
+                                        var="settings.general.notify_about_failed_payment.title"/>
                             </text>
                             <description>
-                                <locale group="quiqqer/payment-stripe" var="settings.general.payment_description.description"/>
+                                <locale group="quiqqer/payment-stripe"
+                                        var="settings.general.notify_about_failed_payment.description"/>
                             </description>
                         </input>
 
@@ -103,7 +137,8 @@
                                 <locale group="quiqqer/payment-stripe" var="settings.api.sandbox_public_key.title"/>
                             </text>
                             <description>
-                                <locale group="quiqqer/payment-stripe" var="settings.api.sandbox_public_key.description"/>
+                                <locale group="quiqqer/payment-stripe"
+                                        var="settings.api.sandbox_public_key.description"/>
                             </description>
                         </input>
 
@@ -112,7 +147,8 @@
                                 <locale group="quiqqer/payment-stripe" var="settings.api.sandbox_client_secret.title"/>
                             </text>
                             <description>
-                                <locale group="quiqqer/payment-stripe" var="settings.api.sandbox_client_secret.description"/>
+                                <locale group="quiqqer/payment-stripe"
+                                        var="settings.api.sandbox_client_secret.description"/>
                             </description>
                         </input>
 
@@ -132,12 +168,15 @@
                             <locale group="quiqqer/payment-stripe" var="settings.payment.title"/>
                         </title>
 
-                        <input conf="payment.apple_pay_domains" type="text" data-qui="package/quiqqer/payment-stripe/bin/controls/backend/settings/ApplePayRegister" label="false">
+                        <input conf="payment.apple_pay_domains" type="text"
+                               data-qui="package/quiqqer/payment-stripe/bin/controls/backend/settings/ApplePayRegister"
+                               label="false">
                             <text>
                                 <locale group="quiqqer/payment-stripe" var="settings.payment.apple_pay_domains.title"/>
                             </text>
                             <description>
-                                <locale group="quiqqer/payment-stripe" var="settings.payment.apple_pay_domains.description"/>
+                                <locale group="quiqqer/payment-stripe"
+                                        var="settings.payment.apple_pay_domains.description"/>
                             </description>
                         </input>
 
@@ -155,7 +194,9 @@
                             <locale group="quiqqer/payment-stripe" var="settings.category.stripe_billing_plans.title"/>
                         </title>
 
-                        <input type="hidden" data-qui="package/quiqqer/payment-stripe/bin/controls/backend/settings/BillingPlans" label="false">
+                        <input type="hidden"
+                               data-qui="package/quiqqer/payment-stripe/bin/controls/backend/settings/BillingPlans"
+                               label="false">
                         </input>
                     </settings>
 
@@ -164,15 +205,19 @@
                 <category name="stripe_billing_plan_subscriptions">
                     <icon>fa fa-cc-stripe</icon>
                     <title>
-                        <locale group="quiqqer/payment-stripe" var="settings.category.stripe_billing_plan_subscriptions.title"/>
+                        <locale group="quiqqer/payment-stripe"
+                                var="settings.category.stripe_billing_plan_subscriptions.title"/>
                     </title>
 
                     <settings name="stripe_billing_plan_subscriptions" title="stripe_billing_plan_subscriptions">
                         <title>
-                            <locale group="quiqqer/payment-stripe" var="settings.category.stripe_billing_plan_subscriptions.title"/>
+                            <locale group="quiqqer/payment-stripe"
+                                    var="settings.category.stripe_billing_plan_subscriptions.title"/>
                         </title>
 
-                        <input type="hidden" data-qui="package/quiqqer/payment-stripe/bin/controls/backend/settings/Subscriptions" label="false">
+                        <input type="hidden"
+                               data-qui="package/quiqqer/payment-stripe/bin/controls/backend/settings/Subscriptions"
+                               label="false">
                         </input>
                     </settings>
 
diff --git a/src/QUI/ERP/Payments/Stripe/AbstractBasePayment.php b/src/QUI/ERP/Payments/Stripe/AbstractBasePayment.php
index 119c420..d5d1268 100644
--- a/src/QUI/ERP/Payments/Stripe/AbstractBasePayment.php
+++ b/src/QUI/ERP/Payments/Stripe/AbstractBasePayment.php
@@ -4,11 +4,11 @@
 
 use Exception;
 use QUI;
-use QUI\ERP\Accounting\Payments\Gateway\Gateway;
 use QUI\ERP\Accounting\Payments\Transactions\Factory as TransactionFactory;
 use QUI\ERP\Accounting\Payments\Transactions\Transaction;
 use QUI\ERP\Order\AbstractOrder;
 use QUI\ERP\Order\Handler as OrderHandler;
+use QUI\ERP\Order\OrderInterface;
 use Stripe\Exception\ApiErrorException;
 use Stripe\PaymentIntent as StripePaymentIntent;
 use Stripe\Refund as StripeRefund;
@@ -135,9 +135,9 @@ public function refundSupport(): bool
      */
     public function refund(
         Transaction $Transaction,
-        float|int $amount,
+        float | int $amount,
         string $message = '',
-        bool|string $hash = false
+        bool | string $hash = false
     ): void {
         try {
             if ($hash === false) {
@@ -210,7 +210,7 @@ public function getGatewayDisplay(AbstractOrder $Order, $Step = null): string
      * @throws ApiErrorException
      * @throws StripeException
      */
-    public function createPaymentIntent(AbstractOrder $Order, string $paymentMethodId): StripePaymentIntent|bool
+    public function createPaymentIntent(AbstractOrder $Order, string $paymentMethodId): StripePaymentIntent | bool
     {
         if (!empty($Order->getPaymentDataEntry(self::ATTR_STRIPE_PAYMENT_INTENT_ID))) {
             $PaymentIntent = $this->getPaymentIntentByOrder($Order);
@@ -275,47 +275,30 @@ public function confirmPaymentIntent(AbstractOrder $Order, StripePaymentIntent $
 
         if (
             $PaymentIntent->status === $PaymentIntent::STATUS_REQUIRES_ACTION
-            && $PaymentIntent->next_action->type === 'use_stripe_sdk'
+            && $PaymentIntent->next_action?->type === 'use_stripe_sdk'
         ) {
             $confirmData['status'] = 'action_required';
             $confirmData['clientSecret'] = $PaymentIntent->client_secret;
 
             $this->addOrderHistoryEntry($Order, 'Additional user action required for PaymentIntent confirmation.');
-        } elseif ($PaymentIntent->status === $PaymentIntent::STATUS_SUCCEEDED) {
-            $confirmData['status'] = 'success';
-
-            $this->addOrderHistoryEntry($Order, 'PaymentIntent successully confirmed.');
-
-            try {
-                $Order->setSuccessfulStatus();
-                $Order->setPaymentData(self::ATTR_STRIPE_ORDER_SUCCESSFUL, true);
-
-                $this->saveOrder($Order);
-
-                $Transaction = Gateway::getInstance()->purchase(
-                    $PaymentIntent->amount_received / 100,
-                    QUI\ERP\Currency\Handler::getCurrency(mb_strtoupper($PaymentIntent->currency)),
-                    $Order,
-                    $this
-                );
-
-                $Transaction->setData(
-                    self::ATTR_STRIPE_PAYMENT_INTENT_ID,
-                    $Order->getPaymentDataEntry(self::ATTR_STRIPE_PAYMENT_INTENT_ID)
-                );
-
-                $Transaction->updateData();
-            } catch (Exception $Exception) {
-                QUI\System\Log::writeException($Exception);
-
-                $this->addOrderHistoryEntry(
-                    $Order,
-                    'Error while trying to set order as successful: ' . $Exception->getMessage()
-                );
-            }
         } else {
-            $confirmData['status'] = 'error';
-            $this->addOrderHistoryEntry($Order, 'Confirmation error.');
+            $orderProcessingService = new OrderProcessingService($Order);
+            $orderProcessingService->processPaymentIntent($PaymentIntent);
+
+            switch ($PaymentIntent->status) {
+                case StripePaymentIntent::STATUS_PROCESSING:
+//                    $confirmData['status'] = 'pending';
+                    $confirmData['status'] = 'success'; // TODO: special 'pending' status necessary?
+                    break;
+
+                case StripePaymentIntent::STATUS_SUCCEEDED:
+                    $confirmData['status'] = 'success';
+                    break;
+
+                default:
+                    $confirmData['status'] = 'error';
+                    break;
+            }
         }
 
         return $confirmData;
@@ -330,7 +313,7 @@ public function confirmPaymentIntent(AbstractOrder $Order, StripePaymentIntent $
      * @throws ApiErrorException
      * @throws StripeException
      */
-    public function getPaymentIntentByOrder(AbstractOrder $Order): StripePaymentIntent|bool
+    public function getPaymentIntentByOrder(AbstractOrder $Order): StripePaymentIntent | bool
     {
         $paymentIntentId = $Order->getPaymentDataEntry(self::ATTR_STRIPE_PAYMENT_INTENT_ID);
 
@@ -362,7 +345,7 @@ public function getPaymentIntentByOrder(AbstractOrder $Order): StripePaymentInte
     public function refundPayment(
         Transaction $Transaction,
         string $refundHash,
-        float|int $amount,
+        float | int $amount,
         string $reason = ''
     ): void {
         $Process = new QUI\ERP\Process($Transaction->getGlobalProcessId());
@@ -508,6 +491,11 @@ public function refundPayment(
         }
     }
 
+    public function canBeUsedInOrder(OrderInterface $order): bool
+    {
+        return true;
+    }
+
     /**
      * Add history entry for current Order
      *
diff --git a/src/QUI/ERP/Payments/Stripe/ChargeDisputeService.php b/src/QUI/ERP/Payments/Stripe/ChargeDisputeService.php
new file mode 100644
index 0000000..084a71c
--- /dev/null
+++ b/src/QUI/ERP/Payments/Stripe/ChargeDisputeService.php
@@ -0,0 +1,155 @@
+<?php
+
+namespace QUI\ERP\Payments\Stripe;
+
+use Doctrine\DBAL\Connection;
+use QUI;
+use QUI\ERP\Accounting\Payments\Transactions\Handler as TransactionHandler;
+use QUI\ERP\Accounting\Payments\Transactions\Transaction as EcoynTransaction;
+use QUI\ERP\Order\AbstractOrder;
+use QUI\ERP\Order\Handler as OrderHandler;
+use Stripe\Dispute;
+use Stripe\PaymentIntent;
+
+class ChargeDisputeService
+{
+    public function __construct(
+        private ?TransactionHandler $transactionHandler = null,
+        private ?OrderHandler $orderHandler = null,
+        private ?Connection $connection = null
+    ) {
+        if (is_null($this->transactionHandler)) {
+            $this->transactionHandler = TransactionHandler::getInstance();
+        }
+
+        if (is_null($this->orderHandler)) {
+            $this->orderHandler = OrderHandler::getInstance();
+        }
+
+        if (is_null($this->connection)) {
+            $this->connection = QUI::getDataBaseConnection();
+        }
+
+        Provider::setupApi();
+    }
+
+    public function processDispute(Dispute $dispute): void
+    {
+        $order = $this->getOrderByDispute($dispute);
+        Utils::sendNotificationAboutDisputedTransaction($dispute, $order);
+
+        if ($order) {
+            try {
+                $order->addHistory(
+                    'Stripe :: Order transaction disputed. Dispute ID: ' . $dispute->id
+                );
+                $order->update(QUI::getUsers()->getSystemUser());
+            } catch (\Exception $exception) {
+                QUI\System\Log::writeException($exception);
+            }
+        }
+
+        // If the dispute is lost, mark the corresponding ecoyn transaction as erroneous.
+        if ($dispute->status === Dispute::STATUS_LOST) {
+            $ecoynTransaction = $this->getEcoynTransactionByDispute($dispute);
+
+            try {
+                $ecoynTransaction?->changeStatus(TransactionHandler::STATUS_ERROR);
+            } catch (\Exception $exception) {
+                QUI\System\Log::writeException($exception);
+            }
+        }
+        // TODO: ggf. weiteres Vorgehen wie Transaktion rückgängig machen bzw. negative Transaktion erstellen
+    }
+
+    /**
+     * @param Dispute $dispute
+     * @return EcoynTransaction|null
+     */
+    private function getEcoynTransactionByDispute(Dispute $dispute): ?EcoynTransaction
+    {
+        if (!$dispute->payment_intent) {
+            return null;
+        }
+
+        try {
+            $paymentIntent = PaymentIntent::retrieve($dispute->payment_intent);
+        } catch (\Exception $exception) {
+            QUI\System\Log::writeException($exception);
+            return null;
+        }
+
+        if (empty($paymentIntent->invoice)) {
+            return null;
+        }
+
+        try {
+            $result = $this->connection
+                ->createQueryBuilder()
+                ->select('quiqqer_transaction_id')
+                ->from(Provider::getStripeBillingSubscriptionsTransactionsTable())
+                ->where('stripe_invoice_id = :stripe_invoice_id')
+                ->setParameter('stripe_invoice_id', $paymentIntent->invoice)
+                ->fetchAssociative();
+
+            if (empty($result['quiqqer_transaction_id'])) {
+                return null;
+            }
+
+            return $this->transactionHandler->get($result['quiqqer_transaction_id']);
+        } catch (\Exception | \Throwable $exception) {
+            if ($exception instanceof \Exception) {
+                QUI\System\Log::writeException($exception);
+            }
+            return null;
+        }
+    }
+
+    /**
+     * @param Dispute $dispute
+     * @return AbstractOrder|null
+     */
+    private function getOrderByDispute(Dispute $dispute): ?AbstractOrder
+    {
+        if (!$dispute->payment_intent) {
+            return null;
+        }
+
+        try {
+            $paymentIntent = PaymentIntent::retrieve([
+                'id' => $dispute->payment_intent,
+                'expand' => [
+                    'invoice.subscription'
+                ]
+            ]);
+        } catch (\Exception $exception) {
+            QUI\System\Log::writeException($exception);
+            return null;
+        }
+
+        $metaData = $paymentIntent->metadata->toArray();
+        $orderUuid = null;
+
+        if (!empty($metaData['orderUuid'])) {
+            $orderUuid = $metaData['orderUuid'];
+        } elseif (!empty($paymentIntent->invoice?->subscription)) {
+            $subscriptionMetaData = $paymentIntent->invoice->subscription->metadata->toArray();
+
+            if (!empty($subscriptionMetaData['orderUuid'])) {
+                $orderUuid = $subscriptionMetaData['orderUuid'];
+            }
+        }
+
+        if (is_null($orderUuid)) {
+            return null;
+        }
+
+        try {
+            return $this->orderHandler->get($orderUuid);
+        } catch (\Exception $exception) {
+            QUI\System\Log::writeException($exception);
+        }
+
+        return null;
+    }
+}
diff --git a/src/QUI/ERP/Payments/Stripe/Cron/PendingPaymentsService.php b/src/QUI/ERP/Payments/Stripe/Cron/PendingPaymentsService.php
new file mode 100644
index 0000000..1cf5d52
--- /dev/null
+++ b/src/QUI/ERP/Payments/Stripe/Cron/PendingPaymentsService.php
@@ -0,0 +1,140 @@
+<?php
+
+namespace QUI\ERP\Payments\Stripe\Cron;
+
+use Doctrine\DBAL\Connection;
+use Doctrine\DBAL\Exception;
+use QUI;
+use QUI\ERP\Accounting\Payments\Payments;
+use QUI\ERP\Accounting\Payments\Types\Payment as PaymentType;
+use QUI\ERP\Constants as ErpConstants;
+use QUI\ERP\Order\Handler as OrderHandler;
+use QUI\ERP\Order\Order;
+use QUI\ERP\Payments\Stripe\AbstractBasePayment;
+use QUI\ERP\Payments\Stripe\OrderProcessingService;
+use QUI\ERP\Payments\Stripe\Provider;
+use Stripe\PaymentIntent as StripePaymentIntent;
+
+use function implode;
+
+class PendingPaymentsService
+{
+    public function __construct(
+        private ?Payments $payments = null,
+        private ?OrderHandler $orderHandler = null,
+        private ?Connection $dbConnection = null
+    ) {
+        if (is_null($this->payments)) {
+            $this->payments = Payments::getInstance();
+        }
+
+        if (is_null($this->orderHandler)) {
+            $this->orderHandler = OrderHandler::getInstance();
+        }
+
+        if (is_null($this->dbConnection)) {
+            $this->dbConnection = QUI::getDataBaseConnection();
+        }
+
+        Provider::setupApi();
+    }
+
+    public static function execute(): void
+    {
+        $service = new PendingPaymentsService();
+        $service->checkPendingOrderPayments();
+    }
+
+    /**
+     * Check all pending stripe order payments.
+     *
+     * @return void
+     * @throws Exception
+     */
+    public function checkPendingOrderPayments(): void
+    {
+        $stripePaymentTypeIds = $this->getStripePaymentTypeIds();
+
+        // If no
+        if (empty($stripePaymentTypeIds)) {
+            return;
+        }
+
+        $paidStatusCodes =
+            [
+                ErpConstants::PAYMENT_STATUS_OPEN,
+                ErpConstants::PAYMENT_STATUS_PART,
+                ErpConstants::PAYMENT_STATUS_DEBIT
+            ];
+
+        // Manuay query here because query builder did not work for some reason (peat)
+        $sql = "SELECT `id` FROM {$this->orderHandler->table()}";
+        $sql .= " WHERE `paid_status` IN ('" . implode(',', $paidStatusCodes) . "')";
+        $sql .= " AND `payment_id` IN ('" . implode("','", $stripePaymentTypeIds) . "')";
+        $result = $this->dbConnection->fetchAllAssociative($sql);
+
+        foreach ($result as $row) {
+            try {
+                $orderId = $row['id'];
+                $order = $this->orderHandler->getOrderById($orderId);
+                $this->processOrder($order);
+            } catch (\Exception $exception) {
+                QUI\System\Log::writeException($exception);
+            }
+        }
+    }
+
+    /**
+     * Process an unpaid order.
+     *
+     * Checks if the associated Stripe PaymentIntent is (un)successful and creates/updates the order payment
+     * transaction accordingly.
+     *
+     * @param Order $order
+     * @return void
+     * @throws \Stripe\Exception\ApiErrorException
+     */
+    private function processOrder(Order $order): void
+    {
+        if ($order->isPaid()) {
+            return;
+        }
+
+        $stripePaymentIntentId = $order->getPaymentDataEntry(AbstractBasePayment::ATTR_STRIPE_PAYMENT_INTENT_ID);
+
+        if (empty($stripePaymentIntentId)) {
+            return;
+        }
+
+        $paymentIntent = StripePaymentIntent::retrieve($stripePaymentIntentId);
+
+        $orderProcessingService = new OrderProcessingService($order);
+        $orderProcessingService->processPaymentIntent($paymentIntent);
+    }
+
+    /**
+     * @return int[]
+     */
+    private function getStripePaymentTypeIds(): array
+    {
+        // Determine payment type IDs
+        $payments = $this->payments->getPayments([
+            'select' => ['id'],
+            'where' => [
+                'payment_type' => [
+                    'type' => 'IN',
+                    'value' => Provider::getNonRecurringPaymentTypes()
+                ]
+            ]
+        ]);
+
+        $paymentTypeIds = [];
+
+        /** @var PaymentType $Payment */
+        foreach ($payments as $Payment) {
+            $paymentTypeIds[] = $Payment->getId();
+        }
+
+        return $paymentTypeIds;
+    }
+}
diff --git a/src/QUI/ERP/Payments/Stripe/Events.php b/src/QUI/ERP/Payments/Stripe/Events.php
index bd8f015..417b448 100644
--- a/src/QUI/ERP/Payments/Stripe/Events.php
+++ b/src/QUI/ERP/Payments/Stripe/Events.php
@@ -9,6 +9,7 @@
 use QUI\ERP\Accounting\Payments\Types\Payment;
 use QUI\ERP\Order\OrderInterface;
 use QUI\Package\Package;
+use QUI\ERP\Payments\Stripe\AbstractBasePayment as StripeBasePayment;
 
 use function in_array;
 
@@ -51,20 +52,7 @@ public static function onPaymentsCanUsedInOrder(Payment $Payment, OrderInterface
             return;
         }
 
-        if (
-            !($PaymentType instanceof QUI\ERP\Payments\Stripe\PaymentMethods\AbstractBrowserPay)
-            && !($PaymentType instanceof QUI\ERP\Payments\Stripe\PaymentMethods\Recurring\AbstractRecurringBrowserPay)
-        ) {
-            return;
-        }
-
-        $B = new Browser();
-
-        if (!$B->isMobile()) {
-            throw new PaymentCanNotBeUsedException();
-        }
-
-        if (!in_array($B->getBrowser(), $PaymentType->getBrowserTypes())) {
+        if ($PaymentType instanceof StripeBasePayment && !$PaymentType->canBeUsedInOrder($Order)) {
             throw new PaymentCanNotBeUsedException();
         }
     }
diff --git a/src/QUI/ERP/Payments/Stripe/OrderProcessingService.php b/src/QUI/ERP/Payments/Stripe/OrderProcessingService.php
new file mode 100644
index 0000000..deb0b7f
--- /dev/null
+++ b/src/QUI/ERP/Payments/Stripe/OrderProcessingService.php
@@ -0,0 +1,244 @@
+<?php
+
+namespace QUI\ERP\Payments\Stripe;
+
+use Exception;
+use QUI;
+use QUI\ERP\Accounting\Payments\Transactions\Factory as TransactionFactory;
+use QUI\ERP\Accounting\Payments\Transactions\Handler as TransactionHandler;
+use QUI\ERP\Accounting\Payments\Transactions\Transaction;
+use QUI\ERP\Order\AbstractOrder;
+use Stripe\PaymentIntent;
+
+class OrderProcessingService implements OrderProcessingServiceInterface
+{
+    const ORDER_PAYMENT_ATTR_TRANSACTION_TX_ID = 'stripe_transaction_tx_id';
+
+    public function __construct(
+        private readonly AbstractOrder $order,
+        private ?TransactionHandler $transactionHandler = null
+    ) {
+        if (is_null($this->transactionHandler)) {
+            $this->transactionHandler = TransactionHandler::getInstance();
+        }
+    }
+
+    /**
+     * Processes a Stripe PaymentIntent for this order.
+     *
+     * This may set the order to SUCESSFUL or FAILED, depending on the status.
+     * This also creates transactions for successful orders.
+     *
+     * @param PaymentIntent $paymentIntent
+     * @return void
+     */
+    public function processPaymentIntent(PaymentIntent $paymentIntent): void
+    {
+        switch ($paymentIntent->status) {
+            case PaymentIntent::STATUS_PROCESSING:
+                $this->processPendingPaymentIntent($paymentIntent);
+                break;
+
+            case PaymentIntent::STATUS_SUCCEEDED:
+                $this->processSuccessfulPaymentIntent($paymentIntent);
+                break;
+
+            case PaymentIntent::STATUS_CANCELED:
+            case PaymentIntent::STATUS_REQUIRES_PAYMENT_METHOD:
+                $this->processFailedPaymentIntent($paymentIntent);
+                break;
+        }
+    }
+
+    /**
+     * Process a successful Stripe PaymentIntent
+     *
+     * @param PaymentIntent $paymentIntent
+     * @return void
+     */
+    private function processPendingPaymentIntent(PaymentIntent $paymentIntent): void
+    {
+        try {
+            if (is_null($this->getPaymentIntentTransaction())) {
+                $this->createPaymentIntentTransaction($paymentIntent);
+            }
+
+            $this->order->setSuccessfulStatus();
+            $this->order->setPaymentData(AbstractBasePayment::ATTR_STRIPE_ORDER_SUCCESSFUL, true);
+
+            $this->addOrderHistoryEntry(
+                'PaymentIntent transaction is PENDING. Waiting for successful status from Stripe. Order is successful.'
+            );
+        } catch (Exception $Exception) {
+            QUI\System\Log::writeException($Exception);
+
+            $this->addOrderHistoryEntry(
+                'Error while trying to process PENDING PaymentIntent: ' . $Exception->getMessage()
+            );
+        }
+    }
+
+    /**
+     * Process a successful Stripe PaymentIntent
+     *
+     * @param PaymentIntent $paymentIntent
+     * @return void
+     */
+    private function processSuccessfulPaymentIntent(PaymentIntent $paymentIntent): void
+    {
+        try {
+            $transaction = $this->getPaymentIntentTransaction();
+
+            if (is_null($transaction)) {
+                $transaction = $this->createPaymentIntentTransaction($paymentIntent);
+            }
+
+            $transactionAmount = (int)($transaction->getAmount() * 100);
+
+            if ($transactionAmount !== $paymentIntent->amount_received) {
+                $transaction->changeStatus(TransactionHandler::STATUS_ERROR);
+                $transaction = $this->createPaymentIntentTransaction($paymentIntent);
+            }
+
+            $transaction->changeStatus(TransactionHandler::STATUS_COMPLETE);
+            $this->order->setSuccessfulStatus();
+            $this->order->setPaymentData(AbstractBasePayment::ATTR_STRIPE_ORDER_SUCCESSFUL, true);
+
+            $this->addOrderHistoryEntry('PaymentIntent confirmed. Payment successful.');
+        } catch (Exception $Exception) {
+            QUI\System\Log::writeException($Exception);
+
+            $this->addOrderHistoryEntry(
+                'Error while trying to set order as successful: ' . $Exception->getMessage()
+            );
+        }
+    }
+
+    /**
+     * Process a failed Stripe PaymentIntent
+     *
+     * @param PaymentIntent $paymentIntent
+     * @return void
+     */
+    private function processFailedPaymentIntent(PaymentIntent $paymentIntent): void
+    {
+        try {
+            $transaction = $this->getPaymentIntentTransaction();
+
+            if (is_null($transaction)) {
+                $transaction = $this->createPaymentIntentTransaction($paymentIntent);
+            }
+
+            $transaction->changeStatus(TransactionHandler::STATUS_ERROR);
+
+            $this->addOrderHistoryEntry(
+                "PaymentIntent transaction {$transaction->getTxId()} FAILED."
+            );
+
+            if (Provider::shouldNotifyOnFailedPayment()) {
+                $lastError = $paymentIntent->last_payment_error->toArray();
+                $errorMsg = '';
+
+                if (!empty($lastError['message'])) {
+                    $errorMsg = $lastError['message'];
+                }
+
+                if (!empty($lastError['type'])) {
+                    $errorMsg .= ' (' . $lastError['type'] . ')';
+                }
+
+                if (!empty($lastError['decline_code'])) {
+                    $errorMsg .= ' (' . $lastError['decline_code'] . ')';
+                }
+
+                Utils::sendNotificationAboutFailedOrderPayment($this->order, $errorMsg);
+            }
+
+            $this->order->setPaymentStatus(QUI\ERP\Constants::PAYMENT_STATUS_ERROR);
+        } catch (Exception $Exception) {
+            QUI\System\Log::writeException($Exception);
+
+            $this->addOrderHistoryEntry(
+                'Error while trying to process FAILED PaymentIntent: ' . $Exception->getMessage()
+            );
+        }
+    }
+
+    /**
+     * Add history entry for current Order
+     *
+     * @param string $msg
+     * @return void
+     */
+    private function addOrderHistoryEntry(string $msg): void
+    {
+        try {
+            $this->order->addHistory('Stripe :: ' . $msg);
+            $this->saveOrder();
+        } catch (\Exception | \Throwable $exception) {
+            if ($exception instanceof \Exception) {
+                QUI\System\Log::writeException($exception);
+            }
+        }
+    }
+
+    /**
+     * @param PaymentIntent $paymentIntent
+     * @return Transaction
+     *
+     * @throws QUI\ERP\Accounting\Payments\Exception
+     * @throws QUI\ERP\Accounting\Payments\Transactions\Exception
+     * @throws QUI\Exception
+     */
+    private function createPaymentIntentTransaction(PaymentIntent $paymentIntent): Transaction
+    {
+        $transaction = TransactionFactory::createPaymentTransaction(
+            $paymentIntent->amount / 100,
+            QUI\ERP\Currency\Handler::getCurrency(mb_strtoupper($paymentIntent->currency)),
+            $this->order->getUUID(),
+            $this->order->getPayment()->getPaymentType()->getName(),
+            [
+                AbstractBasePayment::ATTR_STRIPE_PAYMENT_INTENT_ID => $paymentIntent->id
+            ],
+            null,
+            false,
+            false,
+            TransactionHandler::STATUS_PENDING
+        );
+
+        $this->addOrderHistoryEntry("PaymentIntent and transaction {$transaction->getTxId()} created.");
+        $this->order->setPaymentData(self::ORDER_PAYMENT_ATTR_TRANSACTION_TX_ID, $transaction->getTxId());
+        $this->saveOrder();
+
+        return $transaction;
+    }
+
+    /**
+     * @return Transaction|null
+     */
+    private function getPaymentIntentTransaction(): ?Transaction
+    {
+        $transactionId = $this->order->getPaymentDataEntry(self::ORDER_PAYMENT_ATTR_TRANSACTION_TX_ID);
+
+        if (empty($transactionId)) {
+            return null;
+        }
+
+        foreach ($this->order->getTransactions() as $transaction) {
+            if ($transaction->getTxId() === $transactionId) {
+                return $transaction;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * @return void
+     * @throws QUI\Exception
+     */
+    private function saveOrder(): void
+    {
+        $this->order->update(QUI::getUsers()->getSystemUser());
+    }
+}
diff --git a/src/QUI/ERP/Payments/Stripe/OrderProcessingServiceInterface.php b/src/QUI/ERP/Payments/Stripe/OrderProcessingServiceInterface.php
new file mode 100644
index 0000000..f3e1390
--- /dev/null
+++ b/src/QUI/ERP/Payments/Stripe/OrderProcessingServiceInterface.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace QUI\ERP\Payments\Stripe;
+
+use Stripe\PaymentIntent;
+
+interface OrderProcessingServiceInterface
+{
+    /**
+     * Processes a Stripe PaymentIntent for this order.
+     *
+     * This may set the order to SUCESSFUL or FAILED, depending on the status.
+     * This also creates transactions for successful orders.
+     *
+     * @param PaymentIntent $paymentIntent
+     * @return void
+     */
+    public function processPaymentIntent(PaymentIntent $paymentIntent): void;
+}
diff --git a/src/QUI/ERP/Payments/Stripe/PaymentMethods/AbstractBrowserPay.php b/src/QUI/ERP/Payments/Stripe/PaymentMethods/AbstractBrowserPay.php
index 2ab92ed..0e97863 100644
--- a/src/QUI/ERP/Payments/Stripe/PaymentMethods/AbstractBrowserPay.php
+++ b/src/QUI/ERP/Payments/Stripe/PaymentMethods/AbstractBrowserPay.php
@@ -5,10 +5,13 @@
 use QUI;
 use QUI\ERP\Accounting\Payments\Payments;
 use QUI\ERP\Order\AbstractOrder;
+use QUI\ERP\Order\OrderInterface;
 use QUI\ERP\Payments\Stripe\AbstractBasePayment;
+use QUI\ERP\Payments\Stripe\Utils;
 use Stripe\Exception\ApiErrorException;
 use Stripe\PaymentIntent as StripePaymentIntent;
-use QUI\ERP\Payments\Stripe\Utils;
+use Browser;
+use QUI\ERP\Accounting\Payments\Exceptions\PaymentCanNotBeUsed as PaymentCanNotBeUsedException;
 
 /**
  * Class BrowserPay
@@ -61,16 +64,31 @@ public function getPaymentMethodType(): string
     protected function createPaymentIntentForOrder(AbstractOrder $Order, string $paymentMethodId): StripePaymentIntent
     {
         return StripePaymentIntent::create([
-            'payment_method'      => $paymentMethodId,
-            'amount'              => Utils::getCentAmount($Order),
-            'currency'            => mb_strtolower($Order->getCurrency()->getCode()),
+            'payment_method' => $paymentMethodId,
+            'amount' => Utils::getCentAmount($Order),
+            'currency' => mb_strtolower($Order->getCurrency()->getCode()),
 //            'confirmation_method' => 'manual',
-            'confirm'             => true,
-            'description'         => Utils::getPaymentDescriptionForOrder($Order),
+            'confirm' => true,
+            'description' => Utils::getPaymentDescriptionForOrder($Order),
             'automatic_payment_methods' => [
                 'enabled' => true,
                 'allow_redirects' => 'never'
             ]
         ]);
     }
+
+    public function canBeUsedInOrder(OrderInterface $order): bool
+    {
+        $B = new Browser();
+
+        if (!$B->isMobile()) {
+            return false;
+        }
+
+        if (!in_array($B->getBrowser(), $this->getBrowserTypes())) {
+            return false;
+        }
+
+        return true;
+    }
 }
diff --git a/src/QUI/ERP/Payments/Stripe/PaymentMethods/ApplePay.php b/src/QUI/ERP/Payments/Stripe/PaymentMethods/ApplePay.php
index fe1d001..1359fa9 100644
--- a/src/QUI/ERP/Payments/Stripe/PaymentMethods/ApplePay.php
+++ b/src/QUI/ERP/Payments/Stripe/PaymentMethods/ApplePay.php
@@ -104,7 +104,7 @@ public function getIcon(): string
      * Return the extra text for the invoice
      *
      * @param Invoice|InvoiceTemporary|InvoiceView $Invoice |InvoiceView $Invoice
-     * @return mixed
+     * @return string
      */
     public function getInvoiceInformationText(Invoice|InvoiceTemporary|InvoiceView $Invoice): string
     {
diff --git a/src/QUI/ERP/Payments/Stripe/PaymentMethods/Card.php b/src/QUI/ERP/Payments/Stripe/PaymentMethods/Card.php
index 5aa8ff6..51b66de 100644
--- a/src/QUI/ERP/Payments/Stripe/PaymentMethods/Card.php
+++ b/src/QUI/ERP/Payments/Stripe/PaymentMethods/Card.php
@@ -13,6 +13,7 @@
 use Stripe\Exception\ApiErrorException;
 use Stripe\PaymentIntent as StripePaymentIntent;
 use QUI\ERP\Payments\Stripe\Utils;
+use Stripe\PaymentMethod;
 
 /**
  * Class Card
@@ -97,7 +98,7 @@ public function getIcon(): string
      */
     public function getPaymentMethodType(): string
     {
-        return \Stripe\Card::OBJECT_NAME;
+        return PaymentMethod::TYPE_CARD;
     }
 
     /**
@@ -130,7 +131,7 @@ protected function createPaymentIntentForOrder(AbstractOrder $Order, string $pay
      * Return the extra text for the invoice
      *
      * @param Invoice|InvoiceTemporary|InvoiceView $Invoice |InvoiceView $Invoice
-     * @return mixed
+     * @return string
      */
     public function getInvoiceInformationText(Invoice|InvoiceTemporary|InvoiceView $Invoice): string
     {
diff --git a/src/QUI/ERP/Payments/Stripe/PaymentMethods/GooglePay.php b/src/QUI/ERP/Payments/Stripe/PaymentMethods/GooglePay.php
index 583400c..90e2ca5 100644
--- a/src/QUI/ERP/Payments/Stripe/PaymentMethods/GooglePay.php
+++ b/src/QUI/ERP/Payments/Stripe/PaymentMethods/GooglePay.php
@@ -102,7 +102,7 @@ public function getIcon(): string
      * Return the extra text for the invoice
      *
      * @param Invoice|InvoiceTemporary|InvoiceView $Invoice |InvoiceView $Invoice
-     * @return mixed
+     * @return string
      */
     public function getInvoiceInformationText(Invoice|InvoiceTemporary|InvoiceView $Invoice): string
     {
diff --git a/src/QUI/ERP/Payments/Stripe/PaymentMethods/MicrosoftPay.php b/src/QUI/ERP/Payments/Stripe/PaymentMethods/MicrosoftPay.php
index 30591a9..bcef294 100644
--- a/src/QUI/ERP/Payments/Stripe/PaymentMethods/MicrosoftPay.php
+++ b/src/QUI/ERP/Payments/Stripe/PaymentMethods/MicrosoftPay.php
@@ -102,7 +102,7 @@ public function getIcon(): string
      * Return the extra text for the invoice
      *
      * @param Invoice|InvoiceTemporary|InvoiceView $Invoice |InvoiceView $Invoice
-     * @return mixed
+     * @return string
      */
     public function getInvoiceInformationText(Invoice|InvoiceTemporary|InvoiceView $Invoice): string
     {
diff --git a/src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/AbstractBaseRecurringPayment.php b/src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/AbstractBaseRecurringPayment.php
index 130f7ac..c7a2451 100644
--- a/src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/AbstractBaseRecurringPayment.php
+++ b/src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/AbstractBaseRecurringPayment.php
@@ -5,15 +5,17 @@
 use Exception;
 use QUI;
 use QUI\ERP\Accounting\Invoice\Invoice;
+use QUI\ERP\Accounting\Payments\Types\RecurringPaymentInterface;
 use QUI\ERP\Order\AbstractOrder;
 use QUI\ERP\Payments\Stripe\AbstractBasePayment;
+use QUI\ERP\Payments\Stripe\Provider;
 use QUI\ERP\Payments\Stripe\StripeException;
+use QUI\ERP\Payments\Stripe\Utils;
 use Stripe\Exception\ApiErrorException;
+use Stripe\PaymentIntent;
 use Stripe\PaymentIntent as StripePaymentIntent;
-use QUI\ERP\Accounting\Payments\Types\RecurringPaymentInterface;
-use QUI\ERP\Payments\Stripe\Provider;
+use Stripe\Subscription;
 use Stripe\Subscription as StripeSubscription;
-use QUI\ERP\Payments\Stripe\Utils;
 
 use function array_column;
 
@@ -83,7 +85,7 @@ public function captureSubscription(Invoice $Invoice): void
      *
      * @throws Exception
      */
-    public function cancelSubscription(int|string $subscriptionId, string $reason = ''): void
+    public function cancelSubscription(int | string $subscriptionId, string $reason = ''): void
     {
         Subscriptions::cancelSubscription($subscriptionId, $reason);
     }
@@ -99,7 +101,7 @@ public function cancelSubscription(int|string $subscriptionId, string $reason =
      * @throws ApiErrorException
      * @throws StripeException
      */
-    public function suspendSubscription(int|string $subscriptionId, string $note = null): void
+    public function suspendSubscription(int | string $subscriptionId, string $note = null): void
     {
         Subscriptions::suspendSubscription($subscriptionId);
     }
@@ -115,7 +117,7 @@ public function suspendSubscription(int|string $subscriptionId, string $note = n
      * @throws ApiErrorException
      * @throws StripeException
      */
-    public function resumeSubscription(int|string $subscriptionId, string $note = null): void
+    public function resumeSubscription(int | string $subscriptionId, string $note = null): void
     {
         Subscriptions::resumeSubscription($subscriptionId);
     }
@@ -126,7 +128,7 @@ public function resumeSubscription(int|string $subscriptionId, string $note = nu
      * @param int|string $subscriptionId
      * @return bool
      */
-    public function isSuspended(int|string $subscriptionId): bool
+    public function isSuspended(int | string $subscriptionId): bool
     {
         return Subscriptions::isSuspended($subscriptionId);
     }
@@ -140,7 +142,7 @@ public function isSuspended(int|string $subscriptionId): bool
      * @param int|string $subscriptionId
      * @return void
      */
-    public function setSubscriptionAsInactive(int|string $subscriptionId): void
+    public function setSubscriptionAsInactive(int | string $subscriptionId): void
     {
         Subscriptions::setSubscriptionAsInactive($subscriptionId);
     }
@@ -163,7 +165,7 @@ public function isSubscriptionEditable(): bool
      * @param AbstractOrder $Order
      * @return int|string|false - ID or false of no ID associated
      */
-    public function getSubscriptionIdByOrder(AbstractOrder $Order): bool|int|string
+    public function getSubscriptionIdByOrder(AbstractOrder $Order): bool | int | string
     {
         try {
             $result = QUI::getDataBase()->fetch([
@@ -191,7 +193,7 @@ public function getSubscriptionIdByOrder(AbstractOrder $Order): bool|int|string
      * @param int|string $subscriptionId
      * @return bool
      */
-    public function isSubscriptionActiveAtPaymentProvider(int|string $subscriptionId): bool
+    public function isSubscriptionActiveAtPaymentProvider(int | string $subscriptionId): bool
     {
         try {
             $data = Subscriptions::getSubscriptionDetails($subscriptionId);
@@ -217,7 +219,7 @@ public function isSubscriptionActiveAtPaymentProvider(int|string $subscriptionId
      * @param string|int $subscriptionId - Payment provider subscription ID
      * @return bool
      */
-    public function isSubscriptionActiveAtQuiqqer(int|string $subscriptionId): bool
+    public function isSubscriptionActiveAtQuiqqer(int | string $subscriptionId): bool
     {
         try {
             $result = QUI::getDataBase()->fetch([
@@ -273,7 +275,7 @@ public function getSubscriptionIds(bool $includeInactive = false): array
      * @param string|int $subscriptionId
      * @return string|false
      */
-    public function getSubscriptionGlobalProcessingId(int|string $subscriptionId): bool|string
+    public function getSubscriptionGlobalProcessingId(int | string $subscriptionId): bool | string
     {
         try {
             $result = QUI::getDataBase()->fetch([
@@ -339,4 +341,18 @@ public function supportsRecurringPaymentsOnly(): bool
     {
         return true;
     }
+
+    /**
+     * Confirms a PaymentIntent with status "requires_confirmation" for a subscription.
+     *
+     * @param StripeSubscription $subscription
+     * @param StripePaymentIntent $paymentIntent
+     * @return void
+     */
+    public function confirmPaymentIntentForSubscription(
+        Subscription $subscription,
+        PaymentIntent $paymentIntent
+    ): void {
+        // nothing by default
+    }
 }
diff --git a/src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/AbstractRecurringBrowserPay.php b/src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/AbstractRecurringBrowserPay.php
index b8bcaca..f4817af 100644
--- a/src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/AbstractRecurringBrowserPay.php
+++ b/src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/AbstractRecurringBrowserPay.php
@@ -2,6 +2,15 @@
 
 namespace QUI\ERP\Payments\Stripe\PaymentMethods\Recurring;
 
+use Browser;
+use QUI\ERP\Order\OrderInterface;
+use Stripe\PaymentIntent;
+use Stripe\PaymentIntent as StripePaymentIntent;
+use Stripe\Subscription;
+use Stripe\Subscription as StripeSubscription;
+
+use function in_array;
+
 /**
  * Class AbstractRecurringBrowserPay
  *
@@ -25,4 +34,19 @@ public function getPaymentMethodType(): string
     {
         return 'browser_pay';
     }
+
+    public function canBeUsedInOrder(OrderInterface $order): bool
+    {
+        $B = new Browser();
+
+        if (!$B->isMobile()) {
+            return false;
+        }
+
+        if (!in_array($B->getBrowser(), $this->getBrowserTypes())) {
+            return false;
+        }
+
+        return true;
+    }
 }
diff --git a/src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/ApplePay.php b/src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/ApplePay.php
index de97a16..95250ba 100644
--- a/src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/ApplePay.php
+++ b/src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/ApplePay.php
@@ -104,7 +104,7 @@ public function getIcon(): string
      * Return the extra text for the invoice
      *
      * @param Invoice|InvoiceView|InvoiceTemporary $Invoice |InvoiceView $Invoice
-     * @return mixed
+     * @return string
      */
     public function getInvoiceInformationText(Invoice|InvoiceView|InvoiceTemporary $Invoice): string
     {
diff --git a/src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/Card.php b/src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/Card.php
index 4aeed5b..a1255ae 100644
--- a/src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/Card.php
+++ b/src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/Card.php
@@ -7,6 +7,7 @@
 use QUI\ERP\Accounting\Invoice\Invoice;
 use QUI\ERP\Accounting\Invoice\InvoiceView;
 use QUI\ERP\Accounting\Payments\Payments;
+use Stripe\PaymentMethod;
 
 /**
  * Class Card
@@ -91,14 +92,14 @@ public function getIcon(): string
      */
     public function getPaymentMethodType(): string
     {
-        return \Stripe\Card::OBJECT_NAME;
+        return PaymentMethod::TYPE_CARD;
     }
 
     /**
      * Return the extra text for the invoice
      *
      * @param Invoice|InvoiceView $Invoice
-     * @return mixed
+     * @return string
      */
     public function getInvoiceInformationText($Invoice): string
     {
diff --git a/src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/GooglePay.php b/src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/GooglePay.php
index b2ceff4..4a38f46 100644
--- a/src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/GooglePay.php
+++ b/src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/GooglePay.php
@@ -102,7 +102,7 @@ public function getIcon(): string
      * Return the extra text for the invoice
      *
      * @param Invoice|InvoiceView|InvoiceTemporary $Invoice |InvoiceView $Invoice
-     * @return mixed
+     * @return string
      */
     public function getInvoiceInformationText(Invoice|InvoiceView|InvoiceTemporary $Invoice): string
     {
diff --git a/src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/MicrosoftPay.php b/src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/MicrosoftPay.php
index bb81953..90dc245 100644
--- a/src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/MicrosoftPay.php
+++ b/src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/MicrosoftPay.php
@@ -101,7 +101,7 @@ public function getIcon(): string
      * Return the extra text for the invoice
      *
      * @param Invoice|InvoiceView|InvoiceTemporary $Invoice |InvoiceView $Invoice
-     * @return mixed
+     * @return string
      */
     public function getInvoiceInformationText(Invoice|InvoiceView|InvoiceTemporary $Invoice): string
     {
diff --git a/src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/SepaDebit.php b/src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/SepaDebit.php
new file mode 100644
index 0000000..20cbb71
--- /dev/null
+++ b/src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/SepaDebit.php
@@ -0,0 +1,207 @@
+<?php
+
+namespace QUI\ERP\Payments\Stripe\PaymentMethods\Recurring;
+
+use Exception;
+use QUI;
+use QUI\ERP\Accounting\Invoice\Invoice;
+use QUI\ERP\Accounting\Invoice\InvoiceView;
+use QUI\ERP\Accounting\Payments\Payments;
+use QUI\ERP\Order\AbstractOrder;
+use QUI\ERP\Order\OrderInterface;
+use QUI\ERP\Payments\Stripe\Provider;
+use QUI\ERP\Payments\Stripe\Utils;
+use Stripe\Exception\ApiErrorException;
+use Stripe\PaymentIntent;
+use Stripe\PaymentIntent as StripePaymentIntent;
+use Stripe\PaymentMethod;
+use Stripe\Subscription;
+use Stripe\Subscription as StripeSubscription;
+
+/**
+ * Stripe payment with SEPA Direct Debit for recurring payments
+ */
+class SepaDebit extends AbstractBaseRecurringPayment
+{
+    /**
+     * @return string
+     */
+    public function getTitle(): string
+    {
+        return $this->getLocale()->get('quiqqer/payment-stripe', 'payment.Recurring.SepaDebit.title');
+    }
+
+    /**
+     * @return string
+     */
+    public function getDescription(): string
+    {
+        return $this->getLocale()->get('quiqqer/payment-stripe', 'payment.Recurring.SepaDebit.description');
+    }
+
+    /**
+     * Get title for frontend
+     *
+     * @return string
+     */
+    public function getFrontendTitle(): string
+    {
+        return $this->getLocale()->get('quiqqer/payment-stripe', 'payment.frontend.Recurring.SepaDebit.title');
+    }
+
+    /**
+     * Get description for frontend
+     *
+     * @return string
+     */
+    public function getFrontendDescription(): string
+    {
+        return $this->getLocale()->get('quiqqer/payment-stripe', 'payment.frontend.Recurring.SepaDebit.description');
+    }
+
+    /**
+     * Get title for the Payment step (OrderProcess)
+     *
+     * @return string
+     */
+    public function getPaymentStepTitle(): string
+    {
+        return $this->getLocale()->get('quiqqer/payment-stripe', 'payment.PaymentStep.title.SepaDebit');
+    }
+
+    /**
+     * Get description step for the Payment step (OrderProcess)
+     *
+     * @return string
+     */
+    public function getPaymentStepInfo(): string
+    {
+        return $this->getLocale()->get('quiqqer/payment-stripe', 'payment.Recurring.PaymentStep.info.SepaDebit');
+    }
+
+//    /**
+//     * Return the payment icon (the URL path)
+//     * Can be overwritten
+//     *
+//     * @return string
+//     */
+//    public function getIcon(): string
+//    {
+//        return Payments::getInstance()->getHost() .
+//            URL_OPT_DIR .
+//            'quiqqer/payment-stripe/bin/images/Payment_Card.png'; // TODO: richtiges Logo
+//    }
+
+    /**
+     * Get type string of Stripe PaymentMethod
+     *
+     * @return string
+     */
+    public function getPaymentMethodType(): string
+    {
+        return PaymentMethod::TYPE_SEPA_DEBIT;
+    }
+
+    /**
+     * Return the extra text for the invoice
+     *
+     * @param Invoice|InvoiceView $Invoice
+     * @return string
+     */
+    public function getInvoiceInformationText($Invoice): string
+    {
+        try {
+            return $Invoice->getCustomer()->getLocale()->get(
+                'quiqqer/payment-stripe',
+                'additional_invoice_text.SepaDebit'
+            );
+        } catch (Exception $Exception) {
+            QUI\System\Log::writeException($Exception);
+            return '';
+        }
+    }
+
+    /**
+     * Return attributes for creating a PaymentIntent
+     *
+     * @param AbstractOrder $Order
+     * @param string $paymentMethodId - Stripe PaymentMethod ID
+     * @return StripePaymentIntent
+     *
+     * @throws ApiErrorException
+     * @throws QUI\Exception
+     */
+    protected function createPaymentIntentForOrder(AbstractOrder $Order, string $paymentMethodId): StripePaymentIntent
+    {
+        return StripePaymentIntent::create([
+            'payment_method' => $paymentMethodId,
+            'amount' => Utils::getCentAmount($Order),
+            'currency' => mb_strtolower($Order->getCurrency()->getCode()),
+            'confirmation_method' => 'manual',
+            'confirm' => true,
+            'setup_future_usage' => 'off_session',
+            'use_stripe_sdk' => true,
+            'description' => Utils::getPaymentDescriptionForOrder($Order),
+            'statement_descriptor' => Provider::getStatementDescriptor($Order),
+            'metadata' => [
+                'orderUuid' => $Order->getUUID()
+            ],
+            'mandate_data' => [
+                'customer_acceptance' => [
+                    'type' => 'online',
+                    'online' => [
+                        'ip_address' => $_SERVER['REMOTE_ADDR'],
+                        'user_agent' => $_SERVER['HTTP_USER_AGENT'],
+                    ],
+                ],
+            ]
+        ]);
+    }
+
+    public function canBeUsedInOrder(OrderInterface $order): bool
+    {
+        // Currency must be EUR
+        if ($order->getCurrency()->getCode() !== 'EUR') {
+            return false;
+        }
+
+        if ($order->getInvoiceAddress()->getCountry()->isEU()) {
+            return true;
+        }
+
+        // Some Non-EU countries are also part of SEPA
+        return match ($order->getInvoiceAddress()->getCountry()->getCode()) {
+            'CH', 'GB', 'SM', 'VA', 'AD', 'MC', 'IS', 'NO', 'LI' => true,
+            default => false,
+        };
+    }
+
+    /**
+     * Confirms a PaymentIntent with status "requires_confirmation" for a subscription.
+     *
+     * @param StripeSubscription $subscription
+     * @param StripePaymentIntent $paymentIntent
+     * @return void
+     * @throws ApiErrorException
+     */
+    public function confirmPaymentIntentForSubscription(
+        Subscription $subscription,
+        PaymentIntent $paymentIntent
+    ): void {
+        Provider::setupApi();
+
+        $data = [
+            'mandate_data' => [
+                'customer_acceptance' => [
+                    'type' => 'online',
+                    'online' => [
+                        'ip_address' => $_SERVER['REMOTE_ADDR'],
+                        'user_agent' => $_SERVER['HTTP_USER_AGENT'],
+                    ],
+                ],
+            ]
+        ];
+
+        $paymentIntent->confirm($data);
+    }
+}
diff --git a/src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/Subscriptions.php b/src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/Subscriptions.php
index b651fe3..d70a032 100644
--- a/src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/Subscriptions.php
+++ b/src/QUI/ERP/Payments/Stripe/PaymentMethods/Recurring/Subscriptions.php
@@ -5,26 +5,28 @@
 use Exception;
 use PDO;
 use QUI;
-use QUI\ERP\Order\AbstractOrder;
+use QUI\ERP\Accounting\Invoice\Handler as InvoiceHandler;
 use QUI\ERP\Accounting\Invoice\Invoice;
+use QUI\ERP\Accounting\Payments\Payments;
+use QUI\ERP\Accounting\Payments\Transactions\Factory as TransactionFactory;
+use QUI\ERP\Accounting\Payments\Transactions\Handler as TransactionHandler;
+use QUI\ERP\Order\AbstractOrder;
+use QUI\ERP\Payments\Stripe\AbstractBasePayment;
+use QUI\ERP\Payments\Stripe\Provider;
+use QUI\ERP\Payments\Stripe\Utils;
 use QUI\ExceptionStack;
 use QUI\Interfaces\Users\User;
 use QUI\System\Log;
 use QUI\Utils\Security\Orthos;
-use QUI\ERP\Accounting\Payments\Transactions\Factory as TransactionFactory;
-use QUI\ERP\Accounting\Invoice\Handler as InvoiceHandler;
-use QUI\ERP\Accounting\Payments\Payments;
-use QUI\ERP\Accounting\Payments\Transactions\Handler as TransactionHandler;
-use QUI\ERP\Payments\Stripe\AbstractBasePayment;
-use Stripe\Exception\ApiErrorException;
-use Stripe\Subscription as StripeSubscription;
 use Stripe\Customer as StripeCustomer;
+use Stripe\Dispute;
+use Stripe\Exception\ApiErrorException;
 use Stripe\Invoice as StripeInvoice;
 use Stripe\PaymentIntent as StripePaymentIntent;
-use QUI\ERP\Payments\Stripe\Provider;
-use QUI\ERP\Payments\Stripe\Utils;
+use Stripe\Subscription as StripeSubscription;
 
 use function array_keys;
+use function array_pop;
 use function json_decode;
 use function json_encode;
 use function mb_strtoupper;
@@ -50,17 +52,22 @@ class Subscriptions
      * Create a Stripe subscription form an order
      *
      * @param AbstractOrder $Order
-     * @return int|false - Subscription ID
+     * @return string|null - Subscription ID
      *
      * @throws Exception
      */
-    public static function createSubscription(AbstractOrder $Order): bool|int
+    public static function createSubscription(AbstractOrder $Order): ?string
     {
         $SystemUser = QUI::getUsers()->getSystemUser();
 
         if ($Order->getPaymentDataEntry(AbstractBasePayment::ATTR_STRIPE_SUBSCRIPTION_ID)) {
+            /** @var AbstractBaseRecurringPayment $paymentType */
+            $paymentType = $Order->getPayment()->getPaymentType();
+
+
             $confirmData = self::confirmSubscription(
-                $Order->getPaymentDataEntry(AbstractBasePayment::ATTR_STRIPE_SUBSCRIPTION_ID)
+                $Order->getPaymentDataEntry(AbstractBasePayment::ATTR_STRIPE_SUBSCRIPTION_ID),
+                $paymentType
             );
 
             switch ($confirmData['status']) {
@@ -91,7 +98,7 @@ public static function createSubscription(AbstractOrder $Order): bool|int
         $paymentMethodId = $Order->getPaymentDataEntry(AbstractBasePayment::ATTR_STRIPE_PAYMENT_METHOD_ID);
 
         if (empty($paymentMethodId)) {
-            return false;
+            return null;
         }
 
         // Create Stripe customer for subscription
@@ -107,8 +114,12 @@ public static function createSubscription(AbstractOrder $Order): bool|int
                     'plan' => $billingPlanId
                 ]
             ],
-            'payment_behavior' => 'allow_incomplete',
-            'default_payment_method' => $paymentMethodId
+//            'payment_behavior' => 'allow_incomplete',
+            'payment_behavior' => 'default_incomplete',
+            'default_payment_method' => $paymentMethodId,
+            'metadata' => [
+                'orderUuid' => $Order->getUUID()
+            ]
         ]);
 
         try {
@@ -142,10 +153,11 @@ public static function createSubscription(AbstractOrder $Order): bool|int
     /**
      * Confirm subscription status (was creation and payment successful?)
      *
-     * @param string|int $subscriptionId
+     * @param string $subscriptionId
+     * @param AbstractBaseRecurringPayment $payment
      * @return array
      */
-    public static function confirmSubscription(string|int $subscriptionId): array
+    public static function confirmSubscription(string $subscriptionId, AbstractBaseRecurringPayment $payment): array
     {
         Provider::setupApi();
 
@@ -177,6 +189,19 @@ public static function confirmSubscription(string|int $subscriptionId): array
                     $data['status'] = 'retry_payment_method';
                     break;
 
+                case StripePaymentIntent::STATUS_REQUIRES_CONFIRMATION:
+                    $paymentIntent = $StripeSubscription->latest_invoice->payment_intent;
+
+                    try {
+                        $payment->confirmPaymentIntentForSubscription($StripeSubscription, $paymentIntent);
+                        $StripeSubscription->refresh();
+                        $data['status'] = $StripeSubscription->status;
+                    } catch (\Exception $exception) {
+                        QUI\System\Log::writeException($exception);
+                        $data['status'] = 'error';
+                    }
+                    break;
+
                 default:
                     $data['status'] = 'error';
             }
@@ -192,7 +217,7 @@ public static function confirmSubscription(string|int $subscriptionId): array
      * @return string - Stripe Invoice ID
      * @throws ApiErrorException
      */
-    public static function getLatestSubscriptionInvoiceId(int|string $subscriptionId): string
+    public static function getLatestSubscriptionInvoiceId(int | string $subscriptionId): string
     {
         Provider::setupApi();
 
@@ -238,15 +263,15 @@ public static function billSubscriptionBalance(Invoice $Invoice): void
 //            $data = self::getSubscriptionDetails($subscriptionId);
 //        }
 
-        // Check if a Paymill transaction matches the Invoice
+        // Check if a Stripe transaction matches the Invoice
         $unprocessedTransactions = self::getUnprocessedTransactions($subscriptionId);
         $Invoice->calculatePayments();
 
         $invoiceAmount = Utils::getCentAmount($Invoice);
         $invoiceCurrency = $Invoice->getCurrency()->getCode();
 
-        /** @var AbstractBaseRecurringPayment $Payment */
         $paymentMethodData = json_decode($Invoice->getAttribute('payment_method_data'), true);
+        /** @var AbstractBaseRecurringPayment $Payment */
         $Payment = Payments::getInstance()->getPayment($paymentMethodData['id'])->getPaymentType();
 
         foreach ($unprocessedTransactions as $transaction) {
@@ -276,6 +301,26 @@ public static function billSubscriptionBalance(Invoice $Invoice): void
                     $Invoice->getGlobalProcessId()
                 );
 
+                // Check if associated charge is disputed and dispute is lost
+                if (!empty($transaction['charge']['dispute'])) {
+                    $dispute = $transaction['charge']['dispute'];
+
+                    if ($dispute['status'] === Dispute::STATUS_LOST) {
+                        $InvoiceTransaction->changeStatus(TransactionHandler::STATUS_ERROR);
+                        $Invoice->addHistory(
+                            QUI::getSystemLocale()->get(
+                                'quiqqer/payment-stripe',
+                                'history.Invoice.stripe_charge_dispute_lost',
+                                [
+                                    'quiqqerTransactionId' => $InvoiceTransaction->getTxId(),
+                                    'stripeInvoiceId' => $transaction['id'],
+                                    'stripeDisputeId' => $dispute['id']
+                                ]
+                            )
+                        );
+                    }
+                }
+
                 $Invoice->addTransaction($InvoiceTransaction);
 
                 QUI::getDataBase()->update(
@@ -315,7 +360,7 @@ public static function billSubscriptionBalance(Invoice $Invoice): void
      * @return array|int
      * @throws QUI\Exception
      */
-    public static function getSubscriptionList(array $searchParams, bool $countOnly = false): array|int
+    public static function getSubscriptionList(array $searchParams, bool $countOnly = false): array | int
     {
         $Grid = new QUI\Utils\Grid($searchParams);
         $gridParams = $Grid->parseDBParams($searchParams);
@@ -397,7 +442,7 @@ public static function getSubscriptionList(array $searchParams, bool $countOnly
      * @return array
      * @throws ApiErrorException
      */
-    public static function getSubscriptionDetails(int|string $subscriptionId): array
+    public static function getSubscriptionDetails(int | string $subscriptionId): array
     {
         Provider::setupApi();
         return StripeSubscription::retrieve($subscriptionId)->toArray();
@@ -413,7 +458,7 @@ public static function getSubscriptionDetails(int|string $subscriptionId): array
      * @throws QUI\ERP\Payments\Stripe\StripeException
      * @throws ApiErrorException
      */
-    public static function cancelSubscription(int|string $subscriptionId, string $reason = ''): void
+    public static function cancelSubscription(int | string $subscriptionId, string $reason = ''): void
     {
         $data = self::getSubscriptionData($subscriptionId);
 
@@ -455,7 +500,7 @@ public static function cancelSubscription(int|string $subscriptionId, string $re
      * @throws QUI\ERP\Payments\Stripe\StripeException
      * @throws ApiErrorException
      */
-    public static function suspendSubscription(int|string $subscriptionId): void
+    public static function suspendSubscription(int | string $subscriptionId): void
     {
         $data = self::getSubscriptionData($subscriptionId);
 
@@ -500,7 +545,7 @@ public static function suspendSubscription(int|string $subscriptionId): void
      * @throws QUI\ERP\Payments\Stripe\StripeException
      * @throws ApiErrorException
      */
-    public static function resumeSubscription(int|string $subscriptionId): void
+    public static function resumeSubscription(int | string $subscriptionId): void
     {
         $data = self::getSubscriptionData($subscriptionId);
 
@@ -539,7 +584,7 @@ public static function resumeSubscription(int|string $subscriptionId): void
      * @return bool
      * @throws ApiErrorException
      */
-    public static function isSuspended(int|string $subscriptionId): bool
+    public static function isSuspended(int | string $subscriptionId): bool
     {
         $data = self::getSubscriptionDetails($subscriptionId);
 
@@ -556,7 +601,7 @@ public static function isSuspended(int|string $subscriptionId): bool
      * @param int|string $subscriptionId - Stripe Subscription ID
      * @return void
      */
-    public static function setSubscriptionAsInactive(int|string $subscriptionId): void
+    public static function setSubscriptionAsInactive(int | string $subscriptionId): void
     {
         try {
             QUI::getDataBase()->update(
@@ -572,33 +617,46 @@ public static function setSubscriptionAsInactive(int|string $subscriptionId): vo
     /**
      * Fetches subscription transactions (invoices) from Stripe
      *
-     * @param int|string $subscriptionId - Stripe Subscription ID
+     * @param string $stripeSubscriptionId - Stripe Subscription ID
      * @param string|null $startAfterInvoiceId (optional) - Only fetch invoices after this Stripe Invoice ID
-     * @return array
+     * @return StripeInvoice[]
      *
      * @throws ApiErrorException
      */
     public static function getSubscriptionTransactions(
-        int|string $subscriptionId,
+        string $stripeSubscriptionId,
         string $startAfterInvoiceId = null
     ): array {
         $searchParams = [
             'limit' => 100,
-            'subscription' => $subscriptionId
+            'subscription' => $stripeSubscriptionId,
+            'expand' => [
+                'data.charge.dispute'
+            ]
         ];
 
-//        if ($startAfterInvoiceId) {
-//            $searchParams['starting_after'] = $startAfterInvoiceId;
-//        }
+        if ($startAfterInvoiceId) {
+            $searchParams['starting_after'] = $startAfterInvoiceId;
+        }
 
-        $result = StripeInvoice::all($searchParams);
-        $transactions = [];
+        $list = [];
 
-        foreach ($result->autoPagingIterator() as $Transaction) {
-            $transactions[] = $Transaction->toArray();
-        }
+        do {
+            $result = StripeInvoice::all($searchParams);
 
-        return $result['data'];
+            if (empty($result['data'])) {
+                break;
+            }
+
+            $list = array_merge($list, $result['data']);
+
+            /** @var StripeInvoice $last */
+            $last = $result->last();
+
+            $searchParams['starting_after'] = $last->id;
+        } while (!empty($result['has_more']));
+
+        return $list;
     }
 
     /**
@@ -781,6 +839,10 @@ public static function processDeniedTransactions(Invoice $Invoice): void
 
                 $InvoiceTransaction->changeStatus(TransactionHandler::STATUS_ERROR);
 
+                if (Provider::shouldNotifyOnFailedPayment()) {
+                    Utils::sendNotificationAboutFailedInvoicePayment($Invoice, $transaction['id']);
+                }
+
                 $Invoice->addTransaction($InvoiceTransaction);
 
                 QUI::getDataBase()->update(
@@ -818,7 +880,7 @@ public static function processDeniedTransactions(Invoice $Invoice): void
      * @throws QUI\Database\Exception
      * @throws ApiErrorException
      */
-    protected static function refreshTransactionList(int|string $subscriptionId): void
+    protected static function refreshTransactionList(int | string $subscriptionId): void
     {
         if (isset(self::$transactionsRefreshed[$subscriptionId])) {
             return;
@@ -847,6 +909,17 @@ protected static function refreshTransactionList(int|string $subscriptionId): vo
         $transactions = self::getSubscriptionTransactions($data['stripe_id']);
 
         foreach ($transactions as $transaction) {
+            // Only save invoices with final state
+            switch ($transaction->status) {
+                case StripeInvoice::STATUS_PAID:
+                case StripeInvoice::STATUS_UNCOLLECTIBLE:
+                case StripeInvoice::STATUS_VOID:
+                    break;
+
+                default:
+                    continue 2;
+            }
+
             $TransactionTime = date_create('@' . $transaction['created']);
 
             if (empty($TransactionTime)) {
@@ -886,23 +959,23 @@ protected static function refreshTransactionList(int|string $subscriptionId): vo
     /**
      * Get all completed Subscription transactions that are unprocessed by QUIQQER ERP
      *
-     * @param int|string $subscriptionId
+     * @param string $stripeSubscriptionId
      * @param string $status (optional) - Get transactions with this status [default: "paid"]
      * @return array
+     * @throws ApiErrorException
      * @throws QUI\Database\Exception
-     * @throws Exception
      */
     protected static function getUnprocessedTransactions(
-        int|string $subscriptionId,
+        string $stripeSubscriptionId,
         string $status = StripeInvoice::STATUS_PAID
     ): array {
-        self::refreshTransactionList($subscriptionId);
+        self::refreshTransactionList($stripeSubscriptionId);
 
         $result = QUI::getDataBase()->fetch([
             'select' => ['stripe_invoice_data'],
             'from' => Provider::getStripeBillingSubscriptionsTransactionsTable(),
             'where' => [
-                'stripe_subscription_id' => $subscriptionId,
+                'stripe_subscription_id' => $stripeSubscriptionId,
                 'quiqqer_transaction_id' => null
             ]
         ]);
@@ -928,7 +1001,7 @@ protected static function getUnprocessedTransactions(
      * @param int|string $subscriptionId - Stripe Subscription ID
      * @return array|false
      */
-    protected static function getSubscriptionData(int|string $subscriptionId): bool|array
+    protected static function getSubscriptionData(int | string $subscriptionId): bool | array
     {
         try {
             $result = QUI::getDataBase()->fetch([
@@ -994,7 +1067,8 @@ public static function createStripeCustomerFromQuiqqerUser(QUI\Interfaces\Users\
             ],
             'email' => $quiqqerCustomerEmail ?: '',
             'description' => QUI::conf('globals', 'host'),
-            'preferred_locales' => [str_replace('_', '-', $userLocaleList[0])]
+            'preferred_locales' => [str_replace('_', '-', $userLocaleList[0])],
+            'name' => $User->getName()
             // transform strings like "de_DE" to "de-DE"
         ]);
 
diff --git a/src/QUI/ERP/Payments/Stripe/PaymentMethods/SepaDebit.php b/src/QUI/ERP/Payments/Stripe/PaymentMethods/SepaDebit.php
new file mode 100644
index 0000000..52cb75f
--- /dev/null
+++ b/src/QUI/ERP/Payments/Stripe/PaymentMethods/SepaDebit.php
@@ -0,0 +1,179 @@
+<?php
+
+namespace QUI\ERP\Payments\Stripe\PaymentMethods;
+
+use Exception;
+use QUI;
+use QUI\ERP\Accounting\Invoice\Invoice;
+use QUI\ERP\Accounting\Invoice\InvoiceTemporary;
+use QUI\ERP\Accounting\Invoice\InvoiceView;
+use QUI\ERP\Accounting\Payments\Payments;
+use QUI\ERP\Order\AbstractOrder;
+use QUI\ERP\Order\OrderInterface;
+use QUI\ERP\Payments\Stripe\AbstractBasePayment;
+use QUI\ERP\Payments\Stripe\Utils;
+use Stripe\Exception\ApiErrorException;
+use Stripe\PaymentIntent as StripePaymentIntent;
+use Stripe\PaymentMethod;
+use QUI\ERP\Payments\Stripe\Provider;
+
+/**
+ * Stripe payment with SEPA Direct Debit
+ */
+class SepaDebit extends AbstractBasePayment
+{
+    /**
+     * @return string
+     */
+    public function getTitle(): string
+    {
+        return $this->getLocale()->get('quiqqer/payment-stripe', 'payment.SepaDebit.title');
+    }
+
+    /**
+     * @return string
+     */
+    public function getDescription(): string
+    {
+        return $this->getLocale()->get('quiqqer/payment-stripe', 'payment.SepaDebit.description');
+    }
+
+    /**
+     * Get title for frontend
+     *
+     * @return string
+     */
+    public function getFrontendTitle(): string
+    {
+        return $this->getLocale()->get('quiqqer/payment-stripe', 'payment.frontend.SepaDebit.title');
+    }
+
+    /**
+     * Get description for frontend
+     *
+     * @return string
+     */
+    public function getFrontendDescription(): string
+    {
+        return $this->getLocale()->get('quiqqer/payment-stripe', 'payment.frontend.SepaDebit.description');
+    }
+
+    /**
+     * Get title for the Payment step (OrderProcess)
+     *
+     * @return string
+     */
+    public function getPaymentStepTitle(): string
+    {
+        return $this->getLocale()->get('quiqqer/payment-stripe', 'payment.PaymentStep.title.Card');
+    }
+
+    /**
+     * Get description step for the Payment step (OrderProcess)
+     *
+     * @return string
+     */
+    public function getPaymentStepInfo(): string
+    {
+        return $this->getLocale()->get('quiqqer/payment-stripe', 'payment.PaymentStep.info.Card');
+    }
+
+//    /**
+//     * Return the payment icon (the URL path)
+//     * Can be overwritten
+//     *
+//     * @return string
+//     */
+//    public function getIcon(): string
+//    {
+//        return Payments::getInstance()->getHost() .
+//            URL_OPT_DIR .
+//            'quiqqer/payment-stripe/bin/images/Payment_Card.png'; // TODO: richtiges Logo verwenden
+//    }
+
+    /**
+     * Get type string of Stripe PaymentMethod
+     *
+     * @return string
+     */
+    public function getPaymentMethodType(): string
+    {
+        return PaymentMethod::TYPE_SEPA_DEBIT;
+    }
+
+    /**
+     * Return attributes for creating a PaymentIntent
+     *
+     * @param AbstractOrder $Order
+     * @param string $paymentMethodId - Stripe PaymentMethod ID
+     * @return StripePaymentIntent
+     *
+     * @throws ApiErrorException
+     * @throws QUI\Exception
+     */
+    protected function createPaymentIntentForOrder(AbstractOrder $Order, string $paymentMethodId): StripePaymentIntent
+    {
+        return StripePaymentIntent::create([
+            'payment_method' => $paymentMethodId,
+            'amount' => Utils::getCentAmount($Order),
+            'currency' => mb_strtolower($Order->getCurrency()->getCode()),
+//            'confirmation_method' => 'manual',
+            'confirm' => true,
+            'description' => Utils::getPaymentDescriptionForOrder($Order),
+            'statement_descriptor' => Provider::getStatementDescriptor($Order),
+            'automatic_payment_methods' => [
+                'enabled' => true,
+                'allow_redirects' => 'never'
+            ],
+            'metadata' => [
+                'orderUuid' => $Order->getUUID()
+            ],
+            'mandate_data' => [
+                'customer_acceptance' => [
+                    'type' => 'online',
+                    'online' => [
+                        'ip_address' => $_SERVER['REMOTE_ADDR'],
+                        'user_agent' => $_SERVER['HTTP_USER_AGENT'],
+                    ],
+                ],
+            ]
+        ]);
+    }
+
+    /**
+     * Return the extra text for the invoice
+     *
+     * @param Invoice|InvoiceTemporary|InvoiceView $Invoice |InvoiceView $Invoice
+     * @return string
+     */
+    public function getInvoiceInformationText(Invoice | InvoiceTemporary | InvoiceView $Invoice): string
+    {
+        try {
+            return $Invoice->getCustomer()->getLocale()->get(
+                'quiqqer/payment-stripe',
+                'additional_invoice_text.Card'
+            );
+        } catch (Exception $Exception) {
+            QUI\System\Log::writeException($Exception);
+            return '';
+        }
+    }
+
+    public function canBeUsedInOrder(OrderInterface $order): bool
+    {
+        // Currency must be EUR
+        if ($order->getCurrency()->getCode() !== 'EUR') {
+            return false;
+        }
+
+        if ($order->getInvoiceAddress()->getCountry()->isEU()) {
+            return true;
+        }
+
+        // Some Non-EU countries are also part of SEPA
+        return match ($order->getInvoiceAddress()->getCountry()->getCode()) {
+            'CH', 'GB', 'SM', 'VA', 'AD', 'MC', 'IS', 'NO', 'LI' => true,
+            default => false,
+        };
+    }
+}
diff --git a/src/QUI/ERP/Payments/Stripe/Provider.php b/src/QUI/ERP/Payments/Stripe/Provider.php
index d937590..29942e8 100644
--- a/src/QUI/ERP/Payments/Stripe/Provider.php
+++ b/src/QUI/ERP/Payments/Stripe/Provider.php
@@ -6,16 +6,19 @@
 use QUI;
 use QUI\ERP\Accounting\Payments\Api\AbstractPaymentProvider;
 use QUI\ERP\Accounting\Payments\Types\Factory;
-use QUI\ERP\Payments\Stripe\PaymentMethods\Card;
-use Stripe\Stripe as StripeApiClient;
-use QUI\ERP\Payments\Stripe\PaymentMethods\Recurring\Card as CardRecurring;
+use QUI\ERP\Order\OrderInterface;
 use QUI\ERP\Payments\Stripe\PaymentMethods\ApplePay;
+use QUI\ERP\Payments\Stripe\PaymentMethods\Card;
 use QUI\ERP\Payments\Stripe\PaymentMethods\GooglePay;
 use QUI\ERP\Payments\Stripe\PaymentMethods\MicrosoftPay;
 use QUI\ERP\Payments\Stripe\PaymentMethods\Recurring\ApplePay as ApplePayRecurring;
+use QUI\ERP\Payments\Stripe\PaymentMethods\Recurring\Card as CardRecurring;
 use QUI\ERP\Payments\Stripe\PaymentMethods\Recurring\GooglePay as GooglePayRecurring;
 use QUI\ERP\Payments\Stripe\PaymentMethods\Recurring\MicrosoftPay as MicrosoftPayRecurring;
+use QUI\ERP\Payments\Stripe\PaymentMethods\SepaDebit;
 use QUI\System\VhostManager;
+use Stripe\Stripe as StripeApiClient;
+use QUI\ERP\Payments\Stripe\PaymentMethods\Recurring\SepaDebit as SepaDebitRecurring;
 
 use function array_flip;
 use function array_map;
@@ -23,6 +26,7 @@
 use function file_exists;
 use function file_get_contents;
 use function file_put_contents;
+use function mb_strlen;
 
 /**
  * Class Provider
@@ -47,10 +51,22 @@ public function getPaymentTypes(): array
             Card::class,
             GooglePay::class,
             ApplePay::class,
-            MicrosoftPay::class
+            MicrosoftPay::class,
+            SepaDebit::class
         ], self::getRecurringPaymentTypes());
     }
 
+    public static function getNonRecurringPaymentTypes(): array
+    {
+        return [
+            Card::class,
+            GooglePay::class,
+            ApplePay::class,
+            MicrosoftPay::class,
+            SepaDebit::class
+        ];
+    }
+
     /**
      * @return array
      */
@@ -60,7 +76,8 @@ public static function getRecurringPaymentTypes(): array
             CardRecurring::class,
             ApplePayRecurring::class,
             GooglePayRecurring::class,
-            MicrosoftPayRecurring::class
+            MicrosoftPayRecurring::class,
+            SepaDebitRecurring::class
         ];
     }
 
@@ -70,7 +87,7 @@ public static function getRecurringPaymentTypes(): array
      * @param string $setting - Setting name
      * @return bool|string
      */
-    public static function getApiSetting(string $setting): bool|string
+    public static function getApiSetting(string $setting): bool | string
     {
         try {
             $Conf = QUI::getPackage('quiqqer/payment-stripe')->getConfig();
@@ -89,7 +106,7 @@ public static function getApiSetting(string $setting): bool|string
      * @param string $setting - Setting name
      * @return bool|string
      */
-    public static function getGeneralSetting(string $setting): bool|string
+    public static function getGeneralSetting(string $setting): bool | string
     {
         try {
             $Conf = QUI::getPackage('quiqqer/payment-stripe')->getConfig();
@@ -102,12 +119,56 @@ public static function getGeneralSetting(string $setting): bool|string
         return $Conf->get('general', $setting);
     }
 
+    /**
+     * The statement descriptor is shown in the account statement for a Stripe transaction.
+     *
+     * E.g. on the bank statement of a SEPA payment.
+     *
+     * @param OrderInterface $order
+     * @return string|null
+     */
+    public static function getStatementDescriptor(OrderInterface $order): ?string
+    {
+        $descriptor = self::getGeneralSetting('statement_descriptor');
+
+        if (empty($descriptor)) {
+            return null;
+        }
+
+        $descriptor = str_replace(
+            [
+                '{orderId}'
+            ],
+            [
+                $order->getPrefixedNumber()
+            ],
+            $descriptor
+        );
+
+        // Max 22 characters
+        if (mb_strlen($descriptor) <= 22) {
+            return $descriptor;
+        }
+
+        return mb_substr($descriptor, 0, 21) . '*';
+    }
+
+    /**
+     * Should the system admin be notified if a Stripe payment fails?
+     *
+     * @return bool
+     */
+    public static function shouldNotifyOnFailedPayment(): bool
+    {
+        return !empty(self::getGeneralSetting('notify_about_failed_payment'));
+    }
+
     /**
      * Get Stripe public key ("Publishable key")
      *
      * @return bool|string
      */
-    public static function getPublicKey(): bool|string
+    public static function getPublicKey(): bool | string
     {
         if (self::isSandbox()) {
             return self::getApiSetting('sandbox_public_key');
@@ -122,7 +183,7 @@ public static function getPublicKey(): bool|string
      * @param bool $considerSandbox (optional) - Considers sandbox settings when retrieving key [default: true]
      * @return bool|string
      */
-    public static function getClientSecret(bool $considerSandbox = true): bool|string
+    public static function getClientSecret(bool $considerSandbox = true): bool | string
     {
         if ($considerSandbox && self::isSandbox()) {
             return self::getApiSetting('sandbox_client_secret');
diff --git a/src/QUI/ERP/Payments/Stripe/Utils.php b/src/QUI/ERP/Payments/Stripe/Utils.php
index e76ebac..74ec27c 100644
--- a/src/QUI/ERP/Payments/Stripe/Utils.php
+++ b/src/QUI/ERP/Payments/Stripe/Utils.php
@@ -7,9 +7,13 @@
 use QUI\ERP\ErpEntityInterface;
 use QUI\ERP\Exception;
 use QUI\ERP\Order\AbstractOrder;
-use Stripe\PaymentMethod as StripePaymentMethod;
-use Stripe\Exception\ApiErrorException;
+use QUI\ERP\Order\Handler as OrderHandler;
+use QUI\ERP\Order\OrderInterface;
 use QUI\Interfaces\Users\User as UserInterface;
+use Stripe\Dispute;
+use Stripe\Exception\ApiErrorException;
+use Stripe\PaymentIntent;
+use Stripe\PaymentMethod as StripePaymentMethod;
 
 use function mb_strpos;
 use function str_replace;
@@ -31,7 +35,7 @@ class Utils
      * @throws QUI\Exception
      * @throws StripeException
      */
-    public static function getCentAmount(QUI\ERP\ErpEntityInterface $Source): float|int
+    public static function getCentAmount(QUI\ERP\ErpEntityInterface $Source): float | int
     {
         if ($Source instanceof AbstractOrder) {
             $PriceCalculation = $Source->getPriceCalculation();
@@ -143,4 +147,187 @@ public static function getPaymentDescriptionForOrder(AbstractOrder $Order): stri
             return $defaultDescription;
         }
     }
+
+    /**
+     * @param Invoice $invoice
+     * @param string $stripeInvoiceId
+     * @param string $errorMsg
+     * @return void
+     */
+    public static function sendNotificationAboutFailedInvoicePayment(
+        Invoice $invoice,
+        string $stripeInvoiceId,
+        string $errorMsg = ''
+    ): void {
+        try {
+            $locale = QUI::getSystemLocale();
+            $mailer = QUI::getMailManager()->getMailer();
+            $mailer->addRecipient(QUI::conf('mail', 'admin_mail'));
+            $mailer->setSubject(
+                $locale->get(
+                    'quiqqer/payment-stripe',
+                    'mail.payment_failed.invoice.subject',
+                    [
+                        'invoiceNo' => $invoice->getPrefixedNumber()
+                    ]
+                )
+            );
+
+            $contractNo = '-';
+            $contractData = $invoice->getData('contract');
+
+            if (!empty($contractData['prefixedNumber'])) {
+                $contractNo = $contractData['prefixedNumber'];
+            }
+
+            $mailer->setBody(
+                $locale->get(
+                    'quiqqer/payment-stripe',
+                    'mail.payment_failed.invoice.body',
+                    [
+                        'invoiceNo' => $invoice->getPrefixedNumber(),
+                        'invoiceAmount' => $invoice->getPriceCalculation()->getSum()->formatted($locale),
+                        'contractNo' => $contractNo,
+                        'stripeInvoiceId' => $stripeInvoiceId,
+                        'stripeInvoiceUrl' => self::getStripeDashboardSearchUrl($stripeInvoiceId),
+                        'errorMsg' => $errorMsg ?: '-'
+                    ]
+                )
+            );
+            $mailer->send();
+        } catch (\Exception $exception) {
+            QUI\System\Log::writeException($exception);
+        }
+    }
+
+    /**
+     * @param AbstractOrder $order
+     * @param string $errorMsg
+     * @return void
+     */
+    public static function sendNotificationAboutFailedOrderPayment(AbstractOrder $order, string $errorMsg = ''): void
+    {
+        try {
+            $locale = QUI::getSystemLocale();
+            $mailer = QUI::getMailManager()->getMailer();
+            $mailer->addRecipient(QUI::conf('mail', 'admin_mail'));
+            $mailer->setSubject(
+                $locale->get(
+                    'quiqqer/payment-stripe',
+                    'mail.payment_failed.order.subject',
+                    [
+                        'orderId' => $order->getPrefixedNumber()
+                    ]
+                )
+            );
+
+            $paymentIntentId = $order->getPaymentDataEntry(AbstractBasePayment::ATTR_STRIPE_PAYMENT_INTENT_ID);
+            $paymentIntentUrl = 'https://dashboard.stripe.com/';
+
+            if (Provider::isSandbox()) {
+                $paymentIntentUrl .= 'test/';
+            }
+
+            $paymentIntentUrl .= 'payments/' . $paymentIntentId;
+
+            $mailer->setBody(
+                $locale->get(
+                    'quiqqer/payment-stripe',
+                    'mail.payment_failed.order.body',
+                    [
+                        'orderId' => $order->getPrefixedNumber(),
+                        'orderAmount' => $order->getPriceCalculation()->getSum()->formatted($locale),
+                        'stripePaymentIntentId' => $order->getPaymentDataEntry(
+                            AbstractBasePayment::ATTR_STRIPE_PAYMENT_INTENT_ID
+                        ),
+                        'paymentIntentUrl' => $paymentIntentUrl,
+                        'errorMsg' => $errorMsg ?: '-'
+                    ]
+                )
+            );
+            $mailer->send();
+        } catch (\Exception $exception) {
+            QUI\System\Log::writeException($exception);
+        }
+    }
+
+    /**
+     * @param Dispute $dispute
+     * @param OrderInterface|null $order
+     * @return void
+     */
+    public static function sendNotificationAboutDisputedTransaction(
+        Dispute $dispute,
+        OrderInterface $order = null
+    ): void {
+        try {
+            $locale = QUI::getSystemLocale();
+            $mailer = QUI::getMailManager()->getMailer();
+            $mailer->addRecipient(QUI::conf('mail', 'admin_mail'));
+            $mailer->setSubject(
+                $locale->get(
+                    'quiqqer/payment-stripe',
+                    'mail.disputed_transaction.subject'
+                )
+            );
+
+            $mailer->setBody(
+                $locale->get(
+                    'quiqqer/payment-stripe',
+                    'mail.disputed_transaction.body',
+                    [
+                        'amount' => $dispute->amount / 100,
+                        'currency' => $dispute->currency,
+                        'chargeUrl' => self::getStripeDashboardSearchUrl($dispute->charge),
+                        'reason' => $dispute->reason,
+                        'orderId' => $order ? $order->getPrefixedNumber() : '-'
+                    ]
+                )
+            );
+            $mailer->send();
+        } catch (\Exception $exception) {
+            QUI\System\Log::writeException($exception);
+        }
+    }
+
+    /**
+     * @param PaymentIntent $paymentIntent
+     * @return AbstractOrder|null
+     */
+    public static function getOrderByPaymentIntent(PaymentIntent $paymentIntent): ?AbstractOrder
+    {
+        $metaData = $paymentIntent->metadata->toArray();
+
+        if (empty($metaData['orderUuid'])) {
+            return null;
+        }
+
+        $orderUuid = $metaData['orderUuid'];
+
+        try {
+            return OrderHandler::getInstance()->get($orderUuid);
+        } catch (\Exception $exception) {
+            QUI\System\Log::writeException($exception);
+        }
+
+        return null;
+    }
+
+    /**
+     * Get direct link to Stripe dashboard with an entity id (invoice, dispute, payment intent etc.).
+     *
+     * @param string $entityId
+     * @return string
+     */
+    public static function getStripeDashboardSearchUrl(string $entityId): string
+    {
+        $url = 'https://dashboard.stripe.com/';
+
+        if (Provider::isSandbox()) {
+            $url .= 'test/';
+        }
+
+        $url .= 'search?query=' . $entityId;
+        return $url;
+    }
 }
diff --git a/src/QUI/ERP/Payments/Stripe/WebhookHandler.php b/src/QUI/ERP/Payments/Stripe/WebhookHandler.php
index 0c709c1..31a57d1 100644
--- a/src/QUI/ERP/Payments/Stripe/WebhookHandler.php
+++ b/src/QUI/ERP/Payments/Stripe/WebhookHandler.php
@@ -4,11 +4,14 @@
 
 use Exception;
 use QUI;
+use Stripe\Dispute;
 use Stripe\Event as StripeEvent;
+use Stripe\PaymentIntent;
 use Stripe\Refund as StripeRefund;
 use Stripe\Subscription as StripeSubscription;
 use QUI\ERP\Accounting\Payments\Transactions\Handler as TransactionsHandler;
 use QUI\ERP\Payments\Stripe\PaymentMethods\Recurring\Subscriptions;
+use QUI\ERP\Order\Handler as OrderHandler;
 
 /**
  * Class WebhookHandler
@@ -41,6 +44,16 @@ public static function handleEvent(StripeEvent $Event): void
             case StripeEvent::CUSTOMER_SUBSCRIPTION_UPDATED:
                 self::onCustomerSubscriptionUpdated($Event);
                 break;
+
+            case StripeEvent::PAYMENT_INTENT_SUCCEEDED:
+            case StripeEvent::PAYMENT_INTENT_CANCELED:
+                // currently checked synchronously via cron
+//                self::onPaymentIntentEvent($Event);
+                break;
+
+            case StripeEvent::CHARGE_DISPUTE_CREATED:
+                self::onChargeDisputeCreated($Event);
+                break;
         }
     }
 
@@ -124,4 +137,57 @@ protected static function onChargeRefunded(StripeEvent $Event): void
             QUI\System\Log::writeException($Exception);
         }
     }
+
+    /**
+     * Stripe webhook: customer.subscription.deleted
+     *
+     * @param StripeEvent $event
+     * @return void
+     */
+    protected static function onPaymentIntentEvent(StripeEvent $event): void
+    {
+        $paymentIntent = $event->data->object;
+
+        if (!($paymentIntent instanceof PaymentIntent)) {
+            return;
+        }
+
+        $metaData = $paymentIntent->metadata->toArray();
+
+        if (empty($metaData['orderUuid'])) {
+            return;
+        }
+
+        try {
+            $order = OrderHandler::getInstance()->get($metaData['orderUuid']);
+        } catch (\Exception $exception) {
+            QUI\System\Log::writeDebugException($exception);
+            return;
+        }
+
+        $orderProcessingService = new OrderProcessingService($order);
+        $orderProcessingService->processPaymentIntent($paymentIntent);
+    }
+
+    /**
+     * Stripe webhook: customer.subscription.deleted
+     *
+     * @param StripeEvent $event
+     * @return void
+     */
+    protected static function onChargeDisputeCreated(StripeEvent $event): void
+    {
+        $dispute = $event->data->object;
+
+        if (!($dispute instanceof Dispute)) {
+            return;
+        }
+
+        try {
+            $chargeDisputeService = new ChargeDisputeService();
+            $chargeDisputeService->processDispute($dispute);
+        } catch (\Exception $exception) {
+            QUI\System\Log::writeException($exception);
+        }
+    }
 }
-- 
GitLab